@buenojs/bueno 0.8.5 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +9 -0
- package/README.md +259 -15
- package/dist/cache/index.d.ts +187 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cli/bin.d.ts +8 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +484 -156
- package/dist/cli/commands/add-frontend.d.ts +7 -0
- package/dist/cli/commands/add-frontend.d.ts.map +1 -0
- package/dist/cli/commands/build.d.ts +7 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +7 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +7 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/help.d.ts +7 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/index.d.ts +59 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/migration.d.ts +7 -0
- package/dist/cli/commands/migration.d.ts.map +1 -0
- package/dist/cli/commands/new.d.ts +7 -0
- package/dist/cli/commands/new.d.ts.map +1 -0
- package/dist/cli/commands/start.d.ts +7 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/core/args.d.ts +61 -0
- package/dist/cli/core/args.d.ts.map +1 -0
- package/dist/cli/core/console.d.ts +135 -0
- package/dist/cli/core/console.d.ts.map +1 -0
- package/dist/cli/core/index.d.ts +10 -0
- package/dist/cli/core/index.d.ts.map +1 -0
- package/dist/cli/core/prompt.d.ts +63 -0
- package/dist/cli/core/prompt.d.ts.map +1 -0
- package/dist/cli/core/spinner.d.ts +111 -0
- package/dist/cli/core/spinner.d.ts.map +1 -0
- package/dist/cli/index.d.ts +47 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/templates/database/index.d.ts +24 -0
- package/dist/cli/templates/database/index.d.ts.map +1 -0
- package/dist/cli/templates/database/mysql.d.ts +6 -0
- package/dist/cli/templates/database/mysql.d.ts.map +1 -0
- package/dist/cli/templates/database/none.d.ts +8 -0
- package/dist/cli/templates/database/none.d.ts.map +1 -0
- package/dist/cli/templates/database/postgresql.d.ts +6 -0
- package/dist/cli/templates/database/postgresql.d.ts.map +1 -0
- package/dist/cli/templates/database/sqlite.d.ts +6 -0
- package/dist/cli/templates/database/sqlite.d.ts.map +1 -0
- package/dist/cli/templates/deploy.d.ts +41 -0
- package/dist/cli/templates/deploy.d.ts.map +1 -0
- package/dist/cli/templates/docker.d.ts +30 -0
- package/dist/cli/templates/docker.d.ts.map +1 -0
- package/dist/cli/templates/frontend/index.d.ts +25 -0
- package/dist/cli/templates/frontend/index.d.ts.map +1 -0
- package/dist/cli/templates/frontend/none.d.ts +8 -0
- package/dist/cli/templates/frontend/none.d.ts.map +1 -0
- package/dist/cli/templates/frontend/react.d.ts +6 -0
- package/dist/cli/templates/frontend/react.d.ts.map +1 -0
- package/dist/cli/templates/frontend/solid.d.ts +6 -0
- package/dist/cli/templates/frontend/solid.d.ts.map +1 -0
- package/dist/cli/templates/frontend/svelte.d.ts +6 -0
- package/dist/cli/templates/frontend/svelte.d.ts.map +1 -0
- package/dist/cli/templates/frontend/vue.d.ts +6 -0
- package/dist/cli/templates/frontend/vue.d.ts.map +1 -0
- package/dist/cli/templates/generators/index.d.ts +29 -0
- package/dist/cli/templates/generators/index.d.ts.map +1 -0
- package/dist/cli/templates/generators/types.d.ts +32 -0
- package/dist/cli/templates/generators/types.d.ts.map +1 -0
- package/dist/cli/templates/index.d.ts +12 -0
- package/dist/cli/templates/index.d.ts.map +1 -0
- package/dist/cli/templates/project/api.d.ts +6 -0
- package/dist/cli/templates/project/api.d.ts.map +1 -0
- package/dist/cli/templates/project/default.d.ts +6 -0
- package/dist/cli/templates/project/default.d.ts.map +1 -0
- package/dist/cli/templates/project/fullstack.d.ts +14 -0
- package/dist/cli/templates/project/fullstack.d.ts.map +1 -0
- package/dist/cli/templates/project/index.d.ts +26 -0
- package/dist/cli/templates/project/index.d.ts.map +1 -0
- package/dist/cli/templates/project/minimal.d.ts +6 -0
- package/dist/cli/templates/project/minimal.d.ts.map +1 -0
- package/dist/cli/templates/project/types.d.ts +80 -0
- package/dist/cli/templates/project/types.d.ts.map +1 -0
- package/dist/cli/templates/project/website.d.ts +8 -0
- package/dist/cli/templates/project/website.d.ts.map +1 -0
- package/dist/cli/utils/fs.d.ts +137 -0
- package/dist/cli/utils/fs.d.ts.map +1 -0
- package/dist/cli/utils/index.d.ts +9 -0
- package/dist/cli/utils/index.d.ts.map +1 -0
- package/dist/cli/utils/strings.d.ts +86 -0
- package/dist/cli/utils/strings.d.ts.map +1 -0
- package/dist/cli/utils/version.d.ts +15 -0
- package/dist/cli/utils/version.d.ts.map +1 -0
- package/dist/config/env-validation.d.ts +49 -0
- package/dist/config/env-validation.d.ts.map +1 -0
- package/dist/config/env.d.ts +167 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/index.d.ts +168 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/loader.d.ts +81 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/merge.d.ts +66 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/types.d.ts +322 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/validation.d.ts +100 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/container/forward-ref.d.ts +116 -0
- package/dist/container/forward-ref.d.ts.map +1 -0
- package/dist/container/index.d.ts +95 -0
- package/dist/container/index.d.ts.map +1 -0
- package/dist/container/index.js +26 -3
- package/dist/context/index.d.ts +143 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/database/index.d.ts +219 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/migrations/index.d.ts +146 -0
- package/dist/database/migrations/index.d.ts.map +1 -0
- package/dist/database/orm/builder.d.ts +122 -0
- package/dist/database/orm/builder.d.ts.map +1 -0
- package/dist/database/orm/casts/index.d.ts +16 -0
- package/dist/database/orm/casts/index.d.ts.map +1 -0
- package/dist/database/orm/casts/types.d.ts +16 -0
- package/dist/database/orm/casts/types.d.ts.map +1 -0
- package/dist/database/orm/compiler.d.ts +90 -0
- package/dist/database/orm/compiler.d.ts.map +1 -0
- package/dist/database/orm/hooks/index.d.ts +53 -0
- package/dist/database/orm/hooks/index.d.ts.map +1 -0
- package/dist/database/orm/index.d.ts +21 -0
- package/dist/database/orm/index.d.ts.map +1 -0
- package/dist/database/orm/model-registry.d.ts +33 -0
- package/dist/database/orm/model-registry.d.ts.map +1 -0
- package/dist/database/orm/model.d.ts +245 -0
- package/dist/database/orm/model.d.ts.map +1 -0
- package/dist/database/orm/relationships/base.d.ts +69 -0
- package/dist/database/orm/relationships/base.d.ts.map +1 -0
- package/dist/database/orm/relationships/belongs-to-many.d.ts +47 -0
- package/dist/database/orm/relationships/belongs-to-many.d.ts.map +1 -0
- package/dist/database/orm/relationships/belongs-to.d.ts +17 -0
- package/dist/database/orm/relationships/belongs-to.d.ts.map +1 -0
- package/dist/database/orm/relationships/has-many.d.ts +14 -0
- package/dist/database/orm/relationships/has-many.d.ts.map +1 -0
- package/dist/database/orm/relationships/has-one.d.ts +14 -0
- package/dist/database/orm/relationships/has-one.d.ts.map +1 -0
- package/dist/database/orm/relationships/index.d.ts +10 -0
- package/dist/database/orm/relationships/index.d.ts.map +1 -0
- package/dist/database/orm/scopes/index.d.ts +36 -0
- package/dist/database/orm/scopes/index.d.ts.map +1 -0
- package/dist/database/schema/index.d.ts +155 -0
- package/dist/database/schema/index.d.ts.map +1 -0
- package/dist/events/__tests__/event-system.test.d.ts +2 -0
- package/dist/events/__tests__/event-system.test.d.ts.map +1 -0
- package/dist/events/config.d.ts +16 -0
- package/dist/events/config.d.ts.map +1 -0
- package/dist/events/example-usage.d.ts +12 -0
- package/dist/events/example-usage.d.ts.map +1 -0
- package/dist/events/index.d.ts +27 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/manager.d.ts +33 -0
- package/dist/events/manager.d.ts.map +1 -0
- package/dist/events/registry.d.ts +31 -0
- package/dist/events/registry.d.ts.map +1 -0
- package/dist/events/types.d.ts +105 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/frontend/api-routes.d.ts +189 -0
- package/dist/frontend/api-routes.d.ts.map +1 -0
- package/dist/frontend/bundler.d.ts +99 -0
- package/dist/frontend/bundler.d.ts.map +1 -0
- package/dist/frontend/console-client.d.ts +11 -0
- package/dist/frontend/console-client.d.ts.map +1 -0
- package/dist/frontend/console-stream.d.ts +138 -0
- package/dist/frontend/console-stream.d.ts.map +1 -0
- package/dist/frontend/dev-server.d.ts +174 -0
- package/dist/frontend/dev-server.d.ts.map +1 -0
- package/dist/frontend/file-router.d.ts +170 -0
- package/dist/frontend/file-router.d.ts.map +1 -0
- package/dist/frontend/frameworks/index.d.ts +41 -0
- package/dist/frontend/frameworks/index.d.ts.map +1 -0
- package/dist/frontend/frameworks/react.d.ts +32 -0
- package/dist/frontend/frameworks/react.d.ts.map +1 -0
- package/dist/frontend/frameworks/solid.d.ts +42 -0
- package/dist/frontend/frameworks/solid.d.ts.map +1 -0
- package/dist/frontend/frameworks/svelte.d.ts +57 -0
- package/dist/frontend/frameworks/svelte.d.ts.map +1 -0
- package/dist/frontend/frameworks/vue.d.ts +36 -0
- package/dist/frontend/frameworks/vue.d.ts.map +1 -0
- package/dist/frontend/hmr-client.d.ts +22 -0
- package/dist/frontend/hmr-client.d.ts.map +1 -0
- package/dist/frontend/hmr.d.ts +185 -0
- package/dist/frontend/hmr.d.ts.map +1 -0
- package/dist/frontend/index.d.ts +34 -0
- package/dist/frontend/index.d.ts.map +1 -0
- package/dist/frontend/islands.d.ts +135 -0
- package/dist/frontend/islands.d.ts.map +1 -0
- package/dist/frontend/isr.d.ts +143 -0
- package/dist/frontend/isr.d.ts.map +1 -0
- package/dist/frontend/layout.d.ts +140 -0
- package/dist/frontend/layout.d.ts.map +1 -0
- package/dist/frontend/ssr/react.d.ts +118 -0
- package/dist/frontend/ssr/react.d.ts.map +1 -0
- package/dist/frontend/ssr/solid.d.ts +141 -0
- package/dist/frontend/ssr/solid.d.ts.map +1 -0
- package/dist/frontend/ssr/svelte.d.ts +158 -0
- package/dist/frontend/ssr/svelte.d.ts.map +1 -0
- package/dist/frontend/ssr/vue.d.ts +161 -0
- package/dist/frontend/ssr/vue.d.ts.map +1 -0
- package/dist/frontend/ssr.d.ts +147 -0
- package/dist/frontend/ssr.d.ts.map +1 -0
- package/dist/frontend/types.d.ts +1902 -0
- package/dist/frontend/types.d.ts.map +1 -0
- package/dist/graphql/built-in-engine.d.ts +36 -0
- package/dist/graphql/built-in-engine.d.ts.map +1 -0
- package/dist/graphql/context-builder.d.ts +44 -0
- package/dist/graphql/context-builder.d.ts.map +1 -0
- package/dist/graphql/decorators.d.ts +162 -0
- package/dist/graphql/decorators.d.ts.map +1 -0
- package/dist/graphql/execution-pipeline.d.ts +67 -0
- package/dist/graphql/execution-pipeline.d.ts.map +1 -0
- package/dist/graphql/graphql-module.d.ts +70 -0
- package/dist/graphql/graphql-module.d.ts.map +1 -0
- package/dist/graphql/index.d.ts +48 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +2156 -0
- package/dist/graphql/metadata.d.ts +37 -0
- package/dist/graphql/metadata.d.ts.map +1 -0
- package/dist/graphql/schema-builder.d.ts +34 -0
- package/dist/graphql/schema-builder.d.ts.map +1 -0
- package/dist/graphql/subscription-handler.d.ts +47 -0
- package/dist/graphql/subscription-handler.d.ts.map +1 -0
- package/dist/graphql/types.d.ts +252 -0
- package/dist/graphql/types.d.ts.map +1 -0
- package/dist/health/index.d.ts +176 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/i18n/engine.d.ts +105 -0
- package/dist/i18n/engine.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +13 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/loader.d.ts +79 -0
- package/dist/i18n/loader.d.ts.map +1 -0
- package/dist/i18n/middleware.d.ts +96 -0
- package/dist/i18n/middleware.d.ts.map +1 -0
- package/dist/i18n/negotiator.d.ts +84 -0
- package/dist/i18n/negotiator.d.ts.map +1 -0
- package/dist/i18n/types.d.ts +129 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +520 -434
- package/dist/jobs/drivers/memory.d.ts +38 -0
- package/dist/jobs/drivers/memory.d.ts.map +1 -0
- package/dist/jobs/drivers/redis.d.ts +34 -0
- package/dist/jobs/drivers/redis.d.ts.map +1 -0
- package/dist/jobs/index.d.ts +12 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/queue.d.ts +93 -0
- package/dist/jobs/queue.d.ts.map +1 -0
- package/dist/jobs/types.d.ts +193 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/worker.d.ts +91 -0
- package/dist/jobs/worker.d.ts.map +1 -0
- package/dist/lock/index.d.ts +141 -0
- package/dist/lock/index.d.ts.map +1 -0
- package/dist/logger/index.d.ts +156 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/transports/index.d.ts +371 -0
- package/dist/logger/transports/index.d.ts.map +1 -0
- package/dist/metrics/index.d.ts +163 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/middleware/built-in.d.ts +50 -0
- package/dist/middleware/built-in.d.ts.map +1 -0
- package/dist/middleware/index.d.ts +40 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +10 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/modules/filters.d.ts +150 -0
- package/dist/modules/filters.d.ts.map +1 -0
- package/dist/modules/guards.d.ts +188 -0
- package/dist/modules/guards.d.ts.map +1 -0
- package/dist/modules/index.d.ts +266 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +514 -449
- package/dist/modules/interceptors.d.ts +242 -0
- package/dist/modules/interceptors.d.ts.map +1 -0
- package/dist/modules/lazy.d.ts +187 -0
- package/dist/modules/lazy.d.ts.map +1 -0
- package/dist/modules/lifecycle.d.ts +221 -0
- package/dist/modules/lifecycle.d.ts.map +1 -0
- package/dist/modules/metadata.d.ts +32 -0
- package/dist/modules/metadata.d.ts.map +1 -0
- package/dist/modules/pipes.d.ts +287 -0
- package/dist/modules/pipes.d.ts.map +1 -0
- package/dist/notification/channels/base.d.ts +32 -0
- package/dist/notification/channels/base.d.ts.map +1 -0
- package/dist/notification/channels/email.d.ts +37 -0
- package/dist/notification/channels/email.d.ts.map +1 -0
- package/dist/notification/channels/push.d.ts +37 -0
- package/dist/notification/channels/push.d.ts.map +1 -0
- package/dist/notification/channels/sms.d.ts +37 -0
- package/dist/notification/channels/sms.d.ts.map +1 -0
- package/dist/notification/channels/whatsapp.d.ts +37 -0
- package/dist/notification/channels/whatsapp.d.ts.map +1 -0
- package/dist/notification/index.d.ts +15 -0
- package/dist/notification/index.d.ts.map +1 -0
- package/dist/notification/service.d.ts +100 -0
- package/dist/notification/service.d.ts.map +1 -0
- package/dist/notification/types.d.ts +253 -0
- package/dist/notification/types.d.ts.map +1 -0
- package/dist/observability/__tests__/observability.test.d.ts +2 -0
- package/dist/observability/__tests__/observability.test.d.ts.map +1 -0
- package/dist/observability/breadcrumbs.d.ts +48 -0
- package/dist/observability/breadcrumbs.d.ts.map +1 -0
- package/dist/observability/index.d.ts +95 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/interceptor.d.ts +19 -0
- package/dist/observability/interceptor.d.ts.map +1 -0
- package/dist/observability/service.d.ts +101 -0
- package/dist/observability/service.d.ts.map +1 -0
- package/dist/observability/trace.d.ts +21 -0
- package/dist/observability/trace.d.ts.map +1 -0
- package/dist/observability/types.d.ts +172 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/openapi/__tests__/decorators.test.d.ts +2 -0
- package/dist/openapi/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/openapi/__tests__/document-builder.test.d.ts +2 -0
- package/dist/openapi/__tests__/document-builder.test.d.ts.map +1 -0
- package/dist/openapi/__tests__/route-scanner.test.d.ts +2 -0
- package/dist/openapi/__tests__/route-scanner.test.d.ts.map +1 -0
- package/dist/openapi/__tests__/schema-generator.test.d.ts +2 -0
- package/dist/openapi/__tests__/schema-generator.test.d.ts.map +1 -0
- package/dist/openapi/decorators.d.ts +173 -0
- package/dist/openapi/decorators.d.ts.map +1 -0
- package/dist/openapi/document-builder.d.ts +82 -0
- package/dist/openapi/document-builder.d.ts.map +1 -0
- package/dist/openapi/index.d.ts +48 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +59 -40
- package/dist/openapi/metadata.d.ts +36 -0
- package/dist/openapi/metadata.d.ts.map +1 -0
- package/dist/openapi/route-scanner.d.ts +34 -0
- package/dist/openapi/route-scanner.d.ts.map +1 -0
- package/dist/openapi/schema-generator.d.ts +53 -0
- package/dist/openapi/schema-generator.d.ts.map +1 -0
- package/dist/openapi/swagger-module.d.ts +57 -0
- package/dist/openapi/swagger-module.d.ts.map +1 -0
- package/dist/openapi/types.d.ts +344 -0
- package/dist/openapi/types.d.ts.map +1 -0
- package/dist/orm/index.d.ts +10 -0
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/router/index.d.ts +73 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/linear.d.ts +54 -0
- package/dist/router/linear.d.ts.map +1 -0
- package/dist/router/regex.d.ts +49 -0
- package/dist/router/regex.d.ts.map +1 -0
- package/dist/router/tree.d.ts +112 -0
- package/dist/router/tree.d.ts.map +1 -0
- package/dist/rpc/index.d.ts +321 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/schema/index.d.ts +10 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/security/index.d.ts +126 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/ssg/index.d.ts +73 -0
- package/dist/ssg/index.d.ts.map +1 -0
- package/dist/storage/index.d.ts +99 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +376 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/template/index.d.ts +7 -0
- package/dist/template/index.d.ts.map +1 -0
- package/dist/templates/engine.d.ts +60 -0
- package/dist/templates/engine.d.ts.map +1 -0
- package/dist/templates/index.d.ts +9 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/loader.d.ts +45 -0
- package/dist/templates/loader.d.ts.map +1 -0
- package/dist/templates/renderers/markdown.d.ts +46 -0
- package/dist/templates/renderers/markdown.d.ts.map +1 -0
- package/dist/templates/renderers/simple.d.ts +35 -0
- package/dist/templates/renderers/simple.d.ts.map +1 -0
- package/dist/templates/types.d.ts +138 -0
- package/dist/templates/types.d.ts.map +1 -0
- package/dist/testing/index.d.ts +539 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +116 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/validation/index.d.ts +89 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/schemas.d.ts +243 -0
- package/dist/validation/schemas.d.ts.map +1 -0
- package/dist/websocket/index.d.ts +252 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/llms.txt +231 -0
- package/package.json +6 -2
- package/src/cli/ARCHITECTURE.md +3 -3
- package/src/cli/commands/add-frontend.ts +444 -0
- package/src/cli/commands/new.ts +23 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/templates/frontend/react.ts +2 -1
- package/src/cli/templates/frontend/solid.ts +2 -1
- package/src/cli/templates/frontend/svelte.ts +2 -1
- package/src/cli/templates/frontend/vue.ts +2 -1
- package/src/cli/templates/project/api.ts +1 -1
- package/src/cli/templates/project/default.ts +1 -1
- package/src/cli/templates/project/fullstack.ts +14 -104
- package/src/cli/templates/project/website.ts +63 -12
- package/src/config/types.ts +21 -0
- package/src/graphql/built-in-engine.ts +598 -0
- package/src/graphql/context-builder.ts +110 -0
- package/src/graphql/decorators.ts +358 -0
- package/src/graphql/execution-pipeline.ts +227 -0
- package/src/graphql/graphql-module.ts +563 -0
- package/src/graphql/index.ts +101 -0
- package/src/graphql/metadata.ts +237 -0
- package/src/graphql/schema-builder.ts +319 -0
- package/src/graphql/subscription-handler.ts +283 -0
- package/src/graphql/types.ts +324 -0
- package/src/index.ts +3 -0
- package/src/modules/index.ts +48 -1
- package/tests/integration/cli.test.ts +19 -19
- package/tests/unit/cli.test.ts +1 -1
- package/tests/unit/graphql.test.ts +991 -0
- package/tsconfig.declaration.json +14 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Decorators
|
|
3
|
+
*
|
|
4
|
+
* Code-first decorator API for defining GraphQL schemas from TypeScript classes.
|
|
5
|
+
* Follows the same WeakMap metadata pattern as the OpenAPI module.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* @ObjectType()
|
|
10
|
+
* class User {
|
|
11
|
+
* @Field(() => String) id: string;
|
|
12
|
+
* @Field(() => String) name: string;
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* @Resolver()
|
|
16
|
+
* class UserResolver {
|
|
17
|
+
* @Query(() => [User])
|
|
18
|
+
* users(): User[] { return []; }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
Constructor,
|
|
25
|
+
TypeFn,
|
|
26
|
+
FieldDecoratorOptions,
|
|
27
|
+
FieldOptions,
|
|
28
|
+
} from "./types";
|
|
29
|
+
import {
|
|
30
|
+
setResolverMetadata,
|
|
31
|
+
setObjectTypeMetadata,
|
|
32
|
+
setInputTypeMetadata,
|
|
33
|
+
addQueryField,
|
|
34
|
+
addMutationField,
|
|
35
|
+
addSubscriptionField,
|
|
36
|
+
getQueryFields,
|
|
37
|
+
getMutationFields,
|
|
38
|
+
getSubscriptionFields,
|
|
39
|
+
setGqlPropertyMetadata,
|
|
40
|
+
setParamMetadata,
|
|
41
|
+
getParamMetadata,
|
|
42
|
+
} from "./metadata";
|
|
43
|
+
import { registerTypeClass } from "./schema-builder";
|
|
44
|
+
|
|
45
|
+
// ============= Class Decorators =============
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Marks a class as a GraphQL resolver.
|
|
49
|
+
* Dependencies are resolved from the DI container via @Inject.
|
|
50
|
+
*
|
|
51
|
+
* @param name - Optional GraphQL type name this resolver is for (e.g. 'User').
|
|
52
|
+
* Omit for root-level Query/Mutation/Subscription resolvers.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* @Resolver()
|
|
57
|
+
* class UserResolver { ... }
|
|
58
|
+
*
|
|
59
|
+
* @Resolver('User')
|
|
60
|
+
* class UserFieldResolver { ... }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function Resolver(name?: string): ClassDecorator {
|
|
64
|
+
return (target: unknown) => {
|
|
65
|
+
const ctor = target as Constructor;
|
|
66
|
+
setResolverMetadata(ctor, {
|
|
67
|
+
name: name ?? ctor.name,
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Marks a class as a GraphQL output type.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* @ObjectType()
|
|
78
|
+
* class User {
|
|
79
|
+
* @Field(() => String) id: string;
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function ObjectType(
|
|
84
|
+
name?: string,
|
|
85
|
+
options?: { description?: string },
|
|
86
|
+
): ClassDecorator {
|
|
87
|
+
return (target: unknown) => {
|
|
88
|
+
const ctor = target as Constructor;
|
|
89
|
+
setObjectTypeMetadata(ctor, {
|
|
90
|
+
name: name ?? ctor.name,
|
|
91
|
+
kind: "object",
|
|
92
|
+
description: options?.description,
|
|
93
|
+
});
|
|
94
|
+
registerTypeClass(ctor);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Marks a class as a GraphQL input type.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* @InputType()
|
|
104
|
+
* class CreateUserInput {
|
|
105
|
+
* @Field(() => String) name: string;
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function InputType(
|
|
110
|
+
name?: string,
|
|
111
|
+
options?: { description?: string },
|
|
112
|
+
): ClassDecorator {
|
|
113
|
+
return (target: unknown) => {
|
|
114
|
+
const ctor = target as Constructor;
|
|
115
|
+
setInputTypeMetadata(ctor, {
|
|
116
|
+
name: name ?? ctor.name,
|
|
117
|
+
kind: "input",
|
|
118
|
+
description: options?.description,
|
|
119
|
+
});
|
|
120
|
+
registerTypeClass(ctor);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============= Method Decorators =============
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Marks a method as a GraphQL Query field.
|
|
128
|
+
*
|
|
129
|
+
* @param typeFn - Thunk returning the return type constructor
|
|
130
|
+
* @param options - Field options (nullable, description, name override)
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* @Query(() => [User])
|
|
135
|
+
* async users(): Promise<User[]> { ... }
|
|
136
|
+
*
|
|
137
|
+
* @Query(() => User, { nullable: true })
|
|
138
|
+
* async user(@Args('id') id: string): Promise<User | null> { ... }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export function Query(typeFn: TypeFn, options: FieldOptions = {}): MethodDecorator {
|
|
142
|
+
return (
|
|
143
|
+
target: object,
|
|
144
|
+
propertyKey: string | symbol,
|
|
145
|
+
descriptor: PropertyDescriptor,
|
|
146
|
+
): PropertyDescriptor => {
|
|
147
|
+
const methodName = String(propertyKey);
|
|
148
|
+
addQueryField(target, {
|
|
149
|
+
methodName,
|
|
150
|
+
fieldName: options.name ?? methodName,
|
|
151
|
+
typeFn,
|
|
152
|
+
kind: "query",
|
|
153
|
+
nullable: options.nullable ?? false,
|
|
154
|
+
description: options.description,
|
|
155
|
+
deprecationReason: options.deprecationReason,
|
|
156
|
+
paramMetadata: getParamMetadata(target, methodName),
|
|
157
|
+
});
|
|
158
|
+
return descriptor;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Marks a method as a GraphQL Mutation field.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* @Mutation(() => User)
|
|
168
|
+
* async createUser(@Args('input', CreateUserInput) input: CreateUserInput): Promise<User> { ... }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export function Mutation(typeFn: TypeFn, options: FieldOptions = {}): MethodDecorator {
|
|
172
|
+
return (
|
|
173
|
+
target: object,
|
|
174
|
+
propertyKey: string | symbol,
|
|
175
|
+
descriptor: PropertyDescriptor,
|
|
176
|
+
): PropertyDescriptor => {
|
|
177
|
+
const methodName = String(propertyKey);
|
|
178
|
+
addMutationField(target, {
|
|
179
|
+
methodName,
|
|
180
|
+
fieldName: options.name ?? methodName,
|
|
181
|
+
typeFn,
|
|
182
|
+
kind: "mutation",
|
|
183
|
+
nullable: options.nullable ?? false,
|
|
184
|
+
description: options.description,
|
|
185
|
+
deprecationReason: options.deprecationReason,
|
|
186
|
+
paramMetadata: getParamMetadata(target, methodName),
|
|
187
|
+
});
|
|
188
|
+
return descriptor;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Marks a method as a GraphQL Subscription field.
|
|
194
|
+
* The method must return an AsyncGenerator.
|
|
195
|
+
*
|
|
196
|
+
* Requires an engine with supportsSubscriptions = true (e.g. GraphQLJsAdapter).
|
|
197
|
+
* Requires subscriptions: true in GraphQLModule.setup().
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* @Subscription(() => Message)
|
|
202
|
+
* async *messageAdded(): AsyncGenerator<Message> {
|
|
203
|
+
* yield* pubSub.subscribe('message:added');
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
export function Subscription(typeFn: TypeFn, options: FieldOptions = {}): MethodDecorator {
|
|
208
|
+
return (
|
|
209
|
+
target: object,
|
|
210
|
+
propertyKey: string | symbol,
|
|
211
|
+
descriptor: PropertyDescriptor,
|
|
212
|
+
): PropertyDescriptor => {
|
|
213
|
+
const methodName = String(propertyKey);
|
|
214
|
+
addSubscriptionField(target, {
|
|
215
|
+
methodName,
|
|
216
|
+
fieldName: options.name ?? methodName,
|
|
217
|
+
typeFn,
|
|
218
|
+
kind: "subscription",
|
|
219
|
+
nullable: options.nullable ?? false,
|
|
220
|
+
description: options.description,
|
|
221
|
+
deprecationReason: options.deprecationReason,
|
|
222
|
+
paramMetadata: getParamMetadata(target, methodName),
|
|
223
|
+
});
|
|
224
|
+
return descriptor;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============= Property Decorator =============
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Marks a property as a GraphQL field.
|
|
232
|
+
* Used on @ObjectType and @InputType classes.
|
|
233
|
+
*
|
|
234
|
+
* @param typeFn - Thunk returning the field type
|
|
235
|
+
* @param options - Field options
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```typescript
|
|
239
|
+
* @ObjectType()
|
|
240
|
+
* class User {
|
|
241
|
+
* @Field(() => String) id: string;
|
|
242
|
+
* @Field(() => String, { nullable: true }) bio?: string;
|
|
243
|
+
* @Field(() => [Post]) posts: Post[];
|
|
244
|
+
* }
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export function Field(
|
|
248
|
+
typeFn: TypeFn,
|
|
249
|
+
options: FieldDecoratorOptions = {},
|
|
250
|
+
): PropertyDecorator {
|
|
251
|
+
return (target: object, propertyKey: string | symbol): void => {
|
|
252
|
+
const key =
|
|
253
|
+
typeof propertyKey === "symbol" ? propertyKey.toString() : propertyKey;
|
|
254
|
+
setGqlPropertyMetadata(target, propertyKey, {
|
|
255
|
+
propertyKey: key,
|
|
256
|
+
typeFn,
|
|
257
|
+
nullable: options.nullable ?? false,
|
|
258
|
+
description: options.description,
|
|
259
|
+
deprecationReason: options.deprecationReason,
|
|
260
|
+
defaultValue: options.defaultValue,
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ============= Parameter Decorators =============
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Extracts a specific GraphQL argument by name into a method parameter.
|
|
269
|
+
*
|
|
270
|
+
* @param name - The argument name as it appears in the GraphQL query
|
|
271
|
+
* @param inputTypeFn - Optional: type thunk for object input types (validates shape)
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* @Query(() => User)
|
|
276
|
+
* async user(@Args('id') id: string): Promise<User> { ... }
|
|
277
|
+
*
|
|
278
|
+
* @Mutation(() => User)
|
|
279
|
+
* async createUser(@Args('input', CreateUserInput) input: CreateUserInput): Promise<User> { ... }
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
export function Args(name: string, inputType?: Constructor): ParameterDecorator {
|
|
283
|
+
return (
|
|
284
|
+
target: object,
|
|
285
|
+
propertyKey: string | symbol | undefined,
|
|
286
|
+
parameterIndex: number,
|
|
287
|
+
): void => {
|
|
288
|
+
if (!propertyKey) return;
|
|
289
|
+
const methodName = String(propertyKey);
|
|
290
|
+
setParamMetadata(target, methodName, parameterIndex, {
|
|
291
|
+
index: parameterIndex,
|
|
292
|
+
kind: inputType ? "argsObject" : "args",
|
|
293
|
+
argName: name,
|
|
294
|
+
inputTypeFn: inputType ? () => inputType : undefined,
|
|
295
|
+
});
|
|
296
|
+
// Re-sync param metadata into existing resolver field metadata
|
|
297
|
+
syncParamMetadata(target, methodName);
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Injects the GraphQL context object into a resolver method parameter.
|
|
303
|
+
*
|
|
304
|
+
* Named @GqlContext to avoid collision with the HTTP Context class.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* @Mutation(() => Post)
|
|
309
|
+
* async createPost(
|
|
310
|
+
* @Args('title') title: string,
|
|
311
|
+
* @GqlContext() ctx: GraphQLContext,
|
|
312
|
+
* ): Promise<Post> {
|
|
313
|
+
* const userId = (ctx.user as User).id;
|
|
314
|
+
* ...
|
|
315
|
+
* }
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
export function GqlContext(): ParameterDecorator {
|
|
319
|
+
return (
|
|
320
|
+
target: object,
|
|
321
|
+
propertyKey: string | symbol | undefined,
|
|
322
|
+
parameterIndex: number,
|
|
323
|
+
): void => {
|
|
324
|
+
if (!propertyKey) return;
|
|
325
|
+
const methodName = String(propertyKey);
|
|
326
|
+
setParamMetadata(target, methodName, parameterIndex, {
|
|
327
|
+
index: parameterIndex,
|
|
328
|
+
kind: "context",
|
|
329
|
+
});
|
|
330
|
+
syncParamMetadata(target, methodName);
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ============= Internal Helper =============
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Re-syncs param metadata into already-registered resolver field entries.
|
|
338
|
+
* Decorators are applied bottom-up in TypeScript, so @Args/@GqlContext may run
|
|
339
|
+
* before @Query/@Mutation. This helper updates the field entry with the latest
|
|
340
|
+
* param metadata after each parameter decorator runs.
|
|
341
|
+
*/
|
|
342
|
+
function syncParamMetadata(prototype: object, methodName: string): void {
|
|
343
|
+
const params = getParamMetadata(prototype, methodName);
|
|
344
|
+
|
|
345
|
+
const syncFields = (
|
|
346
|
+
fields: ReturnType<typeof getQueryFields>,
|
|
347
|
+
addFn: typeof addQueryField,
|
|
348
|
+
): void => {
|
|
349
|
+
const field = fields.find((f) => f.methodName === methodName);
|
|
350
|
+
if (field) {
|
|
351
|
+
addFn(prototype, { ...field, paramMetadata: params });
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
syncFields(getQueryFields(prototype), addQueryField);
|
|
356
|
+
syncFields(getMutationFields(prototype), addMutationField);
|
|
357
|
+
syncFields(getSubscriptionFields(prototype), addSubscriptionField);
|
|
358
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Execution Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Applies the same guard/interceptor pipeline used for HTTP controllers
|
|
5
|
+
* to GraphQL resolver methods.
|
|
6
|
+
*
|
|
7
|
+
* Guards and interceptors receive the real HTTP Context, enriched with
|
|
8
|
+
* GraphQL-specific metadata:
|
|
9
|
+
* - context.get('graphql:operation') → resolver field name (e.g. 'users')
|
|
10
|
+
* - context.get('graphql:type') → 'query' | 'mutation' | 'subscription'
|
|
11
|
+
* - context.get('graphql:resolver') → resolver class constructor
|
|
12
|
+
*
|
|
13
|
+
* ## Guard Compatibility
|
|
14
|
+
* - ✅ Auth guards reading Authorization/Cookie headers
|
|
15
|
+
* - ✅ API key guards
|
|
16
|
+
* - ✅ Custom guards reading any header/cookie
|
|
17
|
+
* - ⚠️ Route-based guards (reading context.path) — see /graphql for all ops
|
|
18
|
+
* - ⚠️ CSRF guards (may block all POSTs) — need graphql-aware adaptation
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Context } from "../context";
|
|
22
|
+
import type { Container, Token } from "../container";
|
|
23
|
+
import type { Guard, CanActivate, GuardFn } from "../modules/guards";
|
|
24
|
+
import type { Interceptor, NestInterceptor, InterceptorFn } from "../modules/interceptors";
|
|
25
|
+
import type { GraphQLContext, ResolvedField, GraphQLError } from "./types";
|
|
26
|
+
import {
|
|
27
|
+
executeGuards,
|
|
28
|
+
getClassGuards,
|
|
29
|
+
getMethodGuards,
|
|
30
|
+
} from "../modules/guards";
|
|
31
|
+
import {
|
|
32
|
+
executeInterceptors,
|
|
33
|
+
getClassInterceptors,
|
|
34
|
+
getMethodInterceptors,
|
|
35
|
+
isNestInterceptor,
|
|
36
|
+
isInterceptorFn,
|
|
37
|
+
} from "../modules/interceptors";
|
|
38
|
+
import { enrichContextForGraphQL } from "./context-builder";
|
|
39
|
+
|
|
40
|
+
export type { Guard, Interceptor };
|
|
41
|
+
|
|
42
|
+
// ============= Pipeline Options =============
|
|
43
|
+
|
|
44
|
+
export interface ResolverPipelineOptions {
|
|
45
|
+
/** The resolver class constructor (for reading class-level guard metadata) */
|
|
46
|
+
resolverClass: new (...args: unknown[]) => unknown;
|
|
47
|
+
/** Field name (for reading method-level guard metadata) */
|
|
48
|
+
fieldName: string;
|
|
49
|
+
/** The resolved field descriptor */
|
|
50
|
+
resolvedField: ResolvedField;
|
|
51
|
+
/** GraphQL arguments extracted from the query */
|
|
52
|
+
args: Record<string, unknown>;
|
|
53
|
+
/** Built GraphQL context */
|
|
54
|
+
graphqlContext: GraphQLContext;
|
|
55
|
+
/** HTTP Context from the current request */
|
|
56
|
+
httpContext: Context;
|
|
57
|
+
/** DI container for resolving guard/interceptor instances */
|
|
58
|
+
container: Container;
|
|
59
|
+
/** Global guards from app.getGlobalGuards() */
|
|
60
|
+
globalGuards: Guard[];
|
|
61
|
+
/** Global interceptors from app.getGlobalInterceptors() */
|
|
62
|
+
globalInterceptors: Interceptor[];
|
|
63
|
+
/** Operation type for enriching context */
|
|
64
|
+
operationType: "query" | "mutation" | "subscription";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============= Pipeline Executor =============
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Execute a resolver method through the full guard → interceptor pipeline.
|
|
71
|
+
*
|
|
72
|
+
* Returns the resolver method's return value, or throws if a guard blocks.
|
|
73
|
+
* GraphQL errors should be caught by the caller and placed into the errors array.
|
|
74
|
+
*/
|
|
75
|
+
export async function executeResolverWithPipeline(
|
|
76
|
+
options: ResolverPipelineOptions,
|
|
77
|
+
): Promise<unknown> {
|
|
78
|
+
const {
|
|
79
|
+
resolverClass,
|
|
80
|
+
fieldName,
|
|
81
|
+
resolvedField,
|
|
82
|
+
args,
|
|
83
|
+
graphqlContext,
|
|
84
|
+
httpContext,
|
|
85
|
+
container,
|
|
86
|
+
globalGuards,
|
|
87
|
+
globalInterceptors,
|
|
88
|
+
operationType,
|
|
89
|
+
} = options;
|
|
90
|
+
|
|
91
|
+
// Enrich HTTP context with GraphQL metadata for guard inspection
|
|
92
|
+
enrichContextForGraphQL(
|
|
93
|
+
httpContext,
|
|
94
|
+
fieldName,
|
|
95
|
+
operationType,
|
|
96
|
+
resolverClass,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Read class-level and method-level guards/interceptors
|
|
100
|
+
const classGuards = getClassGuards(resolverClass) ?? [];
|
|
101
|
+
const methodGuards = getMethodGuards(resolverClass.prototype as object, fieldName) ?? [];
|
|
102
|
+
const classInterceptors = getClassInterceptors(resolverClass) ?? [];
|
|
103
|
+
const methodInterceptors =
|
|
104
|
+
getMethodInterceptors(resolverClass.prototype as object, fieldName) ?? [];
|
|
105
|
+
|
|
106
|
+
// Guard resolver helper
|
|
107
|
+
const resolveGuard = (guard: Guard): CanActivate | GuardFn | null => {
|
|
108
|
+
if (
|
|
109
|
+
typeof guard === "object" &&
|
|
110
|
+
guard !== null &&
|
|
111
|
+
!("canActivate" in guard)
|
|
112
|
+
) {
|
|
113
|
+
try {
|
|
114
|
+
return container.resolve(guard as Token) as CanActivate;
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Interceptor resolver helper
|
|
123
|
+
const resolveInterceptor = (
|
|
124
|
+
interceptor: Interceptor,
|
|
125
|
+
): NestInterceptor | InterceptorFn | null => {
|
|
126
|
+
if (!isNestInterceptor(interceptor) && !isInterceptorFn(interceptor)) {
|
|
127
|
+
try {
|
|
128
|
+
return container.resolve(
|
|
129
|
+
interceptor as Token,
|
|
130
|
+
) as NestInterceptor;
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Step 1: Execute guards
|
|
139
|
+
const guardsPassed = await executeGuards(httpContext, {
|
|
140
|
+
globalGuards,
|
|
141
|
+
classGuards,
|
|
142
|
+
methodGuards,
|
|
143
|
+
resolveGuard,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!guardsPassed) {
|
|
147
|
+
throw new GraphQLForbiddenError(
|
|
148
|
+
`Access denied to field '${fieldName}'`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Step 2: Execute interceptors wrapping the resolver call
|
|
153
|
+
return executeInterceptors(
|
|
154
|
+
httpContext,
|
|
155
|
+
async () => {
|
|
156
|
+
return callResolver(resolvedField, args, graphqlContext);
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
globalInterceptors,
|
|
160
|
+
classInterceptors,
|
|
161
|
+
methodInterceptors,
|
|
162
|
+
resolveInterceptor,
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============= Resolver Caller =============
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Calls the actual resolver method with the correct argument list.
|
|
171
|
+
* Resolves @Args and @GqlContext parameters at their declared positions.
|
|
172
|
+
*/
|
|
173
|
+
export async function callResolver(
|
|
174
|
+
field: ResolvedField,
|
|
175
|
+
args: Record<string, unknown>,
|
|
176
|
+
gqlContext: GraphQLContext,
|
|
177
|
+
): Promise<unknown> {
|
|
178
|
+
const instance = field.resolverInstance as Record<
|
|
179
|
+
string,
|
|
180
|
+
(...a: unknown[]) => unknown
|
|
181
|
+
>;
|
|
182
|
+
const methodArgs = buildMethodArgs(field, args, gqlContext);
|
|
183
|
+
const result = instance[field.methodName](...methodArgs);
|
|
184
|
+
return result instanceof Promise ? await result : result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildMethodArgs(
|
|
188
|
+
field: ResolvedField,
|
|
189
|
+
args: Record<string, unknown>,
|
|
190
|
+
gqlContext: GraphQLContext,
|
|
191
|
+
): unknown[] {
|
|
192
|
+
const methodArgs: unknown[] = [];
|
|
193
|
+
|
|
194
|
+
for (const param of field.paramMetadata) {
|
|
195
|
+
switch (param.kind) {
|
|
196
|
+
case "context":
|
|
197
|
+
methodArgs[param.index] = gqlContext;
|
|
198
|
+
break;
|
|
199
|
+
case "args":
|
|
200
|
+
methodArgs[param.index] = param.argName ? args[param.argName] : args;
|
|
201
|
+
break;
|
|
202
|
+
case "argsObject":
|
|
203
|
+
methodArgs[param.index] = param.argName ? args[param.argName] : args;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return methodArgs;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ============= Custom Error =============
|
|
212
|
+
|
|
213
|
+
export class GraphQLForbiddenError extends Error {
|
|
214
|
+
readonly extensions = { code: "FORBIDDEN" };
|
|
215
|
+
|
|
216
|
+
constructor(message = "Forbidden") {
|
|
217
|
+
super(message);
|
|
218
|
+
this.name = "GraphQLForbiddenError";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
toGraphQLError(): GraphQLError {
|
|
222
|
+
return {
|
|
223
|
+
message: this.message,
|
|
224
|
+
extensions: this.extensions,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|