From f3c10f4ff13222c23a91b5fc0a6d0bb59761c8c5 Mon Sep 17 00:00:00 2001 From: Mikita Pitunou Date: Wed, 7 Aug 2024 04:45:12 +0300 Subject: [PATCH] feat: initialization --- .eslintrc.js | 15 ++++ .github/FUNDING.yml | 6 ++ .github/dependabot.yml | 16 +++++ .gitignore | 7 ++ .npmignore | 27 +++++++ .prettierrc | 6 ++ CHANGELOG.md | 2 + LICENSE | 21 ++++++ README.md | 141 ++++++++++++++++++++++++++++++++++++ build.ts | 28 ++++++++ bun.lockb | Bin 0 -> 86250 bytes example/index.ts | 95 +++++++++++++++++++++++++ package.json | 54 ++++++++++++++ src/command.ts | 33 +++++++++ src/event.ts | 27 +++++++ src/index.ts | 43 +++++++++++ src/mediator.ts | 10 +++ src/query.ts | 30 ++++++++ test/command.test.ts | 27 +++++++ test/elysia.test.ts | 142 +++++++++++++++++++++++++++++++++++++ test/event.test.ts | 31 ++++++++ test/node/.gitignore | 2 + test/node/cjs/index.js | 11 +++ test/node/cjs/package.json | 6 ++ test/node/esm/index.js | 11 +++ test/node/esm/package.json | 6 ++ test/query.test.ts | 27 +++++++ tsconfig.dts.json | 106 +++++++++++++++++++++++++++ tsconfig.json | 104 +++++++++++++++++++++++++++ 29 files changed, 1034 insertions(+) create mode 100644 .eslintrc.js create mode 100644 .github/FUNDING.yml create mode 100644 .github/dependabot.yml create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .prettierrc create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.ts create mode 100755 bun.lockb create mode 100644 example/index.ts create mode 100644 package.json create mode 100644 src/command.ts create mode 100644 src/event.ts create mode 100644 src/index.ts create mode 100644 src/mediator.ts create mode 100644 src/query.ts create mode 100644 test/command.test.ts create mode 100644 test/elysia.test.ts create mode 100644 test/event.test.ts create mode 100644 test/node/.gitignore create mode 100644 test/node/cjs/index.js create mode 100644 test/node/cjs/package.json create mode 100644 test/node/esm/index.js create mode 100644 test/node/esm/package.json create mode 100644 test/query.test.ts create mode 100644 tsconfig.dts.json create mode 100644 tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..4598474 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,15 @@ +const { + configure, + presets +} = require("eslint-kit"); + +module.exports = configure({ + allowDebug: process.env.NODE_ENV !== "production", + + presets: [ + presets.imports(), + presets.node(), + presets.prettier(), + presets.typescript() + ], +}); \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b1f0f67 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,6 @@ +# These are supported funding model platforms + +github: [jassix] +patreon: jassix +open_collective: jassix +buy_me_a_coffee: jassix diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e7d6a19 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ + # NodeJS + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a80e4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store + +node_modules +.pnpm-debug.log +dist + +.vscode diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..1dd0c9c --- /dev/null +++ b/.npmignore @@ -0,0 +1,27 @@ +.git +.github +.gitignore +.prettierrc +.cjs.swcrc +.es.swcrc +.idea +.vscode +bun.lockb + +node_modules +tsconfig.json +pnpm-lock.yaml +jest.config.js +nodemon.json + +example +tests +test +CHANGELOG.md +.eslintrc.js +tsconfig.cjs.json +tsconfig.esm.json +tsconfig.dts.json + +build.ts +src diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..2e4bf83 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "quoteProps": "consistent" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..598410e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# 1.0.0 - 7 Aug 2024 +Release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c61fa0f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Mikita Pitunoŭ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff3f5a0 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# elysia-cqrs +Plugin for [Elysia](https://github.com/elysiajs/elysia) for using [CQRS pattern](https://en.wikipedia.org/wiki/Command_Query_Responsibility_Segregation). + +CQRS Plugin for Elysia is a lightweight extension that implements the Command Query Responsibility Segregation pattern into your Elysia-based application. This plugin allows you to effectively separate read and write operations, providing better scalability, maintainability, and testability of your code. + +## Installation +```bash +bun add elysia-cqrs +``` + +## Example + +```typescript +// commands/create-user/command.ts (example) +import { ICommand } from 'elysia-cqrs' + +class CreateUserCommand extends ICommand { + constructor(public name: string) { + super() + } +} + +// commands/create-user/handler.ts (example) +import { ICommandHandler } from 'elysia-cqrs' +import { CreateUserCommand } from './command.ts' + +class CreateUserHandler implements ICommandHandler { + execute(command: CreateUserCommand) { + return `New user with name ${command.name} was created!` + } +} + +// index.ts (example) +import { Elysia } from 'elysia' +import { cqrs } from 'elysia-cqrs' +import { CreateUserCommand, CreateUserHandler } from '@/commands/create-user' + +const app = new Elysia() + .use(cqrs({ + commands: [ + [CreateUserCommand, new CreateUserHandler()] + ] + })) + .post('/user', ({ body: { name }, commandMediator }) => { + return commandMediator.send(new CreateUserCommand(name)) + }, { + body: t.Object({ + name: t.String(), + }) + }) + .listen(5000) +``` + +## API +This plugin decorates `commandMediator`, `eventMediator`, `queryMediator` into `Context`. + +### commandMediator +The `commandMediator` implements the `CommandMediator` class with these methods and properties: + +```typescript +class CommandMediator extends Mediator { + register( + command: Class, + handler: ICommandHandler, + ): void + + async send(command: ICommand): Promise +} +``` +###### * this is just a sample, not real code. + +*** + +### eventMediator +The `eventMediator` implements the `EventMediator` class with these methods and properties: + +```typescript +class EventMediator extends Mediator { + register(event: Class, handler: IEventHandler): void + send(event: IEvent): void +} +``` +###### * this is just a sample, not real code. + +*** + +### queryMediator +The `queryMediator` implements the `QueryMediator` class with these methods and properties: + +```typescript +class QueryMediator extends Mediator { + register( + query: Class, + handler: IQueryHandler + ): void + + async send(query: IQuery): Promise +} +``` +###### * this is just a sample, not real code. + +*** + +### Base Classes +The library features foundational abstract classes like `ICommand`, `IEvent`, and `IQuery`. These are essential for ensuring standardization and polymorphism. + +*** + +### Handler interfaces +The module additionally offers a range of handler interfaces, drawing inspiration from the @nestsjs/cqrs package. + +```typescript +interface ICommandHandler< + TCommand extends ICommand = never, + TResponse = never, +> { + execute(command: TCommand): TResponse +} + +interface IEventHandler { + handle(event: TEvent): void +} + +interface IQueryHandler< + TQuery extends IQuery = never, + TResponse = never, +> { + execute(query: TQuery): TResponse +} +``` + +## Config +Below is the configurable property for customizing the CQRS plugin. + +```typescript +interface CqrsPluginParams { + commands?: Array<[Class, ICommandHandler]> + events?: Array<[Class, IEventHandler]> + queries?: Array<[Class, IQueryHandler]> +} +``` \ No newline at end of file diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..8d6fa9a --- /dev/null +++ b/build.ts @@ -0,0 +1,28 @@ +import { build, type Options } from 'tsup' + +const tsupConfig: Options = { + entry: ['src/**/*.ts'], + splitting: false, + sourcemap: false, + clean: true, + bundle: true, +} satisfies Options + +await Promise.all([ + // ? tsup esm + build({ + outDir: 'dist', + format: 'esm', + target: 'node20', + cjsInterop: false, + ...tsupConfig, + }), + // ? tsup cjs + build({ + outDir: 'dist/cjs', + format: 'cjs', + target: 'node20', + // dts: true, + ...tsupConfig, + }), +]) diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..e032c56696fc63744342d5697ab2eaab9d8fe651 GIT binary patch literal 86250 zcmeFa2{=|=`#yZ*CM9Ds%ak%?2o2^jLWT^PG7ph?N`xq+goFqQnKG84G>Bv_88T-c zG7}=2{MX{%?{EL#<9(k;Pv3VO-*+6p>v*>F-fOS(ysmYvwbxpE-}G>>33$4@30T-U z30ON_V72gc+64|i2WLxr8#@PUJ{xB@M{^Ip3xd1wF&K=6{m;EiM@c@9Fj-^jk2zdb zFc|C~T`+7ia9-3Hj89ZB)Wid|FqpB;pBN1BzhMCGynPt=VG}4Yn1F-e(=LD>?w&5N zkPZm#1n2IamTuX;4lAdA8P0b~fIaHr#nXKvGct z4d_q;Jm=tS0U7yN42A}r-vbE!o+sSmo8r*R*3Qxv)U$K9zGw|_5tNgIdeuZ*dM@T3 zwtV*1-nRiC`c2=l6{n+}lbwsXyZd!e4&yciO^3WffMfszaOEe#Md()?AdF80G#b|5 z0T8x-5Qpuca5p%20ZwQES_6ds;=y%@2H+y(X9I-1ztb54m0=wJuCs{Cb1y*X#|#9` z2v7+iJwPT9EG!oS=X(K?fqKv%5lAXT1AtTjJ={c&!u+~;I(b_|StOLjj@F7509s-a9pcO!NfF}U506c(0 zVt}yT6bJ^EHvxoxiU7j)*tlC?uy*or&j9Bzu2m2#%#vyBFuSb z&maOET*7?^KkfbhDbwc9ys zAq?gxPRGujZ*xeV1i?a`IZzJAg^jtphb!>!X6@+?$8!_74&#ep-HNvtAn1yKeq4Pk zJGV`tOtvjQcITX&-N3+gcQAK{;~0!$OLKtFK+>R29zfXs6o9b(F*ro-gB#!+#uEq- z#_jB6?P2F=jd8Sdw6o;1ak6%|@U(NV!Ym)y8W;Tl;r&#H!^b#G#9;_PIBwhkf-Vj) z!J!HckK&LWhh#YX#<^Ah6AnKBgz;D6Fdv5rI1B{{x;Eef4y|ygg+m#n+}gtP9LR?w z=qqTJw$Ao;R_1PQ*5+0qmoDu5Tm43j!_NT0P!2G6@&@yurLCp80t~8xC*!n5RS)7INSpe#%}=-j(2r{Fdt$#Jb=T0pFe1i;QB*kE02f{ z0jLkhizK+~V1I!r=>iO_7@Wg#YU5z;;b9H>NpP#4|FJFq=iT`>hq$@hITv#`cWXCL z4#&YN;6hA1zSSNc(XIZ6gVfx?9b@h8;%04)!SvwD%`KhXoB+rC{H9w>sTc;s0Qji@ zVY@njUpP)Z-R$_zgItf{&g%feyuAPj{T1SnSaPc!8xmW7(?L0G2Xen8fOFW+TR6Nc zwbd>M>kBXpOe8pm*L`sq0uYX0D{E^PK09|z2##lC=VZkPCM18EEq!+!y1*q5tSkV* zNcX_F%Whq_cK7tK+ibt9r?s0m7%%1y_Q0Q^+*Uhm&E5GxuFqLJc)Qzy8%;}oD}QnT zDS&@&fUv(UoxwN*`J)5pl;9j(+l+TXVJl9F6I=cQ0m8fp1BCJG-0;1>OL=_Hea?VG zyusEM(T$Ba@^&6r<4GvEmG0wk=}C(jO7e3Empnf7} zcjvDS`3@0!Dk=vZ4)XR_T6S;ne|@;6>F@Be?{RV7JI9Ex4>Xn(vk&R@YEw;j$?P4B z7poxl+cl-fES+-mTbp~uuMRDVoxv1m9UOx2l0H}axg>SH``}J=%g-`i>>>;Ph{@^s zc^89n@MFv=$9+@7J?+ZL?*VRpZdGlc;& z`!Gl4PWESpcu5}>zutHG>45jhgO{3yRXK6*(`km_m-g$F*7m~n@n)}O7E^fhsGEyp|4c?QBfmAO^KY=*n< zmIldlOIch$Mn-7-zK~t~uFm7vnhqaNl4r<9g}Nmk>;|&a^>SjiX*mIeIcDDqMRBYfs;ShjHs8sWKt#J-?q7x9P&aw`zKkZSCGvo zehtqfyY3D52JILXt~iv~E3QkYa$!W}gP?l2G5P)zI?}O+&Nl5nR&7JKb>{iGdpoJQt;65@ z85l5|eTimr(QVeB&~v+-aVL3}kkw4~f}Rj_xUYKSTjHq$D(a2zko$mZabUrn_Vd$D zb;Yci$YBvZv0T$|O1!v2V+9V9xTI+c4oP{h9m`K!Qz@9Mxu!H6%+fYGqn^p;D%u&l zNgqrirB#?pF1S#qCDp>q#7vo$%qaN$!<40A{b~SH?`S65L3z{E0$yumZ zd8oLA@xn$^KVG#_Jbb73broh_>A)1e4BfYHhqW4yTG#K;j8mL+tRkkA;w{6Ac^8V! zvo)m@yO{RS|NJ~LXPcm0g6UHta{QyG-aSdZUE=e4JSuw+t@0Eg%)_knYUw()pJ}zdWbOrI@>}8pRLRhw>8=BRb#n{w$rmRIyrc9+T)z&7e)M>(-bfsgTBQ5a zuUYp?FUQ{0hojO1Z1hySB+2mqeg0?&_a&pn@3^Si7sw&GV_g~15ppPAZfv$aSI2D{ z;5BSR{%ex`%U6%RcOAL+S>(i6Xtbsbwii5M$+7mI@^Uj6&eF(l^_uIVKN8UsM!x+hD@eUMK(yfol&KwUa2Saz%MDI(4F-4Q9JvCALSk- z4X8cKdn05o#p4$~kxuc{VZ9@|?`krf)xaHN^D4F) zrFi-k0dissmZ;)sL~#GGo%r;KhKzrz>9 z_J*pVxZ(0`xpR4(m1GkSm5QwvdpMdngHF!M&=49ht21A1Ap2xT#!!0NUCDP(s)fmL z)|u09)y*kN>bf{2b_9E03t2JNy!q3;kw+j)s`68?h2KSwg9CRdDTxnn*wVT^xvi4L|^o6aioly~&05z;+DbzX5z5 zod50ckU9vT7C1Z!_%L^{?04Jm3HUJoNd6H0zeB`-F^-Rnz3uiP!e;=34m{p(j$h~# zDc`RDQ~$>QJ05Z!@#7EpiYWic^}j=e-wOD0fWO^#Bf1EG4@ew%bll|slkuwx_|kw6 zx!}3)xBEXI@WlZi8Nb_Y2NM4zE`9);zL9dIo&ODydaPjLhx0dhX4|yiy?^WgAKiby zJO0Z7Ujdgt7yuI2Ry6dV>A8T|@jQ;QWJTY#LJj zcRENt6VUN6e%OD%V+j8N;KTJF+yR zUEXFRalYW^e!z$GCz5~U`rjdvKSuD<0-1k*CkBLX1Nd^lKXU%N`6~i^*nf}*517a8 z7~*dp@ZtD@?cc6%sE_cuz=jF>N9K?1_(t=saQuHd{!##62Ka~L55|wg1%3RLA@$|~ zUjgu8U8L-`C8YutZo#D0&;R}NoiZFf{JB$G- z|2q++o*&@D^#|epZvNf?zC12|^c-Sg?P1Oj9*oLwP1pEyW7q#Z9>hNdcq#fP_m4f` zp921o`ySE#cYptWj?{Y!_#n*9_QN{A+x`i_hx;F>hxEa}`}$Akh<_IF<%JwB{@+={ z5WWlGgRcq#VEe(x-;O^XytFs^1O77L8~g$P6!?e_||{GSE0jTO#guY@DKUKV8Z>A{GGZ|5xc0+q zNI6pPzadi3AMnKhADO$6^8dy|%8`0yfDhLXcn;^_?Y0BqPXfLO%0H4v#Ls_2gwMpZ zHUGnBukCW7Cc?J{e7Jvu=l_KN0PvCg!5kvG(8pgHQjdyxYyC&o@9nOE2wxBIp?`Re z^x@qn?uAu{l2aJH!^O1mv003u>IT3+rPIz1@K|} zpa3-vK^cKM}pZ^NUN9Zs&-kE~xXgx_ud zEx?EKC({2&T-$AbE#M>jzwP>l+K8W>oLk>N!ZPqE_1pWOT?ev_{jUKf0F+;z(4s1`D5VzGrq+i@^k)>zx0QEF`hqb|CK-FH~t}? zig#=M{-^hk{vYy_|ByfShkTwx|MUL)pWHuAf5Ge-*Mq}gfGbdXM9`0R{-%N`-koL zM*Gjh@nJmwWc|ex*qZ+#|94_Q+OG=uNdBPz?P?(WtAG#w+x-4?2jG)|V>^cMivSts z4~`#Lw%r^cd;;+DMGo+xPgwT5<4+6l;r@Lm;Qo{G({~vB%?98jaU%%h`71->R|hwb z?jP_U0KOLB!~DTxyN39u01vO~fDii}@_)DgO#ojR@FACEyUsy9#D5mxYvTC7LH`&3 zE8xolKICpUc32nje;jl^j31W4=lAUz!oLIfaQ_Z{!@ft#|4s*~*8=!TfDgIw*lrFH zKHHJ4=MTgm)Yy(8d}F|e`$u>Vxv(F%V+cPB@ZtE`E)UU0_$z=9_n*Ybg>53jR{{?| z@cj?s|99Je74YHvTgZoV2cq+LVIclr0Y3Wq@pt|I0DSP@=J-Lz(BJun*Af3h$1oTf z9DlpM(flaDNA7>f->wGYe-7|r{4j6Z<)Zy_fW-%S{)FY*)j<3k0kRA#e^6sPhVZj- z{O$HTqK)t;0bdUIN8&~hsrTOy;R}oY-}A?RtN-ui2;UL#K^FvUcilyF5Pmw~gIjR( z_vf}7JJdkYE?Lzpx;#=z%Ec=~%0O4N(d_~|Nx%VM%#}NK&z=!)6==-1eU%~l@ZU3En z0P!ylCSL{MABh{mziR`+zYO>?fDe7czC+5l%df!kk@e$u`Baiy>jyG^|IROR9r2?M z_;CG)@o%^7Py^wI0zOVfOV0wzv3a+kh(G` zKHNjYJZ#4h{zJfr>pyaiwBzqIkot>&57%#4ANtO{s(Fx<*+?}Wk@|+z*hk6hwl!MvcKXX*O0n-fDiYN|8)F}0X|$mVeIhO zt|9(S<+pzS5!t)^ZvN8%AI^V>e?<51;y~(61HK`S4>{Xy1HxAT$%pNS+<(&l7Xe=m z$7cl^>`-VMhWLLD_;CM;w12xXApAAJ2V0nb@wfX7kMNaFY|X#aAZFNy+ucVa{BXdR z1^(gu!2pi!&Lar_J>bLq!~F~7Zuj{gRNd?^BwrAzz(EHu0D}ncDHeDF42^J%vV#NW zoCAmZ0Ydvv2(`i244VcK#(rq)bQ7T-*n@BCfwg$kAVPicdTi6s2>D>oziDWM^+7H+ zd0Ld)_H6FAWB@{*EI6P+gznmZj@s7gUm~ohiK_<@#tYubZ5kS(e{FEU`e!z;ZXqn!!=0lM_NN`L{QrxH z1+h8e;`>hs`^g=59U>gx7r_DZ;{y(85Mlc-;m{X{egL7N5!Uku2gJ+ZfCdrj1%d;X zUjYX+h_E~e9I!kX9MI4R%R@Fx|1-kW4i0E&g#ACiS-OR=-U99%BIGaP&LP6}ZA;SCT8}9r+A=LYhyZ)aL`u~Z$4iVP>g*%4`&o^-A5aInu2n%rt`8&Wl zEF#90{}saeyKwa&!jB|4+>Jw0SO^S_un7M9VUSOOt4E2e2NA|ai#vx1Kf-?p4%&Z0 zc!?hTfO?Do;W-ohfkWKK|6LzJK4icF^C1fkXb|CeRDdVI&nno; zmjCbi2$BHIZVUbr;r;!e)*N2fk6ZKX|NHCXFY;e- zLjQl*@Gx7tXld*(hJkw32e!va0{Mb&kZWR)oEUL{RG~BdL z4Nrk-Bw9R#?17CW(a@)Z@2GNj@Uv69f*289xaK2<)zmX3CY+DT^Vd!*_7+y}mBA=@ z(bQVxC4Y%DR$?L0{1v4pdiEI8u4gR1`?>dKTzwqZ(IzaU{!VAG!*o}2kQfj`bm1C@ z7*;?&H&cLzAx3dy*?8@;$4gEB0k(-}bMDG)nIfZCuAL4$_`IF_?B4E6BN6e6vV1WK zd+9G>%g>N3nq4z1oTzOFLWnM00}#W0R_q|-Ee{=>pgeTg`;a{M9P{%_hV7Oa`zAgq zb1n)>7JvMH%I4O)%#{{~I;r!AvzPe_%$3t>(xXl{w@3wvz-M|ymk<#Jh_zhJEr0f_ z;N{o->=|G9_v>_h@Z)~+%ba^mr+7@2{v?5-B)4Wk%*nC$j- zrd9q?ZMR4ON*At4h+!Y6&9amxn_nFvcTy!AX+04+@;$Xz^2Rqt?U$G~AcVvV-$fvXZEjK&nV-0K6&vz6^~=+u zguvtLH@+D9pK@`d7c4mIhsjuL>H6>%--eLs>#v*pE|#;|2lG1`^m$a7%oVRJ`@m-e zL>E4TAcj@w(`5M;y8PYi$=(L?9fwNg%bKRMXCh=|VqgVS|CXLrQsm{FR>j2N z^*X&aITiaofaiK))Fp|WY4`qxxgTwf_MSvZSnY<&)0ZT2B^GjMMuQ48ZOCpuT^xgB z3&{g1A_@>Y%lJdVc>fIDVPd3U1k z{O3#!_K~_m-jtgA0_L-djKY^^_n~yr@9(f4*oB>+)HdExJaTw+Nz&%sCGD7>Pgvdr z`F`sVEVdv08e%{Bpyb)cD8V>=~AF|oh9yeEJ(chP#W51#N(b(9yFu>d3^Lise>ayzuUoz1;<<87V`!< zPAiPiXpY^sxO2tt)a1c>0^_!_^AYxK*@Y-wO0@2Eist5bFIzdjbjc|%~`BV zft59-wo~4ga#satq|bM`?|r){zW>A+=j8P3Zl_f#RT8Ez8CB*xjjr!CuC^u`he2O zHZVs;e4=2*oZjnREqcS<3Q=c_qvE-Z~5APJZfjrWs}!`o=j}U zbk#GT*^04p@!L*r-qU5?-i0Rb4!Ee9tC^iKKQ*48DcJSnZdN8gX~cJnj~|aI!GEgc zU*mT#A_@?@uyBut@ob&n`oNH3Zsi-Lt4`Il`8xz(SQDh)6(LS?Wu7X@HSP|2D_F>= z-ZL6Z=TVYsa4PO;v~(lW4<98)^m;*q)=d*lKBjW-J0C~!VZPJ|i9-2-+&C9OW#Sl) zf+;f7Q3KQ4?`X?%^$X~@)NfDJU)z!BL>-dnKQ5NpeZ8%+OkoNYFWg%rhBYoAePdFi z+QKcP<@elTgp;2u&-T+?(r2xg40m7OD)T5N=QUq>zKb+3$-Je{N<7C5+w~@}#)K%x z;259L_Z21}gxtq)O+pO2zeU)(W@gvGQ5}nUOG#nh-EVwJ8VsBQ{I1Ir?!Kybt12cu z^Hb#k-qBF;7osa(vx+s>veX`65?bv(w9--EHX6(d(&tuGIF3Ir%Gi`rj z4>XW?8PK|n>0hk2FSq98{_G zd?MG9#J|5Yw&JtzfkgZkLPI*)2Pj=ew62K@aXI-u;VJy743fG$ez`+?i+pFpy1a~- zI{FOHoH9G|#pFrnDQ2fiChCkKZrL8gcdzb7qzM@9(AXP9eJCG2&S0()!wv)`YtHRC zZ1N(gU*>MbJIEg9u&2jhlIWbFJf_ZdB>$tSu`|LlNaJ&| zvF!{F7L^7P(8x z9nTs&+VS39>XT=tY53*6#?{}A_pOKPjENZ^AQ~d%YaFZzd$fYS-`LQ)mY4TRvi0T2 z+V6@wC;xF?**oX?%zCQ(jr4QTQ>nRyKF8>rWn#}f-%s0qawyB0GQ`TXqU{FpY)}cJb5ls`FHcgAwLzlHq`$*`K(2X=QB-3$C7HcKG$F@H@yXWmO@l)Iv z_PaN*xc|JPETm0S;Xi%+)6a}H>%+3U;2ehJ0nV9-VJq`)o!Di0P?b0H&UMeHjpV^(s;l^)h*5tSwR4yFqwvFXyB0r{3_Ho{$fWga{*tM{Y zUWz;7wct(%gORe+lVX%6adJQdiI)?tdzh=csV4k5>Ap(5sb=}Z8G+7KWJQsDYK*>) zJ~xVIBKGczY?azM)3q4#bTVmxUAu`K`@*AN*o4Y{XNQ(~&?HLt09v={kzXFWjE3RI z2u^ut+w)wfo6KICJ<%XN&1BFQ;zI7~bF|OMI0ir2Bdz_};kAIt;1A<8_nv$tlsOb? zViiLL*GeSbgJ|6ol=xEh-)}4|Iw$Nhe@n>BV@KUH8~N*ks7@>QD`Dw`gQkQh29Ma> z6(e<-s3jSSB8zMMsqQP#I*7cZu)kkutKmXB@Cra$2^clxkZ`y}x ze-9M+p6D9oCwk${gSx??1)(#^vX_YFNws}a&vxxv8g4KJUz+xcq+=Q(9eauXx;nK5)*Na&Q^K8RqWJ; zd-E_#`voXUeD~H(UDN;B;-V7B9GaXgWz5XQ;mj!4YEEw)cJX!PxA!I37~x@>Pi9-+ zL2ZrSLulQ7Ztr(8FND`hI8KxDcX6D%KT+$K;BSeY*0(2nnwX|?^Zfld$MKi<+q=>~ zzkk?z<5L##K+qSu^v)T~3yS*KY@mhYfe)?A(Y(hzHfiNzJTvw24KLAxL96q{c$HUF1X9?9qEJRC;ratQsf z2_#^pKC?$u&XrExmiM70hj(Dxu645e4tg47WY-@0Kgg8hyZ@+$J+_>0cNb&I;A*A# z+DKNeN=EZy0(#yNLhD}n*>fU|O51Bxf|#_ZEKo7^j?Al^va=q;SILcjyjgT7d91dY zKyk%!_5Eb#34ZbJ^aH-ltkqX?Suc-%mV6)%_c+LS5Jv01>=Cm)CFEdl_}Z@M$B)lR zX76uulNhkBR^^V;nXw+p!!0QRKDPFnbr8{Ds#==h@4uJ~6X6mABLnd{DZ_(Yog3fr*UKfdetdu}{|mLN4<})q8ft zNGX=?TK|?ka>VH)trAb+k@XL<9cMqQzasoNetnsAF!h62QANIrrqnO^-3pRlQM9h7 zm~G|BzF4xxtNXM(=y{Zmm{U^Irfo>awK}eJoLX5=X?5Hs%V}tQS$l*(zmlV=H1w|U zz446wF$DNV)XUeZQM&M*7h>4*uuGbfIxDtfU6&69y-*eVD)VB2;E}*cjFEzl*N*SI z@451*?Qz8Taol_M>t+J!{&PLzIo37=Jvc*PM>fLNv@ag6nOLYHJ3 zD80**7kP#S#HiQs=}$lX+1DiU;HCB9@PI&FYP!)Hi`~BWEv0qY1FVdK-q8~nX0c@S z?-lew=}Mq=sm@+FEzQ}?q<*&1ZOro)eL{LZL4LmG8~fEytHvJd>b!EE?hX}i1CvRQ zH=U9QSlXvvZgl0~vw)iMzSe739&J5mVTj@WPZF)0E;MEq)Nk>4;|uS`8T}vo66DK@ z2{h?b9yLiFn^i5*^IXgr68_+pGi~(qloa#iz2H^WUd?YiFD*}UP3nBQ5xyDkmM+}0 zB8Huvclwo_%`K}RPSbO%Jecq0p~fpRkq6_LIFE^6@2@z1^tkpD2JRY3_7n7FkEoAc z+1s2xPa;WdT-Nu4SrX6u6A;1!U(E&pgnM_yu#KWe1$R;;ghZO{FBptbJ(&N>OLBe5 zRia<-!^Nf3S(PDk4_H6m4dT3^N5lH-NL^v@Ws+(sb?HQ_l9h?G1GVs72GoV`CS?#& zfLQij?q@R#PRwcBeccz?9v!~FfKfK}#ScZZ%Q=i`E{_Ngn>-tP+1i)8tUB$x@=anv ze`m|>p`9Zmf^_>Nct2c$&w@Y~ALdsUt^2UtT(J5pO+`zy_Kmd91)5EshvikZh6k{G zS0z5voDQMJ44<*5I%p@cNA8?BjftxLqqg9~+;?xXUg%F{oZqvxj|zbQUlsVfX8$s* zUU`yn`}mNYNL{L00>g3gdq<1;2fRk+b)Di9YeOeRG*_wVFC;reDn5B$OLix>2HSQk zsf4J?=HPR&qx;sXYJd`mcT-pXU#cJvm#39)?3=BikiCPnt!=!RjD( z!w9#F2d)S!EL?oePat%?be^I}#;!w%J^5~{J3Y^{$TGuE(oZ&Zi7^2H70|jAPpeH$ ze_gcEKb#WkP{5J>QoVC`^vMxUn^zAV_^D0=8lDX3;F{_Ysk1#d8s2ukhAdf%C7P|W zrIh6}+aBK*xVGbgZ@pn2;P$v>SjBqN|9A0>nypwuKTNROS$; za){!ccviLR{CNQ}nX$spOaixT-@Xou$jHi0IB@3rB3JRfOC&_K>zURaQcSUIM=Wx# zb;me-!|B2#E1`Aoc=3~jT)W=Tcegm|KFht+DXdKsCVW4wt@#YQnTL~TU60tGFK(9F6ZNnDxn*Lcs_V?8!{HpoX>pqK4NYR^3$?&!tpN{fDuy%jMOOy{H&yWl%BJn&l? zFu%%Z-6e+ubW+ckoM~)r=0YCRoofk<|DMuAP-{e|T+6bs3u9RjVf!_k(uhdlF6cA_pJsWHvti=Hk?fe!|4KL_-||c%doFBGVSZHbbK1Uw3?d5a4vsE^T2@*!fgG9(IMyD z?Bt7C@BeHO@s7-WA{c(Be$TT53tmcNO(v7or;A+ij9#oG&jCcjZQ`0{WhR>w9C3F zB}w=W?I65&k#$HNt@}%nvfAve(c$+yp1-PUkC8iVUs|W*+GzTA{MRZ6Ub@>$nWEu3 z2V%*v+ha%hlsXcAUN^ui;;P`E)@|{`F9>^!($zrgJ`9!Q`dQ{7bv~+4dGx7=oqGRxsdoHEv}&3beC ztX%!Fr}=v0eS!3W1^@2xELT#3l0%qFKlxAHr?_{jrGF$ju$X;s(T1{I zH-#J-OceQhlyPDlt;ChM}0<;XP)p!k*c0tmE{tP*!o?wt>>Pq*K;1M{PWQ*1p>!Pq0r zk(XZp-|`mgnTOQWu&X)*ao7mfQ9$UvGUzv?EHq4-yaW7k@`|3G@evtPI=dO zaVgHNf9Rz&7vGy(9ye>BC#=&*2aztYim$u6Yr6Y3y1qo|8lrVAUO&9#dF@aky#+s? z{_&#+`34-BR`Y!X$?3R#&XLO)x|D?V&FzlQzwuqOJWY~2ZHecpRM6fM z^z*S1T6ZS;dze(*1RHP1JEkjN4m%bJ&M6-Yn0@AP&GFogH{~PKAy1X1M@D}5`aESX z5mEfrZB}$|=;i^ooIFg`$sUQvsCdt!bw@SR{7;Hqs$PoaF)kG|;nU_RXI0P>nJc+e ztsu45HGS?}C2574$WXQFsK+h|QI(a~g-4Uat!;FO&eR&;bWKL-8l!cW3Oc9=b{bvr zEv@U27GVAG;jBE7u``}R4sC_OT`rE}6B{12A>3*Qf9Z(m$P~k(o;_ zP*$QZjJr6S3bc?sn4@)5%>1?K%?#=(Ztv#$d=AUFBQy1|W1kk;N@lMEeF7t4eG1jx zHLInz=#}L=0}Nzu?{Hl{!q%o|U0G~!?!6BaI$jI3?&H1k1G=$no+*<|&ljUFy<(8G zH`H_)?8Kf4z^{BmE2|y9^J)YAm@U=#KJxt zWko1JJI;{4o3e8L6`PM_;}?`JT=NmbDxTDkx>sC~QYk~edb9i7`z~^Pqi@4r=IqJ6 z3lSFy5(GFNIYra)&DMN>)`xlFJJf!RTT+d;*;BXUU_#F7@iicX#A}O)0>m1sbY`_i z4P^{+*$5wh7F}j-(QyJi`yE?&_S5yf_pi=xWYa~oIcMc>F^0Mx3Qn1_{P-<3J-5f@ z>h;!4;T`U*C|x_WZh%nYV&*B*p^ehmyf43A<;wX9?_`e4JEd1Wnq>JkJ68EaX1~x* zMk1~D&(}=Fst(ag1{xVQ=}LB$wQ?>CS5}~O&!ct6&gM5S@!Hl;XYJ@8yuR<_n-4nm zJvXDC2(GU`Ee;wU$JgxofhBt392=uL*z)RCX5|>wY=c<&$KA;h=9k8Pz&QcQgFRaJ zYWtDkti-qe5z4$S&e!LN%|mQ+8FaZ?X6jzo1~EPF@%H0$eirV_;!E}PVokk~eKgCB z<`bTb?`4MG1$YRv_n>qg(7H=w53LiJ@M2^!OIX%6%f9E5ENp2{49+$+y`b8sZxK2w zsC{sm?cMI$7$&vr55JX+Nq>I!j(8|4Mu~H-^D7&Ay>LY95P=L< zPH5d7fhW%8RA|@^hp4hfJ*Me%an`@~#^}96vI46;iN_p&!#LrG=oG6XrX}?|?&iJM zOs{0NFl1u5Zh2pI=TEEs@H=B9zs_h~)kvDA*cS^ghed8o%Xos#+;ZeCg(1hM)eBgBWJ&E@*$NU0Van|Ke^q;-}3TW6j-HVOv34Y1- zW2g5Fl8fHe)@n?cm57oqNeW!xDN3Pv$?kvs@`d0e4>J0knp>ac?WrF!&lIy{M!rp3 zt=FoZ8${{4qIIiWR;@HL);X$vO;fe44D4xJ`F@Juw(&;nkDr(tuE8pY`jS8cwzJI4 zrwjO>@WmbJU7G16`Wj&}@|xJJ>J}sVJ*^vB_sRP^VRH`#L^yKzzr4A{d91P~S>X}M zC{H4#u!juEj`MNW!Ct1TD?5)3Nj$zTQWs!EtKp|XC~lC~*TG`z5((#WBoD~%0YiCg zkRkhl{o^y<6W>(%n9Lk65#HwUAjvwgGI9G_@X(RC%TBL8l;5-9$};qN)*I=nbh+lS zr%o~1i2r+zoJ%L34Q)NYZSBuI(D6n+H&kZ3u69nw@^+-|C>gg$u2k<2lFAoV*OVRK zGEeBYCY$}DPkd>4!d5%w(|EFgVL9>DpK3hgQQW^q$1~&6>yRf}S1j{=bOKg(hVD6^w*83eVv@Tor`}l<$`}3bVNnP;J2`7Fl+(EEH z_CU-XzrDhZ@!pW+Rngo1Ja$xv-L$2) zo`r2>GhVQIf@EAm>$*=Kt z1tD*WV;8?37ZdMeicKEVe7kBuX+Pxlz~uh=(<*~59Y)y|s+q5p#Ho)g7}fA1C8BWg z1_1O!>z+GFrZ%vCW@WmuV4}#>tC=X(RYd(t?mS*CfmTJK*arW;JzCs*inIqxZy?k)h~(bCi{*u^dc!%@OERA~buq-O0R7RrOvP$%23?DOc=n!_)!wz|^9i}fysaWf zT#v^0%oR_3jrh2PuhBHzS>It!Q=6RMbg*BX@%U9|g`bHHN4*ba6>sf-x8A2-M(e(x z5iMq##@_ns?d$SN*t20LrOz$WtCW&=2=4TzO(ayX->RMpIXPr9r=+B&GNfg;#B4<& zQ<;5?UaoUS(dA-0(89xPy-y85>vmFh{7fdJToz0(ep!%Q^-5os?BXnmOMXdgeGuKN zG2->YQM?NNeOJgn?|fSEi#ktZ%y~E7i{d6n{BNo~F4)mc-L3a$foR?9MTLn6gn24W zOzV6(ig)}JX3;EAjI@1HMc=h%r`W#%j$gE*bLHD*Sc=z4lhu_u4 zBur!<4sYsWh=~DSLF*nIuo9tLd9V1+q_u;^*@&p@RwDszPOzpZ5oRqY@59fGe(B4% zrH(!z&CJZoRbY}o;GM21m>?_N@7vL7x@xfXJ__6xVEkT1>ykf-JaK}QnZkvIX;JZ; z??le1rpE~*oqICa#XEyq16oQ*{gOq!(oj=E{??1n)l6> zwv8Xovxyj{Pr)ib<>$NY%S%F{0u17*wQ>_k+v*_upxbjT)rBSScRb}Bj1Mq)m209?Mo8MoABBB7X zj~5zlnv&kJh-JJ^%07GNu<2^go#Z^096PROhkLt92ckYS2o#^cax9=^)wUp0RsP1u z;U$q1{%47(shMhGEa4sp>IMM34i3byr8_QnOfdFATfrtXc&P;N| z8=AhAQPPf?%Ix94)?Dz$JF8&r(0Y5MtOr?zSGv=U{wm)OI*MyKZUVBkw7J>B(fcP- z?;Be=UmYRJ0=tgR>@O6p2>-gU(BN$Sqa*+ephVrUK!VS z#TvNRgYyo!GcfQwGsLhRtV1lBT9Tq>SNYsH3a>hyEM8`sVv5>}fBG_}$&qPk_Hs;g znzMaXcl8KW0IegTpJ&r4!yt3t>ql%o84j4hy$G^@h(JUEVl(?0MU=BH3JG|W>pdQ{ zB_;oykF`20vW9me<4Cjy&&sp9wI%V66DD#x(&`KH_74-uG-A{C*e)dv>Nj?I`a>;5 zHxjM;$d0^j+)}6~V6tX)KHxYtrNSZ2yXS(+OtL~mT?%L3YJWTW^_50zL)sl_`47CD z`SB$;TPqSg6{^c$9Fb7G_7SCf3#~h+v(mr*Ag%2o1CN|hn}Q5U9>t;*mB7Kc!l5Be znfU7^bfaNHpUOo9Cx0Ay*m~>muJS&q0)uphObIM2Y3CDZlrCKB5W@zIl`QujT%>h+ z*g0HIW-$0jgpzAES1RWoEsOsf>cL2Fnz8gJHrns|QyVJ+_XqeeXn8jaoc^y){77MxD{O{se8VTA`iCA2G|p9^Eqx;rQR z=WlvHoPT>@q%Jj_#fN!n-#7u&4$9NBVLfM9gOgY?&O$)31s=zY^NdFMav4vGya5lPKMIv@V9_I3^2QUp#j)80E2(e&a~ChwOneU9ot6E=<#Mv}TzC1}Yj#I#B9HDG+O|E6 zANr!BCARh@Tm6=R)+Owmd^>fig!DbVQ*b1klA-)M|Bzdx&e)*aXk$J@-J$;I!3|EX zCojC3JFuBvTH#d%vBxusVn|-ja^;=j`G&sV64AQOj01YXz7=;EN4yBtBxfYK=&{ps zw~sY`;OElSRjv%XszZH>)Tg<@L-lJj`*@CDV&_D?EQi7RGea4h(ywpfo)_u2B(!eV zcg%1f?>h=1ZxxE^n}$JdnfZycNsm&7X%~$yoS?J26~&<`p{=Ud5<{Uw-o>I|?)u?D zf=^Aifv~p8>itTz?me__Rh$%`S6U-iUiG>{)T#FAw@a#eGpuv(uF|de^}UdCrn`Ea z-<~eYCF%P;T_Kr_faHbV$40fKAC$c_QISYi4 zJlsb_0br0!oy^YqpL(v7HwQlVAtWSVJ$65F zjv~awcU{z=fZDjIKsB!MR_wh1lx_<6j2PC-EX0~Qd+y8g$7YvyJAbtwFrrHrVyeVH zcjwMhYWnSTZmkgANj}=yui|kbYKQN#h|sR-TAF-OvsA46!e}D42M8hYrXr#Mu>^&C zh~_Ig&Fh}M*WX`G-KuLs*PR~oh-A9NCRi}Cd!Jso#k=IvU;E@=u@WxrTR$}ZuEzm;B%h=Y72acBO-?d-{`&w^Z&JjombTInK2gAbgx6S>Wf5Q zVpZHHy=9My!!pfS$bO=Bhf#K4E%b*Vw6)PlU0pcyT9>EWfz_;F#e)!W*uEMl*&Xa8HKh zAswyzlV@T0qhJGptbeFtmbC3u$X7jU%|z>pwNY053~6NZdGOVj zx0sGAvny{LkG0^ozZ|7-h=*tR=e4q1Z9ksUg1>=FUU53QgMb8JS$%P+RR$@ z3zTjaTG!a4y^?q2ae=Mf8%2c;E#Hs&*p}U4V;Yr|Hcn?0Xp1iu<8{mOS6Hu$D>EG) zlCtb$TkSeSdZO{jZh2DORXKR?BKgfm>t24!M7{J>*;Ofv*;_NPNa21B=MKKIOphj` z@cwVF9|TVDdNI81P%u*Psb3{z;=^3YcDnVWF(UuT(_5bictX(MLq0_7KG>^t%i}Ud z+$wR$gb}r>(ZiJ4q$@j>h+4!I7%W!ZI=I@GwQu-oXmR@&Zj=>9bDzOf`1@H!yt|Yj zQgAaT6t*3S7xp1y*x^!d!Y>yRvRY2veWjPEA=&lICNVWM>_ZvG`)UJIzWdz_kq!CX zdrz3Tz4o(x|0;uOwBNzk#rCev7pF`PdlqFNgy_O|J&0jV>v#lHm&XrUK7xB8@}%1Vuxr)yW(^?dxiRGdlJq|Oi`86?w*+9U8ty`63Q2fER=FW9W zc15z z9fb-g-9ohP^1_*ipFYxx>3tdVHzuk5_adSa#95uD90kvzD*J zML|aVWYJkQ!9(5IJ;k?qE_Rdc;aCq1$jx`ndz~t$VPR|_N_!msQ|ABbx5sGRjEZ*_ z*0Uu$h5k=_-vQP{*7Y9{6|sPdT@<^bp^FU=d+%azAp{79Bqjj_8(3L;m&J;`t%|+( zin{jRb?u71uloO;JCn?Skf`tXJ?|4{Qc?ekX6>jXRg@emj8zR)5I(FrGQO~;~x$DJp zOXMHfy=42YryhOG=Nq)DuVUJ*nnnH$YI@huF8INA#W#m7yU!fS`D*FqF?XLVneiz# zN6D7wt4;POtb4TO&(bL+WS>QHH;Cod-z&F1^g`h_!?~vIrEbmgSh>9zUHIu{$8l3z zI9^CtzH7^ay&)5Z`mZY6diy8UgX*un8}-^!rtTr@(>E5U72Z)pDU!QUEH`<1)7|%S zHSF~ATlJ&;ieG5CDQ-y6zSvgVHcvZv<;^5lx0y~Kj&|RXeD(CAF%`?TE_`Cx6uw`+9sj+zV+)iiR99~@n_@Jsjgk($j2WZjkPY&eZ;QzUjMc}?p>}x&yerabL z=TpJ%z*@W8&+mGd4hb)Qr0lvEby^;{b@KF*Tbm~(ym{5X7X%sW8@)Sc{MoEotNJ)i zHmsrb=z5L%mQYmLTKnSY;QQ6OZl0UBY`(brM;7fL`obk>#ocL7$1SxP_~pG{+imOe zmWki=ynM9>`PyHKhajWeEk>cZ+H6W~dUTRrUE|!_nHQdnwz;?a!js_peZJg1Vo6D_mi)~5D(ic2;&v&S{G+>N{q}<*}X>!UL~Q+flOPwWPVu zuWOtLE#o-L!85$9!bORoyH%w z(7P%|`%+(L{MihPo&0{J=X<}nk;V3uz0mMOue^y18s=$tJKy6}?_9Yalya}Pu9b_Q zpVQyX`d$C34f6j|LYc3;bN=Ojbvaqf%6sq~2r|muX%vd9P1^eD$E6<{^_ue`wf%Yb z%C6UoU250lxo6oPftzGkeS7o{8rOMVnJV+fG&nN&!?-JNidA3zcEgc5E~omgu0F2v zryIFMa(9X4){!@Q^=M0%a}D2@x$v~*{9%R}$th1i+#KG{)??dN&j#_;MraIU&KlIu zM%tchF{nh&*|Q6T*N!RGJGMz&P5t54tweHni{%!%=`1Pw?byD!#FehE?v>qGeML)` zPcuq~4;ww=+XsWzZ>2u|__+;>@;n{mxu@WwxaA(phF5hr{2LHoCvZ`_oNj(1xqHNN zPp_1(Fa+-2>}hxC-3FUsy*)SXS~H_z<9v?}ggyH_^Tj&dc-t^52j7C9Po2N%@6vG* zUVTsm{MDrX<&KYZ{pu|$Cz88YEca1ckED$+ALbn}XI-6r!#{MZbtm*|&-Mi;_U`S~ z@Lh*|Py099`*zuq9YaqZ+jpJY}NbY{IT$j*Px2Vsh{^`2&dw#r4()O=lx6jr^^SrM)>fq)@|Ngt{ zakZ75bHwk|2KHFc`)sEnTWtM{rA{9<ShyY8$rE@43B zS=Sf;y=eO^tDebUbCnuiQ5!O{=IJT<>WuFa^E&PCX6q7i=h?jST>kM#KQ>y~yi-S; z&C)iLN{jS8D3;r|wC&4A$$_>-mn|RR_Wat}J#~kTiM1NKp+(ymrjPhwy!T2-b;^JHq*Uc9OTO$&dwIt8;iyq4 zt~Q@GzTZ03=AV?2X=NYYczV6n7l-LH_AeeAo2n_$ZC;Q94u;$vq~P+o$XF^jZ-EqwZE7+vhIVGMSB#>Rqgw=`%e^8o385HV%MouWu8?7lRB;Jcxc7Z zYl(5sFEn$x>{R2@1Cd-ByBmKtHa9lso|WXj->>~~`RdS^5wnK(``pmCrswI7T_eVo zY&YlfVUM=D#@lXG%uzVD%ak5P7e`*uX#30d<-g-w^5NtdZk?7}FQK(R{NyUG6-zh(StIOyE zDvvq#(;FYHwdL?0dgsAdZZsA){%q_|8@>-ZIbvdon2^h!lf(KfYIoOvUVy)*mUX4# zc}F!Vo%*$Zr*HKRG>F(Y?n=dB`V;w%wSJzk`$c4{j>kT!W>tC#K}NZM8-?O(^WxO0 z-hSHY>kq{H9@wy>!m7*qQ>`x9KYRFPr|05kTZb4t>_S6Uyjtz>)+5PzYQTi6;{%6W z*wk_Tn~2rZgLC&=w^gdz@m(bMv{-J!`<9O%RM0r8 zZ{8mGH6*&wK3n%Gt(V(XUE2H1mwGGDG{0`GIZ-ny8 zQ)2^4-n{hf%CH7wx3nHJ^2y+4OJ_d1oU%SCWm|ob+_Pf2ZAK)`Ir*|wj;1Noq6T!n z>}&V(-aL=F13#@)jJl9g)#`8m0qq{mN|`ZcyHo7P$LBjWi@J0D`qI|guIfW=yXsf{ z(L^NooLKH(-D~VVb~4Y|VG9e+d0O25K!w4v#fBd~I?n4@%GP$zzsYATpS8~a-lnAT zk6RncH?>K%-IC%qqKqW4$M9?Q%E-H45y_>!PUFvJO3%_OVrtiRiG5Ys5Y^nKTDR@r zk1o2^#x7^@u~kDReZ5$wyZ5^46S^Gp?J@mEpQiolgkEs`chgt2imT0lwaNpR9wm0Q^MBQ|qep>XA+O}Eb7 z`qB9x&+WzPA8-qn@449dgS+#Kz#MCK`oAbRIqlnN_2=dr+YYMXXE)vZX}7JkHfxl7 zNi28exUkhyT8NkZ-$EtOKMl2p8n#STfSZ&p1WMLe!VB|*6v&0 z|9U;Vnlv%j{@w*#oO=Zi`S|o@Gm+fOV!4H@pSVxZ2bjEp0J8e0UM{-71T(%y84*dK;enZi+*i&+6;P z_P!c-luKK>vyJ+_+&s@g6d3;n48AkhF6U!a3a7{Jk zsNN^bI@d4SZAgW&H=R#g#V<%~IC)yB3ngZ!lymfX>mTfv<58s&ANx0Xt@mpF?&0Vv zla9Nr96RTvcZ(z9eW&YUx!soA^(fG%Q>in-s-iD@-ik>6Vr59avj5vn|Gw_3^X@ge z*v!4rHEKuC`kq(5r!{J}CHIo0$)`UgyAGV-?dR0_XnB#oGzT~SY&z}gbaqV5et9Y4*)tojh+pzHE zFm20ItK7u>@lB&pTy6SCMWU=7!rlVbaK%u%J{pj!_M=S*(iYUA)n zTb1&6TO|96)w`UyYFdkjhvN1R&U(6)sH+puzCaQ!;{-> z@0fDFeyy>JFxhy=r0a&o`+ZL9hxxBEgq7>ye#mOd0lojEM5T8Q|Cu?X2lS{;dlp7J z&>YeDv#H#Ee7%RshK6=4I}Dh%uxyt&$1Y)B&kxl*QUA`nD8s5+%TvldXg{Ov$%<~f zMkaQT$=S;-VetB?EXfg<_|9e0w3EfId@%~j zaHVlP{ft-RJ%z>3tP!SjU-L*LadMT?H5?U<=(ZW+A^2bYqkM#_G(n_fePqA^`Jwy& z$seUb2Z`E2wjHkj8)^Jc>YFVq+XDZM7NB~h)2LLDT8TtfNFuTSU#P49jrsn+t`n78 zj4VP$`3O>~T_dQo$o^$p;Qy!v$e$JZph%@ESn{nR`+G2P|3|Zut@i(C7T|nWrBoYm zJ_ROO`Lp}b|2hw#G78sAS(kaM7W;cwasPEnW>AQ9mdQ0bHLgB&xp`rRDA~bm3uIg1 zzsLeuzRCV&TOiv4*%rvQK(+<4Es$-2Yzt&tAlm}j7Ra_hwgs{+kZpl%3uId$+XC4Z z$hJVX1+p!WZGmhHWLqHH0@)VGwm`N8vMrEpfouz8TOiv4*%rvQK(+<4Es$-2Yzt&t zAlm|FEKvL|M^y12bKCjqb#hmwT5pi4RIVzGynl#NrEu-6Qz-nMJ>8x4%2V209pc+7Uhw6 z@r|gYIY2U=0`v^uUrYP}lJN|nXNi1yw0?*Hes;c*UY`Cidlm&iJY-AC8`+~E9uxu! z1CBrufWD940@_w!8?YVN0qg{J0lNV@Gi)!g57-YJ01g6&fWrWt$#fK;vyM^$Iz#9L zKxgyNIXZOa&1rznlA&{7&H;3u3Y~pI{y@G!WuFV6vLbtt-O0{m6S4u>jqF5rDG1=x zT1jD`1oSTn&>2x5flokAgmVD}0DE9Lp1lU%0QnKN1@Ztx5vH;y<5~eWKmdX@04d-K z&=~}e04gggo9DnVAP%?#kl)c6hAV)Tz-nL(uohScOaZ0>(}3x~3}7ZO3rGb11d@Q+ zz#L#MFb|jyEC7;$g}@?UF|Y(!3M>Og17m;$U@R~W7!OPUCIS(F2G9ajcXWUf2nV_X z-GLrJPoNjj8|VY{1^NLpAPA5H!GHn?0YZT=pd(NmC;?DCECrMXoPaVEhCM&y+8A)a zy&Z51_qPFezzui-qyfW$5kNd}7q|y(1Y&{pz+_+)5Djz!Is<_~8=x)F4yXuJ0;&L2 zfoecmz!@k9Gy(ELhgZ0c1pWaA0qudBfDhmaln3%5-e6p70rh}ZKx?28a0OTeyaXbF z%77P89jFb|0qO$2Kue$j&=6<=GzFRi{y=?zo)L&X@FD)e@7YuCYOa2h4rEKZ{s~wE zP?`A^`4`!Xd>{%?0c0<-8|9U3MY>UbZGapA-&C6}9(d*sssR2{~T!3;wHNY7lJJWM| zMrA}-DsL)xW0||*p7@EE%7d=1fE1uIrgEY(_U5m~@~wj~l^vBE`4jn7YoHa-5}-1o za%%=q8ImtG2FM?%>>2<80F^0~tsl@4=l~dv+8_7f0M)ZFpbHQPgaR_44?wi8KzE=U z&;#fR^aA<<{eWN~2#^EBs{ld(C13#bfDRzr4*;})22cYL0F?##?F3*vFb)_C`~f5Y zV}Q}XC}1Qo0vHa&198AGU??yIpgKZzC>DqT1_IH*Bwz+W`JjAF14#d=z!ZS&OZl4) z&@;ME1fqbgz)|2LFcH`Uklii-=YhWglC=;x2^jeqbLD%6BSo0yqvF1ISiK0D4Ygx}FA3 z0cU`-z&U_qR0b{qmw}tW4d6O(4Y&%BuTuCna1Xcx+~u#%&v1iZA?iHwv-9og>C-#1 zHU@z{rZI>|`az&V6{AFMjl(Ml6aaQaTR**tKe;&;|8sDSpL!0s*bCVt2?L$J)O7#KTAy=yE;#0Q2NxS zRJfLb(wSc=u157yiXc#Gy4G~{;{2%qC{C9~^h~^UG{M@ckGHEk@yKOz5++I8nd@8R z6MKJ9d|Yd}dU#6YVVeHRV3{s-@7Ug@>bq?R#S79rP%9Ndk)hz3I6~rmr)273Ce4c| z#_#YQD)=>u&i!T!DDJMF9Aisbc|<}v{zkgZoj0#6rRVE8$FTT{&}{&QGQnI<9q4LqW-lbe6my7~HAA;ruKe=7~2zATTFUL zPFnk+s-4$UHmn8(mL^I#Pubm3dBFPHxyhjL9BKqkz~7*Rq=zl1TlYO_Ip^Gb zURBUwEhyAhb?tDzP4Cyo?hp^Bbv=k851F@e>eHe{&W-}5HYg~c=Ae+Rm&bh5cr7T^ zz=ru78T&jaRFl7|tk!AEH@*rUv|+^af#>=7c~IE?Zqt`oTiwPJs!_R7;i%3zmA_MA zZ=Ukoz(b_~>r}>L(k;0Af&KIB7v!|IDu^dV@WdNeu*U_SdsZ=4tj_XxbOB&TG9 z%yY`~WRlZ5Tzh>RnV~Z6e%Pnula`;JAwQgpN<2|{sBG_iJGrK+cm70BsEtQD%|Iyu zO3bTGm#@{|$+d52d%Y$7Kq&>vr-F+9-@H%$$#_t9?vm1QP_k&h7V^C1{|F>c;2g|WgjTsT&rx*dDGBBQa9wz~eElWQ%Go84s&J+DJ9J+F(UB|DZo=70B5e6y%didkGjQ9sfSo zJI}wcy=9HDi@R%0$Pdvgq7-U_e(U7n#YUwi+_bS8>dEy#>%l`d*x^{TQ13~94!5y_ z0We0v7#0+aDdRSMfA-q0WNIaW@)(q|pmaz|OAAKn+lYNpi~59YJBPLL!;hR6)1U8 z^+{Uin|qSSyp1m@P^y7KUR6JLhY(%Dv0#DH5ELg+oMPKq-5-1*PN4J!g)C}+_l!r4 ze~Gqy{Mi>qS&Yv8(3O-`c6Wh*P!sV z{>KrDzaKTebub>4s^L14k==Yk3Ld@LhejyWIl4>qDw&>ondfu!I95|k_*;-hYo|JN zGfz4mK6Y8LG#r$gtXq&oD0HC;Pl@F1(n52rr-o2JhZclgzrFzqDrDTa;OQwluO8zl ztS&s~DSK+nzFIG^IMouW^fb-bKw`+C*7g37(YfD3kunw>Z_YC^u=D%T!!yyvlykJ0hPFZ;pWXfb60ups$*k? ziogoBBTrFo^Ih(pE8>Ab3FaveXBu87*MGl5phWSMefOs>pkvV#`~P7^4jL7@@Kyi)Gl&pnp+5Gb=jp*coJ|I^h=I~80eP*QkG{$h25GFP zm50ex{Xsc(FW$X%(O#E9;oH$HR(?>Z3cVJaq*&c6)+D^>&IC{JV5UcnRn~pILO&o< zp^K4R^m_Lw|Awjx;$cf0kQQlBs`Qe9F1Je!-g^+`!&+se6QWcHOR)e};DNG0$xR*f zpin=DUgpQrHfo*8?49?w(cq&tzG%+wPHmk=9J4m9vQ6eG&QUGb=^R_`MLHPlkgcfmiNSFI2WwEaRccZdUVp|OM&8zJ{Z!DfKmWFj^nQ04LosZ2`GHebO99V zPfHw~lIO;#b*mV~gL3*0PbuP%vLkkqnw$T?6R;u&IDkhn^l`MV`yJ|?`MFFMqo>-~ zdx3t$@T}Uae3hdBwf@jDj3t^u$vpCa(|tI^Ho$j`luYvDh4CA7 z%eboRVD%1VLvy8glqcX)Jg9{Y1zA(@lZ9L1U#1HdHq8zE#H!k{4h&{6oE4dJr%wSLiOU^8~b$-p}vMWKs zESN^#Sdd~3&$mbA-+nL@p?NSZTOvOjAdUPwqFR}tmD7XTVa<+DC+oH<3md?8nYTNn zH_a7fXhU3gZ*^_xS@kS5@Zj7k>lR4|3sKFwxp|enyyKDue12eS;|Y)xuZ*+*v}iSX zBHBtS)2vHjD5Q~JxBKf%^z%z4Xnakz1k$jou9fNZ3Tykc3tjf#q!n};b%HV*HHF4} ztIBm-_<7X#ilC6cA)WVVkx4hv$~Iq1Twre+!#>UDTq5uAO4Rm}Nx@@01M@QQe@)2d^#DWIarm(rVVy1;v#iQDIFtSC4b!E zUe(8rhBR;Z0i+|DFkXz{B@Hn|P@s>z^;x^7PSWppbQtpRCs@ zzWZ`EknJybs99o(cT59T=WP8r@9pn5Jz>DP>qkK0=bBklvgk|DCJy%Jq-{7hY-+>M z)s-NPUlR&eD6~?gUUH?v$x0)eKMMc_B}(n+Xz)<^>@DzcMY9dTR0_m{`ZkG|R(qy& zdbtCIs0|?-Ag5XP&VPc(0n!F;dsi!G-4@hdlXXz5vexa#bcXWjWX+=w;Oc3Y4#(Td z95#~O`1#LJp3-b^;pCt`to7$TL@UKRQ`i%@RKYj*sF*S22Yl}g3g#aM$-Ca|$A8tQ zx`0AH3J=jY;QP5{{(B#c3ZhkNvKwrWY56cpChLiOZS2vI>*tyrkjqb2d830gnju44 zm`pE4dlfqEdf4{--L9pA!fRc;5jRRJJJ3L438ygK)0}u5j4vi94pE$6P)^4eu!mk^GLgjP+a<%@qd}kJB6qGl1$w8sIZGUU_ zJ&%d~qKQI{yjP~XJkHE=S*P=&G3Ql-o`s%FDD<>Et<2#+v^ic)ILe~qi81rT&nhv7 ztxY?3?h;>6sO_EN)?-5DO@4J~tPG_+U2DgKLaj?4jql+n9g=o{f?DE(2Kz@I7qmq5 z>&NeoI@9Ye_@#e)POpfFQs`Wp3^XWoYMH8&L8nxQ`a63F8>qF@ZJ-+TawcW(7sn;K z>60CCHoY4?v%k5>H_d6lc7tB8&_QDFbI$P{Lt1@=shP$jYu7wsHL$?9jQgT7pi}Fc z`ev5j(0ws4#v@F%@I$T1ORFs{;WYzcDyPE{hFVc>Bd%uL>l=(rk*%%;4qDuGm2-(P z2$O~4ivsW<3iT-92f@C4WY5Y;BdoViID~?LS&ID1O8eJ*^aH^nP{}62ZFhDz(XkzE8Bo3+0Z^% zrI6|r10t0=1;1#BMnM)V#a3>JGE}F~YgADRX^2h};qHn7pfpHfkh!|+HIX{GLK-2{ z8ZeKL_E)M@0s3gU8xAp61YqGpr_pOd3~n;5QmPnez|xsPqcf+RQ3z>hyq8j|>&euF zZ}H{LUs&$R!s4)*1o&tWZ)Cs_k7_K85D>0Y28Sx56)Kfms6tJd(!%%UnurLQI#{8@ z&b~q~#Vu^2ij2V9rg|xk09WWjup%gxhbiR!6=pM+Npw~cKT45OQ|jSGTA9fVzmW?W z{GF6qMjs1Y5wT#1h?TP4V2X=*y;O^G#U7$2C|n^oNU_a|!4|(uE5$ptW|R=cqRenR zyTpTHqmBm&pklCThO@7;w`EBpDKz}{u#g>*_TUtSkUn))=1rI`!{ARBhB>KiHZS1^ zuYi;vo-r5pCP}QOD)bR<{NL~}V-Fe$Ll|A8n3U5v90I~+^8R|QLe`(QZ`pbbTRYUt zBXzo%5LCNRod&fT{)oSC~*259?-Vi3jV)0kJA8?t1K3h5>`>M5xaGH>~GDTAG9fZ-2c4>1%&M=y<5 z1{=aKBC9J72e2uzyZDh*9|c#BdbN6Xx_Gkt!7{8??b_e6<>ky3C}I1qTw#g7Q->nMfr_5`~x> zS#m&8=@?DMl@;>Lk5VwG)aYoqjmeU^?4^qbVd=s`#pfQYWfA5YK_QY!5OGi=i%M(0 zPMLfVtfqTmN@1pe1iu5T5MXWSPZ^Rq26#oTXL4W*=^A~FPuUoR>+gUbdP~=7=K=69>3jCbMj0Rad74{Ce>THJFG_OI*ru&~7@@6qn zf*jGb3Hhh@j*)+HbiD$z3!P3DBYsHpLN6Vzl%`PzyV-;U+CNpNZpjHMsaBqIf z_mdLSDF}J#!YrenKjhRnW5ycmPo^T`KpLVD8!=hxd(&CKXl2T~@gBLh>=eR$-efkK z2P2QdoS(`xI!tDW4wXjg%uNl2NDw9jgnF*h1mjgBSG`7O2#SeT1Sx}6d;>@WAaniL zkC7>fA0)t?Ef2Nn%{y?@g^hApKWL`RjH}t;G2WutwLDWzms>EW3qw;Hy27+47$iN@D4hOE z88VTj`Z}dv9_8ar$z+;AouFW+( znWbVP{#GIx+eaaVjFjIJZ!!k8L1;<@@fj)qvG`1y1=bN!^Y&Z9IalFiP`fVZo{{of z;zjoL@fKvKd;D91#l5f~SN!zn#2R03gAC&>8;n?5!%8y1;h)I=h*a zxDOn`!QHSlEDaiI6+b8jJlu_xEn+fDg4y*d+7Dt3gWGoX0o4&B(HfXpdDvOfR zM})NW4}^6yEKsQmt=s)JJT$D$LCQ#L-9> z5f?KCYl!exZzPssXwx1mVExS$ONN+`nBfT%YOxE-(Ae-lrVl+1UBO> ztKh6i*!~esg3Smp#eoFVJ!_gQ^bQAm#Q%`5gbl}wIUirJ9Eb2mSxk$ zCgs_#N4lr1tSn_Eog$EvF3h~yk_2(KAV3^sMP{iYicJK5aqy>dAlg_aI|y6TtSDJu zX2#Woq5`uJ5Vl>+@S4gStfqUWv!y)qpHG30zx$~++xWo|*o?PA3DLR~|J8ySmH8Nu zz~9kgI4!!;5W`56DF;fmw3uRgVw!Dac!DJf(-VE9TBDbWpK#-R@e^V8Zers^)9R1u z5xy-8(g$ZEDqSqmNZ-V1d?rpa1Pg5$#hLOW(w}>lS-#nFfhi4*R-w))Pc)-3#S_mM zOb?9;a%+4>gBw*$|CCcmrO~KEe^8U3;G5)faU+yky^puq{w50!I7E68BEK->P8p=} zTA73ymmvfn`*~S>zD=_D@Gt6>&a#k|F8qtK(v=-#r3?R}EU|MylsNc{l0>ctL88zv z$`QFWmmexQHs#XKo=qKi&8jK zaHV9r|3w+5E)j~D?tf7RX9i9W)BSJB;5>zs@#}tK8WbQeru(1MgMDm7WkJVWVNA&H zKAY(aBJ_wJp=YBTOWUi)W3|9xyv4|mf58>QG7OAlI=n}VQ+;qoLIlp4jm4WP2$@OW z_|Yn48gB)eX86)40Pge;erj@&US-Hg{~+Cnk1~dtw}QQvW^PxOH-Zosa({RR(U#xK z#e2MLSqK7*x5yT~(QAIW0&o3+1^;4bjoG=SiFW$;-bveZFT%r%_$_nxDm*@GU@ya) zDVlWgATM2*{Fr~$j}4- zK^CM^C?g_u_#dCngz=2fT&v(WoY~w5#m(ltI5b&F9~58>l6?tOIU0`}q~ zXtB?j7$-|7$6A;|iX}j}1$)bqPa{gHS3(Us)Jkt;;S7d-VS43*&HAFlH1rmi9xe|7 z3an^o6*P6Fm%`Y4JzAcZQjfrDs38VX@gkTu69S(y`|3D@_y|pKB-B(!Xvs1<_69VT zG+`Y+H5_e$H~tJ^nn(kCKN?{(CGk`ID5#%`#}EhgAjY(l}& zm__0Kv|NW8$9+O3ykJXr+(9z#W^9&;UkE9Py#-a9e?`=cv@G2MGIRvVchV{HCY1wCGKj*ywLTjp;a zQ)6nW&&%*qF;vR%1TKgFm?J}3d_hR>3Yk$wh{R=&=RoExzc-p`wjm~+8CbVvsnP#= zxBQvX&)673rHqe+H$>^fYm0BD3z0~ciG%r~rAC9k-v$?55Fa0IQ|)DKQcz?_h}nGw zQ7lLhh1ht)Qo^JsJ}Kul#vQmUFNzuql`9e*_1(r>*4;CQ=bCVQzJfQKxdlk395rEd zkeL~O%Oo~Z$mA(YjAfA)xcHC8&3DawBxWip+d8Dp6iZdz_ysxGjJNPv%ib@x0eHnh zW@wf!w=pN>`UNv)5%1VSm^k=T4PLr*ARt{>=!WFjmB5I=?EC1ZI1ppH7mD6YCTQmf z=LCR{zmsyu1Xvg#at9B9i@Tu@KKN2&pUGn+VD5t$Qyd5|-LvI?)|O`0O>(DYkXtbZ zGdW^(lVJA2zolA-qZf!FJPN~Id=AV${bQAb>lLJUQ5A2K>$&kAN60V)GNTV06lThV zG_xiOQwKA$GgA^$Gkc8Q!SXg`h5@WmL19SCe9F@7fjbWY%-jtsgXNSnjFrKh;Rz}P zKZ-JAkMzTF2uc3{+Vk6{W@Lzm_mChCA}91pxcRvg-!HPM0{F#2;ZqrfUZ=pPhI$3P zFv2;P8I^=cOrVj2)fh`nr70!G%BPBqSRPM_ra#~ng=iGTb%|zlX4}+UlLl^4i0#%` zGNLdE1G5l7%9g(d5T7yvK z;~r5g$PZflIks({;g`%})roqjIKD64v2+UeE5?6=PE)X3MK@Ebr(R{ji&>&q{${opKp^M9yG^AvF z#ByY*mCrJ7MuEYKAM*{#mIwOW(KQgm-N1MG6+bin@Pp~WPR2wbfS( { + execute(command: CreateUserCommand): string { + return `User created "${command.name}"` + } +} + +// Query + +class ReceiveUserQuery extends IQuery { + constructor(public name: string) { + super() + } +} + +class ReceiveUserHandler implements IQueryHandler { + execute(query: ReceiveUserQuery): string { + return `Found user with name "${query.name}"` + } +} + +// Event + +const messageBuffer: string[] = [] + +class UserRegisteredEvent extends IEvent { + constructor(public name: string) { + super() + } +} + +class UserRegisteredHandler implements IEventHandler { + handle(event: UserRegisteredEvent) { + messageBuffer.push(`A new user registered with name "${event.name}"`) + } +} + +const app = new Elysia() + .use( + cqrs({ + commands: [[CreateUserCommand, new CreateUserHandler()]], + events: [[UserRegisteredEvent, new UserRegisteredHandler()]], + queries: [[ReceiveUserQuery, new ReceiveUserHandler()]], + }), + ) + .get( + '/user/:name', + ({ params: { name }, queryMediator }) => { + return queryMediator.send(new ReceiveUserQuery(name)) + }, + { + params: t.Object({ + name: t.String(), + }), + }, + ) + .post( + '/user', + ({ body: { name }, query, commandMediator, eventMediator }) => { + if (query.event) { + eventMediator.send(new UserRegisteredEvent(name)) + } + + return commandMediator.send(new CreateUserCommand(name)) + }, + { + body: t.Object({ + name: t.String(), + }), + + query: t.Object({ + event: t.Boolean({ default: false }), + }), + }, + ) + .listen(8080) diff --git a/package.json b/package.json new file mode 100644 index 0000000..17469f5 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "elysia-cqrs", + "version": "1.0.0", + "description": "Plugin for Elysia for retrieving Bearer token", + "license": "MIT", + "main": "./dist/cjs/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/cjs/index.js" + } + }, + "keywords": [ + "elysia", + "cqrs", + "pattern" + ], + "repository": { + "type": "git", + "url": "https://github.com/jassix/elysia-cqrs" + }, + "author": { + "name": "Mikita Pitunoŭ", + "url": "https://github.com/jassix", + "email": "jassix@pm.me" + }, + "homepage": "https://github.com/jassix/elysia-cqrs", + "bugs": "https://github.com/jassix/elysia-cqrs/issues", + "scripts": { + "dev": "bun run --watch example/index.ts", + "test": "bun test && npm run test:node", + "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", + "build": "bun build.ts", + "release": "npm run build && npm run test && npm publish --access public", + "lint": "eslint \"src/**/*.{js,mjs,cjs,ts,mts}\"", + "lint:fix": "eslint \"src/**/*.{js,mjs,cjs,ts,mts}\" --fix" + }, + "peerDependencies": { + "elysia": ">= 1.1.0" + }, + "devDependencies": { + "elysia": ">= 1.1.0-rc.2", + "@types/bun": "1.1.6", + "eslint": "^8.57.0", + "eslint-kit": "^10.33.0", + "tsup": "^8.1.0", + "typescript": "^5.5.3", + "prettier": "^3.3.3" + } +} \ No newline at end of file diff --git a/src/command.ts b/src/command.ts new file mode 100644 index 0000000..e6fbc09 --- /dev/null +++ b/src/command.ts @@ -0,0 +1,33 @@ +import { Class, Mediator } from './mediator' + +export abstract class ICommand {} + +export interface ICommandHandler< + TCommand extends ICommand = never, + TResponse = never, +> { + execute(command: TCommand): TResponse +} + +export class CommandMediator extends Mediator { + constructor() { + super() + } + + register( + command: Class, + handler: ICommandHandler, + ): void { + this.handlers.set(command.name, handler.execute) + } + + async send(command: ICommand): Promise { + const handler = this.handlers.get(command.constructor.name) + + if (!handler) { + throw new Error(`Cant found handler for command: ${command}`) + } + + return handler(command) + } +} diff --git a/src/event.ts b/src/event.ts new file mode 100644 index 0000000..d3b7aba --- /dev/null +++ b/src/event.ts @@ -0,0 +1,27 @@ +import { Class, Mediator } from './mediator' + +export abstract class IEvent {} + +export interface IEventHandler { + handle(event: TEvent): void +} + +export class EventMediator extends Mediator { + constructor() { + super() + } + + register(event: Class, handler: IEventHandler): void { + this.handlers.set(event.name, handler.handle) + } + + send(event: IEvent): void { + const handler = this.handlers.get(event.constructor.name) + + if (!handler) { + throw new Error(`Cant found handler for event: ${event}`) + } + + return handler(event) + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7ae7a3b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,43 @@ +import { Elysia } from 'elysia' +import { CommandMediator, ICommand, ICommandHandler } from './command' +import { EventMediator, IEvent, IEventHandler } from './event' +import { Class } from './mediator' +import { IQuery, IQueryHandler, QueryMediator } from './query' + +export interface CqrsPluginParams { + commands?: Array<[Class, ICommandHandler]> + events?: Array<[Class, IEventHandler]> + queries?: Array<[Class, IQueryHandler]> +} + +export const cqrs = ({ + commands = [], + events = [], + queries = [], +}: CqrsPluginParams) => { + const commandMediator = new CommandMediator() + const eventMediator = new EventMediator() + const queryMediator = new QueryMediator() + + for (const [command, handler] of commands) { + commandMediator.register(command, handler) + } + + for (const [event, handler] of events) { + eventMediator.register(event, handler) + } + + for (const [query, handler] of queries) { + queryMediator.register(query, handler) + } + + return new Elysia({ name: 'elysia-cqrs' }).derive({ as: 'global' }, () => ({ + commandMediator, + eventMediator, + queryMediator, + })) +} + +export * from './command' +export * from './event' +export * from './query' diff --git a/src/mediator.ts b/src/mediator.ts new file mode 100644 index 0000000..f35d596 --- /dev/null +++ b/src/mediator.ts @@ -0,0 +1,10 @@ +export type IMediatorHandler = (...args: any[]) => any + +// eslint-disable-next-line @typescript-eslint/ban-types +export interface Class extends Function { + new (...args: never[]): T +} + +export abstract class Mediator { + protected handlers: Map = new Map() +} diff --git a/src/query.ts b/src/query.ts new file mode 100644 index 0000000..2ca13ea --- /dev/null +++ b/src/query.ts @@ -0,0 +1,30 @@ +import { Class, Mediator } from './mediator' + +export abstract class IQuery {} + +export interface IQueryHandler< + TQuery extends IQuery = never, + TResponse = never, +> { + execute(query: TQuery): TResponse +} + +export class QueryMediator extends Mediator { + constructor() { + super() + } + + register(query: Class, handler: IQueryHandler): void { + this.handlers.set(query.name, handler.execute) + } + + async send(query: IQuery): Promise { + const handler = this.handlers.get(query.constructor.name) + + if (!handler) { + throw new Error(`Cant found handler for query: ${query}`) + } + + return handler(query) + } +} diff --git a/test/command.test.ts b/test/command.test.ts new file mode 100644 index 0000000..2e43a77 --- /dev/null +++ b/test/command.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'bun:test' +import { CommandMediator, ICommand, ICommandHandler } from '../src' + +class CreateUserCommand extends ICommand { + constructor(public name: string) { + super() + } +} + +class CreateUserHandler implements ICommandHandler { + execute(command: CreateUserCommand): string { + return `User created "${command.name}"` + } +} + +describe('register `CommandMediator` without Elysia', () => { + const mediator = new CommandMediator() + + mediator.register(CreateUserCommand, new CreateUserHandler()) + + it('should send data to mediator', async () => { + const command = new CreateUserCommand('Alex') + const mediatorResponse = await mediator.send(command) + + expect(mediatorResponse).toBe(new CreateUserHandler().execute(command)) + }) +}) diff --git a/test/elysia.test.ts b/test/elysia.test.ts new file mode 100644 index 0000000..1660f5e --- /dev/null +++ b/test/elysia.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, it } from 'bun:test' +import { Elysia, t } from 'elysia' +import { + cqrs, + ICommand, + ICommandHandler, + IEvent, + IEventHandler, + IQuery, + IQueryHandler, +} from '../src' + +// Command + +class CreateUserCommand extends ICommand { + constructor(public name: string) { + super() + } +} + +class CreateUserHandler implements ICommandHandler { + execute(command: CreateUserCommand): string { + return `User created "${command.name}"` + } +} + +// Query + +class ReceiveUserQuery extends IQuery { + constructor(public name: string) { + super() + } +} + +class ReceiveUserHandler implements IQueryHandler { + execute(query: ReceiveUserQuery): string { + return `Found user with name "${query.name}"` + } +} + +// Event + +const messageBuffer: string[] = [] + +class UserRegisteredEvent extends IEvent { + constructor(public name: string) { + super() + } +} + +class UserRegisteredHandler implements IEventHandler { + handle(event: UserRegisteredEvent) { + messageBuffer.push(`A new user registered with name "${event.name}"`) + } +} + +// Elysia App + +const app = new Elysia() + .use( + cqrs({ + commands: [[CreateUserCommand, new CreateUserHandler()]], + events: [[UserRegisteredEvent, new UserRegisteredHandler()]], + queries: [[ReceiveUserQuery, new ReceiveUserHandler()]], + }), + ) + .get( + '/user/:name', + ({ params: { name }, queryMediator }) => { + return queryMediator.send(new ReceiveUserQuery(name)) + }, + { + params: t.Object({ + name: t.String(), + }), + }, + ) + .post( + '/user', + ({ body: { name }, query, commandMediator, eventMediator }) => { + if (query.event) { + eventMediator.send(new UserRegisteredEvent(name)) + } + + return commandMediator.send(new CreateUserCommand(name)) + }, + { + body: t.Object({ + name: t.String(), + }), + + query: t.Object({ + event: t.Boolean({ default: false }), + }), + }, + ) + .listen(8080) + +// Base request + +const sendRequest = (url: string, method = 'get', body?: string) => + new Request('http://localhost:5000'.concat(url), { + headers: { 'Content-Type': 'application/json' }, + method, + body, + }) + +// Tests + +describe('elysia test with cqrs module', () => { + it('should send GET /user/:name and receive identical result', async () => { + const name = 'alex' + + const res = await app + .handle(sendRequest(`/user/${name}`)) + .then((r) => r.text()) + + expect(res).toBe( + new ReceiveUserHandler().execute(new ReceiveUserQuery(name)), + ) + }) + + it('should send POST /user and receive identical result', async () => { + const command = new CreateUserCommand('alex') + + const res = await app + .handle(sendRequest(`/user`, 'post', JSON.stringify(command))) + .then((r) => r.text()) + + expect(res).toBe(new CreateUserHandler().execute(command)) + }) + + it('should send POST /user?event=true and receive identical result', async () => { + const command = new CreateUserCommand('alex') + + await app + .handle(sendRequest(`/user?event=true`, 'post', JSON.stringify(command))) + .then((r) => r.text()) + + expect(messageBuffer.length).toBe(1) + }) +}) diff --git a/test/event.test.ts b/test/event.test.ts new file mode 100644 index 0000000..b742d7a --- /dev/null +++ b/test/event.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'bun:test' +import { EventMediator, IEvent, IEventHandler } from '../src' + +const messageBuffer: string[] = [] + +class UserRegisteredEvent extends IEvent { + constructor(public name: string) { + super() + } +} + +class UserRegisteredHandler implements IEventHandler { + handle(event: UserRegisteredEvent) { + messageBuffer.push(`A new user registered with name "${event.name}"`) + } +} + +describe('register `EventMediator` without Elysia', () => { + const mediator = new EventMediator() + + mediator.register(UserRegisteredEvent, new UserRegisteredHandler()) + + it('should send data to mediator', async () => { + const event = new UserRegisteredEvent('Alex') + + new UserRegisteredHandler().handle(event) + mediator.send(event) + + expect(messageBuffer.length).toBe(2) + }) +}) diff --git a/test/node/.gitignore b/test/node/.gitignore new file mode 100644 index 0000000..3ea1bb5 --- /dev/null +++ b/test/node/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json \ No newline at end of file diff --git a/test/node/cjs/index.js b/test/node/cjs/index.js new file mode 100644 index 0000000..6d1c1e1 --- /dev/null +++ b/test/node/cjs/index.js @@ -0,0 +1,11 @@ +if ('Bun' in globalThis) { + throw new Error('❌ Use Node.js to run this test!') +} + +const { cqrs } = require('elysia-cqrs') + +if (typeof cqrs !== 'function') { + throw new TypeError('❌ CommonJS Node.js failed') +} + +console.log('✅ CommonJS Node.js works!') diff --git a/test/node/cjs/package.json b/test/node/cjs/package.json new file mode 100644 index 0000000..738141b --- /dev/null +++ b/test/node/cjs/package.json @@ -0,0 +1,6 @@ +{ + "type": "commonjs", + "dependencies": { + "elysia-cqrs": "../../.." + } +} \ No newline at end of file diff --git a/test/node/esm/index.js b/test/node/esm/index.js new file mode 100644 index 0000000..d0d7a1d --- /dev/null +++ b/test/node/esm/index.js @@ -0,0 +1,11 @@ +import { cqrs } from 'elysia-cqrs' + +if ('Bun' in globalThis) { + throw new Error('❌ Use Node.js to run this test!') +} + +if (typeof cqrs !== 'function') { + throw new TypeError('❌ ESM Node.js failed') +} + +console.log('✅ ESM Node.js works!') diff --git a/test/node/esm/package.json b/test/node/esm/package.json new file mode 100644 index 0000000..cdfd903 --- /dev/null +++ b/test/node/esm/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "elysia-cqrs": "../../.." + } +} \ No newline at end of file diff --git a/test/query.test.ts b/test/query.test.ts new file mode 100644 index 0000000..cdc02de --- /dev/null +++ b/test/query.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'bun:test' +import { IQuery, IQueryHandler, QueryMediator } from '../src' + +class ReceiveUserQuery extends IQuery { + constructor(public name: string) { + super() + } +} + +class ReceiveUserHandler implements IQueryHandler { + execute(query: ReceiveUserQuery): string { + return `Found user with name "${query.name}"` + } +} + +describe('register `EventMediator` without Elysia', () => { + const mediator = new QueryMediator() + + mediator.register(ReceiveUserQuery, new ReceiveUserHandler()) + + it('should send data to mediator', async () => { + const query = new ReceiveUserQuery('Alex') + const mediatorResponse = await mediator.send(query) + + expect(mediatorResponse).toBe(new ReceiveUserHandler().execute(query)) + }) +}) diff --git a/tsconfig.dts.json b/tsconfig.dts.json new file mode 100644 index 0000000..cd54e87 --- /dev/null +++ b/tsconfig.dts.json @@ -0,0 +1,106 @@ +{ + "compilerOptions": { + "preserveSymlinks": true, + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ES2022", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + }, + "exclude": ["node_modules", "test", "example", "dist", "build.ts"] + // "include": ["src/**/*"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..80b213d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,104 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["ESNext", "DOM", "ScriptHost"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ES2022", /* Specify what module code is generated. */ + // "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + }, + // "include": ["src/**/*"] +}