From c75fa7318cd75df4ec833e0ca72e0474e02975bc Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:18:10 +0100 Subject: [PATCH 1/2] remove abandonware from the comparison. --- .../typescript/oss-comparison-ts.mdx | 106 +++++------------- 1 file changed, 25 insertions(+), 81 deletions(-) diff --git a/docs/languages/typescript/oss-comparison-ts.mdx b/docs/languages/typescript/oss-comparison-ts.mdx index dfbf603d..89ec4b89 100644 --- a/docs/languages/typescript/oss-comparison-ts.mdx +++ b/docs/languages/typescript/oss-comparison-ts.mdx @@ -1,6 +1,6 @@ --- -title: "Comparison guide: OpenAPI/Swagger TypeScript client generation" -description: "Comparing the new Speakeasy TypeScript SDK generator with the most popular open source OpenAPI TypeScript generators" +title: "Comparing OpenAPI TypeScript SDK Generators" +description: "Comparing the new Speakeasy TypeScript SDK generator with the most popular open-source OpenAPI TypeScript generators" keywords: [ api, @@ -26,23 +26,22 @@ date: 2024-05-08 import { Table } from "@/mdx/components"; -# Comparison guide: OpenAPI/Swagger TypeScript client generation +# Comparing OpenAPI TypeScript SDK Generators -At Speakeasy, [idiomatic SDKs](/docs/languages/philosophy) are created in a variety of languages. The generators follow principles that ensure SDKs offer the best developer experience, allowing focus on building APIs while developer-users focus on delighting their users. +At Speakeasy, [idiomatic SDKs](/docs/languages/philosophy) are created in a variety of languages, with generators that follow principles ensuring SDKs the best developer experience. The goal is to let developers focus on building great APIs and applications, without being distracted by hand-rolling custom SDKs just to get basic functionality. In this post, we'll compare TypeScript SDKs managed by Speakeasy to those generated by open-source generators. ## The TypeScript SDK generator landscape -We'll compare the Speakeasy SDK generator to two generators from the OpenAPI Generators project and two additional popular open-source generators. +We'll compare the Speakeasy SDK generator to some popular popular open-source generators. Our evaluation includes: 1. The [TypeScript Fetch](https://openapi-generator.tech/docs/generators/typescript-fetch/) generator from OpenAPI Generators. 2. The [TypeScript Node](https://openapi-generator.tech/docs/generators/typescript-node/) generator from OpenAPI Generators. -3. [Oazapfts](https://github.com/oazapfts/oazapfts), an open-source generator with almost 400 stars on GitHub. -4. [OpenAPI TypeScript Codegen](https://github.com/ferdikoomen/openapi-typescript-codegen), a popular open-source generator with 1,700 stars on GitHub. -5. The [Speakeasy SDK generator](/docs/speakeasy-reference/cli/getting-started). +3. [Oazapfts](https://github.com/oazapfts/oazapfts), an open-source generator with over 500 stars on GitHub. +4. The [Speakeasy SDK generator](/docs/speakeasy-reference/cli/getting-started). Here's the summary of how the different generators compare: @@ -54,7 +53,6 @@ Here's the summary of how the different generators compare: tsFetch: "✅ Basic", tsNode: "✅ Basic", oazapfts: "❌", - openapiTs: "❌", }, { name: "Documentation generation", @@ -62,7 +60,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "Union types/polymorphism", @@ -70,7 +67,6 @@ Here's the summary of how the different generators compare: tsFetch: "✅", tsNode: "❌", oazapfts: "✅ With discriminator", - openapiTs: "✅ Without discriminator", }, { name: "Browser support", @@ -78,7 +74,6 @@ Here's the summary of how the different generators compare: tsFetch: "✅", tsNode: "❌", oazapfts: "✅", - openapiTs: "✅", }, { name: "Tree-shaking support", @@ -86,7 +81,6 @@ Here's the summary of how the different generators compare: tsFetch: "⚠️ Limited", tsNode: "⚠️ Limited", oazapfts: "⚠️ Limited", - openapiTs: "⚠️ Limited", }, { name: "OAuth 2.0", @@ -94,7 +88,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "Retries", @@ -102,7 +95,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "Pagination", @@ -110,7 +102,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "React Hooks generation", @@ -118,7 +109,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "Data streaming", @@ -126,7 +116,6 @@ Here's the summary of how the different generators compare: tsFetch: "✅", tsNode: "✅", oazapfts: "✅", - openapiTs: "✅", }, { name: "Node.js support", @@ -134,7 +123,6 @@ Here's the summary of how the different generators compare: tsFetch: "✅", tsNode: "✅", oazapfts: "✅", - openapiTs: "✅", }, { name: "Deno support", @@ -142,7 +130,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "Bun support", @@ -150,7 +137,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "React Native support", @@ -158,7 +144,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "Package publishing", @@ -166,7 +151,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, { name: "CI/CD integration", @@ -174,7 +158,6 @@ Here's the summary of how the different generators compare: tsFetch: "❌", tsNode: "❌", oazapfts: "❌", - openapiTs: "❌", }, ]} columns={[ @@ -183,7 +166,6 @@ Here's the summary of how the different generators compare: { key: "tsFetch", header: "TypeScript Fetch" }, { key: "tsNode", header: "TypeScript Node" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "openapiTs", header: "OpenAPI TypeScript Codegen" }, ]} /> @@ -205,34 +187,29 @@ Installing `openapi-generator` using Homebrew installs `openjdk@11` and its nume brew install openapi-generator ``` -To install oazapfts and openapi-typescript-codegen, add them to an npm package as dependencies: +To install oazapfts, add it to an NPM package a dependency: ```bash # Install oazapfts as a dependency npm install oazapfts --save - -# Install openapi-typescript-codegen and save it as a devDependency -npm install openapi-typescript-codegen --save-dev ``` -## Downloading the Swagger Petstore specification - -Before we run our generators, we'll need an OpenAPI specification to generate a TypeScript SDK for. The standard specification for testing OpenAPI SDK generators and Swagger UI generators is the [Swagger Petstore](https://petstore3.swagger.io/). +Before we run our generators, we'll need an OpenAPI document to feed in. A common OpenAPI document used for testing OpenAPI SDK generators is the [Swagger Petstore](https://petstore3.swagger.io/). -We'll download the YAML specification at [https://petstore3.swagger.io/api/v3/openapi.yaml](https://petstore3.swagger.io/api/v3/openapi.yaml) to our working directory and name it `petstore.yaml`: +Start by downloading the YAML from to the working directory. ```bash -curl https://petstore3.swagger.io/api/v3/openapi.yaml --output petstore.yaml +wget https://petstore3.swagger.io/api/v3/openapi.yaml ``` -## Validating the spec +## Document validation -Both the OpenAPI Generator and Speakeasy CLI can validate an OpenAPI spec. Oazapfts and OpenAPI TypeScript Codegen don't offer validation, so if we were to use them at scale, we would need a separate validation step. +Both the OpenAPI Generator and Speakeasy CLI can validate an OpenAPI document to make sure it's valid and well-formed. Oazapfts doesn't offer document validation, so a separate validation step is needed to use it at scale. -To validate `petstore.yaml` using OpenAPI Generator, run the following in the terminal: +To validate `openapi.yaml` using OpenAPI Generator, run the following in the terminal: ```bash -openapi-generator validate -i petstore.yaml +openapi-generator validate -i openapi.yaml ``` The OpenAPI Generator returns two warnings: @@ -250,7 +227,7 @@ Warnings: We'll validate the spec with Speakeasy by running the following in the terminal: ```bash -speakeasy validate openapi -s petstore.yaml +speakeasy validate openapi -s openapi.yaml ``` The Speakeasy validator returns ten warnings, seven hints that some methods don't specify return values, and three unused components. Each warning includes a detailed, structured error with line numbers to help us fix validation errors. @@ -260,10 +237,10 @@ Since both validators validated the spec with only warnings, we can assume that The Speakeasy validator includes an option to get hints on how to improve our schema. Use the `--output-hints` argument to activate this feature: ```bash -speakeasy validate openapi --output-hints -s petstore.yaml +speakeasy validate openapi --output-hints -s openapi.yaml ``` -This provides a detailed list of hints, all of which would improve our eventual SDK users' experience. +This provides a detailed list of hints, all of which would improve SDK users' experience. Here's how the generators' validation features compare: @@ -274,29 +251,25 @@ Here's how the generators' validation features compare: speakeasy: "✅", openapiGen: "✅", oazapfts: "❌", - codegen: "❌", }, { name: "Shows line numbers", speakeasy: "✅", openapiGen: "❌", oazapfts: "❌", - codegen: "❌", }, { name: "Schema hints", speakeasy: "✅", openapiGen: "❌", oazapfts: "❌", - codegen: "❌", }, ]} columns={[ { key: "name", header: "" }, { key: "speakeasy", header: "Speakeasy" }, - { key: "openapiGen", header: "openapi-gen" }, + { key: "openapiGen", header: "OpenAPI Generator" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "codegen", header: "Codegen" }, ]} /> @@ -352,14 +325,14 @@ We'll generate an SDK for each by running the following in the terminal: ```bash # Generate Petstore SDK using typescript-fetch generator openapi-generator generate \ - --input-spec petstore.yaml \ + --input-spec openapi.yaml \ --generator-name typescript-fetch \ --output ./petstore-sdk-typescript-fetch \ --additional-properties=npmName=petstore-sdk-typescript-fetch # Generate Petstore SDK using typescript-node generator openapi-generator generate \ - --input-spec petstore.yaml \ + --input-spec openapi.yaml \ --generator-name typescript-node \ --output ./petstore-sdk-typescript-node \ --additional-properties=npmName=petstore-sdk-typescript-node @@ -374,7 +347,7 @@ To run oazapfts, we'll either need to run it from the local JavaScript project b Navigate to the local JavaScript project we created for `petstore-sdk-oazapfts` and run the following: ```bash -$(npm bin)/oazapfts ../petstore.yaml index.ts +$(npm bin)/oazapfts ../openapi.yaml index.ts ``` Oazapfts runs without any output and generates a single TypeScript file, `index.ts`. Remember that we had to install `oazapfts` as a runtime dependency. Let's see what gets called from the dependency: @@ -402,27 +375,11 @@ This is an excerpt from the oazapfts `package.json` file: Some of these dependencies clearly relate to the generator itself. For example, we can assume that no SDK client would need access to `swagger-parser` at runtime. -### Generating an SDK with OpenAPI TypeScript Codegen - -As with oazapfts, we'll need to run the OpenAPI TypeScript Codegen CLI from our npm binaries location, where it is aliased as `openapi`. - -Navigate to the `petstore-sdk-otc` JavaScript project and run: - -```bash -$(npm bin)/openapi \ - -i ../petstore.yaml - -o src/ -``` - -OpenAPI TypeScript Codegen uses the fetch API for requests by default, so it's aimed at SDKs used in the browser. However, it can be configured to use Axios. We tried using Axios and noted that OpenAPI TypeScript Codegen does not create an npm package with dependencies, so we had to manually install a version of Axios. - -By contrast, Speakeasy manages dependencies on behalf of the developer when generating an SDK, eliminating the need to guess which version of a dependency to install. - ## Polymorphism The Petstore schema does not include examples of polymorphism in OpenAPI, so we'll add two new schemas for `Dog` and `Cat`, and use them as input and output in the `updatePet` operation. -Add the following to the component schemas in `petstore.yaml`: +Add the following to the component schemas in `openapi.yaml`: ```yaml components: @@ -638,7 +595,6 @@ Here's a summary of how each generator handles OpenAPI polymorphism: node: "❌", fetch: "✅", oazapfts: "✅", - codegen: "✅", }, { name: "Uses discriminator", @@ -646,7 +602,6 @@ Here's a summary of how each generator handles OpenAPI polymorphism: node: "❌", fetch: "✅", oazapfts: "✅", - codegen: "❌", }, ]} columns={[ @@ -655,7 +610,6 @@ Here's a summary of how each generator handles OpenAPI polymorphism: { key: "node", header: "Node" }, { key: "fetch", header: "Fetch" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "codegen", header: "Codegen" }, ]} /> @@ -667,7 +621,7 @@ This provides a straightforward developer experience for error handling. To enable this feature, we use the Speakeasy `x-speakeasy-retries` extension in the OpenAPI spec. We'll update the OpenAPI spec to add retries to the `addPet` operation as a test. -Edit `petstore.yaml` and add the following to the `addPet` operation: +Edit `openapi.yaml` and add the following to the `addPet` operation: ```yaml x-speakeasy-retries: @@ -751,7 +705,6 @@ None of the other generators generate React Hooks for their SDKs. { key: "node", header: "Node" }, { key: "fetch", header: "Fetch" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "codegen", header: "Codegen" }, ]} /> @@ -824,7 +777,6 @@ None of the other generators include pagination as a feature. node: "❌", fetch: "❌", oazapfts: "❌", - codegen: "❌", }, ]} columns={[ @@ -833,7 +785,6 @@ None of the other generators include pagination as a feature. { key: "node", header: "Node" }, { key: "fetch", header: "Fetch" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "codegen", header: "Codegen" }, ]} /> @@ -881,7 +832,6 @@ None of the other generators include auto-pagination as a feature. node: "❌", fetch: "❌", oazapfts: "❌", - codegen: "❌", }, ]} columns={[ @@ -890,7 +840,6 @@ None of the other generators include auto-pagination as a feature. { key: "node", header: "Node" }, { key: "fetch", header: "Fetch" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "codegen", header: "Codegen" }, ]} /> @@ -906,7 +855,6 @@ All the generators in our comparison generate SDKs that use the `Fetch` API, whi node: "✅", fetch: "✅", oazapfts: "✅", - codegen: "✅", }, ]} columns={[ @@ -915,7 +863,6 @@ All the generators in our comparison generate SDKs that use the `Fetch` API, whi { key: "node", header: "Node" }, { key: "fetch", header: "Fetch" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "codegen", header: "Codegen" }, ]} /> @@ -935,7 +882,6 @@ Here's how the generators add documentation: node: "❌", fetch: "❌", oazapfts: "❌", - codegen: "❌", }, { name: "Adds usage examples", @@ -943,7 +889,6 @@ Here's how the generators add documentation: node: "❌", fetch: "❌", oazapfts: "❌", - codegen: "❌", }, ]} columns={[ @@ -952,7 +897,6 @@ Here's how the generators add documentation: { key: "node", header: "Node" }, { key: "fetch", header: "Fetch" }, { key: "oazapfts", header: "Oazapfts" }, - { key: "codegen", header: "Codegen" }, ]} /> @@ -960,7 +904,7 @@ Speakeasy generates a `README.md` at the root of the SDK, [which you can customi The Speakeasy SDK also includes working usage examples for all operations, complete with imports and appropriately formatted string examples. For instance, if a type is formatted as `email` in our OpenAPI spec, Speakeasy generates usage examples with strings that look like email addresses. Types formatted as `uri` will generate examples that look like URLs. This makes example code clear and scannable. -Here's the usage example managed by Speakeasy after we update `petstore.yaml` to format the string items in `photoUrls` as `uri`: +Here's the usage example managed by Speakeasy after we update `openapi.yaml` to format the string items in `photoUrls` as `uri`: ```typescript docs/sdks/pet/README.md mark=16:18 import { SDK } from "openapi"; From fce2f0fb14458b32a7c34996bb310c5d451fe41e Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Sun, 12 Oct 2025 16:14:11 +0100 Subject: [PATCH 2/2] docs: complete typescript comparison on streaming and docs --- .../assets/openapi-generator-ts-errors.png | Bin 0 -> 114760 bytes .../typescript/oss-comparison-ts.mdx | 1138 +++++++++++------ 2 files changed, 743 insertions(+), 395 deletions(-) create mode 100644 docs/languages/typescript/assets/openapi-generator-ts-errors.png diff --git a/docs/languages/typescript/assets/openapi-generator-ts-errors.png b/docs/languages/typescript/assets/openapi-generator-ts-errors.png new file mode 100644 index 0000000000000000000000000000000000000000..5fbad8e07686a5361a3913b33c556f6d9df5b494 GIT binary patch literal 114760 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 DhApJxxPU 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

Rj+b0XH#?;mba+tP zRQ`ie!MoFM6LLD$)gEWDH>O!$^D}xrCR?5e2jY#Zv8+b0 zvYsw>-gZ6;?i3fj#O1_B`r&zg39LYm^0{;tC3^pAR4~P0R4d1a9_}*IrJ5g=SxBSJ zHn?f8ByLZ)eB&)FtQ;D&vnE7B0J|>-cD!mF%Q}L$+TfsWW`yevrnw6-;k42qSn2l& znA@uO+}h^4?HFNb*;ue8rU9~OSB-HxmyZPAXBt2tP%*_v)u7YVjq ziSG2h!@J6H%3YX97@yTYlgCw`mH zbgNf1;Vl*>#^<2f6@+Q1W^jXU=5n@UjB@@#TD>ojXz^jcBaQtV52K0-4f%od^Di_j_tHFB}9M1RzR7 zK) zB7U6;$koLA8TN+R$-ur!O#i`_p*#HWpu;a(EENIfw*G8U$a2xLVAZ(PPMnP^Xt~rt z|0`ht32m{%>DPLukloM=>BNyn*+gc&WK~dis~$7c9|vL9CL7c*9I$rC6omd$xSR6$ z^Sm^DY+3}V+CRKYa8)9jN*9YiDB2<$YMJs(GiXSJ2=k;+YB=P9|2^TRs#jE2+A6@KaYmpRL$A6KOpX0!qQ%j6_ z<$Aq_|9gldN;#Z?(e!TZ>oY>B6;K0yDiB^wDo5Y;|EV;N!WM_ou)AxCi0GS)+-KKu zf4;%Af&T?SoK%=s+;-|!c()6ZGV8nCmWYpo+uL>3?q$IQT2u8;$Ck>0*p7R#`YG>R0T7n+z2{<0S=T%5h0JHQtP#x_r_BcpL~1$Cd~ zdu$6Op}b!M2UA1HZISB{)KjB*x3|-(>fly!a13EEgI!krjfSl|9}1&+;eNSY{2360 z)ZllEV$01V2UA~Atl|Gjo#auWU21EhA{~hB&-2oPoKK*OZ3b2APuZWUR>7BMdh8IL z4%&1+#b}rfZFS&vB5;0ooxf0X+H|rh9eeA3-B@T`uJN2yt>v~igT2K$HS+=BSt6_H z3O(a3eD+zj{v}$;RJAJ2J_=GEx<^Ta^Jq>wNnNj?BR{m8pt#UYfRVFbpvo`8q`{v~ zQL!>fz-7|zK=43$i8DcZZ_H3;YK#qM%zz6TBW;oRMr_GrJCKtuxIjOYo{=@@ilN?n zMTVdACVW8-u4qD_^Cx%Kgi0KzaO4!GLx_cSRKFQGK}I^RA5)lsL>r#0Ql5$@WyNy1K9Eo!6v7s0yQ60YFGRSOOSpE$JC$2Tx=j0 znL8WY!#2P9cy}_$4q3}TZn`j87>oQ6aolmoUDm$VSEdVcc)T&yTqCk+6TPB)!pMV% zoP{^Bt|q*FGA9nGK@M7Z_^=4G^vV#dwfkl<{f(SQD&LzE7dBsZe8+Tw~B+}9fvJ+8`p$9kzOX`bWSZMs^B#= z7qA3O7=WAz5DU{ZrpBko^rqn9$( zDg6KVdh58TzVKZXP(Wd5&=CfZ?ifNxQbM}BYiMbZM!I2Wq`N_+yK4aHl$4MzNonu) z`#a}+&i&(F{$TdZ>^*C(z1CiDJ@509cEBWlpfl;z0xBiS%RrAkSzg{jFzX*Y6nDd(+#Y>dm#ssg( zOxcKJWd$i3iGy`$u=goa#C)&Fn|P@asnc4vUnbkDaGcAyVC3&H8n|m*mER38Eta+C z#}W1%IF+hfK1Je=I#@)(IbZL0!gSFz!#Sbh7?NoUE;J4Xa+`4u}yt=FTkL?Ekfi_AE<{xsHq z@6Q!=jsVbp(b*NkoP}0LX7KTK&jLM#vd0%W0LpGi#U+6!mG^P%i)@@45k2CQBPjc) z`?=W?CqCk34g8LlE+G;whiS89x<#nBxAi64Om~d${-6?pFV$%1WoW7NBuGFRMRmj+>Nq* z{tWMj_x-c6o>jvg5+-{n^6@n8Lby#ud#owl$X+7B+ppJHvQ!g8N8;*Ae-6=2j>?&xkU#)ef0zC^*LPM2j-Na)Tjm5>YilE@9AKqKrD!6jeHevi~ z`VcZ3NXm~f*E?x!8kK+Ks$unc{9XYol`5R=R6elNsJ7Reo3R%iAob`AW$}VxqhocX+MKi!#W&#Fn*Mf>10-6F)mnqa1C0oKorLJH>cIv$gw=}6 z%8VwY-!ue{R_c8nofM6UWV{HW9&57WVx14=MuY>VxG!LeqhO^;!Ey(e#bl2ex%H3% z%%o=6VWs6ic$!Pp#`xv*V-r}Y&p-r_O@d$jE6rHdZ3!o2wUaS$d%U&9Eb%!2UDoXnjb2YA->mQ%%MCWk*I--WvERNCjbfb%=ykz*k1*YVM4j zaCIR>>k3hi8K)fI`|pgflr5TwtXo>jCbToK2ez^f2mFoEhmp~$a~{%xytED=PP$oc zi#jL_6WQeQt)=EQ8$ubu77KAz3HeBe`{e*OKy#?~yVcP_PDaE6t$|rz|EmJVLE`Q8 z=AVkwACCFUG(gVAyP`;Tp+A78h{TgJ=c4tGK)^XaHJ?}N#z&OAWk*ymJR8{<#}oF} z#Tle9<8M1W!@ijggv(V?hz_-#4LgT)(ZXJ8hz9+oscy zFwShO`L5l}qW%4$K@FES)f?V^OhLYj%%qfdi_A~-psk{GJHRHMAT zEfFcb71@aK80_5TKFO~AGI_3HJL-;zMFV&stZhGn9Ux+s;6|CUk~FJ&tZ5d=CIbz{ zP%oX5fq-)hqoRr!S@HuF%?u;O5nzl<=k~6xr|cHf4AwZ!65inOzM020F{j(PHBt$x zial#A(BzSo3(Qo9h?SuG{KTQW(4-T?L{?E9IDbV6#YKd}kyVPKl~QRRbcDSeqS(EZ zJB10t&66nj0Z_(9dJW3?8kqbUG@EYJcCJM$tPeKOPLwZf!Q-k#Ay2C}V9NDGCKA>w z7=R_8Eu@eP+Yo=pa4P;b0Q@#M2%6>~r+`Gn@u#1CGcMdhLMwIY(mAuOzA+B=;vxMo z25W3s2l}C$&nnF29GpXfeec`I-luG_jE@a5~(>Dvy{+G7rN zpD!KO>WXcrpA>@5KHMpSzuTr)j)KjN7T-8Reri=aVIvve;(Pd(eUu3-V@s+IiG*`S zs+myfUz0N;3#B@gfw(PNVk|k6DTZa<4ZZDqwM3B~=%iw(QM+q6ZAM@oZOd3kjX#_i zHa#wrh_2BMdzj?W+dDY4i*t;MH#AbWGa(KplDZNtcg+B5_Z=DA^SuL=% z-z8YbIz)*#ZiOc0v&-rUTG-uXg}Nti@gN+1oJ2U=MDYUs1cwmKPN}A5+UtKHu{yxR4uEoAkpZ zL?pt{)75B~UpYYXwV17fCxaZKgsUorzt{J=xk%Wqd&Ud3A*4dn3l_v7$^YF*NqX4` zpFj(xD0sZAEU5>AHmP6zQznUFTwoFLjP(&Zy>X>7j3%Zn7k1Mu{TB2#G0xdEW1_?Zm_(1NG`%o8!hdLSqIkGx z>`>+S$~aYTfh& zOTE3%nP7eAGS34X~(?ZYsvBS?esnhITAc4dnI;GXt92Qf>?aR75JSxg; zwc|HU+Bg)5@XvhWv&NxgvHh@t$*8@}`U1yHpIE}%w1Ma)hU$p=5@7`W3rrcMcppeU z3GsLH@m#YN&ai*V@mONMKPJ*rTH>1x`BqET2Y`dGML+M=@dkL*mk*z7Svm@yCu||` zWFHrDa1_R!(LI|h2otMxen$&MS1>e=SH;ISgM1A9j7tXT^Z$ zx(UAMBf`@qQPxLOM?$=sjO4X%-R8>`7>C6QE-`VnqT8TN$FIj&SqF0R?{LfSV z%-EL+K4lZ(OI%Vg`rEy(!MUp3nT1)x&6LFBQ&DDb2YqX8N8|eqQJnVCxkg=;*(NAK zy{+Tquft7nM{w=^c~HotuIlYjVVI-b3kMLjG>&?hdJ8~TL|vwn%SJD4Be&kQ0dDKt zvtWkg^9tKQ_fokUzoIUD0c>EU@mD8)%*><5$FXK_dbbLuXARIbobiJGstJD^njJxU z>TxgW|CK*RtA!|+9IpGrgVgjOGzo%csBRVn^|Y2RzlA%>iLASv65sGWXZL=*k<{qN zd>hF<5p*#-=ZDg6j9}y@BGlInoL1m>)LaAW58*MXS^rj55bO?wvx+rv8MlUZ?R?+j zo$LiBW>Z&2qy1OZ%NLoC-tUJ$$xqS2+3%O{XNr?Tg%s8=o=^x~m(Ba$TndwjUy*P4 z!vS{^U_TUIQ;iy?Y)T-Z5lbdR#f2>)Sj{`)eW-%%nWbZzH+~q;k>bLcW0mGh4!S>I zY=u&H09u2$VLnNXzYBNU%Pn>0pN6MclceEEQLVQoD|xY4b*voUdk2Enn(nu2m7Y-v zS3MaQLWra+*M@H-5fjUqk_TLOm2sH%5bNiGk(@eUEF<^UGMkhS6Gn}zr4ii!+dSUAoD zsl_}arBqXF0EJDu3ZBzcd#jZmFVg>71h6`y94b z4x~$ZzjC8hf0a2o{&n1&Z$=>Le`ZZd%1Ai8+jCT|cQk8yS!e!-9=8&pzM_)pD^g># zfKPo70FVaprTq@MJ57*jC>J}z1FCd{NzD_n~M;Ke}NW4i}rkE=1YIg4%3;l0~PB*~;wGl~1jf7?)+^&z(V~pRG z*(K4Hlx#j}O3ZFGymN=qKgj1g{=o`(L4jm^(G&@H*Y#V-^fhNLb&Y}#L{qW}QGcd7 zHJ=J-Yf@<_D%!qPle~D=AqF;@_v_~n{f*%eC27hJESkK5-NWPd6Wk;((HqgkdrZNz zL9w2+SI6}XLk0PpDW7&a6Mu97A(Pd|i%^uroBdWpSA$E>YvTC}TcsU`|D0M+uJQd| zvrA9tncZoWqa{9N)fmj2IdQD@$Z;3}0$aen^-y4{i4vVnAtfzOddo-PJu|Cmv(OTk zHQja8{F+z~zE^`@M=a>*2tq-R`Py;BV?f%3Yutm;CnCOKdU(aP1z&^c{W&PxEzRp(tlOasKvXzZ4@kHpom8CY` zM}ibo`zhHmapm2oiP4U7PD3DOT10L;CNk<3w`hWR9U_AlfT%iPdOQKr6wc+#-5O(` z?>UwzuYuOVHtZFqiJT%_TRZYq=Hj9_8Ih8HJsY#rvE*#iL z(DIQ_APeoh-c7Iy%?(Yno=bI`&8ovkD2m7ZX{a9KlRm0^ZUS`yNtes5FO&)9WOpy8 z@Gu)%HWBGoYcGEfk`nDg1rU>C!EoA&PZf>;af%KkKkS{;rpydOcH7U{*)nqi9Mil= zcPJ3&Dm>`3NWAb2OBn#nW+2)%Y(dLY<#aifb(0xhg%Jr_7(^l1c5U>3^2{^utB%e= z{s!2>8DBalf>_jn(qk9JZkOSxpL`;s(~d5N!aG2eK6-JkF_rlxvW^}G{WNC#{NrJM zxp?T?3WKH+vbCgzJ(oYOVU$cNc$x-xcz#J{-(Ok@KV>i*jcV|B1HN&8O)6lT zeUTc+R!B`pyAKTqbJ=kIN#O{&S=8#Gr_31IJr|^|9z%i{Xx|M2MF^%#woZKiPTPOJ zJLk4gEI=Po`uzIUo1|{NR1<-~Dz25Orqg`V`^|;$PI}ZmNORT;0m1hWBBrhi4HBPu z`Xoj(n#-{aL7}jAxJtcpgya4^{dcJE=OVs)jE8Erc;%>iy za9bP?WWwS5{9}n5iGP%Ju3p?-omj59a`BXUzDz7H!_ychD*stX%W)~_>3XuVI0gft z9K*!2EIs2ivVrpkNRfw z?@?)`b46+StTs96_IW{i>b@SMXS#0I*t$+zrCLnuAL2X{APg$WToZe96}oA5Tt2e0 zDg!Fyu6KxNGh>sqvN`(AB#lagdy!E$S+NW$#<{MeL}QtGdp%}sS+P{YR*2x|oFZd~ z2)o-p=+1!`XouAn0YF!b4r_eKJ zv^{fRYwg`9mGt*G~E6)Y?s8P(O~>SAV7dq&#ck+4) zMKWckb1P~*8Uk3F_sAiEY1PWlKLcLVTQE86JlOzmT!)K&LlW6q51dYm&?mUgj|e7y zN!@LcGn_3is~lm%CItF5kt=~9PslMnOZ9!j6gCv+e4jzLgVqO~k^4wKnsv({Xg4iO zcTQQDdYtZjStqwsXSUQ*NB07_(gn5mOMJTA^552{jFhTe2|mt25C%zZp67i~&Y7@( z@F#qv-@j#q;s^{JczF*2It9G;L0|vJj`hDj{(qmf+B++YnXB)OCOV$N@7rQHNB}ey z2FFLDeC75ShIPi${JYjKIqluYnx8}ezC@X@wl zAQt}5Isd=M|1%0;7{KZO*Rj~dOzEZuYj!LAt}H*aF@wMTZn*!6CI>m|85edfGnr|v z&GMl3zhR@GYz{0;b8pNa?rF-d;CnTVBUNIPal+ zacgh7*khsn>S*a?FM|Rp*-duap?U&+jBd5lUJe9H ztJzvxsF?4l?CgWh*zzmD6Gdr%vKN3J zM|Btan*nv4Xupq)@JIC!{S5y!GQ!ZokThUjn1CzKkeN!_j^0{+=+wLsp1tB~-XJSZ zN4Lln62uW?=cMdmA|frW7e%=X7I~^RulDpMIlNX|+RVY>bxGrZ6n~HDlvhDz);9$p zk&1adTUg9VBCn$DhwCI31XeZf!Cm4h=4UOg>PiM8)&vTlk$R$ioQCh5Da5x4ST{o{EDniE4shoX^;qRR@_&%w0 zS=S4AoPl-WyG+l6g2u3^`7k)ae5b?e?tP*%7&HJbm!S|&A^iDQT#IK1q030Cn|)kH z+>ZvI$duyX5Vqe^U-rONRHOy)->Z~n+b@4zPlGY8vSN4q<8i73Q6_52Pk-Cmv zOChx?UP3{#(}LpPxUU19V-fk}V~R zrN%&ayNx)>6O^ap=d0yV()Gf$_5?l~re&+>;H>{nVO$xKq|FgNukbwbKQxEOS*d^T z_&!LL_{UB&5P@1nWA5Y>U|OLODSM0JcHs`qMqLW%d?JNh0}+Eyn8bdq5ITyRmZbLp zoLYq%csMwhco)-u8*Ns8eK_z#nS#R7sMv)(JsUfoSWiRWyDDI8YigNX;*pCT@p&Cb zQI^5^LcB)*O4il6)tQ?WTN&erMMvflz8}7}8&76SFR7brD0)8$jZ@AoaKHFLK+B7E z-$PyQU=uqy^VoF6Pfh)zLc8=lEpnmFc*xNA;q0fcFX88#8{L>0LJBy1815IlR6_z@ zS2MX?HI;Nte?nJns0U2Fy&?7jbj7rcYSF!x$19zos#XOIGs|rr`4ts|s(e6Q%_F#~ zaO8`0M(5s;Pl@nGNl$loa4L)z{T@46OqnKr!+;7#`<6r2f4&QX_myZM3V{F!W^iIa z66ES`Tf=6}8~eO0>w?q`CDf4ED6iHeFHAYiAxeQJ-|;N{i@zNmLc!`HHS5PK{8HP$ z%r~~^*Nt89bu@K#>C_!}S+<^&NXrJtJ@fe55rJd%5ip|B<+AHJxN2)9B){r)_Z~UdP-;1EY(cy?tZsQ`s#!5M?1@MWUzB33@4( z(W-1MVtHAPR&_eQ!28u!Si62wQlZiA@NmRdp?~H6uFXe#d;f^quf}RLFQ$&zjZxRI zplz?mlyQ?y1v8z(%xsCn68I+>`Of3Z-VcdlpuT^%0SHW#k;tepA`ePqszu8wnjYik zdnOWgG{w(zPo59XCo3}V2MtbeW_&Y66UOYsLW@>sU-1pC^S*Jrkl z@0YSV5f1xOKe(c3xSSXoYwiG$QB<1K91o$ZWO(sPj$J!R*owbP+`I(5kRruy*T32=Ra0(@?F+&4JOsHvHqPF z*=))$!#WZp?0vbDE$k(U>_W-n{`WxA{C0`Dz63EFo0x3)UX2B4B4|G5_?mmX9U0*W zsbU%#F$LxKLisbmfswA3sjogzMhKZdi>Q|@8&xhpmTUI%95`Vrn@qYWYAtFlIk*)3 zluHzMD!&T=pSBtSQ0yWq=5gzt?7!Nx#XV zlXkt1W!!2bA0j+4&h7fUEAY=t$EY}L&Fh3O2A?8yXLa?{`}>&CD3ul`iHwwV@t4Eo175bWeE912kfXm%Y!U_ny__(s(qOPHT=trjhml z1X)@7{+8G*cDl(c1O^FXt6o1>D8y%>5GE{U8TWHOvNsiy=tqk@iQ~rd3E?^81vG1f zeWw1ygMvjaB9;x;?ClZZ#Or4_j8i`t7h*;TKT&i9;@zW1y7 zcx;rfkgwJ9%W%}o)Jj#%ZXKikhEw_+##M@MH*LlMFle{y*^gD={JcX0$P?>#e~0FU zskrPj8?JK$i?%x_cCZ~jpk7l7-fH^4&10o^n(KSoF|$-H;qGHclN+@boE zIP;Cgak!l!QTCMT26#PK>nVDO^sWscKN8p$|N5b(|Gm&xQDF>Y-CkrdV6a*cL}~VDpbj*CZ$XP z6&Xh6U8^~hKM)ERDy|_;E2>F+63ochGC3J2$;y0Hstjl(#+0sR6`+AI_%l`&@M|(M zHZN-v;p|UOz+R+2oYC->iV;9|_|$Z6Nc)#WuLcW+C3?h0{@YGD}jz@FV1nRkb1e zW=f_0q@h9vpSLOGJl*gwW9q>DFG>6JdOSofsk&{6N})^=GdCKtaZ zT*}pixzJcdG8&_Zrnu7h6hF^4;a=En!^v@nY3+IVW4Ly!lO3Z*WMe%oM}kvvw#e&Tt^kc^#Qu)7YI!ljA^}s^eJ8$)lHAC}?Vd5;;+M z;oNX*CG;sIDQC|raq^ehHR}()3XCdiW0?uYQI*9taLdZ%P5>Oj`xt1cLXZiYl27F> zb;!^Gw7>o$zk}Y==lj1Ld6yLO^BuI|MMiI4&(!cpo8iW}uD3tD|t=PDQUd1Hw_+#QL6r!x6n&PQ5a{8}iT zvqe$sQkoz6Y^5<$Q%4l{N1Uigre2r7`#?U;gNYB{FejSzeYH2U(f`&fHcJjO)tpsz z=2b7_lo$Vc@zYmZBt>2SuY{G8C&^KEIMKv$Je89Gd z88FFmKSXpsjl9YB)%8CAAP6`tO00WbWRsYIT?IlEHX?@iEpafB=DhRaYAKO+x;75G z2pjp@i_&RuPnNP!j{aOjpm|;d^Rf@Rg_p+7^m#d(2mgfAjTW6V4hu+#e*AK28fQs- zsK;{*4uNyNyE@*-(|3$&4NEN!`OzOJN8-!NI>BL}LxB9YMA~ z@}z)*fKmRhL`M++)RxqHxs%h)6nsO8$cJ#rQ_#sVBO!&oomsxsZwW-%$ozE;;)9qM zq}FurYiimKQ_-z^U{4uq|1b?CSAQ^#w_M%)DMZIuN-pGvrBpo+ub9S?=?&J`Qtg!O zNV0)oxTSvGsK^?2slTX1OQgvw%_;Nfcv}Y$+Zi=!eALI8jPgmt@ZapmH+UI3ax<)C zq;1l2o86W9Vi!cb>mWR3Up^ePo}bl1;tE#Dn5SV@hpRo)^MuIVHzo%V@~nx|kh7%x z4;&xrTqLRXZURWcL3Jf&K+HSe6cSQ8Y}W@TD=uMVn}Q1To4~G8#54Y)?HPq^bF?0s zFb6I>9mofhnE%pf+R7oBWn@uLQEO{6>yE$B-)P+W)T#>1q%23)TT;Z$_}}~DAo$$Z zj+k%%Gmm2bW^v?oDQuu|2UAl>iJq&iw&QY6H<79m`T33v$8OOfs^({$q%pCWkPQ~g z%ddCa{>btD82IOfYaSfaAR6g>&eTbEZbjj{W$s&+z5D+803XaA`T{dfP&h24iGiwb zgb{|}4&{_hq!*Uobj1k^S-}+|bOfD5$~%*|o)0SxeEEF$6Y{F#+LPD76&pssB&tc_ zy8&I|dS^jrl+3&Ec;@QTyb=Z1kjY`6$o8ISVkF2xZhlwu$6WzsUKP48 z&V3RhB1^{8mPgPI1A`s<^m)=r47@2Uf)SO~bx*pC97i~c{atGxvlhFg2;N z{fhY^2_xXSPMrK#cRiClLy&LG8@?1$Gg2oJP_?Pk!w7R|mSo@Q`rbhZ9LDb;(tz8F zgLHRnH=?#Jc%r)jPJZZt^n%zv(P+9x>_2iJ9DoKFE?wDf_`ULnSN#1V-B);8-G`u-8@QQlt3OZ58 zPuMYZS7=Xj_}XFfRkx9zoYwxLDw3>1eDv?~`PiAY*Wpc}zBw`1UOkNd&cWIgGOhNU z*r138;qiF0BaVkYOSb_ocoidY_#ufCHk^?~?BqPC$4azP#F@*nmQs?lFKm`Nv<@!S zNRUvDCrf?w&Vyl;%v*^V;VsYKCd0O?MYi>6wZgq-QbfBZF|bIbG$CTl*$NZ}?q9^k zKq%UEEFzO!i8hBvmLuQxcNfCo8I52e(81F|DHl_tpXr?J=!{|)6aLDgMzmSOQplj1N!=uv{_W!BHuxDd ztEXiwVgI3Ot2@s6SJJR_u+MTA9o%bHUunmX5 zQX%;NV1pMqhP}hqRbvdt3X9$>tuRp%_o3#{wV{Nk+`i^pS5!UR6KUllM zm6U7J%YM0k1626muZ{UthRj6`H>)O;m0^__23=4)6THNamF>gvu>>(oWGPE`7NMR}u!oRrB z;Z3T|;)O~d6f1h`C_!yOk4h!!qQUySP`UNKWVTOyo`UtM>^8596HI~@nbEDwcH0p* zZT$xbr$&UxF#m~@C*=sk)rQOto5+2soiG1rq&C?M5P z+El&*)CDT4i(d{PS()u`7ZVI>JxUu3Gt?D(U9tqR{kafh7vhcL;j$Xn8@VA7o+qh{ z2&VUxWn~(|%8*UM$s*ZkQJ|xGM@_7xj?w|b`mzwQ#pXrS@Tp=5BT~Oyr9lNNMc3i$H59<>#pn~{zN-cXP1Tpzy8=wl$q7ue- zW%o?C2v4;b7ZM4+|E0Qp82|FUF*TxlJEPH~8DkeUyC{4Eyc}>p%W0TSdB27!4^hSE zd}@rIu7`0_;J??~dAD)`BF4tgr`kwy8Z=&&WT50X=QN|CM)^HPYT{g%?g+ZgI@#Fe z4l?f=Uh9b*vj|M&EJ^5vzJH3Qg-a|?=(z!=K5-guB6KJ7PjPIds>Jl|!y-eUlfrT7 z>2fGblX9qc9v5UG;wi^whkX&=Ew8DKmq&0-;GV{c;r(h}la^NMau-}oNwExRJ?F(b zRxpFWmreRoN;k>tyP7Z5u*U4rg`4mdE}5_JT4EGts;d(n4c>W}U|Hzh&_}}|0gOdC z%>V6J3w#5mBW>!E+YpZ;0B`(~^AhT3h_b+nk- zRqj9KtrUg<;5zv~UkzcBVX8hG#fLa|IjW|0Wwf6a_9|rkN%HwK;20hxQ@nbX12g}d zACnG2P<6(%xXs+x3XEd8QWjxy8I=`LilnpkkJ@g4&pkbvCY~=o(mKeEaLOC(8!g#n z>aK%pMVv=drhv%qE9wv3{Z>Y66qp|xYZw8pY-?r|;l)gtMDCNX_jUu&MJ5z1a&fq~ z94@jhQ3!@n$1_re);@_PX;c(k3SJA}@firG_IeAG^1TD%0`1H>!-U_CSlm+UG@dcE% zkvV|g*=;CFZ6u7DB0e&7w5(>P)n-_%+kE_!9`Y@a%u`|I-f)#1RZ;P zw^qf2e617{9y;*^P_E=P<>2Op#Jok(_rLG>KbjW;4}x0hnN=_J+22QH+{uVP%d#)H zA7B#iJlD}P*aoHNS4FCyuY(PW)z049uM(?j`0nuk-2OPyy8bO}@8Fc2zk|+}%1>$} znYzGzYgRLau0 zgm(&Qe(9g_1>1;QO1_}smkWV|z)?OTb%c#5{0UnfPWC_b52A1jvhQ~#h0k*K+EvT* z&Ae3nE;aYV;TghgB%-|jK-MJd)yPOU)Oz#C5yWbwm96B#0-;nnzs~W-osk|{;pQY@ z1t%`cJ%hU4ZGzc*3O;OjZj3eSEKDLHRCZ zZ@0~?E#0~gpAi4cZ`FV!ry?Su9ep5;uI!k9V-Zx%~Hl$bB|HsS^^RnQ&`DZ{C^fYGnLc*4q}#h$gAJo7RJrhAeB z*a|VJxl@?4)aUQ1gf;mtua@jTWmijtC6>8#LBSfPWtkCzGGld{U~yXrT`my#k3lIwZJ%wwNiazu+19Kvn(&z}5;6nIG-(WjFAyRkl$_yM*^#=4hu3?`Q0RxJv%dg4Y{3(LhXI04IB$2h?Se9rS z=mwX5amNY~@-_aIxko^)`|!ts4@QSD3|}qDZuj;YJViQph71{`W~Fympu~PP$p0slY;}wcR zmfP>)s-bQ@(DJONk4O7nm=R+#dq94t-uz>Y8hpO~>wkK)Iy2&(``l1ycFVJO?pI9x zwm-%xxb70+sh6d_AsZ_cZRDUFF4!9w4*li7C$jD@jZr7>mjj7fLO~c3}!s z{q=*I+q0I+^FwzOP)r~=SlL-c-{gK~E>EVES<1xf;_2m4ji}{k{HHYNXiZ$pjBxAC zX#^bP7SIwL@7x`{)1a38qozSoX=jYji@+*y#g<(yW#NZ7iZ#D7fBnyAdZJqLJQjD+ zkryr+PDHv-@TVSftj?cyyBcFc!qWsu^|^7;J()BV-1W5qdV4Sq%8oX8sS=7v6SdRF zVl3!kS_9KW5iUl(n4C4OgU;i*zM_f2)eE69#~m=nny00BVe#?@Bk8MGlNgMFvT>(M z(V!_xB%?Q-znbeyF7HOer+`=;74&9odYKdJ&l%nBpUNG-JAzD!L`1(DE@j)+*NgCt z#1Nd!x8Q-C|6qWA&Cjm?nj=7B9^jvF&0&S)Le;6{%+x{s<~X|73|LS+!Egq@Rde~+ zT=*J2QPhF!pUPd1A2ml*M0-q>-oJ%uLeC2Sj}wdpIihEkt@`S+quGAJy;&H8Q2E+; z`{Zljcw@w$eh*~(DH`IGSgNPC%~TN$3Em>8nJ~p#_PpFz2aF^-;-oNm?A>u|aV*pG z4(H)gXE+G1OVH1m@j}C&7|F>vWBo(E+xYk(`L67pT;k9^iqH4T3dI+;xfWQ{d)vBp z5Ra*i?5(BdeM8w`C3Q(<-mP};MaQUBv9K~_ZEeB;@KY)xy}A(1lsLXd?V7c*SKlfE z`d04NQm(W$LB{2=k8fCLGL_}oHd9J2bBix2>zm{}H1zoZXjOh$k%?uzP=5aRta#KM zn7v~lvuTP7&T$N!RwE)P@zJL9*+n1J?hTla0c%jvp){Mk{bg#v62=>DEL`pUe#E2% zYf(;eM}K&MUn)w@k)zz5hM!g4kNsvq&#TJ-3ekV6Gm1EHov#Gv>wO*y*z1R?85MN~ z{BIMd%(5uXhsPraMMqvvzhKQa=!+6hA(ff?I}h5=sPgl)y{$EW%u-*#=-uwSLE^Ym z&yl<4vuz5wv%615L3%-BYCOiE{=yYZLOu7! z0C6_{P0p7aABi9FX$mH>711YQsgdarg+;qGUrJK;Adc+IE>tqEq<_pY&9$>(TCyj_ zb(nAfJvc@QaRm`nYgk2bCRcmL4VtELN`Fy`DfOFVS=?<&E=~oB|5g4TL=SQqdIhW3 zIt5i8TAC0~Shx_k>2&eD3hVw(Z>}`;U9poP+Op|7EY};|)Y#++W_w(2fazPdm;T8n z@3X_;7gs%eLmrH)q~;R-vxL~CTRzLnL2FXyrv=|!KE`gSA#H+N^V8@vOIt7dotU;5Hjfx6ksl(vrOZ`eP z-pgR+@K+P5@2m;&b=A6{%$3(ZRe4iKb&hTMVv>{kiT3Xj1=ffq=v;9Qnc~o%{gn0) z>s5Zd<3M{L@;ZGFt+#Yfzn#F^4>a90%T9=SC$ENmLGZN~E1mMpF$Aw~dK1-ZDI!QG z<@bYT+2FPN@Z-p;J}t6ZTp8sd{brjY^}jzyb}heD<9!mX^m80HMtR$$s#8Wplh%($ zuAkp^XncPv+^fjxeSPzA{52u`gAR4;80z&ru^+%w{Tf{<#NyW5);+%-^qI*_JFS2k zVI`PTcg)P2QU-YC)yR>Y7^z~5Rl+{lboo|#>@7NnDOL+f3)4Tp#jW3JH*5aC+H?yM66u z%n6K2oVQi{uJ=+gNNDYjTqV7#~@XHV_#4r!4Y&$7hnl3=a+|^sQU%nEHAgt69TN7c+HJp-#|_k^&jlauTC(n?|6P8QAmaa0H~p$bRE8%1}4Dk z&)iV>Q>eBe*-%m0A22Xx;9MQs+c`ECj*J!Ix!3gNru}I$(9I|Rp|G|`%Wapd4%hrg zt)df$Bz>dOhFWiogp$x}v|b!voTL8-`ubmBvHv;l@-3fdPi+zt&SHM~A5snID9!B{ zLO)#{|EUuA2;0rArHgIg>yu@Dxc3k52or8Xy{xReOhUVhC;y8Z?6=H+xViuM(f}&L zf9SQdn1ARpfLZY0oBz!n7M#~nZQ^a>?gPKv-=_{Lo|(Pe8rWVOwDi>%Yxep_Nai$> zE&cKP)R|s9?P+H44k!OxbydxMQ{Wpv$hq!H`&Z5RFQ?f2@7`IT*j#V}AHPT3G$Y0a z@Uu3)xOurPO=%oh|If){p^|r}=}RjspN&sg6PKg{IPZ;+HvbEH+W=s(KzN@FS7`Nh zThEde@xLpSKTb^G^6e{5e|^2@Ev+rYuEt2u&I&7jOTB-Rl++(RS|2r1(7W6R?w`51 zUcSE=Is8TG`(Qc6T^m21C78p`w%h$^H#@c1b#;7nAP}CZC?EN)l1VIgy%@44fegVg zDJV2Uud|vK&h5lF5-8c&=pE&vD;~f&X1af?tgUao1V;a`!1tFqNv2Sxlo**o%UeI= zbG1J^p>_04C`j^=_l-~#AD`}{{R&`tr91+Na`YY0&nAzRi{C)m_Oa!(mIwfLq0R7y z=iY59eDeJ9!7IV(VSrbqL#(vw$PAv*H1?PL?EBd9-0CB4=eb*q*=y`e9Ce&%-e-C1 zQ#$6Z!@p*JEZ&}7jcwoFVy)Gb!2X1-5AnC^ynJEtwsEep;casA&K+h;>JJ)EN0Ti? z6fWyGRunmavaz;CRRDvRgMA2IU}@r{M(Fc0uuwjoFd<*w&crDymCuueHZm(A-Mrf{Jt4RxScJ4sSM>Zd??sVE5PF`VrEKURPQiDY+?Y14FV}&MC zKkQ|{W2>xcyxd)@K-1tvpPC}-zyUb?KXNiDl^H$5z-#2gA}uLnUs=kJ!W`uox%Oe{ znR(4IW)Yra)9nXulBYp-4{6&}Fn@ck>KOHJ^3|C8p$xj-uWuGYH6=7^Ho$c?EvB~) zJ7cyM>?iT^$LBlaHrjWFwSHVL*|W+k*>>a`yzyl`5+ZM2f?7Y&9kTMjy_{JCL!)l+ zS3B9sj_FO@6kD0r5O5govgPY}Cab@kPlOma^d)wb)p^Y*Sm^0LILCybu{#;y1;t;J zBc37F*`FVgp$Y}yARQD#bs5+#-)kg9avl?Xrz>*ro)cnWqfvIxh-|v;tDvAktaHWP z8JH0_ebZqoI}ELf5bcNlpG7Qis$9SU!w4K+OxsA2jG^#k1&Hs$Tc0WrHC!NLC{rT< z9O%Wa>E!H+#EQ|&j8q(wPfv{xqC89`4#ocOF%vM&y#zSU z{k`rY;LDfmy_l-i&4ohO4WTlQQGeFU`$J5hHRz&fkb%pn|C(y($6|e!R%o3ME;E{z zVgE8_^Tp6~W}2I*Ap!*r8kLEvG{sF=DC^RNN^ji}n_s^*9St}2H@ek$%kIc>9)`iV zcF{r5T`_bph;-BF+bq?p=Yp-6%ae|)lms1o0R}a|j1T>TGq2aYN!Ck5i3umnk z^sCa^6wf5pH#YhD!FUg3n%{%_zCTQ{P3L)jBc5ZkHY&A|kArHLN7Y;e3jGS~`jm-p zQK-Ai!USI))J*A&)GZ0nsvK>EnR`M2BhBX{jy?i|*T`jmMIe9|6i9#7YsWSYX znU+?q8~CFYu0t4tNsZDjd2_8|^$oR-GQPCA8O6EW!D}aeq~aFf(tf0a3n#Q6$;x6+g?n<_ zIB+w9ck_6=6*G>kGwc8k$>#(37n5Pc>0NqF8TQ)wuatefMH>~kXb+xNEl-V8r~ro; zUcO>l2MC)Cnj*WSu3)^wO;H0A$ov8hX14j_#-gIcdHtBT5<&(U{uz(+xxj<1uOwtU zz0zs9v4z!$t4N}IsEd2$SFq4(O?LEjc*<*Vo?UxMJFL>Q1oEQJQXG)u({;rP5TSHcVt3_oCp>R z$hSf0p@o-UcndFZ|K8oD(wI9nIbAs&2G3O)?I}CFL~*d;pw`Rts0|h6&`+&Q&aqP- zJubVWKUD8c+b_4qK}}c+6_kCIvN$$UBPblv@g4jC=N`zMK_MO&)34}W;kvr^C>6ie zw}16S8dGq{j1Vs_neDwZxQ1zQ4IDtiefru)xr|j93wRCt?CMUB-TThgi|6x!dzL&) z>@0>5d8PW)(0S%Nm%kt2@xy1T6Df8IJtxO+Xj1F)*^a2kD&{hMtkIk-9K^z4L(i*T zm&@UWmyA$&9hzMmp0}%rNm7C-{=j^V5Tt9~5(Sw*aFZapt9|p@h{$L5;|-ID!XS+l z-UDLv5swZ9rluV2NM%_EXvR4&+iN`)zFZi=lKv0|X|VleCuo|4j2w0<m5*CjMq`o{;#v9fZa!f7}Nc_mCmB^H**Io?p3_ZA@24ZZ6CJxdQ-9wx%qdDQ|Mtya ze4XSRs;!Qo>;|w*xVdl9jjZE}#_?2G91u@WYYb=g{;nLX49G^@b`UB02*2^sPMv4ufMAO?3@sjLZQ9(l~J9B zGcYh1L29CHhu^!Ex?FRbPDlWH7Wm7Y!k>LR45h0 z0ZiVS#Q=ipXA&Yrotf)w={m|Qd|Vkcnt>zeM1I??R$Z@i~=aei!{F6Hc3g$UAA@nO?3LDSsz3A2pu zYpMD`DK4dyDr9g>>RYFlQxj-o$LC;j=`=`y*Nn)k%OhyK6VuFqb zucr~dYo2>QV!(9O_Pf3NKZwxEgYFFgjlv*tWWT^7oN7f? zv2Se_6V5@aFG8gkm~{N6fa40dp1N2P4rwfkHoN*WL0Rl0*hsuC98bDE=!li(XUAz9 za!_LoT_mNo9VwTG8rz2{?&`tMFm30+(3{#|y(*f}GvFowD2~NWwl;yZSKvad{PzNAEny$IHt`fpTZ8WD1+`!wVRt>yzxpi3p@^nI1O$g+&&}Y{;#`z)ms(kTJA ze!nyR+u94#HK#PlH>?L=M!^ZZeCW(-QTK}FL}#2==D*GaNzT4R+RQgqIu92}AE|u< zLew_ucFu!e-NR90|Hr!2nsW8dQ~L5`rvJM5k&ig$@kCBXfX|jYD|uybjuuF zXK;qmy%`DS8AtV1mYpb!GI6V|gAZ;2wWGf7>$5s#?1n?#&h2@GMYl05dl)tD1t zQx!yeEhNoieLo_XlDWnIdWkXoqq&u^TY;jkTy) z)bLL=*8k|Kch7e^&VU71T9Cm~U#H2|`@k6V&WCwxg)dm=&?6=o^J=B56p6F&yD475 zQH6b$|$&He;|F_wQBOJV+Sp$BeIO9cD{wR-G|An< zf-x;#Tpbjjrb`{+^NfST(tR|<4pE5GX5~nD&<=9BQQ0N}1M-fS3jII zwQMnRRL}VL*nB^+p!jiWdYtV%k7n2goZ5MLD|Psqwzo=~n&zWSyiqD&JEOXmWV&8l+XOXA41WY`r{pzEcgbcZwhaqb6%B>?dR~1Xm@f+U~s6 zyJ$k*P!;$qS`CwW2yo!~e}*&lWgA%y>B_at$g|5P{4Zm`WNb#?9g}m$j90+5d!?54 z5$Jp|J2HKH#z^U^i&FvKWk9Nr7?IvcwYBcvN^Pc?h)9ZDCzZr_iGHio0ZNq4qn`_< z^vTu!kW&+9Fw=%WWAGk69M+1XBOw3bcXCwhwLTpG((MMdhHM%AGTzeA zPRGs8z4lgy;~%M(h(Z}n`^2EcI90qH<_qU2+0~G}-OLjN6z}^ca4DL~hNU4LlY^qb zn@A2-t=`gPvqjY=f@Ivcs?Xl0Z;(@Z-J2{m0HsY6h&d$Py~3OJ!S0 zndM&;s5`k%UKzcXIpO<>)8JY8dlOwFud)whOMTLvI@B*?QTM;ZHT{;o!4IMc78C-@ z++av)q<0e0Isf*3YSs>iJlZW|l3h=yo6)wXdlAbxeFGL~;e*d>AN*SZ>#uc^ z+;i5DeaZ7~Trtb*Fvug<;*Y=55|-I*?%e4mUbgK|EA&fnwDKp7nSKPCFJVSH)*7bd zN}-}Jk3T~x?y8>4q*+v!&QNSdy}bT99jkYMD19D{_3-YLTopsfM34z-r30O1jx5Sl zXu0k5@#QBc&|s;mGu5=8^lZO5bxKWpuz1vhv3lMpM8Ye-`+qEyb~$bR&p!)eRs+;1 zJYj5ge>31-PVv-oc}APH^`Eex=smN99>*io?_)a)Q6ed743d8GF41xr+*t#3Lp&(0 zH0X%WV|j-znqmnvu3nS1#H0X2Pe9NxOGGZ?@<#DZ0zMLo?15E2Gb{;* zh9}U5%&CYbp>_)yiX-U#k=g=4tT7gUx<6!Y0sJtTzf7r~A^sLO)+wvoJ^zKB@k5_* z0b>VN4zQFlY+zkEi(2oE7tNXL{~DwY_#1&AuYw9{-R0}Zs3L~}B+@t>|LBkagXcXy z>h-kK#|LixpZErD2mB$om~ATl8BEkz+K!6PEnd{@AmZAJWz}d1(Sn=I@mSSy_j+nX z$;=i!#Pc*QbeI3@e!P6FFy4(uD-EQzC+yhCi4l8N`C8(|%hPnS`a2_s4H@Kq6xQ9_ zpSE1+Mc22C&pqQhaF!6NZxsDee|D4zs@rb->pB~X?!;<0q-AWNzh_35G0FKy?D`rG z@O5iI(^!5uN~AO!$Ur)K)xV|E_&K&B#2I5w$-R&^4%`?V0t3;fk+?%06+_B2+Mc{Q zyttE|DhAD}9Ue9pYvp#9G#3?b<0GY#Bg*TU2 zBr21^@M0hZUA;2@V_LTKzqIeFp8ITP-w6xp%0MA%3ZlSp?)+lhwiNQpnghS+PJFsLj;oUw`M!RjF zl!0rmN;%T5Nb8S7mJ0V#c-Xp5!m|Yq;jDCdrJ2c!isi>zcm&G+u*iE3r|FpTQH;B9 z(4}s%*7({XMZ403lN;-VL*C}U@?wv4o#YD=ag4%?y`f**J7UUz`@Zsj*(^n?hcoSy zgFj|0#2wF@Lt}dmwzr>RV*>Zl6>q)|<+K*rSqLoNxHXxNJJ^7$0Ujb3p%` zwrccL#JBc!*db~`NBm7=XzcsoBC+xLMs6OizpP%s7iMQ-*I8<|Cs~@}%@nuWu4kkc zxoDnI`t9^Hw2NmGgmQ%Mc6Fh$BoWh1boCwAtim`sSz2Z8Wywf>$610tM{p*4w43$% z*j}=vJ}E=fVTj9qzbJ!~G9u@C6B3%cGg`hmTaiJS;6%427|4xiA|tGFid~Mp&CmP0 z9H?v&d?dP@6DkK{6D}1$XuaBMKevU%KM)AQSUa8_wQ;HAuOvrw$OeT~2?yu?w?kEF zbMom=L`KIUwU8K5x+jn@9U%?XbX@I*Y&f#FeLEu-1*Hyz1>pORBpXp3$_hE zM*z9%N?VeWeUkXZBz1KqkQ#Yy z#IQ49ojj(*palTCFOTNO2-QyHpL+Ycv*X#>sVRFZ=$(c~wcp!~X+T@b{`<||X0P<_ zkh}ep1|>MD6=kra*ub~&v#3%ROhTFXL+m#to80d7u%0ZVa1WUCHh6$_QwpU&qKhcp4Ly_5^ammwku! zA_lKw^@G;qRvo|FR$OC+5M$yA$h5O!Qq?cwYNiuiVYb zR_tD;4N@;$WT$Sn0_7dbIT8gIdbIP)U8yvW9{(E;trMa{S{Bs;|i&c_S@)z-dgl-4a|J>;Iab5iQkcQMz7L zu3j0(FEo|Ax2%!!T9=lVP0bjcWQYS$<}ohoLDQ-Yr%pExC8ebZ$f3w#oa_vI1m73t z^-Am4GBf{9Ocw7u>`V#^3cliP_}M@LN7XH2jljS)|oKxOjAO60G2ZWXBQX*Amg$`CL)KuESg{77&z>u=Rb1J{9xYU;HA^ zzOoW}BqBk0G(dOO-7Qtk%*UtDbWDdVDVp2Zc*@P8gRAp76{Qg|JuQs_NM2mbgo%zl z%|DJmaPHyJY-(mv_v=>&ikiB!l$T6ISq9;>^FsRJ8*5`CVMizH{DuZ0A;tus&)$-X z=H}!=&uJ-VXCOoX$NG}cyc}1*#I6{^lYD4lp~pC#2yIeO9dP6LczIlfUr5 zRw`L+IW*w{ke5Hdl{pC_nrxI08M8!K-sbLp=vXPq3|w>$NYjPMGnn%En+*48RsW14$dAp3C-puG6{vQyy%E7MpMTyiqX`WoXLQ#$0z zE~s{MtaUQ;=2@I_>|6iWcQ`z_h-`z;YcJ34-C8R#IQJBKOu=Fyzqv^eIor<((y1{# zTLrGWj`l3siea)8uEiF8v(mxGt)~kXIiu09Dw}p$@Z?)#89QT*r#0aaFa&$sD790q zrVHo2W@5+=vkq7n#kJ0Vn>C%6C`_HS8|L( z=H{g80>W%pYd4mnAV0w71951il;77Xb={`1ZVxxmd``w}v>hl1?=%@S2Z)ewVnww@ z?&oO@yKbB!AZ(7hGQCbYp{u@o6`dnG0B9Dv#K?+RFDOu(%}9a~`skj$p1N7=dc~`q z{4!bLB#^V2YDJa+f#jS_5Wy53hv7^vn%T+kpMmfvSak+e@2t0w#l|NJNTgClH3#Ro zVIbSog4qoKmh<4V(bIu8lFjIxZ-IbVv5A;Ur_1+c0lmvi??H6V-*jq!nD%S3l8Kno zvV?oPKA8~dG@X|YC16s^&d<3hC4d(xVLEOntMC9_zuij*h!h}jm$>x-QS2i*6izOs zf8P9R)`c}h78sBe0jJxAj3QlLS`NMy;z*N%(@O3?Z>GM7ai~yad!L^%L(@pXyB0%A zXt|FoKYn3a0IzrIx^Y>WYF>t#iN*5Uu|pu$L$|q%L#dmo&oegcJE4X?S4p*K9Nf92 z_B{IVS36l}zoT~`A6~3^0k_ziyqRmfIbO`(@}^wCFP>bp32&>0Z$$(|^x^U6d0p|i zV_`fF9ZIyN=sMsthR})wZ&wsbC_|t3Gt`u%2K1_hYuQ>HRqL+{%+Df#fl zI^2D`3ZK{q!m7^}Dmp;>*Jdwp3KvlZ;6T^@pgb!r$omVFCz1WHfp# zLbt!o?ZyY^Q7Of4wQHDghyp!1w9&Wg*)1ncKjlsH8%E(kMk0t?NIJZWqnrL*YT26ERi(Z%A#DRfZ%EE%wU{rT*3h{80io@z|-29DGjoJx%mx_bQ zeJdas)pw$m-B5#vj6xIHuv09H#g3x)$qt@fQ|_WH2>SaYlGF)s3q< z=Xp*#z{?zj-Kg$zi&=m7#9nlZ_i#lb-m7880XVDv!0P@oW;g@8>qcgv&)^V%h1rWN zrH_aBI^ZKDY=T5YSDIyle>?l@@uo@<0p?45)aTdZ&9SM>hH}8zS=FQdAYM!o;Dm6` zfZ=OiKyMP)vV+AKjZufJIed#>Ydb0z2w$EC#uF6`cqH!b2Z1MJjo~N}vOwD%l~Iu| zUxm?(ZL+1j(*!y}ibF&6#FYpaDF2QVXG#vDU?;p4hy3UBe)qHEg`mkG9oueFZ_)Eh zblI5j=KAh41m_Ns)M@Oc*>?xt^@c55_(-4yxZeWI$v=i&Q)VDYIYS|UQ5BprG+w6i zsW2Z3lXpBG-U(H0_qFgb#TI*Ir2^N~PP#)S$4=8zg)KxTVsKwhh5&S^SodGE8( zY|51+a*Pr@jy>>A{}9p(QZP8rJlj8g`h86py9F@O;mU;Sf3i!@Zoi5&dTbSDXP^k& z0+dCnv417bH^IaSgM?wyDw0`QUoI5w07YlnIg1z~R7uOJe^JQ#BI1u3jjg-Kq)>e(G$N$k5-0HNDE> zdzhIs1WDU`W7lJZ`cO<3lJp2Qs)yJ2ct+k$+h88XEdkVu)=B)NEdQ-cOU7Ai1v0dU;>fx8h>nUY1>L0l` z{`Ij9uI((z13o45litzte{&YX7heC0_T(b&`u0Sv*yg#A*BltU)_l{}fZ1{-Zsw{ffXkl_YU!hXxpt47%uyesV^ULPuDN}3CodrV_ zW!~;iMi^9}m$w;Nt{3l6-zK8gHEd8hQC@0ON%YlR; z-!pJV?n6?9nY0P6+Q`JDr{=*?I5*{doI|T##O*@5sZ~igXk-GRlL15C6JS52#MY4{ z?@J_5=NoIxPE`8v02W2Whm~7?(#%Fy-PW4fOrNGM@pQ;nO}^*CF|5vvg8UhF#4^!k zN$5#z1)G!?f2!Y#&+Exfef=6?M(yC@Voa=H4c*d`=zV%P_O6(>r8yMhtq}rKojaF0 z`F+FjotQLDgpWR}%mOOOw?AC0(w0rDvJZ#lBkXSWpAtcLmsAZcN)$;$fM}osA?bpIVdlJ9q$Y(B z`Z~W~c-y6A54)OC&kBta9~q$jVM!!LwU;iEoa$dZ&WPv`L3yG{L82&2`Hwzhi)}Mr zP%!F4!K(*U7^q7^<+E~tOy>y8d3uI@Gev(F(ohl=RTOpLI^Zjsx%3JnT!#_CgnjSj z#S^}*HH+Q@&;S-c$@jS0)#YVO%N?GvL}H_^>O z^6>i6KVb)K$9e&-u=crIZO0;fZsS4+m3Z#7Sl?XLU!*OBn{dgwkBjw}MrH_>Iy+Jp zq&D;D7U9A*CZ?>!3)_Rp&qq>C;kXM7VBm~Y8Nu`$V@*(RfurD-3`2RN*YHR0ltzIU zIfWywFYQlShoUTH9VL19N@{)*XQI#x7N?5$N8kt9uQk-=cAOhpOqm7JiIsnS4&YLn zX)s(P_inyv0*@OwpSFmhyduqI67~%48#?Pd@dgxQU1pQLRfH3h|+r}>GWw%ehf2d4i;-{Ev#Fg8mUq#NUHp%cp zK`6wB^ba252M~f@BzND@R$K{-%%3%c4D*z*eIsR$HvdNj-&kY}yO8U~`5@6e zaTz@&sm1d|LKPe#s@{n2r>}T)coUC*;N6W61KGq>%nRmUx>Ok&Z(8`YXSHGc?S(nJ^3AAr7?l#HJ zuR6^t1)c#nC&D`(Zfc@>HMg;q@_l(|K2^w^jjpi5#vN<-6mBYXs2B>lqyA`(1{y7_ZnMT~ z1CxHKEGV7S(WdyGuOfF|ukO_P^RUjI&9%=;HZj5cl`DlJP<=%P9qNcb7L^f8lz6>c z(a2Rb4|g->RO59ISzoU3?#HlN0)MLEF8pfxW{c@6@U5S5j{1W$(yojn>R9l-EC5qD z{ZlTTV=SiY%rOMb%J^xp_gN%`>ll_}lv7t9s|K>u zJK(S=W)20~h7!EtEO-ngs-?~1KlF+pZFuZx{jmzKW(3E3?D>=6bHTHH+} zU(xOkU%erqRbcnwsZ9F>l=_}#jKNA5uEWewmTsJPFo>-mWDpBtgW?z8S}JHqhFhu3(9Y5wqlR*{bYaTYXg1`;J9wm! zc|oZ^XXJMD>U!C6vgR@6ytkA-G}^7@X=`yO^I~nb3Wy9FkG`Xoj~I4X)(8@8zvp_` zJ;H+_J|J9tCC$&;)aPknW@RaFu-QC}xVd$R63+H=xRyfW&=P(;Y`Jl78E?AhZ@=SR zOM?4ws=+U6sS(&2l3kp!&TMNF^hD-dl_AUo^{+@KO7*pVwXKbmaPJ3Mvn7SZX-qwT zIxu2TrZAy3HHaf*5L}kPCxB@*s0Kh9lP=-2Q}?e)}2 zXR`E5pjBJ0$b0W~7AA{(+Yv90+39QW&DPAk%Vgenp7=FRAViwlcaA~bhT!weO;()) zIU{CuG2ug1To> zc?BiU>BK4CtMZS$$m*Dpfl=fCIUbUdJ3p5Dhg(s^O+Z;(l{J}?Cxy5a zq30Bd>!!6;IAs^@(6PGZ**l%QDjYa8JL&El94({>k=&j$WT{?~9~-9apKhf*<#^#e zLSbTsVmu0zEAh|7LJ-5(Wo%k+f+5mYW>aL0ouEp)L=xGC%#nG}bdhXEOr6gI-!GLo z=}YVyp-axk*jz^K?6-h3^sac&z~WZkU+NcC=kuu&4Q`#Jr@hRU-`{<0XoBQ>i`Ru{I`q(umQSPK zPCHlf%e0!1Bl|rg;QTYl)HlH5>{J*gQBybuhDD15PsyGr5`0tYePM^0i0y&{xzEbd9JT6Y|7;V3Xf29 zP9?KU_xjyU)UT0z-H#h}?|%H={&K_pauE;U4v08s>B?=SoI38&SUUCHOg-RJaKI`{ z5p8lNEn+Tjrac|r0PG}0{DreyHCdXBV6;P3xleE|tLVNkWv%GhxjK-%$-;L2W}N1a zWL^;lK}HTB*2J-XcYr(m#*)3hVb$v=_o{SZ$3Jkr)y@s=V~s_S+PZz1zn-HqkdV<^ zQ3CZy$g_xqud)LeLdkZGRJpC%Cur4BU@Ejc`a+#`oUS_h>jV7kY;jFpbkb|M=pR@kY1Q7p|) zkr40x&*<-><_V7&jK!naD>hWIV6i$ekY=X5=->^yH6a?1r@4j6pXZGwYV}#Q0Emas zZKK6OfAUtUPG1CMT9QzTScZ<|GZS*lUzbKMoDVMnf5Y2T(Qv!?gRBA(x>R@_^6}o> z#)@iD=yjgTG(EkhCs&v^jLS?2s z8RK{auBu|SP1e8yY$dhJqKu+{=?19KWoU54r!v~fFiF$2mtV%30%_-=zlP2rgPfjr z10_4#Qn>fWRN#;Sf4ww}QkHxpiQOOgu>JM%a51)2))Ydlz=I<(FFT#r?)+}R0<}_m zAjY64)`?hJ@E40R^UTPlo&i0|&m0><-nRuQ9+|^U=|x(E6=Eu0LRC&5HUC*Xe3-PV zbnF2u9}HwgMPN}$Zl⁞=lpmLN*4ek~`O-x4Q3d@X_ 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 to the working directory. +Start by downloading the YAML from to the working directory. ```bash -wget https://petstore3.swagger.io/api/v3/openapi.yaml +wget https://raw.githubusercontent.com/bump-sh-examples/train-travel-api/refs/heads/main/openapi.yaml ``` ## Document validation @@ -212,14 +218,11 @@ To validate `openapi.yaml` using OpenAPI Generator, run the following in the ter openapi-generator validate -i openapi.yaml ``` -The OpenAPI Generator returns two warnings: +The OpenAPI Generator validator returns the following output: ``` -Warnings: - - Unused model: Address - - Unused model: Customer - -[info] Spec has 2 recommendation(s). +Validating spec (openapi.yaml) +No validation issues detected. ``` ### Validation using Speakeasy @@ -230,17 +233,9 @@ We'll validate the spec with Speakeasy by running the following in the terminal: speakeasy validate openapi -s openapi.yaml ``` -The Speakeasy validator returns ten warnings, seven hints that some methods don't specify return values, and three unused components. Each warning includes a detailed, structured error with line numbers to help us fix validation errors. - -Since both validators validated the spec with only warnings, we can assume that all our generators will generate SDKs without issues. +The Speakeasy validator returns one warning, and some hints reminding the author to add examples. Each warning or hint includes a detailed, structured error with line numbers to help us fix anything that needs fixing. -The Speakeasy validator includes an option to get hints on how to improve our schema. Use the `--output-hints` argument to activate this feature: - -```bash -speakeasy validate openapi --output-hints -s openapi.yaml -``` - -This provides a detailed list of hints, all of which would improve SDK users' experience. +Since the Speakeasy validator produced only a warning and hints, we can assume that all our generators will generate SDKs without issues. Here's how the generators' validation features compare: @@ -259,7 +254,7 @@ Here's how the generators' validation features compare: oazapfts: "❌", }, { - name: "Schema hints", + name: "Helpful hints beyond validation", speakeasy: "✅", openapiGen: "❌", oazapfts: "❌", @@ -275,316 +270,687 @@ Here's how the generators' validation features compare: ## Generating SDKs -Now that we know our OpenAPI spec is valid, we can start generating and comparing SDKs. First, we'll create an SDK using Speakeasy and take a brief look at its structure. Then we'll generate SDKs using the open-source generators and compare the generated code to the Speakeasy SDK. +Now that the OpenAPI document has been confirmed valid, it's time to start generating and comparing SDKs. First, create an SDK using Speakeasy, and take a brief look at its structure. Then generate SDKs using the other generators, and compare the generated code to the Speakeasy SDK. ### Generating an SDK using Speakeasy To create a TypeScript SDK using the Speakeasy CLI, run the following in the terminal: ```bash -# Create Petstore SDK using Speakeasy TypeScript generator speakeasy quickstart ``` -The command above creates a new directory called `petstore-sdk-speakeasy`, with the following structure: +It will ask a few questions about the SDK we want to create, including the OpenAPI document (`openapi.yaml`), the name of the SDK (`TrainTravel`), the language/framework which will be TypeScript, and a package name for publishing to NPM (`train-travel-sdk`). Then pick an output directory for the SDK, for example `train-travel-sdk`. -``` -./ +That's it! The Speakeasy CLI generates the SDK, turns it into a Git repository if requested, and creates the following file structure: + +├── CONTRIBUTING.md +├── FUNCTIONS.md ├── README.md +├── RUNTIMES.md ├── USAGE.md -├── docs/ -│ ├── models/ -│ └── sdks/ -├── files.gen -├── gen.yaml* -├── package-lock.json -├── package.json -├── src/ +├── dist +│ ├── commonjs +│ ├── esm +│ └── node_modules +├── docs +│ ├── lib +│ ├── models +│ └── sdks +├── eslint.config.mjs +├── examples +│ ├── node_modules +│ ├── package-lock.json +│ ├── package.json +│ ├── README.md +│ └── stationsGetStations.example.ts +├── src +│ ├── core.ts +│ ├── funcs +│ ├── hooks │ ├── index.ts -│ ├── lib/ -│ ├── models/ -│ ├── sdk/ -│ └── types/ +│ ├── lib +│ ├── models +│ ├── sdk +│ └── types └── tsconfig.json -``` -At a glance, we can see that Speakeasy creates documentation for each model in our schema and that it creates a full-featured npm package. Code is split between internal tools and the SDK code. +At a glance, we can see that Speakeasy creates documentation for each model in the API description. It also creates a full-featured NPM package, with all the Markdown files you'd expect to see in any open-source project. -We'll look at the generated code in more detail in our comparisons below, starting with OpenAPI Generator. +Code is split between internal tools and the SDK code, and comes packaged ready for distribution to NPM with support for CommonJS and ES Modules. + +We'll start poking around the code to get a feel for how it all works, but first, let's generate SDKs using the other generators. ### Generating SDKs using OpenAPI Generator OpenAPI Generator is an open-source collection of community-maintained generators. It features generators for a wide variety of client languages, and for some languages, there are multiple generators. TypeScript tops this list of languages with multiple generators, with 11 options to choose from. -The two TypeScript SDK generators from OpenAPI Generator we tried are [typescript-fetch](https://openapi-generator.tech/docs/generators/typescript-fetch/) and [typescript-node](https://openapi-generator.tech/docs/generators/typescript-node/). - -Usage is the same for both generators, but we'll specify a unique output directory, generator name, and npm project name for each. +The two TypeScript SDK generators from OpenAPI Generator covered here are [typescript-fetch](https://openapi-generator.tech/docs/generators/typescript-fetch/) and [typescript-node](https://openapi-generator.tech/docs/generators/typescript-node/). Both generators are very similar, but the `typescript-fetch` generator creates SDKs that work in both browser and Node.js environments, while the `typescript-node` generator creates SDKs optimized for Node.js environments. -We'll generate an SDK for each by running the following in the terminal: +There is no interactive CLI for OpenAPI Generator, so there are no prompts to guide you on the way. Instead you'll do the whole thing with command line arguments: ```bash -# Generate Petstore SDK using typescript-fetch generator +# Generate Train Travel SDK using typescript-fetch generator openapi-generator generate \ --input-spec openapi.yaml \ --generator-name typescript-fetch \ - --output ./petstore-sdk-typescript-fetch \ - --additional-properties=npmName=petstore-sdk-typescript-fetch + --output ./train-travel-sdk-typescript-fetch \ + --additional-properties=npmName=train-travel-sdk-typescript-fetch -# Generate Petstore SDK using typescript-node generator +# Generate Train Travel SDK using typescript-node generator openapi-generator generate \ --input-spec openapi.yaml \ --generator-name typescript-node \ - --output ./petstore-sdk-typescript-node \ - --additional-properties=npmName=petstore-sdk-typescript-node + --output ./train-travel-sdk-typescript-node \ + --additional-properties=npmName=train-travel-sdk-typescript-node +``` + +Once run there will be lots of output as OpenAPI Generator churns through the document and generates the SDK, with warnings and output about unsafe access to caffeine... but that's just Java being Java. Ignore all that and look for something like: + +``` +# Thanks for using OpenAPI Generator. +# We appreciate your support! +``` + +If they both worked there will be a list of files generated in each output directory. Let's take a look at the file structure of each generated SDK. + + +The `typescript-fetch` generator creates the following file structure. There is no documentation or examples included, nor contributing guides or other supporting internal Markdown files. Only a README and the code itself are included. + +``` +# train-travel-sdk-typescript-fetch +├── package.json +├── README.md +├── src +│ ├── apis +│ ├── index.ts +│ ├── models +│ └── runtime.ts +└── tsconfig.json ``` -Each command will output a list of files generated and create a unique directory. We specified an npm package name as a configuration argument, `npmName`, for each generator. This argument is required for the generators to create full packages. +The `typescript-node` generator a much flatter structure, with no `src/` directory, just an `api` and `model` directory. Similar to the `typescript-fetch` generator, there is no documentation or examples of any sort, and not even a README. + +``` +# train-travel-sdk-typescript-node +├── api +├── api.ts +├── model +├── package.json +└── tsconfig.json +``` + +The code structure is quite different between the two generators, but looking through that comes a little later. There's one more generator to try out. ### Generating an SDK with oazapfts -To run oazapfts, we'll either need to run it from the local JavaScript project bin folder or install it globally. We opted to run it from the bin folder. +Oazapfts is essentially a thin wrapper around [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with TypeScript type definitions. The SDK is generated as a single file, with no documentation, no examples, no package structure at all, so it's all super minimalistic. -Navigate to the local JavaScript project we created for `petstore-sdk-oazapfts` and run the following: +When oazapfts has been added to a project with `npm install`, it can be called with `npm exec`. This will take the OpenAPI document as one argument and the output `.ts` file as a second argument. ```bash -$(npm bin)/oazapfts ../openapi.yaml index.ts +npm exec oazapfts openapi.yaml index.ts ``` -Oazapfts runs without any output and generates a single TypeScript file, `index.ts`. Remember that we had to install `oazapfts` as a runtime dependency. Let's see what gets called from the dependency: +The output TypeScript file `index.ts` will rely on `oazapfts` as a runtime dependency, which provides the necessary functionality for the SDK. -```typescript +```ts import * as Oazapfts from "oazapfts/lib/runtime"; import * as QS from "oazapfts/lib/runtime/query"; ``` -Code generated by oazapfts excludes the HTTP client code, error handling, and serialization. We can look at the runtime dependencies from `Oazapfts` itself, to get an idea of the dependency graph: +Code generated by oazapfts excludes the HTTP client code, error handling, and serialization. This means that oazapfts relies on the runtime library to provide these features. This keeps the generated code small, but it also means that the SDK cannot be used without the runtime library and its dependencies. -This is an excerpt from the oazapfts `package.json` file: +## Comparing generated code -```json -{ - "dependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "swagger2openapi": "^7.0.8", - "typescript": "^5.2.2" - } -} +Let's take a look at how each of the SDK generators handles the same OpenAPI document, and seeing as this is TypeScript lets start with type definitions. To keep things interesting the example we'll focus on is a polymorphic model. Polymorphism is about representing different types that share a common interface. In OpenAPI, [polymorphic objects](/openapi/schemas/objects/polymorphism) are represented using `oneOf` sub-schemas and sometimes the `discriminator` object. + +Here's a slightly trimmed down example from the Train Travel API: + +```yaml +# components.schemas. + BookingPayment: + type: object + properties: + amount: + type: number + description: Amount intended to be collected by this payment. A positive decimal figure describing the amount to be collected. + currency: + $ref: '#/components/schemas/Currency' + description: Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + source: + oneOf: + - title: Card + type: object + properties: + object: + type: string + const: card + name: + type: string + number: + type: string + cvc: + type: string + writeOnly: true + exp_month: + type: integer + format: int64 + exp_year: + type: integer + format: int64 + address_post_code: + type: string + required: + - name + - number + - cvc + - exp_month + - exp_year + - title: Bank Account + type: object + properties: + object: + const: bank_account + type: string + name: + type: string + number: + type: string + sort_code: + type: string + bank_name: + type: string + required: + - name + - number + - bank_name ``` -Some of these dependencies clearly relate to the generator itself. For example, we can assume that no SDK client would need access to `swagger-parser` at runtime. +How will each generator handle this polymorphic object? Let's find out! + +### Speakeasy type definitions + +Speakeasy will generate union types for polymorphic objects, and use the discriminator to add runtime type casting for input and output objects. The following code is a snippet of types generated for the `BookingPayment` schema: + +```ts +/** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + */ +export const Currency = { + Bam: "bam", + Bgn: "bgn", + Chf: "chf", + Eur: "eur", + Gbp: "gbp", + Nok: "nok", + Sek: "sek", + Try: "try", +} as const; +/** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + */ +export type Currency = ClosedEnum; + +/** + * A bank account to take payment from. Must be able to make payments in the currency specified in the payment. + */ +export type BankAccount = { + object?: "bank_account" | undefined; + name: string; + /** + * The account number for the bank account, in string form. Must be a current account. + */ + number: string; + /** + * The sort code for the bank account, in string form. Must be a six-digit number. + */ + sortCode?: string | undefined; + /** + * The name of the bank associated with the routing number. + */ + bankName: string; +}; + +/** + * A card (debit or credit) to take payment from. + */ +export type Card = { + object?: "card" | undefined; + /** + * Cardholder's full name as it appears on the card. + */ + name: string; + /** + * The card number, as a string without any separators. On read all but the last four digits will be masked for security. + */ + number: string; + /** + * Card security code, 3 or 4 digits usually found on the back of the card. + */ + cvc: string; + /** + * Two-digit number representing the card's expiration month. + */ + expMonth: number; + /** + * Four-digit number representing the card's expiration year. + */ + expYear: number; + /** + * Postal code associated with the card's billing address. + */ + addressPostCode?: string | undefined; +}; + +/** + * The payment source to take the payment from. This can be a card or a bank account. Some of these properties will be hidden on read to protect PII leaking. + */ +export type Source = Card | BankAccount; + +/** + * A payment for a booking. + */ +export type BookingPayment = { + /** + * Amount intended to be collected by this payment. A positive decimal figure describing the amount to be collected. + */ + amount?: number | undefined; + /** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + */ + currency?: Currency | undefined; + /** + * The payment source to take the payment from. This can be a card or a bank account. Some of these properties will be hidden on read to protect PII leaking. + */ + source?: Card | BankAccount | undefined; +}; +``` -## Polymorphism +Reusing the descriptions as comments means the code is nicely decorated for anyone who goes prodding around, and by using docblock syntax it will be read by JS/TS documentation generators too. -The Petstore schema does not include examples of polymorphism in OpenAPI, so we'll add two new schemas for `Dog` and `Cat`, and use them as input and output in the `updatePet` operation. +The types are also defined and exported so they can be used in runtime code easily, instead of defined inline as many of the other generators do. This helps reuse throughout the rest of the SDK for request/responses, and allow for the most complex of scenarios to be handles easily. -Add the following to the component schemas in `openapi.yaml`: +```ts +export type CreateBookingPaymentResponseBody$Outbound = { + id?: string | undefined; + amount?: number | undefined; + currency?: string | undefined; + source?: Card$Outbound | BankAccount$Outbound | undefined; + status?: string | undefined; + links?: models.LinksBooking$Outbound | undefined; +}; +``` -```yaml -components: - schemas: - Dog: - allOf: - - $ref: "#/components/schemas/Pet" - - type: object - properties: - petType: - type: string - example: Dog - bark: - type: string - xml: - name: dog - Cat: - allOf: - - $ref: "#/components/schemas/Pet" - - type: object - properties: - petType: - type: string - example: Cat - hunts: - type: boolean - age: - type: integer - format: int32 - xml: - name: cat -``` - -Then add [discriminated unions](https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/) to the `updatePet` operation: +### Oazapfts type definition + +Over to oazapfts, which sticks to its minimalist approach and generates one type for the request and one type for the response, with anything inside that being defined in line. If there are lots of shared parameters between requests and responses then these will be repeated, and that makes documentation and code suffer, but it keeps things simple. As for polymorphism, oazapfts handles union types with runtime type casting. + +```ts +export type BookingPaymentRead = { + /** Unique identifier for the payment. This will be a unique identifier for the payment, and is used to reference the payment in other objects. */ + id?: string; + /** Amount intended to be collected by this payment. A positive decimal figure describing the amount to be collected. */ + amount?: number; + /** Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. */ + currency?: "bam" | "bgn" | "chf" | "eur" | "gbp" | "nok" | "sek" | "try"; + /** The status of the payment, one of `pending`, `succeeded`, or `failed`. */ + status?: "pending" | "succeeded" | "failed"; +}; +export type BookingPaymentWrite = { + /** Amount intended to be collected by this payment. A positive decimal figure describing the amount to be collected. */ + amount?: number; + /** Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. */ + currency?: "bam" | "bgn" | "chf" | "eur" | "gbp" | "nok" | "sek" | "try"; + /** The payment source to take the payment from. This can be a card or a bank account. Some of these properties will be hidden on read to protect PII leaking. */ + source?: { + "object"?: "card"; + /** Cardholder's full name as it appears on the card. */ + name: string; + /** The card number, as a string without any separators. On read all but the last four digits will be masked for security. */ + "number": string; + /** Card security code, 3 or 4 digits usually found on the back of the card. */ + cvc: string; + /** Two-digit number representing the card's expiration month. */ + exp_month: number; + /** Four-digit number representing the card's expiration year. */ + exp_year: number; + /** The postal code associated with the card's billing address. */ + address_post_code?: string; + } | { + "object"?: "bank_account"; + name: string; + /** The account number for the bank account, in string form. Must be a current account. */ + "number": string; + /** The sort code for the bank account, in string form. Must be a six-digit number. */ + sort_code?: string; + /** The name of the bank associated with the routing number. */ + bank_name: string; + }; +}; +``` -```yaml -paths: - /pet: - put: - requestBody: - content: - application/json: - schema: - discriminator: - propertyName: petType - mapping: - dog: "#/components/schemas/Dog" - cat: "#/components/schemas/Cat" - oneOf: - - $ref: "#/components/schemas/Cat" - - $ref: "#/components/schemas/Dog" - responses: - "200": - application/json: - schema: - discriminator: - propertyName: petType - mapping: - dog: "#/components/schemas/Dog" - cat: "#/components/schemas/Cat" - oneOf: - - $ref: "#/components/schemas/Cat" - - $ref: "#/components/schemas/Dog" -``` - -After regenerating the SDKs, let's inspect each SDK's `updatePet` method. - -We see that oazapfts generates a method that type casts the input and output objects based on the discriminating field `petType`: - -```typescript -export function updatePet( - body: - | ({ - petType: "dog"; - } & Dog) - | ({ - petType: "cat"; - } & Cat), - opts?: Oazapfts.RequestOpts, -) { - return oazapfts.fetchJson< - | { - status: 200; - data: - | ({ - petType: "dog"; - } & Dog) - | ({ - petType: "cat"; - } & Cat); - } - | { - status: 400; - } - | { - status: 404; - } - | { - status: 405; - } - >( - "/pet", - oazapfts.json({ - ...opts, - method: "PUT", - body, - }), - ); +The verbosity of these types can be improved with the `--mergeReadWriteOnly` to combine the read and write models into one, but similar models with shared parameters will still be defining everything over again. + +### OpenAPI Generated typescript-fetch type definitions + +OpenAPI Generated's generated typescript-fetch SDK is much more verbose their either Speakeasy or Oazapfts. It does not seem too familiar with TypeScript and uses it rather loosely with a whole lot of if statements, and the bank account vs card payment logic really seems awkward. + +```ts +/** + * A card (debit or credit) to take payment from. + * @export + * @interface Card + */ +export interface Card { + /** + * + * @type {string} + * @memberof Card + */ + object?: CardObjectEnum; + /** + * Cardholder's full name as it appears on the card. + * @type {string} + * @memberof Card + */ + name: string; + /** + * The card number, as a string without any separators. On read all but the last four digits will be masked for security. + * @type {string} + * @memberof Card + */ + number: string; + /** + * Card security code, 3 or 4 digits usually found on the back of the card. + * @type {string} + * @memberof Card + */ + cvc: string; + /** + * Two-digit number representing the card's expiration month. + * @type {number} + * @memberof Card + */ + expMonth: number; + /** + * Four-digit number representing the card's expiration year. + * @type {number} + * @memberof Card + */ + expYear: number; + /** + * + * @type {string} + * @memberof Card + */ + addressPostCode?: string; } -``` -The SDK generated by typescript-fetch is slightly more verbose, and uses switch statements to derive the types for input and output objects: +/** + * @export + */ +export const CardObjectEnum = { + Card: 'card' +} as const; +export type CardObjectEnum = typeof CardObjectEnum[keyof typeof CardObjectEnum]; + + +/** + * Check if a given object implements the Card interface. + */ +export function instanceOfCard(value: object): value is Card { + if (!('name' in value) || value['name'] === undefined) return false; + if (!('number' in value) || value['number'] === undefined) return false; + if (!('cvc' in value) || value['cvc'] === undefined) return false; + if (!('expMonth' in value) || value['expMonth'] === undefined) return false; + if (!('expYear' in value) || value['expYear'] === undefined) return false; + return true; +} -```typescript -export type UpdatePetRequest = - | ({ petType: "cat" } & Cat) - | ({ petType: "dog" } & Dog); +export function CardFromJSON(json: any): Card { + return CardFromJSONTyped(json, false); +} -export function UpdatePetRequestFromJSON(json: any): UpdatePetRequest { - return UpdatePetRequestFromJSONTyped(json, false); +export function CardFromJSONTyped(json: any, ignoreDiscriminator: boolean): Card { + if (json == null) { + return json; + } + return { + 'object': json['object'] == null ? undefined : json['object'], + 'name': json['name'], + 'number': json['number'], + 'cvc': json['cvc'], + 'expMonth': json['exp_month'], + 'expYear': json['exp_year'], + 'addressPostCode': json['address_post_code'] == null ? undefined : json['address_post_code'], + }; } -export function UpdatePetRequestFromJSONTyped( - json: any, - ignoreDiscriminator: boolean, -): UpdatePetRequest { - if (json === undefined || json === null) { - return json; - } - switch (json["petType"]) { - case "cat": - return { ...CatFromJSONTyped(json, true), petType: "cat" }; - case "dog": - return { ...DogFromJSONTyped(json, true), petType: "dog" }; - default: - throw new Error( - `No variant of UpdatePetRequest exists with 'petType=${json["petType"]}'`, - ); - } +export function CardToJSON(json: any): Card { + return CardToJSONTyped(json, false); } -export function UpdatePetRequestToJSON(value?: UpdatePetRequest | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - switch (value["petType"]) { - case "cat": - return CatToJSON(value); - case "dog": - return DogToJSON(value); - default: - throw new Error( - `No variant of UpdatePetRequest exists with 'petType=${value["petType"]}'`, - ); - } +export function CardToJSONTyped(value?: Card | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + 'object': value['object'], + 'name': value['name'], + 'number': value['number'], + 'cvc': value['cvc'], + 'exp_month': value['expMonth'], + 'exp_year': value['expYear'], + 'address_post_code': value['addressPostCode'], + }; } ``` -OpenAPI Codegen adds a union for `Cat` and `Dog` on both input and output, but does not use the discriminator to add runtime type casting: - -```typescript -export class PetService { - public static updatePet( - requestBody: Cat | Dog, - ): CancelablePromise { - return __request(OpenAPI, { - method: "PUT", - url: "/pet", - body: requestBody, - mediaType: "application/json", - errors: { - 400: `Invalid ID supplied`, - 404: `Pet not found`, - 405: `Validation exception`, - }, - }); - } +It's even managed to output some syntax errors and import some dependencies that were not used. Was it meant to use those imports somewhere, or is it bringing in unnecessary dependencies? Unclear. + +![](./assets/openapi-generator-ts-errors.png) + +### OpenAPI Generator typescript-node type definitions + +Finally, how about this OpenAPI Generator typescript-node template? + +The typescript-node generator starts off looking simple enough, with a single type for any given payload: + +```ts +/** +* A payment for a booking. +*/ +export class BookingPayment { + /** + * Unique identifier for the payment. This will be a unique identifier for the payment, and is used to reference the payment in other objects. + */ + 'id'?: string; + /** + * Amount intended to be collected by this payment. A positive decimal figure describing the amount to be collected. + */ + 'amount'?: number; + /** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + */ + 'currency'?: BookingPayment.CurrencyEnum; + 'source'?: BookingPaymentSource; + /** + * The status of the payment, one of `pending`, `succeeded`, or `failed`. + */ + 'status'?: BookingPayment.StatusEnum; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "id", + "baseName": "id", + "type": "string" + }, + { + "name": "amount", + "baseName": "amount", + "type": "number" + }, + { + "name": "currency", + "baseName": "currency", + "type": "BookingPayment.CurrencyEnum" + }, + { + "name": "source", + "baseName": "source", + "type": "BookingPaymentSource" + }, + { + "name": "status", + "baseName": "status", + "type": "BookingPayment.StatusEnum" + } ]; + + static getAttributeTypeMap() { + return BookingPayment.attributeTypeMap; + } } -``` -The typescript-node generator does not make use of types for unions in OpenAPI: +export namespace BookingPayment { + export enum CurrencyEnum { + Bam = 'bam', + Bgn = 'bgn', + Chf = 'chf', + Eur = 'eur', + Gbp = 'gbp', + Nok = 'nok', + Sek = 'sek', + Try = 'try' + } + export enum StatusEnum { + Pending = 'pending', + Succeeded = 'succeeded', + Failed = 'failed' + } +} +``` -```typescript -export class UpdatePetRequest { - "id"?: number; - "name": string; - "category"?: Category; - "photoUrls": Array; - "tags"?: Array; - /** - * pet status in the store - */ - "status"?: UpdatePetRequest.StatusEnum; - "petType"?: string; - "hunts"?: boolean; - "age"?: number; - "bark"?: string; +It's hoisted some of the properties up into enums, and namespaced them which is nice. The polymorphic `source` property is defined as a separate type `BookingPaymentSource` in its own file, and here is how that looks: + +```ts +import { RequestFile } from './models'; +import { BankAccount } from './bankAccount'; +import { Card } from './card'; + +/** +* The payment source to take the payment from. This can be a card or a bank account. Some of these properties will be hidden on read to protect PII leaking. +*/ +export class BookingPaymentSource { + 'object'?: BookingPaymentSource.ObjectEnum; + 'name': string; + /** + * The account number for the bank account, in string form. Must be a current account. + */ + 'number': string; + /** + * Card security code, 3 or 4 digits usually found on the back of the card. + */ + 'cvc': string; + /** + * Two-digit number representing the card\'s expiration month. + */ + 'expMonth': number; + /** + * Four-digit number representing the card\'s expiration year. + */ + 'expYear': number; + 'addressPostCode'?: string; + /** + * The sort code for the bank account, in string form. Must be a six-digit number. + */ + 'sortCode'?: string; + /** + * The name of the bank associated with the routing number. + */ + 'bankName': string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "object", + "baseName": "object", + "type": "BookingPaymentSource.ObjectEnum" + }, + { + "name": "name", + "baseName": "name", + "type": "string" + }, + { + "name": "number", + "baseName": "number", + "type": "string" + }, + { + "name": "cvc", + "baseName": "cvc", + "type": "string" + }, + { + "name": "expMonth", + "baseName": "exp_month", + "type": "number" + }, + { + "name": "expYear", + "baseName": "exp_year", + "type": "number" + }, + { + "name": "addressPostCode", + "baseName": "address_post_code", + "type": "string" + }, + { + "name": "sortCode", + "baseName": "sort_code", + "type": "string" + }, + { + "name": "bankName", + "baseName": "bank_name", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return BookingPaymentSource.attributeTypeMap; + } +} - // ... +export namespace BookingPaymentSource { + export enum ObjectEnum { + BankAccount = 'bank_account' + } } ``` +This is completely incorrect, as the `oneOf` for `Card` and `BankAccount` has been flattened into a single class with all the properties of both types. This means that when creating a `BookingPaymentSource` object, all properties from both `Card` and `BankAccount` are available, which is not the intended behavior at all. + +Some older generators require the optional `discriminator` property in the OpenAPI document to handle scenarios that a `oneOf` should otherwise handle by itself, but even adding that doesn't help here. + +```yaml +source: + oneOf: + - $ref: '#/components/schemas/Card' + - $ref: '#/components/schemas/BankAccount' + discriminator: + propertyName: object +``` + +It still produces the exact same output. + +### Type generation summary + Here's a summary of how each generator handles OpenAPI polymorphism: ## Retries -The SDK managed by Speakeasy can automatically retry failed network requests or retry requests based on specific error responses. - -This provides a straightforward developer experience for error handling. +The SDK managed by Speakeasy can automatically retry failed network requests or retry requests based on specific error responses, providing a straightforward developer experience for an otherwise complicated topic. -To enable this feature, we use the Speakeasy `x-speakeasy-retries` extension in the OpenAPI spec. We'll update the OpenAPI spec to add retries to the `addPet` operation as a test. - -Edit `openapi.yaml` and add the following to the `addPet` operation: +To enable this feature use the Speakeasy `x-speakeasy-retries` extension in the OpenAPI document. Here is an example updating `openapi.yaml` to add retries to the `create-booking` operation. ```yaml x-speakeasy-retries: @@ -638,11 +1000,11 @@ Add this snippet to the operation: ```yaml #... paths: - /pet: + /bookings: # ... post: #... - operationId: addPet + operationId: create-booking x-speakeasy-retries: strategy: backoff backoff: @@ -652,7 +1014,9 @@ paths: exponent: 1.5 ``` -Now we'll rerun the Speakeasy generator to enable retries for failed network requests when creating a new pet. It is also possible to enable retries for the SDK as a whole by adding a global `x-speakeasy-retries` at the root of the OpenAPI spec. +Now we'll rerun the Speakeasy generator to enable retries, and the SDK will automatically attempt to retry failed network requests when booking a trip. + +It is also possible to enable retries for the SDK as a whole by adding a global `x-speakeasy-retries` at the root of the OpenAPI document instead of per operation. ## React Hooks @@ -660,7 +1024,7 @@ Now we'll rerun the Speakeasy generator to enable retries for failed network req Speakeasy generates built-in React Hooks using [TanStack Query](https://tanstack.com/query/latest). These hooks provide features like intelligent caching, type safety, pagination, and seamless integration with modern React patterns such as SSR and Suspense. -```typescript example/booksView.tsx +```ts example/booksView.tsx import { useQuery } from "@tanstack/react-query"; function BookShelf() { // loads books from an API @@ -714,22 +1078,22 @@ For an in-depth look at how Speakeasy uses React Hooks, see our [official releas SDKs managed by Speakeasy include optional [pagination for OpenAPI operations](/docs/customize/runtime/pagination). -We'll update our pet store schema to add an `x-speakeasy-pagination` extension and an `offset` query parameter: +We'll update our pet store schema to add an `x-speakeasy-pagination` extension and a `page` query parameter: ```yaml paths: - /store/inventory: + /stations: get: x-speakeasy-pagination: type: offsetLimit inputs: - - name: offset # This offset refers to the value called `offset` - in: parameters # In this case, offset is an operation parameter (header, query, or path) - type: offset # The offset parameter will be used as the offset, which will be incremented by the length of the `output.results` array + - name: page + in: parameters + type: page outputs: - results: $.data.resultArray # The length of data.resultsArray value of the response will be added to the `offset` value to determine the new offset + results: $ parameters: - - name: offset + - name: page in: query description: The offset to start from required: false @@ -738,36 +1102,31 @@ paths: default: 0 ``` -After regenerating the SDK with Speakeasy, the `getInventory` operation includes pagination: - -```typescript -import { SDK } from "openapi"; -import { GetInventorySecurity } from "openapi/models/operations"; - -async function run() { - const sdk = new SDK(); - - const offset = 20; - const operationSecurity: GetInventorySecurity = ""; +After regenerating the SDK with Speakeasy, the `get-stations` operation is automatically paginated, and can be iterated through with async/await until the clients needs are met. - const res = await sdk.store.getInventory(operationSecurity, offset); +```ts +import { TrainTravel } from "train-travel-sdk"; - if (res?.statusCode !== 200) { - throw new Error("Unexpected status code: " + res?.statusCode || "-"); - } +const trainTravel = new TrainTravel({ + oAuth2: process.env["TRAINTRAVEL_O_AUTH2"] ?? "", +}); - let items: typeof res | null = res; - while (items != null) { - // handle items +async function run() { + const result = await trainTravel.stations.get({ + coordinates: "52.5200,13.4050", + search: "Milano Centrale", + country: "DE", + }); - items = await items.next(); + for await (const page of result) { + console.log(page); } } run(); ``` -None of the other generators include pagination as a feature. +None of the other generators include pagination as a feature, leaving it all to the API client developers to figure out.
-## Auto-pagination +## Streaming files & data -Speakeasy's React Hooks also enable auto-pagination, which automatically fetches more data when the user scrolls to the bottom of the page. This feature is useful for infinite scrolling in social media feeds or search results. +All the generators in our comparison generate SDKs that use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), which enables streaming for large uploads or downloads. Speakeasy makes this clear by providing documentation showing how to use streaming for file uploads in the README. It's important to show developer-users how to take advantage of this streaming, and helping them handle large file uploads in different runtimes will cut down on support interactions. -```typescript example/booksView.tsx -import { useInView } from "react-intersection-observer"; +```typescript example/train-travel-sdk/README.md mark=7:13 +import { TrainTravel } from "train-travel-sdk"; -import { useBooksInfinite } from "@speakeasy-api/books/react-query"; +const trainTravel = new TrainTravel({ + oAuth2: process.env["TRAINTRAVEL_O_AUTH2"] ?? "", +}); -export function BooksView() { - const { data, fetchNextPage, hasNextPage } = useBooksInfinite(); - - const { ref } = useInView({ - rootMargin: "50px", - onChange(inView) { - if (inView) { fetchNextPage(); } - }, - }); - - return ( -
-
    - {data?.pages.flatMap((page) => { - return page.books.map((book) => ( -
  • {book.title}
  • - )); - })} -
- {hasNextPage ?
: null} -
+async function run() { + const result = await trainTravel.bookings.createRaw( + bytesToStream( + new TextEncoder().encode( + "{\"trip_id\":\"4f4e4e1-c824-4d63-b37a-d8d698862f1d\",\"passenger_name\":\"John Doe\"}", + ), + ), ); + + console.log(result); } + +run(); ``` -None of the other generators include auto-pagination as a feature. +Beyond simply uploading and download files with streaming, Speakeasy SDKs also support JSON streaming for large JSON payloads using standards and conventions like [JSONL](https://jsonlines.org/) or [ND-JSON](https://ndjson.org/). This is particularly useful when dealing with large datasets that may not fit into memory all at once. Speakeasy provides built-in support for JSON streaming, allowing developers to process JSON data in chunks as it is received. -
+```typescript !!tabs TypeScript +import { SDK } from '@speakeasy/sdk'; -## Data streaming +const sdk = new SDK(); -All the generators in our comparison generate SDKs that use the `Fetch` API, which enables streaming for large uploads or downloads. +async function streamLogs() { + const result = await sdk.logs.fetch_logs(); + + for await (const event of result) { + // Each event is a parsed JSON object from the stream + console.log(`[${event.timestamp}] ${event.message}`); + } +} + +streamLogs().catch(error => { + console.error('Error streaming logs:', error); +}); +``` + +OpenAPI Generator and Oazapfts do not support JSON streaming in their generated SDKs, which limits their ability to handle large JSON payloads efficiently. + +There's an extra issue with OpenAPI Generator's typescript-node SDK, in that its content negotiation strategy (looking at `Accept` and `Content-Type` headers) is overly simplistic. It checks if the content type includes `application/json` with the line `if (produces.indexOf('application/json') >= 0) {` which is too broad, because `application/jsonl` will match that condition. If an API returns `application/jsonl` it will try to parse the response as a single JSON object instead of a stream of JSON objects, which will lead to runtime errors and frustrated developer-users.
-Speakeasy creates detailed documentation as part of the SDK, detailing how to open large files on different runtimes to help your developer-users take advantage of streaming. ## Generated documentation -Of all the generators tested, Speakeasy was the only one to generate documentation and usage examples for its SDK. We see documentation generation as a crucial feature if you plan to publish your SDK to npm for others to use. - -Here's how the generators add documentation: +Of all the generators tested, Speakeasy was the only one to generate documentation and usage examples for SDKs. Speakeasy considers documentation generation as a crucial feature to enable rapid adoption and ease of use when an SDK can be published to NPM, and not something that should be left to the API team to produce from scratch.
-Speakeasy generates a `README.md` at the root of the SDK, [which you can customize](/docs/sdk-docs/edit-readme) to add branding, support links, a code of conduct, and any other information your developer-users might find helpful. +Speakeasy generates a `README.md` generated at the root of the SDK, [which you can customize](/docs/sdk-docs/edit-readme) to add branding, support links, a code of conduct, and any other information your developer-users might find helpful. -The Speakeasy SDK also includes working usage examples for all operations, complete with imports and appropriately formatted string examples. For instance, if a type is formatted as `email` in our OpenAPI spec, Speakeasy generates usage examples with strings that look like email addresses. Types formatted as `uri` will generate examples that look like URLs. This makes example code clear and scannable. +The Speakeasy SDK also includes working usage examples for all operations, complete with imports and appropriately formatted examples from the OpenAPI description. This is a huge help to developers getting started with the SDK, as they can copy and paste working code snippets directly into their applications. Here's an example of an operation from the Train Travel SDK's `README.md`: -Here's the usage example managed by Speakeasy after we update `openapi.yaml` to format the string items in `photoUrls` as `uri`: +```ts example/train-travel-sdk/README.md mark=8:12 +import { TrainTravel } from "train-travel-sdk"; -```typescript docs/sdks/pet/README.md mark=16:18 -import { SDK } from "openapi"; -import { Status } from "openapi/models/components"; +const trainTravel = new TrainTravel({ + oAuth2: process.env["TRAINTRAVEL_O_AUTH2"] ?? "", +}); async function run() { - const sdk = new SDK({ - petstoreAuth: "Bearer ", - }); - - const res = await sdk.pet.addPetForm({ - id: 10, - name: "doggie", - category: { - id: 1, - name: "Dogs", - }, - photoUrls: ["http://celebrated-surprise.org"], - tags: [{}], + const result = await trainTravel.stations.get({ + coordinates: "52.5200,13.4050", + search: "Milano Centrale", + country: "DE", }); - if (res?.statusCode !== 200) { - throw new Error("Unexpected status code: " + res?.statusCode || "-"); + for await (const page of result) { + console.log(page); } - - // handle response } run(); @@ -938,11 +1290,11 @@ run(); ## Bundling applications for the browser -Speakeasy creates SDKs that are tree-shakable and can be bundled for the browser using tools like Webpack, Rollup, or esbuild. +Speakeasy creates SDKs that are [tree-shakable](https://webpack.js.org/guides/tree-shaking/) and can be bundled for the browser using tools like Webpack, Rollup, or esbuild. Because Speakeasy supports a wider range of OpenAPI features, Speakeasy-created SDKs are likely to be slightly larger than those generated by other tools. Speakeasy also limits abstraction, which can lead to larger SDKs. This does not translate to a larger bundle size, as the SDK can be tree-shaken to remove unused code. -Any SDK that supports runtime type checking or validation will have a larger bundle size, but the benefits of type checking and validation far outweigh the cost of a slightly larger bundle. If you use Zod elsewhere in your application, you can exclude it from the SDK bundle to reduce its size. +Any SDK that supports runtime type checking or validation will have a larger bundle size, but the benefits of type checking and validation far outweigh the cost of a slightly larger bundle. If you use the popular validation library [Zod](https://zod.dev/) in your application already, you can exclude it from the SDK bundle to reduce its size. Here's an example of how to exclude Zod from the SDK bundle: @@ -956,10 +1308,6 @@ npx esbuild src/speakeasy-app.ts \ --external:zod ``` -## Automation - -This comparison focuses on the installation and usage of command line generators, but the Speakeasy generator can also run as part of a CI workflow, for instance as a [GitHub Action](https://github.com/speakeasy-api/sdk-generation-action), to make sure your SDK is always up to date when your API spec changes. - ## A live example: Vessel API Node SDK [Vessel](https://www.vessel.dev/) trusts Speakeasy to generate and publish SDKs for its widely used APIs. We recently spoke to Zach Kirby about how Vessel uses Speakeasy. Zach shared that [the Vessel Node SDK](https://www.npmjs.com/package/@vesselapi/nodesdk) is downloaded from npm hundreds of times a week.