├── .editorconfig ├── .github ├── social-card.jpeg └── thecompaniesapi.png ├── .gitignore ├── .npmrc ├── .nuxtrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── README.md ├── build.config.ts ├── eslint.config.js ├── package.json ├── playground ├── app.vue ├── assets │ └── css │ │ └── app.css ├── components │ └── OAuthProviders.vue ├── dbschema │ ├── default.esdl │ ├── interfaces.ts │ └── migrations │ │ └── 00001.edgeql ├── edgedb.toml ├── nuxt.config.ts ├── package.json ├── pages │ ├── auth │ │ ├── callback.vue │ │ ├── forgot-password.vue │ │ ├── login.vue │ │ ├── logout.vue │ │ ├── reset-password.vue │ │ ├── signup.vue │ │ └── verify.vue │ ├── blogposts │ │ └── [id].vue │ ├── index.vue │ └── new.vue ├── pnpm-lock.yaml ├── queries │ ├── allBlogPosts.edgeql │ ├── deleteBlogPost.edgeql │ ├── getBlogPost.edgeql │ └── insertBlogPost.edgeql ├── server │ ├── api │ │ └── blogpost.ts │ ├── plugins │ │ └── auth.ts │ └── tsconfig.json └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── src ├── cli.ts ├── module.ts ├── runtime │ ├── api │ │ └── auth │ │ │ ├── authorize.ts │ │ │ ├── callback.ts │ │ │ ├── identity.ts │ │ │ ├── login.ts │ │ │ ├── logout.ts │ │ │ ├── providers.ts │ │ │ ├── reset-password-ui.ts │ │ │ ├── reset-password.ts │ │ │ ├── send-password-reset-email.ts │ │ │ ├── signup.ts │ │ │ └── verify.ts │ ├── components │ │ └── auth │ │ │ ├── base │ │ │ ├── EdgeDbAuthEmailLogin.vue │ │ │ ├── EdgeDbAuthEmailSignup.vue │ │ │ ├── EdgeDbAuthEmailVerify.vue │ │ │ ├── EdgeDbAuthLogout.vue │ │ │ ├── EdgeDbAuthProviders.vue │ │ │ ├── EdgeDbAuthResetPassword.vue │ │ │ └── EdgeDbAuthSendPasswordReset.vue │ │ │ └── oauth │ │ │ ├── EdgeDbOAuthButton.vue │ │ │ └── EdgeDbOAuthCallback.vue │ ├── composables │ │ └── useEdgeDbIdentity.ts │ ├── plugins │ │ └── edgedb-auth.ts │ └── server │ │ ├── composables │ │ ├── useEdgeDb.ts │ │ ├── useEdgeDbEnv.ts │ │ ├── useEdgeDbIdentity.ts │ │ ├── useEdgeDbPKCE.ts │ │ ├── useEdgeDbQueries.ts │ │ └── useEdgeDbQueryBuilder.ts │ │ └── plugins │ │ └── edgedb-client.ts └── utils.ts ├── templates ├── default-auth.esdl └── default.esdl ├── test ├── basic.test.ts └── fixtures │ └── basic │ ├── app.vue │ ├── nuxt.config.ts │ └── package.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/social-card.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tahul/nuxt-edgedb/a1ffd27c44b0ff9870654754fcab2b11baa6c211/.github/social-card.jpeg -------------------------------------------------------------------------------- /.github/thecompaniesapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tahul/nuxt-edgedb/a1ffd27c44b0ff9870654754fcab2b11baa6c211/.github/thecompaniesapi.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .data 23 | .vercel_build_output 24 | .build-* 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | 58 | # Asdf 59 | .tool-versions 60 | 61 | # EdgeDB 62 | **/*.edgeql.ts 63 | **/dbschema/queries.* 64 | **/dbschema/query-builder 65 | **/queries/*.edgeql.ts 66 | **/dbschema/interfaces.ts 67 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | # enable TypeScript bundler module resolution - https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler 2 | experimental.typescriptBundlerResolution=true 3 | modules[]=nuxt-edgedb-module 4 | edgeDb.auth=true 5 | edgeDb.oauth=true 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enable the ESlint flat config support 3 | "eslint.experimental.useFlatConfig": true, 4 | 5 | // Disable the default formatter, use eslint instead 6 | "prettier.enable": false, 7 | "editor.formatOnSave": false, 8 | 9 | // Auto fix 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": "explicit", 12 | "source.organizeImports": "never" 13 | }, 14 | 15 | // Silent the stylistic rules in you IDE, but still auto fix them 16 | "eslint.rules.customizations": [ 17 | { "rule": "style/*", "severity": "off" }, 18 | { "rule": "*-indent", "severity": "off" }, 19 | { "rule": "*-spacing", "severity": "off" }, 20 | { "rule": "*-spaces", "severity": "off" }, 21 | { "rule": "*-order", "severity": "off" }, 22 | { "rule": "*-dangle", "severity": "off" }, 23 | { "rule": "*-newline", "severity": "off" }, 24 | { "rule": "*quotes", "severity": "off" }, 25 | { "rule": "*semi", "severity": "off" } 26 | ], 27 | 28 | // Enable eslint for all supported languages 29 | "eslint.validate": [ 30 | "javascript", 31 | "javascriptreact", 32 | "typescript", 33 | "typescriptreact", 34 | "vue", 35 | "html", 36 | "markdown", 37 | "json", 38 | "jsonc", 39 | "yaml" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## v0.0.52 5 | 6 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.51...v0.0.52) 7 | 8 | ### 🚀 Enhancements 9 | 10 | - **utils:** Use execa properly ([916a807](https://github.com/tahul/nuxt-edgedb/commit/916a807)) 11 | 12 | ### ❤️ Contributors 13 | 14 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 15 | 16 | ## v0.0.51 17 | 18 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.50...v0.0.51) 19 | 20 | ### 🚀 Enhancements 21 | 22 | - **module:** Transpile ([1d2ba1f](https://github.com/tahul/nuxt-edgedb/commit/1d2ba1f)) 23 | 24 | ### ❤️ Contributors 25 | 26 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 27 | 28 | ## v0.0.50 29 | 30 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.49...v0.0.50) 31 | 32 | ### 🩹 Fixes 33 | 34 | - **auth:** Import ([afbfe39](https://github.com/tahul/nuxt-edgedb/commit/afbfe39)) 35 | 36 | ### ❤️ Contributors 37 | 38 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 39 | 40 | ## v0.0.49 41 | 42 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.48...v0.0.49) 43 | 44 | ### 🩹 Fixes 45 | 46 | - **nitro:** Proper import for useNitroApp ([63b9075](https://github.com/tahul/nuxt-edgedb/commit/63b9075)) 47 | 48 | ### ❤️ Contributors 49 | 50 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 51 | 52 | ## v0.0.48 53 | 54 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.47...v0.0.48) 55 | 56 | ### 🚀 Enhancements 57 | 58 | - **package:** Cleanup ([fdd6fb7](https://github.com/tahul/nuxt-edgedb/commit/fdd6fb7)) 59 | 60 | ### ❤️ Contributors 61 | 62 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 63 | 64 | ## v0.0.47 65 | 66 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.46...v0.0.47) 67 | 68 | ### 🚀 Enhancements 69 | 70 | - **auth:** Cleanup imports ; only use relative to runtime dir ([6246c7c](https://github.com/tahul/nuxt-edgedb/commit/6246c7c)) 71 | - **cli:** Add cli install wizard ; avoiding bloating the module with 1-time logic ([283fc5d](https://github.com/tahul/nuxt-edgedb/commit/283fc5d)) 72 | - **module:** Cleanup the module from all generate logic ; rely on cli now ([4aef093](https://github.com/tahul/nuxt-edgedb/commit/4aef093)) 73 | - **playground:** Drop import ([9fffe2a](https://github.com/tahul/nuxt-edgedb/commit/9fffe2a)) 74 | - **package:** Update dependencies (nuxt ^3.12) ([3e11791](https://github.com/tahul/nuxt-edgedb/commit/3e11791)) 75 | - **package:** Build cli ([07ab7a3](https://github.com/tahul/nuxt-edgedb/commit/07ab7a3)) 76 | - **readme:** Update readme according to latest updates ([f226ee7](https://github.com/tahul/nuxt-edgedb/commit/f226ee7)) 77 | - **lint:** Update ([29c6ca1](https://github.com/tahul/nuxt-edgedb/commit/29c6ca1)) 78 | 79 | ### 🩹 Fixes 80 | 81 | - Opt in to `import.meta.*` properties ([#23](https://github.com/tahul/nuxt-edgedb/pull/23)) 82 | - Adopt forward-compatible approach to `builder:watch` ([#24](https://github.com/tahul/nuxt-edgedb/pull/24)) 83 | 84 | ### 📖 Documentation 85 | 86 | - Add project init step ([#17](https://github.com/tahul/nuxt-edgedb/pull/17)) 87 | - Use new `nuxi module add` command in installation ([#25](https://github.com/tahul/nuxt-edgedb/pull/25)) 88 | 89 | ### ❤️ Contributors 90 | 91 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 92 | - Daniel Roe ([@danielroe](http://github.com/danielroe)) 93 | 94 | ## v0.0.46 95 | 96 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.45...v0.0.46) 97 | 98 | ## v0.0.45 99 | 100 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.44...v0.0.45) 101 | 102 | ### 🩹 Fixes 103 | 104 | - **clean:** Remove logging ([42f264a](https://github.com/tahul/nuxt-edgedb/commit/42f264a)) 105 | 106 | ### ❤️ Contributors 107 | 108 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 109 | 110 | ## v0.0.44 111 | 112 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.43...v0.0.44) 113 | 114 | ### 🚀 Enhancements 115 | 116 | - **package:** Upgrade deps ([bc81f33](https://github.com/tahul/nuxt-edgedb/commit/bc81f33)) 117 | - **utils:** Use utils in module ; expose it from package ([e3d747f](https://github.com/tahul/nuxt-edgedb/commit/e3d747f)) 118 | 119 | ### 🩹 Fixes 120 | 121 | - **module:** Typecheck fix ([cae29d8](https://github.com/tahul/nuxt-edgedb/commit/cae29d8)) 122 | 123 | ### ❤️ Contributors 124 | 125 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 126 | 127 | ## v0.0.43 128 | 129 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.42...v0.0.43) 130 | 131 | ### 🚀 Enhancements 132 | 133 | - **edgedb:** Refactor dsn management; improve runtimeConfig ([2634967](https://github.com/tahul/nuxt-edgedb/commit/2634967)) 134 | 135 | ### ❤️ Contributors 136 | 137 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 138 | 139 | ## v0.0.42 140 | 141 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.41...v0.0.42) 142 | 143 | ### 🩹 Fixes 144 | 145 | - **logout:** Missing import ([085d0de](https://github.com/tahul/nuxt-edgedb/commit/085d0de)) 146 | 147 | ### ❤️ Contributors 148 | 149 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 150 | 151 | ## v0.0.41 152 | 153 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.40...v0.0.41) 154 | 155 | ### 🚀 Enhancements 156 | 157 | - **auth:** Improve error handling and response types ([83486c6](https://github.com/tahul/nuxt-edgedb/commit/83486c6)) 158 | 159 | ### 🩹 Fixes 160 | 161 | - **components:** Fix components imports ([101e3c9](https://github.com/tahul/nuxt-edgedb/commit/101e3c9)) 162 | 163 | ### ❤️ Contributors 164 | 165 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 166 | 167 | ## v0.0.40 168 | 169 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.39...v0.0.40) 170 | 171 | ### 🩹 Fixes 172 | 173 | - **identity:** Fix useEdgeDbIdentity runtime import ([1c66b13](https://github.com/tahul/nuxt-edgedb/commit/1c66b13)) 174 | 175 | ### ❤️ Contributors 176 | 177 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 178 | 179 | ## v0.0.39 180 | 181 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.38...v0.0.39) 182 | 183 | ### 🩹 Fixes 184 | 185 | - **identity:** Missing setCookie import ([de65844](https://github.com/tahul/nuxt-edgedb/commit/de65844)) 186 | 187 | ### ❤️ Contributors 188 | 189 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 190 | 191 | ## v0.0.38 192 | 193 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.37...v0.0.38) 194 | 195 | ### 🚀 Enhancements 196 | 197 | - **playground:** Improve playground ([c635109](https://github.com/tahul/nuxt-edgedb/commit/c635109)) 198 | - **edgedb:** Small improvements ([8bcc015](https://github.com/tahul/nuxt-edgedb/commit/8bcc015)) 199 | 200 | ### ❤️ Contributors 201 | 202 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 203 | 204 | ## v0.0.37 205 | 206 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.36...v0.0.37) 207 | 208 | ### 🩹 Fixes 209 | 210 | - Add missing imports of `useRouter` ([#20](https://github.com/tahul/nuxt-edgedb/pull/20)) 211 | - **auth:** Fix user creation ([97dee3d](https://github.com/tahul/nuxt-edgedb/commit/97dee3d)) 212 | 213 | ### ❤️ Contributors 214 | 215 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 216 | - Daniel Roe ([@danielroe](http://github.com/danielroe)) 217 | 218 | ## v0.0.36 219 | 220 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.35...v0.0.36) 221 | 222 | ### 🚀 Enhancements 223 | 224 | - **auth:** Support identityModel in `nuxt.config > edgeDb.identityModel` ([e77d950](https://github.com/tahul/nuxt-edgedb/commit/e77d950)) 225 | 226 | ### 🩹 Fixes 227 | 228 | - Import `useEdgeDbIdentity` ([#18](https://github.com/tahul/nuxt-edgedb/pull/18)) 229 | - **types:** Up ([458107c](https://github.com/tahul/nuxt-edgedb/commit/458107c)) 230 | 231 | ### 🏡 Chore 232 | 233 | - Use module builder stubbed types ([#19](https://github.com/tahul/nuxt-edgedb/pull/19)) 234 | - **fix:** IdentityModel ([60f3891](https://github.com/tahul/nuxt-edgedb/commit/60f3891)) 235 | 236 | ### ❤️ Contributors 237 | 238 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 239 | - Daniel Roe ([@danielroe](http://github.com/danielroe)) 240 | 241 | ## v0.0.35 242 | 243 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.34...v0.0.35) 244 | 245 | ### 🩹 Fixes 246 | 247 | - **warnings:** Fix warning errors ([c951440](https://github.com/tahul/nuxt-edgedb/commit/c951440)) 248 | - **auth:** Try to fix updateIdentity ([7c5bee6](https://github.com/tahul/nuxt-edgedb/commit/7c5bee6)) 249 | 250 | ### ❤️ Contributors 251 | 252 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 253 | 254 | ## v0.0.34 255 | 256 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.33...v0.0.34) 257 | 258 | ### 🏡 Chore 259 | 260 | - **auth:** Fix auth ([619b544](https://github.com/tahul/nuxt-edgedb/commit/619b544)) 261 | 262 | ### ❤️ Contributors 263 | 264 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 265 | 266 | ## v0.0.33 267 | 268 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.32...v0.0.33) 269 | 270 | ### 🩹 Fixes 271 | 272 | - Import `computed` from `vue` ([#16](https://github.com/tahul/nuxt-edgedb/pull/16)) 273 | 274 | ### ❤️ Contributors 275 | 276 | - Daniel Roe ([@danielroe](http://github.com/danielroe)) 277 | 278 | ## v0.0.32 279 | 280 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.31...v0.0.32) 281 | 282 | ### 🩹 Fixes 283 | 284 | - **prompts:** Fix prompts ([432a6f1](https://github.com/tahul/nuxt-edgedb/commit/432a6f1)) 285 | 286 | ### ❤️ Contributors 287 | 288 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 289 | 290 | ## v0.0.31 291 | 292 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.30...v0.0.31) 293 | 294 | ### 🏡 Chore 295 | 296 | - **devtools:** Rollback icon change ([218a2c8](https://github.com/tahul/nuxt-edgedb/commit/218a2c8)) 297 | 298 | ### ❤️ Contributors 299 | 300 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 301 | 302 | ## v0.0.30 303 | 304 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.29...v0.0.30) 305 | 306 | ### 🚀 Enhancements 307 | 308 | - **env:** Improve env parameters ([a94077e](https://github.com/tahul/nuxt-edgedb/commit/a94077e)) 309 | - **auth:** Improve auth plugin ([e8d6e9f](https://github.com/tahul/nuxt-edgedb/commit/e8d6e9f)) 310 | - **auth:** Improve auth composable (client) ([4a4df3a](https://github.com/tahul/nuxt-edgedb/commit/4a4df3a)) 311 | - **playground:** Update ([87876d6](https://github.com/tahul/nuxt-edgedb/commit/87876d6)) 312 | - **deps:** Upgrade dependencies ([2448fd5](https://github.com/tahul/nuxt-edgedb/commit/2448fd5)) 313 | - **module:** Improve credentials resolution; allow running from outside of cwd without errors ([febdb50](https://github.com/tahul/nuxt-edgedb/commit/febdb50)) 314 | 315 | ### 🏡 Chore 316 | 317 | - Test bundler module resolution ([#14](https://github.com/tahul/nuxt-edgedb/pull/14)) 318 | - **tsconfig:** Temporary workaround for tsconfig ([45b9517](https://github.com/tahul/nuxt-edgedb/commit/45b9517)) 319 | - **minimal-starter:** Remove minimal starter; playground is enough ([52ac007](https://github.com/tahul/nuxt-edgedb/commit/52ac007)) 320 | - **release:** Prepare release; fix build & lint ([f38e9fe](https://github.com/tahul/nuxt-edgedb/commit/f38e9fe)) 321 | 322 | ### ❤️ Contributors 323 | 324 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 325 | - Daniel Roe ([@danielroe](http://github.com/danielroe)) 326 | 327 | ## v0.0.29 328 | 329 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.28...v0.0.29) 330 | 331 | ### 🩹 Fixes 332 | 333 | - **module:** Allow boot without `edgedb` locally installed ([12c3c9d](https://github.com/tahul/nuxt-edgedb/commit/12c3c9d)) 334 | 335 | ### ❤️ Contributors 336 | 337 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 338 | 339 | ## v0.0.28 340 | 341 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.27...v0.0.28) 342 | 343 | ### 🩹 Fixes 344 | 345 | - **imports:** Use real imports ([bc2578e](https://github.com/tahul/nuxt-edgedb/commit/bc2578e)) 346 | 347 | ### ❤️ Contributors 348 | 349 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 350 | 351 | ## v0.0.27 352 | 353 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.26...v0.0.27) 354 | 355 | ### 🩹 Fixes 356 | 357 | - **edgedb-client:** Create a single client and expose it instead of recreating ([3b5c01d](https://github.com/tahul/nuxt-edgedb/commit/3b5c01d)) 358 | 359 | ### 🏡 Chore 360 | 361 | - **lint:** Fix lint ([1d48b7f](https://github.com/tahul/nuxt-edgedb/commit/1d48b7f)) 362 | 363 | ### ❤️ Contributors 364 | 365 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 366 | 367 | ## v0.0.26 368 | 369 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.25...v0.0.26) 370 | 371 | ### 🏡 Chore 372 | 373 | - **fix:** Fix hasQueryBuilder ([d032306](https://github.com/tahul/nuxt-edgedb/commit/d032306)) 374 | 375 | ### ❤️ Contributors 376 | 377 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 378 | 379 | ## v0.0.25 380 | 381 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.24...v0.0.25) 382 | 383 | ### 🩹 Fixes 384 | 385 | - **queries:** Do not inject composables if missing files ([7ad1db9](https://github.com/tahul/nuxt-edgedb/commit/7ad1db9)) 386 | 387 | ### ❤️ Contributors 388 | 389 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 390 | 391 | ## v0.0.24 392 | 393 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.23...v0.0.24) 394 | 395 | ### 🚀 Enhancements 396 | 397 | - **build:** Do not inject missing generate targets as aliases (breaking build) ([e617f12](https://github.com/tahul/nuxt-edgedb/commit/e617f12)) 398 | 399 | ### 🏡 Chore 400 | 401 | - **cleanup:** Clean ([9e55eca](https://github.com/tahul/nuxt-edgedb/commit/9e55eca)) 402 | 403 | ### ❤️ Contributors 404 | 405 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 406 | 407 | ## v0.0.23 408 | 409 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.22...v0.0.23) 410 | 411 | ### 🩹 Fixes 412 | 413 | - **import:** Fix import path ([e9fa0e4](https://github.com/tahul/nuxt-edgedb/commit/e9fa0e4)) 414 | 415 | ### ❤️ Contributors 416 | 417 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 418 | 419 | ## v0.0.22 420 | 421 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.21...v0.0.22) 422 | 423 | ### 🏡 Chore 424 | 425 | - **useedgedbqueries:** Fix typing ([4d8971e](https://github.com/tahul/nuxt-edgedb/commit/4d8971e)) 426 | 427 | ### ❤️ Contributors 428 | 429 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 430 | 431 | ## v0.0.21 432 | 433 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.20...v0.0.21) 434 | 435 | ### 🏡 Chore 436 | 437 | - **imports:** Make server imports manually to avoid .mjs via importsDir ([8396d12](https://github.com/tahul/nuxt-edgedb/commit/8396d12)) 438 | - **lint:** Lint ([f34801e](https://github.com/tahul/nuxt-edgedb/commit/f34801e)) 439 | 440 | ### ❤️ Contributors 441 | 442 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 443 | 444 | ## v0.0.20 445 | 446 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.19...v0.0.20) 447 | 448 | ### 🚀 Enhancements 449 | 450 | - **types:** Fix composables types to properly expose generated content ([718889b](https://github.com/tahul/nuxt-edgedb/commit/718889b)) 451 | 452 | ### ❤️ Contributors 453 | 454 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 455 | 456 | ## v0.0.19 457 | 458 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.18...v0.0.19) 459 | 460 | ### 🚀 Enhancements 461 | 462 | - **server-imports:** Fix auto imports in server ([85f0ea8](https://github.com/tahul/nuxt-edgedb/commit/85f0ea8)) 463 | 464 | ### 🏡 Chore 465 | 466 | - **cleanup:** Cleanup ([c68bc23](https://github.com/tahul/nuxt-edgedb/commit/c68bc23)) 467 | 468 | ### ❤️ Contributors 469 | 470 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 471 | 472 | ## v0.0.18 473 | 474 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.17...v0.0.18) 475 | 476 | ### 🚀 Enhancements 477 | 478 | - **auto-imports:** Fix server auto-imports; aliases and paths ([5f9bd80](https://github.com/tahul/nuxt-edgedb/commit/5f9bd80)) 479 | 480 | ### ❤️ Contributors 481 | 482 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 483 | 484 | ## v0.0.17 485 | 486 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.16...v0.0.17) 487 | 488 | ### 🩹 Fixes 489 | 490 | - **paths:** Fix ts paths ([970e721](https://github.com/tahul/nuxt-edgedb/commit/970e721)) 491 | 492 | ### ❤️ Contributors 493 | 494 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 495 | 496 | ## v0.0.16 497 | 498 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.15...v0.0.16) 499 | 500 | ### 🚀 Enhancements 501 | 502 | - **types:** Enforce type resolving in nitro context (#edgedb) ([2bd3902](https://github.com/tahul/nuxt-edgedb/commit/2bd3902)) 503 | - **prepare:** Do not run generate on prepare steps ([2c3003a](https://github.com/tahul/nuxt-edgedb/commit/2c3003a)) 504 | 505 | ### ❤️ Contributors 506 | 507 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 508 | 509 | ## v0.0.15 510 | 511 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.14...v0.0.15) 512 | 513 | ### 🚀 Enhancements 514 | 515 | - Single export for server modules under alias '#edgedb/server' ([#7](https://github.com/tahul/nuxt-edgedb/pull/7)) 516 | - Add server logout handler ([#5](https://github.com/tahul/nuxt-edgedb/pull/5)) 517 | 518 | ### 📖 Documentation 519 | 520 | - **readme:** Fix typo and improve some sentences ([#6](https://github.com/tahul/nuxt-edgedb/pull/6)) 521 | 522 | ### 🏡 Chore 523 | 524 | - **lock:** Update lockfile ([642a50e](https://github.com/tahul/nuxt-edgedb/commit/642a50e)) 525 | - **lint:** Fix linting ([d08008f](https://github.com/tahul/nuxt-edgedb/commit/d08008f)) 526 | 527 | ### ❤️ Contributors 528 | 529 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 530 | - Julien Le Coupanec 531 | - Andreas Korth 532 | 533 | ## v0.0.14 534 | 535 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.13...v0.0.14) 536 | 537 | ### 🚀 Enhancements 538 | 539 | - **alias:** Move to #edgedb/ ([2244b66](https://github.com/tahul/nuxt-edgedb/commit/2244b66)) 540 | 541 | ### ❤️ Contributors 542 | 543 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 544 | 545 | ## v0.0.13 546 | 547 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.12...v0.0.13) 548 | 549 | ### 🚀 Enhancements 550 | 551 | - **generate:** Fix paths injection when automatic generation disabled ([0dbbe31](https://github.com/tahul/nuxt-edgedb/commit/0dbbe31)) 552 | 553 | ### ❤️ Contributors 554 | 555 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 556 | 557 | ## v0.0.12 558 | 559 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.11...v0.0.12) 560 | 561 | ### 🚀 Enhancements 562 | 563 | - **auth:** Fix auth base url injection ([d1816e7](https://github.com/tahul/nuxt-edgedb/commit/d1816e7)) 564 | 565 | ### ❤️ Contributors 566 | 567 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 568 | 569 | ## v0.0.11 570 | 571 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.10...v0.0.11) 572 | 573 | ### 🚀 Enhancements 574 | 575 | - **useedgedbpkce:** Fix imports on all occurences; also add linting ([a6595bc](https://github.com/tahul/nuxt-edgedb/commit/a6595bc)) 576 | 577 | ### 🏡 Chore 578 | 579 | - **build:** Fix build ([57c16d7](https://github.com/tahul/nuxt-edgedb/commit/57c16d7)) 580 | 581 | ### ❤️ Contributors 582 | 583 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 584 | 585 | ## v0.0.10 586 | 587 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.9...v0.0.10) 588 | 589 | ## v0.0.9 590 | 591 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.8...v0.0.9) 592 | 593 | ### 🚀 Enhancements 594 | 595 | - **imports:** Use raw imports in runtime/ to avoid caveats from auto-imports ([0924259](https://github.com/tahul/nuxt-edgedb/commit/0924259)) 596 | 597 | ### ❤️ Contributors 598 | 599 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 600 | 601 | ## v0.0.8 602 | 603 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.7...v0.0.8) 604 | 605 | ### 🏡 Chore 606 | 607 | - **social-card:** Add social-card ([eff3fa4](https://github.com/tahul/nuxt-edgedb/commit/eff3fa4)) 608 | - **fix:** Fix cover url ([cad780e](https://github.com/tahul/nuxt-edgedb/commit/cad780e)) 609 | - **auth:** Fix auth plugin ([dd92cad](https://github.com/tahul/nuxt-edgedb/commit/dd92cad)) 610 | 611 | ### ❤️ Contributors 612 | 613 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 614 | 615 | ## v0.0.7 616 | 617 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.6...v0.0.7) 618 | 619 | ### 🏡 Chore 620 | 621 | - **readme:** Up ([a416e2d](https://github.com/tahul/nuxt-edgedb/commit/a416e2d)) 622 | - **readme:** Up ([e900c9a](https://github.com/tahul/nuxt-edgedb/commit/e900c9a)) 623 | - **readme:** Add minimal-starter; improve readme ([9b7da2b](https://github.com/tahul/nuxt-edgedb/commit/9b7da2b)) 624 | - **up:** Update module ; improve prompts handling ([a2cc6ba](https://github.com/tahul/nuxt-edgedb/commit/a2cc6ba)) 625 | 626 | ### ❤️ Contributors 627 | 628 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 629 | 630 | ## v0.0.6 631 | 632 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.5...v0.0.6) 633 | 634 | ### 🏡 Chore 635 | 636 | - **readme:** Up ([f29c0d4](https://github.com/tahul/nuxt-edgedb/commit/f29c0d4)) 637 | - **readme:** Up ([3f0b589](https://github.com/tahul/nuxt-edgedb/commit/3f0b589)) 638 | - **readme:** Up ([0b483b8](https://github.com/tahul/nuxt-edgedb/commit/0b483b8)) 639 | - **readme:** Up ([0542345](https://github.com/tahul/nuxt-edgedb/commit/0542345)) 640 | - **readme:** Up ([66aa58e](https://github.com/tahul/nuxt-edgedb/commit/66aa58e)) 641 | 642 | ### ❤️ Contributors 643 | 644 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 645 | 646 | ## v0.0.5 647 | 648 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.4...v0.0.5) 649 | 650 | ### 🏡 Chore 651 | 652 | - **wizard:** Fix install command ([f93f227](https://github.com/tahul/nuxt-edgedb/commit/f93f227)) 653 | 654 | ### ❤️ Contributors 655 | 656 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 657 | 658 | ## v0.0.4 659 | 660 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.3...v0.0.4) 661 | 662 | ### 🚀 Enhancements 663 | 664 | - **auth:** Add authentication; improve readme ([2bc59dc](https://github.com/tahul/nuxt-edgedb/commit/2bc59dc)) 665 | - **auth:** Add auth implementation; support oauth & email/pw ([a1c13c2](https://github.com/tahul/nuxt-edgedb/commit/a1c13c2)) 666 | - **auth:** Add edgedb:auth:callback ([150db0c](https://github.com/tahul/nuxt-edgedb/commit/150db0c)) 667 | 668 | ### 🏡 Chore 669 | 670 | - **readme:** Update ([d15c6d7](https://github.com/tahul/nuxt-edgedb/commit/d15c6d7)) 671 | 672 | ### ❤️ Contributors 673 | 674 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 675 | 676 | ## v0.0.3 677 | 678 | [compare changes](https://github.com/tahul/nuxt-edgedb/compare/v0.0.2...v0.0.3) 679 | 680 | ### 🏡 Chore 681 | 682 | - **package-name:** Change to nuxt-edgedb-module (nuxt-edgedb already taken) ([2b80325](https://github.com/tahul/nuxt-edgedb/commit/2b80325)) 683 | 684 | ### ❤️ Contributors 685 | 686 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 687 | 688 | ## v0.0.2 689 | 690 | 691 | ### 🚀 Enhancements 692 | 693 | - **init:** Init project ([f986348](https://github.com/tahul/nuxt-edgedb/commit/f986348)) 694 | - **example:** Add nitro auto imports and examples in playground ([b8cf5d2](https://github.com/tahul/nuxt-edgedb/commit/b8cf5d2)) 695 | 696 | ### 🏡 Chore 697 | 698 | - **lint:** Fix linting ([ad0ae15](https://github.com/tahul/nuxt-edgedb/commit/ad0ae15)) 699 | - **build:** Fix build ([1d4c2b1](https://github.com/tahul/nuxt-edgedb/commit/1d4c2b1)) 700 | 701 | ### ❤️ Contributors 702 | 703 | - Yaël Guilloux ([@Tahul](http://github.com/Tahul)) 704 | 705 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![nuxt-edgedb-module](./.github/social-card.jpeg) 2 | 3 | # Nuxt EdgeDB 4 | 5 | [![npm version][npm-version-src]][npm-version-href] 6 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 7 | [![License][license-src]][license-href] 8 | [![Nuxt][nuxt-src]][nuxt-href] 9 | 10 | Integrate [Nuxt 3](https://nuxt.com) with [EdgeDB](https://www.edgedb.com) effortlessly, adding a robust database layer to your app with minimal configuration. 11 | 12 | ## Features 13 | 14 | - 🍱 **Effortless Integration**: Set up a database with just one line of configuration. 15 | - 🎩 **Live Schema Updates**: Experience _HMR-like DX_ with watchers on **schema**, **queries**, and **migrations**. 16 | - 🛟 **Typed Query Generation**: Automatically generate a typed query client with [@edgedb/generate](https://www.edgedb.com/docs/clients/js/generation). 17 | - 🍩 **Integrated Database Management**: Pilot your database from [Nuxt DevTools](https://github.com/nuxt/devtools). 18 | - 🔐 **Flexible Auth**: 1-line toggle [Email](https://www.edgedb.com/docs/guides/auth/email_password) or [OAuth](https://www.edgedb.com/docs/guides/auth/oauth) authentication, with support for custom auth providers. 19 | - 🧙 **Initial guidance**: Guides you through [EdgeDB CLI](https://www.edgedb.com/docs/cli/index) setup and [project initialization](https://www.edgedb.com/docs/cli/edgedb_project/edgedb_project_init). 20 | 21 | ## Quick Setup 22 | 23 | 1. Add `nuxt-edgedb-module` dependency to your project 24 | 25 | ```bash 26 | npx nuxi@latest module add edgedb 27 | ``` 28 | 29 | 2. Add `nuxt-edgedb-module` to the `modules` section of `nuxt.config.ts` 30 | 31 | ```js 32 | export default defineNuxtConfig({ 33 | modules: [ 34 | 'nuxt-edgedb-module' 35 | ] 36 | }) 37 | ``` 38 | 39 | 3. Run `npx nuxt-edgedb-module` in your project root to run the CLI setup wizard. 40 | 41 | ```bash 42 | npx nuxt-edgedb-module 43 | ``` 44 | 45 | That's it! Your Nuxt project now have a database. ✨ 46 | 47 | If you do not already have [EdgeDB](https://www.edgedb.com) installed on your machine, the install wizard will help you install it. 48 | 49 | ## Example project 50 | 51 | If you want to run the example project, you have to clone this repository and run the playground. 52 | 53 | As EdgeDB cannot run on web containers environment like Stackblitz or CodeSandbox. 54 | 55 | ```bash 56 | git clone git@github.com:Tahul/nuxt-edgedb.git 57 | cd nuxt-edgedb 58 | pnpm install 59 | pnpm stub 60 | cd playground 61 | edgedb project init 62 | npx nuxt-edgedb-module 63 | pnpm run dev 64 | ``` 65 | 66 | ## Module options 67 | 68 | You can configure any behavior from the module from your `nuxt.config.ts` file: 69 | 70 | ```typescript 71 | export default defineNuxtConfig({ 72 | modules: ['nuxt-edgedb-module'], 73 | edgeDb: { 74 | // Devtools integrations 75 | devtools: true, 76 | // Completely toggle watchers feature 77 | watch: true, 78 | // Enable or disable prompts on watch events 79 | watchPrompt: true, 80 | // Generate target for your queries and query builder 81 | generateTarget: 'ts', 82 | // dbschema/ dir 83 | dbschemaDir: 'dbschema', 84 | // Individual queries dir (useEdgeDbQueries composable) 85 | queriesDir: 'queries', 86 | // Toggle CLI install wizard 87 | installCli: true, 88 | // Toggles composables 89 | composables: true, 90 | // Toggles auto-injection on auth credentials 91 | injectDbCredentials: true, 92 | // Enables authentication integration 93 | auth: false, 94 | // Enables oauth integration 95 | oauth: false, 96 | } 97 | }) 98 | ``` 99 | 100 | ## Server usage 101 | 102 | The module auto-imports all composables available in the `server/` context of your Nuxt app. 103 | 104 | ### useEdgeDb 105 | 106 | `useEdgeDb` exposes the raw client from the `edgedb` import using your Nuxt environment configuration. 107 | 108 | ```typescript 109 | // server/api/blogpost/[id].ts 110 | import { defineEventHandler, getRouterParams } from 'h3' 111 | 112 | export default defineEventHandler(async (req) => { 113 | const params = getRouterParams(req) 114 | const id = params.id 115 | const client = useEdgeDb() 116 | 117 | const blogpost = await client.querySingle(` 118 | select BlogPost { 119 | title, 120 | description 121 | } filter .id = $id 122 | `, { 123 | id: id 124 | }); 125 | 126 | return blogpost 127 | }) 128 | ``` 129 | 130 | ### useEdgeDbQueries 131 | 132 | `useEdgeDbQueries` exposes all your queries from `dbschema/queries.ts`. 133 | 134 | You do not have to pass them a client. They will use the one generated by `useEdgeDb` that is scoped to the current request. 135 | 136 | ```esdl 137 | // queries/getBlogPost.edgeql 138 | select BlogPost { 139 | title, 140 | description 141 | } filter .id = $blogpost_id 142 | ``` 143 | 144 | ```typescript 145 | // server/api/blogpost/[id].ts 146 | import { defineEventHandler, getRouterParams } from 'h3' 147 | 148 | export default defineEventHandler(async (req) => { 149 | const params = getRouterParams(req) 150 | const id = params.id 151 | const { getBlogpPost } = useEdgeDbQueries() 152 | const blogPost = await getBlogpost({ blogpost_id: id }) 153 | 154 | return blogpost 155 | }) 156 | ``` 157 | 158 | You can still import [queries](https://www.edgedb.com/docs/clients/js/queries) directly from `#edgedb/queries` and pass them the client from `useEdgeDb()`. 159 | 160 | ```typescript 161 | // server/api/blogpost/[id].ts 162 | import { getBlogPost } from '#edgedb/queries' 163 | import { defineEventHandler, getRouterParams } from 'h3' 164 | 165 | export default defineEventHandler(async (req) => { 166 | const params = getRouterParams(req) 167 | const id = params.id 168 | const client = useEdgeDb() 169 | const blogPost = await getBlogpost(client, { blogpost_id: id }) 170 | 171 | return blogpost 172 | }) 173 | ``` 174 | 175 | ### useEdgeDbQueryBuilder 176 | 177 | `useEdgeDbQueryBuilder` exposes the generated [query builder](https://www.edgedb.com/docs/clients/js/querybuilder) directly to your `server/` context. 178 | 179 | ```typescript 180 | // server/api/blogpost/[id].ts 181 | import { defineEventHandler, getRouterParams } from 'h3' 182 | 183 | export default defineEventHandler(async (req) => { 184 | const params = getRouterParams(req) 185 | const id = params.id 186 | const client = useEdgeDb() 187 | const e = useEdgeDbQueryBuilder() 188 | 189 | const blogPostQuery = e.select( 190 | e.BlogPost, 191 | (blogPost) => ({ 192 | id: true, 193 | title: true, 194 | description: true, 195 | filter_single: { id } 196 | }) 197 | ) 198 | 199 | const blogPost = await blogPostQuery.run(client) 200 | 201 | return blogpost 202 | }) 203 | ``` 204 | 205 | ### Typings 206 | 207 | All the interfaces generated by EdgeDB are available through imports via `#edgedb/interfaces`. 208 | 209 | ```vue 210 | 215 | ``` 216 | 217 | ## Authentification 218 | 219 | You can use EdgeDB as a server-only database exposed via `server/api` endpoints and `$fetch` on the client, avoiding the need for authentication. 220 | 221 | But in some projects, you might want your users to login so they have an identity on the server as well. Luckily, the module got you covered. 222 | 223 | > Before going through these auth installation steps, we strongly recommend you to read the [EdgeDB Auth](https://www.edgedb.com/docs/guides/auth/index#auth) documentation. 224 | 225 | ### Enable the `auth` option in your Nuxt configuration 226 | 227 | ```ts 228 | export default defineNuxtConfig({ 229 | modules: ['nuxt-edgedb-module'], 230 | edgedb: { 231 | auth: true 232 | } 233 | }) 234 | ``` 235 | 236 | ### Setup EdgeDB Auth in your schema 237 | 238 | In this example, you can notice: 239 | 240 | - `global current_user` which defines a [global property](https://www.edgedb.com/docs/datamodel/globals) linked to the client token identity. 241 | - `type User` is the model if your user, you are free to change it, that can be done later on thanks to migrations. 242 | - `access policy author_has_full_access` & `using (.author ?= global current_user);` defines the policy for the users to only have access to their own `BlogPost`. 243 | 244 | ```esdl 245 | // dbschema/default.esdl 246 | using extension auth; 247 | 248 | module default { 249 | global current_user := ( 250 | assert_single(( 251 | select User { id, name } 252 | filter .identity = global ext::auth::ClientTokenIdentity 253 | )) 254 | ); 255 | 256 | type User { 257 | required name: str; 258 | required identity: ext::auth::Identity; 259 | } 260 | 261 | type BlogPost { 262 | property content: str { 263 | default := 'My blog post content.'; 264 | }; 265 | property title: str { 266 | default := 'My blog post'; 267 | }; 268 | required author: User; 269 | 270 | access policy author_has_full_access 271 | allow all 272 | using (.author ?= global current_user); 273 | 274 | access policy others_read_only 275 | allow select; 276 | } 277 | } 278 | ``` 279 | 280 | You can edit this schema while your server is running and accept the prompted messages to auto-migrate. 281 | 282 | If you are doing these edits while the server is off, you can run `edgedb migration create` and `edgedb migrate`. 283 | 284 | ### Setup EdgeDB auth in your server 285 | 286 | You will need to enable auth providers on your EdgeDB server. 287 | 288 | That can be done in the `EdgeDB` tab through your DevTools. 289 | 290 | Browse your database to `Auth Admin` and specify: 291 | 292 | - `auth_signing_key` 293 | - `allowed_redirect_urls` 294 | 295 | You must also enable some providers. You can start with `Email + Password` for instance. 296 | 297 | If you enable `required_verification`, you will need to configure a SMTP server for you EdgeDB instance. 298 | 299 | You can find further instructions on how to use [Mailtrap](https://mailpit.axllent.org/docs/configuration/) locally to try this feature [here](https://www.edgedb.com/docs/guides/auth/index#email-and-password). 300 | 301 | > Do not forget these steps must also be performed on your production environment. 302 | 303 | ### Use authentication components on the client 304 | 305 | As you enabled auth in your config, the module injected these components in your project: 306 | 307 | - [`EdgeDbAuthEmailLogin`](./src/runtime/components/EdgeDbAuthEmailLogin.vue) 308 | - [`EdgeDbAuthEmailVerify`](./src/runtime/components/EdgeDbAuthEmailVerify.vue) 309 | - [`EdgeDbAuthLogout`](./src/runtime/components/EdgeDbAuthLogout.vue) 310 | - [`EdgeDbAuthResetPassword`](./src/runtime/components/EdgeDbAuthResetPassword.vue) 311 | - [`EdgeDbAuthSendPasswordReset`](./src/runtime/components/EdgeDbAuthSendPasswordReset.vue) 312 | - [`EdgeDbAuthSignup`](./src/runtime/components/EdgeDbAuthSignup.vue) 313 | - [`EdgeDbAuthProviders`](./src/runtime/components/EdgeDbAuthProviders.vue) 314 | 315 | You can look at the sources of these components to learn more about their props. 316 | 317 | They all are unstyled components that only expose the necessary logic to achieve a smooth authentication flow. 318 | 319 | ```vue 320 | 347 | ``` 348 | 349 | Of course, you can totally rewrite any of these components locally to implement your own authentication flow. 350 | 351 | ### OAuth 352 | 353 | If you want to use OAuth, you will have to enable it in your `nuxt.config`: 354 | 355 | ```typescript 356 | export default defineNuxtConfig({ 357 | edgeDb: { 358 | oauth: true 359 | } 360 | }) 361 | ``` 362 | 363 | That will inject two new components to your app: 364 | 365 | - [`EdgeDbOAuthButton`](./src/runtime/components/EdgeDbOAuthButton.vue) 366 | - [`EdgeDbOAuthCallback`](./src/runtime/components/EdgeDbOAuthCallback.vue) 367 | 368 | EdgeDB currently supports [OAuth](https://www.edgedb.com/docs/guides/auth/oauth#oauth) for the following providers: 369 | 370 | - Apple 371 | - Azure (Microsoft) 372 | - GitHub 373 | - Google 374 | 375 | In order to get OAuth working, you will have to visit your EdgeDB Instance UI, via the Nuxt DevTools. 376 | 377 | Browse to your database and visit the "Auth Admin" tab. 378 | 379 | In your list of providers, you can then add any provider you want and configure the necessary keys (usually client `appid` and `secret`). 380 | 381 | > Do not forget to set the callback url of your provider to the one listed at the top of your EdgeDB Auth Admin. 382 | 383 | Then, you can then create a simple OAuth button in your app like this: 384 | 385 | ```vue 386 | 403 | ``` 404 | 405 | You will also need a call back page, that can use `EdgeDbAuthCallback`. 406 | 407 | ```vue 408 | 422 | ``` 423 | 424 | Amazing right?! In just a few lines, we just added a basic authentication to our application. 425 | 426 | ### Client-side usage 427 | 428 | Now that the authentication is implemented, you also have access to the `useEdgeDbIdentity` composable in your Nuxt app. 429 | 430 | ```vue 431 | 434 | 435 | 441 | ``` 442 | 443 | You can look at the [`useEdgeDbIdentity`](./src/runtime/composables/useEdgeDbIdentity.ts) for more details. 444 | 445 | ### Server-side usage 446 | 447 | The authentication process does use a cookie called `edgedb-auth-token`. 448 | 449 | On the server, if you want to authenticate your requests to the database for the current user, you only have to pass the current request object to composables: 450 | 451 | ```typescript 452 | export default defineEventHandler(async (req) => { 453 | // Will throw an error, as you cannot delete a BlogPost without being the author 454 | const { deleteBlogPost } = useEdgeDbQueries() 455 | await deleteBlogPost({ blogpost_id: id }) 456 | 457 | // Success 458 | const { deleteBlogPost: deleteBlogPostAuthenticated } = useEdgeDbQueries(req) 459 | await deleteBlogPostAuthenticated({ blogpost_id: id }) 460 | 461 | return { id } 462 | }) 463 | ``` 464 | 465 | ### Other authentication solutions 466 | 467 | EdgeDDB Auth is a great solution, but eventually your app may require more features later. 468 | 469 | Do not forget that EdgeDB can also be used as a database. You can build your own auth or use existing solutions like: 470 | 471 | - [Sidebase Nuxt Auth](https://github.com/sidebase/nuxt-auth) 472 | - [Nuxt Auth (when ready)](https://github.com/nuxt-community/auth-module) 473 | - [Nuxt Auth Utils](https://github.com/Atinux/nuxt-auth-utils#supported-oauth-providers) 474 | - Your own implementation 475 | 476 | You can also use _both_ and create Identity objects from your own authentication provider, and use `edgedb-auth-token` as your cookie. 477 | 478 | I would recommend looking at [https://github.com/edgedb/edgedb-examples] that is filled with great examples of custom authentications built on EdgeDB. 479 | 480 | ### Authentication environment variables 481 | 482 | ```sh 483 | # Your EdgeDB instance auth extension base URL 484 | NUXT_EDGEDB_AUTH_BASE_URL=http://localhost:10702/db/edgedb/ext/auth/ 485 | # Your EdgeDB instance OAuth callback URL 486 | NUXT_EDGEDB_OAUTH_CALLBACK=http://localhost:10702/db/edgedb/ext/auth/callback 487 | # Your app callback page 488 | NUXT_EDGEDB_OAUTH_REDIRECT_URL=http://localhost:3000/auth/callback 489 | # Your app app reset password URL (receiving the token from the forgot password email) 490 | NUXT_EDGEDB_AUTH_RESET_PASSWORD_URL=http://localhost:3000/auth/reset-password 491 | # Your app email verify url (receiving the token from email verify feature) 492 | NUXT_EDGEDB_AUTH_VERIFY_REDIRECT_URL=http://localhost:3000/auth/verify 493 | ``` 494 | 495 | ### Going further with authentication 496 | 497 | EdgeDB Auth only provides the bare minimal identity feature for authentication. 498 | 499 | In the code example provided for authentication setup, a `User` type goes along with the `Identity` interface. 500 | 501 | If you want to fill `User` with other attributes upon registration, you will have to implement this behavior yourself. 502 | 503 | If you want to resolve data from OAuth providers, you can use the Nitro hook called `edgedb:auth:callback` from a Nitro plugin. 504 | 505 | ``` 506 | // server/plugins/auth.ts 507 | 508 | export default defineNitroPlugin((app) => { 509 | app.hooks.hook('edgedb:auth:callback', (data) => { 510 | const { 511 | code, 512 | verifier, 513 | codeExchangeUrl, 514 | codeExchangeResponseData, 515 | } = data 516 | 517 | // codeExchangeResponseData contains the OAuth token from the provider. 518 | // ... implement your own authentication logic. 519 | }) 520 | }) 521 | ``` 522 | 523 | ## Production 524 | 525 | If you want to get out of development and deploy your database to prodution, you must follow the [EdgeDB guides](https://www.edgedb.com/docs/guides/deployment/index). 526 | 527 | [EdgeDB](https://www.edgedb.com) is an open-source database that is designed to be self-hosted. 528 | 529 | However, they also offer a [Cloud](https://www.edgedb.com/docs/guides/cloud), which is fully compatible with this module thanks to environment variables. 530 | 531 | If you want to customize the [DSN] used by the composables, you can use the environment variables provided by the module: 532 | 533 | ``` 534 | NUXT_EDGEDB_HOST= 535 | NUXT_EDGEDB_PORT= 536 | NUXT_EDGEDB_USER= 537 | NUXT_EDGEDB_PASS= 538 | NUXT_EDGEDB_DATABASE= 539 | ``` 540 | 541 | > If you want to use the env variables, you have to specify **ALL** of them, otherwise the client will fallback on default values. 542 | 543 | ## Q&A 544 | 545 | ### Will my database client be exposed in userland? 546 | 547 | No, `useEdgeDb` and `useEdgeDbQueries` are only available in the [server/](https://nuxt.com/docs/guide/directory-structure/server) context of Nuxt. 548 | 549 | You can, as an **opt-in** feature, import queries from `@dbschema/queries` on the client. 550 | 551 | You will need to provide these queries with a client from `createClient()`. 552 | 553 | ```vue 554 | 561 | ``` 562 | 563 | You can also, still as an **opt-in** feature, import the query builder to the client. 564 | 565 | I guess that can be useful for a super-admin/internal dashboard, but use it at your own risks in terms of security access. 566 | 567 | ```vue 568 | 575 | ``` 576 | 577 | Be careful with these imports, as if you import wrong queries, you might end up with write operations available to the client, potentially damaging your database. 578 | 579 | ### How do I run my migrations in production? 580 | 581 | - Clone your Nuxt project on your production environment 582 | - Ensure you have [EdgeDB CLI](https://www.edgedb.com/docs/cli/index) installed on the server 583 | - Add `edgedb migrate --quiet` to your CLI script 584 | 585 | ### Should I version generated files? 586 | 587 | No, as they are generated with your Nuxt client, you should add them to your `.gitignore` 588 | 589 | ```.gitignore 590 | **/*.edgeql.ts 591 | dbschema/queries.* 592 | dbschema/query-builder 593 | dbschema/interfaces.ts 594 | queries/*.query.ts 595 | ``` 596 | 597 | You must change these paths accordingly if you change the `**Dir` options. 598 | 599 | ### Is HMR for my database schema really safe? 600 | 601 | Well, it depends on when you want to use it. 602 | 603 | I would suggest keeping `watchPrompt` enabled while you casually dev on your project. 604 | 605 | That will prevent from running any unwanted migration, and will only prompt when you add new things to your schemas. 606 | 607 | If you want to go fast and know what you are doing, you can set `watchPrompt` to false, and profit from automatic migration creation and applying on any change on your schemas. 608 | 609 | If you do not want any of these features, just set `watch` to false and feel safe about changes applied to your development database. 610 | 611 | > HMR on your database obviously has **NO** effect in production context. 612 | 613 | ### Why the name isn't `nuxt-edgedb` 614 | 615 | Because that handle is already taken on NPM. 616 | 617 | It seem to be taken by [`ohmree`](https://github.com/ohmree), but the package seem inactive. 618 | 619 | If anyone happens to know him, I would be happy to get in touch with him! 620 | 621 | ### Contributions 622 | 623 | There is still plenty of great features to build for this integration. 624 | 625 | I would be happy to receive and review any Pull Request. 626 | 627 | ## Development 628 | 629 | ```bash 630 | # Install dependencies 631 | npm install 632 | 633 | # Generate type stubs 634 | npm run dev:prepare 635 | 636 | # Develop with the playground 637 | npm run dev 638 | 639 | # Build the playground 640 | npm run dev:build 641 | 642 | # Run ESLint 643 | npm run lint 644 | 645 | # Run Vitest 646 | npm run test 647 | npm run test:watch 648 | 649 | # Release new version 650 | npm run release 651 | ``` 652 | 653 | ## Sponsors 654 | 655 | [![thecompaniesapi.com](./.github/thecompaniesapi.png)](https://thecompaniesapi.com) 656 | 657 | 658 | [npm-version-src]: https://img.shields.io/npm/v/nuxt-edgedb-module/latest.svg?style=flat&colorA=18181B&colorB=28CF8D 659 | [npm-version-href]: https://npmjs.com/package/nuxt-edgedb-module 660 | 661 | [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-edgedb-module.svg?style=flat&colorA=18181B&colorB=28CF8D 662 | [npm-downloads-href]: https://npmjs.com/package/nuxt-edgedb-module 663 | 664 | [license-src]: https://img.shields.io/npm/l/nuxt-edgedb-module.svg?style=flat&colorA=18181B&colorB=28CF8D 665 | [license-href]: https://npmjs.com/package/nuxt-edgedb-module 666 | 667 | [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js 668 | [nuxt-href]: https://nuxt.com 669 | -------------------------------------------------------------------------------- /build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | entries: [ 5 | './src/module.ts', 6 | './src/utils.ts', 7 | './src/cli.ts', 8 | ], 9 | externals: [ 10 | 'consola', 11 | 'pathe', 12 | 'chalk', 13 | 'prompts', 14 | 'edgedb', 15 | '@edgedb/generate', 16 | ], 17 | }) 18 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default await antfu( 4 | { 5 | ignores: [ 6 | 'dist', 7 | 'node_modules', 8 | '*.md', 9 | 'package.json', 10 | ], 11 | }, 12 | { 13 | rules: { 14 | 'no-console': 'off', 15 | 'node/prefer-global/process': 'off', 16 | }, 17 | }, 18 | ) 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-edgedb-module", 3 | "type": "module", 4 | "version": "0.0.52", 5 | "description": "Nuxt 3 integration for EdgeDB.", 6 | "license": "MIT", 7 | "repository": "tahul/nuxt-edgedb", 8 | "bin": "./dist/cli.mjs", 9 | "exports": { 10 | ".": { 11 | "types": "./dist/module.d.ts", 12 | "import": "./dist/module.mjs", 13 | "require": "./dist/module.cjs" 14 | }, 15 | "./utils": { 16 | "types": "./dist/utils.d.ts", 17 | "import": "./dist/utils.mjs", 18 | "require": "./dist/utils.mjs" 19 | }, 20 | "./runtime/*": "./dist/runtime/*" 21 | }, 22 | "main": "./dist/module.cjs", 23 | "types": "./dist/module.d.ts", 24 | "files": [ 25 | "dist", 26 | "templates" 27 | ], 28 | "scripts": { 29 | "build": "nuxt-module-build build", 30 | "dev": "nuxi dev playground", 31 | "dev:cli": "jiti ./src/cli.ts", 32 | "dev:build": "nuxi build playground", 33 | "stub": "nuxt-module-build build --stub", 34 | "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", 35 | "release": "npm run lint && npm run build && changelogen --release && npm publish && git push --follow-tags", 36 | "lint": "eslint .", 37 | "test": "vitest run", 38 | "test:watch": "vitest watch" 39 | }, 40 | "dependencies": { 41 | "@clack/core": "^0.3.4", 42 | "@clack/prompts": "^0.7.0", 43 | "@edgedb/generate": "^0.5.3", 44 | "@nuxt/kit": "^3.12.2", 45 | "edgedb": "^1.5.7", 46 | "execa": "^9.2.0", 47 | "prompts": "^2.4.2" 48 | }, 49 | "devDependencies": { 50 | "@antfu/eslint-config": "^2.21.1", 51 | "@nuxt/devtools": "latest", 52 | "@nuxt/module-builder": "^0.7.1", 53 | "@nuxt/schema": "^3.12.2", 54 | "@nuxt/test-utils": "^3.13.1", 55 | "@types/node": "^20.14.3", 56 | "@types/prompts": "^2.4.9", 57 | "changelogen": "^0.5.5", 58 | "eslint": "^9.5.0", 59 | "nuxt": "^3.12.2", 60 | "vitest": "^1.6.0" 61 | }, 62 | "resolutions": { 63 | "nuxt-edgedb-module": "link:." 64 | } 65 | } -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 60 | -------------------------------------------------------------------------------- /playground/assets/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tahul/nuxt-edgedb/a1ffd27c44b0ff9870654754fcab2b11baa6c211/playground/assets/css/app.css -------------------------------------------------------------------------------- /playground/components/OAuthProviders.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /playground/dbschema/default.esdl: -------------------------------------------------------------------------------- 1 | using extension auth; 2 | 3 | module default { 4 | global current_user := ( 5 | assert_single(( 6 | select User { id, name } 7 | filter .identity = global ext::auth::ClientTokenIdentity 8 | )) 9 | ); 10 | 11 | type User { 12 | required name: str; 13 | required identity: ext::auth::Identity; 14 | multi link posts -> BlogPost { 15 | on source delete delete target; 16 | } 17 | } 18 | 19 | type BlogPost { 20 | property content: str { 21 | default := 'My super blog post.'; 22 | }; 23 | property description: str { 24 | default := 'My blog post description.'; 25 | }; 26 | property title: str { 27 | default := 'My blog super blog post title.'; 28 | }; 29 | required author: User { 30 | default := global current_user; 31 | }; 32 | access policy author_has_full_access 33 | allow all 34 | using (.author ?= global current_user); 35 | access policy others_read_only 36 | allow select; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /playground/dbschema/interfaces.ts: -------------------------------------------------------------------------------- 1 | // GENERATED by @edgedb/generate v0.4.1 2 | 3 | import type * as edgedb from "edgedb"; 4 | export namespace std { 5 | export interface BaseObject { 6 | "id": string; 7 | } 8 | export interface $Object extends BaseObject {} 9 | export interface FreeObject extends BaseObject {} 10 | export type JsonEmpty = "ReturnEmpty" | "ReturnTarget" | "Error" | "UseNull" | "DeleteKey"; 11 | export namespace enc { 12 | export type Base64Alphabet = "standard" | "urlsafe"; 13 | } 14 | } 15 | export interface User extends std.$Object { 16 | "name": string; 17 | "identity": ext.auth.Identity; 18 | "posts": BlogPost[]; 19 | } 20 | export interface BlogPost extends std.$Object { 21 | "content"?: string | null; 22 | "description"?: string | null; 23 | "title"?: string | null; 24 | "author": User; 25 | } 26 | export interface current_user extends User {} 27 | export namespace ext { 28 | export namespace auth { 29 | export interface ProviderConfig extends cfg.ConfigObject { 30 | "name": string; 31 | } 32 | export interface OAuthProviderConfig extends ProviderConfig { 33 | "name": string; 34 | "secret": string; 35 | "client_id": string; 36 | "display_name": string; 37 | "additional_scope"?: string | null; 38 | } 39 | export interface AppleOAuthProvider extends OAuthProviderConfig { 40 | "name": string; 41 | "display_name": string; 42 | } 43 | export interface Auditable extends std.$Object { 44 | "created_at": Date; 45 | "modified_at": Date; 46 | } 47 | export interface AuthConfig extends cfg.ExtensionConfig { 48 | "auth_signing_key"?: string | null; 49 | "token_time_to_live"?: edgedb.Duration | null; 50 | "allowed_redirect_urls": string[]; 51 | "providers": ProviderConfig[]; 52 | "ui"?: UIConfig | null; 53 | } 54 | export interface AzureOAuthProvider extends OAuthProviderConfig { 55 | "name": string; 56 | "display_name": string; 57 | } 58 | export interface Identity extends Auditable { 59 | "issuer": string; 60 | "subject": string; 61 | } 62 | export interface ClientTokenIdentity extends Identity {} 63 | export interface Factor extends Auditable { 64 | "identity": LocalIdentity; 65 | } 66 | export interface EmailFactor extends Factor { 67 | "email": string; 68 | "verified_at"?: Date | null; 69 | } 70 | export interface EmailPasswordFactor extends EmailFactor { 71 | "password_hash": string; 72 | } 73 | export interface EmailPasswordProviderConfig extends ProviderConfig { 74 | "name": string; 75 | "require_verification": boolean; 76 | } 77 | export type FlowType = "PKCE" | "Implicit"; 78 | export interface GitHubOAuthProvider extends OAuthProviderConfig { 79 | "name": string; 80 | "display_name": string; 81 | } 82 | export interface GoogleOAuthProvider extends OAuthProviderConfig { 83 | "name": string; 84 | "display_name": string; 85 | } 86 | export type JWTAlgo = "RS256" | "HS256"; 87 | export interface LocalIdentity extends Identity { 88 | "subject": string; 89 | } 90 | export interface PKCEChallenge extends Auditable { 91 | "challenge": string; 92 | "auth_token"?: string | null; 93 | "refresh_token"?: string | null; 94 | "identity"?: Identity | null; 95 | } 96 | export interface SMTPConfig extends cfg.ExtensionConfig { 97 | "sender"?: string | null; 98 | "host"?: string | null; 99 | "port"?: number | null; 100 | "username"?: string | null; 101 | "password"?: string | null; 102 | "security": SMTPSecurity; 103 | "validate_certs": boolean; 104 | "timeout_per_email": edgedb.Duration; 105 | "timeout_per_attempt": edgedb.Duration; 106 | } 107 | export type SMTPSecurity = "PlainText" | "TLS" | "STARTTLS" | "STARTTLSOrPlainText"; 108 | export interface UIConfig extends cfg.ConfigObject { 109 | "redirect_to": string; 110 | "redirect_to_on_signup"?: string | null; 111 | "flow_type": FlowType; 112 | "app_name"?: string | null; 113 | "logo_url"?: string | null; 114 | "dark_logo_url"?: string | null; 115 | "brand_color"?: string | null; 116 | } 117 | } 118 | } 119 | export namespace __default { 120 | export interface current_user extends User {} 121 | } 122 | export namespace cfg { 123 | export interface ConfigObject extends std.BaseObject {} 124 | export interface AbstractConfig extends ConfigObject { 125 | "query_execution_timeout": edgedb.Duration; 126 | "session_idle_timeout": edgedb.Duration; 127 | "session_idle_transaction_timeout": edgedb.Duration; 128 | "listen_port": number; 129 | "listen_addresses": string[]; 130 | "allow_dml_in_functions"?: boolean | null; 131 | "allow_bare_ddl"?: AllowBareDDL | null; 132 | "apply_access_policies"?: boolean | null; 133 | "allow_user_specified_id"?: boolean | null; 134 | "shared_buffers"?: edgedb.ConfigMemory | null; 135 | "query_work_mem"?: edgedb.ConfigMemory | null; 136 | "maintenance_work_mem"?: edgedb.ConfigMemory | null; 137 | "effective_cache_size"?: edgedb.ConfigMemory | null; 138 | "effective_io_concurrency"?: number | null; 139 | "default_statistics_target"?: number | null; 140 | "force_database_error"?: string | null; 141 | "_pg_prepared_statement_cache_size": number; 142 | "extensions": ExtensionConfig[]; 143 | "auth": Auth[]; 144 | } 145 | export type AllowBareDDL = "AlwaysAllow" | "NeverAllow"; 146 | export interface Auth extends ConfigObject { 147 | "priority": number; 148 | "user": string[]; 149 | "comment"?: string | null; 150 | "method"?: AuthMethod | null; 151 | } 152 | export interface AuthMethod extends ConfigObject { 153 | "transports": ConnectionTransport[]; 154 | } 155 | export interface Config extends AbstractConfig {} 156 | export type ConnectionTransport = "TCP" | "TCP_PG" | "HTTP" | "SIMPLE_HTTP"; 157 | export interface DatabaseConfig extends AbstractConfig {} 158 | export interface ExtensionConfig extends ConfigObject { 159 | "cfg": AbstractConfig; 160 | } 161 | export interface InstanceConfig extends AbstractConfig {} 162 | export interface JWT extends AuthMethod { 163 | "transports": ConnectionTransport[]; 164 | } 165 | export interface Password extends AuthMethod { 166 | "transports": ConnectionTransport[]; 167 | } 168 | export interface SCRAM extends AuthMethod { 169 | "transports": ConnectionTransport[]; 170 | } 171 | export interface Trust extends AuthMethod {} 172 | } 173 | export namespace fts { 174 | export type ElasticLanguage = "ara" | "bul" | "cat" | "ces" | "ckb" | "dan" | "deu" | "ell" | "eng" | "eus" | "fas" | "fin" | "fra" | "gle" | "glg" | "hin" | "hun" | "hye" | "ind" | "ita" | "lav" | "nld" | "nor" | "por" | "ron" | "rus" | "spa" | "swe" | "tha" | "tur" | "zho" | "edb_Brazilian" | "edb_ChineseJapaneseKorean"; 175 | export type Language = "ara" | "hye" | "eus" | "cat" | "dan" | "nld" | "eng" | "fin" | "fra" | "deu" | "ell" | "hin" | "hun" | "ind" | "gle" | "ita" | "nor" | "por" | "ron" | "rus" | "spa" | "swe" | "tur"; 176 | export type LuceneLanguage = "ara" | "ben" | "bul" | "cat" | "ces" | "ckb" | "dan" | "deu" | "ell" | "eng" | "est" | "eus" | "fas" | "fin" | "fra" | "gle" | "glg" | "hin" | "hun" | "hye" | "ind" | "ita" | "lav" | "lit" | "nld" | "nor" | "por" | "ron" | "rus" | "spa" | "srp" | "swe" | "tha" | "tur" | "edb_Brazilian" | "edb_ChineseJapaneseKorean" | "edb_Indian"; 177 | export type PGLanguage = "xxx_simple" | "ara" | "hye" | "eus" | "cat" | "dan" | "nld" | "eng" | "fin" | "fra" | "deu" | "ell" | "hin" | "hun" | "ind" | "gle" | "ita" | "lit" | "npi" | "nor" | "por" | "ron" | "rus" | "srp" | "spa" | "swe" | "tam" | "tur" | "yid"; 178 | export type Weight = "A" | "B" | "C" | "D"; 179 | } 180 | export namespace schema { 181 | export type AccessKind = "Select" | "UpdateRead" | "UpdateWrite" | "Delete" | "Insert"; 182 | export interface $Object extends std.BaseObject { 183 | "name": string; 184 | "internal": boolean; 185 | "builtin": boolean; 186 | "computed_fields"?: string[] | null; 187 | } 188 | export interface SubclassableObject extends $Object { 189 | "abstract"?: boolean | null; 190 | "is_abstract"?: boolean | null; 191 | "final": boolean; 192 | "is_final": boolean; 193 | } 194 | export interface InheritingObject extends SubclassableObject { 195 | "inherited_fields"?: string[] | null; 196 | "bases": InheritingObject[]; 197 | "ancestors": InheritingObject[]; 198 | } 199 | export interface AnnotationSubject extends $Object { 200 | "annotations": Annotation[]; 201 | } 202 | export interface AccessPolicy extends InheritingObject, AnnotationSubject { 203 | "access_kinds": AccessKind[]; 204 | "condition"?: string | null; 205 | "action": AccessPolicyAction; 206 | "expr"?: string | null; 207 | "errmessage"?: string | null; 208 | "subject": ObjectType; 209 | } 210 | export type AccessPolicyAction = "Allow" | "Deny"; 211 | export interface Alias extends AnnotationSubject { 212 | "expr": string; 213 | "type"?: Type | null; 214 | } 215 | export interface Annotation extends InheritingObject, AnnotationSubject { 216 | "inheritable"?: boolean | null; 217 | } 218 | export interface Type extends SubclassableObject, AnnotationSubject { 219 | "expr"?: string | null; 220 | "from_alias"?: boolean | null; 221 | "is_from_alias"?: boolean | null; 222 | } 223 | export interface PrimitiveType extends Type {} 224 | export interface CollectionType extends PrimitiveType {} 225 | export interface Array extends CollectionType { 226 | "dimensions"?: number[] | null; 227 | "element_type": Type; 228 | } 229 | export interface ArrayExprAlias extends Array {} 230 | export interface CallableObject extends AnnotationSubject { 231 | "return_typemod"?: TypeModifier | null; 232 | "params": Parameter[]; 233 | "return_type"?: Type | null; 234 | } 235 | export type Cardinality = "One" | "Many"; 236 | export interface VolatilitySubject extends $Object { 237 | "volatility"?: Volatility | null; 238 | } 239 | export interface Cast extends AnnotationSubject, VolatilitySubject { 240 | "allow_implicit"?: boolean | null; 241 | "allow_assignment"?: boolean | null; 242 | "from_type"?: Type | null; 243 | "to_type"?: Type | null; 244 | } 245 | export interface ConsistencySubject extends InheritingObject, AnnotationSubject { 246 | "constraints": Constraint[]; 247 | } 248 | export interface Constraint extends CallableObject, InheritingObject { 249 | "expr"?: string | null; 250 | "subjectexpr"?: string | null; 251 | "finalexpr"?: string | null; 252 | "errmessage"?: string | null; 253 | "delegated"?: boolean | null; 254 | "except_expr"?: string | null; 255 | "subject"?: ConsistencySubject | null; 256 | "params": Parameter[]; 257 | } 258 | export interface Delta extends $Object { 259 | "parents": Delta[]; 260 | } 261 | export interface Extension extends AnnotationSubject, $Object { 262 | "package": sys.ExtensionPackage; 263 | } 264 | export interface Function extends CallableObject, VolatilitySubject { 265 | "preserves_optionality"?: boolean | null; 266 | "body"?: string | null; 267 | "language": string; 268 | "used_globals": Global[]; 269 | } 270 | export interface FutureBehavior extends $Object {} 271 | export interface Global extends AnnotationSubject { 272 | "required"?: boolean | null; 273 | "cardinality"?: Cardinality | null; 274 | "expr"?: string | null; 275 | "default"?: string | null; 276 | "target"?: Type | null; 277 | } 278 | export interface Index extends InheritingObject, AnnotationSubject { 279 | "kwargs"?: {name: string, expr: string}[] | null; 280 | "expr"?: string | null; 281 | "except_expr"?: string | null; 282 | "params": Parameter[]; 283 | } 284 | export interface Pointer extends ConsistencySubject, AnnotationSubject { 285 | "cardinality"?: Cardinality | null; 286 | "required"?: boolean | null; 287 | "readonly"?: boolean | null; 288 | "default"?: string | null; 289 | "expr"?: string | null; 290 | "source"?: Source | null; 291 | "target"?: Type | null; 292 | "rewrites": Rewrite[]; 293 | } 294 | export interface Source extends $Object { 295 | "pointers": Pointer[]; 296 | "indexes": Index[]; 297 | } 298 | export interface Link extends Pointer, Source { 299 | "on_target_delete"?: TargetDeleteAction | null; 300 | "on_source_delete"?: SourceDeleteAction | null; 301 | "target"?: ObjectType | null; 302 | "properties": Property[]; 303 | } 304 | export interface Migration extends AnnotationSubject, $Object { 305 | "script": string; 306 | "message"?: string | null; 307 | "generated_by"?: MigrationGeneratedBy | null; 308 | "parents": Migration[]; 309 | } 310 | export type MigrationGeneratedBy = "DevMode" | "DDLStatement"; 311 | export interface Module extends AnnotationSubject, $Object {} 312 | export interface MultiRange extends CollectionType { 313 | "element_type": Type; 314 | } 315 | export interface MultiRangeExprAlias extends MultiRange {} 316 | export interface ObjectType extends Source, ConsistencySubject, InheritingObject, Type, AnnotationSubject { 317 | "compound_type": boolean; 318 | "is_compound_type": boolean; 319 | "union_of": ObjectType[]; 320 | "intersection_of": ObjectType[]; 321 | "properties": Property[]; 322 | "links": Link[]; 323 | "access_policies": AccessPolicy[]; 324 | "triggers": Trigger[]; 325 | } 326 | export interface Operator extends CallableObject, VolatilitySubject { 327 | "operator_kind"?: OperatorKind | null; 328 | "is_abstract"?: boolean | null; 329 | "abstract"?: boolean | null; 330 | } 331 | export type OperatorKind = "Infix" | "Postfix" | "Prefix" | "Ternary"; 332 | export interface Parameter extends $Object { 333 | "typemod": TypeModifier; 334 | "kind": ParameterKind; 335 | "num": number; 336 | "default"?: string | null; 337 | "type": Type; 338 | } 339 | export type ParameterKind = "VariadicParam" | "NamedOnlyParam" | "PositionalParam"; 340 | export interface Property extends Pointer {} 341 | export interface PseudoType extends InheritingObject, Type {} 342 | export interface Range extends CollectionType { 343 | "element_type": Type; 344 | } 345 | export interface RangeExprAlias extends Range {} 346 | export interface Rewrite extends InheritingObject, AnnotationSubject { 347 | "kind": TriggerKind; 348 | "expr": string; 349 | "subject": Pointer; 350 | } 351 | export type RewriteKind = "Update" | "Insert"; 352 | export interface ScalarType extends PrimitiveType, ConsistencySubject, AnnotationSubject { 353 | "default"?: string | null; 354 | "enum_values"?: string[] | null; 355 | "arg_values"?: string[] | null; 356 | } 357 | export type SourceDeleteAction = "DeleteTarget" | "Allow" | "DeleteTargetIfOrphan"; 358 | export type TargetDeleteAction = "Restrict" | "DeleteSource" | "Allow" | "DeferredRestrict"; 359 | export interface Trigger extends InheritingObject, AnnotationSubject { 360 | "timing": TriggerTiming; 361 | "kinds": TriggerKind[]; 362 | "scope": TriggerScope; 363 | "expr"?: string | null; 364 | "condition"?: string | null; 365 | "subject": ObjectType; 366 | } 367 | export type TriggerKind = "Update" | "Delete" | "Insert"; 368 | export type TriggerScope = "All" | "Each"; 369 | export type TriggerTiming = "After" | "AfterCommitOf"; 370 | export interface Tuple extends CollectionType { 371 | "named": boolean; 372 | "element_types": TupleElement[]; 373 | } 374 | export interface TupleElement extends std.BaseObject { 375 | "name"?: string | null; 376 | "type": Type; 377 | } 378 | export interface TupleExprAlias extends Tuple {} 379 | export type TypeModifier = "SetOfType" | "OptionalType" | "SingletonType"; 380 | export type Volatility = "Immutable" | "Stable" | "Volatile"; 381 | } 382 | export namespace sys { 383 | export interface SystemObject extends schema.$Object {} 384 | export interface ExternalObject extends SystemObject {} 385 | export interface Database extends ExternalObject, schema.AnnotationSubject { 386 | "name": string; 387 | } 388 | export interface ExtensionPackage extends SystemObject, schema.AnnotationSubject { 389 | "script": string; 390 | "version": {major: number, minor: number, stage: VersionStage, stage_no: number, local: string[]}; 391 | } 392 | export interface Role extends SystemObject, schema.InheritingObject, schema.AnnotationSubject { 393 | "name": string; 394 | "superuser": boolean; 395 | "is_superuser": boolean; 396 | "password"?: string | null; 397 | "member_of": Role[]; 398 | } 399 | export type TransactionIsolation = "RepeatableRead" | "Serializable"; 400 | export type VersionStage = "dev" | "alpha" | "beta" | "rc" | "final"; 401 | } 402 | export interface types { 403 | "std": { 404 | "BaseObject": std.BaseObject; 405 | "Object": std.$Object; 406 | "FreeObject": std.FreeObject; 407 | "JsonEmpty": std.JsonEmpty; 408 | "enc": { 409 | "Base64Alphabet": std.enc.Base64Alphabet; 410 | }; 411 | }; 412 | "default": { 413 | "User": User; 414 | "BlogPost": BlogPost; 415 | "current_user": current_user; 416 | }; 417 | "ext": { 418 | "auth": { 419 | "ProviderConfig": ext.auth.ProviderConfig; 420 | "OAuthProviderConfig": ext.auth.OAuthProviderConfig; 421 | "AppleOAuthProvider": ext.auth.AppleOAuthProvider; 422 | "Auditable": ext.auth.Auditable; 423 | "AuthConfig": ext.auth.AuthConfig; 424 | "AzureOAuthProvider": ext.auth.AzureOAuthProvider; 425 | "Identity": ext.auth.Identity; 426 | "ClientTokenIdentity": ext.auth.ClientTokenIdentity; 427 | "Factor": ext.auth.Factor; 428 | "EmailFactor": ext.auth.EmailFactor; 429 | "EmailPasswordFactor": ext.auth.EmailPasswordFactor; 430 | "EmailPasswordProviderConfig": ext.auth.EmailPasswordProviderConfig; 431 | "FlowType": ext.auth.FlowType; 432 | "GitHubOAuthProvider": ext.auth.GitHubOAuthProvider; 433 | "GoogleOAuthProvider": ext.auth.GoogleOAuthProvider; 434 | "JWTAlgo": ext.auth.JWTAlgo; 435 | "LocalIdentity": ext.auth.LocalIdentity; 436 | "PKCEChallenge": ext.auth.PKCEChallenge; 437 | "SMTPConfig": ext.auth.SMTPConfig; 438 | "SMTPSecurity": ext.auth.SMTPSecurity; 439 | "UIConfig": ext.auth.UIConfig; 440 | }; 441 | }; 442 | "__default": { 443 | "current_user": __default.current_user; 444 | }; 445 | "cfg": { 446 | "ConfigObject": cfg.ConfigObject; 447 | "AbstractConfig": cfg.AbstractConfig; 448 | "AllowBareDDL": cfg.AllowBareDDL; 449 | "Auth": cfg.Auth; 450 | "AuthMethod": cfg.AuthMethod; 451 | "Config": cfg.Config; 452 | "ConnectionTransport": cfg.ConnectionTransport; 453 | "DatabaseConfig": cfg.DatabaseConfig; 454 | "ExtensionConfig": cfg.ExtensionConfig; 455 | "InstanceConfig": cfg.InstanceConfig; 456 | "JWT": cfg.JWT; 457 | "Password": cfg.Password; 458 | "SCRAM": cfg.SCRAM; 459 | "Trust": cfg.Trust; 460 | }; 461 | "fts": { 462 | "ElasticLanguage": fts.ElasticLanguage; 463 | "Language": fts.Language; 464 | "LuceneLanguage": fts.LuceneLanguage; 465 | "PGLanguage": fts.PGLanguage; 466 | "Weight": fts.Weight; 467 | }; 468 | "schema": { 469 | "AccessKind": schema.AccessKind; 470 | "Object": schema.$Object; 471 | "SubclassableObject": schema.SubclassableObject; 472 | "InheritingObject": schema.InheritingObject; 473 | "AnnotationSubject": schema.AnnotationSubject; 474 | "AccessPolicy": schema.AccessPolicy; 475 | "AccessPolicyAction": schema.AccessPolicyAction; 476 | "Alias": schema.Alias; 477 | "Annotation": schema.Annotation; 478 | "Type": schema.Type; 479 | "PrimitiveType": schema.PrimitiveType; 480 | "CollectionType": schema.CollectionType; 481 | "Array": schema.Array; 482 | "ArrayExprAlias": schema.ArrayExprAlias; 483 | "CallableObject": schema.CallableObject; 484 | "Cardinality": schema.Cardinality; 485 | "VolatilitySubject": schema.VolatilitySubject; 486 | "Cast": schema.Cast; 487 | "ConsistencySubject": schema.ConsistencySubject; 488 | "Constraint": schema.Constraint; 489 | "Delta": schema.Delta; 490 | "Extension": schema.Extension; 491 | "Function": schema.Function; 492 | "FutureBehavior": schema.FutureBehavior; 493 | "Global": schema.Global; 494 | "Index": schema.Index; 495 | "Pointer": schema.Pointer; 496 | "Source": schema.Source; 497 | "Link": schema.Link; 498 | "Migration": schema.Migration; 499 | "MigrationGeneratedBy": schema.MigrationGeneratedBy; 500 | "Module": schema.Module; 501 | "MultiRange": schema.MultiRange; 502 | "MultiRangeExprAlias": schema.MultiRangeExprAlias; 503 | "ObjectType": schema.ObjectType; 504 | "Operator": schema.Operator; 505 | "OperatorKind": schema.OperatorKind; 506 | "Parameter": schema.Parameter; 507 | "ParameterKind": schema.ParameterKind; 508 | "Property": schema.Property; 509 | "PseudoType": schema.PseudoType; 510 | "Range": schema.Range; 511 | "RangeExprAlias": schema.RangeExprAlias; 512 | "Rewrite": schema.Rewrite; 513 | "RewriteKind": schema.RewriteKind; 514 | "ScalarType": schema.ScalarType; 515 | "SourceDeleteAction": schema.SourceDeleteAction; 516 | "TargetDeleteAction": schema.TargetDeleteAction; 517 | "Trigger": schema.Trigger; 518 | "TriggerKind": schema.TriggerKind; 519 | "TriggerScope": schema.TriggerScope; 520 | "TriggerTiming": schema.TriggerTiming; 521 | "Tuple": schema.Tuple; 522 | "TupleElement": schema.TupleElement; 523 | "TupleExprAlias": schema.TupleExprAlias; 524 | "TypeModifier": schema.TypeModifier; 525 | "Volatility": schema.Volatility; 526 | }; 527 | "sys": { 528 | "SystemObject": sys.SystemObject; 529 | "ExternalObject": sys.ExternalObject; 530 | "Database": sys.Database; 531 | "ExtensionPackage": sys.ExtensionPackage; 532 | "Role": sys.Role; 533 | "TransactionIsolation": sys.TransactionIsolation; 534 | "VersionStage": sys.VersionStage; 535 | }; 536 | } 537 | 538 | 539 | export namespace helper { 540 | type LinkType = std.BaseObject | std.BaseObject[]; 541 | 542 | export type propertyKeys = { 543 | [k in keyof T]: NonNullable extends LinkType ? never : k; 544 | }[keyof T]; 545 | 546 | export type linkKeys = { 547 | [k in keyof T]: NonNullable extends LinkType ? k : never; 548 | }[keyof T]; 549 | 550 | export type Props = Pick>; 551 | export type Links = Pick>; 552 | } 553 | -------------------------------------------------------------------------------- /playground/dbschema/migrations/00001.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m1nsxvycfjoyjrxdeejwuuovtufkpjn3vxqg3hsxfxrvwcicxtquya 2 | ONTO initial 3 | { 4 | CREATE EXTENSION pgcrypto VERSION '1.3'; 5 | CREATE EXTENSION auth VERSION '1.0'; 6 | 7 | CREATE TYPE default::User { 8 | CREATE REQUIRED LINK identity: ext::auth::Identity; 9 | CREATE REQUIRED PROPERTY name: std::str; 10 | }; 11 | 12 | CREATE GLOBAL default::current_user := (std::assert_single((SELECT 13 | default::User { 14 | id, 15 | name 16 | } 17 | FILTER 18 | (.identity = GLOBAL ext::auth::ClientTokenIdentity) 19 | ))); 20 | 21 | CREATE TYPE default::BlogPost { 22 | CREATE REQUIRED LINK author: default::User { 23 | SET default := (GLOBAL default::current_user); 24 | }; 25 | CREATE ACCESS POLICY author_has_full_access 26 | ALLOW ALL USING ((.author ?= GLOBAL default::current_user)); 27 | CREATE ACCESS POLICY others_read_only 28 | ALLOW SELECT ; 29 | CREATE PROPERTY content: std::str { 30 | SET default := 'My super blog post.'; 31 | }; 32 | CREATE PROPERTY description: std::str { 33 | SET default := 'My blog post description.'; 34 | }; 35 | CREATE PROPERTY title: std::str { 36 | SET default := 'My blog super blog post title.'; 37 | }; 38 | }; 39 | 40 | ALTER TYPE default::User { 41 | CREATE MULTI LINK posts: default::BlogPost { 42 | ON SOURCE DELETE DELETE TARGET; 43 | }; 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /playground/edgedb.toml: -------------------------------------------------------------------------------- 1 | [edgedb] 2 | server-version = "4.0" 3 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | modules: [ 3 | '../src/module', 4 | '@nuxt/ui', 5 | ], 6 | edgeDb: { 7 | auth: true, 8 | oauth: true, 9 | }, 10 | devtools: { enabled: true }, 11 | tailwindcss: { 12 | viewer: false, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-module-playground", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate", 9 | "prepare": "nuxi prepare" 10 | }, 11 | "dependencies": { 12 | "@edgedb/generate": "^0.4.1", 13 | "@iconify-json/heroicons": "^1.1.20", 14 | "@nuxt/ui": "^2.14.2", 15 | "nuxt-edgedb-module": "latest" 16 | }, 17 | "devDependencies": { 18 | "@nuxt/devtools": "latest", 19 | "nuxt": "latest" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /playground/pages/auth/callback.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /playground/pages/auth/forgot-password.vue: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /playground/pages/auth/login.vue: -------------------------------------------------------------------------------- 1 | 51 | -------------------------------------------------------------------------------- /playground/pages/auth/logout.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /playground/pages/auth/reset-password.vue: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /playground/pages/auth/signup.vue: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /playground/pages/auth/verify.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /playground/pages/blogposts/[id].vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 35 | 40 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 63 | -------------------------------------------------------------------------------- /playground/pages/new.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 82 | -------------------------------------------------------------------------------- /playground/pnpm-lock.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tahul/nuxt-edgedb/a1ffd27c44b0ff9870654754fcab2b11baa6c211/playground/pnpm-lock.yaml -------------------------------------------------------------------------------- /playground/queries/allBlogPosts.edgeql: -------------------------------------------------------------------------------- 1 | select BlogPost { 2 | id, 3 | title, 4 | description, 5 | content, 6 | } 7 | -------------------------------------------------------------------------------- /playground/queries/deleteBlogPost.edgeql: -------------------------------------------------------------------------------- 1 | delete BlogPost 2 | filter .id = $blogpost_id; 3 | -------------------------------------------------------------------------------- /playground/queries/getBlogPost.edgeql: -------------------------------------------------------------------------------- 1 | select BlogPost { 2 | id, 3 | title, 4 | description, 5 | content 6 | } 7 | filter .id = $blogpost_id; 8 | -------------------------------------------------------------------------------- /playground/queries/insertBlogPost.edgeql: -------------------------------------------------------------------------------- 1 | insert BlogPost { 2 | title := $blogpost_title, 3 | description := $blogpost_description, 4 | content := $blogpost_content, 5 | author := global current_user 6 | } 7 | -------------------------------------------------------------------------------- /playground/server/api/blogpost.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler, getQuery, isMethod, readBody } from 'h3' 2 | import type { BlogPost } from '#edgedb/interfaces' 3 | 4 | export default defineEventHandler(async (req) => { 5 | const { insertBlogPost, allBlogPosts, deleteBlogPost, getBlogPost } = useEdgeDbQueries(req) 6 | const query = getQuery(req) 7 | const id = query?.id as string | undefined 8 | 9 | if (isMethod(req, 'POST')) { 10 | const body = await readBody(req) 11 | const { title, description, content } = body 12 | 13 | const blogPost = await insertBlogPost({ 14 | blogpost_title: title, 15 | blogpost_description: description, 16 | blogpost_content: content, 17 | }) 18 | 19 | return blogPost 20 | } 21 | 22 | if (isMethod(req, 'GET')) { 23 | if (id) { 24 | const blogpost = await getBlogPost({ blogpost_id: id }) 25 | return blogpost as BlogPost 26 | } 27 | 28 | const count = await useEdgeDb().query('select count(BlogPost);').then(count => count?.[0] || 0) 29 | 30 | return count ? await allBlogPosts() : [] 31 | } 32 | 33 | if (isMethod(req, 'DELETE') && id) { 34 | await deleteBlogPost({ blogpost_id: id }) 35 | return { deleted: id } 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /playground/server/plugins/auth.ts: -------------------------------------------------------------------------------- 1 | export default defineNitroPlugin((app) => { 2 | app.hooks.hook( 3 | 'edgedb:auth:callback' as any, 4 | () => { 5 | console.log('auth callback!') 6 | }, 7 | ) 8 | }) 9 | -------------------------------------------------------------------------------- /playground/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playground 3 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'node:fs' 2 | import * as p from '@clack/prompts' 3 | import * as execa from 'execa' 4 | import { createResolver } from '@nuxt/kit' 5 | import chalk from 'chalk' 6 | 7 | const { resolve: resolveProject } = createResolver(process.cwd()) 8 | 9 | async function up() { 10 | p.intro(chalk.bgGreen.blue(` nuxt-edgedb `)) 11 | 12 | /** 13 | * CLI Install detection 14 | */ 15 | let edgedbCliVersion: string | undefined 16 | try { 17 | edgedbCliVersion = await execa.execa(`edgedb`, [`--version`], { cwd: resolveProject() }).then(result => result.stdout.replace('EdgeDB CLI ', '')) 18 | } 19 | catch (e) {} 20 | 21 | if (!edgedbCliVersion) { 22 | const setupEdgeDbCli = await p.select({ 23 | message: 'EdgeDB CLI not found, do you want to install EdgeDB it?', 24 | options: [ 25 | { label: 'Yes', value: 'yes', hint: 'recommended' }, 26 | { label: 'No', value: 'no', hint: 'skip installation' }, 27 | ], 28 | }) 29 | 30 | if (setupEdgeDbCli === 'yes') { 31 | const spinner = p.spinner() 32 | 33 | try { 34 | spinner.start('Installing EdgeDB CLI...') 35 | await execa.$`curl https://sh.edgedb.com --proto '=https' -sSf1 | sh` 36 | edgedbCliVersion = await execa.execa(`edgedb`, ['--version'], { cwd: resolveProject() }).then(result => result.stdout.replace('EdgeDB CLI ', '')) 37 | spinner.stop(`EdgeDB CLI version ${edgedbCliVersion} installed.`) 38 | } 39 | catch (e) { 40 | spinner.stop('Failed to install EdgeDB CLI.') 41 | p.log.warn(`Try running: \`${chalk.green('curl https://sh.edgedb.com --proto \'=https\' -sSf1 | sh')}\` manually.`) 42 | } 43 | } 44 | 45 | if (!edgedbCliVersion) { 46 | process.exit(0) 47 | } 48 | } 49 | else { 50 | p.log.success(`EdgeDB CLI version ${chalk.blue(edgedbCliVersion)} found.`) 51 | } 52 | 53 | const groupData = await p.group( 54 | { 55 | path: () => p.text({ message: 'Where to setup dbschema?', defaultValue: './dbschema', placeholder: './dbschema' }), 56 | interfaces: () => p.text({ message: 'Generate interfaces?', defaultValue: 'yes', placeholder: 'yes' }), 57 | queries: () => p.text({ message: 'Generate queries?', defaultValue: 'yes', placeholder: 'yes' }), 58 | queryBuilder: () => p.text({ message: 'Generate query builder?', defaultValue: 'yes', placeholder: 'yes' }), 59 | }, 60 | { 61 | onCancel: () => { 62 | p.cancel('Operation cancelled.') 63 | process.exit(0) 64 | }, 65 | }, 66 | ) 67 | 68 | const dbschemaPath = resolveProject(groupData.path) 69 | 70 | if (!existsSync(dbschemaPath)) { 71 | p.log.error(`Your ${chalk.green('dbschema')} directory does not exist, you must run \`${chalk.green('edgedb project init')}\` at least once before running this command.`) 72 | } 73 | 74 | if (groupData.interfaces === 'yes') { 75 | const spinner = p.spinner() 76 | 77 | spinner.start('Generating interfaces...') 78 | await execa.$`npx @edgedb/generate interfaces --file ${dbschemaPath}/interfaces.ts --force-overwrite` 79 | spinner.stop('Interfaces generated.') 80 | } 81 | 82 | if (groupData.queries === 'yes') { 83 | const spinner = p.spinner() 84 | 85 | spinner.start('Generating queries...') 86 | await execa.$`npx @edgedb/generate queries --file ${dbschemaPath}/queries --target=ts --force-overwrite` 87 | spinner.stop('Queries generated.') 88 | } 89 | 90 | if (groupData.queryBuilder === 'yes') { 91 | const spinner = p.spinner() 92 | 93 | spinner.start('Generating query builder...') 94 | await execa.$`npx @edgedb/generate edgeql-js --output-dir ${dbschemaPath}/query-builder --force-overwrite --target=ts` 95 | spinner.stop('Query builder generated.') 96 | } 97 | 98 | p.log.success('Done. Feel free to checkout the next steps on the README') 99 | 100 | p.log.success('https://github.com/tahul/nuxt-edgedb#readme') 101 | } 102 | 103 | up() 104 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'node:fs' 2 | import type { NuxtModule } from 'nuxt/schema' 3 | import { addComponentsDir, addImports, addPlugin, addServerHandler, addServerImports, addServerPlugin, createResolver, defineNuxtModule, logger } from '@nuxt/kit' 4 | import { join } from 'pathe' 5 | import * as execa from 'execa' 6 | import chalk from 'chalk' 7 | import { getEdgeDbConfiguration } from './utils' 8 | 9 | // Module options TypeScript interface definition 10 | export interface ModuleOptions { 11 | devtools: boolean 12 | watch: boolean 13 | watchPrompt: true 14 | dbschemaDir: string 15 | queriesDir: string 16 | composables: boolean 17 | auth: boolean 18 | oauth: boolean 19 | injectDbCredentials: boolean 20 | projectInit: boolean 21 | installCli: boolean 22 | identityModel: string 23 | } 24 | 25 | const { resolve: resolveLocal } = createResolver(import.meta.url) 26 | 27 | const nuxtModule = defineNuxtModule({ 28 | meta: { 29 | name: 'nuxt-edgedb-module', 30 | configKey: 'edgeDb', 31 | }, 32 | // Default configuration options of the Nuxt module 33 | defaults: { 34 | devtools: true, 35 | watch: true, 36 | watchPrompt: true, 37 | dbschemaDir: 'dbschema', 38 | queriesDir: 'queries', 39 | projectInit: true, 40 | installCli: true, 41 | composables: true, 42 | injectDbCredentials: true, 43 | auth: false, 44 | oauth: false, 45 | identityModel: 'User', 46 | }, 47 | async setup(options, nuxt) { 48 | const { resolve: resolveProject } = createResolver(nuxt.options.rootDir) 49 | const dbschemaDir = resolveProject(options.dbschemaDir) 50 | const canPrompt = nuxt.options.dev 51 | 52 | // Transpile edgedb 53 | nuxt.options.build.transpile ??= [] 54 | nuxt.options.build.transpile.push('edgedb') 55 | nuxt.options.build.transpile.push('nuxt-edgedb-module') 56 | 57 | const envAppUrl = process.env.APP_URL || process.env.NUXT_EDGEDB_APP_URL 58 | 59 | // Create dev app url 60 | const devAppUrl = [ 61 | nuxt.options.devServer.https ? `https://` : `http://`, 62 | nuxt.options.devServer.host ? nuxt.options.devServer.host : 'localhost', 63 | nuxt.options.devServer.port ? `:${nuxt.options.devServer.port}` : '', 64 | ].join('') 65 | 66 | const appUrl = envAppUrl || devAppUrl 67 | 68 | // Inject runtime configuration 69 | nuxt.options.runtimeConfig.edgeDb ??= await getEdgeDbConfiguration(appUrl, options, resolveProject(), options.injectDbCredentials) as any 70 | 71 | /** 72 | * Devtools 73 | */ 74 | 75 | if (canPrompt && options.devtools) { 76 | let uiUrl: any | undefined 77 | if (!process.env.NUXT_EDGEDB_UI_URL && options.injectDbCredentials) { 78 | try { 79 | uiUrl = await execa.execa(`edgedb`, ['ui', '--print-url'], { cwd: resolveProject() }) 80 | } 81 | catch (e) { 82 | // 83 | } 84 | } 85 | 86 | if (process.env?.NUXT_EDGEDB_UI_URL || uiUrl?.stdout) { 87 | nuxt.hook('devtools:customTabs' as any, (tabs: any[]) => { 88 | tabs.push({ 89 | // unique identifier 90 | name: 'nuxt-edgedb-module', 91 | // title to display in the tab 92 | title: 'EdgeDB', 93 | // any icon from Iconify, or a URL to an image 94 | icon: 'logos:edgedb', 95 | category: 'app', 96 | // iframe view 97 | view: { 98 | type: 'iframe', 99 | src: process.env?.NUXT_EDGEDB_UI_URL || uiUrl.stdout, 100 | persistent: true, 101 | }, 102 | }) 103 | }) 104 | } 105 | } 106 | 107 | if (!existsSync(dbschemaDir)) { 108 | logger.withTag('edgedb').error(`Could not find dbschema directory.\n\nYou must run "${chalk.green.bold('edgedb project init')}" in your project root.`) 109 | process.exit(1) 110 | } 111 | 112 | const queriesPath = join(dbschemaDir, '/queries.ts') 113 | const interfacesPath = join(dbschemaDir, '/interfaces.ts') 114 | const builderPath = join(dbschemaDir, '/query-builder/index.ts') 115 | 116 | const hasQueries = existsSync(queriesPath) 117 | const hasInterfaces = existsSync(interfacesPath) 118 | const hasQueryBuilder = existsSync(builderPath) 119 | 120 | // Inject aliases 121 | const nuxtOptions = nuxt.options 122 | nuxtOptions.alias = nuxtOptions.alias ?? {} 123 | 124 | if (hasQueries) 125 | nuxtOptions.alias['#edgedb/queries'] = queriesPath 126 | if (hasInterfaces) 127 | nuxtOptions.alias['#edgedb/interfaces'] = interfacesPath 128 | if (hasQueryBuilder) 129 | nuxtOptions.alias['#edgedb/builder'] = builderPath 130 | 131 | if (options.composables) { 132 | // Add server plugin for EdgeDB client 133 | addServerPlugin(resolveLocal('./runtime/server/plugins/edgedb-client')) 134 | 135 | // Add server imports manually 136 | addServerImports([ 137 | { 138 | from: resolveLocal('./runtime/server/composables/useEdgeDb'), 139 | name: 'useEdgeDb', 140 | }, 141 | { 142 | from: resolveLocal('./runtime/server/composables/useEdgeDbEnv'), 143 | name: 'useEdgeDbEnv', 144 | }, 145 | { 146 | from: resolveLocal('./runtime/server/composables/useEdgeDbPKCE'), 147 | name: 'useEdgeDbPKCE', 148 | }, 149 | ]) 150 | 151 | if (hasQueryBuilder) { 152 | addServerImports([ 153 | { 154 | from: resolveLocal('./runtime/server/composables/useEdgeDbQueryBuilder'), 155 | name: 'useEdgeDbQueryBuilder', 156 | }, 157 | ]) 158 | } 159 | 160 | if (hasQueries) { 161 | addServerImports([ 162 | { 163 | from: resolveLocal('./runtime/server/composables/useEdgeDbQueries'), 164 | name: 'useEdgeDbQueries', 165 | }, 166 | ]) 167 | } 168 | 169 | // Add server-side auto-imports 170 | nuxt.hook( 171 | 'nitro:config', 172 | (config) => { 173 | // Push externals 174 | config.externals ??= {} 175 | config.externals.inline ??= [] 176 | config.externals.inline.push(resolveLocal('./runtime/server')) 177 | 178 | // Fixes for weird cjs query builder imports 179 | if (hasQueryBuilder) { 180 | config.replace ??= {} 181 | config.replace['edgedb/dist/primitives/buffer'] = 'edgedb/dist/primitives/buffer.js' 182 | config.replace['edgedb/dist/reflection/index'] = 'edgedb/dist/reflection/index.js' 183 | } 184 | 185 | // Push server aliases 186 | config.alias ??= {} 187 | 188 | if (hasQueries) 189 | config.alias['#edgedb/queries'] = join(dbschemaDir, '/queries.ts') 190 | if (hasInterfaces) 191 | config.alias['#edgedb/interfaces'] = join(dbschemaDir, '/interfaces.ts') 192 | if (hasQueryBuilder) 193 | config.alias['#edgedb/builder'] = join(dbschemaDir, '/query-builder/index.ts') 194 | 195 | // Enforce paths on typescript config 196 | config.typescript ??= {} 197 | config.typescript.tsConfig ??= {} 198 | config.typescript.tsConfig.compilerOptions ??= {} 199 | config.typescript.tsConfig.compilerOptions.paths ??= {} 200 | 201 | if (hasQueries) 202 | config.typescript.tsConfig.compilerOptions.paths['#edgedb/queries'] = [`${join(dbschemaDir, '/queries.ts')}`] 203 | if (hasInterfaces) 204 | config.typescript.tsConfig.compilerOptions.paths['#edgedb/interfaces'] = [`${join(dbschemaDir, '/interfaces.ts')}`] 205 | if (hasQueryBuilder) 206 | config.typescript.tsConfig.compilerOptions.paths['#edgedb/builder'] = [`${join(dbschemaDir, '/query-builder/index.ts')}`] 207 | }, 208 | ) 209 | } 210 | 211 | if (options.auth) { 212 | // Runtime 213 | addPlugin({ 214 | src: resolveLocal('./runtime/plugins/edgedb-auth'), 215 | mode: 'all', 216 | }) 217 | addComponentsDir({ 218 | path: resolveLocal('./runtime/components/auth/base'), 219 | global: true, 220 | }) 221 | addImports([ 222 | { 223 | from: resolveLocal('./runtime/composables/useEdgeDbIdentity'), 224 | name: 'useEdgeDbIdentity', 225 | }, 226 | ]) 227 | 228 | // Server 229 | addServerImports([ 230 | { 231 | from: resolveLocal('./runtime/server/composables/useEdgeDbIdentity'), 232 | name: 'useEdgeDbIdentity', 233 | }, 234 | ]) 235 | addServerHandler({ 236 | handler: resolveLocal('./runtime/api/auth/login'), 237 | route: '/api/auth/login', 238 | }) 239 | addServerHandler({ 240 | handler: resolveLocal('./runtime/api/auth/logout'), 241 | route: '/api/auth/logout', 242 | }) 243 | addServerHandler({ 244 | handler: resolveLocal('./runtime/api/auth/verify'), 245 | route: '/api/auth/verify', 246 | }) 247 | addServerHandler({ 248 | handler: resolveLocal('./runtime/api/auth/callback'), 249 | route: '/api/auth/callback', 250 | }) 251 | addServerHandler({ 252 | handler: resolveLocal('./runtime/api/auth/reset-password-ui'), 253 | route: '/api/auth/reset-password-ui', 254 | }) 255 | addServerHandler({ 256 | handler: resolveLocal('./runtime/api/auth/reset-password'), 257 | route: '/api/auth/reset-password', 258 | }) 259 | addServerHandler({ 260 | handler: resolveLocal('./runtime/api/auth/send-password-reset-email'), 261 | route: '/api/auth/send-password-reset-email', 262 | }) 263 | addServerHandler({ 264 | handler: resolveLocal('./runtime/api/auth/signup'), 265 | route: '/api/auth/signup', 266 | }) 267 | addServerHandler({ 268 | handler: resolveLocal('./runtime/api/auth/identity'), 269 | route: '/api/auth/identity', 270 | }) 271 | addServerHandler({ 272 | handler: resolveLocal('./runtime/api/auth/providers'), 273 | route: '/api/auth/providers', 274 | }) 275 | } 276 | 277 | if (options.oauth) { 278 | addComponentsDir({ 279 | path: resolveLocal('./runtime/components/auth/oauth'), 280 | global: true, 281 | }) 282 | addServerHandler({ 283 | handler: resolveLocal('./runtime/api/auth/authorize'), 284 | route: '/api/auth/authorize', 285 | }) 286 | addServerHandler({ 287 | handler: resolveLocal('./runtime/api/auth/callback'), 288 | route: '/api/auth/callback', 289 | }) 290 | } 291 | }, 292 | }) 293 | 294 | export default nuxtModule 295 | 296 | declare module 'nuxt/schema' { 297 | interface NuxtConfig { 298 | ['edgeDb']?: typeof nuxtModule extends NuxtModule ? Partial : Record 299 | } 300 | 301 | interface RuntimeConfig { 302 | edgeDb: { 303 | auth: { 304 | enabled: boolean 305 | oauth: boolean 306 | identityModel: string 307 | } 308 | identityModel?: string 309 | urls: { 310 | appUrl?: string 311 | authBaseUrl?: string 312 | resetPasswordUrl?: string 313 | verifyRedirectUrl?: string 314 | oAuthCallbackUrl?: string 315 | oAuthRedirectUrl?: string 316 | } 317 | dsn: { 318 | host?: string 319 | port?: string 320 | user?: string 321 | pass?: string 322 | database?: string 323 | tlsCA?: string 324 | tlsSecurity?: 'insecure' | 'no_host_verification' | 'strict' | 'default' | undefined 325 | } 326 | } 327 | } 328 | 329 | interface PublicRuntimeConfig { 330 | 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/runtime/api/auth/authorize.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, getRequestURL, sendError, setHeaders } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | import { useEdgeDbPKCE } from '../../server/composables/useEdgeDbPKCE' 4 | 5 | /** 6 | * Redirects OAuth requests to EdgeDB Auth OAuth authorize redirect 7 | * with the PKCE challenge, and saves PKCE verifier in an HttpOnly 8 | * cookie for later retrieval. 9 | * 10 | * @param {Request} req 11 | */ 12 | export default defineEventHandler(async (req) => { 13 | const { urls } = useEdgeDbEnv() 14 | const { authBaseUrl, oAuthRedirectUrl } = urls 15 | const requestUrl = getRequestURL(req) 16 | const provider = requestUrl.searchParams.get('provider') 17 | 18 | if (!provider) { 19 | const err = new H3Error('Must provide a \'provider\' value in search parameters') 20 | err.statusCode = 400 21 | return sendError(req, err) 22 | } 23 | 24 | const pkce = useEdgeDbPKCE() 25 | const redirectUrl = new URL('authorize', authBaseUrl) 26 | redirectUrl.searchParams.set('provider', provider) 27 | redirectUrl.searchParams.set('challenge', pkce.challenge) 28 | redirectUrl.searchParams.set('redirect_to', oAuthRedirectUrl!) 29 | 30 | setHeaders( 31 | req, 32 | { 33 | 'Set-Cookie': `edgedb-pkce-verifier=${pkce.verifier}; HttpOnly; Path=/; Secure; SameSite=Strict`, 34 | }, 35 | ) 36 | 37 | return { 38 | redirect: redirectUrl, 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/runtime/api/auth/callback.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, getCookie, getRequestURL, sendError, setHeaders } from 'h3' 2 | import { useNitroApp } from 'nitropack/runtime' 3 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 4 | 5 | /** 6 | * Handles the PKCE callback and exchanges the `code` and `verifier` 7 | * for an auth_token, setting the auth_token as an HttpOnly cookie. 8 | * 9 | * @param {Request} req 10 | */ 11 | export default defineEventHandler(async (req) => { 12 | const { urls } = useEdgeDbEnv() 13 | const { authBaseUrl } = urls 14 | 15 | const requestUrl = getRequestURL(req) 16 | const code = requestUrl.searchParams.get('code') 17 | if (!code) { 18 | const error = requestUrl.searchParams.get('error') 19 | const err = new H3Error(`OAuth callback is missing 'code'. OAuth provider responded with error: ${error}`) 20 | err.statusCode = 400 21 | return sendError(req, err) 22 | } 23 | 24 | const verifier = getCookie(req, 'edgedb-pkce-verifier') 25 | if (!verifier) { 26 | const err = new H3Error(`Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?`) 27 | err.statusCode = 400 28 | return sendError(req, err) 29 | } 30 | 31 | const codeExchangeUrl = new URL('token', authBaseUrl) 32 | codeExchangeUrl.searchParams.set('code', code) 33 | codeExchangeUrl.searchParams.set('verifier', verifier) 34 | const codeExchangeResponse = await fetch(codeExchangeUrl.href, { 35 | method: 'GET', 36 | }) 37 | 38 | if (!codeExchangeResponse.ok) { 39 | const err = new H3Error(await codeExchangeResponse.text()) 40 | err.statusCode = 400 41 | return sendError(req, err) 42 | } 43 | 44 | const codeExchangeResponseData = await codeExchangeResponse.json() 45 | 46 | await useNitroApp().hooks.callHook( 47 | 'edgedb:auth:callback' as any, 48 | { 49 | code, 50 | verifier, 51 | codeExchangeUrl, 52 | codeExchangeResponseData, 53 | }, 54 | ) 55 | 56 | setHeaders(req, { 57 | 'Set-Cookie': `edgedb-auth-token=${codeExchangeResponseData.auth_token}; Path=/; HttpOnly`, 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /src/runtime/api/auth/identity.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler, deleteCookie, getCookie, setCookie } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | import { useEdgeDb } from '../../server/composables/useEdgeDb' 4 | 5 | export default defineEventHandler(async (event) => { 6 | const { auth } = useEdgeDbEnv() 7 | 8 | const token = getCookie(event, 'edgedb-auth-token') 9 | 10 | if (!token) { 11 | deleteCookie(event, 'edgedb-auth-token') 12 | return 13 | } 14 | 15 | const client = useEdgeDb(event) 16 | 17 | try { 18 | let identityTarget = await client.querySingle(`select global current_user;`) 19 | 20 | if (!identityTarget && token) { 21 | identityTarget = await client.query(` 22 | insert ${auth.identityModel} { 23 | name := '', 24 | identity := global ext::auth::ClientTokenIdentity 25 | } 26 | `) 27 | } 28 | 29 | return identityTarget 30 | } 31 | catch (err) { 32 | setCookie( 33 | event, 34 | 'edgedb-auth-token', 35 | '', 36 | { 37 | httpOnly: true, 38 | path: '/', 39 | secure: true, 40 | sameSite: true, 41 | expires: new Date(0), 42 | }, 43 | ) 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /src/runtime/api/auth/login.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, readBody, sendError, setCookie } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | import { useEdgeDbPKCE } from '../../server/composables/useEdgeDbPKCE' 4 | 5 | export default defineEventHandler(async (req) => { 6 | const pkce = useEdgeDbPKCE() 7 | const { urls } = useEdgeDbEnv() 8 | const { authBaseUrl } = urls 9 | 10 | const { email, password, provider } = await readBody(req) 11 | 12 | if (!email || !password || !provider) { 13 | const err = new H3Error(`Request body malformed. Expected JSON body with 'email', 'password', and 'provider' keys, but got: ${Object.entries({ email, password, provider }).filter(([, v]) => !!v)}`) 14 | err.statusCode = 400 15 | return sendError(req, err) 16 | } 17 | 18 | const authenticateUrl = new URL('authenticate', authBaseUrl) 19 | const authenticateResponse = await fetch(authenticateUrl.href, { 20 | method: 'post', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | body: JSON.stringify({ 25 | challenge: pkce.challenge, 26 | email, 27 | password, 28 | provider, 29 | }), 30 | }) 31 | 32 | if (!authenticateResponse.ok) { 33 | const err = new H3Error(await authenticateResponse.text()) 34 | err.statusCode = 400 35 | return sendError(req, err) 36 | } 37 | 38 | const authenticateResponseData = await authenticateResponse.json() 39 | 40 | const tokenUrl = new URL('token', authBaseUrl) 41 | tokenUrl.searchParams.set('code', authenticateResponseData.code) 42 | tokenUrl.searchParams.set('verifier', pkce.verifier) 43 | const tokenResponse = await fetch(tokenUrl.href, { 44 | method: 'get', 45 | }) 46 | 47 | if (!tokenResponse.ok) { 48 | const err = new H3Error(await tokenResponse.text()) 49 | err.statusCode = 400 50 | return sendError(req, err) 51 | } 52 | 53 | const tokenResponseData = await tokenResponse.json() 54 | 55 | setCookie(req, 'edgedb-auth-token', tokenResponseData.auth_token, { 56 | httpOnly: true, 57 | path: '/', 58 | secure: true, 59 | sameSite: true, 60 | }) 61 | 62 | return tokenResponseData 63 | }) 64 | -------------------------------------------------------------------------------- /src/runtime/api/auth/logout.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, getCookie, sendError, setCookie } from 'h3' 2 | 3 | export default defineEventHandler(async (req) => { 4 | const authToken = getCookie(req, 'edgedb-auth-token') 5 | 6 | if (!authToken) { 7 | const err = new H3Error('Not logged in') 8 | err.statusCode = 401 9 | return sendError(req, err) 10 | } 11 | 12 | setCookie( 13 | req, 14 | 'edgedb-auth-token', 15 | '', 16 | { 17 | httpOnly: true, 18 | path: '/', 19 | secure: true, 20 | sameSite: true, 21 | expires: new Date(0), 22 | }, 23 | ) 24 | }) 25 | -------------------------------------------------------------------------------- /src/runtime/api/auth/providers.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler } from 'h3' 2 | import { useEdgeDb } from '../../server/composables/useEdgeDb' 3 | 4 | export default defineEventHandler(async () => { 5 | const client = useEdgeDb() 6 | 7 | const result = await client.query(` 8 | select cfg::Config.extensions[is ext::auth::AuthConfig].providers { 9 | name, 10 | [is ext::auth::OAuthProviderConfig].display_name, 11 | }; 12 | `) 13 | 14 | return result 15 | }) 16 | -------------------------------------------------------------------------------- /src/runtime/api/auth/reset-password-ui.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler, getQuery } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | 4 | /** 5 | * Render a simple reset password UI 6 | * 7 | * @param {Request} req 8 | */ 9 | export default defineEventHandler((req) => { 10 | const { urls } = useEdgeDbEnv() 11 | const { authBaseUrl } = urls 12 | const { reset_token } = getQuery(req) 13 | 14 | return { 15 | headers: { 'Content-Type': 'text/html' }, 16 | body: ` 17 | 18 | 19 |
20 | 21 | 25 | 26 |
27 | 28 | 29 | `, 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /src/runtime/api/auth/reset-password.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, getCookie, readBody, sendError, setHeaders } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | 4 | /** 5 | * Send new password with reset token to EdgeDB Auth. 6 | * 7 | * @param {Request} req 8 | */ 9 | export default defineEventHandler(async (req) => { 10 | const { urls } = useEdgeDbEnv() 11 | const { authBaseUrl } = urls 12 | const { reset_token, password } = await readBody(req) 13 | 14 | if (!reset_token || !password) { 15 | const err = new H3Error(`Request body malformed. Expected JSON body with 'reset_token' and 'password' keys.`) 16 | err.statusCode = 400 17 | return sendError(req, err) 18 | } 19 | 20 | const provider = 'builtin::local_emailpassword' 21 | const verifier = getCookie(req, 'edgedb-pkce-verifier') 22 | if (!verifier) { 23 | const err = new H3Error(`Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?`) 24 | err.statusCode = 400 25 | return sendError(req, err) 26 | } 27 | 28 | const resetUrl = new URL('reset-password', authBaseUrl) 29 | const resetResponse = await fetch(resetUrl.href, { 30 | method: 'post', 31 | headers: { 32 | 'Content-Type': 'application/json', 33 | }, 34 | body: JSON.stringify({ 35 | reset_token, 36 | provider, 37 | password, 38 | }), 39 | }) 40 | 41 | if (!resetResponse.ok) { 42 | const err = new H3Error(await resetResponse.text()) 43 | err.statusCode = 400 44 | return sendError(req, err) 45 | } 46 | 47 | const { code } = await resetResponse.json() 48 | const tokenUrl = new URL('token', authBaseUrl) 49 | tokenUrl.searchParams.set('code', code) 50 | tokenUrl.searchParams.set('verifier', verifier) 51 | const tokenResponse = await fetch(tokenUrl.href, { 52 | method: 'get', 53 | }) 54 | 55 | if (!tokenResponse.ok) { 56 | const err = new H3Error(await tokenResponse.text()) 57 | err.statusCode = 400 58 | return sendError(req, err) 59 | } 60 | 61 | const tokenResponseData = await tokenResponse.json() 62 | setHeaders(req, { 63 | 'Set-Cookie': `edgedb-auth-token=${tokenResponseData.auth_token}; HttpOnly; Path=/; Secure; SameSite=Strict`, 64 | }) 65 | 66 | return tokenResponseData 67 | }) 68 | -------------------------------------------------------------------------------- /src/runtime/api/auth/send-password-reset-email.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, readBody, sendError, setHeaders } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | import { useEdgeDbPKCE } from '../../server/composables/useEdgeDbPKCE' 4 | 5 | /** 6 | * Request a password reset for an email. 7 | * 8 | * @param {Request} req 9 | */ 10 | export default defineEventHandler(async (req) => { 11 | const pkce = useEdgeDbPKCE() 12 | const { urls } = useEdgeDbEnv() 13 | const { authBaseUrl, resetPasswordUrl: reset_url } = urls 14 | 15 | const { email } = await readBody(req) 16 | const provider = 'builtin::local_emailpassword' 17 | 18 | if (!email) { 19 | const err = new H3Error(`Request body is missing 'email'`) 20 | err.statusCode = 400 21 | return sendError(req, err) 22 | } 23 | 24 | const sendResetUrl = new URL('send-reset-email', authBaseUrl) 25 | const sendResetResponse = await fetch(sendResetUrl.href, { 26 | method: 'post', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | }, 30 | body: JSON.stringify({ 31 | email, 32 | provider, 33 | reset_url, 34 | challenge: pkce.challenge, 35 | }), 36 | }) 37 | 38 | if (!sendResetResponse.ok) { 39 | const err = new H3Error(await sendResetResponse.text()) 40 | err.statusCode = 400 41 | return sendError(req, err) 42 | } 43 | 44 | const { email_sent } = await sendResetResponse.json() 45 | 46 | setHeaders( 47 | req, 48 | { 49 | 'Set-Cookie': `edgedb-pkce-verifier=${pkce.verifier}; HttpOnly; Path=/; Secure; SameSite=Strict`, 50 | }, 51 | ) 52 | 53 | return { 54 | message: `Reset email sent to '${email_sent}'.`, 55 | } 56 | }) 57 | -------------------------------------------------------------------------------- /src/runtime/api/auth/signup.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, readBody, sendError, setHeaders } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | import { useEdgeDbPKCE } from '../../server/composables/useEdgeDbPKCE' 4 | 5 | /** 6 | * Handles sign up with email and password. 7 | * 8 | * @param {Request} req 9 | * @param {Response} res 10 | */ 11 | export default defineEventHandler(async (req) => { 12 | const pkce = useEdgeDbPKCE() 13 | const { urls } = useEdgeDbEnv() 14 | const { authBaseUrl, verifyRedirectUrl } = urls 15 | 16 | const { email, password, provider } = await readBody(req) 17 | 18 | if (!email || !password || !provider) { 19 | const err = new H3Error(`Request body malformed. Expected JSON body with 'email', 'password', and 'provider' keys, but got: ${Object.entries({ email, password, provider }).filter(([, v]) => !!v)}`) 20 | err.statusCode = 400 21 | return sendError(req, err) 22 | } 23 | 24 | const registerUrl = new URL('register', authBaseUrl) 25 | const registerResponse = await fetch(registerUrl.href, { 26 | method: 'post', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | }, 30 | body: JSON.stringify({ 31 | challenge: pkce.challenge, 32 | email, 33 | provider, 34 | password, 35 | verify_url: verifyRedirectUrl, 36 | }), 37 | }) 38 | 39 | if (!registerResponse.ok) { 40 | const err = new H3Error(`Error from auth server: ${await registerResponse.text()}`) 41 | err.statusCode = 400 42 | return sendError(req, err) 43 | } 44 | 45 | const registerResponseData = await registerResponse.json() 46 | 47 | setHeaders(req, { 48 | 'Set-Cookie': `edgedb-pkce-verifier=${pkce.verifier}; HttpOnly; Path=/; Secure; SameSite=Strict`, 49 | }) 50 | 51 | return registerResponseData 52 | }) 53 | -------------------------------------------------------------------------------- /src/runtime/api/auth/verify.ts: -------------------------------------------------------------------------------- 1 | import { H3Error, defineEventHandler, getCookie, getRequestURL, sendError, setHeaders } from 'h3' 2 | import { useEdgeDbEnv } from '../../server/composables/useEdgeDbEnv' 3 | 4 | /** 5 | * Handles the link in the email verification flow. 6 | * 7 | * @param {Request} req 8 | */ 9 | export default defineEventHandler(async (req) => { 10 | const { urls } = useEdgeDbEnv() 11 | const { authBaseUrl } = urls 12 | 13 | const requestUrl = getRequestURL(req) 14 | const verification_token = requestUrl.searchParams.get('verification_token') 15 | if (!verification_token) { 16 | const err = new H3Error(`Verify request is missing 'verification_token' search param. The verification email is malformed.`) 17 | err.statusCode = 400 18 | return sendError(req, err) 19 | } 20 | 21 | const verifier = getCookie(req, 'edgedb-pkce-verifier') 22 | if (!verifier) { 23 | const err = new H3Error(`Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?`) 24 | err.statusCode = 400 25 | return sendError(req, err) 26 | } 27 | 28 | const verifyUrl = new URL('verify', authBaseUrl) 29 | const verifyResponse = await fetch(verifyUrl.href, { 30 | method: 'post', 31 | headers: { 32 | 'Content-Type': 'application/json', 33 | }, 34 | body: JSON.stringify({ 35 | verification_token, 36 | verifier, 37 | provider: 'builtin::local_emailpassword', 38 | }), 39 | }) 40 | 41 | if (!verifyResponse.ok) { 42 | const err = new H3Error(await verifyResponse.text()) 43 | err.statusCode = 400 44 | return sendError(req, err) 45 | } 46 | 47 | const { code } = await verifyResponse.json() 48 | 49 | const tokenUrl = new URL('token', authBaseUrl) 50 | tokenUrl.searchParams.set('code', code) 51 | tokenUrl.searchParams.set('verifier', verifier) 52 | const tokenResponse = await fetch(tokenUrl.href, { 53 | method: 'get', 54 | }) 55 | 56 | if (!tokenResponse.ok) { 57 | const err = new H3Error(await tokenResponse.text()) 58 | err.statusCode = 400 59 | return sendError(req, err) 60 | } 61 | 62 | const tokenResponseData = await tokenResponse.json() 63 | 64 | setHeaders(req, { 65 | 'Set-Cookie': `edgedb-auth-token=${tokenResponseData.auth_token}; HttpOnly; Path=/; Secure; SameSite=Strict`, 66 | }) 67 | 68 | return tokenResponseData 69 | }) 70 | -------------------------------------------------------------------------------- /src/runtime/components/auth/base/EdgeDbAuthEmailLogin.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 71 | -------------------------------------------------------------------------------- /src/runtime/components/auth/base/EdgeDbAuthEmailSignup.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 67 | -------------------------------------------------------------------------------- /src/runtime/components/auth/base/EdgeDbAuthEmailVerify.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /src/runtime/components/auth/base/EdgeDbAuthLogout.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /src/runtime/components/auth/base/EdgeDbAuthProviders.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /src/runtime/components/auth/base/EdgeDbAuthResetPassword.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /src/runtime/components/auth/base/EdgeDbAuthSendPasswordReset.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 65 | -------------------------------------------------------------------------------- /src/runtime/components/auth/oauth/EdgeDbOAuthButton.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/runtime/components/auth/oauth/EdgeDbOAuthCallback.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /src/runtime/composables/useEdgeDbIdentity.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue' 2 | import type { H3Event } from 'h3' 3 | import type { User } from '#edgedb/interfaces' 4 | import { useNuxtApp } from '#imports' 5 | 6 | interface UseEdgeDbIdentityData { 7 | identity: Ref 8 | cookie: Ref 9 | update: (event?: H3Event) => Promise 10 | logout: (redirectTo?: string) => Promise 11 | isLoggedIn: ComputedRef 12 | } 13 | 14 | export function useEdgeDbIdentity(): UseEdgeDbIdentityData { 15 | const { 16 | $edgeDbIdentity: identity, 17 | $edgeDbCookie: cookie, 18 | $edgeDbUpdateIdentity: update, 19 | $edgeDbLogout: logout, 20 | $edgeDbIsLoggedIn: isLoggedIn, 21 | } = useNuxtApp() 22 | 23 | const identityData = { 24 | isLoggedIn, 25 | identity, 26 | cookie, 27 | update, 28 | logout, 29 | } as UseEdgeDbIdentityData 30 | 31 | return identityData 32 | } 33 | -------------------------------------------------------------------------------- /src/runtime/plugins/edgedb-auth.ts: -------------------------------------------------------------------------------- 1 | import type { H3Event } from 'h3' 2 | import { fetchWithEvent, getRequestURL } from 'h3' 3 | import { defineNuxtPlugin, navigateTo, useCookie, useState } from 'nuxt/app' 4 | import { computed } from 'vue' 5 | 6 | export default defineNuxtPlugin(async (nuxtApp) => { 7 | const identity = useState('edgedb-auth-identity', () => undefined) 8 | 9 | const cookie = useCookie('edgedb-auth-token') 10 | 11 | const isLoggedIn = computed(() => !!((identity as Ref)?.value)) 12 | 13 | async function updateIdentity(event?: H3Event) { 14 | try { 15 | if (!import.meta.server) { 16 | identity.value = await $fetch('/api/auth/identity') 17 | return 18 | } 19 | 20 | const req = getRequestURL(event) 21 | const url = `${req.protocol}//${req.host}/api/auth/identity` 22 | 23 | const idRequest = await fetchWithEvent(event, url).then(r => r.json()) 24 | 25 | if (identity) { 26 | identity.value = idRequest 27 | } 28 | else { 29 | identity.value = undefined 30 | await logout() 31 | } 32 | } 33 | catch (_) { 34 | // 35 | } 36 | } 37 | 38 | async function logout(redirectTo: string) { 39 | await $fetch('/api/auth/logout') 40 | identity.value = undefined 41 | cookie.value = '' 42 | if (redirectTo) 43 | await navigateTo(redirectTo) 44 | } 45 | 46 | if (import.meta.server) { 47 | const event = nuxtApp?.ssrContext?.event 48 | 49 | if (event) 50 | await updateIdentity(event) 51 | } 52 | 53 | return { 54 | provide: { 55 | edgeDbIsLoggedIn: isLoggedIn, 56 | edgeDbCookie: cookie, 57 | edgeDbIdentity: identity, 58 | edgeDbUpdateIdentity: updateIdentity, 59 | edgeDbLogout: logout, 60 | }, 61 | } 62 | }) 63 | -------------------------------------------------------------------------------- /src/runtime/server/composables/useEdgeDb.ts: -------------------------------------------------------------------------------- 1 | import { getCookie } from 'h3' 2 | import type { EventHandlerRequest, H3Event } from 'h3' 3 | import type { Client } from 'edgedb' 4 | 5 | export function useEdgeDb(req: H3Event | undefined = undefined) { 6 | // @ts-expect-error - untyped global 7 | const client = globalThis.__nuxt_edgedb_client__ as Client 8 | 9 | if (req) { 10 | return client.withGlobals({ 11 | 'ext::auth::client_token': req ? getCookie(req, 'edgedb-auth-token') : undefined, 12 | }) 13 | } 14 | 15 | return client 16 | } 17 | -------------------------------------------------------------------------------- /src/runtime/server/composables/useEdgeDbEnv.ts: -------------------------------------------------------------------------------- 1 | import { useRuntimeConfig } from '#imports' 2 | 3 | export function useEdgeDbEnv() { 4 | const { edgeDb } = useRuntimeConfig() 5 | 6 | return edgeDb 7 | } 8 | -------------------------------------------------------------------------------- /src/runtime/server/composables/useEdgeDbIdentity.ts: -------------------------------------------------------------------------------- 1 | import type { EventHandlerRequest, H3Event } from 'h3' 2 | import { getCookie, sendRedirect, setCookie } from 'h3' 3 | import { useEdgeDb } from './useEdgeDb' 4 | 5 | interface UseEdgeDbIdentityData { 6 | identity: T 7 | cookie: string 8 | update: (event?: H3Event) => Promise 9 | logout: (redirectTo?: string) => Promise 10 | isLoggedIn: boolean 11 | } 12 | 13 | export async function useEdgeDbIdentity( 14 | req: H3Event | undefined = undefined, 15 | ): Promise> { 16 | const client = useEdgeDb(req) 17 | 18 | let token: string | undefined 19 | 20 | let user: T | undefined 21 | 22 | const update = async () => { 23 | if (req) 24 | token = getCookie(req, 'edgedb-auth-token') 25 | 26 | user = client.querySingle(`select global current_user;`) as T 27 | } 28 | 29 | const logout = async (redirectTo: string | undefined) => { 30 | if (!req) 31 | return 32 | 33 | setCookie(req, 'edgedb-auth-token', '') 34 | 35 | if (redirectTo) 36 | return sendRedirect(req, '/') 37 | } 38 | 39 | await update() 40 | 41 | const identityData = { 42 | isLoggedIn: !!user, 43 | identity: user, 44 | cookie: token, 45 | update, 46 | logout, 47 | } as UseEdgeDbIdentityData 48 | 49 | return identityData 50 | } 51 | -------------------------------------------------------------------------------- /src/runtime/server/composables/useEdgeDbPKCE.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'node:crypto' 2 | 3 | /** 4 | * Generate a random Base64 url-encoded string, and derive a "challenge" 5 | * string from that string to use as proof that the request for a token 6 | * later is made from the same user agent that made the original request 7 | * 8 | * @returns {object} The verifier and challenge strings 9 | */ 10 | export function useEdgeDbPKCE() { 11 | const verifier = crypto.randomBytes(32).toString('base64url') 12 | 13 | const challenge = crypto 14 | .createHash('sha256') 15 | .update(verifier) 16 | .digest('base64url') 17 | 18 | return { verifier, challenge } 19 | } 20 | -------------------------------------------------------------------------------- /src/runtime/server/composables/useEdgeDbQueries.ts: -------------------------------------------------------------------------------- 1 | import type { EventHandlerRequest, H3Event } from 'h3' 2 | import { useEdgeDb } from './useEdgeDb' 3 | import * as queries from '#edgedb/queries' 4 | 5 | export type EdgeDbQueries = keyof typeof queries 6 | 7 | export function useEdgeDbQueries( 8 | req: H3Event | undefined = undefined, 9 | ): { [K in EdgeDbQueries]: (arg?: Parameters[1]) => ReturnType } { 10 | const client = useEdgeDb(req) 11 | 12 | return Object.fromEntries( 13 | Object.entries(queries).map(([key, fn]) => { 14 | return [ 15 | key, 16 | (args?: Parameters[1]) => fn(client, args), 17 | ] 18 | }), 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/runtime/server/composables/useEdgeDbQueryBuilder.ts: -------------------------------------------------------------------------------- 1 | import e from '#edgedb/builder' 2 | 3 | export type EdgeDbQueryBuilder = typeof e 4 | 5 | export function useEdgeDbQueryBuilder(): EdgeDbQueryBuilder { 6 | return e 7 | } 8 | -------------------------------------------------------------------------------- /src/runtime/server/plugins/edgedb-client.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'edgedb' 2 | import { defineNitroPlugin } from 'nitropack/dist/runtime/plugin' 3 | import { useEdgeDbEnv } from '../composables/useEdgeDbEnv' 4 | 5 | export default defineNitroPlugin(() => { 6 | const { dsn } = useEdgeDbEnv() 7 | 8 | const client = createClient({ 9 | dsn: dsn.full, 10 | tlsSecurity: dsn.tlsSecurity, 11 | tlsCA: dsn.tlsCA, 12 | }) 13 | 14 | globalThis.__nuxt_edgedb_client__ = client 15 | }) 16 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa' 2 | import type { ModuleOptions } from './module' 3 | 4 | export async function getEdgeDbCredentials( 5 | cwd: string, 6 | processInject: boolean = true, 7 | ) { 8 | let dbCredentials: any | undefined 9 | 10 | try { 11 | dbCredentials = await execa({ cwd })`edgedb instance credentials --json` 12 | } 13 | catch (e) { 14 | // Silently fail, the EdgeDB instance credentials command failed. 15 | } 16 | 17 | if (dbCredentials) { 18 | const { host, port, database, user, password, tls_ca, tls_security } = JSON.parse(dbCredentials.stdout) 19 | 20 | if (processInject) { 21 | if (!process.env.NUXT_EDGEDB_HOST) 22 | process.env.NUXT_EDGEDB_HOST = host 23 | if (!process.env.NUXT_EDGEDB_PORT) 24 | process.env.NUXT_EDGEDB_PORT = port 25 | if (!process.env.NUXT_EDGEDB_DATABASE) 26 | process.env.NUXT_EDGEDB_DATABASE = database 27 | if (!process.env.NUXT_EDGEDB_USER) 28 | process.env.NUXT_EDGEDB_USER = user 29 | if (!process.env.NUXT_EDGEDB_PASS) 30 | process.env.NUXT_EDGEDB_PASS = password 31 | if (!process.env.NUXT_EDGEDB_TLS_CA) 32 | process.env.NUXT_EDGEDB_TLS_CA = tls_ca 33 | if (!process.env.NUXT_EDGEDB_TLS_SECURITY) 34 | process.env.NUXT_EDGEDB_TLS_SECURITY = tls_security 35 | if (!process.env.NUXT_EDGEDB_AUTH_BASE_URL) 36 | process.env.NUXT_EDGEDB_AUTH_BASE_URL = `http://${host}:${port}/db/${database}/ext/auth/` 37 | } 38 | 39 | return { host, port, database, user, password, tls_ca, tls_security } 40 | } 41 | } 42 | 43 | export async function getEdgeDbConfiguration( 44 | appUrl: string, 45 | options: Partial = {}, 46 | cwd: string = process.cwd(), 47 | processInject: boolean = true, 48 | ) { 49 | await getEdgeDbCredentials(cwd, processInject) 50 | 51 | const { 52 | // EdgeDB DSN settings 53 | NUXT_EDGEDB_HOST: host, 54 | NUXT_EDGEDB_PORT: port, 55 | NUXT_EDGEDB_USER: user, 56 | NUXT_EDGEDB_PASS: pass, 57 | NUXT_EDGEDB_DATABASE: database, 58 | NUXT_EDGEDB_TLS_CA: tlsCA, 59 | NUXT_EDGEDB_TLS_SECURITY: tlsSecurity, 60 | 61 | // EdgeDB Auth settings 62 | NUXT_EDGEDB_IDENTITY_MODEL: identityModel = options?.identityModel || 'User', 63 | 64 | // EdgeDB Auth URls 65 | NUXT_EDGEDB_AUTH_BASE_URL: authBaseUrl = `http://${host}:${port}/db/${database}/ext/auth/`, 66 | NUXT_EDGEDB_OAUTH_CALLBACK: oAuthCallbackUrl = `http://${host}:${port}/db/${database}/ext/auth/callback`, 67 | 68 | // EdgeDB Nuxt Auth URLs 69 | NUXT_EDGEDB_AUTH_VERIFY_REDIRECT_URL: verifyRedirectUrl = `${appUrl}/auth/verify`, 70 | NUXT_EDGEDB_AUTH_RESET_PASSWORD_URL: resetPasswordUrl = `${appUrl}/auth/reset-password`, 71 | NUXT_EDGEDB_OAUTH_REDIRECT_URL: oAuthRedirectUrl = `${appUrl}/auth/callback`, 72 | } = process.env 73 | 74 | const dsn = { 75 | host, 76 | port, 77 | user, 78 | pass, 79 | database, 80 | tlsCA, 81 | tlsSecurity: tlsSecurity as 'insecure' | 'no_host_verification' | 'strict' | 'default' | undefined, 82 | full: `edgedb://${user}:${pass}@${host}:${port}/${database}`, 83 | } 84 | 85 | const urls = { 86 | // EdgeDB Nuxt Auth URLs 87 | appUrl, 88 | resetPasswordUrl, 89 | verifyRedirectUrl, 90 | oAuthRedirectUrl, 91 | 92 | // EdgeDB Auth URls 93 | authBaseUrl, 94 | oAuthCallbackUrl, 95 | } 96 | 97 | const auth = { 98 | enabled: options?.auth || false, 99 | oauth: options?.oauth || false, 100 | identityModel, 101 | } 102 | 103 | return { 104 | auth, 105 | dsn, 106 | urls, 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /templates/default-auth.esdl: -------------------------------------------------------------------------------- 1 | using extension auth; 2 | 3 | module default { 4 | global current_user := ( 5 | assert_single(( 6 | select User { id, name } 7 | filter .identity = global ext::auth::ClientTokenIdentity 8 | )) 9 | ); 10 | 11 | type User { 12 | required name: str; 13 | required identity: ext::auth::Identity; 14 | multi link posts -> BlogPost { 15 | on source delete delete target; 16 | } 17 | } 18 | 19 | type BlogPost { 20 | property content: str { 21 | default := 'My super blog post.'; 22 | }; 23 | property description: str { 24 | default := 'My blog post description.'; 25 | }; 26 | property title: str { 27 | default := 'My blog super blog post title.'; 28 | }; 29 | required author: User { 30 | default := global current_user; 31 | }; 32 | access policy author_has_full_access 33 | allow all 34 | using (.author ?= global current_user); 35 | access policy others_read_only 36 | allow select; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /templates/default.esdl: -------------------------------------------------------------------------------- 1 | module default { 2 | type BlogPost { 3 | property content: str { 4 | default := 'My super blog post.'; 5 | }; 6 | property description: str { 7 | default := 'My blog post description.'; 8 | }; 9 | property title: str { 10 | default := 'My blog super blog post title.'; 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { describe, expect, it } from 'vitest' 3 | import { $fetch, setup } from '@nuxt/test-utils' 4 | 5 | describe('ssr', async () => { 6 | await setup({ 7 | rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)), 8 | }) 9 | 10 | it('renders the index page', async () => { 11 | // Get response to a server-rendered page with `$fetch`. 12 | const html = await $fetch('/') 13 | expect(html).toContain('
basic
') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/fixtures/basic/app.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /test/fixtures/basic/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import MyModule from '../../../src/module' 2 | 3 | export default defineNuxtConfig({ 4 | modules: [ 5 | MyModule, 6 | ], 7 | }) 8 | -------------------------------------------------------------------------------- /test/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "type": "module", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "baseUrl": ".", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "paths": { 8 | "#edgedb/queries": ["./src/runtime/server/composables/useEdgeDbQueries.ts"], 9 | "#edgedb/interfaces": ["./src/runtime/server/composables/useEdgeDbQueries.ts"], 10 | "#edgedb/builder": ["./src/runtime/server/composables/useEdgeDbQueryBuilder.ts"] 11 | }, 12 | "strict": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "sourceMap": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true 18 | } 19 | } 20 | --------------------------------------------------------------------------------