From 4ae22925599a295ab8d77297da04a6adf8e86d4c Mon Sep 17 00:00:00 2001 From: uddhab Date: Wed, 8 Apr 2026 08:17:10 +0545 Subject: [PATCH 1/5] docs: add docs of analysis, feature and guide along with readme --- .claude/CONVENTIONS.md | 48 +++ CHANGELOG.md | 576 +++++++++++++---------------------- README.md | 23 +- packages/fastify/ANALYSIS.md | 153 ++++++++++ packages/fastify/FEATURES.md | 112 +++++++ packages/fastify/GUIDE.md | 576 +++++++++++++++++++++++++++++++++++ packages/fastify/README.md | 231 +++++++------- 7 files changed, 1227 insertions(+), 492 deletions(-) create mode 100644 .claude/CONVENTIONS.md create mode 100644 packages/fastify/ANALYSIS.md create mode 100644 packages/fastify/FEATURES.md create mode 100644 packages/fastify/GUIDE.md diff --git a/.claude/CONVENTIONS.md b/.claude/CONVENTIONS.md new file mode 100644 index 00000000..952c5ee4 --- /dev/null +++ b/.claude/CONVENTIONS.md @@ -0,0 +1,48 @@ +# Project Conventions + +Rules that apply across all skills. Each SKILL.md references this file instead of repeating these. + +--- + +## Scope + +- **Stay inside the target package directory.** Do not read files outside of it unless explicitly told to. +- **Do not invent features.** Only document or test what source code confirms exists. + +## Code Examples + +- Use **TypeScript** in all code examples. +- Keep examples minimal — just enough to show the point, no boilerplate. + +## Testing + +### General Rules + +- **Use Vitest** (`import from "vitest"`). The project already has it configured. +- **Test what WE wrote, not what third-party libraries do.** Ask: "Does this verify code our team wrote, or that a third-party library works?" +- **Name tests by behavior**, not implementation. GOOD: `"decorates instance with default documentation path"` BAD: `"line 23 sets routePrefix"` + +### Mock Rules + +- **Use real Fastify instances. Do NOT mock Fastify.** Plugins are side-effect functions — mocking the instance means testing nothing. +- **Do NOT mock base-library plugins** (e.g., `@fastify/swagger`, `@fastify/multipart`). The point of the integration layer tests is to catch breakage from dependency upgrades. +- Mock only our own modules (migrations, sub-plugins we authored). + +### Cleanup + +- **Always close Fastify instances in `afterEach`** to avoid resource leaks. + +### Known Gotchas + +These patterns have been validated in this monorepo. Follow them to avoid known pitfalls: + +1. **`hasContentTypeParser("*")` returns false** even when a `*` catch-all parser is registered in Fastify 5. Use a behavioural test instead: inject a request with an unusual content-type and assert the status is not 415. +2. **Asserting `vi.fn()` plugin calls**: always include `expect.any(Function)` as the third argument — Fastify calls plugins as `plugin(fastify, options, done)`. +3. **`Readable.from(["string"])` emits strings, not Buffers.** `Buffer.concat` will throw. Use `Readable.from([Buffer.from("string")])` instead. +4. **Verify `@fastify/multipart`** with `fastify.hasContentTypeParser("multipart/form-data")`, not `getSchema("fileSchema")` — `sharedSchemaId` does not expose a schema via `fastify.getSchema`. + +## Base Library Documentation + +- **Do not repeat base library documentation in detail.** Link to their docs. +- **For doc links:** use the library's official docs URL. If unsure, use the npm page: `https://www.npmjs.com/package/{package-name}`. +- **List only the delta** for partial/modified passthroughs — what we change, not what we preserve. diff --git a/CHANGELOG.md b/CHANGELOG.md index 34dc6c6b..737d2363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,634 +1,488 @@ # [0.29.0](https://github.com/prefabs-tech/saas/compare/v0.28.0...v0.29.0) (2026-03-17) - ### Bug Fixes -* **fastify/migrations:** increase accounts table database column to VARCHAR(24) ([a496ce3](https://github.com/prefabs-tech/saas/commit/a496ce3e3ab3b4269493f4332a266e08e3b5d81c)) -* **fastify:** zod type assertions in sqlFactory and format useGlobalAccountError ([23b800b](https://github.com/prefabs-tech/saas/commit/23b800baa1064979dcfc750629b18093131912f9)) -* **react/accounts-provider:** fix missing hook dependencies and use queueMicrotask for account fetch ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) -* **react/invitations-table:** fix missing dependencies in useCallback and useMemo ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) -* **react/signup-form-actions:** fix conditional useWatch hook violation ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) -* **vue/users:** fix user display name for users with partial names ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) - +- **fastify/migrations:** increase accounts table database column to VARCHAR(24) ([a496ce3](https://github.com/prefabs-tech/saas/commit/a496ce3e3ab3b4269493f4332a266e08e3b5d81c)) +- **fastify:** zod type assertions in sqlFactory and format useGlobalAccountError ([23b800b](https://github.com/prefabs-tech/saas/commit/23b800baa1064979dcfc750629b18093131912f9)) +- **react/accounts-provider:** fix missing hook dependencies and use queueMicrotask for account fetch ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) +- **react/invitations-table:** fix missing dependencies in useCallback and useMemo ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) +- **react/signup-form-actions:** fix conditional useWatch hook violation ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) +- **vue/users:** fix user display name for users with partial names ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) ### Features -* **react:** export GetSaasAppRoutes as a named React component ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) -* **vue:** rewrite MyAccounts store using Pinia composition API ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) - +- **react:** export GetSaasAppRoutes as a named React component ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) +- **vue:** rewrite MyAccounts store using Pinia composition API ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) ### Refactors -* **react:** rename files and folders to PascalCase ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) -* **fastify/react/vue:** remove ESLint dev dependencies ([bbc73d5](https://github.com/prefabs-tech/saas/commit/bbc73d5aae76964d239a3bf2f38fdf410767e6ae)) +- **react:** rename files and folders to PascalCase ([4b7b81e](https://github.com/prefabs-tech/saas/commit/4b7b81e9252dd24ddcd75ad775d30cf99f1dcde7)) +- **fastify/react/vue:** remove ESLint dev dependencies ([bbc73d5](https://github.com/prefabs-tech/saas/commit/bbc73d5aae76964d239a3bf2f38fdf410767e6ae)) # [0.28.0](https://github.com/prefabs-tech/saas/compare/v0.27.2...v0.28.0) (2025-12-23) - ### Bug Fixes -* fix the styling of forms ([1bfca36](https://github.com/prefabs-tech/saas/commit/1bfca3615a026ec95bf916a22824ab6101255bb0)) - +- fix the styling of forms ([1bfca36](https://github.com/prefabs-tech/saas/commit/1bfca3615a026ec95bf916a22824ab6101255bb0)) ### Features -* **vue/account-signup-form:** add appropriate gap between form fields ([25b0f8c](https://github.com/prefabs-tech/saas/commit/25b0f8cc946907a35bd84b2b9f55167438941d0b)) -* **vue/forms:** implement the customization of form actions ([9542b3d](https://github.com/prefabs-tech/saas/commit/9542b3d38b5ababb6ce5c98df404d11e2206664c)) -* **vue:** implement account signup ([e3e0bc6](https://github.com/prefabs-tech/saas/commit/e3e0bc6b369af81ceec83c82f432381c018b8e49)) - - +- **vue/account-signup-form:** add appropriate gap between form fields ([25b0f8c](https://github.com/prefabs-tech/saas/commit/25b0f8cc946907a35bd84b2b9f55167438941d0b)) +- **vue/forms:** implement the customization of form actions ([9542b3d](https://github.com/prefabs-tech/saas/commit/9542b3d38b5ababb6ce5c98df404d11e2206664c)) +- **vue:** implement account signup ([e3e0bc6](https://github.com/prefabs-tech/saas/commit/e3e0bc6b369af81ceec83c82f432381c018b8e49)) ## [0.27.2](https://github.com/prefabs-tech/saas/compare/v0.27.1...v0.27.2) (2025-10-16) - ### Features -* skip app domain check for config with subdomains disabled ([#165](https://github.com/prefabs-tech/saas/issues/165)) ([3fc4272](https://github.com/prefabs-tech/saas/commit/3fc4272985752e052466be3633325c6e7470c613)) - - +- skip app domain check for config with subdomains disabled ([#165](https://github.com/prefabs-tech/saas/issues/165)) ([3fc4272](https://github.com/prefabs-tech/saas/commit/3fc4272985752e052466be3633325c6e7470c613)) ## [0.27.1](https://github.com/prefabs-tech/saas/compare/v0.27.0...v0.27.1) (2025-10-10) - - # [0.27.0](https://github.com/prefabs-tech/saas/compare/v0.26.0...v0.27.0) (2025-09-26) - ### Bug Fixes -* **account-form:** fix the creation of individual type account when the entity is of type individual ([467f6b1](https://github.com/prefabs-tech/saas/commit/467f6b1fea0bf3d5262fb89065915d7960734760)) -* **react/accounts-table:** persist table states ([f85c3c9](https://github.com/prefabs-tech/saas/commit/f85c3c9781fbea66203c44a5ed0814ebd9560fea)) -* **react/multiple-sessions:** clear persisted account id if not found in user accounts ([3df023b](https://github.com/prefabs-tech/saas/commit/3df023bb0cec5a29d915fa04ff6fb4281e199a0c)) -* **react/signup:** open user app on same window after signup ([885438b](https://github.com/prefabs-tech/saas/commit/885438bcf754fdc4ce446ca9f34ddb1dbe49741e)) - +- **account-form:** fix the creation of individual type account when the entity is of type individual ([467f6b1](https://github.com/prefabs-tech/saas/commit/467f6b1fea0bf3d5262fb89065915d7960734760)) +- **react/accounts-table:** persist table states ([f85c3c9](https://github.com/prefabs-tech/saas/commit/f85c3c9781fbea66203c44a5ed0814ebd9560fea)) +- **react/multiple-sessions:** clear persisted account id if not found in user accounts ([3df023b](https://github.com/prefabs-tech/saas/commit/3df023bb0cec5a29d915fa04ff6fb4281e199a0c)) +- **react/signup:** open user app on same window after signup ([885438b](https://github.com/prefabs-tech/saas/commit/885438bcf754fdc4ce446ca9f34ddb1dbe49741e)) ### Features -* use global account error ([6897990](https://github.com/prefabs-tech/saas/commit/6897990a88ff543742c8094e1120a09672f353fd)) -* **vue-main-app:** implement injection of additional tabs ([9845854](https://github.com/prefabs-tech/saas/commit/9845854c30ae688f02d587558e64aca09d8b6a0d)) -* **vue/account-info:** show badge of account type ([499bdc5](https://github.com/prefabs-tech/saas/commit/499bdc574140eb8f2985317354a27e50ea487a73)) -* **vue/account-view:** show the account type as a badge alongside the account name ([7a88c88](https://github.com/prefabs-tech/saas/commit/7a88c8868cf32f758f8cdd167dbb307413d9f091)) -* **vue/accounts:** make account table dynamic based on entity type ([9dab3fe](https://github.com/prefabs-tech/saas/commit/9dab3fe7e8eb0ff043a1c7f1964c83c950599908)) -* **vue/error-handling:** show error message when no account found error is thrown ([cc865d9](https://github.com/prefabs-tech/saas/commit/cc865d95c3aa8e52cdc17d91e1e3b8330682801e)) -* **vue/tables:** set persist-state prop to true for accounts, invitations and users table ([873b5e6](https://github.com/prefabs-tech/saas/commit/873b5e678c66fd16dbdcab8671735f261a88ef04)) -* **vue/translation:** add translation for type header for accounts table ([9f647ac](https://github.com/prefabs-tech/saas/commit/9f647ac536c0cbc9a8d62c3832c24363d781108b)) -* **vue:** enhance SimpleAccountError component ([4e0eb4b](https://github.com/prefabs-tech/saas/commit/4e0eb4b899471f7806aa6fb0e8bfb7042bd144ee)) -* **vue:** introduce NotFoundMessage component and replace SimpleAccountError usage ([dda5648](https://github.com/prefabs-tech/saas/commit/dda564809ce1ed6a37334be6e9213a233f3d7b77)) -* **vue:** refactor AccountSettings view and update import paths ([903553f](https://github.com/prefabs-tech/saas/commit/903553f2beb63f8c2cc20e230a1df4c6b661fd0e)) - - +- use global account error ([6897990](https://github.com/prefabs-tech/saas/commit/6897990a88ff543742c8094e1120a09672f353fd)) +- **vue-main-app:** implement injection of additional tabs ([9845854](https://github.com/prefabs-tech/saas/commit/9845854c30ae688f02d587558e64aca09d8b6a0d)) +- **vue/account-info:** show badge of account type ([499bdc5](https://github.com/prefabs-tech/saas/commit/499bdc574140eb8f2985317354a27e50ea487a73)) +- **vue/account-view:** show the account type as a badge alongside the account name ([7a88c88](https://github.com/prefabs-tech/saas/commit/7a88c8868cf32f758f8cdd167dbb307413d9f091)) +- **vue/accounts:** make account table dynamic based on entity type ([9dab3fe](https://github.com/prefabs-tech/saas/commit/9dab3fe7e8eb0ff043a1c7f1964c83c950599908)) +- **vue/error-handling:** show error message when no account found error is thrown ([cc865d9](https://github.com/prefabs-tech/saas/commit/cc865d95c3aa8e52cdc17d91e1e3b8330682801e)) +- **vue/tables:** set persist-state prop to true for accounts, invitations and users table ([873b5e6](https://github.com/prefabs-tech/saas/commit/873b5e678c66fd16dbdcab8671735f261a88ef04)) +- **vue/translation:** add translation for type header for accounts table ([9f647ac](https://github.com/prefabs-tech/saas/commit/9f647ac536c0cbc9a8d62c3832c24363d781108b)) +- **vue:** enhance SimpleAccountError component ([4e0eb4b](https://github.com/prefabs-tech/saas/commit/4e0eb4b899471f7806aa6fb0e8bfb7042bd144ee)) +- **vue:** introduce NotFoundMessage component and replace SimpleAccountError usage ([dda5648](https://github.com/prefabs-tech/saas/commit/dda564809ce1ed6a37334be6e9213a233f3d7b77)) +- **vue:** refactor AccountSettings view and update import paths ([903553f](https://github.com/prefabs-tech/saas/commit/903553f2beb63f8c2cc20e230a1df4c6b661fd0e)) # [0.26.0](https://github.com/prefabs-tech/saas/compare/v0.25.0...v0.26.0) (2025-09-04) - ### Bug Fixes -* **react/account-show:** add page toolbar actions ([39b81a4](https://github.com/prefabs-tech/saas/commit/39b81a4d15c3f3ddaba6887f59aba17c1464a732)) -* **vue/account-card:** fix the prop value for severity for BadgeComponent in AccountCard ([40fee03](https://github.com/prefabs-tech/saas/commit/40fee0368b8cd020a4b5feac76f5d95b7308a26a)) - +- **react/account-show:** add page toolbar actions ([39b81a4](https://github.com/prefabs-tech/saas/commit/39b81a4d15c3f3ddaba6887f59aba17c1464a732)) +- **vue/account-card:** fix the prop value for severity for BadgeComponent in AccountCard ([40fee03](https://github.com/prefabs-tech/saas/commit/40fee0368b8cd020a4b5feac76f5d95b7308a26a)) ### Features -* **form-actions:** support form actions aligment customization ([dd7bd01](https://github.com/prefabs-tech/saas/commit/dd7bd01e9c1ea0937a61a8c53974d72241b71677)) -* implement accounts page for main app ([717c106](https://github.com/prefabs-tech/saas/commit/717c1062e450de68722eb42537b2c4cfe3a7b9af)) -* make the account settings page reactive to the change of active account ([cf71eb9](https://github.com/prefabs-tech/saas/commit/cf71eb9bb2af0acb63bd30f470558f165ab92d7a)) -* **react/account-show:** allow to customize account settings page ([301d35d](https://github.com/prefabs-tech/saas/commit/301d35d9389334a3f30bb18c48f0e2248f8b0c67)) -* **vue/account-switcher:** refresh page on account switch and clean up unused watchers ([8413030](https://github.com/prefabs-tech/saas/commit/841303046bcdb292e982fd3bfec3af84c29593b4)) -* **vue/my-accounts:** refresh the page after account is switched from my accounts page ([31974a9](https://github.com/prefabs-tech/saas/commit/31974a93a73b492b5937397877f87cf4012d7d75)) - - +- **form-actions:** support form actions aligment customization ([dd7bd01](https://github.com/prefabs-tech/saas/commit/dd7bd01e9c1ea0937a61a8c53974d72241b71677)) +- implement accounts page for main app ([717c106](https://github.com/prefabs-tech/saas/commit/717c1062e450de68722eb42537b2c4cfe3a7b9af)) +- make the account settings page reactive to the change of active account ([cf71eb9](https://github.com/prefabs-tech/saas/commit/cf71eb9bb2af0acb63bd30f470558f165ab92d7a)) +- **react/account-show:** allow to customize account settings page ([301d35d](https://github.com/prefabs-tech/saas/commit/301d35d9389334a3f30bb18c48f0e2248f8b0c67)) +- **vue/account-switcher:** refresh page on account switch and clean up unused watchers ([8413030](https://github.com/prefabs-tech/saas/commit/841303046bcdb292e982fd3bfec3af84c29593b4)) +- **vue/my-accounts:** refresh the page after account is switched from my accounts page ([31974a9](https://github.com/prefabs-tech/saas/commit/31974a93a73b492b5937397877f87cf4012d7d75)) # [0.25.0](https://github.com/prefabs-tech/saas/compare/v0.24.0...v0.25.0) (2025-09-02) - ### Bug Fixes -* **fastify:** remove slug and domain from account update input for edit operation ([#141](https://github.com/prefabs-tech/saas/issues/141)) ([7b51d48](https://github.com/prefabs-tech/saas/commit/7b51d48b7820815223ceeb760bfcd91775460f94)) -* fix the alphabetical order ([f4335ec](https://github.com/prefabs-tech/saas/commit/f4335ec863adfa8a5a1489f3743395277bffa2a5)) -* update account existence check and improve localization ([674c457](https://github.com/prefabs-tech/saas/commit/674c4570b26f976bcd88307330d90fa4d7122356)) - +- **fastify:** remove slug and domain from account update input for edit operation ([#141](https://github.com/prefabs-tech/saas/issues/141)) ([7b51d48](https://github.com/prefabs-tech/saas/commit/7b51d48b7820815223ceeb760bfcd91775460f94)) +- fix the alphabetical order ([f4335ec](https://github.com/prefabs-tech/saas/commit/f4335ec863adfa8a5a1489f3743395277bffa2a5)) +- update account existence check and improve localization ([674c457](https://github.com/prefabs-tech/saas/commit/674c4570b26f976bcd88307330d90fa4d7122356)) ### Features -* add correct Vue implementation and fix user reactivity issues ([5628023](https://github.com/prefabs-tech/saas/commit/5628023cfea908b6eb9eae9ee51ccf715c2a039d)) -* enhance AccountSwitcher with dropdown functionality and improved styling ([2b39fb9](https://github.com/prefabs-tech/saas/commit/2b39fb942d2b5034a6b3a67bcedb46796d2f3f6a)) -* implement account settings page and show account details ([ac0789a](https://github.com/prefabs-tech/saas/commit/ac0789a312f780a510e07fc59cdf109eb5f783cf)) -* implement accounts dropdown feature with complete functionality and bug fixes ([a57e9ed](https://github.com/prefabs-tech/saas/commit/a57e9ed0be53e282100b54b2f62dea77fd0cde41)) -* implement accounts dropdown with SuperTokens dependency fixes and linting improvements ([a69b544](https://github.com/prefabs-tech/saas/commit/a69b54428a5b82846de0d37afac85e2d66fdd306)) -* replace AccountSetting.vue and add necessary translations ([cac93de](https://github.com/prefabs-tech/saas/commit/cac93dea00ec6499bb94d7dc8e241b000fca6e15)) -* show the accounts dropdown ([3e9c176](https://github.com/prefabs-tech/saas/commit/3e9c17613eef0b47304dd783d8fc6101d516cd54)) -* updated account tab components to use in the main app ([074f769](https://github.com/prefabs-tech/saas/commit/074f769a374dea2148c46780151be8cc0579f015)) - - +- add correct Vue implementation and fix user reactivity issues ([5628023](https://github.com/prefabs-tech/saas/commit/5628023cfea908b6eb9eae9ee51ccf715c2a039d)) +- enhance AccountSwitcher with dropdown functionality and improved styling ([2b39fb9](https://github.com/prefabs-tech/saas/commit/2b39fb942d2b5034a6b3a67bcedb46796d2f3f6a)) +- implement account settings page and show account details ([ac0789a](https://github.com/prefabs-tech/saas/commit/ac0789a312f780a510e07fc59cdf109eb5f783cf)) +- implement accounts dropdown feature with complete functionality and bug fixes ([a57e9ed](https://github.com/prefabs-tech/saas/commit/a57e9ed0be53e282100b54b2f62dea77fd0cde41)) +- implement accounts dropdown with SuperTokens dependency fixes and linting improvements ([a69b544](https://github.com/prefabs-tech/saas/commit/a69b54428a5b82846de0d37afac85e2d66fdd306)) +- replace AccountSetting.vue and add necessary translations ([cac93de](https://github.com/prefabs-tech/saas/commit/cac93dea00ec6499bb94d7dc8e241b000fca6e15)) +- show the accounts dropdown ([3e9c176](https://github.com/prefabs-tech/saas/commit/3e9c17613eef0b47304dd783d8fc6101d516cd54)) +- updated account tab components to use in the main app ([074f769](https://github.com/prefabs-tech/saas/commit/074f769a374dea2148c46780151be8cc0579f015)) # [0.24.0](https://github.com/prefabs-tech/saas/compare/v0.23.0...v0.24.0) (2025-08-28) - ### Features -* seperate routes ([2e9b954](https://github.com/prefabs-tech/saas/commit/2e9b954534401ba0527e99f21607bed29e167e7c)) - - +- seperate routes ([2e9b954](https://github.com/prefabs-tech/saas/commit/2e9b954534401ba0527e99f21607bed29e167e7c)) # [0.23.0](https://github.com/prefabs-tech/saas/compare/v0.22.2...v0.23.0) (2025-08-26) - ### Bug Fixes -* add removed translations ([4f769f5](https://github.com/prefabs-tech/saas/commit/4f769f530c71bd749f576750e89124d99eb92cc4)) -* change the validation messages ([9e2f3cd](https://github.com/prefabs-tech/saas/commit/9e2f3cd53e2ec960809f25a1467b15ea802c4255)) -* code standard ([8f7ec1a](https://github.com/prefabs-tech/saas/commit/8f7ec1a0e61561e687bac15d5d4d0a5bb1cb84af)) -* fix the role picked in the invitation form ([2ac6abd](https://github.com/prefabs-tech/saas/commit/2ac6abdcce34594e54785d6d97ac3b7b891d96d5)) -* fix the success message for when invitation is created ([4dc2493](https://github.com/prefabs-tech/saas/commit/4dc2493e43361e69a5c29d1d068838bb38dacace)) -* fix the translation for role values in invitations table ([fbf9aa5](https://github.com/prefabs-tech/saas/commit/fbf9aa59bd5e6f262a1815f78047ba5c50630406)) -* fix the UserSignupForm ([d783222](https://github.com/prefabs-tech/saas/commit/d7832224c6ababf810fdf9e0c97e271a096fd970)) -* remove unnecessary comemnts and type definitions ([0c13d88](https://github.com/prefabs-tech/saas/commit/0c13d8832bbd9ea80648e64611d6d3123d77f801)) -* the roles and status translation in users table ([ed731ed](https://github.com/prefabs-tech/saas/commit/ed731ed39c7cfbbb08f220c0e7e6eb92a15c7f70)) - +- add removed translations ([4f769f5](https://github.com/prefabs-tech/saas/commit/4f769f530c71bd749f576750e89124d99eb92cc4)) +- change the validation messages ([9e2f3cd](https://github.com/prefabs-tech/saas/commit/9e2f3cd53e2ec960809f25a1467b15ea802c4255)) +- code standard ([8f7ec1a](https://github.com/prefabs-tech/saas/commit/8f7ec1a0e61561e687bac15d5d4d0a5bb1cb84af)) +- fix the role picked in the invitation form ([2ac6abd](https://github.com/prefabs-tech/saas/commit/2ac6abdcce34594e54785d6d97ac3b7b891d96d5)) +- fix the success message for when invitation is created ([4dc2493](https://github.com/prefabs-tech/saas/commit/4dc2493e43361e69a5c29d1d068838bb38dacace)) +- fix the translation for role values in invitations table ([fbf9aa5](https://github.com/prefabs-tech/saas/commit/fbf9aa59bd5e6f262a1815f78047ba5c50630406)) +- fix the UserSignupForm ([d783222](https://github.com/prefabs-tech/saas/commit/d7832224c6ababf810fdf9e0c97e271a096fd970)) +- remove unnecessary comemnts and type definitions ([0c13d88](https://github.com/prefabs-tech/saas/commit/0c13d8832bbd9ea80648e64611d6d3123d77f801)) +- the roles and status translation in users table ([ed731ed](https://github.com/prefabs-tech/saas/commit/ed731ed39c7cfbbb08f220c0e7e6eb92a15c7f70)) ### Features -* accept join invitation ([3510452](https://github.com/prefabs-tech/saas/commit/3510452352bc7338b361e2f77e2a799fd6e337ba)) -* add redirect handling after login for invitation acceptance ([6cc56bd](https://github.com/prefabs-tech/saas/commit/6cc56bd960e3b452d8beac68a85e56808634d8a6)) -* close invitation form and update table after invitation created ([9f047e6](https://github.com/prefabs-tech/saas/commit/9f047e65f3b06c0fdba192451cf1e2fd3c9da666)) -* enhance join invitation messages and actions in English and French locales ([434d88a](https://github.com/prefabs-tech/saas/commit/434d88aa93714d657e703130168d65bb485ba056)) -* implement enable and disable user ([c1d9f65](https://github.com/prefabs-tech/saas/commit/c1d9f65b700cde204465abf226cebdcd94c6805d)) -* refactor the invitation signup form ([3900f83](https://github.com/prefabs-tech/saas/commit/3900f832279bc449c596846b9dbaabd72ae17cfc)) -* remove JoinInvitation related files ([8d850a4](https://github.com/prefabs-tech/saas/commit/8d850a4b09108e863c6bfc24f0bcd98b9cd2f9cf)) -* **routes:** add invitation routes for token-based access ([ca00054](https://github.com/prefabs-tech/saas/commit/ca00054bf90ea254167f6022f3083405ce20d73e)) -* show confimation modal during resend and revoke of invitations ([a9883d1](https://github.com/prefabs-tech/saas/commit/a9883d1f3986d39ec90913879fc84313bfb9ac4f)) -* show join invitation form ([a09d08b](https://github.com/prefabs-tech/saas/commit/a09d08beab5e645a732bbc3d3430864f47a22a7e)) -* show notification toast for resend, revoke and delete of invitation ([e912bf7](https://github.com/prefabs-tech/saas/commit/e912bf7d216c2716095a7a848fd2f34f008c1d4f)) -* translate the invitation created message for french ([354a601](https://github.com/prefabs-tech/saas/commit/354a60148fcd0ac765a9a93f19f37cd6644e38bc)) -* update form ([b8ef9c3](https://github.com/prefabs-tech/saas/commit/b8ef9c30fa8ec163a0d5c340ae4a12e9f77c9fc9)) -* update join invitation titles and success messages in English and French locales ([c79c1fc](https://github.com/prefabs-tech/saas/commit/c79c1fc6fad0f0e4b456108c6d893e53eca57e84)) -* update styling of signup form ([65fc39a](https://github.com/prefabs-tech/saas/commit/65fc39a6998654bad584e6d125606f9d9f4c055b)) -* update vue page for integration with app ([7d31e5d](https://github.com/prefabs-tech/saas/commit/7d31e5dc972a230232146687ff665bd36cbae16a)) - - +- accept join invitation ([3510452](https://github.com/prefabs-tech/saas/commit/3510452352bc7338b361e2f77e2a799fd6e337ba)) +- add redirect handling after login for invitation acceptance ([6cc56bd](https://github.com/prefabs-tech/saas/commit/6cc56bd960e3b452d8beac68a85e56808634d8a6)) +- close invitation form and update table after invitation created ([9f047e6](https://github.com/prefabs-tech/saas/commit/9f047e65f3b06c0fdba192451cf1e2fd3c9da666)) +- enhance join invitation messages and actions in English and French locales ([434d88a](https://github.com/prefabs-tech/saas/commit/434d88aa93714d657e703130168d65bb485ba056)) +- implement enable and disable user ([c1d9f65](https://github.com/prefabs-tech/saas/commit/c1d9f65b700cde204465abf226cebdcd94c6805d)) +- refactor the invitation signup form ([3900f83](https://github.com/prefabs-tech/saas/commit/3900f832279bc449c596846b9dbaabd72ae17cfc)) +- remove JoinInvitation related files ([8d850a4](https://github.com/prefabs-tech/saas/commit/8d850a4b09108e863c6bfc24f0bcd98b9cd2f9cf)) +- **routes:** add invitation routes for token-based access ([ca00054](https://github.com/prefabs-tech/saas/commit/ca00054bf90ea254167f6022f3083405ce20d73e)) +- show confimation modal during resend and revoke of invitations ([a9883d1](https://github.com/prefabs-tech/saas/commit/a9883d1f3986d39ec90913879fc84313bfb9ac4f)) +- show join invitation form ([a09d08b](https://github.com/prefabs-tech/saas/commit/a09d08beab5e645a732bbc3d3430864f47a22a7e)) +- show notification toast for resend, revoke and delete of invitation ([e912bf7](https://github.com/prefabs-tech/saas/commit/e912bf7d216c2716095a7a848fd2f34f008c1d4f)) +- translate the invitation created message for french ([354a601](https://github.com/prefabs-tech/saas/commit/354a60148fcd0ac765a9a93f19f37cd6644e38bc)) +- update form ([b8ef9c3](https://github.com/prefabs-tech/saas/commit/b8ef9c30fa8ec163a0d5c340ae4a12e9f77c9fc9)) +- update join invitation titles and success messages in English and French locales ([c79c1fc](https://github.com/prefabs-tech/saas/commit/c79c1fc6fad0f0e4b456108c6d893e53eca57e84)) +- update styling of signup form ([65fc39a](https://github.com/prefabs-tech/saas/commit/65fc39a6998654bad584e6d125606f9d9f4c055b)) +- update vue page for integration with app ([7d31e5d](https://github.com/prefabs-tech/saas/commit/7d31e5dc972a230232146687ff665bd36cbae16a)) ## [0.22.2](https://github.com/prefabs-tech/saas/compare/v0.22.1...v0.22.2) (2025-08-18) - ### Bug Fixes -* exclude / route by default from account discovery for main app ([f4457b0](https://github.com/prefabs-tech/saas/commit/f4457b06f4179e3cb904b7a0d8a3da520ea3df0b)) - - +- exclude / route by default from account discovery for main app ([f4457b0](https://github.com/prefabs-tech/saas/commit/f4457b06f4179e3cb904b7a0d8a3da520ea3df0b)) ## [0.22.1](https://github.com/prefabs-tech/saas/compare/v0.22.0...v0.22.1) (2025-08-14) - ### Bug Fixes -* add files migration ([773e3ef](https://github.com/prefabs-tech/saas/commit/773e3ef4b6672b0b7aec56a94cf41f59b11f33ae)) - - +- add files migration ([773e3ef](https://github.com/prefabs-tech/saas/commit/773e3ef4b6672b0b7aec56a94cf41f59b11f33ae)) # [0.22.0](https://github.com/prefabs-tech/saas/compare/v0.21.0...v0.22.0) (2025-08-01) - - # [0.21.0](https://github.com/12deg/saas/compare/v0.20.0...v0.21.0) (2025-07-31) - ### Features -* **account-tabs:** allow to customize tabs ([#115](https://github.com/12deg/saas/issues/115)) ([f122991](https://github.com/12deg/saas/commit/f1229917f2053143960beba002c049fbab2dfc8f)) - - +- **account-tabs:** allow to customize tabs ([#115](https://github.com/12deg/saas/issues/115)) ([f122991](https://github.com/12deg/saas/commit/f1229917f2053143960beba002c049fbab2dfc8f)) # [0.20.0](https://github.com/12deg/saas/compare/v0.19.2...v0.20.0) (2025-07-16) - ### Features -* **account-types:** add entity config ([#114](https://github.com/12deg/saas/issues/114)) ([dc98296](https://github.com/12deg/saas/commit/dc982969c23fdefc703db1c6c08fa53f90fe8f71)) -* **react/subdomains:** show error message for unregistered subdomains ([c0d75aa](https://github.com/12deg/saas/commit/c0d75aa406ac7a8bbea58ea35024ea423a5dbbe9)) -* **react/unregistered-domain:** add page and translations ([c216ccb](https://github.com/12deg/saas/commit/c216ccb0deff1ee1ed6146bb2f7e5bae8cf35a98)) -* **subdomains:** update config usage for admin app ([37697ae](https://github.com/12deg/saas/commit/37697ae6f33c25f23025de6e1febd42f0e50d207)) - - +- **account-types:** add entity config ([#114](https://github.com/12deg/saas/issues/114)) ([dc98296](https://github.com/12deg/saas/commit/dc982969c23fdefc703db1c6c08fa53f90fe8f71)) +- **react/subdomains:** show error message for unregistered subdomains ([c0d75aa](https://github.com/12deg/saas/commit/c0d75aa406ac7a8bbea58ea35024ea423a5dbbe9)) +- **react/unregistered-domain:** add page and translations ([c216ccb](https://github.com/12deg/saas/commit/c216ccb0deff1ee1ed6146bb2f7e5bae8cf35a98)) +- **subdomains:** update config usage for admin app ([37697ae](https://github.com/12deg/saas/commit/37697ae6f33c25f23025de6e1febd42f0e50d207)) ## [0.19.2](https://github.com/12deg/saas/compare/v0.19.1...v0.19.2) (2025-06-06) - - ## [0.19.1](https://github.com/12deg/saas/compare/v0.19.0...v0.19.1) (2025-05-29) - ### Features -* add custom tabs on account view page ([10f12c6](https://github.com/12deg/saas/commit/10f12c6ef3cf578181ca535c8ff7f07580a008d2)) - - +- add custom tabs on account view page ([10f12c6](https://github.com/12deg/saas/commit/10f12c6ef3cf578181ca535c8ff7f07580a008d2)) # [0.19.0](https://github.com/12deg/saas/compare/v0.18.1...v0.19.0) (2025-05-26) ### BREAKING CHANGES -* refactor: sync fastify package with latest dzangolab fastify packages ([7a14d05](https://github.com/12deg/saas/commit/7a14d05cd472260649e6e55c1cc83353a29238d0)) +- refactor: sync fastify package with latest dzangolab fastify packages ([7a14d05](https://github.com/12deg/saas/commit/7a14d05cd472260649e6e55c1cc83353a29238d0)) ## [0.18.1](https://github.com/12deg/saas/compare/v0.18.0...v0.18.1) (2025-05-26) - ### Features -* add accounts stores and vue components ([5f54c93](https://github.com/12deg/saas/commit/5f54c93356d679212a8ecbb0b8ead5fa088d0c44)) -* add badge for roles and invitations status ([8009411](https://github.com/12deg/saas/commit/80094111526e3bcd0301abb88ed107dbe9fe5511)) -* add components for add and edit account ([cb2ef42](https://github.com/12deg/saas/commit/cb2ef429567b30d4fecc83c0e4aba89e3a49fb8f)) -* add components for invitations ([c889636](https://github.com/12deg/saas/commit/c889636718a5f06de30451d5b367e8c523b4b87b)) -* add necessary translations ([5fbde3c](https://github.com/12deg/saas/commit/5fbde3cb694f3e74a25bc5e0dddd5bc0fe16f207)) -* add translations ([64ae0d4](https://github.com/12deg/saas/commit/64ae0d4c23ce45cdf41f4dcfc92bcae333e70be5)) -* add users table ([6cec398](https://github.com/12deg/saas/commit/6cec3982a72a21c2bb13c8fd89cd5412e387fcaf)) -* add validation on the form ([e6a5c09](https://github.com/12deg/saas/commit/e6a5c0911f0851f476db5f33e330b04d6c9f8f11)) -* display customer info page and details ([a556b58](https://github.com/12deg/saas/commit/a556b58eda6e688f9e28d64e060613dea8b7c907)) -* export only components stub page ([c897ccb](https://github.com/12deg/saas/commit/c897ccb4574cf3fde12233373914c26768c01085)) -* export router ([8fe7339](https://github.com/12deg/saas/commit/8fe7339705c2137c3c495ab9166f0b74b0e319ec)) -* fix plugin structure to send notification ([50cbc1f](https://github.com/12deg/saas/commit/50cbc1fa58824e2d718cfd09dfa02bce320849b2)) -* implment add invitation ([479cdd8](https://github.com/12deg/saas/commit/479cdd84de13594f62654cbddeed650cd652d031)) -* implment resend and revoke invitation feature ([4e348f2](https://github.com/12deg/saas/commit/4e348f236ee1fb2610efc9b939a16ac4e436f740)) -* initiate vue sass from vue user ([ab8f713](https://github.com/12deg/saas/commit/ab8f7137b6e66c4e2134ce42d8a9410be7a501e3)) -* moved validations to next file and add entity to know the individual options ([6b22145](https://github.com/12deg/saas/commit/6b22145e8d2cdc71fef895f4a97eea633833b4c0)) -* option to get roles from the configs ([84154b1](https://github.com/12deg/saas/commit/84154b1e41b5b08003132034ddc05c79eb00d197)) -* remove test directory ([243266d](https://github.com/12deg/saas/commit/243266dde5ca2e95837f8dc6b12d80112d269097)) -* remove un necessary components ([fdfc9ca](https://github.com/12deg/saas/commit/fdfc9ca80a7606c87a53b050fdb11482a2f9e68d)) -* remove un-necessary code ([eae3249](https://github.com/12deg/saas/commit/eae32493cb788bc5ad2e18f4f50745c26f7fae28)) -* remove un-necessary codes from users table ([5b0fbfe](https://github.com/12deg/saas/commit/5b0fbfe17fb57bbc3db590304678213c689cc6fe)) -* remove un-necessary tests ([a06b2ec](https://github.com/12deg/saas/commit/a06b2ec5ed279594fe3437999b66b39c6d82ccd5)) -* remove unused packages and moved axios ([c5b4685](https://github.com/12deg/saas/commit/c5b46859f3bbe3f801e64eb183c562bd334a0434)) -* removed unused css ([2086445](https://github.com/12deg/saas/commit/2086445150d5d789cf55def9eb08cd31b1a5bc1e)) -* udapte code standards ([5867d87](https://github.com/12deg/saas/commit/5867d87bb1470697786f94b54fd4c3c5339a69c1)) -* update code standard ([71c5870](https://github.com/12deg/saas/commit/71c58700eb53d4f0fa69ed84337e50f9bdd8dbdb)) -* update code standard ([58a9c7c](https://github.com/12deg/saas/commit/58a9c7c1f19e66bdbcf0ab182cd147616b017871)) -* update code standard ([ae5499f](https://github.com/12deg/saas/commit/ae5499fc79f733237798f940744f44e61b1a6fc6)) -* update code standard ([7290a0a](https://github.com/12deg/saas/commit/7290a0a9a0230fea2e73cd105acef08f423cf46c)) -* update code standards ([af69ca2](https://github.com/12deg/saas/commit/af69ca2b8a583f6baf0c539eadd1f4ee756f94c2)) -* update code standards ([d4182ae](https://github.com/12deg/saas/commit/d4182aeea2fd83d4faf2333d9aa6667993d68511)) -* update code standards ([3522b0f](https://github.com/12deg/saas/commit/3522b0f36fdd9d4c603785242f708775223821db)) -* update code standards and remove unused components ([b343c47](https://github.com/12deg/saas/commit/b343c479cfa3b638748898c26ddc6c67b404e8f9)) -* update css for form actions ([23e73d4](https://github.com/12deg/saas/commit/23e73d4ca7daf55a501d69d78142c6d13a826375)) -* update customer to account typo ([1f05bee](https://github.com/12deg/saas/commit/1f05bee300647bf2bcb4ee49225ec4e9ebc06786)) -* update events for cancel and submit ([0eee401](https://github.com/12deg/saas/commit/0eee401b573778f388b93e9b338e0c5794b19e0b)) -* update invitation form ([7a2d755](https://github.com/12deg/saas/commit/7a2d755cdd44ba02219846e04640fe9e78af6262)) -* update lint config for trailing coma ([54c6591](https://github.com/12deg/saas/commit/54c6591f9e917371e8b5fc161a452dd924deefb7)) -* update plugin to support translations ([577391b](https://github.com/12deg/saas/commit/577391b8b6a0df84a8e66dec6b883035b2285f93)) -* update route options ([aae7be1](https://github.com/12deg/saas/commit/aae7be161ac674159e6ddee26d593183ef2eaddc)) -* update routers ([58938da](https://github.com/12deg/saas/commit/58938da2b3c5a9423ff314aa7f1ea04962fd4002)) -* update routes ([743ec90](https://github.com/12deg/saas/commit/743ec90c9cd3baf5a86fd21dae168256915656bb)) -* update routes and form actions ([b5c4292](https://github.com/12deg/saas/commit/b5c42929a1f9360ac4b3d574f56b37f930809a2d)) -* update store for invitations ([5a5c953](https://github.com/12deg/saas/commit/5a5c9531defbd1c13c9bd52a9bd0bca5462827a9)) -* update styles and remove unused class ([7901be5](https://github.com/12deg/saas/commit/7901be50be38f7f624f4ad35b65ce36b606605d0)) -* update table and enable edit fields ([3cfbae3](https://github.com/12deg/saas/commit/3cfbae376c47577e6256ef1c96a43b34f61e8452)) -* update table to have in built add action ([ecd48c7](https://github.com/12deg/saas/commit/ecd48c7f43c4ac767c42a75f72a06f6cf05c1722)) -* update translations ([9af1fac](https://github.com/12deg/saas/commit/9af1fac25555f11473be0e044697daa0f15a3693)) -* update translations ([49aa432](https://github.com/12deg/saas/commit/49aa432cb5debfe527c8b04050c0164a9018a604)) -* update translations ([5dbddb9](https://github.com/12deg/saas/commit/5dbddb9e596db6b8e86d62802d66c1820a016353)) -* update translations ([45a89e2](https://github.com/12deg/saas/commit/45a89e26a5a2a420e9f9d08605820bf9cc5551a9)) -* update translations ([f60b207](https://github.com/12deg/saas/commit/f60b207d1f73fbe7bcaed1750cf089d1791a030e)) -* update translations ([289668e](https://github.com/12deg/saas/commit/289668e3dc5c153f2d88b8afa46b7c4d8b13df70)) -* update translations ([427091c](https://github.com/12deg/saas/commit/427091c5d4cc69cc1ce8d8740b2d1a9a1bfa002b)) -* update translations ([cbf64fb](https://github.com/12deg/saas/commit/cbf64fb1ecdb806ea2158300b4bbb62958c6e9f3)) -* update translations and form ([8c0cd72](https://github.com/12deg/saas/commit/8c0cd72a251eb1a61012dca43d2aaf0c4c29c49b)) -* update validation ([3942562](https://github.com/12deg/saas/commit/39425628216faa63b8fe4011f04f0926e14ce838)) -* update validations for entity ([2e4f443](https://github.com/12deg/saas/commit/2e4f4436a087c35f068e1a71f486142763cf2e3d)) -* update view page ([529be2d](https://github.com/12deg/saas/commit/529be2dd99a8006f356193c87f1919cf276feb59)) -* used config to display the data ([ea7d3e4](https://github.com/12deg/saas/commit/ea7d3e49163175998628110bb43dd684f2e74791)) - - +- add accounts stores and vue components ([5f54c93](https://github.com/12deg/saas/commit/5f54c93356d679212a8ecbb0b8ead5fa088d0c44)) +- add badge for roles and invitations status ([8009411](https://github.com/12deg/saas/commit/80094111526e3bcd0301abb88ed107dbe9fe5511)) +- add components for add and edit account ([cb2ef42](https://github.com/12deg/saas/commit/cb2ef429567b30d4fecc83c0e4aba89e3a49fb8f)) +- add components for invitations ([c889636](https://github.com/12deg/saas/commit/c889636718a5f06de30451d5b367e8c523b4b87b)) +- add necessary translations ([5fbde3c](https://github.com/12deg/saas/commit/5fbde3cb694f3e74a25bc5e0dddd5bc0fe16f207)) +- add translations ([64ae0d4](https://github.com/12deg/saas/commit/64ae0d4c23ce45cdf41f4dcfc92bcae333e70be5)) +- add users table ([6cec398](https://github.com/12deg/saas/commit/6cec3982a72a21c2bb13c8fd89cd5412e387fcaf)) +- add validation on the form ([e6a5c09](https://github.com/12deg/saas/commit/e6a5c0911f0851f476db5f33e330b04d6c9f8f11)) +- display customer info page and details ([a556b58](https://github.com/12deg/saas/commit/a556b58eda6e688f9e28d64e060613dea8b7c907)) +- export only components stub page ([c897ccb](https://github.com/12deg/saas/commit/c897ccb4574cf3fde12233373914c26768c01085)) +- export router ([8fe7339](https://github.com/12deg/saas/commit/8fe7339705c2137c3c495ab9166f0b74b0e319ec)) +- fix plugin structure to send notification ([50cbc1f](https://github.com/12deg/saas/commit/50cbc1fa58824e2d718cfd09dfa02bce320849b2)) +- implment add invitation ([479cdd8](https://github.com/12deg/saas/commit/479cdd84de13594f62654cbddeed650cd652d031)) +- implment resend and revoke invitation feature ([4e348f2](https://github.com/12deg/saas/commit/4e348f236ee1fb2610efc9b939a16ac4e436f740)) +- initiate vue sass from vue user ([ab8f713](https://github.com/12deg/saas/commit/ab8f7137b6e66c4e2134ce42d8a9410be7a501e3)) +- moved validations to next file and add entity to know the individual options ([6b22145](https://github.com/12deg/saas/commit/6b22145e8d2cdc71fef895f4a97eea633833b4c0)) +- option to get roles from the configs ([84154b1](https://github.com/12deg/saas/commit/84154b1e41b5b08003132034ddc05c79eb00d197)) +- remove test directory ([243266d](https://github.com/12deg/saas/commit/243266dde5ca2e95837f8dc6b12d80112d269097)) +- remove un necessary components ([fdfc9ca](https://github.com/12deg/saas/commit/fdfc9ca80a7606c87a53b050fdb11482a2f9e68d)) +- remove un-necessary code ([eae3249](https://github.com/12deg/saas/commit/eae32493cb788bc5ad2e18f4f50745c26f7fae28)) +- remove un-necessary codes from users table ([5b0fbfe](https://github.com/12deg/saas/commit/5b0fbfe17fb57bbc3db590304678213c689cc6fe)) +- remove un-necessary tests ([a06b2ec](https://github.com/12deg/saas/commit/a06b2ec5ed279594fe3437999b66b39c6d82ccd5)) +- remove unused packages and moved axios ([c5b4685](https://github.com/12deg/saas/commit/c5b46859f3bbe3f801e64eb183c562bd334a0434)) +- removed unused css ([2086445](https://github.com/12deg/saas/commit/2086445150d5d789cf55def9eb08cd31b1a5bc1e)) +- udapte code standards ([5867d87](https://github.com/12deg/saas/commit/5867d87bb1470697786f94b54fd4c3c5339a69c1)) +- update code standard ([71c5870](https://github.com/12deg/saas/commit/71c58700eb53d4f0fa69ed84337e50f9bdd8dbdb)) +- update code standard ([58a9c7c](https://github.com/12deg/saas/commit/58a9c7c1f19e66bdbcf0ab182cd147616b017871)) +- update code standard ([ae5499f](https://github.com/12deg/saas/commit/ae5499fc79f733237798f940744f44e61b1a6fc6)) +- update code standard ([7290a0a](https://github.com/12deg/saas/commit/7290a0a9a0230fea2e73cd105acef08f423cf46c)) +- update code standards ([af69ca2](https://github.com/12deg/saas/commit/af69ca2b8a583f6baf0c539eadd1f4ee756f94c2)) +- update code standards ([d4182ae](https://github.com/12deg/saas/commit/d4182aeea2fd83d4faf2333d9aa6667993d68511)) +- update code standards ([3522b0f](https://github.com/12deg/saas/commit/3522b0f36fdd9d4c603785242f708775223821db)) +- update code standards and remove unused components ([b343c47](https://github.com/12deg/saas/commit/b343c479cfa3b638748898c26ddc6c67b404e8f9)) +- update css for form actions ([23e73d4](https://github.com/12deg/saas/commit/23e73d4ca7daf55a501d69d78142c6d13a826375)) +- update customer to account typo ([1f05bee](https://github.com/12deg/saas/commit/1f05bee300647bf2bcb4ee49225ec4e9ebc06786)) +- update events for cancel and submit ([0eee401](https://github.com/12deg/saas/commit/0eee401b573778f388b93e9b338e0c5794b19e0b)) +- update invitation form ([7a2d755](https://github.com/12deg/saas/commit/7a2d755cdd44ba02219846e04640fe9e78af6262)) +- update lint config for trailing coma ([54c6591](https://github.com/12deg/saas/commit/54c6591f9e917371e8b5fc161a452dd924deefb7)) +- update plugin to support translations ([577391b](https://github.com/12deg/saas/commit/577391b8b6a0df84a8e66dec6b883035b2285f93)) +- update route options ([aae7be1](https://github.com/12deg/saas/commit/aae7be161ac674159e6ddee26d593183ef2eaddc)) +- update routers ([58938da](https://github.com/12deg/saas/commit/58938da2b3c5a9423ff314aa7f1ea04962fd4002)) +- update routes ([743ec90](https://github.com/12deg/saas/commit/743ec90c9cd3baf5a86fd21dae168256915656bb)) +- update routes and form actions ([b5c4292](https://github.com/12deg/saas/commit/b5c42929a1f9360ac4b3d574f56b37f930809a2d)) +- update store for invitations ([5a5c953](https://github.com/12deg/saas/commit/5a5c9531defbd1c13c9bd52a9bd0bca5462827a9)) +- update styles and remove unused class ([7901be5](https://github.com/12deg/saas/commit/7901be50be38f7f624f4ad35b65ce36b606605d0)) +- update table and enable edit fields ([3cfbae3](https://github.com/12deg/saas/commit/3cfbae376c47577e6256ef1c96a43b34f61e8452)) +- update table to have in built add action ([ecd48c7](https://github.com/12deg/saas/commit/ecd48c7f43c4ac767c42a75f72a06f6cf05c1722)) +- update translations ([9af1fac](https://github.com/12deg/saas/commit/9af1fac25555f11473be0e044697daa0f15a3693)) +- update translations ([49aa432](https://github.com/12deg/saas/commit/49aa432cb5debfe527c8b04050c0164a9018a604)) +- update translations ([5dbddb9](https://github.com/12deg/saas/commit/5dbddb9e596db6b8e86d62802d66c1820a016353)) +- update translations ([45a89e2](https://github.com/12deg/saas/commit/45a89e26a5a2a420e9f9d08605820bf9cc5551a9)) +- update translations ([f60b207](https://github.com/12deg/saas/commit/f60b207d1f73fbe7bcaed1750cf089d1791a030e)) +- update translations ([289668e](https://github.com/12deg/saas/commit/289668e3dc5c153f2d88b8afa46b7c4d8b13df70)) +- update translations ([427091c](https://github.com/12deg/saas/commit/427091c5d4cc69cc1ce8d8740b2d1a9a1bfa002b)) +- update translations ([cbf64fb](https://github.com/12deg/saas/commit/cbf64fb1ecdb806ea2158300b4bbb62958c6e9f3)) +- update translations and form ([8c0cd72](https://github.com/12deg/saas/commit/8c0cd72a251eb1a61012dca43d2aaf0c4c29c49b)) +- update validation ([3942562](https://github.com/12deg/saas/commit/39425628216faa63b8fe4011f04f0926e14ce838)) +- update validations for entity ([2e4f443](https://github.com/12deg/saas/commit/2e4f4436a087c35f068e1a71f486142763cf2e3d)) +- update view page ([529be2d](https://github.com/12deg/saas/commit/529be2dd99a8006f356193c87f1919cf276feb59)) +- used config to display the data ([ea7d3e4](https://github.com/12deg/saas/commit/ea7d3e49163175998628110bb43dd684f2e74791)) # [0.18.0](https://github.com/12deg/saas/compare/v0.17.1...v0.18.0) (2025-04-07) - ### BREAKING CHANGES -* Requires Fastify >=5.2.1. See [V5 Migration Guide](https://fastify.dev/docs/latest/Guides/Migration-Guide-V5) for more details. -* Only support Node.js v20+ +- Requires Fastify >=5.2.1. See [V5 Migration Guide](https://fastify.dev/docs/latest/Guides/Migration-Guide-V5) for more details. +- Only support Node.js v20+ ### Bug Fixes -* fix create sql for account user sql factory ([b6cd9ed](https://github.com/12deg/saas/commit/b6cd9ed1424ff77ea3754e553203bcbbed5b6513)) -* fix deleteSql in AccountAwareSqlFactory ([b9d6d45](https://github.com/12deg/saas/commit/b9d6d45317604be56409b3c279ab39c2e956f46c)) - - +- fix create sql for account user sql factory ([b6cd9ed](https://github.com/12deg/saas/commit/b6cd9ed1424ff77ea3754e553203bcbbed5b6513)) +- fix deleteSql in AccountAwareSqlFactory ([b9d6d45](https://github.com/12deg/saas/commit/b9d6d45317604be56409b3c279ab39c2e956f46c)) ## [0.17.1](https://github.com/12deg/saas/compare/v0.17.0...v0.17.1) (2025-03-27) - ### Bug Fixes -* **invitations:** show error message correctly ([df04f12](https://github.com/12deg/saas/commit/df04f120714c4a14734bf77857842d02700fe4d6)) - - +- **invitations:** show error message correctly ([df04f12](https://github.com/12deg/saas/commit/df04f120714c4a14734bf77857842d02700fe4d6)) # [0.17.0](https://github.com/12deg/saas/compare/v0.16.2...v0.17.0) (2025-03-26) - ### Bug Fixes -* fix accept invitation route for authenticated user without request body ([c58de86](https://github.com/12deg/saas/commit/c58de861f9cadf8628e81125cb1deb9c857838cf)) -* remove unwanted comment ([23cb91f](https://github.com/12deg/saas/commit/23cb91f87395868333da21c9743bc85ac4b056a4)) - +- fix accept invitation route for authenticated user without request body ([c58de86](https://github.com/12deg/saas/commit/c58de861f9cadf8628e81125cb1deb9c857838cf)) +- remove unwanted comment ([23cb91f](https://github.com/12deg/saas/commit/23cb91f87395868333da21c9743bc85ac4b056a4)) ### Features -* **account:** update my-account to account-settings ([cd55111](https://github.com/12deg/saas/commit/cd55111373b0c36ff340ec28db401e99d1888f34)) -* **config:** remove unwanted support for custom storage key ([a07ae3e](https://github.com/12deg/saas/commit/a07ae3e5153c61081aa77600ec8a7c58ad56cd22)) -* **join-acccount:** refetch accounts after accepting invitation ([b110bdd](https://github.com/12deg/saas/commit/b110bddb3dabae5ce4bf5ed63c671e97a4d4f6d8)) -* **join-account:** add join account page ([0ceb37e](https://github.com/12deg/saas/commit/0ceb37e3b4c0e8787fa78a589410485a782c4814)) -* **join-account:** add new accept-invitation route and page ([9e8ad6c](https://github.com/12deg/saas/commit/9e8ad6cbc7d771f1791d66f55c38e974e2f1aa0b)) -* **my-account:** add constant for account header name ([1db1478](https://github.com/12deg/saas/commit/1db14781a6a5c4c9557ca237319c42eafd7eaa27)) -* **my-account:** add my-account route for the app ([1b6ad99](https://github.com/12deg/saas/commit/1b6ad99020ea38e5f3f53e65d22e81b4a8f149e8)) -* **my-account:** add x-account-id header for all requests ([dc67d4f](https://github.com/12deg/saas/commit/dc67d4f6aa29437a4668f8ca7951086f6ecbc782)) -* **my-account:** update accounts page ([ecc2a98](https://github.com/12deg/saas/commit/ecc2a98c6eec1dbec3dfb110fd86685b4ddb28ef)) -* **my-account:** use my-account endpoint ([183c409](https://github.com/12deg/saas/commit/183c409d286c385cef84fa1d0b7b5653dcbe45aa)) - - +- **account:** update my-account to account-settings ([cd55111](https://github.com/12deg/saas/commit/cd55111373b0c36ff340ec28db401e99d1888f34)) +- **config:** remove unwanted support for custom storage key ([a07ae3e](https://github.com/12deg/saas/commit/a07ae3e5153c61081aa77600ec8a7c58ad56cd22)) +- **join-acccount:** refetch accounts after accepting invitation ([b110bdd](https://github.com/12deg/saas/commit/b110bddb3dabae5ce4bf5ed63c671e97a4d4f6d8)) +- **join-account:** add join account page ([0ceb37e](https://github.com/12deg/saas/commit/0ceb37e3b4c0e8787fa78a589410485a782c4814)) +- **join-account:** add new accept-invitation route and page ([9e8ad6c](https://github.com/12deg/saas/commit/9e8ad6cbc7d771f1791d66f55c38e974e2f1aa0b)) +- **my-account:** add constant for account header name ([1db1478](https://github.com/12deg/saas/commit/1db14781a6a5c4c9557ca237319c42eafd7eaa27)) +- **my-account:** add my-account route for the app ([1b6ad99](https://github.com/12deg/saas/commit/1b6ad99020ea38e5f3f53e65d22e81b4a8f149e8)) +- **my-account:** add x-account-id header for all requests ([dc67d4f](https://github.com/12deg/saas/commit/dc67d4f6aa29437a4668f8ca7951086f6ecbc782)) +- **my-account:** update accounts page ([ecc2a98](https://github.com/12deg/saas/commit/ecc2a98c6eec1dbec3dfb110fd86685b4ddb28ef)) +- **my-account:** use my-account endpoint ([183c409](https://github.com/12deg/saas/commit/183c409d286c385cef84fa1d0b7b5653dcbe45aa)) ## [0.16.2](https://github.com/12deg/saas/compare/v0.16.1...v0.16.2) (2025-03-21) - - ## [0.16.1](https://github.com/12deg/saas/compare/v0.16.0...v0.16.1) (2025-03-21) - ### Bug Fixes -* **styles:** update export file ([c83721b](https://github.com/12deg/saas/commit/c83721b75ba00bb1c61fa75ee496e3f790fbf8f4)) - - +- **styles:** update export file ([c83721b](https://github.com/12deg/saas/commit/c83721b75ba00bb1c61fa75ee496e3f790fbf8f4)) # [0.16.0](https://github.com/12deg/saas/compare/v0.15.0...v0.16.0) (2025-03-19) - ### Features -* add account aware base service and sql factory ([#78](https://github.com/12deg/saas/issues/78)) ([69903de](https://github.com/12deg/saas/commit/69903de578a2cab327e9a95b4093f1f6df8b0af6)) - - +- add account aware base service and sql factory ([#78](https://github.com/12deg/saas/issues/78)) ([69903de](https://github.com/12deg/saas/commit/69903de578a2cab327e9a95b4093f1f6df8b0af6)) # [0.15.0](https://github.com/12deg/saas/compare/v0.14.0...v0.15.0) (2025-03-12) - ### Features -* customizable email subject and templateName from config ([#76](https://github.com/12deg/saas/issues/76)) ([7eb48e0](https://github.com/12deg/saas/commit/7eb48e0d1472b19cc986f82780a39c70695483e1)) - - +- customizable email subject and templateName from config ([#76](https://github.com/12deg/saas/issues/76)) ([7eb48e0](https://github.com/12deg/saas/commit/7eb48e0d1472b19cc986f82780a39c70695483e1)) # [0.14.0](https://github.com/12deg/saas/compare/v0.13.0...v0.14.0) (2025-03-04) - ### Code Refactoring -* **routes:** update protected route names ([f5d4660](https://github.com/12deg/saas/commit/f5d4660d56750481b1b119ddeed041cee92b104f)) - +- **routes:** update protected route names ([f5d4660](https://github.com/12deg/saas/commit/f5d4660d56750481b1b119ddeed041cee92b104f)) ### Features -* add account types models and migrations ([#71](https://github.com/12deg/saas/issues/71)) ([c7f77f1](https://github.com/12deg/saas/commit/c7f77f1d050eeb8ed172699932d9b858bf6a9b70)) - +- add account types models and migrations ([#71](https://github.com/12deg/saas/issues/71)) ([c7f77f1](https://github.com/12deg/saas/commit/c7f77f1d050eeb8ed172699932d9b858bf6a9b70)) ### BREAKING CHANGES -* **routes:** getSaasAdminProtectedRoutes and getSaasAppProtectedRoutes have been renamed to -getSaasAdminRoutes and getSaasAppRoutes, respectively. - - +- **routes:** getSaasAdminProtectedRoutes and getSaasAppProtectedRoutes have been renamed to + getSaasAdminRoutes and getSaasAppRoutes, respectively. # [0.13.0](https://github.com/12deg/saas/compare/v0.12.0...v0.13.0) (2025-02-24) - ### Features -* **routes:** add routes from the package + refactor components ([a0f55b1](https://github.com/12deg/saas/commit/a0f55b1c85216d162d29eb7e0ab55e4acfe851aa)) -* **routes:** update my accounts page ([96ac9fd](https://github.com/12deg/saas/commit/96ac9fdba05b27e19e95d82c25977876ee5e21fe)) - - +- **routes:** add routes from the package + refactor components ([a0f55b1](https://github.com/12deg/saas/commit/a0f55b1c85216d162d29eb7e0ab55e4acfe851aa)) +- **routes:** update my accounts page ([96ac9fd](https://github.com/12deg/saas/commit/96ac9fdba05b27e19e95d82c25977876ee5e21fe)) # [0.12.0](https://github.com/12deg/saas/compare/v0.11.1...v0.12.0) (2025-02-21) - ### Bug Fixes -* remove unwanted translations ([2ef5752](https://github.com/12deg/saas/commit/2ef575239d25549f1977548e6e35f9e2f80bdd48)) -* update accounts data model ([d7f2a9c](https://github.com/12deg/saas/commit/d7f2a9c12475a81b743bfe21abc90a7788b868a6)) - +- remove unwanted translations ([2ef5752](https://github.com/12deg/saas/commit/2ef575239d25549f1977548e6e35f9e2f80bdd48)) +- update accounts data model ([d7f2a9c](https://github.com/12deg/saas/commit/d7f2a9c12475a81b743bfe21abc90a7788b868a6)) ### Features -* accept account invitation for existing users ([#64](https://github.com/12deg/saas/issues/64)) ([2673182](https://github.com/12deg/saas/commit/267318253b3a8308af7ba4f9b54bc11e3d9dac7d)) -* **account:** update AccountShow ([320069e](https://github.com/12deg/saas/commit/320069e57d47584e542f1720d19fd886ccfdb2a2)) - - +- accept account invitation for existing users ([#64](https://github.com/12deg/saas/issues/64)) ([2673182](https://github.com/12deg/saas/commit/267318253b3a8308af7ba4f9b54bc11e3d9dac7d)) +- **account:** update AccountShow ([320069e](https://github.com/12deg/saas/commit/320069e57d47584e542f1720d19fd886ccfdb2a2)) ## [0.11.1](https://github.com/12deg/saas/compare/v0.11.0...v0.11.1) (2025-02-20) - ### Bug Fixes -* discover customer in onRequest hook instead of preHandler hook ([7ea314b](https://github.com/12deg/saas/commit/7ea314ba35d4b5e18a89cb38a5cefffc784bfadf)) - - +- discover customer in onRequest hook instead of preHandler hook ([7ea314b](https://github.com/12deg/saas/commit/7ea314ba35d4b5e18a89cb38a5cefffc784bfadf)) # [0.11.0](https://github.com/12deg/saas/compare/v0.10.0...v0.11.0) (2025-02-19) - ### Bug Fixes -* fix customer invitation link ([#59](https://github.com/12deg/saas/issues/59)) ([af9033e](https://github.com/12deg/saas/commit/af9033ea7b843ee3d63fd8993f9d3184524680e9)) - +- fix customer invitation link ([#59](https://github.com/12deg/saas/issues/59)) ([af9033e](https://github.com/12deg/saas/commit/af9033ea7b843ee3d63fd8993f9d3184524680e9)) ### Features -* **invitation:** add AcceptInvitation page ([d30c255](https://github.com/12deg/saas/commit/d30c2555dcc8fc865d55833f54ca9e94882e4dee)) -* **invitations:** improve callbacks ([5e0e34d](https://github.com/12deg/saas/commit/5e0e34d6912891b9d3a9451381414b7013333595)) -* **signup:** improve terms and conditions field ([6e37b4d](https://github.com/12deg/saas/commit/6e37b4d80236b9c12278bbb53591c0bfd24006f1)) - - +- **invitation:** add AcceptInvitation page ([d30c255](https://github.com/12deg/saas/commit/d30c2555dcc8fc865d55833f54ca9e94882e4dee)) +- **invitations:** improve callbacks ([5e0e34d](https://github.com/12deg/saas/commit/5e0e34d6912891b9d3a9451381414b7013333595)) +- **signup:** improve terms and conditions field ([6e37b4d](https://github.com/12deg/saas/commit/6e37b4d80236b9c12278bbb53591c0bfd24006f1)) # [0.10.0](https://github.com/12deg/saas/compare/v0.9.0...v0.10.0) (2025-02-18) - ### Bug Fixes -* **customer-users:** update enable and disable actions ([90123fe](https://github.com/12deg/saas/commit/90123fe0d9c9df7c23832f5f1e403e60adb574d4)) - +- **customer-users:** update enable and disable actions ([90123fe](https://github.com/12deg/saas/commit/90123fe0d9c9df7c23832f5f1e403e60adb574d4)) ### Features -* **customer-users:** add users page ([ed5d506](https://github.com/12deg/saas/commit/ed5d506c319740808b01516cf17157ade0044283)) -* **customer-users:** allow to select role for invitation ([ce4625e](https://github.com/12deg/saas/commit/ce4625edf4edd11a56b2ecaff8650c6baa8bd576)) - - +- **customer-users:** add users page ([ed5d506](https://github.com/12deg/saas/commit/ed5d506c319740808b01516cf17157ade0044283)) +- **customer-users:** allow to select role for invitation ([ce4625e](https://github.com/12deg/saas/commit/ce4625edf4edd11a56b2ecaff8650c6baa8bd576)) # [0.9.0](https://github.com/12deg/saas/compare/v0.8.2...v0.9.0) (2025-02-17) - ### Features -* **customer:** add users table ([8406bc5](https://github.com/12deg/saas/commit/8406bc540bc1fc74ba5b55ecd66085b90999055d)) -* **customers:** add invitations components ([6bc0d82](https://github.com/12deg/saas/commit/6bc0d821ce90b313720ed1be98d70693e1b35f75)) -* **invitations:** add translations ([4501104](https://github.com/12deg/saas/commit/450110457dc55d281f92f31aaf5782dc763a1c49)) -* **invitations:** refine invitations api calls ([6263455](https://github.com/12deg/saas/commit/626345564fed56a151423c97afd75a92a19a2125)) - - +- **customer:** add users table ([8406bc5](https://github.com/12deg/saas/commit/8406bc540bc1fc74ba5b55ecd66085b90999055d)) +- **customers:** add invitations components ([6bc0d82](https://github.com/12deg/saas/commit/6bc0d821ce90b313720ed1be98d70693e1b35f75)) +- **invitations:** add translations ([4501104](https://github.com/12deg/saas/commit/450110457dc55d281f92f31aaf5782dc763a1c49)) +- **invitations:** refine invitations api calls ([6263455](https://github.com/12deg/saas/commit/626345564fed56a151423c97afd75a92a19a2125)) ## [0.8.2](https://github.com/12deg/saas/compare/v0.8.1...v0.8.2) (2025-02-14) - ### Features -* register saas routes on plugin register ([#48](https://github.com/12deg/saas/issues/48)) ([706af99](https://github.com/12deg/saas/commit/706af99c2b9941ce8bd4266df797e9325c920303)) - - +- register saas routes on plugin register ([#48](https://github.com/12deg/saas/issues/48)) ([706af99](https://github.com/12deg/saas/commit/706af99c2b9941ce8bd4266df797e9325c920303)) ## [0.8.1](https://github.com/12deg/saas/compare/v0.8.0...v0.8.1) (2025-02-14) - ### Features -* add get customer users endpoint ([#46](https://github.com/12deg/saas/issues/46)) ([91d6764](https://github.com/12deg/saas/commit/91d6764922837b77df5da785efc8653cc1bee996)) - - +- add get customer users endpoint ([#46](https://github.com/12deg/saas/issues/46)) ([91d6764](https://github.com/12deg/saas/commit/91d6764922837b77df5da785efc8653cc1bee996)) # [0.8.0](https://github.com/12deg/saas/compare/v0.7.0...v0.8.0) (2025-02-11) - ### Features -* add customer invitations model ([#36](https://github.com/12deg/saas/issues/36)) ([42cad4c](https://github.com/12deg/saas/commit/42cad4c480cb926ee08e366a271be1a5cae1a0c0)) - - +- add customer invitations model ([#36](https://github.com/12deg/saas/issues/36)) ([42cad4c](https://github.com/12deg/saas/commit/42cad4c480cb926ee08e366a271be1a5cae1a0c0)) # [0.7.0](https://github.com/12deg/saas/compare/v0.6.0...v0.7.0) (2025-02-11) - ### Features -* **accounts:** add accounts page component ([6fae366](https://github.com/12deg/saas/commit/6fae3660af42ff371430a42d56e20d0d8c699d81)) -* **accounts:** show limited information in my accounts page ([5f5c3df](https://github.com/12deg/saas/commit/5f5c3dffd717d5ffdbe2014a94b82014b096128a)) - - +- **accounts:** add accounts page component ([6fae366](https://github.com/12deg/saas/commit/6fae3660af42ff371430a42d56e20d0d8c699d81)) +- **accounts:** show limited information in my accounts page ([5f5c3df](https://github.com/12deg/saas/commit/5f5c3dffd717d5ffdbe2014a94b82014b096128a)) # [0.6.0](https://github.com/12deg/saas/compare/v0.5.0...v0.6.0) (2025-02-10) - ### Features -* **customers:** add CustomerForm and CustomersTable comopnents ([#31](https://github.com/12deg/saas/issues/31)) ([5d83183](https://github.com/12deg/saas/commit/5d83183ab04d09837b3ebe685e070d701f372463)) - - +- **customers:** add CustomerForm and CustomersTable comopnents ([#31](https://github.com/12deg/saas/issues/31)) ([5d83183](https://github.com/12deg/saas/commit/5d83183ab04d09837b3ebe685e070d701f372463)) # [0.5.0](https://github.com/12deg/saas/compare/v0.4.0...v0.5.0) (2025-02-07) - ### Features -* **customer:** add customer schema and update types ([e934680](https://github.com/12deg/saas/commit/e934680b98ef77320f24a25ad3756eecf8323077)) - - +- **customer:** add customer schema and update types ([e934680](https://github.com/12deg/saas/commit/e934680b98ef77320f24a25ad3756eecf8323077)) # [0.4.0](https://github.com/12deg/saas/compare/v0.3.4...v0.4.0) (2025-02-06) - ### Bug Fixes -* prefix auth user when customer opted for subdomain ([#27](https://github.com/12deg/saas/issues/27)) ([ad5ae50](https://github.com/12deg/saas/commit/ad5ae50107b5786863be539659583f5ac8fe54cb)) - +- prefix auth user when customer opted for subdomain ([#27](https://github.com/12deg/saas/issues/27)) ([ad5ae50](https://github.com/12deg/saas/commit/ad5ae50107b5786863be539659583f5ac8fe54cb)) ### Features -* **accounts:** add customer signup forms ([#30](https://github.com/12deg/saas/issues/30)) ([0217aa4](https://github.com/12deg/saas/commit/0217aa4effc5e18c93d7734ea6b6a8b3745fc48d)) - - +- **accounts:** add customer signup forms ([#30](https://github.com/12deg/saas/issues/30)) ([0217aa4](https://github.com/12deg/saas/commit/0217aa4effc5e18c93d7734ea6b6a8b3745fc48d)) ## [0.3.4](https://github.com/12deg/saas/compare/v0.3.3...v0.3.4) (2025-01-30) - ### Bug Fixes -* fix validation on create customer ([#28](https://github.com/12deg/saas/issues/28)) ([9ad616f](https://github.com/12deg/saas/commit/9ad616f0ef252e15fab69b156351f3c7e851fed8)) - - +- fix validation on create customer ([#28](https://github.com/12deg/saas/issues/28)) ([9ad616f](https://github.com/12deg/saas/commit/9ad616f0ef252e15fab69b156351f3c7e851fed8)) ## [0.3.3](https://github.com/12deg/saas/compare/v0.3.2...v0.3.3) (2025-01-27) - ### Bug Fixes -* fix email password signup with auto verify user context enabled ([c59e832](https://github.com/12deg/saas/commit/c59e83243e07ce6d2a91bde98d66464fab720642)) - - +- fix email password signup with auto verify user context enabled ([c59e832](https://github.com/12deg/saas/commit/c59e83243e07ce6d2a91bde98d66464fab720642)) ## [0.3.2](https://github.com/12deg/saas/compare/v0.3.1...v0.3.2) (2025-01-24) - ### Bug Fixes -* fix email verification for saas owner signup ([23d1520](https://github.com/12deg/saas/commit/23d1520efe11d2cdec9f70b2626ae879a8c0eed1)) - +- fix email verification for saas owner signup ([23d1520](https://github.com/12deg/saas/commit/23d1520efe11d2cdec9f70b2626ae879a8c0eed1)) ### Features -* delete customer if error in user signup ([77c7783](https://github.com/12deg/saas/commit/77c77832c8e93bb960abfe21b247a0bf894f2c6f)) -* support saas account owner singup from main app ([63a324f](https://github.com/12deg/saas/commit/63a324fb092852bea8569b882f6b9fb70a6de234)) - - +- delete customer if error in user signup ([77c7783](https://github.com/12deg/saas/commit/77c77832c8e93bb960abfe21b247a0bf894f2c6f)) +- support saas account owner singup from main app ([63a324f](https://github.com/12deg/saas/commit/63a324fb092852bea8569b882f6b9fb70a6de234)) ## [0.3.1](https://github.com/12deg/saas/compare/v0.3.0...v0.3.1) (2025-01-22) - ### Bug Fixes -* **customers:** update type to include database column ([14e5c42](https://github.com/12deg/saas/commit/14e5c42a664fabad29f67fcdb4d29f0d301c1fd6)) - - +- **customers:** update type to include database column ([14e5c42](https://github.com/12deg/saas/commit/14e5c42a664fabad29f67fcdb4d29f0d301c1fd6)) # [0.3.0](https://github.com/12deg/saas/compare/v0.2.1...v0.3.0) (2025-01-22) - ### Bug Fixes -* fix create customer method in customer service ([495bd99](https://github.com/12deg/saas/commit/495bd99352b96f4c48122f1e74ce3f55052b5376)) -* fix customer discovery ([0d56cd3](https://github.com/12deg/saas/commit/0d56cd34b5f9cca8f606c6f6f5ebafbf85e170f8)) -* fix supertokens recipe config ([74b310c](https://github.com/12deg/saas/commit/74b310c45acec6bdf964d560dd3b3559bc0b092e)) -* fix zod schema for slug ([852bbc1](https://github.com/12deg/saas/commit/852bbc1876e2f797506b5c104c99f60088c7f268)) - +- fix create customer method in customer service ([495bd99](https://github.com/12deg/saas/commit/495bd99352b96f4c48122f1e74ce3f55052b5376)) +- fix customer discovery ([0d56cd3](https://github.com/12deg/saas/commit/0d56cd34b5f9cca8f606c6f6f5ebafbf85e170f8)) +- fix supertokens recipe config ([74b310c](https://github.com/12deg/saas/commit/74b310c45acec6bdf964d560dd3b3559bc0b092e)) +- fix zod schema for slug ([852bbc1](https://github.com/12deg/saas/commit/852bbc1876e2f797506b5c104c99f60088c7f268)) ### Features -* add my account endpoint ([9978fa8](https://github.com/12deg/saas/commit/9978fa8cb7e44125c2b5508452dfbe6a7b30441a)) -* add my accounts endpoint ([13ce15b](https://github.com/12deg/saas/commit/13ce15bb4a1ae1c4b89ecbf4826d44281572c71d)) -* configurable saas related tables ([99c3275](https://github.com/12deg/saas/commit/99c3275039de5f510f2fa374a13d2107102f80f5)) -* use separate column to store schema/database name ([306b4ea](https://github.com/12deg/saas/commit/306b4eaca5b2377930eb0a590b2ee474d8535f31)) - +- add my account endpoint ([9978fa8](https://github.com/12deg/saas/commit/9978fa8cb7e44125c2b5508452dfbe6a7b30441a)) +- add my accounts endpoint ([13ce15b](https://github.com/12deg/saas/commit/13ce15bb4a1ae1c4b89ecbf4826d44281572c71d)) +- configurable saas related tables ([99c3275](https://github.com/12deg/saas/commit/99c3275039de5f510f2fa374a13d2107102f80f5)) +- use separate column to store schema/database name ([306b4ea](https://github.com/12deg/saas/commit/306b4eaca5b2377930eb0a590b2ee474d8535f31)) ### Performance Improvements -* make my accounts handler configurable from saas config ([b75809f](https://github.com/12deg/saas/commit/b75809fbd012196fa90121cc43e6f043baa82b12)) - - +- make my accounts handler configurable from saas config ([b75809f](https://github.com/12deg/saas/commit/b75809fbd012196fa90121cc43e6f043baa82b12)) ## [0.2.1](https://github.com/12deg/saas/compare/v0.2.0...v0.2.1) (2024-12-20) - ### Bug Fixes -* skip running customer migrations for customer who don't have slug ([9030451](https://github.com/12deg/saas/commit/9030451e228294ff094a329a1f921bfb50fa2e64)) - - +- skip running customer migrations for customer who don't have slug ([9030451](https://github.com/12deg/saas/commit/9030451e228294ff094a329a1f921bfb50fa2e64)) # [0.2.0](https://github.com/12deg/saas/compare/v0.1.0...v0.2.0) (2024-12-19) - ### Features -* add saas account member role to customer user on signup ([ca1b239](https://github.com/12deg/saas/commit/ca1b239163ff8d5bf3730088b57834b88197e43f)) -* create saas roles on plugin register ([c38a295](https://github.com/12deg/saas/commit/c38a295eedf0e0dcd9ba223f5fe5912705e1a9e6)) -* customer discovery ([94ec438](https://github.com/12deg/saas/commit/94ec438b7864e6a8e39b0f42a276d2a1110ec6e1)) -* update customer discovery ([561feb2](https://github.com/12deg/saas/commit/561feb293f478c32e9fa25ebc754ec9a5cdc986f)) - - +- add saas account member role to customer user on signup ([ca1b239](https://github.com/12deg/saas/commit/ca1b239163ff8d5bf3730088b57834b88197e43f)) +- create saas roles on plugin register ([c38a295](https://github.com/12deg/saas/commit/c38a295eedf0e0dcd9ba223f5fe5912705e1a9e6)) +- customer discovery ([94ec438](https://github.com/12deg/saas/commit/94ec438b7864e6a8e39b0f42a276d2a1110ec6e1)) +- update customer discovery ([561feb2](https://github.com/12deg/saas/commit/561feb293f478c32e9fa25ebc754ec9a5cdc986f)) # 0.1.0 (2024-12-09) diff --git a/README.md b/README.md index b1515fb6..e56f16b2 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,64 @@ # @prefabs.tech/saas + A set of saas libraries by prefabs.tech. ## Packages - - @prefabs.tech/saas-fastify (https://www.npmjs.com/package/@prefabs.tech/saas-fastify) - - @prefabs.tech/saas-react (https://www.npmjs.com/package/@prefabs.tech/saas-react) + +- @prefabs.tech/saas-fastify (https://www.npmjs.com/package/@prefabs.tech/saas-fastify) +- @prefabs.tech/saas-react (https://www.npmjs.com/package/@prefabs.tech/saas-react) # Installation & Usage + ## Install dependencies + Install dependencies recursively with this command + ```bash make install ``` ## Build all packages + ```bash make build ``` ## Lint code + ```bash make lint ``` ## Typecheck code + ```bash make typecheck ``` ## Test + ```bash make test ``` # Developing locally & testing + You can test these libraries locally without releasing using the `pnpm link` command. This allows your application to use the local version of the library instead of the published one. [More on pnpm link](https://pnpm.io/cli/link). To link a library locally, run this command from the respective app directory: + ```bash pnpm link .//packages/ ``` To unlink the library: + ```bash pnpm unlink .//packages/ ``` ## Troubleshooting - - Make sure that `package.json` and `pnpm-lock.yml` are synchronized. - - You may need to restart your apps before link and unlink to see the changes. - - All the libraries that defines or uses context has to be linked in order to link one libraries that use the context or defines it. + +- Make sure that `package.json` and `pnpm-lock.yml` are synchronized. +- You may need to restart your apps before link and unlink to see the changes. +- All the libraries that defines or uses context has to be linked in order to link one libraries that use the context or defines it. diff --git a/packages/fastify/ANALYSIS.md b/packages/fastify/ANALYSIS.md new file mode 100644 index 00000000..59aada27 --- /dev/null +++ b/packages/fastify/ANALYSIS.md @@ -0,0 +1,153 @@ + + +# Package Analysis: `@prefabs.tech/saas-fastify` + +## Overview + +A Fastify plugin that provides multi-tenant SaaS primitives: account management, account types, account users, account invitations, per-account database schema migrations, account discovery middleware, and SuperTokens auth recipe overrides. + +--- + +## Base Library Passthrough Analysis + +### supertokens-node — MODIFIED + +- Options type: Custom override functions wrapping `RecipeInterface` from supertokens-node +- Options passed: Transformed — we wrap `emailPasswordSignUp`, `emailPasswordSignIn`, `emailPasswordSignUpPOST`, `emailPasswordSignInPOST`, `thirdPartySignInUp`, `thirdPartySignInUpPOST`, `resetPasswordUsingToken`, `getUserById`, `generatePasswordResetTokenPOST`, `createNewSession`, `verifySession`, `sendEmailVerificationEmail`, `sendPasswordResetEmail` +- Features restricted: none +- Features added: + - Email prefix injection (`account.id_` prefix) to isolate users per account + - Role existence validation before sign-up + - AccountUser record creation on sign-up (linking user to account with a role) + - User profile creation in local DB on sign-up; rollback (deleteUser) on failure + - Email verification token send / auto-verify on sign-up (conditional on config) + - Duplicate-email warning email (conditional on `sendUserAlreadyExistsWarning`) + - Soft-deleted and disabled user checks in `verifySession` and `createNewSession` + - `ProfileValidationClaim` fetched on `createNewSession` (conditional on `profileValidation.enabled`) + +### fastify-plugin — THEIRS (passthrough wrapper) + +Used only to wrap plugins so Fastify skips encapsulation. No custom logic. + +### @prefabs.tech/fastify-slonik (`BaseService`, `DefaultSqlFactory`) — MODIFIED + +- Options type: Custom subclasses +- Options passed: Extended — `AccountAwareBaseService` and `AccountAwareSqlFactory` add account-scoped filtering (`account_id` WHERE clause injected into all queries) +- Features restricted: none +- Features added: + - `accountId` property propagated from service to factory on every query + - `getAccountIdFilterFragment()` injects `table.account_id = $accountId` into all WHERE clauses + - `applyAccountIdFilter` flag to opt out per-query + - Override of `getAllSql`, `getCountSql`, `getDeleteSql`, `getFindByIdSql`, `getFindOneSql`, `getFindSql`, `getListSql`, `getUpdateSql` to route through account-aware WHERE fragment + +### @prefabs.tech/postgres-migrations — THEIRS (called directly) + +Used in `runMigrations` / `runAccountMigrations` to create tables and run per-account schema migrations. No transformation of options. + +### @graphql-tools/merge (`mergeTypeDefs`) — THEIRS + +Used directly to merge GraphQL type definitions. No transformation. + +### nanoid (`customAlphabet`) — THEIRS + +Used directly to generate an 8-char schema name (`s_`) when a per-account database schema is needed. + +### humps — THEIRS + +Used directly in `AccountAwareSqlFactory` to convert camelCase field names to snake_case column identifiers. + +### pg (`Pool`) — THEIRS + +Used directly in `initializePgPool` for per-account migration pool connections. + +--- + +## Summary + +### Exports + +| Export | Type | Description | +| ------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `default` (plugin) | Fastify plugin | Main entry point; runs migrations, registers account discovery, conditionally registers route groups | +| `accountRoutes` | Fastify plugin | REST routes for account CRUD + my-account endpoints | +| `accountInvitationRoutes` | Fastify plugin | REST routes for invitation lifecycle (create, list, join, signup, resend, revoke, remove) | +| `accountUserRoutes` | Fastify plugin | REST routes for account user listing | +| `accountMigrationPlugin` | Fastify plugin | Runs per-account DB schema migrations on startup (for multi-database mode) | +| `accountResolver` | Mercurius resolver | GraphQL resolvers for `Query.account`, `Query.accounts`, `Mutation.createAccount`, `Mutation.deleteAccount`, `Mutation.updateAccount` | +| `accountSchema` | GraphQL DocumentNode | GraphQL type definitions for Account, Accounts, AccountCreateInput, AccountUpdateInput | +| `supertokensRecipesConfig` | Object | Pre-built SuperTokens recipe override config (emailVerification, session, thirdPartyEmailPassword) | +| `AccountService` | Class | Service for account DB operations; adds hostname lookup, slug/domain uniqueness, pre-account migrations | +| `AccountInvitationService` | Class | Service for invitation DB operations; enforces one-active-invite-per-email-per-account | +| `AccountUserService` | Class | Service for account user DB operations; adds `getUsers()` | +| `AccountTypeService` | Class | Service for account type DB operations; adds `createWithI18ns`, `updateWithI18ns` with transactional i18n rows | +| `AccountAwareBaseService` | Abstract class | Base service that injects `accountId` into factory for scoped queries | +| `AccountAwareSqlFactory` | Class | SQL factory that appends `account_id` filter to all queries | +| `ACCOUNT_HEADER_NAME` | Constant | `"x-account-id"` | +| `ACCOUNT_INVITATION_ACCEPT_LINK_PATH` | Constant | Default invitation accept URL path template | +| `NANOID_ALPHABET` | Constant | `"abcdefghijklmnopqrstuvwxyz0123456789"` | +| `NANOID_SIZE` | Constant | `8` | +| `ROLE_SAAS_ACCOUNT_ADMIN` | Constant | `"SAAS_ACCOUNT_ADMIN"` | +| `ROLE_SAAS_ACCOUNT_OWNER` | Constant | `"SAAS_ACCOUNT_OWNER"` | +| `ROLE_SAAS_ACCOUNT_MEMBER` | Constant | `"SAAS_ACCOUNT_MEMBER"` | +| All types (`SaasConfig`, `Account`, `AccountCreateInput`, `AccountUpdateInput`, `AccountInvitation`, `AccountType`, `AccountUser`, …) | Types | TypeScript type definitions | + +### Fastify Module Augmentations + +- `FastifyInstance.verifySession` — decorated with SuperTokens `verifySession` +- `FastifyRequest.authEmailPrefix` — per-account email prefix string +- `FastifyRequest.account` — resolved `Account` object +- `FastifyContextConfig.saas.exclude` — opt a route out of account discovery +- `MercuriusContext.config` — typed as `ApiConfig` +- `MercuriusContext.database` — typed as `Database` +- `ApiConfig.saas` — extended with `SaasConfig` + +### Hooks & Lifecycle Registrations + +| Hook | Location | Purpose | +| ----------- | --------------------------- | -------------------------------------------------------------------------------------------------------- | +| `onReady` | `plugin.ts` | Creates SuperTokens SAAS roles on startup | +| `onRequest` | `accountDiscoveryPlugin.ts` | Resolves `request.account` from hostname or `x-account-id` header; sets `dbSchema` and `authEmailPrefix` | + +### Conditional Branches (Feature Flags) + +| Flag / Condition | Effect | +| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `config.saas.routes.accounts.disabled` | Skips registering account REST routes | +| `config.saas.routes.accountInvitations.disabled` | Skips registering invitation REST routes | +| `config.saas.routes.accountUsers.disabled` | Skips registering account user REST routes | +| `config.saas.routes.accountTypes.disabled` | Skips registering account type REST routes | +| `saasConfig.subdomains === "disabled"` | Strips `slug`/`domain` from create/update; disables hostname-based account discovery | +| `saasConfig.multiDatabase.mode` (`disabled` / `optional` / `required`) | Controls whether per-account DB schemas are created | +| `saasConfig.mainApp.skipHostnameCheck` | Bypasses hostname matching; account resolved by header only | +| `saasConfig.apps[]` matching hostname | Skips account discovery for known app domains | +| `saasConfig.excludeRoutePatterns` | Skips account discovery for matching URL patterns | +| `routeOptions.config.saas.exclude` | Per-route opt-out from account discovery | +| `config.user.features.signUp.emailVerification` | Sends or auto-verifies email on sign-up | +| `input.userContext.autoVerifyEmail` | Auto-verifies email instead of sending token email | +| `config.user.supertokens.sendUserAlreadyExistsWarning` | Sends warning email on duplicate email sign-up | +| `config.user.features.profileValidation.enabled` | Fetches `ProfileValidationClaim` during `createNewSession` | +| `multiDatabase.migrations.path` exists on disk | Runs per-account migrations in `accountMigrationPlugin`; logs warn and skips if path missing | + +### Default Values (from `getSaasConfig`) + +| Option | Default | +| -------------------------------- | ----------------------------------------------------------------------------- | +| `apps` | `[{ name: "admin", subdomain: "admin" }]` | +| `app[].domain` | `${app.subdomain}.${rootDomain}` | +| `excludeRoutePatterns` | `[/^\/$/, /^\/auth\//, "/me", "/invitation/token/"]` (user patterns appended) | +| `invalid.domains` | `[]` | +| `invalid.slugs` | `["admin"]` | +| `mainApp.subdomain` | `"app"` | +| `mainApp.domain` | `${mainApp.subdomain}.${rootDomain}` | +| `mainApp.skipHostnameCheck` | `true` when `subdomains === "disabled"` | +| `multiDatabase.mode` | `"disabled"` | +| `multiDatabase.migrations.path` | `${slonik.migrations.path ?? "migrations"}/accounts` | +| `tables.accounts.name` | `"__accounts"` | +| `tables.accountTypes.name` | `"__account_types"` | +| `tables.accountTypesI18n.name` | `"__account_types_i18n"` | +| `tables.accountUsers.name` | `"__account_users"` | +| `tables.accountAddresses.name` | `"__account_addresses"` | +| `tables.accountInvitations.name` | `"__account_invitations"` | +| Invitation `acceptLinkPath` | `"/invitation/token/:token?accountId=:accountId"` | +| `NANOID_SIZE` | `8` | +| Account schema `s_` prefix | `s_<8-char-nanoid>` | diff --git a/packages/fastify/FEATURES.md b/packages/fastify/FEATURES.md new file mode 100644 index 00000000..bcb2e923 --- /dev/null +++ b/packages/fastify/FEATURES.md @@ -0,0 +1,112 @@ + + +# Features: `@prefabs.tech/saas-fastify` + +## Plugin Registration + +1. **Main plugin registration** — `fastify.register(saasPlugin)` runs core-table migrations, creates SuperTokens SAAS roles (`onReady`), registers account discovery, and conditionally registers all route groups. +2. **Account migration plugin** — `accountMigrationPlugin` is a separately exported plugin that iterates all accounts and runs per-account schema migrations; skips silently if the migrations path does not exist on disk. +3. **Pre-built SuperTokens recipes config** — `supertokensRecipesConfig` is a ready-made object covering `emailVerification`, `session`, and `thirdPartyEmailPassword` recipes with all SaaS-aware overrides applied. + +## Account Discovery Middleware + +4. **`onRequest` hook — account resolution** — on every request, resolves `request.account` from either the request hostname (subdomain-based) or the `x-account-id` header (header-based), depending on `subdomains` mode. +5. **App domain bypass** — requests whose hostname matches a configured `apps[]` entry skip account discovery entirely and are logged as belonging to that app. +6. **Route exclusion patterns** — requests matching any pattern in `excludeRoutePatterns` (default: `/`, `/auth/*`, `/me`, `/invitation/token/`) skip account discovery. +7. **Per-route exclusion** — individual routes can opt out of account discovery via `config.saas.exclude: true` in `FastifyContextConfig`. +8. **`request.dbSchema` assignment** — when an account has a `database` field, `request.dbSchema` is set to that value for per-account schema routing. +9. **`request.authEmailPrefix` assignment** — when an account has a `slug`, `request.authEmailPrefix` is set to `${account.id}_` for user email isolation. +10. **404 on undiscoverable account** — if account discovery throws (account not found), the middleware replies `404 { error: { message: "Account not found" } }`. + +## Account Management Routes + +11. **Account routes (`accountRoutes`)** — registers REST routes for: `GET /accounts`, `GET /accounts/:id`, `GET /accounts/me`, `GET /accounts/my-accounts`, `POST /accounts`, `PUT /accounts/:id`, `PUT /accounts/me`, `DELETE /accounts/:id`. +12. **Disable accounts routes** — set `config.saas.routes.accounts.disabled: true` to skip registering account routes. +13. **Custom account handlers** — every account handler can be replaced via `config.saas.handlers.account.*` (create, delete, getById, list, myAccount, myAccounts, update, updateMyAccount). + +## Account Invitation Routes + +14. **Invitation routes (`accountInvitationRoutes`)** — registers REST routes for: create, list, getByAccountId, getByToken, join, signup, resend, revoke, remove. +15. **Disable invitation routes** — set `config.saas.routes.accountInvitations.disabled: true`. +16. **Custom invitation handlers** — all invitation handlers are replaceable via `config.saas.handlers.accountInvitation.*`. +17. **One-active-invite enforcement** — `AccountInvitationService.preCreate` rejects creation if a non-expired, non-revoked, non-accepted invite already exists for the same email + account. +18. **Invitation accept link path** — default path `/invitation/token/:token?accountId=:accountId`; override via `config.saas.invitation.acceptLinkPath`. +19. **Invitation email subject/template override** — override via `config.saas.invitation.emailOverrides.subject` and `.templateName`. +20. **Post-accept callback** — `config.saas.invitation.postAccept` is called after a user accepts an invitation. + +## Account User Routes + +21. **Account user routes (`accountUserRoutes`)** — registers REST routes for: list, getByAccountId. +22. **Disable account user routes** — set `config.saas.routes.accountUsers.disabled: true`. +23. **Custom account user handlers** — replaceable via `config.saas.handlers.accountUser.*`. + +## Account Type Routes + +24. **Account type routes** — registers REST routes for: all, list, getById, create, update, remove. +25. **Disable account type routes** — set `config.saas.routes.accountTypes.disabled: true`. +26. **Custom account type handlers** — replaceable via `config.saas.handlers.accountType.*`. +27. **Transactional i18n creation** — `AccountTypeService.createWithI18ns` creates the account type and its i18n rows in a single transaction. +28. **Transactional i18n update** — `AccountTypeService.updateWithI18ns` replaces all i18n rows transactionally. + +## Subdomains & Domain Routing + +29. **`subdomains` mode** — three-value enum (`"disabled"` / `"optional"` / `"required"`) controlling whether slugs/domains are stored on accounts and whether hostname-based discovery is active. +30. **Root domain** — `config.saas.rootDomain` is required; used to compute default `mainApp.domain` and per-app domains. +31. **Main app domain** — defaults to `app.${rootDomain}`; override with `config.saas.mainApp.subdomain` or `config.saas.mainApp.domain`. +32. **Deprecated `mainAppSubdomain`** — `config.saas.mainAppSubdomain` is supported but deprecated in favour of `mainApp.subdomain`. +33. **`skipHostnameCheck`** — when `subdomains === "disabled"`, `mainApp.skipHostnameCheck` defaults to `true`; account discovery falls back to header-only mode. +34. **Invalid slugs list** — `config.saas.invalid.slugs` (default `["admin"]`) prevents reserved values from being used as account slugs. +35. **Invalid domains list** — `config.saas.invalid.domains` (default `[]`) prevents specified domains from being registered. + +## Multi-Database Support + +36. **`multiDatabase.mode`** — three-value enum (`"disabled"` / `"optional"` / `"required"`) controlling per-account Postgres schema creation. +37. **Schema name generation** — when a separate schema is needed, a unique 8-char `s_` name is generated using `[a-z0-9]` alphabet. +38. **`useSeparateDatabase` flag** — `AccountCreateInput.useSeparateDatabase` opt-in field for when `multiDatabase.mode === "optional"`. +39. **Per-account migrations path** — defaults to `${slonik.migrations.path ?? "migrations"}/accounts`; override via `config.saas.multiDatabase.migrations.path`. +40. **Migration path existence check** — `accountMigrationPlugin` logs a warning and skips if the migrations path does not exist. + +## SuperTokens Auth Overrides + +41. **Email prefix isolation** — on sign-up, the user's email is stored as `${authEmailPrefix}${email}` in SuperTokens to isolate users per account. +42. **Role validation on sign-up** — `emailPasswordSignUp` checks that all requested roles exist before proceeding; throws `SIGN_UP_FAILED` (500) if any role is missing. +43. **AccountUser record creation on sign-up** — links the new SuperTokens user to the account in the `__account_users` table with the specified role (defaults to `ROLE_SAAS_ACCOUNT_MEMBER`). +44. **User profile local DB creation on sign-up** — creates a user record in the local DB; rolls back the SuperTokens user (`deleteUser`) if local creation fails. +45. **Email verification on sign-up** — if `config.user.features.signUp.emailVerification` is enabled, sends a verification email; skips if `userContext.autoVerifyEmail` is true (auto-verifies instead). +46. **Duplicate email warning** — if `config.user.supertokens.sendUserAlreadyExistsWarning` is true and the email already exists, sends a duplicate-email warning email. +47. **Soft-deleted user session block** — `verifySession` revokes session and throws `SESSION_VERIFICATION_FAILED` (401) if the user has `deletedAt` set. +48. **Disabled user session block** — `verifySession` revokes session and throws `SESSION_VERIFICATION_FAILED` (401) if the user has `disabled: true`. +49. **Soft-deleted/disabled sign-in block** — `createNewSession` throws `SIGN_IN_FAILED` (401) before creating a session if the user is deleted or disabled. +50. **ProfileValidationClaim on session creation** — if `config.user.features.profileValidation.enabled` is true, `ProfileValidationClaim` is fetched and set on the new session. +51. **SuperTokens roles created on startup** — `SAAS_ACCOUNT_ADMIN`, `SAAS_ACCOUNT_OWNER`, `SAAS_ACCOUNT_MEMBER` are created/ensured via `onReady` hook. + +## GraphQL API + +52. **Account GraphQL schema (`accountSchema`)** — exports a `DocumentNode` with types: `Account`, `Accounts`, `AccountCreateInput`, `AccountUpdateInput`, and queries/mutations with `@auth` directives. +53. **Account GraphQL resolver (`accountResolver`)** — implements `Query.account`, `Query.accounts`, `Mutation.createAccount`, `Mutation.deleteAccount`, `Mutation.updateAccount`. +54. **Merged schema export** — `graphql/schema.ts` exports `mergeTypeDefs([accountSchema])` for use with Mercurius. + +## Service Layer + +55. **`AccountService`** — extends `BaseService`; adds `findByHostname`, `findByUserId`, `validateSlugOrDomain`; `preCreate` validates/strips slug/domain, generates DB schema name; `postCreate` runs per-account migrations. +56. **`AccountInvitationService`** — extends `AccountAwareBaseService`; adds `findOneByToken` (with UUID validation and account name enrichment); `preCreate` enforces single-active-invite-per-email-per-account. +57. **`AccountUserService`** — extends `AccountAwareBaseService`; adds `getUsers()` to fetch users joined across accounts. +58. **`AccountTypeService`** — extends `BaseService`; adds `createWithI18ns` and `updateWithI18ns` for transactional i18n row management. +59. **`AccountAwareBaseService`** — abstract base service that stores `accountId` and propagates it to the SQL factory on every query; accepts optional `schema` for multi-database routing. +60. **`AccountAwareSqlFactory`** — extends `DefaultSqlFactory`; injects `table.account_id = $accountId` into all WHERE clauses via `getAdditionalFilterFragments`; `applyAccountIdFilter` flag disables per-query. + +## Module Augmentations & Type Exports + +61. **`FastifyRequest.account`** — typed as `Account | undefined`; populated by account discovery middleware. +62. **`FastifyRequest.authEmailPrefix`** — typed as `string | undefined`; set to `${account.id}_` when slug is present. +63. **`FastifyInstance.verifySession`** — decorated with SuperTokens `verifySession` function. +64. **`FastifyContextConfig.saas.exclude`** — boolean flag to exclude a specific route from account discovery. +65. **`MercuriusContext.config` / `.database`** — typed as `ApiConfig` and `Database` respectively. +66. **`ApiConfig.saas`** — module augmentation extending `@prefabs.tech/fastify-config` with `SaasConfig`. + +## Constants + +67. **`ACCOUNT_HEADER_NAME`** — `"x-account-id"` — the HTTP header used for header-based account resolution. +68. **`ACCOUNT_INVITATION_ACCEPT_LINK_PATH`** — default invitation accept link path template. +69. **`ROLE_SAAS_ACCOUNT_ADMIN`**, **`ROLE_SAAS_ACCOUNT_OWNER`**, **`ROLE_SAAS_ACCOUNT_MEMBER`** — role name constants. +70. **`NANOID_ALPHABET`** / **`NANOID_SIZE`** — alphabet and length used for schema name generation. diff --git a/packages/fastify/GUIDE.md b/packages/fastify/GUIDE.md new file mode 100644 index 00000000..a2bde3ed --- /dev/null +++ b/packages/fastify/GUIDE.md @@ -0,0 +1,576 @@ +# @prefabs.tech/saas-fastify — Developer Guide + +## Installation + +### For package consumers + +```bash +npm install @prefabs.tech/saas-fastify +``` + +```bash +pnpm add @prefabs.tech/saas-fastify +``` + +### For monorepo development + +```bash +pnpm install +pnpm --filter @prefabs.tech/saas-fastify test +pnpm --filter @prefabs.tech/saas-fastify build +``` + +## Setup + +Register the plugin after `@prefabs.tech/fastify-config`, `@prefabs.tech/fastify-slonik`, and `@prefabs.tech/fastify-user` are in place. The plugin reads `fastify.config.saas` at registration time. + +```typescript +import saasPlugin from "@prefabs.tech/saas-fastify"; +import type { ApiConfig } from "@prefabs.tech/fastify-config"; + +// Extend your app config with SaaS options +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + }, + // ...rest of your config +}; + +await fastify.register(saasPlugin); +``` + +All subsequent examples assume this setup and only show the relevant config delta or code. + +--- + +## Base Libraries + +### supertokens-node — Modified + +Handles authentication. We wrap 13 of its recipe functions to add SaaS-specific behaviour. + +-> **Their docs:** [supertokens-node](https://www.npmjs.com/package/supertokens-node) + +We wrap rather than replace: every override calls `originalImplementation.*` and extends the result. What we add on top: + +- Per-account email prefixing to isolate users +- Role existence validation before sign-up +- Local user and AccountUser record creation on sign-up +- Email verification flow wiring +- Soft-deleted / disabled user blocks on sign-in and session verify + +### @prefabs.tech/fastify-slonik — Modified + +Provides `BaseService` and `DefaultSqlFactory`. We subclass both. + +-> **Their docs:** [@prefabs.tech/fastify-slonik](https://www.npmjs.com/package/@prefabs.tech/fastify-slonik) + +What we add: `AccountAwareBaseService` and `AccountAwareSqlFactory` automatically append `WHERE table.account_id = $accountId` to every query, so services scoped to an account never accidentally leak rows from other accounts. + +--- + +## Features + +### Plugin Registration + +Register the default export to activate all SaaS infrastructure: + +```typescript +import saasPlugin from "@prefabs.tech/saas-fastify"; + +await fastify.register(saasPlugin); +``` + +On registration the plugin: + +- Runs core-table migrations (accounts, account types, account users, account addresses, account invitations) +- Registers an `onReady` hook that creates the three SAAS roles in SuperTokens +- Registers the account discovery `onRequest` hook +- Conditionally registers the four route groups + +--- + +### Account Migration Plugin + +A separately exported plugin for per-account schema migrations. Register it after the main plugin when using `multiDatabase` mode: + +```typescript +import saasPlugin, { accountMigrationPlugin } from "@prefabs.tech/saas-fastify"; + +await fastify.register(saasPlugin); +await fastify.register(accountMigrationPlugin); +``` + +On startup it fetches all accounts that have a `database` field, opens a `pg.Pool` connection, and runs migrations from the path configured in `config.saas.multiDatabase.migrations.path`. If the path does not exist on disk it logs a warning and skips. + +--- + +### Pre-built SuperTokens Recipes + +Export a ready-made `supertokensRecipesConfig` object and pass it straight to your user plugin config: + +```typescript +import { supertokensRecipesConfig } from "@prefabs.tech/saas-fastify"; + +const config: ApiConfig = { + user: { + supertokens: { + recipes: supertokensRecipesConfig, + }, + }, +}; +``` + +This wires up all SaaS-aware overrides for `emailVerification`, `session`, and `thirdPartyEmailPassword` recipes. + +--- + +### Account Discovery Middleware + +On every request an `onRequest` hook runs and resolves `request.account`: + +| Situation | Resolution strategy | +| ------------------------------------------------------------------------ | ------------------------------- | +| `subdomains !== "disabled"` and hostname does not match `mainApp.domain` | Lookup by hostname | +| Hostname matches `mainApp.domain` or `skipHostnameCheck` is true | Lookup by `x-account-id` header | + +When an account is found: + +- `request.account` is set to the `Account` object +- `request.dbSchema` is set to `account.database` (if present) +- `request.authEmailPrefix` is set to `${account.id}_` (if `account.slug` is present) + +If discovery fails, the middleware responds with `404 { error: { message: "Account not found" } }`. + +#### Skipping discovery for specific domains + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + apps: [ + { name: "admin", subdomain: "admin" }, // admin.example.com → skip discovery + { name: "marketing", domain: "www.example.com" }, // explicit domain + ], + }, +}; +``` + +#### Skipping discovery for URL patterns + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + excludeRoutePatterns: ["/public/", /^\/webhooks\//], + }, +}; +``` + +The default excluded patterns (`/`, `/auth/*`, `/me`, `/invitation/token/`) are always included — your additions are appended. + +#### Skipping discovery for a single route + +```typescript +fastify.get( + "/healthz", + { + config: { saas: { exclude: true } }, + }, + async () => ({ ok: true }), +); +``` + +--- + +### Subdomains Mode + +Controls whether accounts have slugs/domains and whether hostname-based discovery is active. + +| Value | Effect | +| ------------ | --------------------------------------------------------------------------------------------------- | +| `"disabled"` | No slugs or domains stored; account resolved by header only; `skipHostnameCheck` defaults to `true` | +| `"optional"` | Slug/domain optional; hostname discovery active when slug is present | +| `"required"` | Slug required on account creation; hostname discovery always active | + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "required", // or "optional" | "disabled" + }, +}; +``` + +--- + +### Multi-Database Support + +Each account can have its own Postgres schema. Controlled by `multiDatabase.mode`: + +| Mode | Effect | +| ------------ | ------------------------------------------------------------------------------------------------- | +| `"disabled"` | No per-account schemas (default) | +| `"optional"` | Schema created when `useSeparateDatabase: true` is passed on account creation (and a slug exists) | +| `"required"` | Schema always created for accounts that have a slug | + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "required", + multiDatabase: { + mode: "required", + migrations: { + path: "migrations/accounts", // default + }, + }, + }, +}; +``` + +When a schema is created its name is `s_<8-char-nanoid>` (e.g. `s_a1b2c3d4`), stored in `account.database`. + +--- + +### Route Groups + +All four route groups are registered by default. Disable any individually: + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + routes: { + accounts: { disabled: false }, + accountInvitations: { disabled: false }, + accountUsers: { disabled: false }, + accountTypes: { disabled: true }, // skip account type routes + }, + }, +}; +``` + +--- + +### Custom Route Handlers + +Every route handler can be replaced with a custom function matching the original signature: + +```typescript +import { accountRoutes } from "@prefabs.tech/saas-fastify"; +import type { AccountCreateInput } from "@prefabs.tech/saas-fastify"; + +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + handlers: { + account: { + create: async (request, reply) => { + // custom create logic + }, + }, + accountInvitation: { + signup: async (request, reply) => { + // custom signup-via-invite logic + }, + }, + }, + }, +}; +``` + +Available override keys: `account.*`, `accountInvitation.*`, `accountUser.*`, `accountType.*`. + +--- + +### Account Invitations + +The invitation flow covers: create → resend → join (existing user) / signup (new user) → revoke / remove. + +**One-active-invite rule:** Creating an invitation for an email that already has a non-expired, non-revoked, non-accepted invitation in the same account throws an error. + +Configure invitation behaviour: + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + invitation: { + acceptLinkPath: "/join/:token?accountId=:accountId", + emailOverrides: { + subject: "You're invited to join Acme", + templateName: "custom-invite", + }, + postAccept: async (request, invitation, user) => { + // called after a user accepts the invitation + }, + }, + }, +}; +``` + +--- + +### Account Types with i18n + +`AccountTypeService` supports creating and updating account types together with their i18n rows in a single database transaction: + +```typescript +import { AccountTypeService } from "@prefabs.tech/saas-fastify"; + +const service = new AccountTypeService(config, database); + +const accountType = await service.createWithI18ns({ + name: "enterprise", + i18n: [ + { locale: "en", label: "Enterprise" }, + { locale: "fr", label: "Entreprise" }, + ], +}); + +await service.updateWithI18ns(accountType.id, { + i18n: [{ locale: "en", label: "Enterprise Plus" }], +}); +``` + +`updateWithI18ns` deletes all existing i18n rows then inserts the new set, all within the same transaction. + +--- + +### Account-Aware Service Layer + +Use `AccountAwareBaseService` when you need queries that are automatically scoped to a single account: + +```typescript +import { + AccountAwareBaseService, + AccountAwareSqlFactory, +} from "@prefabs.tech/saas-fastify"; + +class WidgetService extends AccountAwareBaseService< + Widget, + WidgetCreate, + WidgetUpdate +> { + get sqlFactoryClass() { + return WidgetSqlFactory; + } +} + +// All queries from this service include WHERE account_id = $accountId +const service = new WidgetService(config, database, request.account.id); +const widgets = await service.list(20, 0); +``` + +To opt a specific query out of the account filter, set `factory.applyAccountIdFilter = false` before querying. + +--- + +### SuperTokens Auth Overrides + +#### Email prefix isolation + +When an account is discovered, `request.authEmailPrefix` is set to `${account.id}_`. The `emailPasswordSignUp` override prepends this to the stored email, so the same real email can sign up under multiple accounts without SuperTokens seeing a duplicate. + +#### Role validation + +Pass required roles via `userContext.roles`. If any role does not exist in SuperTokens, sign-up is rejected with a 500 before any user is created: + +```typescript +await supertokens.signUp("emailpassword", { + email: "alice@example.com", + password: "secret", + userContext: { + roles: ["SAAS_ACCOUNT_OWNER"], + authEmailPrefix: request.authEmailPrefix, + account: request.account, + }, +}); +``` + +#### Soft-deleted and disabled users + +`verifySession` revokes the session and returns 401 if `user.deletedAt` is set or `user.disabled` is `true`. `createNewSession` applies the same check before issuing a session. + +#### Email verification + +Enabled when `config.user.features.signUp.emailVerification` is `true`. Pass `userContext.autoVerifyEmail = true` to skip sending an email and auto-verify instead. + +#### Profile validation claim + +When `config.user.features.profileValidation.enabled` is `true`, `ProfileValidationClaim` is fetched and attached to the session on creation. + +--- + +### GraphQL API + +Export `accountSchema` and `accountResolver` and register them with Mercurius: + +```typescript +import { accountSchema, accountResolver } from "@prefabs.tech/saas-fastify"; + +await fastify.register(mercurius, { + schema: accountSchema, + resolvers: accountResolver, +}); +``` + +Available operations (all require `@auth`): + +- `Query.account(id)` — fetch single account +- `Query.accounts(limit, offset, filters, sort)` — paginated list +- `Mutation.createAccount(data)` — create account +- `Mutation.updateAccount(id, data)` — update account +- `Mutation.deleteAccount(id)` — delete account + +--- + +### Custom Table Names + +Override any of the six core table names: + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + tables: { + accounts: { name: "tenants" }, + accountTypes: { name: "tenant_types" }, + accountTypesI18n: { name: "tenant_types_i18n" }, + accountUsers: { name: "tenant_users" }, + accountAddresses: { name: "tenant_addresses" }, + accountInvitations: { name: "tenant_invitations" }, + }, + }, +}; +``` + +--- + +### Constants + +```typescript +import { + ACCOUNT_HEADER_NAME, // "x-account-id" + ACCOUNT_INVITATION_ACCEPT_LINK_PATH, + ROLE_SAAS_ACCOUNT_ADMIN, // "SAAS_ACCOUNT_ADMIN" + ROLE_SAAS_ACCOUNT_OWNER, // "SAAS_ACCOUNT_OWNER" + ROLE_SAAS_ACCOUNT_MEMBER, // "SAAS_ACCOUNT_MEMBER" + NANOID_ALPHABET, + NANOID_SIZE, +} from "@prefabs.tech/saas-fastify"; +``` + +--- + +## Use Cases + +### Header-only tenancy (no subdomains) + +When your app does not use subdomains, disable them so account resolution falls back to the `x-account-id` header: + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "disabled", + // mainApp.skipHostnameCheck defaults to true — header-only resolution + }, +}; +``` + +The frontend sends `x-account-id: ` on every authenticated request. The middleware resolves `request.account` from the header and the rest of the request lifecycle proceeds normally. + +--- + +### Subdomain-based tenancy with optional per-account databases + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "acme.io", + subdomains: "required", + mainApp: { subdomain: "app" }, // app.acme.io + apps: [{ name: "admin", subdomain: "admin" }], // admin.acme.io skips discovery + multiDatabase: { + mode: "optional", + migrations: { path: "migrations/accounts" }, + }, + }, +}; + +await fastify.register(saasPlugin); +await fastify.register(accountMigrationPlugin); // runs per-account migrations on startup +``` + +Accounts that opted in (`useSeparateDatabase: true`) get their own schema. All account-scoped services automatically use `request.dbSchema` for query routing. + +--- + +### Invitation-based signup with custom role + +Invite a user and assign them a specific role when they accept: + +```typescript +const config: ApiConfig = { + saas: { + rootDomain: "example.com", + subdomains: "optional", + invitation: { + postAccept: async (request, invitation, user) => { + // grant extra permissions, send a welcome email, etc. + await sendWelcomeEmail(user.email); + }, + }, + handlers: { + accountInvitation: { + signup: async (request, reply) => { + // custom sign-up flow when accepting an invitation + }, + }, + }, + }, +}; +``` + +On the sign-up path, pass `userContext.saasAccountRole` to override the default `ROLE_SAAS_ACCOUNT_MEMBER` assigned to the new user. + +--- + +### Extending account-aware services + +Build your own domain services that automatically scope queries to the current account: + +```typescript +import { AccountAwareBaseService } from "@prefabs.tech/saas-fastify"; +import { DefaultSqlFactory } from "@prefabs.tech/fastify-slonik"; + +class ProjectService extends AccountAwareBaseService< + Project, + ProjectCreate, + ProjectUpdate +> { + get sqlFactoryClass() { + return DefaultSqlFactory; + } +} + +// In a route handler: +const service = new ProjectService( + request.config, + request.slonik, + request.account?.id, + request.dbSchema, +); + +// SELECT * FROM projects WHERE account_id = $1 LIMIT 20 +const projects = await service.list(20, 0); +``` diff --git a/packages/fastify/README.md b/packages/fastify/README.md index 16df29d3..5585a8a7 100644 --- a/packages/fastify/README.md +++ b/packages/fastify/README.md @@ -1,170 +1,149 @@ # @prefabs.tech/saas-fastify -A [Fastify](https://github.com/fastify/fastify) plugin that provides an easy integration of saas. +A Fastify plugin that adds multi-tenant SaaS primitives to your API: account management, per-account database isolation, invitation workflows, account-scoped authentication, and SuperTokens recipe overrides — all wired together and ready to register. -## Requirements +## Why This Package? -* [@prefabs.tech/fastify-config](https://github.com/prefabs-tech/fastify/tree/main/packages/config) -* [@prefabs.tech/fastify-mailer](https://github.com/prefabs-tech/fastify/tree/main/packages/mailer) -* [@prefabs.tech/fastify-s3](https://github.com/prefabs-tech/fastify/tree/main/packages/s3) -* [@prefabs.tech/fastify-slonik](https://github.com/prefabs-tech/fastify/tree/main/packages/slonik) -* [@prefabs.tech/fastify-user](https://github.com/prefabs-tech/fastify/tree/main/packages/user) -* [slonik](https://github.com/spa5k/fastify-slonik) -* [supertokens-node](https://github.com/supertokens/supertokens-node) +Building a SaaS backend means solving the same cross-cutting problems on every project: resolving which account owns a request, isolating users and data per account, managing invitations and roles, and hooking into your auth layer in the right places. This package encodes those patterns as a single Fastify plugin so you register it once and get the full account lifecycle out of the box. -## Installation +## What You Get -Install with npm: +### supertokens-node — Modified -```bash -npm install @prefabs.tech/fastify-config @prefabs.tech/fastify-mailer @prefabs.tech/fastify-s3 @prefabs.tech/fastify-slonik @prefabs.tech/fastify-user slonik supertokens-node @prefabs.tech/saas-fastify -``` +Wraps the [supertokens-node](https://supertokens.com/docs/nodejs) `thirdPartyEmailPassword`, `emailVerification`, and `session` recipes. Our overrides add: -Install with pnpm: +- **Per-account email isolation** — a `account.id_` prefix is injected into all auth emails so the same address can sign up under different accounts +- **Role validation on sign-up** — rejects sign-ups with unknown roles before touching the DB +- **AccountUser creation** — links the new SuperTokens user to the account with a role; rolls back (`deleteUser`) if local DB write fails +- **Soft-deleted / disabled user checks** — `verifySession` and `createNewSession` reject blocked users early +- **Conditional email verification** — sends or auto-verifies on sign-up based on `config.user.features.signUp.emailVerification` +- **Duplicate-email warning** — sends a warning email when the same address signs up again (controlled by `sendUserAlreadyExistsWarning`) +- **`ProfileValidationClaim`** — fetched during `createNewSession` when `profileValidation.enabled` is true -```bash -pnpm add --filter "<@scope/project>" @prefabs.tech/fastify-config @prefabs.tech/fastify-mailer @prefabs.tech/fastify-s3 @prefabs.tech/fastify-slonik @prefabs.tech/fastify-user slonik supertokens-node @prefabs.tech/saas-fastify -``` +Use the exported `supertokensRecipesConfig` object to pass these overrides to `@prefabs.tech/fastify-user`. -## Configuration +### @prefabs.tech/fastify-slonik — Modified -```typescript -import { supertokensRecipesConfig } from "@prefabs.tech/saas-fastify"; +Extends [`BaseService` and `DefaultSqlFactory`](https://github.com/prefabs-tech/fastify/tree/main/packages/slonik) with account-scoped variants: -const config: ApiConfig = { - ... - saas: { - rootDomain: process.env.APP_ROOT_DOMAIN as string, - mainApp: { - subdomain: process.env.MAIN_APP_SUBDOMAIN as string || "app", - domain: process.env.MAIN_APP_DOMAIN as string, - }, - multiDatabase: { - mode: "disabled", // "disabled", "optional", "required", - }, - subdomains: "disabled" // "disabled", "optional", "required", - }, - user: { - ... - supertokens: { - recipes: supertokensRecipesConfig, - } - } - ... -}; -``` +- **`AccountAwareBaseService`** — abstract base class; propagates `accountId` to the factory on every query +- **`AccountAwareSqlFactory`** — injects `table.account_id = $accountId` into all WHERE clauses automatically; opt out per-query with `applyAccountIdFilter: false` -## Usage +Extend `AccountAwareBaseService` for any service that should be scoped to the current account. -Register the saas plugin with your Fastify instance: +### Added by This Package -```typescript -import saasPlugin, { accountMigrationPlugin } from "@prefabs.tech/saas-fastify"; -import configPlugin from "@prefabs.tech/fastify-config"; -import mailerPlugin from "@prefabs.tech/fastify-mailer"; -import s3Plugin from "@prefabs.tech/fastify-s3"; -import slonikPlugin, { migrationPlugin } from "@prefabs.tech/fastify-slonik"; -import userPlugin from "@prefabs.tech/fastify-user"; -import Fastify from "fastify"; +- **Account CRUD** — create, read, update, delete with slug/domain uniqueness enforcement and hostname validation +- **Per-account database schema isolation** — three modes: `disabled`, `optional`, `required`; schemas are created automatically on account creation +- **Account discovery middleware** — resolves `request.account` from subdomain hostname or `x-account-id` header on every request; sets `dbSchema` and `authEmailPrefix` +- **Full invitation lifecycle** — create, list, join, signup via token, resend, revoke, delete +- **Account user management** — list users belonging to an account +- **Account types with i18n** — create and update with transactional i18n rows +- **GraphQL support** — `accountResolver` (Query + Mutation) and `accountSchema` type definitions ready for Mercurius +- **Role constants** — `ROLE_SAAS_ACCOUNT_OWNER`, `ROLE_SAAS_ACCOUNT_ADMIN`, `ROLE_SAAS_ACCOUNT_MEMBER`; roles are created in SuperTokens on startup +- **Per-account DB migrations** — `accountMigrationPlugin` runs schema migrations for each account on startup -import config from "./config"; +→ [Full feature list](FEATURES.md) | [Developer guide](GUIDE.md) -import type { ApiConfig } from "@prefabs.tech/fastify-config"; -import type { FastifyInstance } from "fastify"; +## Usage Guidelines -const start = async () => { - // Create fastify instance - const fastify = Fastify({ - logger: config.logger, - }); +### Excluding routes from account discovery - // Register fastify-config plugin - await fastify.register(configPlugin, { config }); +By default, every request goes through account discovery. Two ways to opt out: - // Register database plugin - await fastify.register(slonikPlugin, config.slonik); +**Via config** (pattern-based): - // Register mailer plugin - await fastify.register(mailerPlugin, config.mailer); - - // Register mailer plugin - await fastify.register(s3Plugin); +```typescript +saas: { + excludeRoutePatterns: ["/docs", /^\/auth\//], +} +``` - // Register fastify-user plugin - await fastify.register(userPlugin); +**Per route** (route-level flag): - // Register saas-fastify plugin - await api.register(saasPlugin); +```typescript +fastify.get( + "/docs", + { + config: { saas: { exclude: true } }, + }, + handler, +); +``` - // Run app database migrations - await fastify.register(migrationPlugin, config.slonik); +### CORS — allow `X-Account-Id` - // Run accounts database migrations - await api.register(accountMigrationPlugin); +If your frontend runs on a different origin, the `X-Account-Id` header must be in your CORS `allowedHeaders`: - try { - await fastify.listen({ - port: 3000, - host: "0.0.0.0", - }); - } catch (error) { - fastify.log.error(error); - } -}; - -start(); +```typescript +await fastify.register(import("@fastify/cors"), { + origin: ["http://localhost:3000"], + allowedHeaders: ["Content-Type", "Authorization", "X-Account-Id"], +}); ``` -### Skip account discovery for specific routes in main app +Without this, cross-origin requests cannot pass the account header and discovery will fall back to hostname only. -You can skip the account discovery process for specific routes in the main application in two ways: +## Requirements -#### Using a pattern in the SaaS configuration +The following plugins must be registered before this one: -Set a route pattern in your SaaS configuration file to exclude certain routes from account discovery: +- [@prefabs.tech/fastify-config](https://github.com/prefabs-tech/fastify/tree/main/packages/config) +- [@prefabs.tech/fastify-mailer](https://github.com/prefabs-tech/fastify/tree/main/packages/mailer) +- [@prefabs.tech/fastify-s3](https://github.com/prefabs-tech/fastify/tree/main/packages/s3) +- [@prefabs.tech/fastify-slonik](https://github.com/prefabs-tech/fastify/tree/main/packages/slonik) +- [@prefabs.tech/fastify-user](https://github.com/prefabs-tech/fastify/tree/main/packages/user) +- [slonik](https://github.com/spa5k/fastify-slonik) +- [supertokens-node](https://github.com/supertokens/supertokens-node) -```ts -saas: { - excludeRoutePattern = ["/docs"]; // it also supports regex route patterns. For example: "/^\/auth\//" -``` - -Any route matching the specified pattern(s) will bypass the account discovery logic. +## Quick Start -#### Route Options +**1. Configure** — add `saas` to your `ApiConfig` and wire in the SuperTokens recipe overrides: -In the route definition, set the exclude flag under the saas key: +```typescript +import { supertokensRecipesConfig } from "@prefabs.tech/saas-fastify"; -```ts -fastify.get( - '/docs', - { - config: { - saas: { - exclude: true, - }, +const config: ApiConfig = { + saas: { + rootDomain: process.env.APP_ROOT_DOMAIN as string, + mainApp: { + subdomain: process.env.MAIN_APP_SUBDOMAIN ?? "app", + domain: process.env.MAIN_APP_DOMAIN as string, }, + multiDatabase: { mode: "disabled" }, // "disabled" | "optional" | "required" + subdomains: "disabled", // "disabled" | "optional" | "required" + }, + user: { + supertokens: { recipes: supertokensRecipesConfig }, }, - async (request, reply) => { - return reply.status(200).send({message: "Account not found"}) - } - ); +}; ``` +**2. Register** — order matters; `saasPlugin` must come after `userPlugin`: -### Cross-Domain Requests (CORS) Configuration +```typescript +import saasPlugin, { accountMigrationPlugin } from "@prefabs.tech/saas-fastify"; -If you are running the frontend and backend on different domains or ports, make sure your backend CORS configuration allows the custom header `X-Account-Id`. +await fastify.register(configPlugin, { config }); +await fastify.register(slonikPlugin, config.slonik); +await fastify.register(mailerPlugin, config.mailer); +await fastify.register(s3Plugin); +await fastify.register(userPlugin); +await fastify.register(saasPlugin); +await fastify.register(migrationPlugin, config.slonik); // app migrations +await fastify.register(accountMigrationPlugin); // per-account migrations +``` -Update your CORS configuration to include it in the allowed headers list, for example: +## Installation -```ts -await fastify.register(import('@fastify/cors'), { - origin: ['http://localhost:50021'], - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: [ - 'Content-Type', - 'Authorization', - 'X-Account-Id' // 👈 Add this header - ], -}); +Install with npm: + +```bash +npm install @prefabs.tech/fastify-config @prefabs.tech/fastify-mailer @prefabs.tech/fastify-s3 @prefabs.tech/fastify-slonik @prefabs.tech/fastify-user slonik supertokens-node @prefabs.tech/saas-fastify +``` + +Install with pnpm: + +```bash +pnpm add --filter "<@scope/project>" @prefabs.tech/fastify-config @prefabs.tech/fastify-mailer @prefabs.tech/fastify-s3 @prefabs.tech/fastify-slonik @prefabs.tech/fastify-user slonik supertokens-node @prefabs.tech/saas-fastify ``` From 6b74e78b004da08913d490585751e7f63aa7dd41 Mon Sep 17 00:00:00 2001 From: uddhab Date: Wed, 8 Apr 2026 08:18:09 +0545 Subject: [PATCH 2/5] tests: add tests and one fix of config --- .../__test__/accountDiscoveryPlugin.test.ts | 179 ++++++++++++++++++ packages/fastify/__test__/config.test.ts | 168 ++++++++++++++++ packages/fastify/__test__/constants.test.ts | 44 +++++ packages/fastify/package.json | 1 + packages/fastify/src/config.ts | 2 +- .../src/lib/__test__/discoverAccount.test.ts | 138 ++++++++++++++ 6 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 packages/fastify/__test__/accountDiscoveryPlugin.test.ts create mode 100644 packages/fastify/__test__/config.test.ts create mode 100644 packages/fastify/__test__/constants.test.ts create mode 100644 packages/fastify/src/lib/__test__/discoverAccount.test.ts diff --git a/packages/fastify/__test__/accountDiscoveryPlugin.test.ts b/packages/fastify/__test__/accountDiscoveryPlugin.test.ts new file mode 100644 index 00000000..9c581c2a --- /dev/null +++ b/packages/fastify/__test__/accountDiscoveryPlugin.test.ts @@ -0,0 +1,179 @@ +import configPlugin from "@prefabs.tech/fastify-config"; +import Fastify from "fastify"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import accountDiscoveryPlugin from "../src/plugins/accountDiscoveryPlugin"; + +vi.mock("../src/lib/discoverAccount", () => ({ + default: vi.fn(), +})); + +import discoverAccount from "../src/lib/discoverAccount"; + +const mockDiscoverAccount = vi.mocked(discoverAccount); + +const baseConfig = { + saas: { rootDomain: "example.test" }, + user: {}, +}; + +type CapturedRequest = { + account: unknown; + dbSchema: unknown; + authEmailPrefix: unknown; +}; + +describe("accountDiscoveryPlugin", () => { + let fastify: ReturnType; + let captured: CapturedRequest; + + beforeEach(() => { + captured = { + account: undefined, + dbSchema: undefined, + authEmailPrefix: undefined, + }; + + fastify = Fastify({ logger: false }); + + fastify.register(configPlugin, { config: baseConfig }); + fastify.register(accountDiscoveryPlugin); + + // Routes used across multiple tests + fastify.get("/test", async (req) => { + captured.account = req.account; + captured.dbSchema = (req as Record).dbSchema; + captured.authEmailPrefix = req.authEmailPrefix; + return { ok: true }; + }); + fastify.get("/", async () => ({ ok: true })); + fastify.get("/auth/login", async () => ({ ok: true })); + fastify.get("/me", async () => ({ ok: true })); + fastify.get( + "/excluded", + { config: { saas: { exclude: true } } }, + async () => ({ ok: true }), + ); + }); + + afterEach(async () => { + await fastify.close(); + vi.clearAllMocks(); + }); + + it("sets request.account when account is discovered", async () => { + const account = { id: "acc-1", name: "Acme" }; + mockDiscoverAccount.mockResolvedValue(account as never); + + await fastify.inject({ method: "GET", url: "/test" }); + + expect(captured.account).toEqual(account); + }); + + it("sets request.dbSchema when account has database field", async () => { + const account = { id: "acc-1", database: "s_abc12345" }; + mockDiscoverAccount.mockResolvedValue(account as never); + + await fastify.inject({ method: "GET", url: "/test" }); + + expect(captured.dbSchema).toBe("s_abc12345"); + }); + + it("sets request.authEmailPrefix to _ when account has slug", async () => { + const account = { id: "acc-1", slug: "myslug" }; + mockDiscoverAccount.mockResolvedValue(account as never); + + await fastify.inject({ method: "GET", url: "/test" }); + + expect(captured.authEmailPrefix).toBe("acc-1_"); + }); + + it("does not set authEmailPrefix when account has no slug", async () => { + const account = { id: "acc-1" }; + mockDiscoverAccount.mockResolvedValue(account as never); + + await fastify.inject({ method: "GET", url: "/test" }); + + expect(captured.authEmailPrefix).toBeUndefined(); + }); + + it("does not set account when discoverAccount returns null", async () => { + mockDiscoverAccount.mockResolvedValue(null as never); + + await fastify.inject({ method: "GET", url: "/test" }); + + expect(captured.account).toBeUndefined(); + }); + + it("replies 404 with error body when discoverAccount throws", async () => { + mockDiscoverAccount.mockRejectedValue(new Error("Account not found")); + + const res = await fastify.inject({ method: "GET", url: "/test" }); + + expect(res.statusCode).toBe(404); + expect(res.json()).toEqual({ error: { message: "Account not found" } }); + }); + + it("extracts hostname from referer header when present", async () => { + mockDiscoverAccount.mockResolvedValue(null as never); + + await fastify.inject({ + method: "GET", + url: "/test", + headers: { referer: "https://tenant.example.test/some/path" }, + }); + + expect(mockDiscoverAccount.mock.calls[0][2]).toBe("tenant.example.test"); + }); + + it("does not call discoverAccount for requests matching a configured app domain", async () => { + // admin.example.test is the default app domain (apps: [{ subdomain: "admin" }]) + await fastify.inject({ + method: "GET", + url: "/test", + headers: { host: "admin.example.test" }, + }); + + expect(mockDiscoverAccount).not.toHaveBeenCalled(); + }); + + it("calls discoverAccount with isRouteExcluded=true for root route", async () => { + mockDiscoverAccount.mockResolvedValue(null as never); + + await fastify.inject({ method: "GET", url: "/" }); + + expect(mockDiscoverAccount).toHaveBeenCalled(); + expect(mockDiscoverAccount.mock.calls[0][4]).toBe(true); + }); + + it("calls discoverAccount with isRouteExcluded=true for /auth/ routes", async () => { + mockDiscoverAccount.mockResolvedValue(null as never); + + await fastify.inject({ method: "GET", url: "/auth/login" }); + + expect(mockDiscoverAccount).toHaveBeenCalled(); + expect(mockDiscoverAccount.mock.calls[0][4]).toBe(true); + }); + + it("calls discoverAccount with isRouteExcluded=true for per-route excluded routes", async () => { + mockDiscoverAccount.mockResolvedValue(null as never); + + await fastify.inject({ method: "GET", url: "/excluded" }); + + expect(mockDiscoverAccount).toHaveBeenCalled(); + expect(mockDiscoverAccount.mock.calls[0][4]).toBe(true); + }); + + it("calls discoverAccount with isRouteExcluded=false for normal routes", async () => { + mockDiscoverAccount.mockResolvedValue(null as never); + + await fastify.inject({ + method: "GET", + url: "/test", + headers: { host: "tenant.example.test" }, + }); + + expect(mockDiscoverAccount).toHaveBeenCalled(); + expect(mockDiscoverAccount.mock.calls[0][4]).toBe(false); + }); +}); diff --git a/packages/fastify/__test__/config.test.ts b/packages/fastify/__test__/config.test.ts new file mode 100644 index 00000000..de3b365b --- /dev/null +++ b/packages/fastify/__test__/config.test.ts @@ -0,0 +1,168 @@ +import { describe, expect, it } from "vitest"; + +import getSaasConfig from "../src/config"; + +import type { ApiConfig } from "@prefabs.tech/fastify-config"; +import type { SaasConfig } from "../src/types"; + +declare module "@prefabs.tech/fastify-config" { + interface ApiConfig { + saas?: SaasConfig; + } +} + +const base = { saas: { rootDomain: "example.test" } } as ApiConfig; + +describe("getSaasConfig", () => { + describe("apps", () => { + it("defaults to a single admin app with computed domain", () => { + const config = getSaasConfig(base); + expect(config.apps).toEqual([ + { name: "admin", subdomain: "admin", domain: "admin.example.test" }, + ]); + }); + + it("computes app domain from subdomain when not provided", () => { + const config = getSaasConfig({ + saas: { + rootDomain: "example.test", + apps: [{ name: "dash", subdomain: "dash" }], + }, + } as ApiConfig); + expect(config.apps[0].domain).toBe("dash.example.test"); + }); + + it("preserves explicit app domain over subdomain computation", () => { + const config = getSaasConfig({ + saas: { + rootDomain: "example.test", + apps: [{ name: "dash", subdomain: "dash", domain: "custom.io" }], + }, + } as ApiConfig); + expect(config.apps[0].domain).toBe("custom.io"); + }); + }); + + describe("excludeRoutePatterns", () => { + it("includes four built-in default patterns", () => { + const config = getSaasConfig(base); + expect(config.excludeRoutePatterns).toHaveLength(4); + expect(config.excludeRoutePatterns[0]).toEqual(/^\/$/) + expect(config.excludeRoutePatterns[1]).toEqual(/^\/auth\//); + expect(config.excludeRoutePatterns[2]).toBe("/me"); + expect(config.excludeRoutePatterns[3]).toBe("/invitation/token/"); + }); + + it("appends custom patterns after the built-in defaults", () => { + const config = getSaasConfig({ + saas: { + rootDomain: "example.test", + excludeRoutePatterns: ["/custom"], + }, + } as ApiConfig); + expect(config.excludeRoutePatterns).toHaveLength(5); + expect(config.excludeRoutePatterns[4]).toBe("/custom"); + }); + }); + + describe("mainApp", () => { + it("defaults mainApp subdomain to 'app'", () => { + const config = getSaasConfig(base); + expect(config.mainApp.subdomain).toBe("app"); + }); + + it("defaults mainApp domain to app.", () => { + const config = getSaasConfig(base); + expect(config.mainApp.domain).toBe("app.example.test"); + }); + + it("sets skipHostnameCheck to true when subdomains is 'disabled'", () => { + const config = getSaasConfig({ + saas: { rootDomain: "example.test", subdomains: "disabled" }, + } as ApiConfig); + expect(config.mainApp.skipHostnameCheck).toBe(true); + }); + + it("sets skipHostnameCheck to false when subdomains is 'required'", () => { + const config = getSaasConfig({ + saas: { rootDomain: "example.test", subdomains: "required" }, + } as ApiConfig); + expect(config.mainApp.skipHostnameCheck).toBe(false); + }); + + it("sets skipHostnameCheck to false when subdomains is 'optional'", () => { + const config = getSaasConfig({ + saas: { rootDomain: "example.test", subdomains: "optional" }, + } as ApiConfig); + expect(config.mainApp.skipHostnameCheck).toBe(false); + }); + }); + + describe("multiDatabase", () => { + it("defaults mode to 'disabled'", () => { + const config = getSaasConfig(base); + expect(config.multiDatabase.mode).toBe("disabled"); + }); + + it("defaults migrations.path to migrations/accounts", () => { + const config = getSaasConfig(base); + expect(config.multiDatabase.migrations.path).toBe("migrations/accounts"); + }); + + it("uses slonik.migrations.path as base for multiDatabase.migrations.path", () => { + const config = getSaasConfig({ + saas: { rootDomain: "example.test" }, + slonik: { migrations: { path: "db/migrations" } }, + } as ApiConfig); + expect(config.multiDatabase.migrations.path).toBe( + "db/migrations/accounts", + ); + }); + }); + + describe("invalid", () => { + it("defaults invalid.domains to []", () => { + const config = getSaasConfig(base); + expect(config.invalid.domains).toEqual([]); + }); + + it("defaults invalid.slugs to ['admin']", () => { + const config = getSaasConfig(base); + expect(config.invalid.slugs).toEqual(["admin"]); + }); + + it("uses custom invalid.slugs when provided", () => { + const config = getSaasConfig({ + saas: { + rootDomain: "example.test", + invalid: { slugs: ["admin", "root", "superuser"] }, + }, + } as ApiConfig); + expect(config.invalid.slugs).toEqual(["admin", "root", "superuser"]); + }); + + it("uses custom invalid.domains when provided", () => { + const config = getSaasConfig({ + saas: { + rootDomain: "example.test", + invalid: { domains: ["blocked.com"] }, + }, + } as ApiConfig); + expect(config.invalid.domains).toEqual(["blocked.com"]); + }); + }); + + describe("tables", () => { + it("defaults all table names", () => { + const config = getSaasConfig(base); + expect(config.tables.accounts.name).toBe("__accounts"); + expect(config.tables.accountTypes.name).toBe("__account_types"); + expect(config.tables.accountTypesI18n.name).toBe("__account_types_i18n"); + expect(config.tables.accountUsers.name).toBe("__account_users"); + expect(config.tables.accountAddresses.name).toBe("__account_addresses"); + expect(config.tables.accountInvitations.name).toBe( + "__account_invitations", + ); + }); + }); +}); diff --git a/packages/fastify/__test__/constants.test.ts b/packages/fastify/__test__/constants.test.ts new file mode 100644 index 00000000..cc40f4ba --- /dev/null +++ b/packages/fastify/__test__/constants.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; + +import { + ACCOUNT_HEADER_NAME, + ACCOUNT_INVITATION_ACCEPT_LINK_PATH, + NANOID_ALPHABET, + NANOID_SIZE, + ROLE_SAAS_ACCOUNT_ADMIN, + ROLE_SAAS_ACCOUNT_MEMBER, + ROLE_SAAS_ACCOUNT_OWNER, +} from "../src/constants"; + +describe("constants", () => { + it("ACCOUNT_HEADER_NAME is x-account-id", () => { + expect(ACCOUNT_HEADER_NAME).toBe("x-account-id"); + }); + + it("ACCOUNT_INVITATION_ACCEPT_LINK_PATH matches expected template", () => { + expect(ACCOUNT_INVITATION_ACCEPT_LINK_PATH).toBe( + "/invitation/token/:token?accountId=:accountId", + ); + }); + + it("NANOID_ALPHABET contains only lowercase letters and digits", () => { + expect(NANOID_ALPHABET).toBe("abcdefghijklmnopqrstuvwxyz0123456789"); + expect(NANOID_ALPHABET).toMatch(/^[a-z0-9]+$/); + }); + + it("NANOID_SIZE is 8", () => { + expect(NANOID_SIZE).toBe(8); + }); + + it("ROLE_SAAS_ACCOUNT_ADMIN is SAAS_ACCOUNT_ADMIN", () => { + expect(ROLE_SAAS_ACCOUNT_ADMIN).toBe("SAAS_ACCOUNT_ADMIN"); + }); + + it("ROLE_SAAS_ACCOUNT_OWNER is SAAS_ACCOUNT_OWNER", () => { + expect(ROLE_SAAS_ACCOUNT_OWNER).toBe("SAAS_ACCOUNT_OWNER"); + }); + + it("ROLE_SAAS_ACCOUNT_MEMBER is SAAS_ACCOUNT_MEMBER", () => { + expect(ROLE_SAAS_ACCOUNT_MEMBER).toBe("SAAS_ACCOUNT_MEMBER"); + }); +}); diff --git a/packages/fastify/package.json b/packages/fastify/package.json index 34d9f6e8..20596c74 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -55,6 +55,7 @@ "@types/node": "24.10.0", "@types/pg": "8.11.10", "@vitest/coverage-istanbul": "3.2.4", + "@vitest/pretty-format": "3.2.4", "eslint": "9.39.2", "fastify": "5.6.0", "fastify-plugin": "5.0.1", diff --git a/packages/fastify/src/config.ts b/packages/fastify/src/config.ts index 8ee1f17d..4c4e96b3 100644 --- a/packages/fastify/src/config.ts +++ b/packages/fastify/src/config.ts @@ -29,7 +29,7 @@ const getSaasConfig = (config: ApiConfig) => { handlers: saasConfig.handlers, invalid: { domains: saasConfig.invalid?.domains || [], - slugs: saasConfig.invalid?.domains || ["admin"], + slugs: saasConfig.invalid?.slugs || ["admin"], }, invitation: saasConfig.invitation, mainApp: { diff --git a/packages/fastify/src/lib/__test__/discoverAccount.test.ts b/packages/fastify/src/lib/__test__/discoverAccount.test.ts new file mode 100644 index 00000000..4a2ce632 --- /dev/null +++ b/packages/fastify/src/lib/__test__/discoverAccount.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; + +import discoverAccount from "../discoverAccount"; + +vi.mock("../../../src/model/accounts/service", () => ({ + default: vi.fn(), +})); + +import AccountService from "../../model/accounts/service"; + +const MockAccountService = vi.mocked(AccountService as unknown as { + new (...args: unknown[]): { + findByHostname: ReturnType; + findOne: ReturnType; + }; +}); + +import type { ApiConfig } from "@prefabs.tech/fastify-config"; +import type { SaasConfig } from "../../types"; + +declare module "@prefabs.tech/fastify-config" { + interface ApiConfig { + saas?: SaasConfig; + } +} + +const mockFindByHostname = vi.fn(); +const mockFindOne = vi.fn(); + +const subdomainConfig = { + saas: { rootDomain: "example.test", subdomains: "required" }, +} as ApiConfig; + +const headerOnlyConfig = { + saas: { rootDomain: "example.test", subdomains: "disabled" }, +} as ApiConfig; + +const database = {} as never; + +beforeEach(() => { + vi.clearAllMocks(); + MockAccountService.mockImplementation(() => ({ + findByHostname: mockFindByHostname, + findOne: mockFindOne, + })); +}); + +describe("discoverAccount", () => { + describe("hostname-based discovery (subdomains active)", () => { + it("resolves account via hostname when hostname is not the main app domain", async () => { + const account = { id: "acc-1", name: "Tenant" }; + mockFindByHostname.mockResolvedValue(account); + + const result = await discoverAccount( + subdomainConfig, + database, + "tenant.example.test", + undefined, + false, + ); + + expect(mockFindByHostname).toHaveBeenCalledWith("tenant.example.test"); + expect(result).toEqual(account); + }); + + it("throws when hostname-based lookup returns null", async () => { + mockFindByHostname.mockResolvedValue(null); + + await expect( + discoverAccount(subdomainConfig, database, "unknown.example.test", undefined, false), + ).rejects.toThrow("Account not found"); + }); + }); + + describe("header-based discovery (skipHostnameCheck / main app domain)", () => { + it("resolves account by id header when subdomains is disabled", async () => { + const account = { id: "acc-1", name: "Header Account" }; + mockFindOne.mockResolvedValue(account); + + const result = await discoverAccount( + headerOnlyConfig, + database, + "app.example.test", + "acc-1", + false, + ); + + expect(mockFindOne).toHaveBeenCalled(); + expect(result).toEqual(account); + }); + + it("resolves account by id header when hostname matches main app domain", async () => { + const account = { id: "acc-2", name: "App Domain Account" }; + mockFindOne.mockResolvedValue(account); + + const result = await discoverAccount( + subdomainConfig, + database, + "app.example.test", + "acc-2", + false, + ); + + expect(mockFindOne).toHaveBeenCalled(); + expect(result).toEqual(account); + }); + + it("returns null when route is excluded and on main app domain", async () => { + const result = await discoverAccount( + headerOnlyConfig, + database, + "app.example.test", + "acc-1", + true, + ); + + expect(result).toBeNull(); + expect(mockFindOne).not.toHaveBeenCalled(); + }); + + it("throws without calling findOne when id header is absent", async () => { + await expect( + discoverAccount(headerOnlyConfig, database, "app.example.test", undefined, false), + ).rejects.toThrow("Account not found"); + + // getAccountById short-circuits on undefined id — findOne is never called + expect(mockFindOne).not.toHaveBeenCalled(); + }); + + it("throws when header-based lookup returns null", async () => { + mockFindOne.mockResolvedValue(null); + + await expect( + discoverAccount(headerOnlyConfig, database, "app.example.test", "missing-id", false), + ).rejects.toThrow("Account not found"); + }); + }); +}); From 55c6f8ab4e055f355d6e5904111c882dc79b8040 Mon Sep 17 00:00:00 2001 From: uddhab Date: Wed, 8 Apr 2026 08:18:55 +0545 Subject: [PATCH 3/5] chore: update gitignore to exclude skills as it is copied from a repo --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d7f019b0..fbffe76d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ **/dist **/*.log* **/node_modules +**/skills +**/skills.old .turbo From 16e12626b58b058801e5c294534569468ca381ec Mon Sep 17 00:00:00 2001 From: uddhab Date: Wed, 8 Apr 2026 08:20:31 +0545 Subject: [PATCH 4/5] style: run code style fixer --- .commitlintrc.json | 4 +--- .github/workflows/test.yml | 3 +-- packages/react/README.md | 8 +++----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.commitlintrc.json b/.commitlintrc.json index 0df1d253..c30e5a97 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -1,5 +1,3 @@ { - "extends": [ - "@commitlint/config-conventional" - ] + "extends": ["@commitlint/config-conventional"] } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c4717e00..180a5e5e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,6 @@ name: "Test suite" -on: - push +on: push jobs: test: diff --git a/packages/react/README.md b/packages/react/README.md index 96b36d1a..10c655df 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -99,7 +99,7 @@ export type SaasConfig = { - **`appRedirection`**: Indicates whether to redirect to the app after signup. - **`termsAndConditionsUrl`**: url for the terms and conditions page. - **`apiBaseUrl`**: The base url for all api requests. -- **`entity`**: The type of accounts allowed. +- **`entity`**: The type of accounts allowed. - **`mainAppSubdomain`**: [Depricated: use `mainApp.subdomain`] Specifies the subdomain for the main application. - **`mainApp`**: Configuration for main app's domain and subdomain - **`subdomain`**: Specifies the subdomain for the main application @@ -111,7 +111,7 @@ export type SaasConfig = { - `"required"`: Subdomains are mandatory for tenant identification. - `"optional"`: Subdomains are optional and can be used if needed. - `"disabled"`: Subdomains are not used in the SaaS platform. - **`ui`**: UI related configuration for customization. + **`ui`**: UI related configuration for customization. ### Routing @@ -187,20 +187,17 @@ This package provides several reusable components and pages to simplify SaaS app #### Pages - **AcceptInvitation pages**: - - `AcceptInvitationPage`: Handles the process of accepting an invitation to join or signup to an account. - `JoinInvitationPage`: Manages the process of joining an account via an invitation. - `SignupInvitationPage`: Facilitates signing up for an account through an invitation. - **Account pages**: - - `AccountAddPage`: Provides a page for adding a new account. - `AccountEditPage`: Allows editing of existing account details. - `AccountSettingsPage`: Displays and manages account information including user and invitations. - `AccountViewPage`: Shows detailed information about a specific account. - **MyAccounts pages**: - - `MyAccountsPage`: Displays a list of accounts associated with the user and allows account switching or management. - **Signup pages**: @@ -271,6 +268,7 @@ ui?: { ``` The current default values are as following + ```{typescript} export const CONFIG_UI_DEFAULT = { account: { From 9d48c06cbc1764a395ddc9b9d3b53ffbc70bd95c Mon Sep 17 00:00:00 2001 From: uddhab Date: Wed, 8 Apr 2026 09:02:03 +0545 Subject: [PATCH 5/5] chore: update dependencies and run code style fix --- .../__test__/accountDiscoveryPlugin.test.ts | 15 +++--- packages/fastify/__test__/config.test.ts | 4 +- .../src/lib/__test__/discoverAccount.test.ts | 47 +++++++++++++------ pnpm-lock.yaml | 3 ++ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/packages/fastify/__test__/accountDiscoveryPlugin.test.ts b/packages/fastify/__test__/accountDiscoveryPlugin.test.ts index 9c581c2a..4745f0c0 100644 --- a/packages/fastify/__test__/accountDiscoveryPlugin.test.ts +++ b/packages/fastify/__test__/accountDiscoveryPlugin.test.ts @@ -2,14 +2,13 @@ import configPlugin from "@prefabs.tech/fastify-config"; import Fastify from "fastify"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import discoverAccount from "../src/lib/discoverAccount"; import accountDiscoveryPlugin from "../src/plugins/accountDiscoveryPlugin"; vi.mock("../src/lib/discoverAccount", () => ({ default: vi.fn(), })); -import discoverAccount from "../src/lib/discoverAccount"; - const mockDiscoverAccount = vi.mocked(discoverAccount); const baseConfig = { @@ -98,7 +97,7 @@ describe("accountDiscoveryPlugin", () => { }); it("does not set account when discoverAccount returns null", async () => { - mockDiscoverAccount.mockResolvedValue(null as never); + mockDiscoverAccount.mockResolvedValue(undefined as never); await fastify.inject({ method: "GET", url: "/test" }); @@ -115,7 +114,7 @@ describe("accountDiscoveryPlugin", () => { }); it("extracts hostname from referer header when present", async () => { - mockDiscoverAccount.mockResolvedValue(null as never); + mockDiscoverAccount.mockResolvedValue(undefined as never); await fastify.inject({ method: "GET", @@ -138,7 +137,7 @@ describe("accountDiscoveryPlugin", () => { }); it("calls discoverAccount with isRouteExcluded=true for root route", async () => { - mockDiscoverAccount.mockResolvedValue(null as never); + mockDiscoverAccount.mockResolvedValue(undefined as never); await fastify.inject({ method: "GET", url: "/" }); @@ -147,7 +146,7 @@ describe("accountDiscoveryPlugin", () => { }); it("calls discoverAccount with isRouteExcluded=true for /auth/ routes", async () => { - mockDiscoverAccount.mockResolvedValue(null as never); + mockDiscoverAccount.mockResolvedValue(undefined as never); await fastify.inject({ method: "GET", url: "/auth/login" }); @@ -156,7 +155,7 @@ describe("accountDiscoveryPlugin", () => { }); it("calls discoverAccount with isRouteExcluded=true for per-route excluded routes", async () => { - mockDiscoverAccount.mockResolvedValue(null as never); + mockDiscoverAccount.mockResolvedValue(undefined as never); await fastify.inject({ method: "GET", url: "/excluded" }); @@ -165,7 +164,7 @@ describe("accountDiscoveryPlugin", () => { }); it("calls discoverAccount with isRouteExcluded=false for normal routes", async () => { - mockDiscoverAccount.mockResolvedValue(null as never); + mockDiscoverAccount.mockResolvedValue(undefined as never); await fastify.inject({ method: "GET", diff --git a/packages/fastify/__test__/config.test.ts b/packages/fastify/__test__/config.test.ts index de3b365b..18cae2b8 100644 --- a/packages/fastify/__test__/config.test.ts +++ b/packages/fastify/__test__/config.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it } from "vitest"; import getSaasConfig from "../src/config"; -import type { ApiConfig } from "@prefabs.tech/fastify-config"; import type { SaasConfig } from "../src/types"; +import type { ApiConfig } from "@prefabs.tech/fastify-config"; declare module "@prefabs.tech/fastify-config" { interface ApiConfig { @@ -47,7 +47,7 @@ describe("getSaasConfig", () => { it("includes four built-in default patterns", () => { const config = getSaasConfig(base); expect(config.excludeRoutePatterns).toHaveLength(4); - expect(config.excludeRoutePatterns[0]).toEqual(/^\/$/) + expect(config.excludeRoutePatterns[0]).toEqual(/^\/$/); expect(config.excludeRoutePatterns[1]).toEqual(/^\/auth\//); expect(config.excludeRoutePatterns[2]).toBe("/me"); expect(config.excludeRoutePatterns[3]).toBe("/invitation/token/"); diff --git a/packages/fastify/src/lib/__test__/discoverAccount.test.ts b/packages/fastify/src/lib/__test__/discoverAccount.test.ts index 4a2ce632..0f5fb9d4 100644 --- a/packages/fastify/src/lib/__test__/discoverAccount.test.ts +++ b/packages/fastify/src/lib/__test__/discoverAccount.test.ts @@ -1,22 +1,23 @@ import { describe, expect, it, vi, beforeEach } from "vitest"; +import AccountService from "../../model/accounts/service"; import discoverAccount from "../discoverAccount"; vi.mock("../../../src/model/accounts/service", () => ({ default: vi.fn(), })); -import AccountService from "../../model/accounts/service"; - -const MockAccountService = vi.mocked(AccountService as unknown as { - new (...args: unknown[]): { - findByHostname: ReturnType; - findOne: ReturnType; - }; -}); +const MockAccountService = vi.mocked( + AccountService as unknown as { + new (...arguments_: unknown[]): { + findByHostname: ReturnType; + findOne: ReturnType; + }; + }, +); -import type { ApiConfig } from "@prefabs.tech/fastify-config"; import type { SaasConfig } from "../../types"; +import type { ApiConfig } from "@prefabs.tech/fastify-config"; declare module "@prefabs.tech/fastify-config" { interface ApiConfig { @@ -64,10 +65,16 @@ describe("discoverAccount", () => { }); it("throws when hostname-based lookup returns null", async () => { - mockFindByHostname.mockResolvedValue(null); + mockFindByHostname.mockResolvedValue(); await expect( - discoverAccount(subdomainConfig, database, "unknown.example.test", undefined, false), + discoverAccount( + subdomainConfig, + database, + "unknown.example.test", + undefined, + false, + ), ).rejects.toThrow("Account not found"); }); }); @@ -120,7 +127,13 @@ describe("discoverAccount", () => { it("throws without calling findOne when id header is absent", async () => { await expect( - discoverAccount(headerOnlyConfig, database, "app.example.test", undefined, false), + discoverAccount( + headerOnlyConfig, + database, + "app.example.test", + undefined, + false, + ), ).rejects.toThrow("Account not found"); // getAccountById short-circuits on undefined id — findOne is never called @@ -128,10 +141,16 @@ describe("discoverAccount", () => { }); it("throws when header-based lookup returns null", async () => { - mockFindOne.mockResolvedValue(null); + mockFindOne.mockResolvedValue(); await expect( - discoverAccount(headerOnlyConfig, database, "app.example.test", "missing-id", false), + discoverAccount( + headerOnlyConfig, + database, + "app.example.test", + "missing-id", + false, + ), ).rejects.toThrow("Account not found"); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9b54a42..be52737d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: '@vitest/coverage-istanbul': specifier: 3.2.4 version: 3.2.4(vitest@3.2.4(@types/node@24.10.0)(jiti@1.21.6)(jsdom@25.0.1)) + '@vitest/pretty-format': + specifier: 3.2.4 + version: 3.2.4 eslint: specifier: 9.39.2 version: 9.39.2(jiti@1.21.6)