chore(): start moving some packages to the core directory (#7215)
This commit is contained in:
1 parent
fdee748eed
commit
bbccd6481d
1436 files changed
+275
-756
No files matched your search
@@ -0,0 +1,426 @@
|
||||
# @medusajs/modules-sdk
|
||||
|
||||
## 1.12.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7176](https://github.com/medusajs/medusa/pull/7176) [`1eeb1e9de3`](https://github.com/medusajs/medusa/commit/1eeb1e9de3e0b571735437b00968ee96e4aabad5) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Load custom modules config
|
||||
|
||||
- [#6864](https://github.com/medusajs/medusa/pull/6864) [`e944a627f0`](https://github.com/medusajs/medusa/commit/e944a627f074fb39a56f4bc7b3d6d315736ebf7c) Thanks [@adrien2p](https://github.com/adrien2p)! - feat: region payment providers management workflows/api
|
||||
|
||||
- [#6925](https://github.com/medusajs/medusa/pull/6925) [`dd35a4dbff`](https://github.com/medusajs/medusa/commit/dd35a4dbff10c86ea3c5f7f817c18b6e60d599e3) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Remote Query "order" as options
|
||||
|
||||
- [#6876](https://github.com/medusajs/medusa/pull/6876) [`1bcb13f892`](https://github.com/medusajs/medusa/commit/1bcb13f892bc61db21b3fc6bdbce85f747aeec4c) Thanks [@olivermrbl](https://github.com/olivermrbl)! - feat: Remove sales channels from pub keys
|
||||
|
||||
- [#6901](https://github.com/medusajs/medusa/pull/6901) [`82a176e30e`](https://github.com/medusajs/medusa/commit/82a176e30e47a7d11caaf31c3023bd8db588b465) Thanks [@adrien2p](https://github.com/adrien2p)! - chore(medusa-test-utils): Handle errors gracefully + Do not set Distributed storage on partial loading
|
||||
|
||||
- [#6982](https://github.com/medusajs/medusa/pull/6982) [`528ef4ca90`](https://github.com/medusajs/medusa/commit/528ef4ca90bb2cf6173dccc9fd6a9f9932ff9b76) Thanks [@adrien2p](https://github.com/adrien2p)! - fix(modules-sdk): Select all should send undefined instead of an empty array
|
||||
|
||||
- [#7153](https://github.com/medusajs/medusa/pull/7153) [`4b57c5d286`](https://github.com/medusajs/medusa/commit/4b57c5d286f9dc6e2098c67e9fecb0d93175b5a1) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Remote query supporting context
|
||||
|
||||
- [#6989](https://github.com/medusajs/medusa/pull/6989) [`667c8609cc`](https://github.com/medusajs/medusa/commit/667c8609ccf3850f5df8cf784723a95bd0d6d2a6) Thanks [@adrien2p](https://github.com/adrien2p)! - fix(modules-sdk): Fix remote query selection
|
||||
|
||||
- [#6865](https://github.com/medusajs/medusa/pull/6865) [`8fd1488938`](https://github.com/medusajs/medusa/commit/8fd148893850eb66c5eae00c4ca9391a80ea2eb9) Thanks [@adrien2p](https://github.com/adrien2p)! - chore: medusa shutdown
|
||||
|
||||
- [#6849](https://github.com/medusajs/medusa/pull/6849) [`1c6ba4468e`](https://github.com/medusajs/medusa/commit/1c6ba4468eab1440931c88929affd5b4c593f377) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Remote Joiner options to check if keys exist on entrypoints or relations
|
||||
|
||||
- Updated dependencies [[`0c0b425de7`](https://github.com/medusajs/medusa/commit/0c0b425de7b154b80b712ab17b16215cf62d1e83), [`e603726985`](https://github.com/medusajs/medusa/commit/e60372698565315145037eb40fbe86c43f91cc16), [`8d356217bd`](https://github.com/medusajs/medusa/commit/8d356217bd31c97a196e861ee243822a4d924df7), [`d333db0842`](https://github.com/medusajs/medusa/commit/d333db08429611c0571a0b173adf37103b8a8aa6), [`20e8df914e`](https://github.com/medusajs/medusa/commit/20e8df914ec5fdf8d562d4fa84f72c58c7056195), [`a164c0d512`](https://github.com/medusajs/medusa/commit/a164c0d5126a40e2bc669f9fc2883be502a15036), [`27f4f0d724`](https://github.com/medusajs/medusa/commit/27f4f0d7243367c2dfc6012bf1f6b7400a77ec7b), [`e0b02a1012`](https://github.com/medusajs/medusa/commit/e0b02a1012981c29830d7779f59ebe805bbfd137), [`e944a627f0`](https://github.com/medusajs/medusa/commit/e944a627f074fb39a56f4bc7b3d6d315736ebf7c), [`1a48fe0282`](https://github.com/medusajs/medusa/commit/1a48fe0282a8bc1f8548a4736255e457d173da09), [`86f499de2f`](https://github.com/medusajs/medusa/commit/86f499de2f31356ab36ad5e93f27345443b3e5f6), [`09a2220569`](https://github.com/medusajs/medusa/commit/09a22205693da62fbf8fd450535d5024cb9c01d1), [`78f603e4f1`](https://github.com/medusajs/medusa/commit/78f603e4f18c9d16f4b58a2189c959026453d8b2), [`cc557c8752`](https://github.com/medusajs/medusa/commit/cc557c8752fd0554f5a1b58522d9a88dc43a8509), [`58c68f6715`](https://github.com/medusajs/medusa/commit/58c68f67156e993255fbc25d91db15ae23bc95c0), [`82a176e30e`](https://github.com/medusajs/medusa/commit/82a176e30e47a7d11caaf31c3023bd8db588b465), [`11517f0faf`](https://github.com/medusajs/medusa/commit/11517f0fafdf00af256240448b58d149d8b6f600), [`62b9dcc6c1`](https://github.com/medusajs/medusa/commit/62b9dcc6c1ce46aadb7944215006c12da3c9f619), [`5d9aea053c`](https://github.com/medusajs/medusa/commit/5d9aea053ce6e04f242f86fb9053c13dec515d5b), [`e26cda4b6a`](https://github.com/medusajs/medusa/commit/e26cda4b6afb7fb25f0b0a7a7ce20b7f914d35db), [`bc06ad2db4`](https://github.com/medusajs/medusa/commit/bc06ad2db48c999023ab823fefc1375196976e9b), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`232322d035`](https://github.com/medusajs/medusa/commit/232322d03515f81e56867ff8c765b8409399ee68), [`38c971f111`](https://github.com/medusajs/medusa/commit/38c971f111af69f176e7e9892eb59f5bae831fa7), [`45c49e89f2`](https://github.com/medusajs/medusa/commit/45c49e89f28123ef622fc1c07253bae94fd74875), [`528ef4ca90`](https://github.com/medusajs/medusa/commit/528ef4ca90bb2cf6173dccc9fd6a9f9932ff9b76), [`65794f4bb5`](https://github.com/medusajs/medusa/commit/65794f4bb56e4fd3f0ccb7656a948f856f05324e), [`93ef94cad3`](https://github.com/medusajs/medusa/commit/93ef94cad3ddc5b6973b4e48e422b0aa0e6ddbbe), [`4cf71af07d`](https://github.com/medusajs/medusa/commit/4cf71af07d1807c83df3889c1774f82cbd1b9a6f), [`4b57c5d286`](https://github.com/medusajs/medusa/commit/4b57c5d286f9dc6e2098c67e9fecb0d93175b5a1), [`c78915c7c5`](https://github.com/medusajs/medusa/commit/c78915c7c5e91a99c1b1bae932656c8d86b17daf), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`3affcc2525`](https://github.com/medusajs/medusa/commit/3affcc252599c93cf52fef24ec4f3695d65db79a), [`f175cac4af`](https://github.com/medusajs/medusa/commit/f175cac4af63b71066a8398ecf9beaa6f28b20cc), [`edcafa140c`](https://github.com/medusajs/medusa/commit/edcafa140c1869df3c420c2becfbf23b6af75ddc), [`0a9b9b073d`](https://github.com/medusajs/medusa/commit/0a9b9b073dd2d3f4aa5e5cb1c16e2221a7200e0d), [`a6562d2a41`](https://github.com/medusajs/medusa/commit/a6562d2a41453cbe7aa43be352c4924e3e4c79d5), [`00e6b21bb5`](https://github.com/medusajs/medusa/commit/00e6b21bb50dbc886bc37ad052a1c40ce865294e), [`8fd1488938`](https://github.com/medusajs/medusa/commit/8fd148893850eb66c5eae00c4ca9391a80ea2eb9), [`1c6ba4468e`](https://github.com/medusajs/medusa/commit/1c6ba4468eab1440931c88929affd5b4c593f377)]:
|
||||
- @medusajs/types@1.11.16
|
||||
- @medusajs/orchestration@0.5.7
|
||||
- @medusajs/utils@1.11.9
|
||||
|
||||
## 1.12.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6742](https://github.com/medusajs/medusa/pull/6742) [`06f22bb48a`](https://github.com/medusajs/medusa/commit/06f22bb48ad1fe73577657b8c5db055312f16a0d) Thanks [@adrien2p](https://github.com/adrien2p)! - fix(orchestration): Properly handle select all
|
||||
|
||||
- [#6739](https://github.com/medusajs/medusa/pull/6739) [`56481e683d`](https://github.com/medusajs/medusa/commit/56481e683d33ff98f0d4c4e144873bb23f993c9c) Thanks [@srindom](https://github.com/srindom)! - feat: v2 - add worker mode
|
||||
|
||||
- Updated dependencies [[`06f22bb48a`](https://github.com/medusajs/medusa/commit/06f22bb48ad1fe73577657b8c5db055312f16a0d), [`deab12e27e`](https://github.com/medusajs/medusa/commit/deab12e27e8249e26d24d7bc904c18195679ff24), [`7e93eda1a4`](https://github.com/medusajs/medusa/commit/7e93eda1a44310311d2f3f8a1d634f60e7c48cb5), [`56481e683d`](https://github.com/medusajs/medusa/commit/56481e683d33ff98f0d4c4e144873bb23f993c9c), [`9073d7aba3`](https://github.com/medusajs/medusa/commit/9073d7aba3419e4dc0a206473291a46ebd79b8c1), [`4974f5e455`](https://github.com/medusajs/medusa/commit/4974f5e4557bd64a328a881ec02b91e15485bd23), [`05e857d256`](https://github.com/medusajs/medusa/commit/05e857d25657b5576a891c9b48c19c1759c70701), [`1ef9c78cea`](https://github.com/medusajs/medusa/commit/1ef9c78cea080c3b7c136f909c6cddec9d8f0c62)]:
|
||||
- @medusajs/orchestration@0.5.6
|
||||
- @medusajs/types@1.11.15
|
||||
- @medusajs/utils@1.11.8
|
||||
|
||||
## 1.12.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6411](https://github.com/medusajs/medusa/pull/6411) [`586df9da25`](https://github.com/medusajs/medusa/commit/586df9da250e492442769f5bac2f8b3de1d46f05) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Fix custom modules loader
|
||||
|
||||
- [#6399](https://github.com/medusajs/medusa/pull/6399) [`339a946f38`](https://github.com/medusajs/medusa/commit/339a946f389033c21e05338f9dbf07d88e140533) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Initial Order module implementation
|
||||
|
||||
- [#6561](https://github.com/medusajs/medusa/pull/6561) [`d550be3685`](https://github.com/medusajs/medusa/commit/d550be3685423218d47a20c57a5e06758f4a961a) Thanks [@riqwan](https://github.com/riqwan)! - feat(core-flows,link-modules,modules-sdk): add cart <> promotion link as source of truth
|
||||
|
||||
- [#6700](https://github.com/medusajs/medusa/pull/6700) [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Version all modules to allow for initial testing
|
||||
|
||||
- [#6622](https://github.com/medusajs/medusa/pull/6622) [`4625bd1241`](https://github.com/medusajs/medusa/commit/4625bd12416275b09c22cde4a09cb0f68df5d7c1) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Remote joiner options autoCreateServiceNameAlias
|
||||
|
||||
- [#6392](https://github.com/medusajs/medusa/pull/6392) [`24fb102a56`](https://github.com/medusajs/medusa/commit/24fb102a564b1253d1f8b039bb1e435cc5312fbb) Thanks [@olivermrbl](https://github.com/olivermrbl)! - feat: CartRegion link, definition + workflow
|
||||
|
||||
- Updated dependencies [[`1fd0457c15`](https://github.com/medusajs/medusa/commit/1fd0457c153b2ef7657c052878d8e5364e1b324a), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`d4b921f3db`](https://github.com/medusajs/medusa/commit/d4b921f3dbe0a38f1565a8de759996c70798d58e), [`ac86362e81`](https://github.com/medusajs/medusa/commit/ac86362e81d8523cb8e3dfad026fc94658513018), [`e4acde1aa2`](https://github.com/medusajs/medusa/commit/e4acde1aa2eb57f07e6692fe8b61f728948b9a96), [`1a661adf3e`](https://github.com/medusajs/medusa/commit/1a661adf3ef4991aa6e237dd894b6a5c47cd4aca), [`56cbf88115`](https://github.com/medusajs/medusa/commit/56cbf88115994adea7037c3f2814f0c96af3cfc0), [`36a61658f9`](https://github.com/medusajs/medusa/commit/36a61658f969a7b19c84a1e621ad1464927cafb1), [`04a532e5ef`](https://github.com/medusajs/medusa/commit/04a532e5efabbf75b1e4155520b1da175b686ffc), [`25d176b851`](https://github.com/medusajs/medusa/commit/25d176b851c041ecc7901163b704b36b7e585076), [`c319edb8e0`](https://github.com/medusajs/medusa/commit/c319edb8e0ecd13d086652147667916e5abab2d8), [`0b9fcb6324`](https://github.com/medusajs/medusa/commit/0b9fcb6324eee9f2556c7e6317775fae93b12a47), [`b3d826497b`](https://github.com/medusajs/medusa/commit/b3d826497b3dae5e1b26b7924706c24fd5e87ca5), [`a86c87fe14`](https://github.com/medusajs/medusa/commit/a86c87fe1442afce9285e39255914e01012b4449), [`640eccd5dd`](https://github.com/medusajs/medusa/commit/640eccd5ddbb163e0f987ce6c772f1129c2e2632), [`8ea37d03c9`](https://github.com/medusajs/medusa/commit/8ea37d03c914a5004a3e42770668b2d1f7f8f564), [`339a946f38`](https://github.com/medusajs/medusa/commit/339a946f389033c21e05338f9dbf07d88e140533), [`ac829fc67f`](https://github.com/medusajs/medusa/commit/ac829fc67f7495b08f28e55923c59f0fd6320311), [`d9d5afc3cf`](https://github.com/medusajs/medusa/commit/d9d5afc3cfc29221d0e65bff7b78474a8fb8f31f), [`c3c4f49fc2`](https://github.com/medusajs/medusa/commit/c3c4f49fc2126f950e69e291ca939ca88a15afd3), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`0d46abf0ff`](https://github.com/medusajs/medusa/commit/0d46abf0ffa4c5e03bf7d2a9cdf1db828a76bea8), [`fafde4f54d`](https://github.com/medusajs/medusa/commit/fafde4f54d3ef75a7d382e6cbf94e38b3deae99b), [`8dad2b51a2`](https://github.com/medusajs/medusa/commit/8dad2b51a26c4c3c14a6c95f70424c8bef2ad63e), [`0c705d7bd4`](https://github.com/medusajs/medusa/commit/0c705d7bd41a768c48017ae95b3c8414d96c6acb), [`8a946beb75`](https://github.com/medusajs/medusa/commit/8a946beb75dd2560baffb5adc508969bf1fe8ef5), [`a6d7070dd6`](https://github.com/medusajs/medusa/commit/a6d7070dd669c21ea19d70434d42c2f8167dc309), [`1d91b7429b`](https://github.com/medusajs/medusa/commit/1d91b7429beebd6f09d5027f7f7e1fe74ce3a8ff), [`168f02f138`](https://github.com/medusajs/medusa/commit/168f02f138ad101e1013f2c8c3f8dc19de12accf), [`1ed5f918c3`](https://github.com/medusajs/medusa/commit/1ed5f918c31794a70aca4a4e4cd83cf456593baa), [`c20eb15cd9`](https://github.com/medusajs/medusa/commit/c20eb15cd9b1bd90c8d01f68eca6f0f181cd902d), [`e5945479e0`](https://github.com/medusajs/medusa/commit/e5945479e091d9560ae3e7240306a31031ef4584), [`f5c2256286`](https://github.com/medusajs/medusa/commit/f5c22562867f412040f8bc6c55ab5de3a3735e62), [`000eb61e33`](https://github.com/medusajs/medusa/commit/000eb61e33e0302db95ee6ad1656ea9b430ed471), [`62a7bcc30c`](https://github.com/medusajs/medusa/commit/62a7bcc30cbc7b234b2b51d7858439951a84edeb), [`a8794d48de`](https://github.com/medusajs/medusa/commit/a8794d48de11c4b31f7d8f0dd254caf17daf052e), [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8), [`6500f18b9b`](https://github.com/medusajs/medusa/commit/6500f18b9b80c5c9c473489e7e740d55dca74303), [`ce39b9b66e`](https://github.com/medusajs/medusa/commit/ce39b9b66e8c277ec0691ea6d0a950003be09cc1), [`a6a4b3f01a`](https://github.com/medusajs/medusa/commit/a6a4b3f01a6d2bd97b1580c59134279a1b033a5d), [`4d51f095b3`](https://github.com/medusajs/medusa/commit/4d51f095b3f98f468cefb760512563f7b77bb9cf), [`4625bd1241`](https://github.com/medusajs/medusa/commit/4625bd12416275b09c22cde4a09cb0f68df5d7c1), [`56b0b45304`](https://github.com/medusajs/medusa/commit/56b0b4530401a6ec5aa155874d371e45bb388fe2), [`cc1b66842c`](https://github.com/medusajs/medusa/commit/cc1b66842cbb37c6eab84e2d8b74844c214f38d7), [`24fb102a56`](https://github.com/medusajs/medusa/commit/24fb102a564b1253d1f8b039bb1e435cc5312fbb), [`e85463b2a7`](https://github.com/medusajs/medusa/commit/e85463b2a717751de2e21c39a4c745449b31affe)]:
|
||||
- @medusajs/types@1.11.14
|
||||
- @medusajs/utils@1.11.7
|
||||
- @medusajs/orchestration@0.5.5
|
||||
|
||||
## 1.12.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6319](https://github.com/medusajs/medusa/pull/6319) [`12054f5c0`](https://github.com/medusajs/medusa/commit/12054f5c01915899223ddc6da734151b31fbb23b) Thanks [@adrien2p](https://github.com/adrien2p)! - Feat/shipping method init
|
||||
|
||||
- [#5923](https://github.com/medusajs/medusa/pull/5923) [`3db2f95e65`](https://github.com/medusajs/medusa/commit/3db2f95e65909f4fff432990b48be74509052e83) Thanks [@fPolic](https://github.com/fPolic)! - feat: Sales Channel module
|
||||
|
||||
- [#6312](https://github.com/medusajs/medusa/pull/6312) [`96ba49329`](https://github.com/medusajs/medusa/commit/96ba49329b6b05922c90f0c55f16455cb40aa5ca) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Load custom modules by path
|
||||
|
||||
- [#6263](https://github.com/medusajs/medusa/pull/6263) [`45134e4d1`](https://github.com/medusajs/medusa/commit/45134e4d11cfcdc08dbd10aae687bfbe9e848ab9) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Workflows passing MedusaContext as argument
|
||||
|
||||
- [#6218](https://github.com/medusajs/medusa/pull/6218) [`884428a1b`](https://github.com/medusajs/medusa/commit/884428a1b573e499d7659aefed639bf797147428) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Feat: Event Aggregator
|
||||
|
||||
- Updated dependencies [[`12054f5c0`](https://github.com/medusajs/medusa/commit/12054f5c01915899223ddc6da734151b31fbb23b), [`3db2f95e65`](https://github.com/medusajs/medusa/commit/3db2f95e65909f4fff432990b48be74509052e83), [`96ba49329`](https://github.com/medusajs/medusa/commit/96ba49329b6b05922c90f0c55f16455cb40aa5ca), [`45134e4d1`](https://github.com/medusajs/medusa/commit/45134e4d11cfcdc08dbd10aae687bfbe9e848ab9), [`82c728bec`](https://github.com/medusajs/medusa/commit/82c728bec7232a245a67dca0b01b28572ebea75d), [`e749dd653`](https://github.com/medusajs/medusa/commit/e749dd653c755bfc3632b134d32c15ceaee0a852), [`884428a1b`](https://github.com/medusajs/medusa/commit/884428a1b573e499d7659aefed639bf797147428), [`d1c18a309`](https://github.com/medusajs/medusa/commit/d1c18a3090d71c68a98343fdbb53516f416504c5), [`882aa549b`](https://github.com/medusajs/medusa/commit/882aa549bdcc6f378934eab2a7c485df354f46aa)]:
|
||||
- @medusajs/utils@1.11.5
|
||||
- @medusajs/types@1.11.12
|
||||
- @medusajs/orchestration@0.5.4
|
||||
|
||||
## 1.12.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5763](https://github.com/medusajs/medusa/pull/5763) [`d85fee42e`](https://github.com/medusajs/medusa/commit/d85fee42ee7f661310584dfee5741d6c53b989bb) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Medusa App loading modules reference
|
||||
|
||||
- [#6111](https://github.com/medusajs/medusa/pull/6111) [`2b9f98895e`](https://github.com/medusajs/medusa/commit/2b9f98895eaca255e01278674b11cd7cb69b388f) Thanks [@pKorsholm](https://github.com/pKorsholm)! - feat(modules-sdk): run hooks on application start
|
||||
|
||||
- [#6128](https://github.com/medusajs/medusa/pull/6128) [`302323916`](https://github.com/medusajs/medusa/commit/302323916b6d8eaf571cd59b5fc92a913af207de) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Modules: Workflows Engine in-memory and Redis
|
||||
|
||||
- [#6059](https://github.com/medusajs/medusa/pull/6059) [`8472460f53`](https://github.com/medusajs/medusa/commit/8472460f533322cc4535199aa768ac163021bc79) Thanks [@fPolic](https://github.com/fPolic)! - feat: add Payment module package
|
||||
|
||||
- Updated dependencies [[`68ddd866a5`](https://github.com/medusajs/medusa/commit/68ddd866a5ff9414e2db5b80d75acc5e81948540), [`72bc52231c`](https://github.com/medusajs/medusa/commit/72bc52231ca3a72fa6d197a248fe07a938ed0d85), [`99045848f`](https://github.com/medusajs/medusa/commit/99045848fd3e863359c7878d9bc05271ed083a0e), [`af7af7374`](https://github.com/medusajs/medusa/commit/af7af737455daa0f330840a9678e6339e519dfe6), [`fc6b1772a7`](https://github.com/medusajs/medusa/commit/fc6b1772a71582bb48602c5cac7b2297e9d267a9), [`a9b4214503`](https://github.com/medusajs/medusa/commit/a9b42145032ee88aa922a11fe03e777b140c68f4), [`d85fee42e`](https://github.com/medusajs/medusa/commit/d85fee42ee7f661310584dfee5741d6c53b989bb), [`5e655dd59`](https://github.com/medusajs/medusa/commit/5e655dd59bda4ffface28db38021ba71cae6de10), [`b132ff7669`](https://github.com/medusajs/medusa/commit/b132ff76693148b3a06373c168e8dd5e02970757), [`e28fa7fbdf`](https://github.com/medusajs/medusa/commit/e28fa7fbdf45c5b1fa19848db731132a0bf1757d), [`a12c28b7d5`](https://github.com/medusajs/medusa/commit/a12c28b7d5faed733bebbb4963dff50b9c8a33bc), [`b782d3bcb7`](https://github.com/medusajs/medusa/commit/b782d3bcb7e8088a962584b9a55200dd29c2161c), [`7f7cb2a263`](https://github.com/medusajs/medusa/commit/7f7cb2a263c26baf540b05a40ab3732ffeb0c73c), [`302323916`](https://github.com/medusajs/medusa/commit/302323916b6d8eaf571cd59b5fc92a913af207de), [`da5cc4cf7`](https://github.com/medusajs/medusa/commit/da5cc4cf7f7f0ef40d409704a95b025ce95477f4), [`daecd82a7`](https://github.com/medusajs/medusa/commit/daecd82a7cdf7315599f464999690414c20d6748), [`ce81cade88`](https://github.com/medusajs/medusa/commit/ce81cade887659cefe9638e3c1c2807378191c62), [`fd78f5e24`](https://github.com/medusajs/medusa/commit/fd78f5e24263f5e158c3b7d11fbf0a4436e9c17a), [`192bc336cc`](https://github.com/medusajs/medusa/commit/192bc336cc2b6ec3820d94524c046dcd3c4ac7d9), [`06b33a9b4`](https://github.com/medusajs/medusa/commit/06b33a9b4525b77b1b14b35b973209700945654e), [`b6ac768698`](https://github.com/medusajs/medusa/commit/b6ac768698a3b49d0162cb49e628386f3352d034), [`130c641e5c`](https://github.com/medusajs/medusa/commit/130c641e5c91cf831de64fb87aebbfdc4d23530d), [`fade8ea7bf`](https://github.com/medusajs/medusa/commit/fade8ea7bf560343ecbde116d226ac44053cdb8e), [`8472460f53`](https://github.com/medusajs/medusa/commit/8472460f533322cc4535199aa768ac163021bc79), [`68d8daccd`](https://github.com/medusajs/medusa/commit/68d8daccd2a8508a13e211130e49017198b51fab)]:
|
||||
- @medusajs/types@1.11.11
|
||||
- @medusajs/utils@1.11.4
|
||||
- @medusajs/orchestration@0.5.3
|
||||
|
||||
## 1.12.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5943](https://github.com/medusajs/medusa/pull/5943) [`6d1e3cc02`](https://github.com/medusajs/medusa/commit/6d1e3cc0285ef157fd6486060e8b32c00c01aa80) Thanks [@pKorsholm](https://github.com/pKorsholm)! - feat(modules-sdk, types): add initial authentication module configuration
|
||||
|
||||
- [#5982](https://github.com/medusajs/medusa/pull/5982) [`925feea04`](https://github.com/medusajs/medusa/commit/925feea04a8222285175c33577548e50516069a7) Thanks [@olivermrbl](https://github.com/olivermrbl)! - feat(cart): Add cart module package
|
||||
|
||||
- [#5920](https://github.com/medusajs/medusa/pull/5920) [`3f6d79961`](https://github.com/medusajs/medusa/commit/3f6d79961dec1c5eb8950f8eacd94a5d87a4acde) Thanks [@riqwan](https://github.com/riqwan)! - feat(types,modules-sdk): basic module setup for promotions
|
||||
|
||||
- Updated dependencies [[`6d1e3cc02`](https://github.com/medusajs/medusa/commit/6d1e3cc0285ef157fd6486060e8b32c00c01aa80), [`42cc8ae3f`](https://github.com/medusajs/medusa/commit/42cc8ae3f89ed7d642e51654d1a3cca011f13155), [`45996d58a2`](https://github.com/medusajs/medusa/commit/45996d58a2665d72335faad11bea958f8da74195), [`9cc787cac4`](https://github.com/medusajs/medusa/commit/9cc787cac4bf1c5d8edf1c4b548bb3205100e822), [`bf63c4e6a`](https://github.com/medusajs/medusa/commit/bf63c4e6a32258565e1d361b8919afbf93cc2c72), [`355075097`](https://github.com/medusajs/medusa/commit/3550750975a0c9359fd887929377733606ef03af), [`dc46ee118`](https://github.com/medusajs/medusa/commit/dc46ee1189c3eb719355da6a1d701c14a77e4578), [`925feea04`](https://github.com/medusajs/medusa/commit/925feea04a8222285175c33577548e50516069a7), [`3f6d79961`](https://github.com/medusajs/medusa/commit/3f6d79961dec1c5eb8950f8eacd94a5d87a4acde), [`fbee006e5`](https://github.com/medusajs/medusa/commit/fbee006e512ef2d56ffb23eeabad8b51b56be285), [`c41f3002f`](https://github.com/medusajs/medusa/commit/c41f3002f3118b1f195c5c822fe0f400091d115b), [`d16d10619`](https://github.com/medusajs/medusa/commit/d16d10619dfbd3966a4709753de3d8cc37c6f2eb), [`c1c470e6b`](https://github.com/medusajs/medusa/commit/c1c470e6b8646c5f0b4bca56a8e785f6c34e1fef), [`890e76a5c`](https://github.com/medusajs/medusa/commit/890e76a5c53039576c42ca4d46af6f6977cdebd1), [`fe007d01b`](https://github.com/medusajs/medusa/commit/fe007d01bd827f0e09ee545e48cef18913540c68), [`76332ca6c`](https://github.com/medusajs/medusa/commit/76332ca6c153a786acc07d3f06ff45c3b9346fd3)]:
|
||||
- @medusajs/types@1.11.10
|
||||
- @medusajs/utils@1.11.3
|
||||
- @medusajs/orchestration@0.5.2
|
||||
|
||||
## 1.12.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5750](https://github.com/medusajs/medusa/pull/5750) [`c4deeee48`](https://github.com/medusajs/medusa/commit/c4deeee481399f5371d773173e20dc149d502e20) Thanks [@riqwan](https://github.com/riqwan)! - fix(modules-sdk): add graphql dependency to modules-sdk
|
||||
|
||||
- Updated dependencies [[`079f0da83`](https://github.com/medusajs/medusa/commit/079f0da83f482562bbb525807ee1a7e32993b4da), [`07107f356`](https://github.com/medusajs/medusa/commit/07107f3565bc2c97e9b89818b390210454f0566d), [`8f25ed8a1`](https://github.com/medusajs/medusa/commit/8f25ed8a10fe23e9342dc3d03545546b4ad4d6da)]:
|
||||
- @medusajs/types@1.11.9
|
||||
- @medusajs/utils@1.11.2
|
||||
- @medusajs/orchestration@0.5.1
|
||||
|
||||
## 1.12.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a39ce125c`](https://github.com/medusajs/medusa/commit/a39ce125cc96f14732d5a6301313d2376484fa23), [`ddbeed4ea`](https://github.com/medusajs/medusa/commit/ddbeed4ea67d67a7aef04eb3135087551a7e9b37), [`18afe0b9a`](https://github.com/medusajs/medusa/commit/18afe0b9addb33ec2e3b285651b4eb1ef8065845), [`6025c702f`](https://github.com/medusajs/medusa/commit/6025c702f37d43e18af32bd716f33410d95efd19), [`de8f74867`](https://github.com/medusajs/medusa/commit/de8f748674bfd19b3dbadb9695d9080aa91940de), [`fc1ef29ed`](https://github.com/medusajs/medusa/commit/fc1ef29ed935e192f0943a2bf4b8fbb05ce6890d), [`0df1c7d42`](https://github.com/medusajs/medusa/commit/0df1c7d4273545bc717555611b9294a5c222e5ae), [`9f9db3969`](https://github.com/medusajs/medusa/commit/9f9db396987776039ad0c2b8d5792d0ebdbf8792), [`dc5750dd6`](https://github.com/medusajs/medusa/commit/dc5750dd665a91d35c0246ba83c7f90ec74907f4)]:
|
||||
- @medusajs/types@1.11.8
|
||||
- @medusajs/orchestration@0.5.0
|
||||
- @medusajs/utils@1.11.1
|
||||
|
||||
## 1.12.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`c39bf69a5`](https://github.com/medusajs/medusa/commit/c39bf69a5e5cae75d7fa12aa6022b10903557a32) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Add missing version bumps
|
||||
|
||||
- [#5496](https://github.com/medusajs/medusa/pull/5496) [`154c9b43b`](https://github.com/medusajs/medusa/commit/154c9b43bde1fdff562aba9da8a79af2660b29b3) Thanks [@adrien2p](https://github.com/adrien2p)! - feat(medusa, modules-sdk, types, utils): Re work modules loading and remove legacy functions
|
||||
|
||||
- Updated dependencies [[`91615f9c4`](https://github.com/medusajs/medusa/commit/91615f9c459a2d8cb842561c5edb335680d30298), [`c39bf69a5`](https://github.com/medusajs/medusa/commit/c39bf69a5e5cae75d7fa12aa6022b10903557a32), [`154c9b43b`](https://github.com/medusajs/medusa/commit/154c9b43bde1fdff562aba9da8a79af2660b29b3)]:
|
||||
- @medusajs/types@1.11.6
|
||||
- @medusajs/orchestration@0.4.4
|
||||
- @medusajs/utils@1.10.5
|
||||
|
||||
## 1.12.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5468](https://github.com/medusajs/medusa/pull/5468) [`a45da9215`](https://github.com/medusajs/medusa/commit/a45da9215d2a7834c368037726aaa3961caadaf9) Thanks [@adrien2p](https://github.com/adrien2p)! - fix(medusa, modules-sdk, modules): Module loading was missing the expected dependencies and remote query reference fix
|
||||
|
||||
- Updated dependencies [[`148f537b4`](https://github.com/medusajs/medusa/commit/148f537b47635e8b73ebaa27bbfbe58624bfe641), [`5c77029cb`](https://github.com/medusajs/medusa/commit/5c77029cb021690a4f617b83b4999ed55bf9dea1)]:
|
||||
- @medusajs/types@1.11.5
|
||||
- @medusajs/orchestration@0.4.3
|
||||
|
||||
## 1.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5317](https://github.com/medusajs/medusa/pull/5317) [`b62af612c`](https://github.com/medusajs/medusa/commit/b62af612c7baa244075e546c949b89c4589bd2cf) Thanks [@riqwan](https://github.com/riqwan)! - feat(link-modules,modules-sdk,pricing): Medusa App Migrations + Core compatible migrations
|
||||
|
||||
- Updated dependencies [[`378ca1b36`](https://github.com/medusajs/medusa/commit/378ca1b36e909a67e39c69ea5ca94ec58a345878), [`69cf7215f`](https://github.com/medusajs/medusa/commit/69cf7215f1f730ffb332129e65211470be1f88f1), [`453297f52`](https://github.com/medusajs/medusa/commit/453297f525bd9f3aaa95bf0b28ff6cd31e6696b4), [`e47461d95`](https://github.com/medusajs/medusa/commit/e47461d95caecf3a447ee9fa0b0950340b93f282), [`66413d094`](https://github.com/medusajs/medusa/commit/66413d094e916debbdb74b68800c96ca2c9302c9)]:
|
||||
- @medusajs/utils@1.10.4
|
||||
- @medusajs/types@1.11.4
|
||||
- @medusajs/orchestration@0.4.2
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#5242](https://github.com/medusajs/medusa/pull/5242) [`130cbc1f4`](https://github.com/medusajs/medusa/commit/130cbc1f437af211b6d05f80128d90138abcd38d) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Modules exporting schema with entities and fields
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5216](https://github.com/medusajs/medusa/pull/5216) [`eeceec791`](https://github.com/medusajs/medusa/commit/eeceec791c141996cf7fd06555afb6e738b52840) Thanks [@adrien2p](https://github.com/adrien2p)! - feat(utils): Convert remote query object to string array of fields and relations from GQL schema
|
||||
|
||||
- Updated dependencies [[`eeceec791`](https://github.com/medusajs/medusa/commit/eeceec791c141996cf7fd06555afb6e738b52840), [`130cbc1f4`](https://github.com/medusajs/medusa/commit/130cbc1f437af211b6d05f80128d90138abcd38d), [`cb569c2df`](https://github.com/medusajs/medusa/commit/cb569c2dfe2d83e1ff72a49f2331450a83b73325), [`c5703a476`](https://github.com/medusajs/medusa/commit/c5703a4765a55da697885438cf3089d923669f21)]:
|
||||
- @medusajs/utils@1.10.3
|
||||
- @medusajs/types@1.11.3
|
||||
|
||||
## 1.11.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5168](https://github.com/medusajs/medusa/pull/5168) [`dfa5d041c`](https://github.com/medusajs/medusa/commit/dfa5d041c90b849b288f8ae9f5a0a1aa3ee1b32e) Thanks [@adrien2p](https://github.com/adrien2p)! - Feat/update cart product isolation
|
||||
|
||||
- Updated dependencies [[`cc4169a94`](https://github.com/medusajs/medusa/commit/cc4169a94c7c5f5bf4d04f7b6e815b409a0a8192), [`30233712c`](https://github.com/medusajs/medusa/commit/30233712cb93b38de50c266a9841cea413949611), [`1e7db5a5c`](https://github.com/medusajs/medusa/commit/1e7db5a5cb7c955e72c52e64df8a16b1607eef70)]:
|
||||
- @medusajs/utils@1.10.2
|
||||
- @medusajs/orchestration@0.4.1
|
||||
- @medusajs/types@1.11.2
|
||||
|
||||
## 1.11.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5115](https://github.com/medusajs/medusa/pull/5115) [`47603f479`](https://github.com/medusajs/medusa/commit/47603f479731ad495938b056da7a493991a1d9a9) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Allow MedusaApp load legacy modules
|
||||
|
||||
## 1.11.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5114](https://github.com/medusajs/medusa/pull/5114) [`1e5a6d435`](https://github.com/medusajs/medusa/commit/1e5a6d4359135b82bb34f1dff184b508810443ac) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Fix default module path
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#4974](https://github.com/medusajs/medusa/pull/4974) [`d8649baca`](https://github.com/medusajs/medusa/commit/d8649bacaa2ed784b9e7b2b0e1f1194d3697bb92) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - object to remote joiner query
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5018](https://github.com/medusajs/medusa/pull/5018) [`4fa675ec2`](https://github.com/medusajs/medusa/commit/4fa675ec25b3d6fccd881c4f5a5b91f0e9e13e82) Thanks [@adrien2p](https://github.com/adrien2p)! - chore(medusa, modules-sdk, types): Refactor modules loading from medusa
|
||||
|
||||
- [#4961](https://github.com/medusajs/medusa/pull/4961) [`6273b4b16`](https://github.com/medusajs/medusa/commit/6273b4b160493463e1199e5db4e9cfa4cff6fbe4) Thanks [@adrien2p](https://github.com/adrien2p)! - feat(utils, module-sdk, medusa, types): Extract pg connection utils to utils package
|
||||
|
||||
- [#4969](https://github.com/medusajs/medusa/pull/4969) [`30863fee5`](https://github.com/medusajs/medusa/commit/30863fee529ed035f161c749fda3cd64fa48efb1) Thanks [@adrien2p](https://github.com/adrien2p)! - feat: store List products remote query with product isolation
|
||||
|
||||
- Updated dependencies [[`d8649baca`](https://github.com/medusajs/medusa/commit/d8649bacaa2ed784b9e7b2b0e1f1194d3697bb92), [`4fa675ec2`](https://github.com/medusajs/medusa/commit/4fa675ec25b3d6fccd881c4f5a5b91f0e9e13e82), [`6273b4b16`](https://github.com/medusajs/medusa/commit/6273b4b160493463e1199e5db4e9cfa4cff6fbe4), [`30863fee5`](https://github.com/medusajs/medusa/commit/30863fee529ed035f161c749fda3cd64fa48efb1), [`3d68be2b6`](https://github.com/medusajs/medusa/commit/3d68be2b6b93ae928f5c955e102ebdf2c34fb364), [`a87d07655`](https://github.com/medusajs/medusa/commit/a87d07655bd8a1da8b90feb739daddd09295f724), [`edf90eecb`](https://github.com/medusajs/medusa/commit/edf90eecb487f6e031f2e2d0899de5ca2504cb12), [`107aaa371`](https://github.com/medusajs/medusa/commit/107aaa371c444843874d125bf8bd493ef89f5756), [`834da5c41`](https://github.com/medusajs/medusa/commit/834da5c41a7c043373f72239b6fdbf7815d9b4aa)]:
|
||||
- @medusajs/orchestration@0.4.0
|
||||
- @medusajs/types@1.11.1
|
||||
- @medusajs/utils@1.10.1
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#4695](https://github.com/medusajs/medusa/pull/4695) [`4d16acf5f`](https://github.com/medusajs/medusa/commit/4d16acf5f096b5656b645f510f9c971e7c2dc9ef) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Medusa App Loader
|
||||
|
||||
- [#4695](https://github.com/medusajs/medusa/pull/4695) [`4d16acf5f`](https://github.com/medusajs/medusa/commit/4d16acf5f096b5656b645f510f9c971e7c2dc9ef) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - introduce @medusajs/link-modules
|
||||
|
||||
- [#4925](https://github.com/medusajs/medusa/pull/4925) [`a4906d0ac`](https://github.com/medusajs/medusa/commit/a4906d0ac0af36b1382d3befe64281b404387bd7) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Use MedusaApp on core and initial JoinerConfig for internal services
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4930](https://github.com/medusajs/medusa/pull/4930) [`c3dba0694`](https://github.com/medusajs/medusa/commit/c3dba069488952945150117a30b1306a2e0bb3ce) Thanks [@riqwan](https://github.com/riqwan)! - fix(modules-sdk, orchestration): add missing dependencies
|
||||
|
||||
- [#4860](https://github.com/medusajs/medusa/pull/4860) [`460161a69`](https://github.com/medusajs/medusa/commit/460161a69f22cf6d561952e92e7d9b56912113e6) Thanks [@riqwan](https://github.com/riqwan)! - feat(pricing, types, utils, medusa-sdk): Pricing Module Setup + Currency service
|
||||
|
||||
- [#4695](https://github.com/medusajs/medusa/pull/4695) [`4d16acf5f`](https://github.com/medusajs/medusa/commit/4d16acf5f096b5656b645f510f9c971e7c2dc9ef) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Add extra fields to link modules
|
||||
|
||||
- Updated dependencies [[`c3dba0694`](https://github.com/medusajs/medusa/commit/c3dba069488952945150117a30b1306a2e0bb3ce), [`460161a69`](https://github.com/medusajs/medusa/commit/460161a69f22cf6d561952e92e7d9b56912113e6), [`fcb6b4f51`](https://github.com/medusajs/medusa/commit/fcb6b4f510dba2757570625acb5da9476b7544fd), [`66bd9a835`](https://github.com/medusajs/medusa/commit/66bd9a835c61b139af7051e5faf6c9de3c7134bb), [`4d16acf5f`](https://github.com/medusajs/medusa/commit/4d16acf5f096b5656b645f510f9c971e7c2dc9ef), [`4d16acf5f`](https://github.com/medusajs/medusa/commit/4d16acf5f096b5656b645f510f9c971e7c2dc9ef), [`87bade096`](https://github.com/medusajs/medusa/commit/87bade096e3d536f29ddc57dbc4c04e5d7a46e4b), [`4d16acf5f`](https://github.com/medusajs/medusa/commit/4d16acf5f096b5656b645f510f9c971e7c2dc9ef), [`a4906d0ac`](https://github.com/medusajs/medusa/commit/a4906d0ac0af36b1382d3befe64281b404387bd7)]:
|
||||
- @medusajs/orchestration@0.3.0
|
||||
- @medusajs/types@1.11.0
|
||||
- @medusajs/utils@1.10.0
|
||||
|
||||
## 1.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4626](https://github.com/medusajs/medusa/pull/4626) [`3f3a84262`](https://github.com/medusajs/medusa/commit/3f3a84262ce9cbd911923278a54e301fbe9a4634) Thanks [@adrien2p](https://github.com/adrien2p)! - [WIP] feat(types, product, utils, medusa): Include shared connection for modules
|
||||
|
||||
- Updated dependencies [[`ac866ebb5`](https://github.com/medusajs/medusa/commit/ac866ebb5197ee694dda91824b501109012a3dd1), [`3f3a84262`](https://github.com/medusajs/medusa/commit/3f3a84262ce9cbd911923278a54e301fbe9a4634), [`30ce35b16`](https://github.com/medusajs/medusa/commit/30ce35b163afa25f4e1d8d1bd392f401a3b413df), [`281b0746c`](https://github.com/medusajs/medusa/commit/281b0746cfbe80b83c6a67d1ea120b47a0ea7121), [`c0ca00290`](https://github.com/medusajs/medusa/commit/c0ca00290106fbdc8e15077bc8d1c3eafbef59f2)]:
|
||||
- @medusajs/orchestration@0.2.0
|
||||
- @medusajs/types@1.10.2
|
||||
- @medusajs/utils@1.9.6
|
||||
|
||||
## 1.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4674](https://github.com/medusajs/medusa/pull/4674) [`4b80ba8a3`](https://github.com/medusajs/medusa/commit/4b80ba8a356806da3c92634e40e8946da25e35ee) Thanks [@adrien2p](https://github.com/adrien2p)! - fix(product, integration-tests): Fix integration tests
|
||||
|
||||
- [#4654](https://github.com/medusajs/medusa/pull/4654) [`8af55aed8`](https://github.com/medusajs/medusa/commit/8af55aed87da7252c7c261175bc98331466a0da8) Thanks [@riqwan](https://github.com/riqwan)! - feat(product,types): added event bus events for products
|
||||
|
||||
- Updated dependencies [[`8af55aed8`](https://github.com/medusajs/medusa/commit/8af55aed87da7252c7c261175bc98331466a0da8), [`5c60aad17`](https://github.com/medusajs/medusa/commit/5c60aad177a99574ffff5ebdc02ce9dc86ef9af9), [`4073b7313`](https://github.com/medusajs/medusa/commit/4073b73130c874dc7d2240726224a01b7b19b1a1)]:
|
||||
- @medusajs/types@1.10.1
|
||||
- @medusajs/utils@1.9.5
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#4596](https://github.com/medusajs/medusa/pull/4596) [`f174bb6fa`](https://github.com/medusajs/medusa/commit/f174bb6fa1b105b39065478a67b6be0b968f707a) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Fix getting loaded modules
|
||||
|
||||
- [#4553](https://github.com/medusajs/medusa/pull/4553) [`f12299deb`](https://github.com/medusajs/medusa/commit/f12299deb10baadab1505cd4ac353dd5d1c8fa7c) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Medusa workflows package
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4611](https://github.com/medusajs/medusa/pull/4611) [`379c83933`](https://github.com/medusajs/medusa/commit/379c83933ed12a4ec712e7f3c9b0252e4a4601dd) Thanks [@adrien2p](https://github.com/adrien2p)! - fix(module-sdk): Shared modules loading should not share the entire core container but only the resources that are meant to be shared
|
||||
|
||||
- Updated dependencies [[`585ebf245`](https://github.com/medusajs/medusa/commit/585ebf2454719ed36957ba798a189a45a5274c5a), [`131477faf`](https://github.com/medusajs/medusa/commit/131477faf0409c49d4aacf26ea591e33b2fa22fd), [`caea44ebf`](https://github.com/medusajs/medusa/commit/caea44ebfdf7393ace931ce2a9884105dadc4f8d), [`f12299deb`](https://github.com/medusajs/medusa/commit/f12299deb10baadab1505cd4ac353dd5d1c8fa7c)]:
|
||||
- @medusajs/types@1.9.0
|
||||
- @medusajs/utils@1.9.3
|
||||
- @medusajs/orchestration@0.1.0
|
||||
|
||||
## 1.8.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4161](https://github.com/medusajs/medusa/pull/4161) [`14c0f62f8`](https://github.com/medusajs/medusa/commit/14c0f62f84704a4c87beff3daaff60a52f5c88b8) Thanks [@adrien2p](https://github.com/adrien2p)! - feat(product): Experimental product module
|
||||
|
||||
- Updated dependencies [[`f98ba5bde`](https://github.com/medusajs/medusa/commit/f98ba5bde83ba785eead31b0c9eb9f135d664178), [`d76ba0cd2`](https://github.com/medusajs/medusa/commit/d76ba0cd29694c2e31f9f89992a9fbc14659c1ae), [`14c0f62f8`](https://github.com/medusajs/medusa/commit/14c0f62f84704a4c87beff3daaff60a52f5c88b8)]:
|
||||
- @medusajs/utils@1.9.1
|
||||
- @medusajs/types@1.8.8
|
||||
|
||||
## 1.8.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4065](https://github.com/medusajs/medusa/pull/4065) [`e73c3e51c`](https://github.com/medusajs/medusa/commit/e73c3e51c9cd192eeae7a57b24b07bd466214145) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - chore: module as singleton instances
|
||||
|
||||
- Updated dependencies [[`3a38c84f8`](https://github.com/medusajs/medusa/commit/3a38c84f88b05f74ee0a172af3e3f78b2ec8c2d2), [`a91987fab`](https://github.com/medusajs/medusa/commit/a91987fab33745f9864eab21bd1c27e8e3e24571), [`db4199530`](https://github.com/medusajs/medusa/commit/db419953075e0907b8c4d27ab5188e9bd3e3d72b), [`c0e527d6e`](https://github.com/medusajs/medusa/commit/c0e527d6e0a67d0c53577a0b9c3d16ee8dc5740f)]:
|
||||
- @medusajs/types@1.8.7
|
||||
- @medusajs/utils@1.9.0
|
||||
|
||||
## 1.8.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a86f0e815`](https://github.com/medusajs/medusa/commit/a86f0e815a9e75d7d562fbe516c5bb7e0ab1f6ee), [`cdbac2c84`](https://github.com/medusajs/medusa/commit/cdbac2c8403a3c15c0e11993f6b7dab268fa5c08), [`6511959e2`](https://github.com/medusajs/medusa/commit/6511959e23177f3b4831915db0e8e788bc9047fa)]:
|
||||
- @medusajs/types@1.8.6
|
||||
- @medusajs/utils@1.8.5
|
||||
|
||||
## 1.8.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7fd22ecb4`](https://github.com/medusajs/medusa/commit/7fd22ecb4d5190e92c6750a9fbf2d8534bb9f4ab), [`1ea57c3a6`](https://github.com/medusajs/medusa/commit/1ea57c3a69a5377a8dd0821df819743ded4a222b)]:
|
||||
- @medusajs/types@1.8.5
|
||||
- @medusajs/utils@1.8.4
|
||||
|
||||
## 1.8.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0e488e71b`](https://github.com/medusajs/medusa/commit/0e488e71b186f7d08b18c4c6ba409ef3cadb8152), [`d539c6fee`](https://github.com/medusajs/medusa/commit/d539c6feeba8ee431f9a655b6cd4e9102cba2b25)]:
|
||||
- @medusajs/types@1.8.4
|
||||
- @medusajs/utils@1.8.3
|
||||
|
||||
## 1.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`af710f1b4`](https://github.com/medusajs/medusa/commit/af710f1b48a4545a5064029a557013af34c4c100), [`491566df6`](https://github.com/medusajs/medusa/commit/491566df6b7ced35f655f810961422945e10ecd0)]:
|
||||
- @medusajs/types@1.8.3
|
||||
- @medusajs/utils@1.8.2
|
||||
|
||||
## 1.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4f58ddee0`](https://github.com/medusajs/medusa/commit/4f58ddee03509a4c46af160e5824cba80d4c950a)]:
|
||||
- @medusajs/types@1.8.2
|
||||
|
||||
## 1.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`654a54622`](https://github.com/medusajs/medusa/commit/654a54622303139e7180538bd686630ad9a46cfd), [`abdb74d99`](https://github.com/medusajs/medusa/commit/abdb74d997f49f994bff49787a396179982843b0)]:
|
||||
- @medusajs/types@1.8.1
|
||||
- @medusajs/utils@1.8.1
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#3329](https://github.com/medusajs/medusa/pull/3329) [`77d46220c`](https://github.com/medusajs/medusa/commit/77d46220c23bfe19e575cbc445874eb6c22f3c73) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Inventory and Stock location modules supporting isolated connection
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3685](https://github.com/medusajs/medusa/pull/3685) [`8ddb3952c`](https://github.com/medusajs/medusa/commit/8ddb3952c045e6c05c8d0f6922f0d4ba30cf3bd4) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Fix RC package versions
|
||||
|
||||
- [#3620](https://github.com/medusajs/medusa/pull/3620) [`55e94d0b4`](https://github.com/medusajs/medusa/commit/55e94d0b45776776639d3970d4264b8f5c5385dd) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Checks if dependency is registered before resolving it
|
||||
|
||||
- [#3649](https://github.com/medusajs/medusa/pull/3649) [`bd12a9508`](https://github.com/medusajs/medusa/commit/bd12a95083b69a70b83ad38578c5a68738c41b2b) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Export initialize method for all modules
|
||||
|
||||
- [#3605](https://github.com/medusajs/medusa/pull/3605) [`bca1f80dd`](https://github.com/medusajs/medusa/commit/bca1f80dd501d878455e1ad4f5091cf20ef900ea) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Improved error message if default module is not installed
|
||||
|
||||
- [#3685](https://github.com/medusajs/medusa/pull/3685) [`271844aed`](https://github.com/medusajs/medusa/commit/271844aedbe45c369e188b5d06458dbd6984cd39) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Add missing changeset for @medusajs/modules-sdk
|
||||
|
||||
- [#3531](https://github.com/medusajs/medusa/pull/3531) [`4e9d257d3`](https://github.com/medusajs/medusa/commit/4e9d257d3bf76703ef5be8ca054cc9f0f7339def) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Remove dependency on @medusajs/medusa from Inventory and Stock-Location Modules
|
||||
|
||||
- Updated dependencies [[`8ddb3952c`](https://github.com/medusajs/medusa/commit/8ddb3952c045e6c05c8d0f6922f0d4ba30cf3bd4), [`a0c919a8d`](https://github.com/medusajs/medusa/commit/a0c919a8d01ca5edf62336de48e9a112e3822f38), [`74bc4b16a`](https://github.com/medusajs/medusa/commit/74bc4b16a07f78668003ca930bf2a0d928897ceb), [`4e9d257d3`](https://github.com/medusajs/medusa/commit/4e9d257d3bf76703ef5be8ca054cc9f0f7339def)]:
|
||||
- @medusajs/types@1.8.0
|
||||
- @medusajs/utils@1.8.0
|
||||
|
||||
## 0.1.0-rc.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3649](https://github.com/medusajs/medusa/pull/3649) [`bd12a9508`](https://github.com/medusajs/medusa/commit/bd12a95083b69a70b83ad38578c5a68738c41b2b) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Export initialize method for all modules
|
||||
|
||||
- Updated dependencies [[`a0c919a8d`](https://github.com/medusajs/medusa/commit/a0c919a8d01ca5edf62336de48e9a112e3822f38)]:
|
||||
- @medusajs/utils@0.0.2-rc.2
|
||||
|
||||
## 0.1.0-rc.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3620](https://github.com/medusajs/medusa/pull/3620) [`55e94d0b4`](https://github.com/medusajs/medusa/commit/55e94d0b45776776639d3970d4264b8f5c5385dd) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Checks if dependency is registered before resolving it
|
||||
|
||||
## 0.1.0-rc.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- chore: Fix RC package versions
|
||||
|
||||
- Updated dependencies []:
|
||||
- @medusajs/types@0.0.2-rc.1
|
||||
- @medusajs/utils@0.0.2-rc.1
|
||||
|
||||
## 0.1.0-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3605](https://github.com/medusajs/medusa/pull/3605) [`bca1f80dd`](https://github.com/medusajs/medusa/commit/bca1f80dd501d878455e1ad4f5091cf20ef900ea) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Improved error message if default module is not installed
|
||||
|
||||
## 0.1.0-rc.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#3329](https://github.com/medusajs/medusa/pull/3329) [`77d46220c`](https://github.com/medusajs/medusa/commit/77d46220c23bfe19e575cbc445874eb6c22f3c73) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Inventory and Stock location modules supporting isolated connection
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3192](https://github.com/medusajs/medusa/pull/3192) [`271844aed`](https://github.com/medusajs/medusa/commit/271844aedbe45c369e188b5d06458dbd6984cd39) Thanks [@pKorsholm](https://github.com/pKorsholm)! - chore: Add missing changeset for @medusajs/modules-sdk
|
||||
|
||||
- [#3531](https://github.com/medusajs/medusa/pull/3531) [`4e9d257d3`](https://github.com/medusajs/medusa/commit/4e9d257d3bf76703ef5be8ca054cc9f0f7339def) Thanks [@carlos-r-l-rodrigues](https://github.com/carlos-r-l-rodrigues)! - Remove dependency on @medusajs/medusa from Inventory and Stock-Location Modules
|
||||
|
||||
- Updated dependencies [[`74bc4b16a`](https://github.com/medusajs/medusa/commit/74bc4b16a07f78668003ca930bf2a0d928897ceb), [`4e9d257d3`](https://github.com/medusajs/medusa/commit/4e9d257d3bf76703ef5be8ca054cc9f0f7339def)]:
|
||||
- @medusajs/types@0.0.2-rc.0
|
||||
- @medusajs/utils@0.0.2-rc.0
|
||||
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
transform: {
|
||||
"^.+\\.[jt]s?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
tsconfig: "tsconfig.json",
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
testEnvironment: `node`,
|
||||
moduleFileExtensions: [`js`, `ts`],
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@medusajs/modules-sdk",
|
||||
"version": "1.12.11",
|
||||
"description": "SDK for medusa modules",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/modules-sdk"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^29.6.3",
|
||||
"rimraf": "^5.0.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@graphql-tools/merge": "^9.0.0",
|
||||
"@graphql-tools/schema": "^10.0.0",
|
||||
"@medusajs/orchestration": "^0.5.7",
|
||||
"@medusajs/types": "^1.11.16",
|
||||
"@medusajs/utils": "^1.11.9",
|
||||
"awilix": "^8.0.0",
|
||||
"graphql": "^16.6.0",
|
||||
"knex": "2.4.2",
|
||||
"pg": "^8.11.2",
|
||||
"resolve-cwd": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "cross-env NODE_ENV=production tsc --build",
|
||||
"build": "rimraf dist && tsc --build",
|
||||
"test": "jest --runInBand --bail --forceExit",
|
||||
"watch": "tsc --build --watch"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export const InventoryModule = {
|
||||
__definition: {
|
||||
key: "inventoryService",
|
||||
registrationName: "inventoryService",
|
||||
defaultPackage: false,
|
||||
label: "InventoryService",
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: [],
|
||||
defaultModuleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
},
|
||||
__joinerConfig: {
|
||||
serviceName: "inventoryService",
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: {
|
||||
inventory_item_id: "InventoryItem",
|
||||
inventory_level_id: "InventoryLevel",
|
||||
reservation_item_id: "ReservationItem",
|
||||
},
|
||||
},
|
||||
|
||||
softDelete: jest.fn(() => {}),
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
export const InventoryStockLocationLink = {
|
||||
__definition: {
|
||||
key: "inventoryStockLocationLink",
|
||||
registrationName: "inventoryStockLocationLink",
|
||||
defaultPackage: "",
|
||||
label: "inventoryStockLocationLink",
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
defaultModuleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
},
|
||||
__joinerConfig: {
|
||||
serviceName: "inventoryStockLocationLink",
|
||||
isLink: true,
|
||||
alias: [
|
||||
{
|
||||
name: "inventory_level_stock_location",
|
||||
},
|
||||
{
|
||||
name: "inventory_level_stock_locations",
|
||||
},
|
||||
],
|
||||
primaryKeys: ["inventory_level_id", "stock_location_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: "inventoryService",
|
||||
primaryKey: "id",
|
||||
foreignKey: "inventory_level_id",
|
||||
alias: "inventory_level",
|
||||
args: {},
|
||||
},
|
||||
{
|
||||
serviceName: "stockLocationService",
|
||||
primaryKey: "id",
|
||||
foreignKey: "stock_location_id",
|
||||
alias: "stock_location",
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: "inventoryService",
|
||||
relationship: {
|
||||
serviceName: "inventoryStockLocationLink",
|
||||
primaryKey: "inventory_level_id",
|
||||
foreignKey: "id",
|
||||
alias: "inventory_location_items",
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: "stockLocationService",
|
||||
relationship: {
|
||||
serviceName: "inventoryStockLocationLink",
|
||||
primaryKey: "stock_location_id",
|
||||
foreignKey: "id",
|
||||
alias: "inventory_location_items",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
create: jest.fn(
|
||||
async (
|
||||
primaryKeyOrBulkData: string | string[] | [string | string[], string][],
|
||||
foreignKeyData?: string
|
||||
) => {}
|
||||
),
|
||||
softDelete: jest.fn(() => {}),
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
export const ProductInventoryLinkModule = {
|
||||
__definition: {
|
||||
key: "productVariantInventoryInventoryItemLink",
|
||||
registrationName: "productVariantInventoryInventoryItemLink",
|
||||
defaultPackage: "",
|
||||
label: "productVariantInventoryInventoryItemLink",
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
defaultModuleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
},
|
||||
__joinerConfig: {
|
||||
serviceName: "productVariantInventoryInventoryItemLink",
|
||||
isLink: true,
|
||||
databaseConfig: {
|
||||
tableName: "product_variant_inventory_item",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: "product_variant_inventory_item",
|
||||
},
|
||||
{
|
||||
name: "product_variant_inventory_items",
|
||||
},
|
||||
],
|
||||
primaryKeys: ["variant_id", "inventory_item_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: "productService",
|
||||
primaryKey: "id",
|
||||
foreignKey: "variant_id",
|
||||
alias: "variant",
|
||||
args: {},
|
||||
deleteCascade: true,
|
||||
},
|
||||
{
|
||||
serviceName: "inventoryService",
|
||||
primaryKey: "id",
|
||||
foreignKey: "inventory_item_id",
|
||||
alias: "inventory",
|
||||
deleteCascade: true,
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: "productService",
|
||||
relationship: {
|
||||
serviceName: "productVariantInventoryInventoryItemLink",
|
||||
primaryKey: "variant_id",
|
||||
foreignKey: "id",
|
||||
alias: "inventory_items",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: "inventoryService",
|
||||
relationship: {
|
||||
serviceName: "productVariantInventoryInventoryItemLink",
|
||||
primaryKey: "inventory_item_id",
|
||||
foreignKey: "id",
|
||||
alias: "variant_link",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
create: jest.fn(
|
||||
async (
|
||||
primaryKeyOrBulkData: string | string[] | [string | string[], string][],
|
||||
foreignKeyData?: string
|
||||
) => {}
|
||||
),
|
||||
softDelete: jest.fn(() => {}),
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export const ProductModule = {
|
||||
__definition: {
|
||||
key: "productService",
|
||||
registrationName: "productModuleService",
|
||||
defaultPackage: false,
|
||||
label: "ProductModuleService",
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["eventBusModuleService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
},
|
||||
__joinerConfig: {
|
||||
serviceName: "productService",
|
||||
primaryKeys: ["id", "handle"],
|
||||
linkableKeys: { product_id: "Product", variant_id: "ProductVariant" },
|
||||
alias: [],
|
||||
},
|
||||
|
||||
softDelete: jest.fn(() => {}),
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export const StockLocationModule = {
|
||||
__definition: {
|
||||
key: "stockLocationService",
|
||||
registrationName: "stockLocationService",
|
||||
defaultPackage: false,
|
||||
label: "StockLocationService",
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
},
|
||||
__joinerConfig: {
|
||||
serviceName: "stockLocationService",
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: { stock_location_id: "StockLocation" },
|
||||
alias: [],
|
||||
},
|
||||
|
||||
softDelete: jest.fn(() => {}),
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { cleanGraphQLSchema } from "../utils/clean-graphql-schema"
|
||||
|
||||
describe("Clean Graphql Schema", function () {
|
||||
it("Should keep the schema intact if all entities are available", function () {
|
||||
const schemaStr = `
|
||||
type Product {
|
||||
id: ID!
|
||||
title: String!
|
||||
variants: [Variant]!
|
||||
}
|
||||
type Variant {
|
||||
id: ID!
|
||||
title: String!
|
||||
product_id: ID!
|
||||
product: Product!
|
||||
}
|
||||
`
|
||||
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||
|
||||
expect(schema.replace(/\s/g, "")).toEqual(schemaStr.replace(/\s/g, ""))
|
||||
expect(notFound).toEqual({})
|
||||
})
|
||||
|
||||
it("Should remove fields where the relation doesn't exist", function () {
|
||||
const schemaStr = `
|
||||
type Product {
|
||||
id: ID!
|
||||
title: String!
|
||||
variants: [Variant!]!
|
||||
profile: ShippingProfile!
|
||||
}
|
||||
`
|
||||
const expectedStr = `
|
||||
type Product {
|
||||
id: ID!
|
||||
title: String!
|
||||
}
|
||||
`
|
||||
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||
|
||||
expect(schema.replace(/\s/g, "")).toEqual(expectedStr.replace(/\s/g, ""))
|
||||
expect(notFound).toEqual({
|
||||
Product: { variants: "Variant", profile: "ShippingProfile" },
|
||||
})
|
||||
})
|
||||
|
||||
it("Should remove fields where the relation doesn't exist and flag extended entity where the main entity doesn't exist", function () {
|
||||
const schemaStr = `
|
||||
scalar JSON
|
||||
type Product {
|
||||
id: ID!
|
||||
title: String!
|
||||
variants: [Variant!]!
|
||||
profile: ShippingProfile!
|
||||
}
|
||||
|
||||
extend type Variant {
|
||||
metadata: JSON
|
||||
}
|
||||
`
|
||||
const expectedStr = `
|
||||
scalar JSON
|
||||
type Product {
|
||||
id: ID!
|
||||
title: String!
|
||||
}
|
||||
`
|
||||
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||
|
||||
expect(schema.replace(/\s/g, "")).toEqual(expectedStr.replace(/\s/g, ""))
|
||||
expect(notFound).toEqual({
|
||||
Product: { variants: "Variant", profile: "ShippingProfile" },
|
||||
Variant: { __extended: "" },
|
||||
})
|
||||
})
|
||||
|
||||
it("Should remove fields from extend where the relation doesn't exist", function () {
|
||||
const schemaStr = `
|
||||
scalar JSON
|
||||
type Product {
|
||||
id: ID!
|
||||
title: String!
|
||||
variants: [Variant!]!
|
||||
profile: ShippingProfile!
|
||||
}
|
||||
|
||||
extend type Product {
|
||||
variants: [Variant!]!
|
||||
profile: ShippingProfile!
|
||||
metadata: JSON
|
||||
}
|
||||
`
|
||||
const expectedStr = `
|
||||
scalar JSON
|
||||
type Product {
|
||||
id: ID!
|
||||
title: String!
|
||||
}
|
||||
|
||||
extend type Product {
|
||||
metadata: JSON
|
||||
}
|
||||
`
|
||||
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||
|
||||
expect(schema.replace(/\s/g, "")).toEqual(expectedStr.replace(/\s/g, ""))
|
||||
expect(notFound).toEqual({
|
||||
Product: { variants: "Variant", profile: "ShippingProfile" },
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,202 @@
|
||||
import { InventoryModule } from "../__mocks__/inventory-module"
|
||||
import { InventoryStockLocationLink } from "../__mocks__/inventory-stock-location-link"
|
||||
import { ProductInventoryLinkModule } from "../__mocks__/product-inventory-link"
|
||||
import { ProductModule } from "../__mocks__/product-module"
|
||||
import { StockLocationModule } from "../__mocks__/stock-location-module"
|
||||
|
||||
import { RemoteLink } from "../remote-link"
|
||||
|
||||
const allModules = [
|
||||
// modules
|
||||
ProductModule,
|
||||
InventoryModule,
|
||||
StockLocationModule,
|
||||
// links
|
||||
ProductInventoryLinkModule,
|
||||
InventoryStockLocationLink,
|
||||
]
|
||||
describe("Remote Link", function () {
|
||||
it("Should get all loaded modules and compose their relationships", async function () {
|
||||
const remoteLink = new RemoteLink(allModules as any)
|
||||
|
||||
const relations = remoteLink.getRelationships()
|
||||
|
||||
const prodInventoryLink = relations.get(
|
||||
"productVariantInventoryInventoryItemLink"
|
||||
)
|
||||
const prodModule = relations.get("productService")
|
||||
const inventoryModule = relations.get("inventoryService")
|
||||
|
||||
expect(prodInventoryLink?.get("variant_id")).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
serviceName: "productService",
|
||||
primaryKey: "id",
|
||||
foreignKey: "variant_id",
|
||||
alias: "variant",
|
||||
deleteCascade: true,
|
||||
isPrimary: false,
|
||||
isForeign: true,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
expect(prodInventoryLink?.get("inventory_item_id")).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
serviceName: "inventoryService",
|
||||
primaryKey: "id",
|
||||
foreignKey: "inventory_item_id",
|
||||
alias: "inventory",
|
||||
deleteCascade: true,
|
||||
isPrimary: false,
|
||||
isForeign: true,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
expect(prodModule?.get("variant_id")).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
serviceName: "productVariantInventoryInventoryItemLink",
|
||||
primaryKey: "variant_id",
|
||||
foreignKey: "id",
|
||||
alias: "inventory_items",
|
||||
isList: true,
|
||||
isPrimary: true,
|
||||
isForeign: false,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
expect(inventoryModule?.get("inventory_item_id")).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
serviceName: "productVariantInventoryInventoryItemLink",
|
||||
primaryKey: "inventory_item_id",
|
||||
foreignKey: "id",
|
||||
alias: "variant_link",
|
||||
isPrimary: true,
|
||||
isForeign: false,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("Should call the correct link module to create relation between 2 keys", async function () {
|
||||
const remoteLink = new RemoteLink(allModules as any)
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
productService: {
|
||||
variant_id: "var_123",
|
||||
},
|
||||
inventoryService: {
|
||||
inventory_item_id: "inv_123",
|
||||
},
|
||||
},
|
||||
{
|
||||
productService: {
|
||||
variant_id: "var_abc",
|
||||
},
|
||||
inventoryService: {
|
||||
inventory_item_id: "inv_abc",
|
||||
},
|
||||
},
|
||||
{
|
||||
inventoryService: {
|
||||
inventory_level_id: "ilev_123",
|
||||
},
|
||||
stockLocationService: {
|
||||
stock_location_id: "loc_123",
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(ProductInventoryLinkModule.create).toBeCalledWith([
|
||||
["var_123", "inv_123"],
|
||||
["var_abc", "inv_abc"],
|
||||
])
|
||||
expect(InventoryStockLocationLink.create).toBeCalledWith([
|
||||
["ilev_123", "loc_123"],
|
||||
])
|
||||
})
|
||||
|
||||
it("Should call delete in cascade all the modules involved in the link", async function () {
|
||||
const remoteLink = new RemoteLink(allModules as any)
|
||||
|
||||
ProductInventoryLinkModule.softDelete.mockImplementation(() => {
|
||||
return {
|
||||
variant_id: ["var_123"],
|
||||
inventory_item_id: ["inv_123"],
|
||||
}
|
||||
})
|
||||
|
||||
ProductModule.softDelete.mockImplementation(() => {
|
||||
return {
|
||||
product_id: ["prod_123", "prod_abc"],
|
||||
variant_id: ["var_123", "var_abc"],
|
||||
}
|
||||
})
|
||||
|
||||
InventoryModule.softDelete.mockImplementation(() => {
|
||||
return {
|
||||
inventory_item_id: ["inv_123"],
|
||||
inventory_level_id: ["ilev_123"],
|
||||
}
|
||||
})
|
||||
|
||||
InventoryStockLocationLink.softDelete.mockImplementation(() => {
|
||||
return {
|
||||
inventory_level_id: ["ilev_123"],
|
||||
stock_location_id: ["loc_123"],
|
||||
}
|
||||
})
|
||||
|
||||
await remoteLink.delete({
|
||||
productService: {
|
||||
variant_id: "var_123",
|
||||
},
|
||||
})
|
||||
|
||||
expect(ProductInventoryLinkModule.softDelete).toBeCalledTimes(2)
|
||||
expect(ProductModule.softDelete).toBeCalledTimes(1)
|
||||
expect(InventoryModule.softDelete).toBeCalledTimes(1)
|
||||
expect(InventoryStockLocationLink.softDelete).toBeCalledTimes(1)
|
||||
|
||||
expect(ProductInventoryLinkModule.softDelete).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
{ variant_id: ["var_123"] },
|
||||
{ returnLinkableKeys: ["variant_id", "inventory_item_id"] }
|
||||
)
|
||||
|
||||
expect(ProductInventoryLinkModule.softDelete).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
{ variant_id: ["var_abc"] },
|
||||
{ returnLinkableKeys: ["variant_id", "inventory_item_id"] }
|
||||
)
|
||||
|
||||
expect(ProductModule.softDelete).toBeCalledWith(
|
||||
{ id: ["var_123"] },
|
||||
{ returnLinkableKeys: ["product_id", "variant_id"] }
|
||||
)
|
||||
|
||||
expect(InventoryModule.softDelete).toBeCalledWith(
|
||||
{ id: ["inv_123"] },
|
||||
{
|
||||
returnLinkableKeys: [
|
||||
"inventory_item_id",
|
||||
"inventory_level_id",
|
||||
"reservation_item_id",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(InventoryStockLocationLink.softDelete).toBeCalledWith(
|
||||
{
|
||||
inventory_level_id: ["ilev_123"],
|
||||
},
|
||||
{ returnLinkableKeys: ["inventory_level_id", "stock_location_id"] }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
import { RemoteQuery } from "../remote-query"
|
||||
|
||||
describe("Remote query", () => {
|
||||
it("should properly handle fields and relations transformation", () => {
|
||||
let expand = {
|
||||
fields: ["name", "age"],
|
||||
expands: {
|
||||
friend: {
|
||||
fields: ["name"],
|
||||
expands: {
|
||||
ball: {
|
||||
fields: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let result = RemoteQuery.getAllFieldsAndRelations(expand)
|
||||
|
||||
expect(result).toEqual({
|
||||
select: ["name", "age", "friend.name"],
|
||||
relations: ["friend", "friend.ball"],
|
||||
args: {
|
||||
"": undefined,
|
||||
friend: undefined,
|
||||
"friend.ball": undefined,
|
||||
},
|
||||
})
|
||||
|
||||
expand = {
|
||||
fields: [],
|
||||
expands: {
|
||||
friend: {
|
||||
fields: ["name"],
|
||||
expands: {
|
||||
ball: {
|
||||
fields: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result = RemoteQuery.getAllFieldsAndRelations(expand)
|
||||
|
||||
expect(result).toEqual({
|
||||
select: ["friend.name"],
|
||||
relations: ["friend", "friend.ball"],
|
||||
args: {
|
||||
"": undefined,
|
||||
friend: undefined,
|
||||
"friend.ball": undefined,
|
||||
},
|
||||
})
|
||||
|
||||
expand = {
|
||||
fields: [],
|
||||
expands: {
|
||||
friend: {
|
||||
fields: ["*"],
|
||||
expands: {
|
||||
ball: {
|
||||
fields: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result = RemoteQuery.getAllFieldsAndRelations(expand)
|
||||
|
||||
expect(result).toEqual({
|
||||
select: [],
|
||||
relations: ["friend", "friend.ball"],
|
||||
args: {
|
||||
"": undefined,
|
||||
friend: undefined,
|
||||
"friend.ball": undefined,
|
||||
},
|
||||
})
|
||||
|
||||
expand = {
|
||||
fields: [],
|
||||
expands: {
|
||||
friend: {
|
||||
fields: [],
|
||||
expands: {
|
||||
ball: {
|
||||
fields: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result = RemoteQuery.getAllFieldsAndRelations(expand)
|
||||
|
||||
expect(result).toEqual({
|
||||
select: [],
|
||||
relations: ["friend", "friend.ball"],
|
||||
args: {
|
||||
"": undefined,
|
||||
friend: undefined,
|
||||
"friend.ball": undefined,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,110 @@
|
||||
import { mergeTypeDefs } from "@graphql-tools/merge"
|
||||
import { makeExecutableSchema } from "@graphql-tools/schema"
|
||||
import { graphqlSchemaToFields } from "../../utils"
|
||||
|
||||
const userModule = `
|
||||
type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
blabla: WHATEVER
|
||||
}
|
||||
|
||||
type Post {
|
||||
author: User!
|
||||
}
|
||||
`
|
||||
|
||||
const postModule = `
|
||||
type Post {
|
||||
id: ID!
|
||||
title: String!
|
||||
date: String
|
||||
}
|
||||
|
||||
type User {
|
||||
posts: [Post!]!
|
||||
}
|
||||
|
||||
type WHATEVER {
|
||||
random_field: String
|
||||
post: Post
|
||||
}
|
||||
`
|
||||
|
||||
const mergedSchema = mergeTypeDefs([userModule, postModule])
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs: mergedSchema,
|
||||
})
|
||||
|
||||
const types = schema.getTypeMap()
|
||||
|
||||
describe("graphqlSchemaToFields", function () {
|
||||
it("Should get all fields of a given entity", async function () {
|
||||
const fields = graphqlSchemaToFields(types, "User")
|
||||
expect(fields).toEqual(expect.arrayContaining(["id", "name"]))
|
||||
})
|
||||
|
||||
it("Should get all fields of a given entity and a relation", async function () {
|
||||
const fields = graphqlSchemaToFields(types, "User", ["posts"])
|
||||
expect(fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
"id",
|
||||
"name",
|
||||
"posts.id",
|
||||
"posts.title",
|
||||
"posts.date",
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("Should get all fields of a given entity and many relations", async function () {
|
||||
const fields = graphqlSchemaToFields(types, "User", [
|
||||
"posts",
|
||||
"blabla",
|
||||
"blabla.post",
|
||||
])
|
||||
expect(fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
"id",
|
||||
"name",
|
||||
"posts.id",
|
||||
"posts.title",
|
||||
"posts.date",
|
||||
"blabla.random_field",
|
||||
"blabla.post.id",
|
||||
"blabla.post.title",
|
||||
"blabla.post.date",
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("Should get all fields of a given entity and many relations limited to the relations given", async function () {
|
||||
const fields = graphqlSchemaToFields(types, "User", ["posts", "blabla"])
|
||||
expect(fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
"id",
|
||||
"name",
|
||||
"posts.id",
|
||||
"posts.title",
|
||||
"posts.date",
|
||||
"blabla.random_field",
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("Should get all fields of a given entity and many relations limited to the relations given if they exists", async function () {
|
||||
const fields = graphqlSchemaToFields(types, "User", [
|
||||
"posts",
|
||||
"doNotExists",
|
||||
])
|
||||
expect(fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
"id",
|
||||
"name",
|
||||
"posts.id",
|
||||
"posts.title",
|
||||
"posts.date",
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,386 @@
|
||||
import {
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
ModuleDefinition,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { upperCaseFirst } from "@medusajs/utils"
|
||||
|
||||
export enum LinkModuleUtils {
|
||||
REMOTE_QUERY = "remoteQuery",
|
||||
REMOTE_LINK = "remoteLink",
|
||||
}
|
||||
|
||||
// TODO: Remove this enum and use the one from @medusajs/utils
|
||||
export enum Modules {
|
||||
AUTH = "auth",
|
||||
CACHE = "cacheService",
|
||||
CART = "cart",
|
||||
CUSTOMER = "customer",
|
||||
EVENT_BUS = "eventBus",
|
||||
INVENTORY = "inventoryService",
|
||||
LINK = "linkModules",
|
||||
PAYMENT = "payment",
|
||||
PRICING = "pricingService",
|
||||
PRODUCT = "productService",
|
||||
PROMOTION = "promotion",
|
||||
SALES_CHANNEL = "salesChannel",
|
||||
TAX = "tax",
|
||||
FULFILLMENT = "fulfillment",
|
||||
STOCK_LOCATION = "stockLocationService",
|
||||
USER = "user",
|
||||
WORKFLOW_ENGINE = "workflows",
|
||||
REGION = "region",
|
||||
ORDER = "order",
|
||||
API_KEY = "apiKey",
|
||||
STORE = "store",
|
||||
CURRENCY = "currency",
|
||||
FILE = "file",
|
||||
}
|
||||
|
||||
export enum ModuleRegistrationName {
|
||||
AUTH = "authModuleService",
|
||||
CACHE = "cacheService",
|
||||
CART = "cartModuleService",
|
||||
CUSTOMER = "customerModuleService",
|
||||
EVENT_BUS = "eventBusModuleService",
|
||||
INVENTORY = "inventoryService",
|
||||
PAYMENT = "paymentModuleService",
|
||||
PRICING = "pricingModuleService",
|
||||
PRODUCT = "productModuleService",
|
||||
PROMOTION = "promotionModuleService",
|
||||
SALES_CHANNEL = "salesChannelModuleService",
|
||||
FULFILLMENT = "fulfillmentModuleService",
|
||||
STOCK_LOCATION = "stockLocationService",
|
||||
TAX = "taxModuleService",
|
||||
USER = "userModuleService",
|
||||
WORKFLOW_ENGINE = "workflowsModuleService",
|
||||
REGION = "regionModuleService",
|
||||
ORDER = "orderModuleService",
|
||||
API_KEY = "apiKeyModuleService",
|
||||
STORE = "storeModuleService",
|
||||
CURRENCY = "currencyModuleService",
|
||||
FILE = "fileModuleService",
|
||||
}
|
||||
|
||||
export const MODULE_PACKAGE_NAMES = {
|
||||
[Modules.AUTH]: "@medusajs/auth",
|
||||
[Modules.CACHE]: "@medusajs/cache-inmemory",
|
||||
[Modules.CART]: "@medusajs/cart",
|
||||
[Modules.CUSTOMER]: "@medusajs/customer",
|
||||
[Modules.EVENT_BUS]: "@medusajs/event-bus-local",
|
||||
[Modules.INVENTORY]: "@medusajs/inventory-next", // TODO: To be replaced when current `@medusajs/inventory` is deprecated
|
||||
[Modules.LINK]: "@medusajs/link-modules",
|
||||
[Modules.PAYMENT]: "@medusajs/payment",
|
||||
[Modules.PRICING]: "@medusajs/pricing",
|
||||
[Modules.PRODUCT]: "@medusajs/product",
|
||||
[Modules.PROMOTION]: "@medusajs/promotion",
|
||||
[Modules.SALES_CHANNEL]: "@medusajs/sales-channel",
|
||||
[Modules.FULFILLMENT]: "@medusajs/fulfillment",
|
||||
[Modules.STOCK_LOCATION]: "@medusajs/stock-location-next", // TODO: To be replaced when current `@medusajs/stock-location` is deprecated
|
||||
[Modules.TAX]: "@medusajs/tax",
|
||||
[Modules.USER]: "@medusajs/user",
|
||||
[Modules.WORKFLOW_ENGINE]: "@medusajs/workflow-engine-inmemory",
|
||||
[Modules.REGION]: "@medusajs/region",
|
||||
[Modules.ORDER]: "@medusajs/order",
|
||||
[Modules.API_KEY]: "@medusajs/api-key",
|
||||
[Modules.STORE]: "@medusajs/store",
|
||||
[Modules.CURRENCY]: "@medusajs/currency",
|
||||
[Modules.FILE]: "@medusajs/file",
|
||||
}
|
||||
|
||||
export const ModulesDefinition: { [key: string | Modules]: ModuleDefinition } =
|
||||
{
|
||||
[Modules.EVENT_BUS]: {
|
||||
key: Modules.EVENT_BUS,
|
||||
isLegacy: true,
|
||||
registrationName: ModuleRegistrationName.EVENT_BUS,
|
||||
defaultPackage: MODULE_PACKAGE_NAMES[Modules.EVENT_BUS],
|
||||
label: upperCaseFirst(ModuleRegistrationName.EVENT_BUS),
|
||||
isRequired: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.STOCK_LOCATION]: {
|
||||
key: Modules.STOCK_LOCATION,
|
||||
isLegacy: true,
|
||||
registrationName: ModuleRegistrationName.STOCK_LOCATION,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.STOCK_LOCATION),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.INVENTORY]: {
|
||||
key: Modules.INVENTORY,
|
||||
isLegacy: true,
|
||||
registrationName: ModuleRegistrationName.INVENTORY,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.INVENTORY),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.CACHE]: {
|
||||
key: Modules.CACHE,
|
||||
isLegacy: true,
|
||||
registrationName: ModuleRegistrationName.CACHE,
|
||||
defaultPackage: MODULE_PACKAGE_NAMES[Modules.CACHE],
|
||||
label: upperCaseFirst(ModuleRegistrationName.CACHE),
|
||||
isRequired: true,
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.PRODUCT]: {
|
||||
key: Modules.PRODUCT,
|
||||
registrationName: ModuleRegistrationName.PRODUCT,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.PRODUCT),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: [ModuleRegistrationName.EVENT_BUS, "logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.PRICING]: {
|
||||
key: Modules.PRICING,
|
||||
registrationName: ModuleRegistrationName.PRICING,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.PRICING),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.PROMOTION]: {
|
||||
key: Modules.PROMOTION,
|
||||
registrationName: ModuleRegistrationName.PROMOTION,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.PROMOTION),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.AUTH]: {
|
||||
key: Modules.AUTH,
|
||||
registrationName: ModuleRegistrationName.AUTH,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.AUTH),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.WORKFLOW_ENGINE]: {
|
||||
key: Modules.WORKFLOW_ENGINE,
|
||||
registrationName: ModuleRegistrationName.WORKFLOW_ENGINE,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.WORKFLOW_ENGINE),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
key: Modules.SALES_CHANNEL,
|
||||
registrationName: ModuleRegistrationName.SALES_CHANNEL,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.SALES_CHANNEL),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.FULFILLMENT]: {
|
||||
key: Modules.FULFILLMENT,
|
||||
registrationName: ModuleRegistrationName.FULFILLMENT,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.FULFILLMENT),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger", "eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.CART]: {
|
||||
key: Modules.CART,
|
||||
registrationName: ModuleRegistrationName.CART,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.CART),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.CUSTOMER]: {
|
||||
key: Modules.CUSTOMER,
|
||||
registrationName: ModuleRegistrationName.CUSTOMER,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.CUSTOMER),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.PAYMENT]: {
|
||||
key: Modules.PAYMENT,
|
||||
registrationName: ModuleRegistrationName.PAYMENT,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.PAYMENT),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.USER]: {
|
||||
key: Modules.USER,
|
||||
registrationName: ModuleRegistrationName.USER,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.USER),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: [ModuleRegistrationName.EVENT_BUS, "logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.REGION]: {
|
||||
key: Modules.REGION,
|
||||
registrationName: ModuleRegistrationName.REGION,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.REGION),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.ORDER]: {
|
||||
key: Modules.ORDER,
|
||||
registrationName: ModuleRegistrationName.ORDER,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.ORDER),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger", "eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.TAX]: {
|
||||
key: Modules.TAX,
|
||||
registrationName: ModuleRegistrationName.TAX,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.TAX),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger", "eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.API_KEY]: {
|
||||
key: Modules.API_KEY,
|
||||
registrationName: ModuleRegistrationName.API_KEY,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.API_KEY),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.STORE]: {
|
||||
key: Modules.STORE,
|
||||
registrationName: ModuleRegistrationName.STORE,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.STORE),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.CURRENCY]: {
|
||||
key: Modules.CURRENCY,
|
||||
registrationName: ModuleRegistrationName.CURRENCY,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.CURRENCY),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.FILE]: {
|
||||
key: Modules.FILE,
|
||||
registrationName: ModuleRegistrationName.FILE,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.FILE),
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const MODULE_DEFINITIONS: ModuleDefinition[] =
|
||||
Object.values(ModulesDefinition)
|
||||
|
||||
export default MODULE_DEFINITIONS
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from "@medusajs/types/dist/modules-sdk"
|
||||
export * from "./definitions"
|
||||
export * from "./loaders"
|
||||
export * from "./medusa-app"
|
||||
export * from "./medusa-module"
|
||||
export * from "./remote-link"
|
||||
export * from "./remote-query"
|
||||
export * from "./utils/initialize-factory"
|
||||
@@ -0,0 +1,13 @@
|
||||
const loader = ({}) => {
|
||||
throw new Error("loader")
|
||||
}
|
||||
|
||||
const service = class TestService {}
|
||||
const migrations = []
|
||||
const loaders = [loader]
|
||||
|
||||
export default {
|
||||
service,
|
||||
migrations,
|
||||
loaders,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
const service = class TestService {}
|
||||
const migrations = []
|
||||
const loaders = []
|
||||
const models = []
|
||||
|
||||
export default {
|
||||
service,
|
||||
migrations,
|
||||
loaders,
|
||||
models,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const migrations = []
|
||||
const loaders = []
|
||||
|
||||
export default {
|
||||
migrations,
|
||||
loaders,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const service = class TestService {}
|
||||
|
||||
export default {
|
||||
services: [service],
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const service = class TestService {}
|
||||
|
||||
export const defaultExport = {
|
||||
services: [service],
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
loaders: [],
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const trackInstallation = jest.fn()
|
||||
export const trackFeatureFlag = jest.fn()
|
||||
@@ -0,0 +1,324 @@
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { MedusaModule } from "../../medusa-module"
|
||||
import { asValue } from "awilix"
|
||||
|
||||
const mockRegisterMedusaModule = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
moduleKey: {
|
||||
definition: {
|
||||
key: "moduleKey",
|
||||
registrationName: "moduleKey",
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const mockModuleLoader = jest.fn().mockImplementation(({ container }) => {
|
||||
container.register({
|
||||
moduleKey: asValue({}),
|
||||
})
|
||||
return Promise.resolve({})
|
||||
})
|
||||
|
||||
jest.mock("./../../loaders", () => ({
|
||||
registerMedusaModule: jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => mockRegisterMedusaModule()),
|
||||
moduleLoader: jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => mockModuleLoader.apply(this, args)),
|
||||
}))
|
||||
|
||||
describe("Medusa Modules", () => {
|
||||
beforeEach(() => {
|
||||
MedusaModule.clearInstances()
|
||||
jest.resetModules()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should create singleton instances", async () => {
|
||||
await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
expect(mockRegisterMedusaModule).toBeCalledTimes(1)
|
||||
expect(mockModuleLoader).toBeCalledTimes(1)
|
||||
|
||||
await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
expect(mockRegisterMedusaModule).toBeCalledTimes(2)
|
||||
expect(mockModuleLoader).toBeCalledTimes(2)
|
||||
})
|
||||
|
||||
it("should prevent the module being loaded multiple times under concurrent requests", async () => {
|
||||
const load: any = []
|
||||
|
||||
for (let i = 5; i--; ) {
|
||||
load.push(
|
||||
MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const intances = Promise.all(load)
|
||||
|
||||
expect(mockRegisterMedusaModule).toBeCalledTimes(1)
|
||||
expect(mockModuleLoader).toBeCalledTimes(1)
|
||||
expect(intances[(await intances).length - 1]).toBe(intances[0])
|
||||
})
|
||||
|
||||
it("getModuleInstance should return the first instance of the module if there is none flagged as 'main'", async () => {
|
||||
const moduleA = await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
const moduleB = await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
expect(MedusaModule.getModuleInstance("moduleKey")).toEqual(moduleA)
|
||||
})
|
||||
|
||||
it("should return the module flagged as 'main' when multiple instances are available", async () => {
|
||||
const moduleA = await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
const moduleB = await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
expect(MedusaModule.getModuleInstance("moduleKey")).toEqual(moduleB)
|
||||
})
|
||||
|
||||
it("should retrieve the module by their given alias", async () => {
|
||||
const moduleA = await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "mod_A",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
const moduleB = await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
alias: "mod_B",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
const moduleC = await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "mod_C",
|
||||
options: {
|
||||
moduleC: true,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
// main
|
||||
expect(MedusaModule.getModuleInstance("moduleKey")).toEqual(moduleB)
|
||||
|
||||
expect(MedusaModule.getModuleInstance("moduleKey", "mod_A")).toEqual(
|
||||
moduleA
|
||||
)
|
||||
expect(MedusaModule.getModuleInstance("moduleKey", "mod_B")).toEqual(
|
||||
moduleB
|
||||
)
|
||||
expect(MedusaModule.getModuleInstance("moduleKey", "mod_C")).toEqual(
|
||||
moduleC
|
||||
)
|
||||
})
|
||||
|
||||
it("should prevent two main modules being set as 'main'", async () => {
|
||||
await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "mod_A",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
alias: "mod_B",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
const moduleC = MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
alias: "mod_C",
|
||||
options: {
|
||||
moduleC: true,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
expect(moduleC).rejects.toThrow(
|
||||
"Module moduleKey already have a 'main' registered."
|
||||
)
|
||||
})
|
||||
|
||||
it("should prevent the same alias be used for different instances of the same module", async () => {
|
||||
await MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "module_alias",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
const moduleC = MedusaModule.bootstrap({
|
||||
moduleKey: "moduleKey",
|
||||
defaultPath: "@path",
|
||||
declaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "module_alias",
|
||||
options: {
|
||||
moduleC: true,
|
||||
},
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
expect(moduleC).rejects.toThrow(
|
||||
"Module moduleKey already registed as 'module_alias'. Please choose a different alias."
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,279 @@
|
||||
import {
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
ModuleResolution,
|
||||
} from "@medusajs/types"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { EOL } from "os"
|
||||
import { moduleLoader } from "../module-loader"
|
||||
|
||||
const logger = {
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as any
|
||||
|
||||
describe("modules loader", () => {
|
||||
let container
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
container = createMedusaContainer()
|
||||
})
|
||||
|
||||
it("should register the service as undefined in the container when no resolution path is given", async () => {
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: false,
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "testService",
|
||||
label: "TestService",
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
const testService = container.resolve(
|
||||
moduleResolutions.testService.definition.key
|
||||
)
|
||||
expect(testService).toBe(undefined)
|
||||
})
|
||||
|
||||
it("should register the service ", async () => {
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: "@modules/default",
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "testService",
|
||||
label: "TestService",
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
const testService = container.resolve(
|
||||
moduleResolutions.testService.definition.key,
|
||||
{}
|
||||
)
|
||||
|
||||
/*
|
||||
expect(trackInstallation).toHaveBeenCalledWith(
|
||||
{
|
||||
module: moduleResolutions.testService.definition.key,
|
||||
resolution: moduleResolutions.testService.resolutionPath,
|
||||
},
|
||||
"module"
|
||||
)
|
||||
*/
|
||||
expect(testService).toBeTruthy()
|
||||
expect(typeof testService).toEqual("object")
|
||||
})
|
||||
|
||||
it("should run the defined loaders and logs the errors if something fails", async () => {
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: "@modules/brokenloader",
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "testService",
|
||||
label: "TestService",
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`Could not resolve module: TestService. Error: Loaders for module TestService failed: loader${EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it("should log the errors if no service is defined", async () => {
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: "@modules/no-service",
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "testService",
|
||||
label: "TestService",
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`Could not resolve module: TestService. Error: No service found in module. Make sure your module exports a service.${EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error if no service is defined and the module is required", async () => {
|
||||
expect.assertions(1)
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: "@modules/no-service",
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "testService",
|
||||
label: "TestService",
|
||||
isRequired: true,
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"No service found in module. Make sure your module exports a service."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw an error if the default package isn't found and the module is required", async () => {
|
||||
expect.assertions(1)
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: "@medusajs/testService",
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "@medusajs/testService",
|
||||
label: "TestService",
|
||||
isRequired: true,
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
`Make sure you have installed the default package: @medusajs/testService`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw an error if no scope is defined on the module declaration", async () => {
|
||||
expect.assertions(1)
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: "@modules/no-service",
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "testService",
|
||||
label: "TestService",
|
||||
isRequired: true,
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
moduleDeclaration: {
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"The module TestService has to define its scope (internal | external)"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw an error if the resources is not set when scope is defined as internal", async () => {
|
||||
expect.assertions(1)
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: "@modules/no-service",
|
||||
definition: {
|
||||
registrationName: "testService",
|
||||
key: "testService",
|
||||
defaultPackage: "testService",
|
||||
label: "TestService",
|
||||
isRequired: true,
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
},
|
||||
} as any,
|
||||
}
|
||||
|
||||
try {
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"The module TestService is missing its resources config"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { Lifetime, asFunction } from "awilix"
|
||||
import { moduleProviderLoader } from "../module-provider-loader"
|
||||
|
||||
const logger = {
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as any
|
||||
|
||||
describe("modules loader", () => {
|
||||
let container
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
container = createMedusaContainer()
|
||||
})
|
||||
|
||||
it("should register the provider service", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
await moduleProviderLoader({ container, providers: moduleProviders })
|
||||
|
||||
const testService = container.resolve("testService")
|
||||
expect(testService).toBeTruthy()
|
||||
expect(testService.constructor.name).toEqual("TestService")
|
||||
})
|
||||
|
||||
it("should register the provider service with custom register fn", async () => {
|
||||
const fn = async (klass, container, details) => {
|
||||
container.register({
|
||||
[`testServiceCustomRegistration`]: asFunction(
|
||||
(cradle) => new klass(cradle, details.options),
|
||||
{
|
||||
lifetime: Lifetime.SINGLETON,
|
||||
}
|
||||
),
|
||||
})
|
||||
}
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: moduleProviders,
|
||||
registerServiceFn: fn,
|
||||
})
|
||||
|
||||
const testService = container.resolve("testServiceCustomRegistration")
|
||||
expect(testService).toBeTruthy()
|
||||
expect(testService.constructor.name).toEqual("TestService")
|
||||
})
|
||||
|
||||
it("should log the errors if no service is defined", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/no-service",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
try {
|
||||
await moduleProviderLoader({ container, providers: moduleProviders })
|
||||
} catch (error) {
|
||||
expect(error.message).toBe(
|
||||
"No services found in plugin @plugins/no-service -- make sure your plugin has a default export of services."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw if no default export is defined", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/no-default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
try {
|
||||
await moduleProviderLoader({ container, providers: moduleProviders })
|
||||
} catch (error) {
|
||||
expect(error.message).toBe(
|
||||
"No services found in plugin @plugins/no-default -- make sure your plugin has a default export of services."
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,244 @@
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
ModuleDefinition,
|
||||
} from "@medusajs/types"
|
||||
import { ModulesDefinition } from "../../definitions"
|
||||
import { registerMedusaModule } from "../register-modules"
|
||||
|
||||
const RESOLVED_PACKAGE = "@medusajs/test-service-resolved"
|
||||
jest.mock("resolve-cwd", () => jest.fn(() => RESOLVED_PACKAGE))
|
||||
|
||||
describe("module definitions loader", () => {
|
||||
const defaultDefinition: ModuleDefinition = {
|
||||
key: "testService",
|
||||
registrationName: "testService",
|
||||
defaultPackage: "@medusajs/test-service",
|
||||
label: "TestService",
|
||||
isLegacy: true,
|
||||
isRequired: false,
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules()
|
||||
jest.clearAllMocks()
|
||||
|
||||
// Clear module definitions
|
||||
const allProperties = Object.getOwnPropertyNames(ModulesDefinition)
|
||||
allProperties.forEach((property) => {
|
||||
delete ModulesDefinition[property]
|
||||
})
|
||||
})
|
||||
|
||||
it("Resolves module with default definition given empty config", () => {
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: defaultDefinition.defaultPackage,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("Resolves a custom module without pre-defined definition", () => {
|
||||
const res = registerMedusaModule("customModulesABC", {
|
||||
options: {
|
||||
test: 123,
|
||||
},
|
||||
})
|
||||
|
||||
expect(res).toEqual({
|
||||
customModulesABC: expect.objectContaining({
|
||||
resolutionPath: "@medusajs/test-service-resolved",
|
||||
definition: expect.objectContaining({
|
||||
key: "customModulesABC",
|
||||
label: "Custom: customModulesABC",
|
||||
registrationName: "customModulesABC",
|
||||
}),
|
||||
moduleDeclaration: {
|
||||
resources: "shared",
|
||||
scope: "internal",
|
||||
},
|
||||
options: {
|
||||
test: 123,
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
describe("boolean config", () => {
|
||||
it("Resolves module with no resolution path when given false", () => {
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, false)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: false,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("Fails to resolve module with no resolution path when given false for a required module", () => {
|
||||
expect.assertions(1)
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: { ...defaultDefinition, isRequired: true },
|
||||
})
|
||||
|
||||
try {
|
||||
registerMedusaModule(defaultDefinition.key, false)
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
`Module: ${defaultDefinition.label} is required`
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it("Module with no resolution path when not given custom resolution path, false as default package and required", () => {
|
||||
const definition = {
|
||||
...defaultDefinition,
|
||||
defaultPackage: false as false,
|
||||
isRequired: true,
|
||||
}
|
||||
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: definition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: false,
|
||||
definition: definition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
describe("string config", () => {
|
||||
it("Resolves module with default definition given empty config", () => {
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(
|
||||
defaultDefinition.key,
|
||||
defaultDefinition.defaultPackage
|
||||
)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("object config", () => {
|
||||
it("Resolves resolution path and provides empty options when none are provided", () => {
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("Resolves default resolution path and provides options when only options are provided", () => {
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, {
|
||||
options: { test: 123 },
|
||||
} as any)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: defaultDefinition.defaultPackage,
|
||||
definition: defaultDefinition,
|
||||
options: { test: 123 },
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("Resolves resolution path and provides options when only options are provided", () => {
|
||||
Object.assign(ModulesDefinition, {
|
||||
[defaultDefinition.key]: defaultDefinition,
|
||||
})
|
||||
|
||||
const res = registerMedusaModule(defaultDefinition.key, {
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
options: { test: 123 },
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
} as any)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: { test: 123 },
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./module-loader"
|
||||
export * from "./module-provider-loader"
|
||||
export * from "./register-modules"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
MODULE_SCOPE,
|
||||
ModuleResolution,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { asValue } from "awilix"
|
||||
import { EOL } from "os"
|
||||
import { loadInternalModule } from "./utils"
|
||||
|
||||
export const moduleLoader = async ({
|
||||
container,
|
||||
moduleResolutions,
|
||||
logger,
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
moduleResolutions: Record<string, ModuleResolution>
|
||||
logger: Logger
|
||||
migrationOnly?: boolean
|
||||
loaderOnly?: boolean
|
||||
}): Promise<void> => {
|
||||
for (const resolution of Object.values(moduleResolutions ?? {})) {
|
||||
const registrationResult = await loadModule(
|
||||
container,
|
||||
resolution,
|
||||
logger!,
|
||||
migrationOnly,
|
||||
loaderOnly
|
||||
)
|
||||
|
||||
if (registrationResult?.error) {
|
||||
const { error } = registrationResult
|
||||
if (resolution.definition.isRequired) {
|
||||
logger?.error(
|
||||
`Could not resolve required module: ${resolution.definition.label}. Error: ${error.message}${EOL}`
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
logger?.warn(
|
||||
`Could not resolve module: ${resolution.definition.label}. Error: ${error.message}${EOL}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadModule(
|
||||
container: MedusaContainer,
|
||||
resolution: ModuleResolution,
|
||||
logger: Logger,
|
||||
migrationOnly?: boolean,
|
||||
loaderOnly?: boolean
|
||||
): Promise<{ error?: Error } | void> {
|
||||
const modDefinition = resolution.definition
|
||||
const registrationName = modDefinition.registrationName
|
||||
|
||||
const { scope, resources } = resolution.moduleDeclaration ?? ({} as any)
|
||||
|
||||
const canSkip =
|
||||
!resolution.resolutionPath &&
|
||||
!modDefinition.isRequired &&
|
||||
!modDefinition.defaultPackage
|
||||
|
||||
if (scope === MODULE_SCOPE.EXTERNAL && !canSkip) {
|
||||
// TODO: implement external Resolvers
|
||||
// return loadExternalModule(...)
|
||||
throw new Error("External Modules are not supported yet.")
|
||||
}
|
||||
|
||||
if (!scope || (scope === MODULE_SCOPE.INTERNAL && !resources)) {
|
||||
let message = `The module ${resolution.definition.label} has to define its scope (internal | external)`
|
||||
if (scope === MODULE_SCOPE.INTERNAL && !resources) {
|
||||
message = `The module ${resolution.definition.label} is missing its resources config`
|
||||
}
|
||||
|
||||
container.register(registrationName, asValue(undefined))
|
||||
|
||||
return {
|
||||
error: new Error(message),
|
||||
}
|
||||
}
|
||||
|
||||
if (resolution.resolutionPath === false) {
|
||||
container.register(registrationName, asValue(undefined))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return await loadInternalModule(
|
||||
container,
|
||||
resolution,
|
||||
logger,
|
||||
migrationOnly,
|
||||
loaderOnly
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { MedusaContainer, ModuleProvider } from "@medusajs/types"
|
||||
import { isString, lowerCaseFirst, promiseAll } from "@medusajs/utils"
|
||||
import { Lifetime, asFunction } from "awilix"
|
||||
|
||||
export async function moduleProviderLoader({
|
||||
container,
|
||||
providers,
|
||||
registerServiceFn,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
providers: ModuleProvider[]
|
||||
registerServiceFn?: (
|
||||
klass,
|
||||
container: MedusaContainer,
|
||||
pluginDetails: any
|
||||
) => Promise<void>
|
||||
}) {
|
||||
if (!providers?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
await promiseAll(
|
||||
providers.map(async (pluginDetails) => {
|
||||
await loadModuleProvider(container, pluginDetails, registerServiceFn)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export async function loadModuleProvider(
|
||||
container: MedusaContainer,
|
||||
provider: ModuleProvider,
|
||||
registerServiceFn?: (klass, container, pluginDetails) => Promise<void>
|
||||
) {
|
||||
let loadedProvider: any
|
||||
|
||||
const pluginName = provider.resolve ?? provider.provider_name ?? ""
|
||||
|
||||
try {
|
||||
loadedProvider = provider.resolve
|
||||
|
||||
if (isString(provider.resolve)) {
|
||||
loadedProvider = await import(provider.resolve)
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Unable to find plugin ${pluginName} -- perhaps you need to install its package?`
|
||||
)
|
||||
}
|
||||
|
||||
loadedProvider = (loadedProvider as any).default ?? loadedProvider
|
||||
|
||||
if (!loadedProvider?.services?.length) {
|
||||
throw new Error(
|
||||
`No services found in plugin ${provider.resolve} -- make sure your plugin has a default export of services.`
|
||||
)
|
||||
}
|
||||
|
||||
const services = await promiseAll(
|
||||
loadedProvider.services.map(async (service) => {
|
||||
const name = lowerCaseFirst(service.name)
|
||||
if (registerServiceFn) {
|
||||
// Used to register the specific type of service in the provider
|
||||
await registerServiceFn(service, container, provider.options)
|
||||
} else {
|
||||
container.register({
|
||||
[name]: asFunction(
|
||||
(cradle) => new service(cradle, provider.options),
|
||||
{
|
||||
lifetime: service.LIFE_TIME || Lifetime.SCOPED,
|
||||
}
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return service
|
||||
})
|
||||
)
|
||||
|
||||
return services
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
ModuleDefinition,
|
||||
ModuleExports,
|
||||
ModuleResolution,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { isObject, isString } from "@medusajs/utils"
|
||||
import resolveCwd from "resolve-cwd"
|
||||
import { ModulesDefinition } from "../definitions"
|
||||
|
||||
export const registerMedusaModule = (
|
||||
moduleKey: string,
|
||||
moduleDeclaration?:
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
| string
|
||||
| false,
|
||||
moduleExports?: ModuleExports,
|
||||
definition?: ModuleDefinition
|
||||
): Record<string, ModuleResolution> => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
|
||||
const modDefinition = definition ?? ModulesDefinition[moduleKey]
|
||||
|
||||
const modDeclaration =
|
||||
moduleDeclaration ??
|
||||
(modDefinition?.defaultModuleDeclaration as InternalModuleDeclaration)
|
||||
|
||||
if (modDeclaration !== false && !modDeclaration) {
|
||||
throw new Error(`Module: ${moduleKey} has no declaration.`)
|
||||
}
|
||||
|
||||
if (
|
||||
isObject(modDeclaration) &&
|
||||
modDeclaration?.scope === MODULE_SCOPE.EXTERNAL
|
||||
) {
|
||||
// TODO: getExternalModuleResolution(...)
|
||||
throw new Error("External Modules are not supported yet.")
|
||||
}
|
||||
|
||||
if (modDefinition === undefined) {
|
||||
moduleResolutions[moduleKey] = getCustomModuleResolution(
|
||||
moduleKey,
|
||||
moduleDeclaration as InternalModuleDeclaration
|
||||
)
|
||||
return moduleResolutions
|
||||
}
|
||||
|
||||
moduleResolutions[moduleKey] = getInternalModuleResolution(
|
||||
modDefinition,
|
||||
moduleDeclaration as InternalModuleDeclaration,
|
||||
moduleExports
|
||||
)
|
||||
|
||||
return moduleResolutions
|
||||
}
|
||||
|
||||
function getCustomModuleResolution(
|
||||
key: string,
|
||||
moduleConfig: InternalModuleDeclaration | string
|
||||
): ModuleResolution {
|
||||
const resolutionPath = resolveCwd(
|
||||
isString(moduleConfig) ? moduleConfig : (moduleConfig.resolve as string)
|
||||
)
|
||||
|
||||
const conf = isObject(moduleConfig)
|
||||
? moduleConfig
|
||||
: ({} as InternalModuleDeclaration)
|
||||
|
||||
const dependencies = conf?.dependencies ?? []
|
||||
|
||||
return {
|
||||
resolutionPath,
|
||||
definition: {
|
||||
key,
|
||||
label: `Custom: ${key}`,
|
||||
isRequired: false,
|
||||
defaultPackage: "",
|
||||
dependencies,
|
||||
registrationName: key,
|
||||
defaultModuleDeclaration: {
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
},
|
||||
},
|
||||
moduleDeclaration: {
|
||||
resources: conf?.resources ?? MODULE_RESOURCE_TYPE.SHARED,
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
},
|
||||
dependencies,
|
||||
options: conf?.options ?? {},
|
||||
}
|
||||
}
|
||||
|
||||
export const registerMedusaLinkModule = (
|
||||
definition: ModuleDefinition,
|
||||
moduleDeclaration: Partial<InternalModuleDeclaration>,
|
||||
moduleExports?: ModuleExports
|
||||
): Record<string, ModuleResolution> => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
|
||||
moduleResolutions[definition.key] = getInternalModuleResolution(
|
||||
definition,
|
||||
moduleDeclaration as InternalModuleDeclaration,
|
||||
moduleExports
|
||||
)
|
||||
|
||||
return moduleResolutions
|
||||
}
|
||||
|
||||
function getInternalModuleResolution(
|
||||
definition: ModuleDefinition,
|
||||
moduleConfig: InternalModuleDeclaration | string | false,
|
||||
moduleExports?: ModuleExports
|
||||
): ModuleResolution {
|
||||
if (typeof moduleConfig === "boolean") {
|
||||
if (!moduleConfig && definition.isRequired) {
|
||||
throw new Error(`Module: ${definition.label} is required`)
|
||||
}
|
||||
|
||||
if (!moduleConfig) {
|
||||
return {
|
||||
resolutionPath: false,
|
||||
definition,
|
||||
dependencies: [],
|
||||
options: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isObj = isObject(moduleConfig)
|
||||
let resolutionPath = definition.defaultPackage
|
||||
|
||||
// If user added a module and it's overridable, we resolve that instead
|
||||
const isStr = isString(moduleConfig)
|
||||
if (isStr || (isObj && moduleConfig.resolve)) {
|
||||
resolutionPath = !moduleExports
|
||||
? resolveCwd(isStr ? moduleConfig : (moduleConfig.resolve as string))
|
||||
: // Explicitly assign an empty string, later, we will check if the value is exactly false.
|
||||
// This allows to continue the module loading while using the module exports instead of re importing the module itself during the process.
|
||||
""
|
||||
}
|
||||
|
||||
const moduleDeclaration = isObj ? moduleConfig : {}
|
||||
const additionalDependencies = isObj ? moduleConfig.dependencies || [] : []
|
||||
|
||||
return {
|
||||
resolutionPath,
|
||||
definition,
|
||||
dependencies: [
|
||||
...new Set(
|
||||
(definition.dependencies || []).concat(additionalDependencies)
|
||||
),
|
||||
],
|
||||
moduleDeclaration: {
|
||||
...(definition.defaultModuleDeclaration ?? {}),
|
||||
...moduleDeclaration,
|
||||
},
|
||||
moduleExports,
|
||||
options: isObj ? moduleConfig.options ?? {} : {},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./load-internal"
|
||||
@@ -0,0 +1,162 @@
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
ModuleExports,
|
||||
ModuleResolution,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
createMedusaContainer,
|
||||
MedusaModuleType,
|
||||
} from "@medusajs/utils"
|
||||
import { asFunction, asValue } from "awilix"
|
||||
|
||||
export async function loadInternalModule(
|
||||
container: MedusaContainer,
|
||||
resolution: ModuleResolution,
|
||||
logger: Logger,
|
||||
migrationOnly?: boolean,
|
||||
loaderOnly?: boolean
|
||||
): Promise<{ error?: Error } | void> {
|
||||
const registrationName = !loaderOnly
|
||||
? resolution.definition.registrationName
|
||||
: resolution.definition.registrationName + "__loaderOnly"
|
||||
|
||||
const { resources } =
|
||||
resolution.moduleDeclaration as InternalModuleDeclaration
|
||||
|
||||
let loadedModule: ModuleExports
|
||||
try {
|
||||
// When loading manually, we pass the exports to be loaded, meaning that we do not need to import the package to find
|
||||
// the exports. This is useful when a package export an initialize function which will bootstrap itself and therefore
|
||||
// does not need to import the package that is currently being loaded as it would create a
|
||||
// circular reference.
|
||||
const modulePath = resolution.resolutionPath as string
|
||||
|
||||
if (resolution.moduleExports) {
|
||||
loadedModule = resolution.moduleExports
|
||||
} else {
|
||||
loadedModule = await import(modulePath)
|
||||
loadedModule = (loadedModule as any).default
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
resolution.definition.isRequired &&
|
||||
resolution.definition.defaultPackage
|
||||
) {
|
||||
return {
|
||||
error: new Error(
|
||||
`Make sure you have installed the default package: ${resolution.definition.defaultPackage}`
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return { error }
|
||||
}
|
||||
|
||||
if (!loadedModule?.service) {
|
||||
container.register({
|
||||
[registrationName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return {
|
||||
error: new Error(
|
||||
"No service found in module. Make sure your module exports a service."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if (migrationOnly) {
|
||||
// Partially loaded module, only register the service __joinerConfig function to be able to resolve it later
|
||||
const moduleService = {
|
||||
__joinerConfig: loadedModule.service.prototype.__joinerConfig,
|
||||
}
|
||||
container.register({
|
||||
[registrationName]: asValue(moduleService),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const localContainer = createMedusaContainer()
|
||||
|
||||
const dependencies = resolution?.dependencies ?? []
|
||||
if (resources === MODULE_RESOURCE_TYPE.SHARED) {
|
||||
dependencies.push(
|
||||
ContainerRegistrationKeys.MANAGER,
|
||||
ContainerRegistrationKeys.CONFIG_MODULE,
|
||||
ContainerRegistrationKeys.LOGGER,
|
||||
ContainerRegistrationKeys.PG_CONNECTION
|
||||
)
|
||||
}
|
||||
|
||||
for (const dependency of dependencies) {
|
||||
localContainer.register(
|
||||
dependency,
|
||||
asFunction(() => {
|
||||
return container.resolve(dependency, { allowUnregistered: true })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const moduleLoaders = loadedModule?.loaders ?? []
|
||||
try {
|
||||
for (const loader of moduleLoaders) {
|
||||
await loader(
|
||||
{
|
||||
container: localContainer,
|
||||
logger,
|
||||
options: resolution.options,
|
||||
dataLoaderOnly: loaderOnly,
|
||||
},
|
||||
resolution.moduleDeclaration as InternalModuleDeclaration
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
container.register({
|
||||
[registrationName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return {
|
||||
error: new Error(
|
||||
`Loaders for module ${resolution.definition.label} failed: ${err.message}`
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const moduleService = loadedModule.service
|
||||
|
||||
container.register({
|
||||
[registrationName]: asFunction((cradle) => {
|
||||
;(moduleService as any).__type = MedusaModuleType
|
||||
return new moduleService(
|
||||
localContainer.cradle,
|
||||
resolution.options,
|
||||
resolution.moduleDeclaration
|
||||
)
|
||||
}).singleton(),
|
||||
})
|
||||
|
||||
if (loaderOnly) {
|
||||
// The expectation is only to run the loader as standalone, so we do not need to register the service and we need to cleanup all services
|
||||
const service = container.resolve(registrationName)
|
||||
await service.__hooks?.onApplicationPrepareShutdown()
|
||||
await service.__hooks?.onApplicationShutdown()
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadModuleMigrations(
|
||||
resolution: ModuleResolution,
|
||||
moduleExports?: ModuleExports
|
||||
): Promise<[Function | undefined, Function | undefined]> {
|
||||
let loadedModule: ModuleExports
|
||||
try {
|
||||
loadedModule =
|
||||
moduleExports ?? (await import(resolution.resolutionPath as string))
|
||||
|
||||
return [loadedModule.runMigrations, loadedModule.revertMigration]
|
||||
} catch {
|
||||
return [undefined, undefined]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
import { mergeTypeDefs } from "@graphql-tools/merge"
|
||||
import { makeExecutableSchema } from "@graphql-tools/schema"
|
||||
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
LoadedModule,
|
||||
MedusaContainer,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
ModuleDefinition,
|
||||
ModuleExports,
|
||||
ModuleJoinerConfig,
|
||||
ModuleServiceInitializeOptions,
|
||||
RemoteJoinerOptions,
|
||||
RemoteJoinerQuery,
|
||||
RemoteQueryFunction,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
createMedusaContainer,
|
||||
isObject,
|
||||
isString,
|
||||
ModulesSdkUtils,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
import {
|
||||
MODULE_PACKAGE_NAMES,
|
||||
ModuleRegistrationName,
|
||||
Modules,
|
||||
} from "./definitions"
|
||||
import { MedusaModule } from "./medusa-module"
|
||||
import { RemoteLink } from "./remote-link"
|
||||
import { RemoteQuery } from "./remote-query"
|
||||
import { cleanGraphQLSchema } from "./utils"
|
||||
|
||||
const LinkModulePackage = MODULE_PACKAGE_NAMES[Modules.LINK]
|
||||
|
||||
export type RunMigrationFn = (
|
||||
options?: ModuleServiceInitializeOptions,
|
||||
injectedDependencies?: Record<any, any>
|
||||
) => Promise<void>
|
||||
|
||||
export type MedusaModuleConfig = {
|
||||
[key: string | Modules]:
|
||||
| string
|
||||
| boolean
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
}
|
||||
|
||||
export type SharedResources = {
|
||||
database?: ModuleServiceInitializeOptions["database"] & {
|
||||
/**
|
||||
* {
|
||||
* name?: string
|
||||
* afterCreate?: Function
|
||||
* min?: number
|
||||
* max?: number
|
||||
* refreshIdle?: boolean
|
||||
* idleTimeoutMillis?: number
|
||||
* reapIntervalMillis?: number
|
||||
* returnToHead?: boolean
|
||||
* priorityRange?: number
|
||||
* log?: (message: string, logLevel: string) => void
|
||||
* }
|
||||
*/
|
||||
pool?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadModules(
|
||||
modulesConfig,
|
||||
sharedContainer,
|
||||
migrationOnly = false,
|
||||
loaderOnly = false,
|
||||
workerMode: "shared" | "worker" | "server" = "server"
|
||||
) {
|
||||
const allModules = {}
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(modulesConfig).map(async (moduleName) => {
|
||||
const mod = modulesConfig[moduleName]
|
||||
let path: string
|
||||
let moduleExports: ModuleExports | undefined = undefined
|
||||
let declaration: any = {}
|
||||
let definition: Partial<ModuleDefinition> | undefined = undefined
|
||||
|
||||
if (isObject(mod)) {
|
||||
const mod_ = mod as unknown as InternalModuleDeclaration
|
||||
path = mod_.resolve ?? MODULE_PACKAGE_NAMES[moduleName]
|
||||
definition = mod_.definition
|
||||
moduleExports = !isString(mod_.resolve)
|
||||
? (mod_.resolve as ModuleExports)
|
||||
: undefined
|
||||
declaration = { ...mod }
|
||||
delete declaration.definition
|
||||
} else {
|
||||
path = MODULE_PACKAGE_NAMES[moduleName]
|
||||
}
|
||||
|
||||
declaration.scope ??= MODULE_SCOPE.INTERNAL
|
||||
if (
|
||||
declaration.scope === MODULE_SCOPE.INTERNAL &&
|
||||
!declaration.resources
|
||||
) {
|
||||
declaration.resources = MODULE_RESOURCE_TYPE.SHARED
|
||||
}
|
||||
|
||||
const loaded = (await MedusaModule.bootstrap({
|
||||
moduleKey: moduleName,
|
||||
defaultPath: path,
|
||||
declaration,
|
||||
sharedContainer,
|
||||
moduleDefinition: definition as ModuleDefinition,
|
||||
moduleExports,
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
})) as LoadedModule
|
||||
|
||||
if (loaderOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = loaded[moduleName]
|
||||
sharedContainer.register({
|
||||
[service.__definition.registrationName]: asValue(service),
|
||||
})
|
||||
|
||||
if (allModules[moduleName] && !Array.isArray(allModules[moduleName])) {
|
||||
allModules[moduleName] = []
|
||||
}
|
||||
|
||||
if (allModules[moduleName]) {
|
||||
;(allModules[moduleName] as LoadedModule[]).push(loaded[moduleName])
|
||||
} else {
|
||||
allModules[moduleName] = loaded[moduleName]
|
||||
}
|
||||
})
|
||||
)
|
||||
return allModules
|
||||
}
|
||||
|
||||
async function initializeLinks({
|
||||
config,
|
||||
linkModules,
|
||||
injectedDependencies,
|
||||
moduleExports,
|
||||
}) {
|
||||
try {
|
||||
const { initialize, runMigrations } =
|
||||
moduleExports ?? (await import(LinkModulePackage))
|
||||
|
||||
const linkResolution = await initialize(
|
||||
config,
|
||||
linkModules,
|
||||
injectedDependencies
|
||||
)
|
||||
|
||||
return { remoteLink: new RemoteLink(), linkResolution, runMigrations }
|
||||
} catch (err) {
|
||||
console.warn("Error initializing link modules.", err)
|
||||
|
||||
return {
|
||||
remoteLink: undefined,
|
||||
linkResolution: undefined,
|
||||
runMigrations: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMedusaModule(mod) {
|
||||
return typeof mod?.initialize === "function"
|
||||
}
|
||||
|
||||
function cleanAndMergeSchema(loadedSchema) {
|
||||
const { schema: cleanedSchema, notFound } = cleanGraphQLSchema(loadedSchema)
|
||||
const mergedSchema = mergeTypeDefs(cleanedSchema)
|
||||
return { schema: makeExecutableSchema({ typeDefs: mergedSchema }), notFound }
|
||||
}
|
||||
|
||||
function getLoadedSchema(): string {
|
||||
return MedusaModule.getAllJoinerConfigs()
|
||||
.map((joinerConfig) => joinerConfig?.schema ?? "")
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
function registerCustomJoinerConfigs(servicesConfig: ModuleJoinerConfig[]) {
|
||||
for (const config of servicesConfig) {
|
||||
if (!config.serviceName || config.isReadOnlyLink) {
|
||||
continue
|
||||
}
|
||||
|
||||
MedusaModule.setJoinerConfig(config.serviceName, config)
|
||||
}
|
||||
}
|
||||
|
||||
export type MedusaAppOutput = {
|
||||
modules: Record<string, LoadedModule | LoadedModule[]>
|
||||
link: RemoteLink | undefined
|
||||
query: RemoteQueryFunction
|
||||
entitiesMap?: Record<string, any>
|
||||
notFound?: Record<string, Record<string, string>>
|
||||
runMigrations: RunMigrationFn
|
||||
onApplicationShutdown: () => Promise<void>
|
||||
onApplicationPrepareShutdown: () => Promise<void>
|
||||
}
|
||||
|
||||
export type MedusaAppOptions = {
|
||||
workerMode?: "shared" | "worker" | "server"
|
||||
sharedContainer?: MedusaContainer
|
||||
sharedResourcesConfig?: SharedResources
|
||||
loadedModules?: LoadedModule[]
|
||||
servicesConfig?: ModuleJoinerConfig[]
|
||||
modulesConfigPath?: string
|
||||
modulesConfigFileName?: string
|
||||
modulesConfig?: MedusaModuleConfig
|
||||
linkModules?: ModuleJoinerConfig | ModuleJoinerConfig[]
|
||||
remoteFetchData?: RemoteFetchDataCallback
|
||||
injectedDependencies?: any
|
||||
onApplicationStartCb?: () => void
|
||||
/**
|
||||
* Forces the modules bootstrapper to only run the modules loaders and return prematurely
|
||||
*/
|
||||
loaderOnly?: boolean
|
||||
}
|
||||
|
||||
async function MedusaApp_({
|
||||
sharedContainer,
|
||||
sharedResourcesConfig,
|
||||
servicesConfig,
|
||||
modulesConfigPath,
|
||||
modulesConfigFileName,
|
||||
modulesConfig,
|
||||
linkModules,
|
||||
remoteFetchData,
|
||||
injectedDependencies = {},
|
||||
onApplicationStartCb,
|
||||
migrationOnly = false,
|
||||
loaderOnly = false,
|
||||
workerMode = "server",
|
||||
}: MedusaAppOptions & {
|
||||
migrationOnly?: boolean
|
||||
} = {}): Promise<MedusaAppOutput> {
|
||||
const sharedContainer_ = createMedusaContainer({}, sharedContainer)
|
||||
|
||||
const onApplicationShutdown = async () => {
|
||||
await promiseAll([
|
||||
MedusaModule.onApplicationShutdown(),
|
||||
sharedContainer_.dispose(),
|
||||
])
|
||||
}
|
||||
|
||||
const onApplicationPrepareShutdown = async () => {
|
||||
await promiseAll([MedusaModule.onApplicationPrepareShutdown()])
|
||||
}
|
||||
|
||||
const modules: MedusaModuleConfig =
|
||||
modulesConfig ??
|
||||
(
|
||||
await import(
|
||||
modulesConfigPath ??
|
||||
process.cwd() + (modulesConfigFileName ?? "/modules-config")
|
||||
)
|
||||
).default
|
||||
|
||||
const dbData = ModulesSdkUtils.loadDatabaseConfig(
|
||||
"medusa",
|
||||
sharedResourcesConfig as ModuleServiceInitializeOptions,
|
||||
true
|
||||
)!
|
||||
|
||||
registerCustomJoinerConfigs(servicesConfig ?? [])
|
||||
|
||||
if (
|
||||
sharedResourcesConfig?.database?.connection &&
|
||||
!injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION]
|
||||
) {
|
||||
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION] =
|
||||
sharedResourcesConfig.database.connection
|
||||
} else if (
|
||||
dbData.clientUrl &&
|
||||
!injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION]
|
||||
) {
|
||||
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION] =
|
||||
ModulesSdkUtils.createPgConnection({
|
||||
...(sharedResourcesConfig?.database ?? {}),
|
||||
...dbData,
|
||||
})
|
||||
}
|
||||
|
||||
// remove the link module from the modules
|
||||
const linkModule = modules[LinkModulePackage] ?? modules[Modules.LINK]
|
||||
delete modules[LinkModulePackage]
|
||||
delete modules[Modules.LINK]
|
||||
|
||||
let linkModuleOptions = {}
|
||||
|
||||
if (isObject(linkModule)) {
|
||||
linkModuleOptions = linkModule
|
||||
}
|
||||
|
||||
for (const injectedDependency of Object.keys(injectedDependencies)) {
|
||||
sharedContainer_.register({
|
||||
[injectedDependency]: asValue(injectedDependencies[injectedDependency]),
|
||||
})
|
||||
}
|
||||
|
||||
const allModules = await loadModules(
|
||||
modules,
|
||||
sharedContainer_,
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode
|
||||
)
|
||||
|
||||
if (loaderOnly) {
|
||||
return {
|
||||
onApplicationShutdown,
|
||||
onApplicationPrepareShutdown,
|
||||
modules: allModules,
|
||||
link: undefined,
|
||||
query: async () => {
|
||||
throw new Error("Querying not allowed in loaderOnly mode")
|
||||
},
|
||||
runMigrations: async () => {
|
||||
throw new Error("Migrations not allowed in loaderOnly mode")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Share Event bus with link modules
|
||||
injectedDependencies[ModuleRegistrationName.EVENT_BUS] =
|
||||
sharedContainer_.resolve(ModuleRegistrationName.EVENT_BUS, {
|
||||
allowUnregistered: true,
|
||||
})
|
||||
|
||||
const { remoteLink, runMigrations: linkModuleMigration } =
|
||||
await initializeLinks({
|
||||
config: linkModuleOptions,
|
||||
linkModules,
|
||||
injectedDependencies,
|
||||
moduleExports: isMedusaModule(linkModule) ? linkModule : undefined,
|
||||
})
|
||||
|
||||
const loadedSchema = getLoadedSchema()
|
||||
const { schema, notFound } = cleanAndMergeSchema(loadedSchema)
|
||||
|
||||
const remoteQuery = new RemoteQuery({
|
||||
servicesConfig,
|
||||
customRemoteFetchData: remoteFetchData,
|
||||
})
|
||||
|
||||
const query = async (
|
||||
query: string | RemoteJoinerQuery | object,
|
||||
variables?: Record<string, unknown>,
|
||||
options?: RemoteJoinerOptions
|
||||
) => {
|
||||
return await remoteQuery.query(query, variables, options)
|
||||
}
|
||||
|
||||
const runMigrations: RunMigrationFn = async (
|
||||
linkModuleOptions
|
||||
): Promise<void> => {
|
||||
for (const moduleName of Object.keys(allModules)) {
|
||||
const moduleResolution = MedusaModule.getModuleResolutions(moduleName)
|
||||
|
||||
if (!moduleResolution.options?.database) {
|
||||
moduleResolution.options ??= {}
|
||||
moduleResolution.options.database = {
|
||||
...(sharedResourcesConfig?.database ?? {}),
|
||||
}
|
||||
}
|
||||
|
||||
await MedusaModule.migrateUp(
|
||||
moduleResolution.definition.key,
|
||||
moduleResolution.resolutionPath as string,
|
||||
moduleResolution.options,
|
||||
moduleResolution.moduleExports
|
||||
)
|
||||
}
|
||||
|
||||
const linkModuleOpt = { ...(linkModuleOptions ?? {}) }
|
||||
linkModuleOpt.database ??= {
|
||||
...(sharedResourcesConfig?.database ?? {}),
|
||||
}
|
||||
|
||||
linkModuleMigration &&
|
||||
(await linkModuleMigration({
|
||||
options: linkModuleOpt,
|
||||
injectedDependencies,
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
onApplicationShutdown,
|
||||
onApplicationPrepareShutdown,
|
||||
modules: allModules,
|
||||
link: remoteLink,
|
||||
query,
|
||||
entitiesMap: schema.getTypeMap(),
|
||||
notFound,
|
||||
runMigrations,
|
||||
}
|
||||
}
|
||||
|
||||
export async function MedusaApp(
|
||||
options: MedusaAppOptions = {}
|
||||
): Promise<MedusaAppOutput> {
|
||||
try {
|
||||
return await MedusaApp_(options)
|
||||
} finally {
|
||||
MedusaModule.onApplicationStart(options.onApplicationStartCb)
|
||||
}
|
||||
}
|
||||
|
||||
export async function MedusaAppMigrateUp(
|
||||
options: MedusaAppOptions = {}
|
||||
): Promise<void> {
|
||||
const migrationOnly = true
|
||||
|
||||
const { runMigrations } = await MedusaApp_({
|
||||
...options,
|
||||
migrationOnly,
|
||||
})
|
||||
|
||||
await runMigrations().finally(MedusaModule.clearInstances)
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
IModuleService,
|
||||
InternalModuleDeclaration,
|
||||
LinkModuleDefinition,
|
||||
LoadedModule,
|
||||
MedusaContainer,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
ModuleBootstrapDeclaration,
|
||||
ModuleDefinition,
|
||||
ModuleExports,
|
||||
ModuleJoinerConfig,
|
||||
ModuleResolution,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
createMedusaContainer,
|
||||
promiseAll,
|
||||
simpleHash,
|
||||
stringifyCircular,
|
||||
} from "@medusajs/utils"
|
||||
import { EOL } from "os"
|
||||
import {
|
||||
moduleLoader,
|
||||
registerMedusaLinkModule,
|
||||
registerMedusaModule,
|
||||
} from "./loaders"
|
||||
|
||||
import { asValue } from "awilix"
|
||||
import { loadModuleMigrations } from "./loaders/utils"
|
||||
|
||||
const logger: any = {
|
||||
log: (a) => console.log(a),
|
||||
info: (a) => console.log(a),
|
||||
warn: (a) => console.warn(a),
|
||||
error: (a) => console.error(a),
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface MedusaModule {
|
||||
getLoadedModules(
|
||||
aliases?: Map<string, string>
|
||||
): { [key: string]: LoadedModule }[]
|
||||
getModuleInstance(moduleKey: string, alias?: string): LoadedModule
|
||||
}
|
||||
}
|
||||
|
||||
type ModuleAlias = {
|
||||
key: string
|
||||
hash: string
|
||||
isLink: boolean
|
||||
alias?: string
|
||||
main?: boolean
|
||||
}
|
||||
|
||||
export type ModuleBootstrapOptions = {
|
||||
moduleKey: string
|
||||
defaultPath: string
|
||||
declaration?: ModuleBootstrapDeclaration
|
||||
moduleExports?: ModuleExports
|
||||
sharedContainer?: MedusaContainer
|
||||
moduleDefinition?: ModuleDefinition
|
||||
injectedDependencies?: Record<string, any>
|
||||
/**
|
||||
* In this mode, all instances are partially loaded, meaning that the module will not be fully loaded and the services will not be available.
|
||||
* Don't forget to clear the instances (MedusaModule.clearInstances()) after the migration are done.
|
||||
*/
|
||||
migrationOnly?: boolean
|
||||
/**
|
||||
* Forces the modules bootstrapper to only run the modules loaders and return prematurely. This
|
||||
* is meant for modules that have data loader. In a test env, in order to clear all data
|
||||
* and load them back, we need to run those loader again
|
||||
*/
|
||||
loaderOnly?: boolean
|
||||
workerMode?: "shared" | "worker" | "server"
|
||||
}
|
||||
|
||||
export type LinkModuleBootstrapOptions = {
|
||||
definition: LinkModuleDefinition
|
||||
declaration?: InternalModuleDeclaration
|
||||
moduleExports?: ModuleExports
|
||||
injectedDependencies?: Record<string, any>
|
||||
}
|
||||
|
||||
export class MedusaModule {
|
||||
private static instances_: Map<string, { [key: string]: IModuleService }> =
|
||||
new Map()
|
||||
private static modules_: Map<string, ModuleAlias[]> = new Map()
|
||||
private static loading_: Map<string, Promise<any>> = new Map()
|
||||
private static joinerConfig_: Map<string, ModuleJoinerConfig> = new Map()
|
||||
private static moduleResolutions_: Map<string, ModuleResolution> = new Map()
|
||||
|
||||
public static getLoadedModules(
|
||||
aliases?: Map<string, string>
|
||||
): { [key: string]: LoadedModule }[] {
|
||||
return [...MedusaModule.modules_.entries()].map(([key]) => {
|
||||
if (aliases?.has(key)) {
|
||||
return MedusaModule.getModuleInstance(key, aliases.get(key))
|
||||
}
|
||||
|
||||
return MedusaModule.getModuleInstance(key)
|
||||
})
|
||||
}
|
||||
|
||||
public static onApplicationStart(onApplicationStartCb?: () => void): void {
|
||||
for (const instances of MedusaModule.instances_.values()) {
|
||||
for (const instance of Object.values(instances) as IModuleService[]) {
|
||||
if (instance?.__hooks) {
|
||||
instance.__hooks?.onApplicationStart
|
||||
?.bind(instance)()
|
||||
.then(() => {
|
||||
onApplicationStartCb?.()
|
||||
})
|
||||
.catch(() => {
|
||||
// The module should handle this and log it
|
||||
return void 0
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static async onApplicationShutdown(): Promise<void> {
|
||||
await promiseAll(
|
||||
[...MedusaModule.instances_.values()]
|
||||
.map((instances) => {
|
||||
return Object.values(instances).map((instance: IModuleService) => {
|
||||
return instance.__hooks?.onApplicationShutdown
|
||||
?.bind(instance)()
|
||||
.catch(() => {
|
||||
// The module should handle this and log it
|
||||
return void 0
|
||||
})
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
)
|
||||
}
|
||||
|
||||
public static async onApplicationPrepareShutdown(): Promise<void> {
|
||||
await promiseAll(
|
||||
[...MedusaModule.instances_.values()]
|
||||
.map((instances) => {
|
||||
return Object.values(instances).map((instance: IModuleService) => {
|
||||
return instance.__hooks?.onApplicationPrepareShutdown
|
||||
?.bind(instance)()
|
||||
.catch(() => {
|
||||
// The module should handle this and log it
|
||||
return void 0
|
||||
})
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
)
|
||||
}
|
||||
|
||||
public static clearInstances(): void {
|
||||
MedusaModule.instances_.clear()
|
||||
MedusaModule.modules_.clear()
|
||||
MedusaModule.joinerConfig_.clear()
|
||||
MedusaModule.moduleResolutions_.clear()
|
||||
}
|
||||
|
||||
public static isInstalled(moduleKey: string, alias?: string): boolean {
|
||||
if (alias) {
|
||||
return (
|
||||
MedusaModule.modules_.has(moduleKey) &&
|
||||
MedusaModule.modules_.get(moduleKey)!.some((m) => m.alias === alias)
|
||||
)
|
||||
}
|
||||
|
||||
return MedusaModule.modules_.has(moduleKey)
|
||||
}
|
||||
|
||||
public static getJoinerConfig(moduleKey: string): ModuleJoinerConfig {
|
||||
return MedusaModule.joinerConfig_.get(moduleKey)!
|
||||
}
|
||||
|
||||
public static getAllJoinerConfigs(): ModuleJoinerConfig[] {
|
||||
return [...MedusaModule.joinerConfig_.values()]
|
||||
}
|
||||
|
||||
public static getModuleResolutions(moduleKey: string): ModuleResolution {
|
||||
return MedusaModule.moduleResolutions_.get(moduleKey)!
|
||||
}
|
||||
|
||||
public static getAllModuleResolutions(): ModuleResolution[] {
|
||||
return [...MedusaModule.moduleResolutions_.values()]
|
||||
}
|
||||
|
||||
public static setModuleResolution(
|
||||
moduleKey: string,
|
||||
resolution: ModuleResolution
|
||||
): ModuleResolution {
|
||||
MedusaModule.moduleResolutions_.set(moduleKey, resolution)
|
||||
|
||||
return resolution
|
||||
}
|
||||
|
||||
public static setJoinerConfig(
|
||||
moduleKey: string,
|
||||
config: ModuleJoinerConfig
|
||||
): ModuleJoinerConfig {
|
||||
MedusaModule.joinerConfig_.set(moduleKey, config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
public static getModuleInstance(
|
||||
moduleKey: string,
|
||||
alias?: string
|
||||
): any | undefined {
|
||||
if (!MedusaModule.modules_.has(moduleKey)) {
|
||||
return
|
||||
}
|
||||
|
||||
let mod
|
||||
const modules = MedusaModule.modules_.get(moduleKey)!
|
||||
if (alias) {
|
||||
mod = modules.find((m) => m.alias === alias)
|
||||
|
||||
return MedusaModule.instances_.get(mod?.hash)
|
||||
}
|
||||
|
||||
mod = modules.find((m) => m.main) ?? modules[0]
|
||||
|
||||
return MedusaModule.instances_.get(mod?.hash)
|
||||
}
|
||||
|
||||
private static registerModule(
|
||||
moduleKey: string,
|
||||
loadedModule: ModuleAlias
|
||||
): void {
|
||||
if (!MedusaModule.modules_.has(moduleKey)) {
|
||||
MedusaModule.modules_.set(moduleKey, [])
|
||||
}
|
||||
|
||||
const modules = MedusaModule.modules_.get(moduleKey)!
|
||||
|
||||
if (modules.some((m) => m.alias === loadedModule.alias)) {
|
||||
throw new Error(
|
||||
`Module ${moduleKey} already registed as '${loadedModule.alias}'. Please choose a different alias.`
|
||||
)
|
||||
}
|
||||
|
||||
if (loadedModule.main) {
|
||||
if (modules.some((m) => m.main)) {
|
||||
throw new Error(`Module ${moduleKey} already have a 'main' registered.`)
|
||||
}
|
||||
}
|
||||
|
||||
modules.push(loadedModule)
|
||||
MedusaModule.modules_.set(moduleKey, modules!)
|
||||
}
|
||||
|
||||
public static async bootstrap<T>({
|
||||
moduleKey,
|
||||
defaultPath,
|
||||
declaration,
|
||||
moduleExports,
|
||||
sharedContainer,
|
||||
moduleDefinition,
|
||||
injectedDependencies,
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
workerMode,
|
||||
}: ModuleBootstrapOptions): Promise<{
|
||||
[key: string]: T
|
||||
}> {
|
||||
const hashKey = simpleHash(
|
||||
stringifyCircular({ moduleKey, defaultPath, declaration })
|
||||
)
|
||||
|
||||
if (!loaderOnly && MedusaModule.instances_.has(hashKey)) {
|
||||
return MedusaModule.instances_.get(hashKey)! as {
|
||||
[key: string]: T
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaderOnly && MedusaModule.loading_.has(hashKey)) {
|
||||
return MedusaModule.loading_.get(hashKey)
|
||||
}
|
||||
|
||||
let finishLoading: any
|
||||
let errorLoading: any
|
||||
|
||||
const loadingPromise = new Promise((resolve, reject) => {
|
||||
finishLoading = resolve
|
||||
errorLoading = reject
|
||||
})
|
||||
|
||||
if (!loaderOnly) {
|
||||
MedusaModule.loading_.set(hashKey, loadingPromise)
|
||||
}
|
||||
|
||||
let modDeclaration =
|
||||
declaration ??
|
||||
({} as InternalModuleDeclaration | ExternalModuleDeclaration)
|
||||
|
||||
if (declaration?.scope !== MODULE_SCOPE.EXTERNAL) {
|
||||
modDeclaration = {
|
||||
scope: declaration?.scope || MODULE_SCOPE.INTERNAL,
|
||||
resources: declaration?.resources || MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: defaultPath,
|
||||
options: declaration?.options ?? declaration,
|
||||
alias: declaration?.alias,
|
||||
main: declaration?.main,
|
||||
worker_mode: workerMode,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Only do that while legacy modules sharing the manager exists then remove the ternary in favor of createMedusaContainer({}, globalContainer)
|
||||
const container =
|
||||
modDeclaration.scope === MODULE_SCOPE.INTERNAL &&
|
||||
modDeclaration.resources === MODULE_RESOURCE_TYPE.SHARED
|
||||
? sharedContainer ?? createMedusaContainer()
|
||||
: createMedusaContainer({}, sharedContainer)
|
||||
|
||||
if (injectedDependencies) {
|
||||
for (const service in injectedDependencies) {
|
||||
container.register(service, asValue(injectedDependencies[service]))
|
||||
if (!container.hasRegistration(service)) {
|
||||
container.register(service, asValue(injectedDependencies[service]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const moduleResolutions = registerMedusaModule(
|
||||
moduleKey,
|
||||
modDeclaration!,
|
||||
moduleExports,
|
||||
moduleDefinition
|
||||
)
|
||||
|
||||
const logger_ =
|
||||
container.resolve("logger", { allowUnregistered: true }) ?? logger
|
||||
|
||||
try {
|
||||
await moduleLoader({
|
||||
container,
|
||||
moduleResolutions,
|
||||
logger: logger_,
|
||||
migrationOnly,
|
||||
loaderOnly,
|
||||
})
|
||||
} catch (err) {
|
||||
errorLoading(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
const services = {}
|
||||
|
||||
if (loaderOnly) {
|
||||
finishLoading(services)
|
||||
return services
|
||||
}
|
||||
|
||||
for (const resolution of Object.values(
|
||||
moduleResolutions
|
||||
) as ModuleResolution[]) {
|
||||
const keyName = resolution.definition.key
|
||||
const registrationName = resolution.definition.registrationName
|
||||
|
||||
services[keyName] = container.resolve(registrationName)
|
||||
services[keyName].__definition = resolution.definition
|
||||
|
||||
if (resolution.definition.isQueryable) {
|
||||
const joinerConfig: ModuleJoinerConfig = await services[
|
||||
keyName
|
||||
].__joinerConfig()
|
||||
|
||||
if (!joinerConfig.primaryKeys) {
|
||||
logger_.warn(
|
||||
`Primary keys are not defined by the module ${keyName}. Setting default primary key to 'id'${EOL}`
|
||||
)
|
||||
|
||||
joinerConfig.primaryKeys = ["id"]
|
||||
}
|
||||
|
||||
services[keyName].__joinerConfig = joinerConfig
|
||||
MedusaModule.setJoinerConfig(keyName, joinerConfig)
|
||||
}
|
||||
|
||||
MedusaModule.setModuleResolution(keyName, resolution)
|
||||
|
||||
MedusaModule.registerModule(keyName, {
|
||||
key: keyName,
|
||||
hash: hashKey,
|
||||
alias: modDeclaration.alias ?? hashKey,
|
||||
main: !!modDeclaration.main,
|
||||
isLink: false,
|
||||
})
|
||||
}
|
||||
|
||||
MedusaModule.instances_.set(hashKey, services)
|
||||
finishLoading(services)
|
||||
MedusaModule.loading_.delete(hashKey)
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
public static async bootstrapLink({
|
||||
definition,
|
||||
declaration,
|
||||
moduleExports,
|
||||
injectedDependencies,
|
||||
}: LinkModuleBootstrapOptions): Promise<{
|
||||
[key: string]: unknown
|
||||
}> {
|
||||
const moduleKey = definition.key
|
||||
const hashKey = simpleHash(stringifyCircular({ moduleKey, declaration }))
|
||||
|
||||
if (MedusaModule.instances_.has(hashKey)) {
|
||||
return { [moduleKey]: MedusaModule.instances_.get(hashKey) }
|
||||
}
|
||||
|
||||
if (MedusaModule.loading_.has(hashKey)) {
|
||||
return MedusaModule.loading_.get(hashKey)
|
||||
}
|
||||
|
||||
let finishLoading: any
|
||||
let errorLoading: any
|
||||
MedusaModule.loading_.set(
|
||||
hashKey,
|
||||
new Promise((resolve, reject) => {
|
||||
finishLoading = resolve
|
||||
errorLoading = reject
|
||||
})
|
||||
)
|
||||
|
||||
let modDeclaration =
|
||||
declaration ?? ({} as Partial<InternalModuleDeclaration>)
|
||||
|
||||
const moduleDefinition: ModuleDefinition = {
|
||||
key: definition.key,
|
||||
registrationName: definition.key,
|
||||
dependencies: definition.dependencies,
|
||||
defaultPackage: "",
|
||||
label: definition.label,
|
||||
isRequired: false,
|
||||
isQueryable: true,
|
||||
defaultModuleDeclaration: definition.defaultModuleDeclaration,
|
||||
}
|
||||
|
||||
modDeclaration = {
|
||||
resolve: "",
|
||||
options: declaration,
|
||||
alias: declaration?.alias,
|
||||
main: declaration?.main,
|
||||
}
|
||||
|
||||
const container = createMedusaContainer()
|
||||
|
||||
if (injectedDependencies) {
|
||||
for (const service in injectedDependencies) {
|
||||
container.register(service, asValue(injectedDependencies[service]))
|
||||
}
|
||||
}
|
||||
|
||||
const moduleResolutions = registerMedusaLinkModule(
|
||||
moduleDefinition,
|
||||
modDeclaration as InternalModuleDeclaration,
|
||||
moduleExports
|
||||
)
|
||||
|
||||
const logger_ =
|
||||
container.resolve("logger", { allowUnregistered: true }) ?? logger
|
||||
|
||||
try {
|
||||
await moduleLoader({
|
||||
container,
|
||||
moduleResolutions,
|
||||
logger: logger_,
|
||||
})
|
||||
} catch (err) {
|
||||
errorLoading(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
const services = {}
|
||||
|
||||
for (const resolution of Object.values(
|
||||
moduleResolutions
|
||||
) as ModuleResolution[]) {
|
||||
const keyName = resolution.definition.key
|
||||
const registrationName = resolution.definition.registrationName
|
||||
|
||||
services[keyName] = container.resolve(registrationName)
|
||||
services[keyName].__definition = resolution.definition
|
||||
|
||||
if (resolution.definition.isQueryable) {
|
||||
const joinerConfig: ModuleJoinerConfig = await services[
|
||||
keyName
|
||||
].__joinerConfig()
|
||||
|
||||
services[keyName].__joinerConfig = joinerConfig
|
||||
MedusaModule.setJoinerConfig(keyName, joinerConfig)
|
||||
|
||||
if (!joinerConfig.isLink) {
|
||||
throw new Error(
|
||||
"MedusaModule.bootstrapLink must be used only for Link Modules"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MedusaModule.setModuleResolution(keyName, resolution)
|
||||
MedusaModule.registerModule(keyName, {
|
||||
key: keyName,
|
||||
hash: hashKey,
|
||||
alias: modDeclaration.alias ?? hashKey,
|
||||
main: !!modDeclaration.main,
|
||||
isLink: true,
|
||||
})
|
||||
}
|
||||
|
||||
MedusaModule.instances_.set(hashKey, services)
|
||||
finishLoading(services)
|
||||
MedusaModule.loading_.delete(hashKey)
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
public static async migrateUp(
|
||||
moduleKey: string,
|
||||
modulePath: string,
|
||||
options?: Record<string, any>,
|
||||
moduleExports?: ModuleExports
|
||||
): Promise<void> {
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
})
|
||||
|
||||
for (const mod in moduleResolutions) {
|
||||
const [migrateUp] = await loadModuleMigrations(
|
||||
moduleResolutions[mod],
|
||||
moduleExports
|
||||
)
|
||||
|
||||
if (typeof migrateUp === "function") {
|
||||
await migrateUp({
|
||||
options,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async migrateDown(
|
||||
moduleKey: string,
|
||||
modulePath: string,
|
||||
options?: Record<string, any>,
|
||||
moduleExports?: ModuleExports
|
||||
): Promise<void> {
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
})
|
||||
|
||||
for (const mod in moduleResolutions) {
|
||||
const [, migrateDown] = await loadModuleMigrations(
|
||||
moduleResolutions[mod],
|
||||
moduleExports
|
||||
)
|
||||
|
||||
if (typeof migrateDown === "function") {
|
||||
await migrateDown({
|
||||
options,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.MedusaModule ??= MedusaModule
|
||||
exports.MedusaModule = global.MedusaModule
|
||||
@@ -0,0 +1,456 @@
|
||||
import {
|
||||
ILinkModule,
|
||||
LoadedModule,
|
||||
ModuleJoinerRelationship,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { isObject, promiseAll, toPascalCase } from "@medusajs/utils"
|
||||
import { Modules } from "./definitions"
|
||||
import { MedusaModule } from "./medusa-module"
|
||||
import { linkingErrorMessage } from "./utils/linking-error"
|
||||
|
||||
export type DeleteEntityInput = {
|
||||
[moduleName: string | Modules]: Record<string, string | string[]>
|
||||
}
|
||||
export type RestoreEntityInput = DeleteEntityInput
|
||||
|
||||
type LinkDefinition = {
|
||||
[moduleName: string]: {
|
||||
[fieldName: string]: string
|
||||
}
|
||||
} & {
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
type RemoteRelationship = ModuleJoinerRelationship & {
|
||||
isPrimary: boolean
|
||||
isForeign: boolean
|
||||
}
|
||||
|
||||
type LoadedLinkModule = LoadedModule & ILinkModule
|
||||
type DeleteEntities = { [key: string]: string[] }
|
||||
type RemovedIds = {
|
||||
[serviceName: string]: DeleteEntities
|
||||
}
|
||||
type RestoredIds = RemovedIds
|
||||
|
||||
type CascadeError = {
|
||||
serviceName: string
|
||||
method: String
|
||||
args: any
|
||||
error: Error
|
||||
}
|
||||
|
||||
export class RemoteLink {
|
||||
private modulesMap: Map<string, LoadedLinkModule> = new Map()
|
||||
private relationsPairs: Map<string, LoadedLinkModule> = new Map()
|
||||
private relations: Map<string, Map<string, RemoteRelationship[]>> = new Map()
|
||||
|
||||
constructor(modulesLoaded?: LoadedModule[]) {
|
||||
if (!modulesLoaded?.length) {
|
||||
modulesLoaded = MedusaModule.getLoadedModules().map(
|
||||
(mod) => Object.values(mod)[0]
|
||||
)
|
||||
}
|
||||
|
||||
for (const mod of modulesLoaded) {
|
||||
this.addModule(mod)
|
||||
}
|
||||
}
|
||||
|
||||
public addModule(mod: LoadedModule): void {
|
||||
if (!mod.__definition.isQueryable || mod.__joinerConfig.isReadOnlyLink) {
|
||||
return
|
||||
}
|
||||
|
||||
const joinerConfig = mod.__joinerConfig
|
||||
|
||||
const serviceName = joinerConfig.isLink
|
||||
? joinerConfig.serviceName!
|
||||
: mod.__definition.key
|
||||
|
||||
if (this.modulesMap.has(serviceName)) {
|
||||
throw new Error(
|
||||
`Duplicated instance of module ${serviceName} is not allowed.`
|
||||
)
|
||||
}
|
||||
|
||||
if (joinerConfig.relationships?.length) {
|
||||
if (joinerConfig.isLink) {
|
||||
const [primary, foreign] = joinerConfig.relationships
|
||||
const key = [
|
||||
primary.serviceName,
|
||||
primary.foreignKey,
|
||||
foreign.serviceName,
|
||||
foreign.foreignKey,
|
||||
].join("-")
|
||||
this.relationsPairs.set(key, mod as unknown as LoadedLinkModule)
|
||||
}
|
||||
for (const relationship of joinerConfig.relationships) {
|
||||
if (joinerConfig.isLink && !relationship.deleteCascade) {
|
||||
continue
|
||||
}
|
||||
|
||||
this.addRelationship(serviceName, {
|
||||
...relationship,
|
||||
isPrimary: false,
|
||||
isForeign: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (joinerConfig.extends?.length) {
|
||||
for (const service of joinerConfig.extends) {
|
||||
const relationship = service.relationship
|
||||
this.addRelationship(service.serviceName, {
|
||||
...relationship,
|
||||
serviceName: serviceName,
|
||||
isPrimary: true,
|
||||
isForeign: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.modulesMap.set(serviceName, mod as unknown as LoadedLinkModule)
|
||||
}
|
||||
|
||||
private addRelationship(
|
||||
serviceName: string,
|
||||
relationship: RemoteRelationship
|
||||
): void {
|
||||
const { primaryKey, foreignKey } = relationship
|
||||
|
||||
if (!this.relations.has(serviceName)) {
|
||||
this.relations.set(serviceName, new Map())
|
||||
}
|
||||
|
||||
const key = relationship.isPrimary ? primaryKey : foreignKey
|
||||
const serviceMap = this.relations.get(serviceName)!
|
||||
if (!serviceMap.has(key)) {
|
||||
serviceMap.set(key, [])
|
||||
}
|
||||
|
||||
serviceMap.get(key)!.push(relationship)
|
||||
}
|
||||
|
||||
getLinkModule(
|
||||
moduleA: string,
|
||||
moduleAKey: string,
|
||||
moduleB: string,
|
||||
moduleBKey: string
|
||||
) {
|
||||
const key = [moduleA, moduleAKey, moduleB, moduleBKey].join("-")
|
||||
return this.relationsPairs.get(key)
|
||||
}
|
||||
|
||||
getRelationships(): Map<string, Map<string, RemoteRelationship[]>> {
|
||||
return this.relations
|
||||
}
|
||||
|
||||
private getLinkableKeys(mod: LoadedLinkModule) {
|
||||
return (
|
||||
(mod.__joinerConfig.linkableKeys &&
|
||||
Object.keys(mod.__joinerConfig.linkableKeys)) ||
|
||||
mod.__joinerConfig.primaryKeys ||
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
private async executeCascade(
|
||||
removedServices: DeleteEntityInput,
|
||||
method: "softDelete" | "restore"
|
||||
): Promise<[CascadeError[] | null, RemovedIds]> {
|
||||
const removedIds: RemovedIds = {}
|
||||
const returnIdsList: RemovedIds = {}
|
||||
const processedIds: Record<string, Set<string>> = {}
|
||||
|
||||
const services = Object.keys(removedServices).map((serviceName) => {
|
||||
const deleteKeys = {}
|
||||
|
||||
for (const field in removedServices[serviceName]) {
|
||||
deleteKeys[field] = Array.isArray(removedServices[serviceName][field])
|
||||
? removedServices[serviceName][field]
|
||||
: [removedServices[serviceName][field]]
|
||||
}
|
||||
|
||||
return { serviceName, deleteKeys }
|
||||
})
|
||||
|
||||
const errors: CascadeError[] = []
|
||||
const cascade = async (
|
||||
services: { serviceName: string; deleteKeys: DeleteEntities }[],
|
||||
isCascading: boolean = false
|
||||
): Promise<RemovedIds> => {
|
||||
if (errors.length) {
|
||||
return returnIdsList
|
||||
}
|
||||
|
||||
const servicePromises = services.map(async (serviceInfo) => {
|
||||
const serviceRelations = this.relations.get(serviceInfo.serviceName)!
|
||||
|
||||
if (!serviceRelations) {
|
||||
return
|
||||
}
|
||||
|
||||
const values = serviceInfo.deleteKeys
|
||||
|
||||
const deletePromises: Promise<void>[] = []
|
||||
|
||||
for (const field in values) {
|
||||
const relatedServices = serviceRelations.get(field)
|
||||
|
||||
if (!relatedServices || !values[field]?.length) {
|
||||
continue
|
||||
}
|
||||
|
||||
const relatedServicesPromises = relatedServices.map(
|
||||
async (relatedService) => {
|
||||
const { serviceName, primaryKey, args } = relatedService
|
||||
const processedHash = `${serviceName}-${primaryKey}`
|
||||
|
||||
if (!processedIds[processedHash]) {
|
||||
processedIds[processedHash] = new Set()
|
||||
}
|
||||
|
||||
const unprocessedIds = values[field].filter(
|
||||
(id) => !processedIds[processedHash].has(id)
|
||||
)
|
||||
|
||||
if (!unprocessedIds.length) {
|
||||
return
|
||||
}
|
||||
|
||||
unprocessedIds.forEach((id) => {
|
||||
processedIds[processedHash].add(id)
|
||||
})
|
||||
|
||||
let cascadeDelKeys: DeleteEntities = {}
|
||||
cascadeDelKeys[primaryKey] = unprocessedIds
|
||||
const service: ILinkModule = this.modulesMap.get(serviceName)!
|
||||
|
||||
const returnFields = this.getLinkableKeys(
|
||||
service as LoadedLinkModule
|
||||
)
|
||||
|
||||
let deletedEntities: Record<string, string[]> = {}
|
||||
|
||||
try {
|
||||
if (args?.methodSuffix) {
|
||||
method += toPascalCase(args.methodSuffix)
|
||||
}
|
||||
|
||||
const removed = await service[method](cascadeDelKeys, {
|
||||
returnLinkableKeys: returnFields,
|
||||
})
|
||||
|
||||
deletedEntities = removed as Record<string, string[]>
|
||||
} catch (error) {
|
||||
errors.push({
|
||||
serviceName,
|
||||
method,
|
||||
args: cascadeDelKeys,
|
||||
error: JSON.parse(
|
||||
JSON.stringify(error, Object.getOwnPropertyNames(error))
|
||||
),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (Object.keys(deletedEntities).length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
removedIds[serviceName] = {
|
||||
...deletedEntities,
|
||||
}
|
||||
|
||||
if (!isCascading) {
|
||||
returnIdsList[serviceName] = {
|
||||
...deletedEntities,
|
||||
}
|
||||
} else {
|
||||
const [mainKey] = returnFields
|
||||
|
||||
if (!returnIdsList[serviceName]) {
|
||||
returnIdsList[serviceName] = {}
|
||||
}
|
||||
if (!returnIdsList[serviceName][mainKey]) {
|
||||
returnIdsList[serviceName][mainKey] = []
|
||||
}
|
||||
|
||||
returnIdsList[serviceName][mainKey] = [
|
||||
...new Set(
|
||||
returnIdsList[serviceName][mainKey].concat(
|
||||
deletedEntities[mainKey]
|
||||
)
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
Object.keys(deletedEntities).forEach((key) => {
|
||||
deletedEntities[key].forEach((id) => {
|
||||
const hash = `${serviceName}-${key}`
|
||||
if (!processedIds[hash]) {
|
||||
processedIds[hash] = new Set()
|
||||
}
|
||||
|
||||
processedIds[hash].add(id)
|
||||
})
|
||||
})
|
||||
|
||||
await cascade(
|
||||
[
|
||||
{
|
||||
serviceName: serviceName,
|
||||
deleteKeys: deletedEntities as DeleteEntities,
|
||||
},
|
||||
],
|
||||
true
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
deletePromises.push(...relatedServicesPromises)
|
||||
}
|
||||
|
||||
await promiseAll(deletePromises)
|
||||
})
|
||||
|
||||
await promiseAll(servicePromises)
|
||||
return returnIdsList
|
||||
}
|
||||
|
||||
const result = await cascade(services)
|
||||
|
||||
return [errors.length ? errors : null, result]
|
||||
}
|
||||
|
||||
async create(link: LinkDefinition | LinkDefinition[]): Promise<unknown[]> {
|
||||
const allLinks = Array.isArray(link) ? link : [link]
|
||||
const serviceLinks = new Map<
|
||||
string,
|
||||
[string | string[], string, Record<string, unknown>?][]
|
||||
>()
|
||||
|
||||
for (const rel of allLinks) {
|
||||
const extraFields = rel.data
|
||||
delete rel.data
|
||||
|
||||
const mods = Object.keys(rel)
|
||||
if (mods.length > 2) {
|
||||
throw new Error(`Only two modules can be linked.`)
|
||||
}
|
||||
|
||||
const [moduleA, moduleB] = mods
|
||||
const pk = Object.keys(rel[moduleA])
|
||||
const moduleAKey = pk.join(",")
|
||||
const moduleBKey = Object.keys(rel[moduleB]).join(",")
|
||||
|
||||
const service = this.getLinkModule(
|
||||
moduleA,
|
||||
moduleAKey,
|
||||
moduleB,
|
||||
moduleBKey
|
||||
)
|
||||
|
||||
if (!service) {
|
||||
throw new Error(
|
||||
linkingErrorMessage({
|
||||
moduleA,
|
||||
moduleAKey,
|
||||
moduleB,
|
||||
moduleBKey,
|
||||
type: "link",
|
||||
})
|
||||
)
|
||||
} else if (!serviceLinks.has(service.__definition.key)) {
|
||||
serviceLinks.set(service.__definition.key, [])
|
||||
}
|
||||
|
||||
const pkValue =
|
||||
pk.length === 1 ? rel[moduleA][pk[0]] : pk.map((k) => rel[moduleA][k])
|
||||
|
||||
const fields: unknown[] = [pkValue, rel[moduleB][moduleBKey]]
|
||||
if (isObject(extraFields)) {
|
||||
fields.push(extraFields)
|
||||
}
|
||||
|
||||
serviceLinks.get(service.__definition.key)?.push(fields as any)
|
||||
}
|
||||
|
||||
const promises: Promise<unknown[]>[] = []
|
||||
for (const [serviceName, links] of serviceLinks) {
|
||||
const service = this.modulesMap.get(serviceName)!
|
||||
promises.push(service.create(links))
|
||||
}
|
||||
|
||||
const created = await promiseAll(promises)
|
||||
return created.flat()
|
||||
}
|
||||
|
||||
async dismiss(link: LinkDefinition | LinkDefinition[]): Promise<unknown[]> {
|
||||
const allLinks = Array.isArray(link) ? link : [link]
|
||||
const serviceLinks = new Map<string, [string | string[], string][]>()
|
||||
|
||||
for (const rel of allLinks) {
|
||||
const mods = Object.keys(rel)
|
||||
if (mods.length > 2) {
|
||||
throw new Error(`Only two modules can be linked.`)
|
||||
}
|
||||
|
||||
const [moduleA, moduleB] = mods
|
||||
const pk = Object.keys(rel[moduleA])
|
||||
const moduleAKey = pk.join(",")
|
||||
const moduleBKey = Object.keys(rel[moduleB]).join(",")
|
||||
|
||||
const service = this.getLinkModule(
|
||||
moduleA,
|
||||
moduleAKey,
|
||||
moduleB,
|
||||
moduleBKey
|
||||
)
|
||||
|
||||
if (!service) {
|
||||
throw new Error(
|
||||
linkingErrorMessage({
|
||||
moduleA,
|
||||
moduleAKey,
|
||||
moduleB,
|
||||
moduleBKey,
|
||||
type: "dismiss",
|
||||
})
|
||||
)
|
||||
} else if (!serviceLinks.has(service.__definition.key)) {
|
||||
serviceLinks.set(service.__definition.key, [])
|
||||
}
|
||||
|
||||
const pkValue =
|
||||
pk.length === 1 ? rel[moduleA][pk[0]] : pk.map((k) => rel[moduleA][k])
|
||||
|
||||
serviceLinks
|
||||
.get(service.__definition.key)
|
||||
?.push([pkValue, rel[moduleB][moduleBKey]])
|
||||
}
|
||||
|
||||
const promises: Promise<unknown[]>[] = []
|
||||
for (const [serviceName, links] of serviceLinks) {
|
||||
const service = this.modulesMap.get(serviceName)!
|
||||
promises.push(service.dismiss(links))
|
||||
}
|
||||
|
||||
const created = await promiseAll(promises)
|
||||
return created.flat()
|
||||
}
|
||||
|
||||
async delete(
|
||||
removedServices: DeleteEntityInput
|
||||
): Promise<[CascadeError[] | null, RemovedIds]> {
|
||||
return await this.executeCascade(removedServices, "softDelete")
|
||||
}
|
||||
|
||||
async restore(
|
||||
removedServices: DeleteEntityInput
|
||||
): Promise<[CascadeError[] | null, RestoredIds]> {
|
||||
return await this.executeCascade(removedServices, "restore")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
import {
|
||||
RemoteFetchDataCallback,
|
||||
RemoteJoiner,
|
||||
toRemoteJoinerQuery,
|
||||
} from "@medusajs/orchestration"
|
||||
import {
|
||||
JoinerArgument,
|
||||
JoinerRelationship,
|
||||
JoinerServiceConfig,
|
||||
LoadedModule,
|
||||
ModuleJoinerConfig,
|
||||
RemoteExpandProperty,
|
||||
RemoteJoinerOptions,
|
||||
RemoteJoinerQuery,
|
||||
RemoteNestedExpands,
|
||||
} from "@medusajs/types"
|
||||
import { isString, toPascalCase } from "@medusajs/utils"
|
||||
import { MedusaModule } from "./medusa-module"
|
||||
|
||||
export class RemoteQuery {
|
||||
private remoteJoiner: RemoteJoiner
|
||||
private modulesMap: Map<string, LoadedModule> = new Map()
|
||||
private customRemoteFetchData?: RemoteFetchDataCallback
|
||||
|
||||
constructor({
|
||||
modulesLoaded,
|
||||
customRemoteFetchData,
|
||||
servicesConfig = [],
|
||||
}: {
|
||||
modulesLoaded?: LoadedModule[]
|
||||
customRemoteFetchData?: RemoteFetchDataCallback
|
||||
servicesConfig?: ModuleJoinerConfig[]
|
||||
}) {
|
||||
const servicesConfig_ = [...servicesConfig]
|
||||
|
||||
if (!modulesLoaded?.length) {
|
||||
modulesLoaded = MedusaModule.getLoadedModules().map(
|
||||
(mod) => Object.values(mod)[0]
|
||||
)
|
||||
}
|
||||
|
||||
for (const mod of modulesLoaded) {
|
||||
if (!mod.__definition.isQueryable) {
|
||||
continue
|
||||
}
|
||||
|
||||
const serviceName = mod.__definition.key
|
||||
|
||||
if (this.modulesMap.has(serviceName)) {
|
||||
throw new Error(
|
||||
`Duplicated instance of module ${serviceName} is not allowed.`
|
||||
)
|
||||
}
|
||||
|
||||
this.modulesMap.set(serviceName, mod)
|
||||
servicesConfig_!.push(mod.__joinerConfig)
|
||||
}
|
||||
|
||||
this.customRemoteFetchData = customRemoteFetchData
|
||||
|
||||
this.remoteJoiner = new RemoteJoiner(
|
||||
servicesConfig_ as JoinerServiceConfig[],
|
||||
this.remoteFetchData.bind(this),
|
||||
{ autoCreateServiceNameAlias: false }
|
||||
)
|
||||
}
|
||||
|
||||
public setFetchDataCallback(
|
||||
remoteFetchData: (
|
||||
expand: RemoteExpandProperty,
|
||||
keyField: string,
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: any
|
||||
) => Promise<{
|
||||
data: unknown[] | { [path: string]: unknown[] }
|
||||
path?: string
|
||||
}>
|
||||
): void {
|
||||
this.remoteJoiner.setFetchDataCallback(remoteFetchData)
|
||||
}
|
||||
|
||||
public static getAllFieldsAndRelations(
|
||||
expand: RemoteExpandProperty | RemoteNestedExpands[number],
|
||||
prefix = "",
|
||||
args: JoinerArgument = {} as JoinerArgument
|
||||
): {
|
||||
select?: string[]
|
||||
relations: string[]
|
||||
args: JoinerArgument
|
||||
} {
|
||||
expand = JSON.parse(JSON.stringify(expand))
|
||||
|
||||
let fields: Set<string> = new Set()
|
||||
let relations: string[] = []
|
||||
|
||||
let shouldSelectAll = false
|
||||
|
||||
for (const field of expand.fields ?? []) {
|
||||
if (field === "*") {
|
||||
shouldSelectAll = true
|
||||
break
|
||||
}
|
||||
fields.add(prefix ? `${prefix}.${field}` : field)
|
||||
}
|
||||
|
||||
args[prefix] = expand.args
|
||||
|
||||
for (const property in expand.expands ?? {}) {
|
||||
const newPrefix = prefix ? `${prefix}.${property}` : property
|
||||
|
||||
relations.push(newPrefix)
|
||||
fields.delete(newPrefix)
|
||||
|
||||
const result = RemoteQuery.getAllFieldsAndRelations(
|
||||
expand.expands![property],
|
||||
newPrefix,
|
||||
args
|
||||
)
|
||||
|
||||
result.select?.forEach(fields.add, fields)
|
||||
relations = relations.concat(result.relations)
|
||||
}
|
||||
|
||||
const allFields = Array.from(fields)
|
||||
const select =
|
||||
allFields.length && !shouldSelectAll
|
||||
? allFields
|
||||
: shouldSelectAll
|
||||
? undefined
|
||||
: []
|
||||
return { select, relations, args }
|
||||
}
|
||||
|
||||
private hasPagination(options: { [attr: string]: unknown }): boolean {
|
||||
if (!options) {
|
||||
return false
|
||||
}
|
||||
|
||||
const attrs = ["skip", "cursor"]
|
||||
return Object.keys(options).some((key) => attrs.includes(key))
|
||||
}
|
||||
|
||||
private buildPagination(options, count) {
|
||||
return {
|
||||
skip: options.skip,
|
||||
take: options.take,
|
||||
cursor: options.cursor,
|
||||
// TODO: next cursor
|
||||
count,
|
||||
}
|
||||
}
|
||||
|
||||
public async remoteFetchData(
|
||||
expand: RemoteExpandProperty,
|
||||
keyField: string,
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: JoinerRelationship
|
||||
): Promise<{
|
||||
data: unknown[] | { [path: string]: unknown }
|
||||
path?: string
|
||||
}> {
|
||||
if (this.customRemoteFetchData) {
|
||||
const resp = await this.customRemoteFetchData(expand, keyField, ids)
|
||||
if (resp !== undefined) {
|
||||
return resp
|
||||
}
|
||||
}
|
||||
|
||||
const serviceConfig = expand.serviceConfig
|
||||
const service = this.modulesMap.get(serviceConfig.serviceName)!
|
||||
|
||||
let filters = {}
|
||||
const options = {
|
||||
...RemoteQuery.getAllFieldsAndRelations(expand),
|
||||
}
|
||||
|
||||
const availableOptions = [
|
||||
"skip",
|
||||
"take",
|
||||
"limit",
|
||||
"offset",
|
||||
"cursor",
|
||||
"sort",
|
||||
"order",
|
||||
"withDeleted",
|
||||
]
|
||||
const availableOptionsAlias = new Map([
|
||||
["limit", "take"],
|
||||
["offset", "skip"],
|
||||
])
|
||||
|
||||
for (const arg of expand.args || []) {
|
||||
if (arg.name === "filters" && arg.value) {
|
||||
filters = { ...filters, ...arg.value }
|
||||
} else if (arg.name === "context" && arg.value) {
|
||||
filters["context"] = arg.value
|
||||
} else if (availableOptions.includes(arg.name)) {
|
||||
const argName = availableOptionsAlias.has(arg.name)
|
||||
? availableOptionsAlias.get(arg.name)!
|
||||
: arg.name
|
||||
options[argName] = arg.value
|
||||
}
|
||||
}
|
||||
|
||||
if (ids) {
|
||||
filters[keyField] = ids
|
||||
}
|
||||
|
||||
const hasPagination = this.hasPagination(options)
|
||||
|
||||
let methodName = hasPagination ? "listAndCount" : "list"
|
||||
|
||||
if (relationship?.args?.methodSuffix) {
|
||||
methodName += toPascalCase(relationship.args.methodSuffix)
|
||||
} else if (serviceConfig?.args?.methodSuffix) {
|
||||
methodName += toPascalCase(serviceConfig.args.methodSuffix)
|
||||
}
|
||||
|
||||
if (typeof service[methodName] !== "function") {
|
||||
throw new Error(
|
||||
`Method "${methodName}" does not exist on "${serviceConfig.serviceName}"`
|
||||
)
|
||||
}
|
||||
|
||||
const result = await service[methodName](filters, options)
|
||||
|
||||
if (hasPagination) {
|
||||
const [data, count] = result
|
||||
return {
|
||||
data: {
|
||||
rows: data,
|
||||
metadata: this.buildPagination(options, count),
|
||||
},
|
||||
path: "rows",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
public async query(
|
||||
query: string | RemoteJoinerQuery | object,
|
||||
variables?: Record<string, unknown>,
|
||||
options?: RemoteJoinerOptions
|
||||
): Promise<any> {
|
||||
let finalQuery: RemoteJoinerQuery = query as RemoteJoinerQuery
|
||||
|
||||
if (isString(query)) {
|
||||
finalQuery = RemoteJoiner.parseQuery(query, variables)
|
||||
} else if (!isString(finalQuery?.service) && !isString(finalQuery?.alias)) {
|
||||
finalQuery = toRemoteJoinerQuery(query, variables)
|
||||
}
|
||||
|
||||
return await this.remoteJoiner.query(finalQuery, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Kind, parse, print, visit } from "graphql"
|
||||
|
||||
export function cleanGraphQLSchema(schema: string): {
|
||||
schema: string
|
||||
notFound: Record<string, Record<string, string>>
|
||||
} {
|
||||
const extractTypeNameAndKind = (type) => {
|
||||
if (type.kind === Kind.NAMED_TYPE) {
|
||||
return [type.name.value, type.kind]
|
||||
}
|
||||
if (type.kind === Kind.NON_NULL_TYPE || type.kind === Kind.LIST_TYPE) {
|
||||
return extractTypeNameAndKind(type.type)
|
||||
}
|
||||
return [null, null]
|
||||
}
|
||||
|
||||
const ast = parse(schema)
|
||||
|
||||
const typeNames = new Set(["String", "Int", "Float", "Boolean", "ID"])
|
||||
const extendedTypes = new Set()
|
||||
|
||||
const kinds = [
|
||||
Kind.OBJECT_TYPE_DEFINITION,
|
||||
Kind.INTERFACE_TYPE_DEFINITION,
|
||||
Kind.ENUM_TYPE_DEFINITION,
|
||||
Kind.SCALAR_TYPE_DEFINITION,
|
||||
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
||||
Kind.UNION_TYPE_DEFINITION,
|
||||
]
|
||||
ast.definitions.forEach((def: any) => {
|
||||
if (kinds.includes(def.kind)) {
|
||||
typeNames.add(def.name.value)
|
||||
} else if (def.kind === Kind.OBJECT_TYPE_EXTENSION) {
|
||||
extendedTypes.add(def.name.value)
|
||||
}
|
||||
})
|
||||
|
||||
const nonExistingMap: Record<string, Record<string, string>> = {}
|
||||
const parentStack: string[] = []
|
||||
|
||||
/*
|
||||
Traverse the graph mapping all the entities + fields and removing the ones that don't exist.
|
||||
Extensions are not removed, but marked with a "__extended" key if the main entity doesn't exist. (example: Link modules injecting fields into another module)
|
||||
*/
|
||||
const cleanedAst = visit(ast, {
|
||||
ObjectTypeExtension: {
|
||||
enter(node) {
|
||||
const typeName = node.name.value
|
||||
|
||||
parentStack.push(typeName)
|
||||
if (!typeNames.has(typeName)) {
|
||||
nonExistingMap[typeName] ??= {}
|
||||
nonExistingMap[typeName]["__extended"] = ""
|
||||
return null
|
||||
}
|
||||
return
|
||||
},
|
||||
leave() {
|
||||
parentStack.pop()
|
||||
},
|
||||
},
|
||||
ObjectTypeDefinition: {
|
||||
enter(node) {
|
||||
parentStack.push(node.name.value)
|
||||
},
|
||||
leave() {
|
||||
parentStack.pop()
|
||||
},
|
||||
},
|
||||
FieldDefinition: {
|
||||
leave(node) {
|
||||
const [typeName, kind] = extractTypeNameAndKind(node.type)
|
||||
|
||||
if (!typeNames.has(typeName) && kind === Kind.NAMED_TYPE) {
|
||||
const currentParent = parentStack[parentStack.length - 1]
|
||||
|
||||
nonExistingMap[currentParent] ??= {}
|
||||
nonExistingMap[currentParent][node.name.value] = typeName
|
||||
return null
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Return the schema and the map of non existing entities and fields
|
||||
return {
|
||||
schema: print(cleanedAst),
|
||||
notFound: nonExistingMap,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { GraphQLNamedType, GraphQLObjectType, isObjectType } from "graphql"
|
||||
|
||||
/**
|
||||
* From graphql schema get all the fields for the requested type and relations
|
||||
*
|
||||
* @param schemaTypeMap
|
||||
* @param typeName
|
||||
* @param relations
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* const userModule = `
|
||||
* type User {
|
||||
* id: ID!
|
||||
* name: String!
|
||||
* blabla: WHATEVER
|
||||
* }
|
||||
*
|
||||
* type Post {
|
||||
* author: User!
|
||||
* }
|
||||
* `
|
||||
*
|
||||
* const postModule = `
|
||||
* type Post {
|
||||
* id: ID!
|
||||
* title: String!
|
||||
* date: String
|
||||
* }
|
||||
*
|
||||
* type User {
|
||||
* posts: [Post!]!
|
||||
* }
|
||||
*
|
||||
* type WHATEVER {
|
||||
* random_field: String
|
||||
* post: Post
|
||||
* }
|
||||
* `
|
||||
*
|
||||
* const mergedSchema = mergeTypeDefs([userModule, postModule])
|
||||
* const schema = makeExecutableSchema({
|
||||
* typeDefs: mergedSchema,
|
||||
* })
|
||||
*
|
||||
* const fields = graphqlSchemaToFields(types, "User", ["posts"])
|
||||
*
|
||||
* console.log(fields)
|
||||
*
|
||||
* // [
|
||||
* // "id",
|
||||
* // "name",
|
||||
* // "posts.id",
|
||||
* // "posts.title",
|
||||
* // "posts.date",
|
||||
* // ]
|
||||
*/
|
||||
export function graphqlSchemaToFields(
|
||||
schemaTypeMap: { [key: string]: GraphQLNamedType },
|
||||
typeName: string,
|
||||
relations: string[] = []
|
||||
) {
|
||||
const result: string[] = []
|
||||
|
||||
function traverseFields(typeName, parent = "") {
|
||||
const type = schemaTypeMap[typeName]
|
||||
|
||||
if (!(type instanceof GraphQLObjectType)) {
|
||||
return
|
||||
}
|
||||
|
||||
const fields = type.getFields()
|
||||
|
||||
for (const fieldName in fields) {
|
||||
const field = fields[fieldName]
|
||||
let fieldType = field.type as any
|
||||
|
||||
while (fieldType.ofType) {
|
||||
fieldType = fieldType.ofType
|
||||
}
|
||||
|
||||
const composedField = parent ? `${parent}.${fieldName}` : fieldName
|
||||
if (!isObjectType(fieldType)) {
|
||||
result.push(composedField)
|
||||
} else if (relations.includes(composedField)) {
|
||||
traverseFields(fieldType.name, composedField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverseFields(typeName)
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./clean-graphql-schema"
|
||||
export * from "./graphql-schema-to-fields"
|
||||
export * from "./initialize-factory"
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
ModuleExports,
|
||||
ModuleServiceInitializeCustomDataLayerOptions,
|
||||
ModuleServiceInitializeOptions,
|
||||
} from "@medusajs/types"
|
||||
import { MODULE_PACKAGE_NAMES } from "../definitions"
|
||||
import { MedusaModule } from "../medusa-module"
|
||||
|
||||
/**
|
||||
* Generate a initialize module factory that is exported by the module to be initialized manually
|
||||
*
|
||||
* @param moduleName
|
||||
* @param moduleDefinition
|
||||
*/
|
||||
export function initializeFactory<T>({
|
||||
moduleName,
|
||||
moduleDefinition,
|
||||
}: {
|
||||
moduleName: string
|
||||
moduleDefinition: ModuleExports
|
||||
}) {
|
||||
return async (
|
||||
options?:
|
||||
| ModuleServiceInitializeOptions
|
||||
| ModuleServiceInitializeCustomDataLayerOptions
|
||||
| ExternalModuleDeclaration
|
||||
| InternalModuleDeclaration,
|
||||
injectedDependencies?: any
|
||||
) => {
|
||||
const loaded = await MedusaModule.bootstrap<T>({
|
||||
moduleKey: moduleName,
|
||||
defaultPath: MODULE_PACKAGE_NAMES[moduleName],
|
||||
declaration: options as
|
||||
| InternalModuleDeclaration
|
||||
| ExternalModuleDeclaration,
|
||||
injectedDependencies,
|
||||
moduleExports: moduleDefinition,
|
||||
})
|
||||
|
||||
return loaded[moduleName] as T
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
const typeToMethod = new Map([
|
||||
[`dismiss`, `dismiss`],
|
||||
[`link`, `create`],
|
||||
])
|
||||
|
||||
type LinkingErrorMessageInput = {
|
||||
moduleA: string
|
||||
moduleAKey: string
|
||||
moduleB: string
|
||||
moduleBKey: string
|
||||
type: "dismiss" | "link"
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Example: Module to dismiss salesChannel and apiKey by keys sales_channel_id and api_key_id was not found. Ensure the link exists, keys are correct, and the link is passed in the correct order to method 'remoteLink.dismiss'
|
||||
*/
|
||||
export const linkingErrorMessage = (input: LinkingErrorMessageInput) => {
|
||||
const { moduleA, moduleB, moduleAKey, moduleBKey, type } = input
|
||||
return `Module to type ${moduleA} and ${moduleB} by keys ${moduleAKey} and ${moduleBKey} was not found. Ensure the link exists, keys are correct, and link is passed in the correct order to method 'remoteLink.${typeToMethod.get(
|
||||
type
|
||||
)}'.
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true // to use ES5 specific tooling
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": [
|
||||
"./dist/**/*",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user