@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,563 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Module
|
|
3
|
+
*
|
|
4
|
+
* Static factory class for setting up GraphQL support in a Bueno application.
|
|
5
|
+
*
|
|
6
|
+
* ## Quick Start
|
|
7
|
+
*
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createApp } from '@buenojs/bueno';
|
|
10
|
+
* import { GraphQLModule } from '@buenojs/bueno/graphql';
|
|
11
|
+
*
|
|
12
|
+
* const app = createApp(AppModule);
|
|
13
|
+
*
|
|
14
|
+
* GraphQLModule.setup(app, {
|
|
15
|
+
* resolvers: [UserResolver, PostResolver],
|
|
16
|
+
* playground: true,
|
|
17
|
+
* subscriptions: true,
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* await app.listen(3000);
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ## Resolver Dependencies
|
|
24
|
+
* Resolvers are instantiated after app.init() via deferred initialization.
|
|
25
|
+
* Dependencies registered in @Module({ providers }) are available automatically.
|
|
26
|
+
*
|
|
27
|
+
* ## Guard/Interceptor Compatibility
|
|
28
|
+
* Guards and interceptors receive the HTTP Context enriched with GraphQL metadata:
|
|
29
|
+
* - context.get('graphql:operation') → field name being resolved
|
|
30
|
+
* - context.get('graphql:type') → 'query' | 'mutation' | 'subscription'
|
|
31
|
+
* Works with: auth guards, API key guards, logging interceptors.
|
|
32
|
+
* May need adaptation: CSRF guards (all GraphQL = POST), route-based guards.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { Application } from "../modules";
|
|
36
|
+
import type { Constructor } from "./types";
|
|
37
|
+
import type { GraphQLModuleOptions, GraphQLEngine } from "./types";
|
|
38
|
+
import {
|
|
39
|
+
getInjectTokens,
|
|
40
|
+
resolveForwardRef,
|
|
41
|
+
type Token,
|
|
42
|
+
} from "../container";
|
|
43
|
+
import { SchemaBuilder, getRegisteredTypeClasses } from "./schema-builder";
|
|
44
|
+
import { BuiltinGraphQLEngine } from "./built-in-engine";
|
|
45
|
+
import { SubscriptionHandler } from "./subscription-handler";
|
|
46
|
+
import { buildGraphQLContext, parseGraphQLRequest } from "./context-builder";
|
|
47
|
+
import { executeResolverWithPipeline, GraphQLForbiddenError } from "./execution-pipeline";
|
|
48
|
+
import type { GraphQLResult, ResolvedSchema, ResolverFieldsByType } from "./types";
|
|
49
|
+
import { Context } from "../context";
|
|
50
|
+
|
|
51
|
+
// ============= Playground HTML =============
|
|
52
|
+
|
|
53
|
+
function generatePlaygroundHTML(path: string): string {
|
|
54
|
+
return `<!DOCTYPE html>
|
|
55
|
+
<html>
|
|
56
|
+
<head>
|
|
57
|
+
<meta charset="utf-8" />
|
|
58
|
+
<title>GraphQL Playground</title>
|
|
59
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
60
|
+
<link rel="stylesheet" href="https://unpkg.com/graphiql@3/graphiql.min.css" />
|
|
61
|
+
<style>body { margin: 0; }</style>
|
|
62
|
+
</head>
|
|
63
|
+
<body>
|
|
64
|
+
<div id="graphiql" style="height:100vh;"></div>
|
|
65
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
66
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
67
|
+
<script crossorigin src="https://unpkg.com/graphiql@3/graphiql.min.js"></script>
|
|
68
|
+
<script>
|
|
69
|
+
const root = ReactDOM.createRoot(document.getElementById('graphiql'));
|
|
70
|
+
root.render(React.createElement(GraphiQL, {
|
|
71
|
+
fetcher: GraphiQL.createFetcher({ url: '${path}' }),
|
|
72
|
+
}));
|
|
73
|
+
</script>
|
|
74
|
+
</body>
|
|
75
|
+
</html>`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============= GraphQL Module Instance =============
|
|
79
|
+
|
|
80
|
+
export class GraphQLModuleInstance {
|
|
81
|
+
constructor(
|
|
82
|
+
readonly sdl: string,
|
|
83
|
+
readonly resolvedSchema: ResolvedSchema,
|
|
84
|
+
readonly path: string,
|
|
85
|
+
) {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============= Deferred Initializer =============
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Handles deferred resolver initialization.
|
|
92
|
+
* Resolvers can only be instantiated after app.init() completes
|
|
93
|
+
* (all DI providers must be registered and resolved first).
|
|
94
|
+
*
|
|
95
|
+
* The route handler calls ensureInitialized() on first request,
|
|
96
|
+
* which is always after app.init() has completed.
|
|
97
|
+
*/
|
|
98
|
+
class DeferredInitializer {
|
|
99
|
+
private initialized = false;
|
|
100
|
+
private initPromise: Promise<void> | null = null;
|
|
101
|
+
private initError: Error | null = null;
|
|
102
|
+
|
|
103
|
+
resolverInstances = new Map<Constructor, unknown>();
|
|
104
|
+
resolverFields: ResolverFieldsByType | null = null;
|
|
105
|
+
resolvedSchema: ResolvedSchema | null = null;
|
|
106
|
+
sdl = "";
|
|
107
|
+
engineSchema: unknown = null;
|
|
108
|
+
|
|
109
|
+
constructor(
|
|
110
|
+
private resolverClasses: Constructor[],
|
|
111
|
+
private app: Application,
|
|
112
|
+
private engine: GraphQLEngine,
|
|
113
|
+
) {}
|
|
114
|
+
|
|
115
|
+
async ensureInitialized(): Promise<void> {
|
|
116
|
+
if (this.initialized) {
|
|
117
|
+
if (this.initError) throw this.initError;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (this.initPromise) {
|
|
121
|
+
await this.initPromise;
|
|
122
|
+
if (this.initError) throw this.initError;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.initPromise = this.initialize();
|
|
126
|
+
await this.initPromise;
|
|
127
|
+
if (this.initError) throw this.initError;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private async initialize(): Promise<void> {
|
|
131
|
+
try {
|
|
132
|
+
// Instantiate resolvers via DI (same pattern as registerController)
|
|
133
|
+
for (const ResolverClass of this.resolverClasses) {
|
|
134
|
+
if (this.resolverInstances.has(ResolverClass)) continue;
|
|
135
|
+
|
|
136
|
+
// Register in container if not already present
|
|
137
|
+
const token = ResolverClass as unknown as Token;
|
|
138
|
+
if (!this.app.container.has(token)) {
|
|
139
|
+
this.app.container.register({
|
|
140
|
+
token,
|
|
141
|
+
useClass: ResolverClass,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Resolve inject tokens from @Inject decorators
|
|
146
|
+
const injectTokens =
|
|
147
|
+
getInjectTokens<Array<Token>>(ResolverClass, "inject:tokens") ?? [];
|
|
148
|
+
|
|
149
|
+
const deps = injectTokens.map((t) => {
|
|
150
|
+
const resolved = resolveForwardRef(t);
|
|
151
|
+
return this.app.container.resolve(resolved as Token);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const instance = new ResolverClass(...deps);
|
|
155
|
+
this.resolverInstances.set(ResolverClass, instance);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build schema
|
|
159
|
+
const builder = new SchemaBuilder(
|
|
160
|
+
this.resolverClasses,
|
|
161
|
+
this.resolverInstances,
|
|
162
|
+
);
|
|
163
|
+
const { sdl, resolvedSchema, resolverFields, typeFields } = builder.build();
|
|
164
|
+
|
|
165
|
+
this.sdl = sdl;
|
|
166
|
+
this.resolvedSchema = resolvedSchema;
|
|
167
|
+
this.resolverFields = resolverFields;
|
|
168
|
+
this.engineSchema = this.engine.buildSchema(resolverFields, typeFields, sdl);
|
|
169
|
+
this.initialized = true;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
this.initError = err instanceof Error ? err : new Error(String(err));
|
|
172
|
+
throw this.initError;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============= GraphQL Module =============
|
|
178
|
+
|
|
179
|
+
export class GraphQLModule {
|
|
180
|
+
/**
|
|
181
|
+
* Set up the GraphQL module on a Bueno application.
|
|
182
|
+
*
|
|
183
|
+
* Registers:
|
|
184
|
+
* - POST <path> — GraphQL query/mutation endpoint
|
|
185
|
+
* - GET <path> — GraphiQL playground (if enabled)
|
|
186
|
+
* - GET <path>/schema — SDL endpoint (if introspection enabled)
|
|
187
|
+
*
|
|
188
|
+
* @param app - The Bueno Application instance
|
|
189
|
+
* @param options - GraphQL configuration
|
|
190
|
+
* @returns GraphQLModuleInstance (access .sdl after first request)
|
|
191
|
+
*/
|
|
192
|
+
static setup(
|
|
193
|
+
app: Application,
|
|
194
|
+
options: GraphQLModuleOptions,
|
|
195
|
+
): GraphQLModuleInstance {
|
|
196
|
+
const engine: GraphQLEngine = options.engine ?? new BuiltinGraphQLEngine();
|
|
197
|
+
const path = options.path ?? "/graphql";
|
|
198
|
+
const introspection = options.introspection !== false;
|
|
199
|
+
|
|
200
|
+
// Determine playground: only auto-enable with engines that support introspection
|
|
201
|
+
const playgroundEnabled =
|
|
202
|
+
options.playground !== undefined
|
|
203
|
+
? options.playground
|
|
204
|
+
: engine.supportsIntrospection;
|
|
205
|
+
|
|
206
|
+
if (options.playground === true && !engine.supportsIntrospection) {
|
|
207
|
+
console.warn(
|
|
208
|
+
"[GraphQL] Playground enabled but the built-in engine does not support " +
|
|
209
|
+
"introspection. GraphiQL will not work correctly. " +
|
|
210
|
+
"Use GraphQLJsAdapter for full playground support.",
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Deferred initializer (resolvers instantiated after app.init())
|
|
215
|
+
const initializer = new DeferredInitializer(options.resolvers, app, engine);
|
|
216
|
+
|
|
217
|
+
// ── POST <path> — Query / Mutation handler ──
|
|
218
|
+
app.router.post(path, async (httpContext: Context) => {
|
|
219
|
+
// Parse request body
|
|
220
|
+
const parsed = await parseGraphQLRequest(httpContext.req);
|
|
221
|
+
if (!parsed) {
|
|
222
|
+
return httpContext.json(
|
|
223
|
+
{
|
|
224
|
+
errors: [{ message: "Invalid GraphQL request: expected JSON body with 'query' field" }],
|
|
225
|
+
},
|
|
226
|
+
{ status: 400 },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Lazy initialize resolvers (only on first request, after app.init())
|
|
231
|
+
try {
|
|
232
|
+
await initializer.ensureInitialized();
|
|
233
|
+
} catch (err) {
|
|
234
|
+
return httpContext.json({
|
|
235
|
+
errors: [{ message: `GraphQL initialization failed: ${(err as Error).message}` }],
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const gqlContext = buildGraphQLContext(httpContext);
|
|
240
|
+
const schema = initializer.engineSchema;
|
|
241
|
+
const fields = initializer.resolverFields!;
|
|
242
|
+
|
|
243
|
+
// Determine operation type from query string
|
|
244
|
+
const trimmed = parsed.query.trim();
|
|
245
|
+
const isMutation = trimmed.startsWith("mutation");
|
|
246
|
+
const operationType = isMutation ? "mutation" : "query";
|
|
247
|
+
const operationFields = isMutation ? fields.mutations : fields.queries;
|
|
248
|
+
|
|
249
|
+
// Execute with engine (which calls resolvers via execution pipeline)
|
|
250
|
+
// We wrap the engine execution to run guards/interceptors per field
|
|
251
|
+
let result: GraphQLResult;
|
|
252
|
+
try {
|
|
253
|
+
result = await GraphQLModule.executeWithPipeline(
|
|
254
|
+
engine,
|
|
255
|
+
schema,
|
|
256
|
+
parsed,
|
|
257
|
+
gqlContext,
|
|
258
|
+
httpContext,
|
|
259
|
+
operationFields,
|
|
260
|
+
operationType,
|
|
261
|
+
isMutation ? "mutation" : "query",
|
|
262
|
+
options,
|
|
263
|
+
app,
|
|
264
|
+
);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
result = {
|
|
267
|
+
errors: [{ message: (err as Error).message }],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return httpContext.json(result);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ── GET <path>/schema — SDL endpoint ──
|
|
275
|
+
if (introspection) {
|
|
276
|
+
app.router.get(`${path}/schema`, async (httpContext: Context) => {
|
|
277
|
+
await initializer.ensureInitialized().catch(() => {});
|
|
278
|
+
return httpContext.text(initializer.sdl || "# Schema not yet initialized");
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── GET <path> — Playground ──
|
|
283
|
+
if (playgroundEnabled) {
|
|
284
|
+
app.router.get(path, (httpContext: Context) => {
|
|
285
|
+
return httpContext.html(generatePlaygroundHTML(path));
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Subscriptions ──
|
|
290
|
+
if (options.subscriptions) {
|
|
291
|
+
if (!engine.supportsSubscriptions) {
|
|
292
|
+
console.warn(
|
|
293
|
+
"[GraphQL] subscriptions: true but the configured engine does not support " +
|
|
294
|
+
"subscriptions. Use GraphQLJsAdapter for subscription support.",
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
const subHandler = new SubscriptionHandler(
|
|
298
|
+
engine,
|
|
299
|
+
null, // engineSchema set after init
|
|
300
|
+
path,
|
|
301
|
+
app.container,
|
|
302
|
+
app.getGlobalGuards(),
|
|
303
|
+
app.getGlobalInterceptors(),
|
|
304
|
+
);
|
|
305
|
+
app.setWebSocketHandler(subHandler.getWebSocketConfig());
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── Optional OpenAPI sync ──
|
|
310
|
+
if (options.syncOpenAPI) {
|
|
311
|
+
// Deferred: runs after first initialization when type classes are known
|
|
312
|
+
queueMicrotask(async () => {
|
|
313
|
+
try {
|
|
314
|
+
await initializer.ensureInitialized().catch(() => {});
|
|
315
|
+
const typeClasses = getRegisteredTypeClasses();
|
|
316
|
+
const { setApiPropertyMetadata } = await import("../openapi/metadata");
|
|
317
|
+
const { getGqlPropertyKeys, getGqlPropertyMetadata } = await import("./metadata");
|
|
318
|
+
|
|
319
|
+
for (const TypeClass of typeClasses) {
|
|
320
|
+
const keys = getGqlPropertyKeys(TypeClass.prototype);
|
|
321
|
+
for (const key of keys) {
|
|
322
|
+
const fieldMeta = getGqlPropertyMetadata(TypeClass.prototype, key);
|
|
323
|
+
if (!fieldMeta) continue;
|
|
324
|
+
const apiOptions = GraphQLModule.fieldToApiProperty(fieldMeta);
|
|
325
|
+
setApiPropertyMetadata(TypeClass.prototype, key, apiOptions);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
console.warn(
|
|
330
|
+
"[GraphQL] syncOpenAPI: failed to sync field metadata to OpenAPI store. " +
|
|
331
|
+
"Ensure @buenojs/bueno/openapi is available.",
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return new GraphQLModuleInstance("", {} as ResolvedSchema, path);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ============= Per-field pipeline execution ============
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Executes a GraphQL request by:
|
|
344
|
+
* 1. Parsing with the engine
|
|
345
|
+
* 2. Running guards/interceptors per top-level field
|
|
346
|
+
* 3. Passing execution back to the engine for sub-selection resolution
|
|
347
|
+
*
|
|
348
|
+
* For the built-in engine this is simple since it handles everything.
|
|
349
|
+
* For external engines, we run guards/interceptors on the top-level
|
|
350
|
+
* operation fields, then delegate full execution to the engine.
|
|
351
|
+
*/
|
|
352
|
+
private static async executeWithPipeline(
|
|
353
|
+
engine: GraphQLEngine,
|
|
354
|
+
schema: unknown,
|
|
355
|
+
parsed: { query: string; variables: Record<string, unknown>; operationName?: string },
|
|
356
|
+
gqlContext: ReturnType<typeof buildGraphQLContext>,
|
|
357
|
+
httpContext: Context,
|
|
358
|
+
operationFields: Map<string, import("./types").ResolvedField>,
|
|
359
|
+
operationType: "query" | "mutation",
|
|
360
|
+
_opTypeStr: string,
|
|
361
|
+
options: GraphQLModuleOptions,
|
|
362
|
+
app: Application,
|
|
363
|
+
): Promise<GraphQLResult> {
|
|
364
|
+
// Run guards for each top-level operation field in the query
|
|
365
|
+
// We do a lightweight parse to find top-level field names
|
|
366
|
+
const topLevelFields = extractTopLevelFields(parsed.query);
|
|
367
|
+
|
|
368
|
+
for (const fieldName of topLevelFields) {
|
|
369
|
+
const resolvedField = operationFields.get(fieldName);
|
|
370
|
+
if (!resolvedField) continue;
|
|
371
|
+
|
|
372
|
+
// Find which resolver class owns this field
|
|
373
|
+
const resolverClass = findResolverClass(
|
|
374
|
+
options.resolvers,
|
|
375
|
+
fieldName,
|
|
376
|
+
operationType,
|
|
377
|
+
);
|
|
378
|
+
if (!resolverClass) continue;
|
|
379
|
+
|
|
380
|
+
// Run guards and interceptors
|
|
381
|
+
const guardsPassed = await runGuardsForField(
|
|
382
|
+
resolverClass,
|
|
383
|
+
fieldName,
|
|
384
|
+
httpContext,
|
|
385
|
+
gqlContext,
|
|
386
|
+
operationType,
|
|
387
|
+
app,
|
|
388
|
+
);
|
|
389
|
+
if (!guardsPassed) {
|
|
390
|
+
return {
|
|
391
|
+
errors: [
|
|
392
|
+
{
|
|
393
|
+
message: `Access denied to field '${fieldName}'`,
|
|
394
|
+
path: [fieldName],
|
|
395
|
+
extensions: { code: "FORBIDDEN" },
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Execute with engine (handles the actual resolver calls for built-in engine,
|
|
403
|
+
// or full SDL schema execution for external engines)
|
|
404
|
+
return engine.execute(
|
|
405
|
+
schema,
|
|
406
|
+
parsed.query,
|
|
407
|
+
parsed.variables,
|
|
408
|
+
gqlContext,
|
|
409
|
+
parsed.operationName,
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ============= OpenAPI sync helper ============
|
|
414
|
+
|
|
415
|
+
private static fieldToApiProperty(
|
|
416
|
+
fieldMeta: import("./types").FieldMetadata,
|
|
417
|
+
): Record<string, unknown> {
|
|
418
|
+
const type = fieldMeta.typeFn();
|
|
419
|
+
const isArray = Array.isArray(type);
|
|
420
|
+
const inner = isArray ? (type as Constructor[])[0] : (type as Constructor);
|
|
421
|
+
|
|
422
|
+
let apiType: unknown;
|
|
423
|
+
if (inner === String) apiType = "string";
|
|
424
|
+
else if (inner === Number) apiType = "number";
|
|
425
|
+
else if (inner === Boolean) apiType = "boolean";
|
|
426
|
+
else if ((inner as Constructor).name === "GraphQLID") apiType = "string";
|
|
427
|
+
else if ((inner as Constructor).name === "GraphQLInt") apiType = "integer";
|
|
428
|
+
else if ((inner as Constructor).name === "GraphQLFloat") apiType = "number";
|
|
429
|
+
else apiType = inner; // class reference — OpenAPI SchemaGenerator will handle
|
|
430
|
+
|
|
431
|
+
const options: Record<string, unknown> = {
|
|
432
|
+
required: !fieldMeta.nullable,
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
if (isArray) {
|
|
436
|
+
options.type = "array";
|
|
437
|
+
options.items =
|
|
438
|
+
typeof apiType === "string"
|
|
439
|
+
? { type: apiType }
|
|
440
|
+
: { $ref: `#/components/schemas/${(inner as Constructor).name}` };
|
|
441
|
+
} else {
|
|
442
|
+
options.type = apiType;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (fieldMeta.description) {
|
|
446
|
+
options.description = fieldMeta.description;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return options;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ============= Helpers ============
|
|
454
|
+
|
|
455
|
+
/** Quick regex-based top-level field name extractor — avoids full parse overhead */
|
|
456
|
+
function extractTopLevelFields(query: string): string[] {
|
|
457
|
+
// Strip operation keyword + optional name + optional variables
|
|
458
|
+
const stripped = query
|
|
459
|
+
.trim()
|
|
460
|
+
.replace(/^(query|mutation)\s*\w*\s*(\([^)]*\))?\s*/, "");
|
|
461
|
+
|
|
462
|
+
// Extract the outermost { ... } and find immediate field names
|
|
463
|
+
const match = stripped.match(/^\{([\s\S]*)\}$/);
|
|
464
|
+
if (!match) return [];
|
|
465
|
+
|
|
466
|
+
const body = match[1];
|
|
467
|
+
const fields: string[] = [];
|
|
468
|
+
|
|
469
|
+
// Simple state machine — extract names at depth 0
|
|
470
|
+
let depth = 0;
|
|
471
|
+
let nameStart = -1;
|
|
472
|
+
|
|
473
|
+
for (let i = 0; i < body.length; i++) {
|
|
474
|
+
const ch = body[i];
|
|
475
|
+
if (ch === "{") {
|
|
476
|
+
depth++;
|
|
477
|
+
} else if (ch === "}") {
|
|
478
|
+
depth--;
|
|
479
|
+
} else if (depth === 0 && /[A-Za-z_]/.test(ch) && nameStart < 0) {
|
|
480
|
+
nameStart = i;
|
|
481
|
+
} else if (depth === 0 && nameStart >= 0 && !/[\w]/.test(ch)) {
|
|
482
|
+
const name = body.slice(nameStart, i);
|
|
483
|
+
// Skip alias separator
|
|
484
|
+
if (body[i] === ":") {
|
|
485
|
+
// This was an alias — next identifier is the real name
|
|
486
|
+
nameStart = -1;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (name !== "on") fields.push(name);
|
|
490
|
+
nameStart = -1;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (nameStart >= 0) {
|
|
494
|
+
fields.push(body.slice(nameStart));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return [...new Set(fields)]; // deduplicate
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
import {
|
|
501
|
+
getQueryFields,
|
|
502
|
+
getMutationFields,
|
|
503
|
+
} from "./metadata";
|
|
504
|
+
|
|
505
|
+
function findResolverClass(
|
|
506
|
+
resolverClasses: Constructor[],
|
|
507
|
+
fieldName: string,
|
|
508
|
+
operationType: "query" | "mutation",
|
|
509
|
+
): Constructor | undefined {
|
|
510
|
+
for (const ResolverClass of resolverClasses) {
|
|
511
|
+
const fields =
|
|
512
|
+
operationType === "mutation"
|
|
513
|
+
? getMutationFields(ResolverClass.prototype)
|
|
514
|
+
: getQueryFields(ResolverClass.prototype);
|
|
515
|
+
|
|
516
|
+
if (fields.some((f) => f.fieldName === fieldName)) {
|
|
517
|
+
return ResolverClass;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function runGuardsForField(
|
|
524
|
+
resolverClass: Constructor,
|
|
525
|
+
fieldName: string,
|
|
526
|
+
httpContext: Context,
|
|
527
|
+
gqlContext: ReturnType<typeof buildGraphQLContext>,
|
|
528
|
+
operationType: "query" | "mutation",
|
|
529
|
+
app: Application,
|
|
530
|
+
): Promise<boolean> {
|
|
531
|
+
const { executeGuards, getClassGuards, getMethodGuards } = await import("../modules/guards");
|
|
532
|
+
const { resolveForwardRef } = await import("../container");
|
|
533
|
+
|
|
534
|
+
const classGuards = getClassGuards(resolverClass) ?? [];
|
|
535
|
+
const methodGuards = getMethodGuards(resolverClass.prototype as object, fieldName) ?? [];
|
|
536
|
+
const globalGuards = app.getGlobalGuards();
|
|
537
|
+
|
|
538
|
+
// Enrich context
|
|
539
|
+
const { enrichContextForGraphQL } = await import("./context-builder");
|
|
540
|
+
enrichContextForGraphQL(httpContext, fieldName, operationType, resolverClass);
|
|
541
|
+
|
|
542
|
+
return executeGuards(httpContext, {
|
|
543
|
+
globalGuards,
|
|
544
|
+
classGuards,
|
|
545
|
+
methodGuards,
|
|
546
|
+
resolveGuard: (guard) => {
|
|
547
|
+
if (
|
|
548
|
+
typeof guard === "object" &&
|
|
549
|
+
guard !== null &&
|
|
550
|
+
!("canActivate" in guard)
|
|
551
|
+
) {
|
|
552
|
+
try {
|
|
553
|
+
return app.container.resolve(
|
|
554
|
+
guard as import("../container").Token,
|
|
555
|
+
) as import("../modules/guards").CanActivate;
|
|
556
|
+
} catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Module for Bueno Framework
|
|
3
|
+
*
|
|
4
|
+
* Code-first GraphQL support with:
|
|
5
|
+
* - Decorator-based schema definition (@Resolver, @Query, @Mutation, @ObjectType, @Field, etc.)
|
|
6
|
+
* - Full DI integration (guards, interceptors work on resolvers)
|
|
7
|
+
* - Pluggable engine (built-in lightweight engine or graphql-js/Yoga via adapter)
|
|
8
|
+
* - Subscriptions via WebSocket (graphql-transport-ws protocol)
|
|
9
|
+
* - GraphiQL playground
|
|
10
|
+
* - Unified types: one class for both GraphQL and REST/OpenAPI (opt-in)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createApp } from '@buenojs/bueno';
|
|
15
|
+
* import {
|
|
16
|
+
* GraphQLModule,
|
|
17
|
+
* Resolver, Query, Mutation,
|
|
18
|
+
* ObjectType, InputType, Field,
|
|
19
|
+
* Args, GqlContext,
|
|
20
|
+
* } from '@buenojs/bueno/graphql';
|
|
21
|
+
*
|
|
22
|
+
* @ObjectType()
|
|
23
|
+
* class User {
|
|
24
|
+
* @Field(() => String) id: string;
|
|
25
|
+
* @Field(() => String) name: string;
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* @Resolver()
|
|
29
|
+
* class UserResolver {
|
|
30
|
+
* @Query(() => [User])
|
|
31
|
+
* users(): User[] { return []; }
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* const app = createApp(AppModule);
|
|
35
|
+
* GraphQLModule.setup(app, { resolvers: [UserResolver] });
|
|
36
|
+
* await app.listen(3000);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// ============= Module Setup =============
|
|
41
|
+
|
|
42
|
+
export { GraphQLModule, GraphQLModuleInstance } from "./graphql-module";
|
|
43
|
+
|
|
44
|
+
// ============= Engines =============
|
|
45
|
+
|
|
46
|
+
export { BuiltinGraphQLEngine } from "./built-in-engine";
|
|
47
|
+
|
|
48
|
+
// ============= Decorators =============
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
Resolver,
|
|
52
|
+
ObjectType,
|
|
53
|
+
InputType,
|
|
54
|
+
Field,
|
|
55
|
+
Query,
|
|
56
|
+
Mutation,
|
|
57
|
+
Subscription,
|
|
58
|
+
Args,
|
|
59
|
+
GqlContext,
|
|
60
|
+
} from "./decorators";
|
|
61
|
+
|
|
62
|
+
// ============= Scalar Sentinels =============
|
|
63
|
+
|
|
64
|
+
export { GraphQLID, GraphQLInt, GraphQLFloat } from "./types";
|
|
65
|
+
|
|
66
|
+
// ============= Types =============
|
|
67
|
+
|
|
68
|
+
export type {
|
|
69
|
+
GraphQLEngine,
|
|
70
|
+
GraphQLContext,
|
|
71
|
+
GraphQLResult,
|
|
72
|
+
GraphQLError,
|
|
73
|
+
GraphQLModuleOptions,
|
|
74
|
+
GraphQLConfig,
|
|
75
|
+
TypeFn,
|
|
76
|
+
FieldDecoratorOptions,
|
|
77
|
+
FieldOptions,
|
|
78
|
+
FieldMetadata,
|
|
79
|
+
ResolverFieldMetadata,
|
|
80
|
+
ParamMetadata,
|
|
81
|
+
ResolvedField,
|
|
82
|
+
ResolvedSchema,
|
|
83
|
+
ResolverFieldsByType,
|
|
84
|
+
Constructor,
|
|
85
|
+
} from "./types";
|
|
86
|
+
|
|
87
|
+
// ============= Schema =============
|
|
88
|
+
|
|
89
|
+
export { SchemaBuilder } from "./schema-builder";
|
|
90
|
+
|
|
91
|
+
// ============= Context =============
|
|
92
|
+
|
|
93
|
+
export { buildGraphQLContext } from "./context-builder";
|
|
94
|
+
|
|
95
|
+
// ============= Pipeline =============
|
|
96
|
+
|
|
97
|
+
export { GraphQLForbiddenError } from "./execution-pipeline";
|
|
98
|
+
|
|
99
|
+
// ============= Subscriptions =============
|
|
100
|
+
|
|
101
|
+
export { SubscriptionHandler } from "./subscription-handler";
|