diff --git a/README.md b/README.md
index 51d3702..0003331 100644
--- a/README.md
+++ b/README.md
@@ -109,7 +109,7 @@ Discord [here](https://discord.gg/3hWPDTA4kD)
### Testing
- [x] Manual test scenario for auth and sub workflows passing
-- [ ] Unit tests for server functions
+- [x] Unit test framework (vitest)
- [ ] Integration tests for auth and sub workflows
## Special Mention
@@ -120,8 +120,22 @@ This https://blog.checklyhq.com/building-a-multi-tenant-saas-data-model/ Article
The focus is on separation of concerns and avoiding vendor lock in.
+### Diagram
+
+### Walkthrough
+
+[
](https://www.youtube.com/watch?v=AFfbGuJYRqI)
+
+### Tricky Decisions
+
+_Composition over options API_ - I have decided to use composition api and setup functions accross the board including components, pages and Pinia stores. I was resistant at first, especially with the stores as I was used to Vuex but have come to the conclusion that it is easier to go one approach all over. It's also the latest and greatest and folks don't like to use a starter that starts behind the cutting edge.
+
+_Prisma over Supabase API_ - I went with Prisma for direct DB access rather than use the Supabase client. This is Primarily to avoid lock-in with Supabase too much. Supabase is great but I thought burdening my users with a future situation where it's difficult to move off it wouldn't be very cool. Also, I really like how Prisma handles schema changes and updates to the client layer and types with just two bash commands, after using other approaches, I find this super smooth.
+
+_Trpc over REST_ - Primarily for full thickness types without duplication on the client. Also I think the remote procedure call paradigm works well. Note however that I still include a [REST endpoint example](/server/api/note.ts) for flexibility. My preference for mobile is Flutter and there is not a Trpc client for Flutter that i'm aware off so it was important for me to make sure REST works also.
+
## Externals Setup
Things you gotta do that aren't code (and are therefore not very interesting)
@@ -224,12 +238,22 @@ Your webhook signing secret is whsec_xxxxxxxxxxxxx (^C to quit)
take ths signing secret and update the STRIPE_ENDPOINT_SECRET value in .env
+### Start the Server
+
Start the development server on http://localhost:3000
```bash
npm run dev
```
+### Running the Tests
+
+There are a few unit tests, just for the stores because I needed to refactor. Feel free to extend the tests for your use cases, or not, it's your SaaS, not mine.
+
+```bash
+npm run test
+```
+
## Production
Build the application for production:
diff --git a/package-lock.json b/package-lock.json
index 4065bce..3dea166 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,6 +24,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
+ "@nuxt/test-utils": "^3.8.1",
"@nuxtjs/supabase": "^1.1.3",
"@nuxtjs/tailwindcss": "^6.6.6",
"@prisma/client": "^5.2.0",
@@ -33,7 +34,8 @@
"nuxt-icon": "^0.3.3",
"prisma": "^5.2.0",
"ts-node": "^10.9.1",
- "typescript": "^5.0.3"
+ "typescript": "^5.0.3",
+ "vitest": "^0.33.0"
}
},
"node_modules/@alloc/quick-lru": {
@@ -1106,6 +1108,18 @@
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"dev": true
},
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -1421,6 +1435,227 @@
"url": "https://opencollective.com/node-fetch"
}
},
+ "node_modules/@nuxt/test-utils": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.8.1.tgz",
+ "integrity": "sha512-8ZQ+OZ7z5Sc5KG2aCvk0piheYSpGb2UQJMCWr8ORwEyZIw4awrkkwGzUY06e344E4StvJB8zxN122MEcFNOkow==",
+ "dev": true,
+ "dependencies": {
+ "@nuxt/kit": "3.8.1",
+ "@nuxt/schema": "3.8.1",
+ "consola": "^3.2.3",
+ "defu": "^6.1.3",
+ "execa": "^8.0.1",
+ "get-port-please": "^3.1.1",
+ "ofetch": "^1.3.3",
+ "pathe": "^1.1.1",
+ "ufo": "^1.3.1"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ },
+ "peerDependencies": {
+ "@jest/globals": "^29.5.0",
+ "playwright-core": "^1.34.3",
+ "vitest": "^0.30.0 || ^0.31.0 || ^0.32.0 || ^0.33.0",
+ "vue": "^3.3.4"
+ },
+ "peerDependenciesMeta": {
+ "@jest/globals": {
+ "optional": true
+ },
+ "playwright-core": {
+ "optional": true
+ },
+ "vitest": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/@nuxt/kit": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.8.1.tgz",
+ "integrity": "sha512-DrhG1Z85iH68QOTkgfb0HVfM2g7+CfcMWrFWMDwck9ofyM2RXQUZyfmvMedwBnui1AjjpgpLO9078yZM+RqNUg==",
+ "dev": true,
+ "dependencies": {
+ "@nuxt/schema": "3.8.1",
+ "c12": "^1.5.1",
+ "consola": "^3.2.3",
+ "defu": "^6.1.3",
+ "globby": "^13.2.2",
+ "hash-sum": "^2.0.0",
+ "ignore": "^5.2.4",
+ "jiti": "^1.21.0",
+ "knitwork": "^1.0.0",
+ "mlly": "^1.4.2",
+ "pathe": "^1.1.1",
+ "pkg-types": "^1.0.3",
+ "scule": "^1.0.0",
+ "semver": "^7.5.4",
+ "ufo": "^1.3.1",
+ "unctx": "^2.3.1",
+ "unimport": "^3.4.0",
+ "untyped": "^1.4.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/@nuxt/schema": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.8.1.tgz",
+ "integrity": "sha512-fSaWRcI/2mUskfTZTGSnH6Ny0x05CRzylbVn6WFV0d6UEKIVy42Qd6n+h7yoFfp4cq4nji6u16PT4SqS1DEhsw==",
+ "dev": true,
+ "dependencies": {
+ "@nuxt/ui-templates": "^1.3.1",
+ "consola": "^3.2.3",
+ "defu": "^6.1.3",
+ "hookable": "^5.5.3",
+ "pathe": "^1.1.1",
+ "pkg-types": "^1.0.3",
+ "std-env": "^3.4.3",
+ "ufo": "^1.3.1",
+ "unimport": "^3.4.0",
+ "untyped": "^1.4.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/npm-run-path": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+ "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@nuxt/test-utils/node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@nuxt/ui-templates": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@nuxt/ui-templates/-/ui-templates-1.3.1.tgz",
@@ -2189,6 +2424,12 @@
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
"node_modules/@supabase/functions-js": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz",
@@ -2330,6 +2571,21 @@
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"devOptional": true
},
+ "node_modules/@types/chai": {
+ "version": "4.3.11",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz",
+ "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==",
+ "dev": true
+ },
+ "node_modules/@types/chai-subset": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz",
+ "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==",
+ "dev": true,
+ "dependencies": {
+ "@types/chai": "*"
+ }
+ },
"node_modules/@types/eslint": {
"version": "8.44.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz",
@@ -2590,6 +2846,74 @@
"vue": "^3.0.0"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz",
+ "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/spy": "0.33.0",
+ "@vitest/utils": "0.33.0",
+ "chai": "^4.3.7"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz",
+ "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "0.33.0",
+ "p-limit": "^4.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz",
+ "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==",
+ "dev": true,
+ "dependencies": {
+ "magic-string": "^0.30.1",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.5.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz",
+ "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==",
+ "dev": true,
+ "dependencies": {
+ "tinyspy": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz",
+ "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==",
+ "dev": true,
+ "dependencies": {
+ "diff-sequences": "^29.4.3",
+ "loupe": "^2.3.6",
+ "pretty-format": "^29.5.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/@vue-macros/common": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.7.2.tgz",
@@ -3232,6 +3556,15 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/ast-kit": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.10.0.tgz",
@@ -3636,6 +3969,24 @@
}
]
},
+ "node_modules/chai": {
+ "version": "4.3.10",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz",
+ "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==",
+ "dev": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
@@ -3648,6 +3999,18 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -4306,6 +4669,18 @@
}
}
},
+ "node_modules/deep-eql": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
@@ -4331,9 +4706,9 @@
}
},
"node_modules/defu": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz",
- "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ=="
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.3.tgz",
+ "integrity": "sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ=="
},
"node_modules/delayed-stream": {
"version": "1.0.0",
@@ -4411,6 +4786,15 @@
"node": ">=0.3.1"
}
},
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -5100,6 +5484,15 @@
"node": "6.* || 8.* || >= 10.*"
}
},
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@@ -5115,9 +5508,9 @@
}
},
"node_modules/get-port-please": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.0.1.tgz",
- "integrity": "sha512-R5pcVO8Z1+pVDu8Ml3xaJCEkBiiy1VQN9za0YqH8GIi1nIqD4IzQhzY6dDzMRtdS1lyiGlucRzm8IN8wtLIXng==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.1.tgz",
+ "integrity": "sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA==",
"dev": true
},
"node_modules/get-stream": {
@@ -6354,6 +6747,15 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"dev": true
},
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -7314,6 +7716,21 @@
"openapi-typescript": "bin/cli.js"
}
},
+ "node_modules/p-limit": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+ "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parent-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz",
@@ -7425,6 +7842,15 @@
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
"integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q=="
},
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -8342,6 +8768,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/prisma": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.2.0.tgz",
@@ -8462,6 +8914,12 @@
"flat": "^5.0.2"
}
},
+ "node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -9052,6 +9510,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -9130,6 +9594,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
@@ -9671,6 +10141,30 @@
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
"dev": true
},
+ "node_modules/tinybench": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz",
+ "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==",
+ "dev": true
+ },
+ "node_modules/tinypool": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz",
+ "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz",
+ "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -9792,6 +10286,15 @@
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
"dev": true
},
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/type-fest": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
@@ -10754,6 +11257,83 @@
"@esbuild/win32-x64": "0.18.20"
}
},
+ "node_modules/vitest": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz",
+ "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/chai": "^4.3.5",
+ "@types/chai-subset": "^1.3.3",
+ "@types/node": "*",
+ "@vitest/expect": "0.33.0",
+ "@vitest/runner": "0.33.0",
+ "@vitest/snapshot": "0.33.0",
+ "@vitest/spy": "0.33.0",
+ "@vitest/utils": "0.33.0",
+ "acorn": "^8.9.0",
+ "acorn-walk": "^8.2.0",
+ "cac": "^6.7.14",
+ "chai": "^4.3.7",
+ "debug": "^4.3.4",
+ "local-pkg": "^0.4.3",
+ "magic-string": "^0.30.1",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.3.3",
+ "strip-literal": "^1.0.1",
+ "tinybench": "^2.5.0",
+ "tinypool": "^0.6.0",
+ "vite": "^3.0.0 || ^4.0.0",
+ "vite-node": "0.33.0",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": ">=v14.18.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@vitest/browser": "*",
+ "@vitest/ui": "*",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "playwright": "*",
+ "safaridriver": "*",
+ "webdriverio": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "playwright": {
+ "optional": true
+ },
+ "safaridriver": {
+ "optional": true
+ },
+ "webdriverio": {
+ "optional": true
+ }
+ }
+ },
"node_modules/vscode-jsonrpc": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
@@ -11053,6 +11633,22 @@
"node": ">= 8"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
+ "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+ "dev": true,
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -11170,6 +11766,18 @@
"node": ">=6"
}
},
+ "node_modules/yocto-queue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/zhead": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/zhead/-/zhead-2.0.10.tgz",
@@ -11955,6 +12563,15 @@
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"dev": true
},
+ "@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "requires": {
+ "@sinclair/typebox": "^0.27.8"
+ }
+ },
"@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -12218,6 +12835,146 @@
}
}
},
+ "@nuxt/test-utils": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.8.1.tgz",
+ "integrity": "sha512-8ZQ+OZ7z5Sc5KG2aCvk0piheYSpGb2UQJMCWr8ORwEyZIw4awrkkwGzUY06e344E4StvJB8zxN122MEcFNOkow==",
+ "dev": true,
+ "requires": {
+ "@nuxt/kit": "3.8.1",
+ "@nuxt/schema": "3.8.1",
+ "consola": "^3.2.3",
+ "defu": "^6.1.3",
+ "execa": "^8.0.1",
+ "get-port-please": "^3.1.1",
+ "ofetch": "^1.3.3",
+ "pathe": "^1.1.1",
+ "ufo": "^1.3.1"
+ },
+ "dependencies": {
+ "@nuxt/kit": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.8.1.tgz",
+ "integrity": "sha512-DrhG1Z85iH68QOTkgfb0HVfM2g7+CfcMWrFWMDwck9ofyM2RXQUZyfmvMedwBnui1AjjpgpLO9078yZM+RqNUg==",
+ "dev": true,
+ "requires": {
+ "@nuxt/schema": "3.8.1",
+ "c12": "^1.5.1",
+ "consola": "^3.2.3",
+ "defu": "^6.1.3",
+ "globby": "^13.2.2",
+ "hash-sum": "^2.0.0",
+ "ignore": "^5.2.4",
+ "jiti": "^1.21.0",
+ "knitwork": "^1.0.0",
+ "mlly": "^1.4.2",
+ "pathe": "^1.1.1",
+ "pkg-types": "^1.0.3",
+ "scule": "^1.0.0",
+ "semver": "^7.5.4",
+ "ufo": "^1.3.1",
+ "unctx": "^2.3.1",
+ "unimport": "^3.4.0",
+ "untyped": "^1.4.0"
+ }
+ },
+ "@nuxt/schema": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.8.1.tgz",
+ "integrity": "sha512-fSaWRcI/2mUskfTZTGSnH6Ny0x05CRzylbVn6WFV0d6UEKIVy42Qd6n+h7yoFfp4cq4nji6u16PT4SqS1DEhsw==",
+ "dev": true,
+ "requires": {
+ "@nuxt/ui-templates": "^1.3.1",
+ "consola": "^3.2.3",
+ "defu": "^6.1.3",
+ "hookable": "^5.5.3",
+ "pathe": "^1.1.1",
+ "pkg-types": "^1.0.3",
+ "std-env": "^3.4.3",
+ "ufo": "^1.3.1",
+ "unimport": "^3.4.0",
+ "untyped": "^1.4.0"
+ }
+ },
+ "execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ }
+ },
+ "get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true
+ },
+ "human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true
+ },
+ "is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true
+ },
+ "mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true
+ },
+ "npm-run-path": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+ "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+ "dev": true,
+ "requires": {
+ "path-key": "^4.0.0"
+ }
+ },
+ "onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^4.0.0"
+ }
+ },
+ "path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true
+ },
+ "strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true
+ }
+ }
+ },
"@nuxt/ui-templates": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@nuxt/ui-templates/-/ui-templates-1.3.1.tgz",
@@ -12678,6 +13435,12 @@
}
}
},
+ "@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
"@supabase/functions-js": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz",
@@ -12802,6 +13565,21 @@
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"devOptional": true
},
+ "@types/chai": {
+ "version": "4.3.11",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz",
+ "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==",
+ "dev": true
+ },
+ "@types/chai-subset": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz",
+ "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==",
+ "dev": true,
+ "requires": {
+ "@types/chai": "*"
+ }
+ },
"@types/eslint": {
"version": "8.44.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz",
@@ -13015,6 +13793,59 @@
"@vue/babel-plugin-jsx": "^1.1.5"
}
},
+ "@vitest/expect": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz",
+ "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==",
+ "dev": true,
+ "requires": {
+ "@vitest/spy": "0.33.0",
+ "@vitest/utils": "0.33.0",
+ "chai": "^4.3.7"
+ }
+ },
+ "@vitest/runner": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz",
+ "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==",
+ "dev": true,
+ "requires": {
+ "@vitest/utils": "0.33.0",
+ "p-limit": "^4.0.0",
+ "pathe": "^1.1.1"
+ }
+ },
+ "@vitest/snapshot": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz",
+ "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==",
+ "dev": true,
+ "requires": {
+ "magic-string": "^0.30.1",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.5.0"
+ }
+ },
+ "@vitest/spy": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz",
+ "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==",
+ "dev": true,
+ "requires": {
+ "tinyspy": "^2.1.1"
+ }
+ },
+ "@vitest/utils": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz",
+ "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==",
+ "dev": true,
+ "requires": {
+ "diff-sequences": "^29.4.3",
+ "loupe": "^2.3.6",
+ "pretty-format": "^29.5.0"
+ }
+ },
"@vue-macros/common": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.7.2.tgz",
@@ -13568,6 +14399,12 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
+ "assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
"ast-kit": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.10.0.tgz",
@@ -13832,12 +14669,36 @@
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001523.tgz",
"integrity": "sha512-I5q5cisATTPZ1mc588Z//pj/Ox80ERYDfR71YnvY7raS/NOk8xXlZcB0sF7JdqaV//kOaa6aus7lRfpdnt1eBA=="
},
+ "chai": {
+ "version": "4.3.10",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz",
+ "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==",
+ "dev": true,
+ "requires": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.8"
+ }
+ },
"chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"dev": true
},
+ "check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "requires": {
+ "get-func-name": "^2.0.2"
+ }
+ },
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -14315,6 +15176,15 @@
"ms": "2.1.2"
}
},
+ "deep-eql": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "dev": true,
+ "requires": {
+ "type-detect": "^4.0.0"
+ }
+ },
"deep-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
@@ -14334,9 +15204,9 @@
"dev": true
},
"defu": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz",
- "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ=="
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.3.tgz",
+ "integrity": "sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ=="
},
"delayed-stream": {
"version": "1.0.0",
@@ -14395,6 +15265,12 @@
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"devOptional": true
},
+ "diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true
+ },
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -14918,6 +15794,12 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
+ "get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true
+ },
"get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@@ -14930,9 +15812,9 @@
}
},
"get-port-please": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.0.1.tgz",
- "integrity": "sha512-R5pcVO8Z1+pVDu8Ml3xaJCEkBiiy1VQN9za0YqH8GIi1nIqD4IzQhzY6dDzMRtdS1lyiGlucRzm8IN8wtLIXng==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.1.tgz",
+ "integrity": "sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA==",
"dev": true
},
"get-stream": {
@@ -15888,6 +16770,15 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"dev": true
},
+ "loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "requires": {
+ "get-func-name": "^2.0.1"
+ }
+ },
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -16596,6 +17487,15 @@
"yargs-parser": "^21.1.1"
}
},
+ "p-limit": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+ "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^1.0.0"
+ }
+ },
"parent-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz",
@@ -16683,6 +17583,12 @@
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
"integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q=="
},
+ "pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true
+ },
"perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -17247,6 +18153,25 @@
"integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==",
"dev": true
},
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ }
+ }
+ },
"prisma": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.2.0.tgz",
@@ -17331,6 +18256,12 @@
"flat": "^5.0.2"
}
},
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -17765,6 +18696,12 @@
"object-inspect": "^1.9.0"
}
},
+ "siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -17832,6 +18769,12 @@
}
}
},
+ "stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
"standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
@@ -18217,6 +19160,24 @@
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
"dev": true
},
+ "tinybench": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz",
+ "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==",
+ "dev": true
+ },
+ "tinypool": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz",
+ "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==",
+ "dev": true
+ },
+ "tinyspy": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz",
+ "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==",
+ "dev": true
+ },
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -18299,6 +19260,12 @@
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
"dev": true
},
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
"type-fest": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
@@ -18837,6 +19804,38 @@
}
}
},
+ "vitest": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz",
+ "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==",
+ "dev": true,
+ "requires": {
+ "@types/chai": "^4.3.5",
+ "@types/chai-subset": "^1.3.3",
+ "@types/node": "*",
+ "@vitest/expect": "0.33.0",
+ "@vitest/runner": "0.33.0",
+ "@vitest/snapshot": "0.33.0",
+ "@vitest/spy": "0.33.0",
+ "@vitest/utils": "0.33.0",
+ "acorn": "^8.9.0",
+ "acorn-walk": "^8.2.0",
+ "cac": "^6.7.14",
+ "chai": "^4.3.7",
+ "debug": "^4.3.4",
+ "local-pkg": "^0.4.3",
+ "magic-string": "^0.30.1",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.3.3",
+ "strip-literal": "^1.0.1",
+ "tinybench": "^2.5.0",
+ "tinypool": "^0.6.0",
+ "vite": "^3.0.0 || ^4.0.0",
+ "vite-node": "0.33.0",
+ "why-is-node-running": "^2.2.2"
+ }
+ },
"vscode-jsonrpc": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
@@ -19070,6 +20069,16 @@
"isexe": "^2.0.0"
}
},
+ "why-is-node-running": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
+ "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+ "dev": true,
+ "requires": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ }
+ },
"wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -19160,6 +20169,12 @@
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"devOptional": true
},
+ "yocto-queue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+ "dev": true
+ },
"zhead": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/zhead/-/zhead-2.0.10.tgz",
diff --git a/package.json b/package.json
index 11680ae..0963414 100644
--- a/package.json
+++ b/package.json
@@ -16,9 +16,11 @@
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
- "postinstall": "prisma generate && nuxt prepare"
+ "postinstall": "prisma generate && nuxt prepare",
+ "test": "vitest"
},
"devDependencies": {
+ "@nuxt/test-utils": "^3.8.1",
"@nuxtjs/supabase": "^1.1.3",
"@nuxtjs/tailwindcss": "^6.6.6",
"@prisma/client": "^5.2.0",
@@ -28,7 +30,8 @@
"nuxt-icon": "^0.3.3",
"prisma": "^5.2.0",
"ts-node": "^10.9.1",
- "typescript": "^5.0.3"
+ "typescript": "^5.0.3",
+ "vitest": "^0.33.0"
},
"dependencies": {
"@pinia/nuxt": "^0.4.6",
diff --git a/stores/account.store.ts b/stores/account.store.ts
index 6ca2319..da90bb8 100644
--- a/stores/account.store.ts
+++ b/stores/account.store.ts
@@ -1,5 +1,6 @@
import { ACCOUNT_ACCESS } from '~~/prisma/account-access-enum';
import { defineStore } from 'pinia';
+import { ref, computed } from 'vue';
import { FullDBUser, MembershipWithUser } from '~~/lib/services/service.types';
/*
@@ -22,167 +23,183 @@ so that other routers can use them to filter results to the active user and acco
| | |
account account acccount*
*/
-interface State {
- dbUser: FullDBUser | null;
- activeAccountId: number | null;
- activeAccountMembers: MembershipWithUser[];
-}
+export const useAccountStore = defineStore('account', () => {
+ const dbUser = ref(null);
+ const activeAccountId = ref(null);
+ const activeAccountMembers = ref([]);
+ const activeMembership = computed(() =>
+ dbUser?.value?.memberships.find(m => m.account_id === activeAccountId.value)
+ );
-export const useAccountStore = defineStore('account', {
- state: (): State => {
- return {
- dbUser: null,
- activeAccountId: null,
- activeAccountMembers: []
- };
- },
- getters: {
- activeMembership: state =>
- state.dbUser?.memberships.find(
- m => m.account_id === state.activeAccountId
- )
- },
- actions: {
- async init() {
- const { $client } = useNuxtApp();
- if (!this.dbUser) {
- const { dbUser } = await $client.auth.getDBUser.query();
- if (dbUser) {
- this.dbUser = dbUser;
- }
- }
- if (!this.activeAccountId) {
- const { activeAccountId } =
- await $client.account.getActiveAccountId.query();
- if (activeAccountId) {
- this.activeAccountId = activeAccountId;
- }
- }
- },
- signout() {
- this.dbUser = null;
- this.activeAccountId = null;
- this.activeAccountMembers = [];
- },
- async getActiveAccountMembers() {
- if (
- this.activeMembership &&
- (this.activeMembership.access === ACCOUNT_ACCESS.ADMIN ||
- this.activeMembership.access === ACCOUNT_ACCESS.OWNER)
- ) {
- const { $client } = useNuxtApp();
- const { data: memberships } =
- await $client.account.getAccountMembers.useQuery();
- if (memberships.value?.memberships) {
- this.activeAccountMembers = memberships.value?.memberships;
- }
- }
- },
- async changeActiveAccount(account_id: number) {
- const { $client } = useNuxtApp();
- await $client.account.changeActiveAccount.mutate({ account_id }); // sets active account on context for other routers and sets the preference in a cookie
-
- this.activeAccountId = account_id; // because this is used as a trigger to some other components, NEEDS TO BE AFTER THE MUTATE CALL
- await this.getActiveAccountMembers(); // these relate to the active account and need to ber re-fetched
- },
- async changeAccountName(new_name: string) {
- if (!this.activeMembership) {
- return;
- }
- const { $client } = useNuxtApp();
- const { account } = await $client.account.changeAccountName.mutate({
- new_name
- });
- if (account) {
- this.activeMembership.account.name = account.name;
- }
- },
- async acceptPendingMembership(membership_id: number) {
- const { $client } = useNuxtApp();
- const { data: membership } =
- await $client.account.acceptPendingMembership.useQuery({
- membership_id
- });
-
- if (membership.value && membership.value.membership?.pending === false) {
- for (const m of this.activeAccountMembers) {
- if (m.id === membership_id) {
- m.pending = false;
- }
- }
- }
- },
- async rejectPendingMembership(membership_id: number) {
- const { $client } = useNuxtApp();
- const { data: membership } =
- await $client.account.rejectPendingMembership.useQuery({
- membership_id
- });
-
- if (membership.value) {
- this.activeAccountMembers = this.activeAccountMembers.filter(
- m => m.id !== membership_id
- );
- }
- },
- async deleteMembership(membership_id: number) {
- const { $client } = useNuxtApp();
- const { data: membership } =
- await $client.account.deleteMembership.useQuery({ membership_id });
-
- if (membership.value) {
- this.activeAccountMembers = this.activeAccountMembers.filter(
- m => m.id !== membership_id
- );
- }
- },
- async rotateJoinPassword() {
- const { $client } = useNuxtApp();
- const { account } = await $client.account.rotateJoinPassword.mutate();
- if (account && this.activeMembership) {
- this.activeMembership.account = account;
- }
- },
- async joinUserToAccountPending(account_id: number) {
- if (!this.dbUser) {
- return;
- }
- const { $client } = useNuxtApp();
- const { membership } =
- await $client.account.joinUserToAccountPending.mutate({
- account_id,
- user_id: this.dbUser.id
- });
- if (membership && this.activeMembership) {
- this.dbUser?.memberships.push(membership);
- }
- },
- async changeUserAccessWithinAccount(
- user_id: number,
- access: ACCOUNT_ACCESS
- ) {
- const { $client } = useNuxtApp();
- const { membership } =
- await $client.account.changeUserAccessWithinAccount.mutate({
- user_id,
- access
- });
- if (membership) {
- for (const m of this.activeAccountMembers) {
- if (m.id === membership.id) {
- m.access = membership.access;
- }
- }
- }
- },
- async claimOwnershipOfAccount() {
- const { $client } = useNuxtApp();
- const { memberships } =
- await $client.account.claimOwnershipOfAccount.mutate();
- if (memberships) {
- this.activeAccountMembers = memberships;
- this.activeMembership!.access = ACCOUNT_ACCESS.OWNER;
+ const init = async () => {
+ const { $client } = useNuxtApp();
+ if (!dbUser.value) {
+ const { dbUser: _dbUser } = await $client.auth.getDBUser.query();
+ if (_dbUser) {
+ dbUser.value = _dbUser;
}
}
- }
+ if (!activeAccountId.value) {
+ const { activeAccountId: _activeAccountId } =
+ await $client.account.getActiveAccountId.query();
+ if (_activeAccountId) {
+ activeAccountId.value = _activeAccountId;
+ }
+ }
+ };
+
+ const signout = () => {
+ dbUser.value = null;
+ activeAccountId.value = null;
+ activeAccountMembers.value = [];
+ };
+
+ const getActiveAccountMembers = async () => {
+ if (
+ activeMembership.value &&
+ (activeMembership.value.access === ACCOUNT_ACCESS.ADMIN ||
+ activeMembership.value.access === ACCOUNT_ACCESS.OWNER)
+ ) {
+ const { $client } = useNuxtApp();
+ const { data: memberships } =
+ await $client.account.getAccountMembers.useQuery();
+ if (memberships.value?.memberships) {
+ activeAccountMembers.value = memberships.value?.memberships;
+ }
+ }
+ };
+
+ const changeActiveAccount = async (account_id: number) => {
+ const { $client } = useNuxtApp();
+ await $client.account.changeActiveAccount.mutate({ account_id }); // sets active account on context for other routers and sets the preference in a cookie
+
+ activeAccountId.value = account_id; // because this is used as a trigger to some other components, NEEDS TO BE AFTER THE MUTATE CALL
+ await getActiveAccountMembers(); // these relate to the active account and need to ber re-fetched
+ };
+
+ const changeAccountName = async (new_name: string) => {
+ if (!activeMembership.value) {
+ return;
+ }
+ const { $client } = useNuxtApp();
+ const { account } = await $client.account.changeAccountName.mutate({
+ new_name
+ });
+ if (account) {
+ activeMembership.value.account.name = account.name;
+ }
+ };
+
+ const acceptPendingMembership = async (membership_id: number) => {
+ const { $client } = useNuxtApp();
+ const { data: membership } =
+ await $client.account.acceptPendingMembership.useQuery({
+ membership_id
+ });
+
+ if (membership.value && membership.value.membership?.pending === false) {
+ for (const m of activeAccountMembers.value) {
+ if (m.id === membership_id) {
+ m.pending = false;
+ }
+ }
+ }
+ };
+
+ const rejectPendingMembership = async (membership_id: number) => {
+ const { $client } = useNuxtApp();
+ const { data: membership } =
+ await $client.account.rejectPendingMembership.useQuery({
+ membership_id
+ });
+
+ if (membership.value) {
+ activeAccountMembers.value = activeAccountMembers.value.filter(
+ m => m.id !== membership_id
+ );
+ }
+ };
+
+ const deleteMembership = async (membership_id: number) => {
+ const { $client } = useNuxtApp();
+ const { data: membership } =
+ await $client.account.deleteMembership.useQuery({ membership_id });
+
+ if (membership.value) {
+ activeAccountMembers.value = activeAccountMembers.value.filter(
+ m => m.id !== membership_id
+ );
+ }
+ };
+
+ const rotateJoinPassword = async () => {
+ const { $client } = useNuxtApp();
+ const { account } = await $client.account.rotateJoinPassword.mutate();
+ if (account && activeMembership.value) {
+ activeMembership.value.account = account;
+ }
+ };
+
+ const joinUserToAccountPending = async (account_id: number) => {
+ if (!dbUser.value) {
+ return;
+ }
+ const { $client } = useNuxtApp();
+ const { membership } =
+ await $client.account.joinUserToAccountPending.mutate({
+ account_id,
+ user_id: dbUser.value.id
+ });
+ if (membership && activeMembership.value) {
+ dbUser?.value?.memberships.push(membership);
+ }
+ };
+
+ const changeUserAccessWithinAccount = async (
+ user_id: number,
+ access: ACCOUNT_ACCESS
+ ) => {
+ const { $client } = useNuxtApp();
+ const { membership } =
+ await $client.account.changeUserAccessWithinAccount.mutate({
+ user_id,
+ access
+ });
+ if (membership) {
+ for (const m of activeAccountMembers.value) {
+ if (m.id === membership.id) {
+ m.access = membership.access;
+ }
+ }
+ }
+ };
+
+ const claimOwnershipOfAccount = async () => {
+ const { $client } = useNuxtApp();
+ const { memberships } =
+ await $client.account.claimOwnershipOfAccount.mutate();
+ if (memberships) {
+ activeAccountMembers.value = memberships;
+ activeMembership.value!.access = ACCOUNT_ACCESS.OWNER;
+ }
+ };
+
+ return {
+ dbUser,
+ activeAccountId,
+ activeAccountMembers,
+ activeMembership,
+ init,
+ signout,
+ getActiveAccountMembers,
+ changeActiveAccount,
+ changeAccountName,
+ acceptPendingMembership,
+ rejectPendingMembership,
+ deleteMembership,
+ rotateJoinPassword,
+ joinUserToAccountPending,
+ changeUserAccessWithinAccount,
+ claimOwnershipOfAccount
+ };
});
diff --git a/stores/notes.store.ts b/stores/notes.store.ts
index 1bb5aab..2075298 100644
--- a/stores/notes.store.ts
+++ b/stores/notes.store.ts
@@ -2,12 +2,6 @@ import { Note } from '.prisma/client';
import { defineStore, storeToRefs } from 'pinia';
import { Ref } from 'vue';
-/*
-Note) the Notes Store needs to be a 'Setup Store' (https://pinia.vuejs.org/core-concepts/#setup-stores)
-because this enables the use of the watch on the Account Store
-If the UI does not need to dynamically respond to a change in the active Account e.g. if state is always retrieved with an explicit fetch after onMounted.
-then an Options store can be used.
-*/
export const useNotesStore = defineStore('notes', () => {
const accountStore = useAccountStore();
const { activeAccountId } = storeToRefs(accountStore);
diff --git a/stores/notify.store.ts b/stores/notify.store.ts
index 0c15fca..ee06ad9 100644
--- a/stores/notify.store.ts
+++ b/stores/notify.store.ts
@@ -1,4 +1,5 @@
import { defineStore } from 'pinia';
+import { ref } from 'vue';
/*
This store manages User and Account state including the ActiveAccount
@@ -17,35 +18,33 @@ export enum NotificationType {
Error
}
-interface State {
- notifications: Notification[];
- notificationsArchive: Notification[];
-}
+export const useNotifyStore = defineStore('notify', () => {
+ const notifications = ref([]);
+ const notificationsArchive = ref([]);
-export const useNotifyStore = defineStore('notify', {
- state: (): State => {
- return {
- notifications: [],
- notificationsArchive: []
+ const notify = (messageOrError: unknown, type: NotificationType) => {
+ let message: string = '';
+ if (messageOrError instanceof Error) message = messageOrError.message;
+ if (typeof messageOrError === 'string') message = messageOrError;
+ const notification: Notification = {
+ message,
+ type,
+ notifyTime: Date.now()
};
- },
- actions: {
- notify(messageOrError: unknown, type: NotificationType) {
- let message: string = '';
- if (messageOrError instanceof Error) message = messageOrError.message;
- if (typeof messageOrError === 'string') message = messageOrError;
- const notification: Notification = {
- message,
- type,
- notifyTime: Date.now()
- };
- this.notifications.push(notification);
- setTimeout(this.removeNotification.bind(this), 5000, notification);
- },
- removeNotification(notification: Notification) {
- this.notifications = this.notifications.filter(
- n => n.notifyTime != notification.notifyTime
- );
- }
- }
+ notifications.value.push(notification);
+ setTimeout(removeNotification.bind(this), 5000, notification);
+ };
+
+ const removeNotification = (notification: Notification) => {
+ notifications.value = notifications.value.filter(
+ n => n.notifyTime != notification.notifyTime
+ );
+ };
+
+ return {
+ notifications,
+ notificationsArchive,
+ notify,
+ removeNotification
+ };
});
diff --git a/test/account.store.spec.ts b/test/account.store.spec.ts
new file mode 100644
index 0000000..b469cbf
--- /dev/null
+++ b/test/account.store.spec.ts
@@ -0,0 +1,105 @@
+import { describe, expect, afterEach, beforeEach, it, vi } from 'vitest';
+import { useAccountStore } from '../stores/account.store';
+import { setActivePinia, createPinia } from 'pinia';
+
+import { FullDBUser } from '~/lib/services/service.types';
+
+const fakeInitAccountStoreAdmin = (accountStore: any) => {
+ const dbUser: FullDBUser = {
+ id: 1,
+ name: 'John Doe',
+ memberships: [
+ { account_id: 1, access: 'ADMIN' },
+ { account_id: 2, access: 'READ_ONLY' }
+ ]
+ } as any;
+ accountStore.dbUser = dbUser;
+ accountStore.activeAccountId = 1;
+};
+
+describe('Account Store', async () => {
+ beforeEach(() => {
+ setActivePinia(createPinia());
+ });
+
+ it('should initialize the store', async () => {
+ // stub the useNuxtApp function with a mock client
+ vi.stubGlobal('useNuxtApp', () => ({
+ $client: {
+ auth: {
+ getDBUser: {
+ query: () => ({
+ dbUser: {
+ id: 1,
+ name: 'John Doe',
+ memberships: []
+ }
+ })
+ }
+ },
+ account: {
+ getActiveAccountId: {
+ query: () => ({ activeAccountId: 1 })
+ }
+ }
+ }
+ }));
+
+ const accountStore = useAccountStore();
+
+ // method under test
+ await accountStore.init();
+
+ expect(accountStore.dbUser).toEqual({
+ id: 1,
+ name: 'John Doe',
+ memberships: []
+ });
+
+ expect(accountStore.activeAccountId).toEqual(1);
+ });
+
+ it('should get active account members', async () => {
+ // stub the useNuxtApp function with a mock client
+ vi.stubGlobal('useNuxtApp', () => ({
+ $client: {
+ account: {
+ getAccountMembers: {
+ useQuery: () => ({
+ data: { value: { memberships: [new Object() as any] } }
+ })
+ }
+ }
+ }
+ }));
+
+ const accountStore = useAccountStore();
+ fakeInitAccountStoreAdmin(accountStore);
+
+ // method under test
+ await accountStore.getActiveAccountMembers();
+
+ expect(accountStore.activeAccountMembers.length).toEqual(1);
+ });
+
+ it('should get an active membership', async () => {
+ const accountStore = useAccountStore();
+ fakeInitAccountStoreAdmin(accountStore);
+
+ expect(accountStore.activeMembership).toEqual({
+ account_id: 1,
+ access: 'ADMIN'
+ });
+ });
+
+ it('should signout', async () => {
+ const accountStore = useAccountStore();
+ fakeInitAccountStoreAdmin(accountStore);
+
+ await accountStore.signout();
+
+ expect(accountStore.dbUser).toBeNull();
+ expect(accountStore.activeAccountId).toBeNull();
+ expect(accountStore.activeAccountMembers.length).toEqual(0);
+ });
+});
diff --git a/test/notify.store.spec.ts b/test/notify.store.spec.ts
new file mode 100644
index 0000000..153c714
--- /dev/null
+++ b/test/notify.store.spec.ts
@@ -0,0 +1,54 @@
+import { describe, test, expect, beforeEach } from 'vitest';
+import { setup, $fetch } from '@nuxt/test-utils';
+import { useNotifyStore, NotificationType } from '../stores/notify.store';
+import { setActivePinia, createPinia } from 'pinia';
+
+describe('Notify Store', async () => {
+ await setup({
+ // test context options
+ });
+
+ beforeEach(() => {
+ // creates a fresh pinia and makes it active
+ // so it's automatically picked up by any useStore() call
+ // without having to pass it to it: `useStore(pinia)`
+ setActivePinia(createPinia());
+ });
+
+ test('should add a notification', () => {
+ const notifyStore = useNotifyStore();
+ const message = 'Test notification';
+ const type = NotificationType.Info;
+
+ notifyStore.notify(message, type);
+
+ expect(notifyStore.notifications).toHaveLength(1);
+ expect(notifyStore.notifications[0].message).toBe(message);
+ expect(notifyStore.notifications[0].type).toBe(type);
+ });
+
+ test('should add an Error notification', () => {
+ const notifyStore = useNotifyStore();
+ const error = new Error('Test error');
+ const type = NotificationType.Error;
+
+ notifyStore.notify(error, type);
+
+ expect(notifyStore.notifications).toHaveLength(1);
+ expect(notifyStore.notifications[0].message).toBe(error.message);
+ expect(notifyStore.notifications[0].type).toBe(type);
+ });
+
+ test('should remove a notification', () => {
+ const notifyStore = useNotifyStore();
+ const message = 'Test notification';
+ const type = NotificationType.Info;
+
+ notifyStore.notify(message, type);
+ const notification = notifyStore.notifications[0];
+
+ notifyStore.removeNotification(notification);
+
+ expect(notifyStore.notifications).toHaveLength(0);
+ });
+});
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..30f8eed
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,14 @@
+import path from 'path';
+import { fileURLToPath } from 'url';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ // ... Specify options here.
+ },
+ resolve: {
+ alias: {
+ '~~': fileURLToPath(new URL('./', import.meta.url))
+ }
+ }
+});