raffel 0.2.3 → 1.0.0-next.f81d4db
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/dist/adapters/grpc.d.ts.map +1 -1
- package/dist/adapters/grpc.js +1 -15
- package/dist/adapters/grpc.js.map +1 -1
- package/dist/adapters/http.d.ts +6 -0
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +53 -86
- package/dist/adapters/http.js.map +1 -1
- package/dist/adapters/index.d.ts +0 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +0 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/tcp.d.ts +16 -0
- package/dist/adapters/tcp.d.ts.map +1 -1
- package/dist/adapters/tcp.js +85 -34
- package/dist/adapters/tcp.js.map +1 -1
- package/dist/adapters/websocket.d.ts.map +1 -1
- package/dist/adapters/websocket.js +1 -14
- package/dist/adapters/websocket.js.map +1 -1
- package/dist/cache/drivers/index.d.ts +0 -1
- package/dist/cache/drivers/index.d.ts.map +1 -1
- package/dist/cache/drivers/index.js +0 -1
- package/dist/cache/drivers/index.js.map +1 -1
- package/dist/cache/factory.d.ts +2 -9
- package/dist/cache/factory.d.ts.map +1 -1
- package/dist/cache/factory.js +1 -19
- package/dist/cache/factory.js.map +1 -1
- package/dist/cache/index.d.ts +2 -7
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -6
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/types.d.ts +1 -32
- package/dist/cache/types.d.ts.map +1 -1
- package/dist/core/event-delivery.d.ts.map +1 -1
- package/dist/core/event-delivery.js +5 -3
- package/dist/core/event-delivery.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/registry.d.ts +3 -3
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +30 -8
- package/dist/core/registry.js.map +1 -1
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +5 -4
- package/dist/core/router.js.map +1 -1
- package/dist/docs/generators/code-samples.d.ts +32 -0
- package/dist/docs/generators/code-samples.d.ts.map +1 -0
- package/dist/docs/generators/code-samples.js +322 -0
- package/dist/docs/generators/code-samples.js.map +1 -0
- package/dist/docs/generators/errors-types.d.ts +2 -2
- package/dist/docs/generators/errors-types.js +2 -2
- package/dist/docs/generators/http-generator.d.ts +10 -0
- package/dist/docs/generators/http-generator.d.ts.map +1 -1
- package/dist/docs/generators/http-generator.js +100 -3
- package/dist/docs/generators/http-generator.js.map +1 -1
- package/dist/docs/generators/usd-generator.d.ts +31 -1
- package/dist/docs/generators/usd-generator.d.ts.map +1 -1
- package/dist/docs/generators/usd-generator.js +19 -1
- package/dist/docs/generators/usd-generator.js.map +1 -1
- package/dist/docs/generators/websocket-generator.d.ts.map +1 -1
- package/dist/docs/generators/websocket-generator.js +6 -4
- package/dist/docs/generators/websocket-generator.js.map +1 -1
- package/dist/docs/ui/html-builder.d.ts.map +1 -1
- package/dist/docs/ui/html-builder.js +260 -54
- package/dist/docs/ui/html-builder.js.map +1 -1
- package/dist/docs/ui/styles.d.ts.map +1 -1
- package/dist/docs/ui/styles.js +240 -30
- package/dist/docs/ui/styles.js.map +1 -1
- package/dist/docs/ui/types.d.ts +15 -1
- package/dist/docs/ui/types.d.ts.map +1 -1
- package/dist/docs/usd-middleware.d.ts +32 -7
- package/dist/docs/usd-middleware.d.ts.map +1 -1
- package/dist/docs/usd-middleware.js +5 -1
- package/dist/docs/usd-middleware.js.map +1 -1
- package/dist/dx/index.d.ts +2 -0
- package/dist/dx/index.d.ts.map +1 -1
- package/dist/dx/index.js +1 -0
- package/dist/dx/index.js.map +1 -1
- package/dist/dx/logging/index.d.ts.map +1 -1
- package/dist/dx/logging/index.js +5 -3
- package/dist/dx/logging/index.js.map +1 -1
- package/dist/dx/scenarios.d.ts +26 -0
- package/dist/dx/scenarios.d.ts.map +1 -0
- package/dist/dx/scenarios.js +109 -0
- package/dist/dx/scenarios.js.map +1 -0
- package/dist/errors/codes.d.ts +1 -1
- package/dist/errors/codes.js +1 -1
- package/dist/graphql/adapter.js +1 -1
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +4 -0
- package/dist/http/index.js.map +1 -1
- package/dist/http/openapi-ui.d.ts +174 -0
- package/dist/http/openapi-ui.d.ts.map +1 -0
- package/dist/http/openapi-ui.js +221 -0
- package/dist/http/openapi-ui.js.map +1 -0
- package/dist/http/serve.d.ts +14 -0
- package/dist/http/serve.d.ts.map +1 -1
- package/dist/http/serve.js +8 -1
- package/dist/http/serve.js.map +1 -1
- package/dist/index.d.ts +17 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/mcp/cli.d.ts +2 -1
- package/dist/mcp/cli.d.ts.map +1 -1
- package/dist/mcp/cli.js +30 -7
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/docs/adapters.d.ts.map +1 -1
- package/dist/mcp/docs/adapters.js +1 -117
- package/dist/mcp/docs/adapters.js.map +1 -1
- package/dist/mcp/docs/index.d.ts +0 -1
- package/dist/mcp/docs/index.d.ts.map +1 -1
- package/dist/mcp/docs/index.js +0 -1
- package/dist/mcp/docs/index.js.map +1 -1
- package/dist/mcp/docs/interceptors.d.ts.map +1 -1
- package/dist/mcp/docs/interceptors.js +272 -1
- package/dist/mcp/docs/interceptors.js.map +1 -1
- package/dist/mcp/docs/patterns.js +1 -1
- package/dist/mcp/prompts/index.d.ts.map +1 -1
- package/dist/mcp/prompts/index.js +147 -22
- package/dist/mcp/prompts/index.js.map +1 -1
- package/dist/mcp/resources/index.d.ts.map +1 -1
- package/dist/mcp/resources/index.js +508 -10
- package/dist/mcp/resources/index.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/definitions.d.ts +2 -0
- package/dist/mcp/tools/definitions.d.ts.map +1 -1
- package/dist/mcp/tools/definitions.js +149 -1
- package/dist/mcp/tools/definitions.js.map +1 -1
- package/dist/mcp/tools/handlers.d.ts.map +1 -1
- package/dist/mcp/tools/handlers.js +828 -44
- package/dist/mcp/tools/handlers.js.map +1 -1
- package/dist/mcp/types.d.ts +1 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/metrics/interceptor.d.ts.map +1 -1
- package/dist/metrics/interceptor.js +10 -2
- package/dist/metrics/interceptor.js.map +1 -1
- package/dist/middleware/interceptors/cache.d.ts +1 -1
- package/dist/middleware/interceptors/cache.d.ts.map +1 -1
- package/dist/middleware/interceptors/cache.js +4 -2
- package/dist/middleware/interceptors/cache.js.map +1 -1
- package/dist/middleware/interceptors/envelope.d.ts.map +1 -1
- package/dist/middleware/interceptors/envelope.js +12 -14
- package/dist/middleware/interceptors/envelope.js.map +1 -1
- package/dist/middleware/interceptors/logging.d.ts.map +1 -1
- package/dist/middleware/interceptors/logging.js +11 -6
- package/dist/middleware/interceptors/logging.js.map +1 -1
- package/dist/middleware/session/drivers/memory.d.ts +36 -0
- package/dist/middleware/session/drivers/memory.d.ts.map +1 -0
- package/dist/middleware/session/drivers/memory.js +82 -0
- package/dist/middleware/session/drivers/memory.js.map +1 -0
- package/dist/middleware/session/drivers/redis.d.ts +58 -0
- package/dist/middleware/session/drivers/redis.d.ts.map +1 -0
- package/dist/middleware/session/drivers/redis.js +70 -0
- package/dist/middleware/session/drivers/redis.js.map +1 -0
- package/dist/middleware/session/drivers/s3db.d.ts +65 -0
- package/dist/middleware/session/drivers/s3db.d.ts.map +1 -0
- package/dist/middleware/session/drivers/s3db.js +98 -0
- package/dist/middleware/session/drivers/s3db.js.map +1 -0
- package/dist/middleware/session/index.d.ts +47 -0
- package/dist/middleware/session/index.d.ts.map +1 -0
- package/dist/middleware/session/index.js +46 -0
- package/dist/middleware/session/index.js.map +1 -0
- package/dist/middleware/session/interceptor.d.ts +42 -0
- package/dist/middleware/session/interceptor.d.ts.map +1 -0
- package/dist/middleware/session/interceptor.js +264 -0
- package/dist/middleware/session/interceptor.js.map +1 -0
- package/dist/middleware/session/types.d.ts +116 -0
- package/dist/middleware/session/types.d.ts.map +1 -0
- package/dist/middleware/session/types.js +9 -0
- package/dist/middleware/session/types.js.map +1 -0
- package/dist/proxy/connect-tunnel.d.ts +50 -0
- package/dist/proxy/connect-tunnel.d.ts.map +1 -0
- package/dist/proxy/connect-tunnel.js +163 -0
- package/dist/proxy/connect-tunnel.js.map +1 -0
- package/dist/proxy/http-forward.d.ts +48 -0
- package/dist/proxy/http-forward.d.ts.map +1 -0
- package/dist/proxy/http-forward.js +160 -0
- package/dist/proxy/http-forward.js.map +1 -0
- package/dist/proxy/index.d.ts +19 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +14 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/socks5.d.ts +22 -0
- package/dist/proxy/socks5.d.ts.map +1 -0
- package/dist/proxy/socks5.js +307 -0
- package/dist/proxy/socks5.js.map +1 -0
- package/dist/proxy/transparent.d.ts +40 -0
- package/dist/proxy/transparent.d.ts.map +1 -0
- package/dist/proxy/transparent.js +144 -0
- package/dist/proxy/transparent.js.map +1 -0
- package/dist/proxy/types.d.ts +32 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/types.js +5 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/proxy/utils/auth.d.ts +31 -0
- package/dist/proxy/utils/auth.d.ts.map +1 -0
- package/dist/proxy/utils/auth.js +69 -0
- package/dist/proxy/utils/auth.js.map +1 -0
- package/dist/proxy/utils/hop-headers.d.ts +13 -0
- package/dist/proxy/utils/hop-headers.d.ts.map +1 -0
- package/dist/proxy/utils/hop-headers.js +38 -0
- package/dist/proxy/utils/hop-headers.js.map +1 -0
- package/dist/proxy/utils/pipe.d.ts +22 -0
- package/dist/proxy/utils/pipe.d.ts.map +1 -0
- package/dist/proxy/utils/pipe.js +60 -0
- package/dist/proxy/utils/pipe.js.map +1 -0
- package/dist/rate-limit/factory.d.ts.map +1 -1
- package/dist/rate-limit/factory.js +30 -6
- package/dist/rate-limit/factory.js.map +1 -1
- package/dist/server/builder/config-preview.d.ts +34 -0
- package/dist/server/builder/config-preview.d.ts.map +1 -0
- package/dist/server/builder/config-preview.js +95 -0
- package/dist/server/builder/config-preview.js.map +1 -0
- package/dist/server/builder/lifecycle-utils.d.ts +11 -0
- package/dist/server/builder/lifecycle-utils.d.ts.map +1 -0
- package/dist/server/builder/lifecycle-utils.js +32 -0
- package/dist/server/builder/lifecycle-utils.js.map +1 -0
- package/dist/server/builder/lifecycle.d.ts +86 -0
- package/dist/server/builder/lifecycle.d.ts.map +1 -0
- package/dist/server/builder/lifecycle.js +738 -0
- package/dist/server/builder/lifecycle.js.map +1 -0
- package/dist/server/builder/single-port-utils.d.ts +27 -0
- package/dist/server/builder/single-port-utils.d.ts.map +1 -0
- package/dist/server/builder/single-port-utils.js +170 -0
- package/dist/server/builder/single-port-utils.js.map +1 -0
- package/dist/server/builder.d.ts.map +1 -1
- package/dist/server/builder.js +446 -601
- package/dist/server/builder.js.map +1 -1
- package/dist/server/discovery-bootstrap.d.ts +21 -0
- package/dist/server/discovery-bootstrap.d.ts.map +1 -0
- package/dist/server/discovery-bootstrap.js +39 -0
- package/dist/server/discovery-bootstrap.js.map +1 -0
- package/dist/server/errors.d.ts.map +1 -1
- package/dist/server/errors.js +1 -33
- package/dist/server/errors.js.map +1 -1
- package/dist/server/front-door.d.ts +34 -0
- package/dist/server/front-door.d.ts.map +1 -0
- package/dist/server/front-door.js +238 -0
- package/dist/server/front-door.js.map +1 -0
- package/dist/server/fs-routes/types.d.ts +6 -0
- package/dist/server/fs-routes/types.d.ts.map +1 -1
- package/dist/server/handler-builders.d.ts +22 -4
- package/dist/server/handler-builders.d.ts.map +1 -1
- package/dist/server/handler-builders.js +37 -14
- package/dist/server/handler-builders.js.map +1 -1
- package/dist/server/index.d.ts +4 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/interceptor-utils.d.ts +8 -0
- package/dist/server/interceptor-utils.d.ts.map +1 -0
- package/dist/server/interceptor-utils.js +23 -0
- package/dist/server/interceptor-utils.js.map +1 -0
- package/dist/server/port-binding.d.ts +44 -0
- package/dist/server/port-binding.d.ts.map +1 -0
- package/dist/server/port-binding.js +94 -0
- package/dist/server/port-binding.js.map +1 -0
- package/dist/server/protocol-aliases.d.ts +56 -0
- package/dist/server/protocol-aliases.d.ts.map +1 -0
- package/dist/server/protocol-aliases.js +47 -0
- package/dist/server/protocol-aliases.js.map +1 -0
- package/dist/server/protocol-config.d.ts +17 -1
- package/dist/server/protocol-config.d.ts.map +1 -1
- package/dist/server/protocol-config.js +116 -9
- package/dist/server/protocol-config.js.map +1 -1
- package/dist/server/resource-builder.js +3 -3
- package/dist/server/resource-builder.js.map +1 -1
- package/dist/server/rest-middleware.d.ts.map +1 -1
- package/dist/server/rest-middleware.js +1 -15
- package/dist/server/rest-middleware.js.map +1 -1
- package/dist/server/router-module.d.ts.map +1 -1
- package/dist/server/router-module.js +22 -99
- package/dist/server/router-module.js.map +1 -1
- package/dist/server/shared-context.d.ts.map +1 -1
- package/dist/server/shared-context.js +3 -1
- package/dist/server/shared-context.js.map +1 -1
- package/dist/server/single-port/dispatcher.d.ts +10 -0
- package/dist/server/single-port/dispatcher.d.ts.map +1 -0
- package/dist/server/single-port/dispatcher.js +188 -0
- package/dist/server/single-port/dispatcher.js.map +1 -0
- package/dist/server/single-port/index.d.ts +4 -0
- package/dist/server/single-port/index.d.ts.map +1 -0
- package/dist/server/single-port/index.js +3 -0
- package/dist/server/single-port/index.js.map +1 -0
- package/dist/server/single-port/registry.d.ts +19 -0
- package/dist/server/single-port/registry.d.ts.map +1 -0
- package/dist/server/single-port/registry.js +24 -0
- package/dist/server/single-port/registry.js.map +1 -0
- package/dist/server/single-port/types.d.ts +46 -0
- package/dist/server/single-port/types.d.ts.map +1 -0
- package/dist/server/single-port/types.js +2 -0
- package/dist/server/single-port/types.js.map +1 -0
- package/dist/server/telemetry-bootstrap.d.ts +38 -0
- package/dist/server/telemetry-bootstrap.d.ts.map +1 -0
- package/dist/server/telemetry-bootstrap.js +115 -0
- package/dist/server/telemetry-bootstrap.js.map +1 -0
- package/dist/server/types.d.ts +422 -21
- package/dist/server/types.d.ts.map +1 -1
- package/dist/testing/echo-protection.d.ts +30 -0
- package/dist/testing/echo-protection.d.ts.map +1 -0
- package/dist/testing/echo-protection.js +158 -0
- package/dist/testing/echo-protection.js.map +1 -0
- package/dist/testing/index.d.ts +344 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +1072 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-dns-server.d.ts +31 -0
- package/dist/testing/mock-dns-server.d.ts.map +1 -0
- package/dist/testing/mock-dns-server.js +247 -0
- package/dist/testing/mock-dns-server.js.map +1 -0
- package/dist/testing/mock-hls-server.d.ts +85 -0
- package/dist/testing/mock-hls-server.d.ts.map +1 -0
- package/dist/testing/mock-hls-server.js +252 -0
- package/dist/testing/mock-hls-server.js.map +1 -0
- package/dist/testing/mock-proxy-server.d.ts +78 -0
- package/dist/testing/mock-proxy-server.d.ts.map +1 -0
- package/dist/testing/mock-proxy-server.js +277 -0
- package/dist/testing/mock-proxy-server.js.map +1 -0
- package/dist/testing/mock-sse-server.d.ts +56 -0
- package/dist/testing/mock-sse-server.d.ts.map +1 -0
- package/dist/testing/mock-sse-server.js +206 -0
- package/dist/testing/mock-sse-server.js.map +1 -0
- package/dist/testing/proxy-certs.d.ts +9 -0
- package/dist/testing/proxy-certs.d.ts.map +1 -0
- package/dist/testing/proxy-certs.js +2 -0
- package/dist/testing/proxy-certs.js.map +1 -0
- package/dist/testing/service-suite.d.ts +47 -0
- package/dist/testing/service-suite.d.ts.map +1 -0
- package/dist/testing/service-suite.js +82 -0
- package/dist/testing/service-suite.js.map +1 -0
- package/dist/testing/service-types.d.ts +32 -0
- package/dist/testing/service-types.d.ts.map +1 -0
- package/dist/testing/service-types.js +2 -0
- package/dist/testing/service-types.js.map +1 -0
- package/dist/ui/core/event-delivery.d.ts.map +1 -1
- package/dist/ui/core/index.d.ts +1 -1
- package/dist/ui/core/index.d.ts.map +1 -1
- package/dist/ui/core/registry.d.ts +3 -3
- package/dist/ui/core/registry.d.ts.map +1 -1
- package/dist/ui/core/router.d.ts.map +1 -1
- package/dist/ui/docs/generators/code-samples.d.ts +32 -0
- package/dist/ui/docs/generators/code-samples.d.ts.map +1 -0
- package/dist/ui/docs/generators/errors-types.d.ts +2 -2
- package/dist/ui/docs/generators/http-generator.d.ts +10 -0
- package/dist/ui/docs/generators/http-generator.d.ts.map +1 -1
- package/dist/ui/docs/generators/usd-generator.d.ts +31 -1
- package/dist/ui/docs/generators/usd-generator.d.ts.map +1 -1
- package/dist/ui/docs/generators/websocket-generator.d.ts.map +1 -1
- package/dist/ui/docs/ui/html-builder.d.ts.map +1 -1
- package/dist/ui/docs/ui/styles.d.ts.map +1 -1
- package/dist/ui/docs/ui/types.d.ts +15 -1
- package/dist/ui/docs/ui/types.d.ts.map +1 -1
- package/dist/ui/docs/usd-middleware.d.ts +32 -7
- package/dist/ui/docs/usd-middleware.d.ts.map +1 -1
- package/dist/ui/errors/codes.d.ts +1 -1
- package/dist/ui/server/fs-routes/types.d.ts +6 -0
- package/dist/ui/server/fs-routes/types.d.ts.map +1 -1
- package/dist/ui/usd/index.d.ts +1 -1
- package/dist/ui/usd/index.d.ts.map +1 -1
- package/dist/ui/usd/spec/types.d.ts +91 -0
- package/dist/ui/usd/spec/types.d.ts.map +1 -1
- package/dist/usd/index.d.ts +1 -1
- package/dist/usd/index.d.ts.map +1 -1
- package/dist/usd/index.js.map +1 -1
- package/dist/usd/spec/types.d.ts +91 -0
- package/dist/usd/spec/types.d.ts.map +1 -1
- package/dist/usd/spec/types.js.map +1 -1
- package/dist/utils/certs.d.ts +30 -0
- package/dist/utils/certs.d.ts.map +1 -0
- package/dist/utils/certs.js +230 -0
- package/dist/utils/certs.js.map +1 -0
- package/dist/utils/context-utils.d.ts +11 -0
- package/dist/utils/context-utils.d.ts.map +1 -0
- package/dist/utils/context-utils.js +24 -0
- package/dist/utils/context-utils.js.map +1 -0
- package/package.json +11 -2
- package/dist/adapters/grpc.int.test.d.ts +0 -5
- package/dist/adapters/grpc.int.test.d.ts.map +0 -1
- package/dist/adapters/grpc.int.test.js +0 -168
- package/dist/adapters/grpc.int.test.js.map +0 -1
- package/dist/adapters/http.int.test.d.ts +0 -5
- package/dist/adapters/http.int.test.d.ts.map +0 -1
- package/dist/adapters/http.int.test.js +0 -396
- package/dist/adapters/http.int.test.js.map +0 -1
- package/dist/adapters/jsonrpc.int.test.d.ts +0 -5
- package/dist/adapters/jsonrpc.int.test.d.ts.map +0 -1
- package/dist/adapters/jsonrpc.int.test.js +0 -640
- package/dist/adapters/jsonrpc.int.test.js.map +0 -1
- package/dist/adapters/s3db/adapter.d.ts +0 -105
- package/dist/adapters/s3db/adapter.d.ts.map +0 -1
- package/dist/adapters/s3db/adapter.js +0 -536
- package/dist/adapters/s3db/adapter.js.map +0 -1
- package/dist/adapters/s3db/index.d.ts +0 -20
- package/dist/adapters/s3db/index.d.ts.map +0 -1
- package/dist/adapters/s3db/index.js +0 -25
- package/dist/adapters/s3db/index.js.map +0 -1
- package/dist/adapters/s3db/s3db.int.test.d.ts +0 -5
- package/dist/adapters/s3db/s3db.int.test.d.ts.map +0 -1
- package/dist/adapters/s3db/s3db.int.test.js +0 -472
- package/dist/adapters/s3db/s3db.int.test.js.map +0 -1
- package/dist/adapters/s3db/types.d.ts +0 -277
- package/dist/adapters/s3db/types.d.ts.map +0 -1
- package/dist/adapters/s3db/types.js +0 -7
- package/dist/adapters/s3db/types.js.map +0 -1
- package/dist/adapters/s3db/utils/etag.d.ts +0 -65
- package/dist/adapters/s3db/utils/etag.d.ts.map +0 -1
- package/dist/adapters/s3db/utils/etag.js +0 -99
- package/dist/adapters/s3db/utils/etag.js.map +0 -1
- package/dist/adapters/s3db/utils/guards.d.ts +0 -121
- package/dist/adapters/s3db/utils/guards.d.ts.map +0 -1
- package/dist/adapters/s3db/utils/guards.js +0 -169
- package/dist/adapters/s3db/utils/guards.js.map +0 -1
- package/dist/adapters/s3db/utils/index.d.ts +0 -9
- package/dist/adapters/s3db/utils/index.d.ts.map +0 -1
- package/dist/adapters/s3db/utils/index.js +0 -10
- package/dist/adapters/s3db/utils/index.js.map +0 -1
- package/dist/adapters/s3db/utils/populate.d.ts +0 -89
- package/dist/adapters/s3db/utils/populate.d.ts.map +0 -1
- package/dist/adapters/s3db/utils/populate.js +0 -165
- package/dist/adapters/s3db/utils/populate.js.map +0 -1
- package/dist/adapters/tcp.int.test.d.ts +0 -5
- package/dist/adapters/tcp.int.test.d.ts.map +0 -1
- package/dist/adapters/tcp.int.test.js +0 -379
- package/dist/adapters/tcp.int.test.js.map +0 -1
- package/dist/adapters/udp.int.test.d.ts +0 -5
- package/dist/adapters/udp.int.test.d.ts.map +0 -1
- package/dist/adapters/udp.int.test.js +0 -397
- package/dist/adapters/udp.int.test.js.map +0 -1
- package/dist/adapters/websocket.int.test.d.ts +0 -5
- package/dist/adapters/websocket.int.test.d.ts.map +0 -1
- package/dist/adapters/websocket.int.test.js +0 -383
- package/dist/adapters/websocket.int.test.js.map +0 -1
- package/dist/cache/drivers/memory.int.test.d.ts +0 -5
- package/dist/cache/drivers/memory.int.test.d.ts.map +0 -1
- package/dist/cache/drivers/memory.int.test.js +0 -241
- package/dist/cache/drivers/memory.int.test.js.map +0 -1
- package/dist/cache/drivers/s3db.d.ts +0 -78
- package/dist/cache/drivers/s3db.d.ts.map +0 -1
- package/dist/cache/drivers/s3db.js +0 -216
- package/dist/cache/drivers/s3db.js.map +0 -1
- package/dist/channels/channel-manager.int.test.d.ts +0 -5
- package/dist/channels/channel-manager.int.test.d.ts.map +0 -1
- package/dist/channels/channel-manager.int.test.js +0 -378
- package/dist/channels/channel-manager.int.test.js.map +0 -1
- package/dist/core/ctx-call.int.test.d.ts +0 -7
- package/dist/core/ctx-call.int.test.d.ts.map +0 -1
- package/dist/core/ctx-call.int.test.js +0 -263
- package/dist/core/ctx-call.int.test.js.map +0 -1
- package/dist/core/registry.unit.test.d.ts +0 -2
- package/dist/core/registry.unit.test.d.ts.map +0 -1
- package/dist/core/registry.unit.test.js +0 -133
- package/dist/core/registry.unit.test.js.map +0 -1
- package/dist/core/router.int.test.d.ts +0 -2
- package/dist/core/router.int.test.d.ts.map +0 -1
- package/dist/core/router.int.test.js +0 -368
- package/dist/core/router.int.test.js.map +0 -1
- package/dist/docs/generators/generators.int.test.d.ts +0 -2
- package/dist/docs/generators/generators.int.test.d.ts.map +0 -1
- package/dist/docs/generators/generators.int.test.js +0 -105
- package/dist/docs/generators/generators.int.test.js.map +0 -1
- package/dist/docs/openapi/generator.int.test.d.ts +0 -5
- package/dist/docs/openapi/generator.int.test.d.ts.map +0 -1
- package/dist/docs/openapi/generator.int.test.js +0 -260
- package/dist/docs/openapi/generator.int.test.js.map +0 -1
- package/dist/dx/health/health.int.test.d.ts +0 -5
- package/dist/dx/health/health.int.test.d.ts.map +0 -1
- package/dist/dx/health/health.int.test.js +0 -249
- package/dist/dx/health/health.int.test.js.map +0 -1
- package/dist/dx/logging/http-logging.int.test.d.ts +0 -5
- package/dist/dx/logging/http-logging.int.test.d.ts.map +0 -1
- package/dist/dx/logging/http-logging.int.test.js +0 -319
- package/dist/dx/logging/http-logging.int.test.js.map +0 -1
- package/dist/errors/factories.unit.test.d.ts +0 -7
- package/dist/errors/factories.unit.test.d.ts.map +0 -1
- package/dist/errors/factories.unit.test.js +0 -358
- package/dist/errors/factories.unit.test.js.map +0 -1
- package/dist/graphql/graphql.int.test.d.ts +0 -10
- package/dist/graphql/graphql.int.test.d.ts.map +0 -1
- package/dist/graphql/graphql.int.test.js +0 -698
- package/dist/graphql/graphql.int.test.js.map +0 -1
- package/dist/graphql/schema-generator.int.test.d.ts +0 -5
- package/dist/graphql/schema-generator.int.test.d.ts.map +0 -1
- package/dist/graphql/schema-generator.int.test.js +0 -51
- package/dist/graphql/schema-generator.int.test.js.map +0 -1
- package/dist/http/http.int.test.d.ts +0 -7
- package/dist/http/http.int.test.d.ts.map +0 -1
- package/dist/http/http.int.test.js +0 -605
- package/dist/http/http.int.test.js.map +0 -1
- package/dist/http/session-redis.int.test.d.ts +0 -8
- package/dist/http/session-redis.int.test.d.ts.map +0 -1
- package/dist/http/session-redis.int.test.js +0 -492
- package/dist/http/session-redis.int.test.js.map +0 -1
- package/dist/metrics/metrics.int.test.d.ts +0 -5
- package/dist/metrics/metrics.int.test.d.ts.map +0 -1
- package/dist/metrics/metrics.int.test.js +0 -288
- package/dist/metrics/metrics.int.test.js.map +0 -1
- package/dist/middleware/auth/oauth2.int.test.d.ts +0 -2
- package/dist/middleware/auth/oauth2.int.test.d.ts.map +0 -1
- package/dist/middleware/auth/oauth2.int.test.js +0 -714
- package/dist/middleware/auth/oauth2.int.test.js.map +0 -1
- package/dist/middleware/auth.int.test.d.ts +0 -5
- package/dist/middleware/auth.int.test.d.ts.map +0 -1
- package/dist/middleware/auth.int.test.js +0 -366
- package/dist/middleware/auth.int.test.js.map +0 -1
- package/dist/middleware/compose.int.test.d.ts +0 -7
- package/dist/middleware/compose.int.test.d.ts.map +0 -1
- package/dist/middleware/compose.int.test.js +0 -238
- package/dist/middleware/compose.int.test.js.map +0 -1
- package/dist/middleware/interceptors/bulkhead.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/bulkhead.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/bulkhead.int.test.js +0 -263
- package/dist/middleware/interceptors/bulkhead.int.test.js.map +0 -1
- package/dist/middleware/interceptors/cache.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/cache.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/cache.int.test.js +0 -470
- package/dist/middleware/interceptors/cache.int.test.js.map +0 -1
- package/dist/middleware/interceptors/circuit-breaker.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/circuit-breaker.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/circuit-breaker.int.test.js +0 -253
- package/dist/middleware/interceptors/circuit-breaker.int.test.js.map +0 -1
- package/dist/middleware/interceptors/envelope.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/envelope.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/envelope.int.test.js +0 -409
- package/dist/middleware/interceptors/envelope.int.test.js.map +0 -1
- package/dist/middleware/interceptors/fallback.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/fallback.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/fallback.int.test.js +0 -211
- package/dist/middleware/interceptors/fallback.int.test.js.map +0 -1
- package/dist/middleware/interceptors/logging.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/logging.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/logging.int.test.js +0 -144
- package/dist/middleware/interceptors/logging.int.test.js.map +0 -1
- package/dist/middleware/interceptors/rate-limit.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/rate-limit.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/rate-limit.int.test.js +0 -221
- package/dist/middleware/interceptors/rate-limit.int.test.js.map +0 -1
- package/dist/middleware/interceptors/request-id.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/request-id.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/request-id.int.test.js +0 -136
- package/dist/middleware/interceptors/request-id.int.test.js.map +0 -1
- package/dist/middleware/interceptors/retry.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/retry.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/retry.int.test.js +0 -534
- package/dist/middleware/interceptors/retry.int.test.js.map +0 -1
- package/dist/middleware/interceptors/timeout.int.test.d.ts +0 -5
- package/dist/middleware/interceptors/timeout.int.test.d.ts.map +0 -1
- package/dist/middleware/interceptors/timeout.int.test.js +0 -228
- package/dist/middleware/interceptors/timeout.int.test.js.map +0 -1
- package/dist/rate-limit/drivers/drivers.int.test.d.ts +0 -7
- package/dist/rate-limit/drivers/drivers.int.test.d.ts.map +0 -1
- package/dist/rate-limit/drivers/drivers.int.test.js +0 -466
- package/dist/rate-limit/drivers/drivers.int.test.js.map +0 -1
- package/dist/server/builder.int.test.d.ts +0 -7
- package/dist/server/builder.int.test.d.ts.map +0 -1
- package/dist/server/builder.int.test.js +0 -455
- package/dist/server/builder.int.test.js.map +0 -1
- package/dist/server/fs-routes/loader.int.test.d.ts +0 -5
- package/dist/server/fs-routes/loader.int.test.d.ts.map +0 -1
- package/dist/server/fs-routes/loader.int.test.js +0 -48
- package/dist/server/fs-routes/loader.int.test.js.map +0 -1
- package/dist/server/hooks.int.test.d.ts +0 -7
- package/dist/server/hooks.int.test.d.ts.map +0 -1
- package/dist/server/hooks.int.test.js +0 -564
- package/dist/server/hooks.int.test.js.map +0 -1
- package/dist/server/route-discovery.int.test.d.ts +0 -5
- package/dist/server/route-discovery.int.test.d.ts.map +0 -1
- package/dist/server/route-discovery.int.test.js +0 -49
- package/dist/server/route-discovery.int.test.js.map +0 -1
- package/dist/stream/raffel-stream.int.test.d.ts +0 -2
- package/dist/stream/raffel-stream.int.test.d.ts.map +0 -1
- package/dist/stream/raffel-stream.int.test.js +0 -265
- package/dist/stream/raffel-stream.int.test.js.map +0 -1
- package/dist/tracing/tracing.int.test.d.ts +0 -5
- package/dist/tracing/tracing.int.test.d.ts.map +0 -1
- package/dist/tracing/tracing.int.test.js +0 -412
- package/dist/tracing/tracing.int.test.js.map +0 -1
- package/dist/usd/usd.int.test.d.ts +0 -10
- package/dist/usd/usd.int.test.d.ts.map +0 -1
- package/dist/usd/usd.int.test.js +0 -719
- package/dist/usd/usd.int.test.js.map +0 -1
- package/dist/validation/multi-validator.int.test.d.ts +0 -7
- package/dist/validation/multi-validator.int.test.d.ts.map +0 -1
- package/dist/validation/multi-validator.int.test.js +0 -808
- package/dist/validation/multi-validator.int.test.js.map +0 -1
- package/dist/validation/schema.int.test.d.ts +0 -5
- package/dist/validation/schema.int.test.d.ts.map +0 -1
- package/dist/validation/schema.int.test.js +0 -340
- package/dist/validation/schema.int.test.js.map +0 -1
|
@@ -127,6 +127,14 @@ function formatError(e) {
|
|
|
127
127
|
}
|
|
128
128
|
return md;
|
|
129
129
|
}
|
|
130
|
+
function toTitleCase(input) {
|
|
131
|
+
return input
|
|
132
|
+
.replace(/[-_]/g, ' ')
|
|
133
|
+
.split(' ')
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
136
|
+
.join(' ');
|
|
137
|
+
}
|
|
130
138
|
// === Tool Handlers ===
|
|
131
139
|
export const handlers = {
|
|
132
140
|
// === Documentation Tools ===
|
|
@@ -268,9 +276,22 @@ export const handlers = {
|
|
|
268
276
|
serverChain += ` .use(createAuthMiddleware({\n`;
|
|
269
277
|
serverChain += ` strategy: createBearerStrategy({\n`;
|
|
270
278
|
serverChain += ` validate: async (token) => {\n`;
|
|
271
|
-
serverChain += `
|
|
272
|
-
serverChain += `
|
|
273
|
-
serverChain +=
|
|
279
|
+
serverChain += ` const tokenValue = String(token || '').trim()\n`;
|
|
280
|
+
serverChain += ` if (!tokenValue) return { authenticated: false }\n`;
|
|
281
|
+
serverChain += `\n`;
|
|
282
|
+
serverChain += ` const allowedTokens = new Set(\n`;
|
|
283
|
+
serverChain += ` (process.env.RAFFEL_BEARER_TOKENS || 'dev-token')\n`;
|
|
284
|
+
serverChain += ` .split(',')\n`;
|
|
285
|
+
serverChain += ` .map((t) => t.trim())\n`;
|
|
286
|
+
serverChain += ` .filter(Boolean)\n`;
|
|
287
|
+
serverChain += ` )\n`;
|
|
288
|
+
serverChain += `\n`;
|
|
289
|
+
serverChain += ` if (!allowedTokens.has(tokenValue)) return { authenticated: false }\n`;
|
|
290
|
+
serverChain += `\n`;
|
|
291
|
+
serverChain += ` return {\n`;
|
|
292
|
+
serverChain += ` authenticated: true,\n`;
|
|
293
|
+
serverChain += ` principal: { id: tokenValue, roles: ['user'] },\n`;
|
|
294
|
+
serverChain += ` }\n`;
|
|
274
295
|
serverChain += ` }\n`;
|
|
275
296
|
serverChain += ` })\n`;
|
|
276
297
|
serverChain += ` }))\n\n`;
|
|
@@ -443,10 +464,21 @@ export const handlers = {
|
|
|
443
464
|
code += ` throw new RaffelError('UNAUTHENTICATED', 'Login required')\n`;
|
|
444
465
|
code += ` }\n\n`;
|
|
445
466
|
}
|
|
446
|
-
code += ` //
|
|
467
|
+
code += ` // Replace with your business logic\n`;
|
|
447
468
|
code += ` return {\n`;
|
|
448
469
|
for (const field of outputFields) {
|
|
449
|
-
|
|
470
|
+
const defaultValue = field.type === 'number'
|
|
471
|
+
? '0'
|
|
472
|
+
: field.type === 'boolean'
|
|
473
|
+
? 'false'
|
|
474
|
+
: field.type === 'array'
|
|
475
|
+
? '[]'
|
|
476
|
+
: field.type === 'object'
|
|
477
|
+
? '{}'
|
|
478
|
+
: field.type === 'date'
|
|
479
|
+
? `new Date().toISOString()`
|
|
480
|
+
: `''`;
|
|
481
|
+
code += ` ${field.name}: ${defaultValue},\n`;
|
|
450
482
|
}
|
|
451
483
|
if (outputFields.length === 0) {
|
|
452
484
|
code += ` success: true\n`;
|
|
@@ -473,9 +505,15 @@ export const handlers = {
|
|
|
473
505
|
code += ` // ${description}\n`;
|
|
474
506
|
code += ` .handler(async function* (input, ctx) {\n`;
|
|
475
507
|
code += ` // Server streams data to client\n`;
|
|
508
|
+
code += ` let index = 0\n`;
|
|
476
509
|
code += ` while (!ctx.signal.aborted) {\n`;
|
|
477
|
-
code += `
|
|
478
|
-
code += `
|
|
510
|
+
code += ` const dataPayload = {\n`;
|
|
511
|
+
code += ` index,\n`;
|
|
512
|
+
code += ` timestamp: new Date().toISOString(),\n`;
|
|
513
|
+
code += ` value: Math.floor(Math.random() * 1000)\n`;
|
|
514
|
+
code += ` } as unknown as ${dataType}\n`;
|
|
515
|
+
code += ` index += 1\n`;
|
|
516
|
+
code += ` const data: ${dataType} = dataPayload\n`;
|
|
479
517
|
code += ` yield data\n`;
|
|
480
518
|
code += `\n`;
|
|
481
519
|
code += ` // Optional: Wait between chunks\n`;
|
|
@@ -492,8 +530,13 @@ export const handlers = {
|
|
|
492
530
|
code += ` const results = []\n`;
|
|
493
531
|
code += `\n`;
|
|
494
532
|
code += ` for await (const chunk of inputStream) {\n`;
|
|
495
|
-
code += `
|
|
496
|
-
code += `
|
|
533
|
+
code += ` const normalized =\n`;
|
|
534
|
+
code += ` typeof chunk === 'string' ? chunk : JSON.stringify(chunk)\n`;
|
|
535
|
+
code += ` const entry = {\n`;
|
|
536
|
+
code += ` payload: normalized,\n`;
|
|
537
|
+
code += ` receivedAt: new Date().toISOString()\n`;
|
|
538
|
+
code += ` }\n`;
|
|
539
|
+
code += ` results.push(entry)\n`;
|
|
497
540
|
code += ` }\n`;
|
|
498
541
|
code += `\n`;
|
|
499
542
|
code += ` // Return final result\n`;
|
|
@@ -511,8 +554,12 @@ export const handlers = {
|
|
|
511
554
|
code += ` // Process incoming stream in background\n`;
|
|
512
555
|
code += ` ;(async () => {\n`;
|
|
513
556
|
code += ` for await (const chunk of inputStream) {\n`;
|
|
514
|
-
code += `
|
|
515
|
-
code += `
|
|
557
|
+
code += ` const normalized =\n`;
|
|
558
|
+
code += ` typeof chunk === 'string' ? chunk.toUpperCase() : JSON.stringify(chunk)\n`;
|
|
559
|
+
code += ` const response = {\n`;
|
|
560
|
+
code += ` payload: normalized,\n`;
|
|
561
|
+
code += ` receivedAt: new Date().toISOString()\n`;
|
|
562
|
+
code += ` }\n`;
|
|
516
563
|
code += ` output.write(response)\n`;
|
|
517
564
|
code += ` }\n`;
|
|
518
565
|
code += ` output.end()\n`;
|
|
@@ -555,8 +602,13 @@ export const handlers = {
|
|
|
555
602
|
if (delivery === 'at-least-once') {
|
|
556
603
|
code += ` .handler(async (payload, ctx, ack) => {\n`;
|
|
557
604
|
code += ` try {\n`;
|
|
558
|
-
code += `
|
|
559
|
-
code += `
|
|
605
|
+
code += ` const handled = {\n`;
|
|
606
|
+
code += ` processed: true,\n`;
|
|
607
|
+
code += ` id: crypto.randomUUID?.() || String(Date.now()),\n`;
|
|
608
|
+
code += ` at: new Date().toISOString(),\n`;
|
|
609
|
+
code += ` payload\n`;
|
|
610
|
+
code += ` }\n`;
|
|
611
|
+
code += ` console.log('Event processed', handled)\n`;
|
|
560
612
|
code += `\n`;
|
|
561
613
|
code += ` // Acknowledge successful processing\n`;
|
|
562
614
|
code += ` ack()\n`;
|
|
@@ -569,8 +621,12 @@ export const handlers = {
|
|
|
569
621
|
}
|
|
570
622
|
else {
|
|
571
623
|
code += ` .handler(async (payload, ctx) => {\n`;
|
|
572
|
-
code += `
|
|
573
|
-
code += `
|
|
624
|
+
code += ` const handled = {\n`;
|
|
625
|
+
code += ` processed: true,\n`;
|
|
626
|
+
code += ` at: new Date().toISOString(),\n`;
|
|
627
|
+
code += ` payload\n`;
|
|
628
|
+
code += ` }\n`;
|
|
629
|
+
code += ` console.log('Fire-and-forget event handled', handled)\n`;
|
|
574
630
|
code += ` })\n`;
|
|
575
631
|
}
|
|
576
632
|
let md = `# Event: ${name}\n\n`;
|
|
@@ -599,33 +655,58 @@ export const handlers = {
|
|
|
599
655
|
switch (type) {
|
|
600
656
|
case 'auth-bearer':
|
|
601
657
|
imports += 'createAuthMiddleware, createBearerStrategy';
|
|
602
|
-
code = `.use(createAuthMiddleware({
|
|
603
|
-
strategy: createBearerStrategy({
|
|
604
|
-
validate: async (token) => {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
658
|
+
code = `.use(createAuthMiddleware({\n`;
|
|
659
|
+
code += ` strategy: createBearerStrategy({\n`;
|
|
660
|
+
code += ` validate: async (token) => {\n`;
|
|
661
|
+
code += ` const tokenValue = String(token || '').trim()\n`;
|
|
662
|
+
code += ` if (!tokenValue) {\n`;
|
|
663
|
+
code += ` return { authenticated: false }\n`;
|
|
664
|
+
code += ` }\n`;
|
|
665
|
+
code += `\n`;
|
|
666
|
+
code += ` const allowedTokens = new Set(\n`;
|
|
667
|
+
code += ` (process.env.RAFFEL_BEARER_TOKENS || 'dev-token')\n`;
|
|
668
|
+
code += ` .split(',')\n`;
|
|
669
|
+
code += ` .map((item) => item.trim())\n`;
|
|
670
|
+
code += ` .filter(Boolean)\n`;
|
|
671
|
+
code += ` )\n`;
|
|
672
|
+
code += `\n`;
|
|
673
|
+
code += ` if (!allowedTokens.has(tokenValue)) {\n`;
|
|
674
|
+
code += ` return { authenticated: false }\n`;
|
|
675
|
+
code += ` }\n`;
|
|
676
|
+
code += `\n`;
|
|
677
|
+
code += ` return {\n`;
|
|
678
|
+
code += ` authenticated: true,\n`;
|
|
679
|
+
code += ` principal: { id: tokenValue, roles: ['user'] }\n`;
|
|
680
|
+
code += ` }\n`;
|
|
681
|
+
code += ` }\n`;
|
|
682
|
+
code += ` })\n`;
|
|
683
|
+
code += `}))`;
|
|
613
684
|
break;
|
|
614
685
|
case 'auth-apikey':
|
|
615
686
|
imports += 'createAuthMiddleware, createApiKeyStrategy';
|
|
616
|
-
code = `.use(createAuthMiddleware({
|
|
617
|
-
strategy: createApiKeyStrategy({
|
|
618
|
-
validate: async (key) => {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
687
|
+
code = `.use(createAuthMiddleware({\n`;
|
|
688
|
+
code += ` strategy: createApiKeyStrategy({\n`;
|
|
689
|
+
code += ` validate: async (key) => {\n`;
|
|
690
|
+
code += ` const keyValue = String(key || '').trim()\n`;
|
|
691
|
+
code += ` if (!keyValue) {\n`;
|
|
692
|
+
code += ` return { authenticated: false }\n`;
|
|
693
|
+
code += ` }\n`;
|
|
694
|
+
code += `\n`;
|
|
695
|
+
code += ` const allowedKeys = new Set(\n`;
|
|
696
|
+
code += ` (process.env.RAFFEL_API_KEYS || '')\n`;
|
|
697
|
+
code += ` .split(',')\n`;
|
|
698
|
+
code += ` .map((item) => item.trim())\n`;
|
|
699
|
+
code += ` .filter(Boolean)\n`;
|
|
700
|
+
code += ` )\n`;
|
|
701
|
+
code += `\n`;
|
|
702
|
+
code += ` return allowedKeys.has(keyValue)\n`;
|
|
703
|
+
code += ` ? { authenticated: true, principal: { id: keyValue, source: 'api-key' } }\n`;
|
|
704
|
+
code += ` : { authenticated: false }\n`;
|
|
705
|
+
code += ` },\n`;
|
|
706
|
+
code += ` extractFrom: 'header',\n`;
|
|
707
|
+
code += ` headerName: 'X-API-Key'\n`;
|
|
708
|
+
code += ` })\n`;
|
|
709
|
+
code += `}))`;
|
|
629
710
|
break;
|
|
630
711
|
case 'rate-limit':
|
|
631
712
|
imports += 'createRateLimitInterceptor';
|
|
@@ -742,14 +823,349 @@ const tracer = createTracer({
|
|
|
742
823
|
md += '```\n';
|
|
743
824
|
return text(md);
|
|
744
825
|
},
|
|
826
|
+
raffel_project_blueprint: async (args) => {
|
|
827
|
+
const projectName = String(args.projectName || 'raffel-service');
|
|
828
|
+
const architecture = String(args.architecture || 'api');
|
|
829
|
+
const includeAuth = Boolean(args.includeAuth);
|
|
830
|
+
const includeDatabase = Boolean(args.includeDatabase);
|
|
831
|
+
const includeRealtime = Boolean(args.includeRealtime);
|
|
832
|
+
const includeObservability = Boolean(args.includeObservability);
|
|
833
|
+
const safeName = projectName.replace(/[^a-z0-9-]/gi, '-');
|
|
834
|
+
let structure = `src/\n`;
|
|
835
|
+
structure += `├─ ${safeName}.ts\n`;
|
|
836
|
+
structure += `├─ config/\n`;
|
|
837
|
+
structure += `│ ├─ env.ts\n`;
|
|
838
|
+
structure += `│ ├─ middleware.ts\n`;
|
|
839
|
+
structure += `│ └─ features.ts\n`;
|
|
840
|
+
structure += `├─ modules/\n`;
|
|
841
|
+
structure += `│ ├─ health/\n`;
|
|
842
|
+
structure += `│ ├─ users/\n`;
|
|
843
|
+
structure += `│ └─ <business-domain>/\n`;
|
|
844
|
+
structure += `├─ plugins/\n`;
|
|
845
|
+
structure += `│ ├─ middleware/\n`;
|
|
846
|
+
structure += `│ │ ├─ auth.ts\n`;
|
|
847
|
+
structure += `│ │ ├─ errors.ts\n`;
|
|
848
|
+
structure += `│ │ └─ observability.ts\n`;
|
|
849
|
+
structure += `│ └─ resolvers/\n`;
|
|
850
|
+
structure += `├─ lib/\n`;
|
|
851
|
+
structure += `│ ├─ logger.ts\n`;
|
|
852
|
+
structure += `│ ├─ errors.ts\n`;
|
|
853
|
+
structure += `│ └─ bootstrap.ts\n`;
|
|
854
|
+
structure += `├─ bootstrap/\n`;
|
|
855
|
+
structure += `│ ├─ dependencies.ts\n`;
|
|
856
|
+
structure += `│ └─ container.ts\n`;
|
|
857
|
+
if (includeRealtime) {
|
|
858
|
+
structure += `├─ services/\n`;
|
|
859
|
+
structure += `│ ├─ streaming/\n`;
|
|
860
|
+
structure += `│ │ ├─ channels.ts\n`;
|
|
861
|
+
structure += `│ │ └─ topics.ts\n`;
|
|
862
|
+
structure += `│ └─ events/\n`;
|
|
863
|
+
structure += `│ ├─ publisher.ts\n`;
|
|
864
|
+
structure += `│ └─ consumer.ts\n`;
|
|
865
|
+
}
|
|
866
|
+
structure += `└─ index.ts\n`;
|
|
867
|
+
let setupPlan = '';
|
|
868
|
+
setupPlan += `1) Create core server entry in \`src/index.ts\` with \`createServer()\`.\n`;
|
|
869
|
+
setupPlan += `2) Add environment loading from \`src/config/env.ts\`.\n`;
|
|
870
|
+
setupPlan += `3) Build shared middleware stack in \`src/plugins/middleware.ts\`.\n`;
|
|
871
|
+
setupPlan += `4) Register domain modules from \`src/modules/\`.\n`;
|
|
872
|
+
setupPlan += `5) Add integrations (database, cache, streams, observability).\n`;
|
|
873
|
+
setupPlan += `6) Add tests for one happy path and one error path per module.\n`;
|
|
874
|
+
let starterCode = `import { createServer } from 'raffel'\n`;
|
|
875
|
+
starterCode += `import { createRouterModule } from 'raffel'\n`;
|
|
876
|
+
starterCode += `import { createServerConfig } from './config/env.js'\n`;
|
|
877
|
+
starterCode += `import { errorMiddleware, loggerMiddleware } from './plugins/middleware/index.js'\n`;
|
|
878
|
+
starterCode += `import { createHealthModule } from './modules/health/index.js'\n\n`;
|
|
879
|
+
starterCode += `const config = createServerConfig()\n`;
|
|
880
|
+
starterCode += `const server = createServer({\n`;
|
|
881
|
+
starterCode += ` port: config.port,\n`;
|
|
882
|
+
starterCode += ` logLevel: config.logLevel,\n`;
|
|
883
|
+
starterCode += `})\n`;
|
|
884
|
+
starterCode += `\n`;
|
|
885
|
+
starterCode += `server.use(errorMiddleware()).use(loggerMiddleware())\n`;
|
|
886
|
+
starterCode += `server.mount('/', createHealthModule())\n`;
|
|
887
|
+
starterCode += `\n`;
|
|
888
|
+
starterCode += `await server.start()\n`;
|
|
889
|
+
starterCode += `console.log('${safeName} running at ' + 'http://localhost:' + config.port)\n`;
|
|
890
|
+
const featureChecklist = [
|
|
891
|
+
includeAuth ? '- [ ] Add bearer and API key strategy in `src/plugins/middleware/auth.ts`' : '',
|
|
892
|
+
includeDatabase
|
|
893
|
+
? '- [ ] Add Prisma schema + migration scripts'
|
|
894
|
+
: '- [ ] Start with in-memory repositories and add DB later',
|
|
895
|
+
architecture === 'realtime'
|
|
896
|
+
? '- [ ] Add WebSocket adapter and event bus in `src/services/streaming`'
|
|
897
|
+
: '',
|
|
898
|
+
architecture === 'microservice'
|
|
899
|
+
? '- [ ] Add queue-based integrations and explicit service boundaries'
|
|
900
|
+
: '',
|
|
901
|
+
includeObservability
|
|
902
|
+
? '- [ ] Add metrics/tracing and alerting hooks before first release'
|
|
903
|
+
: '- [ ] Add basic logs and health checks in phase 2',
|
|
904
|
+
].filter(Boolean);
|
|
905
|
+
let md = `# Project Blueprint: ${projectName}\n\n`;
|
|
906
|
+
md += `**Architecture:** ${architecture}\n`;
|
|
907
|
+
md += `**Goal:** Teach-first scaffold with practical Raffel defaults.\n\n`;
|
|
908
|
+
md += `## Recommended directory structure\n\n`;
|
|
909
|
+
md += '```text\n';
|
|
910
|
+
md += `${structure}\n`;
|
|
911
|
+
md += '```\n\n';
|
|
912
|
+
md += `## Migration path\n\n`;
|
|
913
|
+
md += `${setupPlan}\n\n`;
|
|
914
|
+
md += `## Starter entrypoint\n\n`;
|
|
915
|
+
md += '```typescript\n';
|
|
916
|
+
md += starterCode;
|
|
917
|
+
md += '```\n\n';
|
|
918
|
+
md += `## Feature checklist\n\n`;
|
|
919
|
+
for (const item of featureChecklist) {
|
|
920
|
+
md += `${item}\n`;
|
|
921
|
+
}
|
|
922
|
+
md += '\n';
|
|
923
|
+
md += `## Suggested next commands\n\n`;
|
|
924
|
+
md += `- npx raffel-mcp --category architecture\n`;
|
|
925
|
+
md += `- Use \`raffel_create_module\` for \`src/modules\` boundaries\n`;
|
|
926
|
+
md += `- Use \`raffel_runtime_config\` with profile \`development\` for defaults\n`;
|
|
927
|
+
return text(md);
|
|
928
|
+
},
|
|
929
|
+
raffel_api_endpoint_blueprint: async (args) => {
|
|
930
|
+
const resourceName = String(args.resourceName || 'resource');
|
|
931
|
+
const includeAuth = Boolean(args.includeAuth);
|
|
932
|
+
const includeSearch = Boolean(args.includeSearch);
|
|
933
|
+
const includeBulk = Boolean(args.includeBulk);
|
|
934
|
+
const includeStreaming = Boolean(args.includeStreaming);
|
|
935
|
+
const withEvents = Boolean(args.withEvents);
|
|
936
|
+
const safeResource = resourceName.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
937
|
+
const singular = safeResource.replace(/s$/, '');
|
|
938
|
+
const moduleName = `${singular}Module`;
|
|
939
|
+
const moduleFile = `${singular}.ts`;
|
|
940
|
+
const title = toTitleCase(safeResource);
|
|
941
|
+
let procedures = [
|
|
942
|
+
`.list(params): paginated list`,
|
|
943
|
+
`.get({ id })`,
|
|
944
|
+
`.create(input)`,
|
|
945
|
+
`.update({ id, ...input })`,
|
|
946
|
+
`.delete({ id })`,
|
|
947
|
+
];
|
|
948
|
+
if (includeSearch) {
|
|
949
|
+
procedures.push('.search({ query, filters, page, pageSize })');
|
|
950
|
+
}
|
|
951
|
+
if (includeBulk) {
|
|
952
|
+
procedures.push('.bulkCreate(input[])');
|
|
953
|
+
procedures.push('.bulkDelete(ids[])');
|
|
954
|
+
}
|
|
955
|
+
if (withEvents) {
|
|
956
|
+
procedures.push('.onCreated / .onUpdated / .onDeleted events');
|
|
957
|
+
}
|
|
958
|
+
if (includeStreaming) {
|
|
959
|
+
procedures.push('.stream("liveUpdates")');
|
|
960
|
+
}
|
|
961
|
+
let routeTree = `# ${title} Module\n\n`;
|
|
962
|
+
routeTree += `\nsrc/modules/${safeResource}/${moduleFile}\n`;
|
|
963
|
+
routeTree += `\n`;
|
|
964
|
+
routeTree += `## Procedure ideas\n\n`;
|
|
965
|
+
for (const p of procedures) {
|
|
966
|
+
routeTree += `- ${resourceName}${p}\n`;
|
|
967
|
+
}
|
|
968
|
+
routeTree += '\n';
|
|
969
|
+
let moduleCode = `import { createRouterModule, z, RaffelError } from 'raffel'\n\n`;
|
|
970
|
+
moduleCode += `const ${toTitleCase(singular).replace(/ /g, '')}Input = z.object({\n`;
|
|
971
|
+
moduleCode += ` id: z.string().optional(),\n`;
|
|
972
|
+
moduleCode += ` name: z.string(),\n`;
|
|
973
|
+
moduleCode += `})\n\n`;
|
|
974
|
+
moduleCode += `export const ${moduleName} = createRouterModule()\n`;
|
|
975
|
+
moduleCode += ` .procedure('list')\n`;
|
|
976
|
+
moduleCode += ` .input(z.object({ page: z.number().int().min(1).default(1), pageSize: z.number().int().min(1).max(100).default(20) }))\n`;
|
|
977
|
+
moduleCode += ` .handler(async () => ({ items: [], page: 1, pageSize: 20, total: 0 }))\n`;
|
|
978
|
+
moduleCode += `\n`;
|
|
979
|
+
moduleCode += ` .procedure('get')\n`;
|
|
980
|
+
moduleCode += ` .input(z.object({ id: z.string() }))\n`;
|
|
981
|
+
moduleCode += ` .handler(async ({ id }) => {\n`;
|
|
982
|
+
moduleCode += ` const item = null\n`;
|
|
983
|
+
moduleCode += ` if (!item) throw new RaffelError('NOT_FOUND', \`\${id} not found\`)\n`;
|
|
984
|
+
moduleCode += ` return item\n`;
|
|
985
|
+
moduleCode += ` })\n\n`;
|
|
986
|
+
moduleCode += ` .procedure('create')\n`;
|
|
987
|
+
moduleCode += ` .input(${toTitleCase(singular).replace(/ /g, '')}Input)\n`;
|
|
988
|
+
moduleCode += ` .handler(async (input) => ({ id: 'id', ...input }))\n`;
|
|
989
|
+
if (includeAuth) {
|
|
990
|
+
moduleCode += ` // protect with auth middleware in mount or procedure\n`;
|
|
991
|
+
}
|
|
992
|
+
let md = `# API Endpoint Blueprint: ${title}\n\n`;
|
|
993
|
+
md += `**Resource:** ${resourceName}\n`;
|
|
994
|
+
md += `**Suggested protection:** ${includeAuth ? 'Mutations protected' : 'Optional auth'}\n\n`;
|
|
995
|
+
md += `## Procedures to generate\n\n`;
|
|
996
|
+
md += `${routeTree}\n\n`;
|
|
997
|
+
md += `## Module starter\n\n`;
|
|
998
|
+
md += '```typescript\n';
|
|
999
|
+
md += moduleCode;
|
|
1000
|
+
md += '```\n\n';
|
|
1001
|
+
md += `## Suggested mount\n\n`;
|
|
1002
|
+
md += `\`\`\`typescript\n`;
|
|
1003
|
+
md += `import { ${moduleName} } from './modules/${safeResource}/${moduleFile.replace('.ts', '')}'\n`;
|
|
1004
|
+
md += `\n`;
|
|
1005
|
+
md += `server.mount('/${safeResource}', ${moduleName})\n`;
|
|
1006
|
+
md += `\`\`\`\n\n`;
|
|
1007
|
+
if (includeStreaming) {
|
|
1008
|
+
md += `## Streaming extension\n\n`;
|
|
1009
|
+
md += `Add stream in the same module or a dedicated stream module:\n\n`;
|
|
1010
|
+
md += '```typescript\n';
|
|
1011
|
+
md += `server.stream('${safeResource}.live')\n`;
|
|
1012
|
+
md += ` .handler(async function* () {\n`;
|
|
1013
|
+
md += ` while (true) {\n`;
|
|
1014
|
+
md += ` yield { type: 'heartbeat', now: new Date().toISOString() }\n`;
|
|
1015
|
+
md += ` await new Promise((resolve) => setTimeout(resolve, 1000))\n`;
|
|
1016
|
+
md += ` }\n`;
|
|
1017
|
+
md += ` })\n`;
|
|
1018
|
+
md += '```\n\n';
|
|
1019
|
+
}
|
|
1020
|
+
if (withEvents) {
|
|
1021
|
+
md += `## Events to wire\n\n`;
|
|
1022
|
+
md += `- ${safeResource}.created\n`;
|
|
1023
|
+
md += `- ${safeResource}.updated\n`;
|
|
1024
|
+
md += `- ${safeResource}.deleted\n`;
|
|
1025
|
+
md += `\n`;
|
|
1026
|
+
}
|
|
1027
|
+
md += `## Suggested tests\n\n`;
|
|
1028
|
+
md += `- List + pagination\n`;
|
|
1029
|
+
md += `- Get not found path\n`;
|
|
1030
|
+
md += `- Create validation errors\n`;
|
|
1031
|
+
md += `- Delete idempotency checks\n`;
|
|
1032
|
+
if (includeAuth) {
|
|
1033
|
+
md += `- Unauthorized on create/update/delete\n`;
|
|
1034
|
+
}
|
|
1035
|
+
return text(md);
|
|
1036
|
+
},
|
|
1037
|
+
raffel_runtime_config: async (args) => {
|
|
1038
|
+
const profile = String(args.profile || 'production');
|
|
1039
|
+
const includeSecurity = Boolean(args.includeSecurity);
|
|
1040
|
+
const includeMonitoring = Boolean(args.includeMonitoring);
|
|
1041
|
+
const includeResilience = Boolean(args.includeResilience);
|
|
1042
|
+
const baseEnv = [
|
|
1043
|
+
'# Environment',
|
|
1044
|
+
'NODE_ENV=' + profile.toUpperCase(),
|
|
1045
|
+
'PORT=3000',
|
|
1046
|
+
'LOG_LEVEL=info',
|
|
1047
|
+
'CORS_ORIGINS=*',
|
|
1048
|
+
'',
|
|
1049
|
+
'# Feature flags',
|
|
1050
|
+
'ENABLE_METRICS=' + (includeMonitoring ? 'true' : 'false'),
|
|
1051
|
+
'ENABLE_TRACING=' + (includeMonitoring ? 'true' : 'false'),
|
|
1052
|
+
includeSecurity ? 'ENABLE_RATE_LIMIT=true' : 'ENABLE_RATE_LIMIT=false',
|
|
1053
|
+
includeResilience ? 'ENABLE_RESILIENCE=true' : 'ENABLE_RESILIENCE=false',
|
|
1054
|
+
];
|
|
1055
|
+
const env = baseEnv.join('\n');
|
|
1056
|
+
const setup = [];
|
|
1057
|
+
const security = [];
|
|
1058
|
+
const observability = [];
|
|
1059
|
+
if (profile === 'production') {
|
|
1060
|
+
setup.push('set "trust proxy" equivalent for reverse proxies');
|
|
1061
|
+
setup.push('prefer structured logs + correlation IDs');
|
|
1062
|
+
if (includeSecurity) {
|
|
1063
|
+
security.push('.use(createAuthMiddleware(...))');
|
|
1064
|
+
security.push('Use RAFFEL_BEARER_TOKENS + RAFFEL_API_KEYS');
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (profile === 'staging') {
|
|
1068
|
+
setup.push('mirror production middleware, relaxed sampling');
|
|
1069
|
+
setup.push('keep debug headers disabled');
|
|
1070
|
+
}
|
|
1071
|
+
if (profile === 'development') {
|
|
1072
|
+
setup.push('enable debug logs and stack traces');
|
|
1073
|
+
setup.push('disable strict security checks where local dev needs speed');
|
|
1074
|
+
if (includeMonitoring) {
|
|
1075
|
+
setup.push('start metrics endpoint but hide from external traffic');
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (includeMonitoring) {
|
|
1079
|
+
observability.push('Metrics endpoint with Prometheus registry');
|
|
1080
|
+
observability.push('OpenTelemetry exporter for tracing');
|
|
1081
|
+
observability.push('Request/response timing at middleware boundary');
|
|
1082
|
+
}
|
|
1083
|
+
if (includeResilience) {
|
|
1084
|
+
setup.push('Use timeout interceptor globally (1-5s by endpoint)');
|
|
1085
|
+
setup.push('Enable retry + circuit breaker for downstream calls');
|
|
1086
|
+
setup.push('Standardize idempotency keys for unsafe operations');
|
|
1087
|
+
}
|
|
1088
|
+
const profileChecklist = setup.map((item) => `- [ ] ${item}`).join('\n');
|
|
1089
|
+
const securityList = security.map((item) => `- ${item}`).join('\n');
|
|
1090
|
+
const monitorList = observability.map((item) => `- ${item}`).join('\n');
|
|
1091
|
+
let md = `# Runtime configuration for ${toTitleCase(profile)} profile\n\n`;
|
|
1092
|
+
md += `## \`.env.example\`\n\n`;
|
|
1093
|
+
md += '```env\n';
|
|
1094
|
+
md += `${env}\n`;
|
|
1095
|
+
md += '```\n\n';
|
|
1096
|
+
md += `## Setup checklist\n\n${profileChecklist}\n\n`;
|
|
1097
|
+
if (includeSecurity) {
|
|
1098
|
+
md += `## Security\n\n${securityList || '- [ ] Configure auth middleware'}\n\n`;
|
|
1099
|
+
}
|
|
1100
|
+
if (includeMonitoring) {
|
|
1101
|
+
md += `## Monitoring\n\n${monitorList}\n\n`;
|
|
1102
|
+
}
|
|
1103
|
+
md += `## Recommended middleware order\n\n`;
|
|
1104
|
+
md += `1. logger\n`;
|
|
1105
|
+
if (includeSecurity) {
|
|
1106
|
+
md += `2. errorBoundary\n`;
|
|
1107
|
+
md += `3. auth (before protected procedures)\n`;
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
md += `2. errorBoundary\n`;
|
|
1111
|
+
}
|
|
1112
|
+
md += `4. validation\n`;
|
|
1113
|
+
md += `5. metrics/tracing\n`;
|
|
1114
|
+
if (includeResilience) {
|
|
1115
|
+
md += `6. timeout / retry / circuit-breaker\n`;
|
|
1116
|
+
}
|
|
1117
|
+
md += `\n`;
|
|
1118
|
+
md += `## Snippet\n\n`;
|
|
1119
|
+
md += '```typescript\n';
|
|
1120
|
+
md += `import { createServer } from 'raffel'\n`;
|
|
1121
|
+
md += `const profile = process.env.NODE_ENV || '${profile}'\n`;
|
|
1122
|
+
md += `const server = createServer({\n`;
|
|
1123
|
+
md += ` port: Number(process.env.PORT || 3000),\n`;
|
|
1124
|
+
md += ` logLevel: process.env.LOG_LEVEL || 'info',\n`;
|
|
1125
|
+
md += `})\n`;
|
|
1126
|
+
if (includeMonitoring) {
|
|
1127
|
+
md += `\n`;
|
|
1128
|
+
md += `// Metrics and tracing\n`;
|
|
1129
|
+
md += `// import { createMetricRegistry, createMetricsInterceptor } from 'raffel'\n`;
|
|
1130
|
+
md += `// createMetricRegistry\n`;
|
|
1131
|
+
md += `// server.use(createMetricsInterceptor({ registry: metrics }))\n`;
|
|
1132
|
+
}
|
|
1133
|
+
if (includeResilience) {
|
|
1134
|
+
md += `\n`;
|
|
1135
|
+
md += `// Resilience defaults for downstream calls\n`;
|
|
1136
|
+
md += `// server.use(createRateLimitInterceptor({ windowMs: 60000, maxRequests: 120 }))\n`;
|
|
1137
|
+
md += `// server.use(timeout({ ms: 30000 }))\n`;
|
|
1138
|
+
}
|
|
1139
|
+
md += `\n`;
|
|
1140
|
+
md += `await server.start()\n`;
|
|
1141
|
+
md += `console.log('Server running in ${profile}')\n`;
|
|
1142
|
+
md += '```\n';
|
|
1143
|
+
return text(md);
|
|
1144
|
+
},
|
|
745
1145
|
raffel_create_module: async (args) => {
|
|
746
1146
|
const name = String(args.name || 'myModule');
|
|
747
1147
|
const procedures = args.procedures || [];
|
|
748
1148
|
const withMiddleware = args.withMiddleware || [];
|
|
749
1149
|
let code = `// src/modules/${name}.ts\n`;
|
|
750
|
-
code += `import { createRouterModule } from 'raffel'\n`;
|
|
1150
|
+
code += `import { createRouterModule, RaffelError } from 'raffel'\n`;
|
|
1151
|
+
code += `import { z } from 'zod'\n`;
|
|
1152
|
+
const moduleClassName = `${name.charAt(0).toUpperCase() + name.slice(1)}`;
|
|
1153
|
+
const needsCreateInput = procedures.some((proc) => proc.method === 'create');
|
|
1154
|
+
const needsUpdateInput = procedures.some((proc) => proc.method === 'update');
|
|
1155
|
+
if (needsCreateInput) {
|
|
1156
|
+
code += `\nconst Create${moduleClassName}Input = z.object({\n`;
|
|
1157
|
+
code += ` data: z.record(z.unknown())\n`;
|
|
1158
|
+
code += `})\n`;
|
|
1159
|
+
}
|
|
1160
|
+
if (needsUpdateInput) {
|
|
1161
|
+
code += `\nconst Update${moduleClassName}Input = z.object({\n`;
|
|
1162
|
+
code += ` id: z.string(),\n`;
|
|
1163
|
+
code += ` data: z.record(z.unknown())\n`;
|
|
1164
|
+
code += `})\n`;
|
|
1165
|
+
}
|
|
751
1166
|
if (withMiddleware.length > 0) {
|
|
752
|
-
code +=
|
|
1167
|
+
code += `\n`;
|
|
1168
|
+
code += `// Add needed middleware imports in your codebase\n`;
|
|
753
1169
|
}
|
|
754
1170
|
code += `\nexport const ${name}Module = createRouterModule()\n`;
|
|
755
1171
|
if (withMiddleware.length > 0) {
|
|
@@ -769,7 +1185,9 @@ const tracer = createTracer({
|
|
|
769
1185
|
switch (method) {
|
|
770
1186
|
case 'list':
|
|
771
1187
|
code += ` .handler(async (input, ctx) => {\n`;
|
|
772
|
-
code += `
|
|
1188
|
+
code += ` if (!ctx.db?.${name}?.findMany) {\n`;
|
|
1189
|
+
code += ` return []\n`;
|
|
1190
|
+
code += ` }\n`;
|
|
773
1191
|
code += ` return await ctx.db.${name}.findMany()\n`;
|
|
774
1192
|
code += ` })\n\n`;
|
|
775
1193
|
break;
|
|
@@ -802,8 +1220,13 @@ const tracer = createTracer({
|
|
|
802
1220
|
break;
|
|
803
1221
|
default:
|
|
804
1222
|
code += ` .handler(async (input, ctx) => {\n`;
|
|
805
|
-
code += `
|
|
806
|
-
code += `
|
|
1223
|
+
code += ` if (!ctx.auth?.authenticated) {\n`;
|
|
1224
|
+
code += ` throw new RaffelError('UNAUTHENTICATED', 'Authentication required')\n`;
|
|
1225
|
+
code += ` }\n`;
|
|
1226
|
+
code += ` return {\n`;
|
|
1227
|
+
code += ` input,\n`;
|
|
1228
|
+
code += ` receivedAt: new Date().toISOString()\n`;
|
|
1229
|
+
code += ` }\n`;
|
|
807
1230
|
code += ` })\n\n`;
|
|
808
1231
|
}
|
|
809
1232
|
}
|
|
@@ -849,6 +1272,367 @@ const tracer = createTracer({
|
|
|
849
1272
|
}
|
|
850
1273
|
return text(md);
|
|
851
1274
|
},
|
|
1275
|
+
raffel_implement_auth: async (args) => {
|
|
1276
|
+
const method = String(args.method || '');
|
|
1277
|
+
const provider = String(args.provider || 'google');
|
|
1278
|
+
const withSession = Boolean(args.withSession ?? true);
|
|
1279
|
+
const storage = String(args.storage || 'database');
|
|
1280
|
+
if (!method)
|
|
1281
|
+
return error('method is required');
|
|
1282
|
+
switch (method) {
|
|
1283
|
+
case 'bearer-jwt': {
|
|
1284
|
+
let md = `# Implementing Bearer JWT Authentication\n\n`;
|
|
1285
|
+
md += `JWT tokens are self-contained — no DB lookup needed to validate them. ` +
|
|
1286
|
+
`The server signs tokens on login and verifies signatures on each request.\n\n`;
|
|
1287
|
+
md += `## When to use\n- REST APIs / RPC services consumed by mobile or SPA clients\n` +
|
|
1288
|
+
`- Microservices that need stateless auth\n- Anywhere you can't use cookies (e.g. CLI, mobile)\n\n`;
|
|
1289
|
+
md += `## Step 1 — Install\n\`\`\`bash\npnpm add jsonwebtoken @types/jsonwebtoken bcrypt @types/bcrypt\n\`\`\`\n\n`;
|
|
1290
|
+
md += `## Step 2 — Environment variables\n\`\`\`bash\nJWT_SECRET=at-least-32-char-random-secret # openssl rand -base64 32\nJWT_EXPIRES_IN=1h\n\`\`\`\n\n`;
|
|
1291
|
+
md += `## Step 3 — Auth module (src/modules/auth.ts)\n\`\`\`typescript\n`;
|
|
1292
|
+
md += `import { createBearerStrategy, createAuthMiddleware, requireAuth, RaffelError } from 'raffel'\n`;
|
|
1293
|
+
md += `import jwt from 'jsonwebtoken'\nimport bcrypt from 'bcrypt'\n\n`;
|
|
1294
|
+
md += `const SECRET = process.env.JWT_SECRET!\n\n`;
|
|
1295
|
+
md += `export const bearerStrategy = createBearerStrategy({\n`;
|
|
1296
|
+
md += ` verify: async (token) => {\n`;
|
|
1297
|
+
md += ` try {\n`;
|
|
1298
|
+
md += ` const payload = jwt.verify(token, SECRET) as jwt.JwtPayload\n`;
|
|
1299
|
+
md += ` return { authenticated: true, principal: payload.sub!, claims: payload }\n`;
|
|
1300
|
+
md += ` } catch {\n`;
|
|
1301
|
+
md += ` return null // expired or tampered\n`;
|
|
1302
|
+
md += ` }\n`;
|
|
1303
|
+
md += ` },\n`;
|
|
1304
|
+
md += `})\n\n`;
|
|
1305
|
+
md += `export const authMiddleware = createAuthMiddleware({\n`;
|
|
1306
|
+
md += ` strategies: [bearerStrategy],\n`;
|
|
1307
|
+
md += ` publicProcedures: ['auth.login', 'auth.register', 'health.check'],\n`;
|
|
1308
|
+
md += `})\n\n`;
|
|
1309
|
+
md += `// Procedures\n`;
|
|
1310
|
+
md += `export function registerAuthProcedures(server: ReturnType<typeof createServer>) {\n`;
|
|
1311
|
+
md += ` server.procedure('auth.login').handler(async ({ email, password }) => {\n`;
|
|
1312
|
+
md += ` const user = await db.users.findByEmail(email)\n`;
|
|
1313
|
+
md += ` if (!user || !(await bcrypt.compare(password, user.passwordHash)))\n`;
|
|
1314
|
+
md += ` throw new RaffelError('UNAUTHENTICATED', 'Invalid credentials')\n\n`;
|
|
1315
|
+
md += ` const token = jwt.sign(\n`;
|
|
1316
|
+
md += ` { sub: user.id, email: user.email, roles: user.roles },\n`;
|
|
1317
|
+
md += ` SECRET,\n`;
|
|
1318
|
+
md += ` { expiresIn: process.env.JWT_EXPIRES_IN || '1h' }\n`;
|
|
1319
|
+
md += ` )\n`;
|
|
1320
|
+
md += ` return { token, expiresIn: 3600 }\n`;
|
|
1321
|
+
md += ` })\n\n`;
|
|
1322
|
+
md += ` server.procedure('users.me').handler(async (_input, ctx) => {\n`;
|
|
1323
|
+
md += ` requireAuth(ctx)\n`;
|
|
1324
|
+
md += ` return { userId: ctx.auth!.principal, email: ctx.auth!.claims?.email }\n`;
|
|
1325
|
+
md += ` })\n`;
|
|
1326
|
+
md += `}\n\`\`\`\n\n`;
|
|
1327
|
+
md += `## Step 4 — Server setup (src/server.ts)\n\`\`\`typescript\n`;
|
|
1328
|
+
md += `import { createServer } from 'raffel'\nimport { authMiddleware, registerAuthProcedures } from './modules/auth'\n\n`;
|
|
1329
|
+
md += `const server = createServer({ port: 3000 })\nserver.use(authMiddleware)\nregisterAuthProcedures(server)\nawait server.start()\n\`\`\`\n\n`;
|
|
1330
|
+
md += `## Step 5 — Test\n\`\`\`bash\n`;
|
|
1331
|
+
md += `# Login\ncurl -X POST http://localhost:3000/auth/login \\\n -H 'Content-Type: application/json' \\\n -d '{"email":"alice@example.com","password":"secret"}'\n# → {"token":"eyJ..."}\n\n`;
|
|
1332
|
+
md += `# Use the token\ncurl http://localhost:3000/users/me \\\n -H 'Authorization: Bearer eyJ...'\n\`\`\`\n\n`;
|
|
1333
|
+
md += `## Token refresh pattern\n\`\`\`typescript\n`;
|
|
1334
|
+
md += `// Issue short-lived access token + long-lived refresh token\nconst accessToken = jwt.sign({ sub: user.id }, SECRET, { expiresIn: '15m' })\nconst refreshToken = jwt.sign({ sub: user.id, type: 'refresh' }, SECRET, { expiresIn: '7d' })\n// Store refreshToken in httpOnly cookie or DB\n\`\`\`\n`;
|
|
1335
|
+
return text(md);
|
|
1336
|
+
}
|
|
1337
|
+
case 'api-key': {
|
|
1338
|
+
let md = `# Implementing API Key Authentication\n\n`;
|
|
1339
|
+
md += `API keys are long-lived credentials for machine-to-machine (M2M) access. ` +
|
|
1340
|
+
`Never expose private API keys — they should only be used server-side or in trusted clients.\n\n`;
|
|
1341
|
+
md += `## When to use\n- SDKs / CLI tools / server-to-server integrations\n` +
|
|
1342
|
+
`- Public APIs where OAuth2 is overkill\n- Webhooks that need signed requests\n\n`;
|
|
1343
|
+
if (storage === 'env') {
|
|
1344
|
+
md += `## Setup (env-var storage — simpler, no DB)\n\n`;
|
|
1345
|
+
md += `### Step 1 — Environment variable\n\`\`\`bash\nRAFFEL_API_KEYS=key-abc123,key-xyz789 # comma-separated\n\`\`\`\n\n`;
|
|
1346
|
+
md += `### Step 2 — Auth module\n\`\`\`typescript\n`;
|
|
1347
|
+
md += `import { createApiKeyStrategy, createAuthMiddleware } from 'raffel'\n\n`;
|
|
1348
|
+
md += `const allowed = new Set(\n`;
|
|
1349
|
+
md += ` (process.env.RAFFEL_API_KEYS || '').split(',').map((k) => k.trim()).filter(Boolean)\n`;
|
|
1350
|
+
md += `)\n\n`;
|
|
1351
|
+
md += `export const apiKeyStrategy = createApiKeyStrategy({\n`;
|
|
1352
|
+
md += ` validate: async (key) =>\n`;
|
|
1353
|
+
md += ` allowed.has(key) ? { authenticated: true, principal: key } : null,\n`;
|
|
1354
|
+
md += ` extractFrom: 'header',\n`;
|
|
1355
|
+
md += ` headerName: 'X-API-Key',\n`;
|
|
1356
|
+
md += `})\n\n`;
|
|
1357
|
+
md += `export const authMiddleware = createAuthMiddleware({ strategies: [apiKeyStrategy] })\n\`\`\`\n\n`;
|
|
1358
|
+
}
|
|
1359
|
+
else {
|
|
1360
|
+
md += `## Setup (database storage)\n\n`;
|
|
1361
|
+
md += `### Step 1 — Database schema\n\`\`\`sql\nCREATE TABLE api_keys (\n`;
|
|
1362
|
+
md += ` id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n`;
|
|
1363
|
+
md += ` owner_id UUID NOT NULL REFERENCES users(id),\n`;
|
|
1364
|
+
md += ` key_hash TEXT NOT NULL UNIQUE, -- SHA-256 of the key\n`;
|
|
1365
|
+
md += ` name TEXT, -- human label\n`;
|
|
1366
|
+
md += ` scopes TEXT[] DEFAULT '{}',\n`;
|
|
1367
|
+
md += ` last_used TIMESTAMPTZ,\n`;
|
|
1368
|
+
md += ` revoked_at TIMESTAMPTZ,\n`;
|
|
1369
|
+
md += ` created_at TIMESTAMPTZ DEFAULT now()\n`;
|
|
1370
|
+
md += `);\n\`\`\`\n\n`;
|
|
1371
|
+
md += `### Step 2 — Key generation helper\n\`\`\`typescript\n`;
|
|
1372
|
+
md += `import { createHash, randomBytes } from 'crypto'\n\n`;
|
|
1373
|
+
md += `export function generateApiKey() {\n`;
|
|
1374
|
+
md += ` const raw = 'rfl_' + randomBytes(32).toString('hex')\n`;
|
|
1375
|
+
md += ` const hash = createHash('sha256').update(raw).digest('hex')\n`;
|
|
1376
|
+
md += ` return { raw, hash } // store hash, return raw once to user\n`;
|
|
1377
|
+
md += `}\n\`\`\`\n\n`;
|
|
1378
|
+
md += `### Step 3 — Auth module\n\`\`\`typescript\n`;
|
|
1379
|
+
md += `import { createApiKeyStrategy, createAuthMiddleware } from 'raffel'\nimport { createHash } from 'crypto'\n\n`;
|
|
1380
|
+
md += `export const apiKeyStrategy = createApiKeyStrategy({\n`;
|
|
1381
|
+
md += ` validate: async (key) => {\n`;
|
|
1382
|
+
md += ` const hash = createHash('sha256').update(key).digest('hex')\n`;
|
|
1383
|
+
md += ` const record = await db.apiKeys.findUnique({ where: { keyHash: hash } })\n`;
|
|
1384
|
+
md += ` if (!record || record.revokedAt) return null\n`;
|
|
1385
|
+
md += ` // optionally update last_used\n`;
|
|
1386
|
+
md += ` await db.apiKeys.update({ where: { id: record.id }, data: { lastUsed: new Date() } })\n`;
|
|
1387
|
+
md += ` return { authenticated: true, principal: record.ownerId, claims: { keyId: record.id, scopes: record.scopes } }\n`;
|
|
1388
|
+
md += ` },\n`;
|
|
1389
|
+
md += ` headerName: 'X-API-Key',\n`;
|
|
1390
|
+
md += `})\n\n`;
|
|
1391
|
+
md += `export const authMiddleware = createAuthMiddleware({ strategies: [apiKeyStrategy] })\n\`\`\`\n\n`;
|
|
1392
|
+
md += `### Step 4 — Key management procedures\n\`\`\`typescript\n`;
|
|
1393
|
+
md += `server.procedure('apiKeys.create').handler(async ({ name, scopes }, ctx) => {\n`;
|
|
1394
|
+
md += ` requireAuth(ctx)\n`;
|
|
1395
|
+
md += ` const { raw, hash } = generateApiKey()\n`;
|
|
1396
|
+
md += ` await db.apiKeys.create({ data: { ownerId: ctx.auth!.principal, keyHash: hash, name, scopes } })\n`;
|
|
1397
|
+
md += ` return { key: raw } // show ONCE, never again\n`;
|
|
1398
|
+
md += `})\n\n`;
|
|
1399
|
+
md += `server.procedure('apiKeys.revoke').handler(async ({ id }, ctx) => {\n`;
|
|
1400
|
+
md += ` requireAuth(ctx)\n`;
|
|
1401
|
+
md += ` await db.apiKeys.update({ where: { id, ownerId: ctx.auth!.principal }, data: { revokedAt: new Date() } })\n`;
|
|
1402
|
+
md += ` return { revoked: true }\n`;
|
|
1403
|
+
md += `})\n\`\`\`\n\n`;
|
|
1404
|
+
}
|
|
1405
|
+
md += `## Test\n\`\`\`bash\ncurl http://localhost:3000/items \\\n -H 'X-API-Key: rfl_abc123'\n\`\`\`\n`;
|
|
1406
|
+
return text(md);
|
|
1407
|
+
}
|
|
1408
|
+
case 'oauth2': {
|
|
1409
|
+
const prov = provider || 'google';
|
|
1410
|
+
const envPrefix = prov.toUpperCase();
|
|
1411
|
+
const provCap = prov.charAt(0).toUpperCase() + prov.slice(1);
|
|
1412
|
+
let md = `# Implementing OAuth2 Social Login (${provCap})\n\n`;
|
|
1413
|
+
md += `OAuth2 lets users log in with existing accounts (${provCap}, GitHub, etc.) without a password. ` +
|
|
1414
|
+
`You redirect → they approve → you get an access token + user info.\n\n`;
|
|
1415
|
+
md += `## When to use\n- Web apps where users are humans\n- You want "Login with ${provCap}"\n- Reduce signup friction\n\n`;
|
|
1416
|
+
md += `## Step 1 — Create OAuth2 app on ${provCap}\n`;
|
|
1417
|
+
if (prov === 'google') {
|
|
1418
|
+
md += `1. Go to https://console.cloud.google.com/apis/credentials\n`;
|
|
1419
|
+
md += `2. Create → OAuth 2.0 Client ID → Web application\n`;
|
|
1420
|
+
md += `3. Authorized redirect URI: \`http://localhost:3000/auth/callback\`\n`;
|
|
1421
|
+
md += `4. Copy Client ID and Secret\n\n`;
|
|
1422
|
+
}
|
|
1423
|
+
else if (prov === 'github') {
|
|
1424
|
+
md += `1. Go to https://github.com/settings/applications/new\n`;
|
|
1425
|
+
md += `2. Homepage URL: \`http://localhost:3000\`\n`;
|
|
1426
|
+
md += `3. Authorization callback URL: \`http://localhost:3000/auth/callback\`\n`;
|
|
1427
|
+
md += `4. Copy Client ID and Secret\n\n`;
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
md += `1. Create an OAuth2 app in ${provCap}'s developer console\n`;
|
|
1431
|
+
md += `2. Set redirect URI: \`http://localhost:3000/auth/callback\`\n`;
|
|
1432
|
+
md += `3. Copy Client ID and Secret\n\n`;
|
|
1433
|
+
}
|
|
1434
|
+
md += `## Step 2 — Environment variables\n\`\`\`bash\n`;
|
|
1435
|
+
md += `${envPrefix}_CLIENT_ID=your-client-id\n${envPrefix}_CLIENT_SECRET=your-client-secret\n`;
|
|
1436
|
+
if (withSession)
|
|
1437
|
+
md += `SESSION_SECRET=random-32-char-string\n`;
|
|
1438
|
+
md += `\`\`\`\n\n`;
|
|
1439
|
+
md += `## Step 3 — Auth module (src/modules/auth.ts)\n\`\`\`typescript\n`;
|
|
1440
|
+
md += `import { createServer, createAuthMiddleware, createOAuth2Strategy, generateState, RaffelError } from 'raffel'\n`;
|
|
1441
|
+
if (withSession)
|
|
1442
|
+
md += `import { createSessionInterceptor } from 'raffel'\n`;
|
|
1443
|
+
md += `\n`;
|
|
1444
|
+
md += `export const ${prov}Auth = createOAuth2Strategy({\n`;
|
|
1445
|
+
md += ` provider: '${prov}',\n`;
|
|
1446
|
+
md += ` clientId: process.env.${envPrefix}_CLIENT_ID!,\n`;
|
|
1447
|
+
md += ` clientSecret: process.env.${envPrefix}_CLIENT_SECRET!,\n`;
|
|
1448
|
+
md += ` redirectUri: 'http://localhost:3000/auth/callback',\n`;
|
|
1449
|
+
md += ` scopes: ${prov === 'google' ? "['openid', 'email', 'profile']" : "['read:user', 'user:email']"},\n`;
|
|
1450
|
+
md += `})\n\n`;
|
|
1451
|
+
md += `export function registerAuthProcedures(server: ReturnType<typeof createServer>) {\n`;
|
|
1452
|
+
md += ` // Step A: redirect user to ${provCap}\n`;
|
|
1453
|
+
md += ` server.procedure('auth.authorize').handler(async (_input, ctx) => {\n`;
|
|
1454
|
+
md += ` const state = generateState()\n`;
|
|
1455
|
+
if (withSession) {
|
|
1456
|
+
md += ` ctx.session.data.oauthState = state\n ctx.session.touch()\n`;
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
md += ` // store state in a short-lived cookie or cache\n`;
|
|
1460
|
+
}
|
|
1461
|
+
md += ` return { redirect: ${prov}Auth.getAuthorizationUrl({ state }) }\n`;
|
|
1462
|
+
md += ` })\n\n`;
|
|
1463
|
+
md += ` // Step B: handle redirect back from ${provCap}\n`;
|
|
1464
|
+
md += ` server.procedure('auth.callback').handler(async ({ code, state }, ctx) => {\n`;
|
|
1465
|
+
if (withSession) {
|
|
1466
|
+
md += ` if (state !== ctx.session.data.oauthState)\n`;
|
|
1467
|
+
md += ` throw new RaffelError('INVALID_ARGUMENT', 'Invalid state — possible CSRF')\n`;
|
|
1468
|
+
}
|
|
1469
|
+
md += ` const tokens = await ${prov}Auth.exchangeCode(String(code))\n`;
|
|
1470
|
+
md += ` const userInfo = await ${prov}Auth.getUserInfo(tokens.accessToken)\n`;
|
|
1471
|
+
md += ` // upsert user in your DB\n`;
|
|
1472
|
+
md += ` const user = await db.users.upsert({\n`;
|
|
1473
|
+
md += ` where: { email: userInfo.email },\n`;
|
|
1474
|
+
md += ` create: { email: userInfo.email, name: userInfo.name, provider: '${prov}' },\n`;
|
|
1475
|
+
md += ` update: { name: userInfo.name },\n`;
|
|
1476
|
+
md += ` })\n`;
|
|
1477
|
+
if (withSession) {
|
|
1478
|
+
md += ` ctx.session.data.userId = user.id\n ctx.session.data.oauthState = undefined\n ctx.session.touch()\n`;
|
|
1479
|
+
}
|
|
1480
|
+
md += ` return { ok: true, user: { id: user.id, email: user.email } }\n`;
|
|
1481
|
+
md += ` })\n\n`;
|
|
1482
|
+
md += ` server.procedure('auth.me').handler(async (_input, ctx) => {\n`;
|
|
1483
|
+
if (withSession) {
|
|
1484
|
+
md += ` if (!ctx.session.data.userId) throw new RaffelError('UNAUTHENTICATED', 'Not logged in')\n`;
|
|
1485
|
+
md += ` return db.users.findById(ctx.session.data.userId)\n`;
|
|
1486
|
+
}
|
|
1487
|
+
else {
|
|
1488
|
+
md += ` // check your session/token here\n return { ok: true }\n`;
|
|
1489
|
+
}
|
|
1490
|
+
md += ` })\n}\n\`\`\`\n\n`;
|
|
1491
|
+
md += `## Step 4 — Server setup\n\`\`\`typescript\n`;
|
|
1492
|
+
md += `import { createServer } from 'raffel'\n`;
|
|
1493
|
+
if (withSession)
|
|
1494
|
+
md += `import { createSessionInterceptor } from 'raffel'\n`;
|
|
1495
|
+
md += `import { registerAuthProcedures } from './modules/auth'\n\n`;
|
|
1496
|
+
md += `const server = createServer({ port: 3000 })\n`;
|
|
1497
|
+
if (withSession) {
|
|
1498
|
+
md += `server.use(createSessionInterceptor({\n driver: 'memory', // use redis in prod\n ttl: 3600,\n secret: process.env.SESSION_SECRET,\n}))\n`;
|
|
1499
|
+
}
|
|
1500
|
+
md += `registerAuthProcedures(server)\nawait server.start()\n\`\`\`\n\n`;
|
|
1501
|
+
md += `## Flow diagram\n\`\`\`\nClient → POST /auth/authorize → {redirect: "${prov}.com/oauth?state=..."}\nClient → browser opens ${provCap} → user approves\n${provCap} → GET /auth/callback?code=...&state=...\nServer → exchange code → get userInfo → store in session → return ok\nClient → GET /auth/me → {id, email}\n\`\`\`\n`;
|
|
1502
|
+
return text(md);
|
|
1503
|
+
}
|
|
1504
|
+
case 'oidc': {
|
|
1505
|
+
const prov = provider || 'google';
|
|
1506
|
+
const envPrefix = prov.toUpperCase();
|
|
1507
|
+
let md = `# Implementing OIDC Authentication\n\n`;
|
|
1508
|
+
md += `OIDC (OpenID Connect) extends OAuth2 with an ID token — a signed JWT containing user claims. ` +
|
|
1509
|
+
`The library auto-discovers endpoints from \`/.well-known/openid-configuration\`.\n\n`;
|
|
1510
|
+
md += `## When to use\n- Enterprise SSO (Okta, Auth0, Azure AD, Keycloak)\n` +
|
|
1511
|
+
`- When you need to validate user identity (not just "who has access")\n` +
|
|
1512
|
+
`- When the provider supports OIDC (all major ones do)\n\n`;
|
|
1513
|
+
md += `## Step 1 — Environment variables\n\`\`\`bash\n`;
|
|
1514
|
+
md += `${envPrefix}_CLIENT_ID=your-client-id\n${envPrefix}_CLIENT_SECRET=your-client-secret\n`;
|
|
1515
|
+
md += `${envPrefix}_ISSUER=https://accounts.google.com # or your IdP URL\n`;
|
|
1516
|
+
if (withSession)
|
|
1517
|
+
md += `SESSION_SECRET=random-32-char-string\n`;
|
|
1518
|
+
md += `\`\`\`\n\n`;
|
|
1519
|
+
md += `## Step 2 — Auth module\n\`\`\`typescript\n`;
|
|
1520
|
+
md += `import { createServer, createAuthMiddleware, createOIDCStrategy, generateState, RaffelError } from 'raffel'\n\n`;
|
|
1521
|
+
md += `export const oidcStrategy = createOIDCStrategy({\n`;
|
|
1522
|
+
md += ` issuer: process.env.${envPrefix}_ISSUER!,\n`;
|
|
1523
|
+
md += ` clientId: process.env.${envPrefix}_CLIENT_ID!,\n`;
|
|
1524
|
+
md += ` clientSecret: process.env.${envPrefix}_CLIENT_SECRET!,\n`;
|
|
1525
|
+
md += ` redirectUri: 'http://localhost:3000/auth/callback',\n`;
|
|
1526
|
+
md += ` // issuer auto-discovers: authorizationEndpoint, tokenEndpoint, jwksUri\n`;
|
|
1527
|
+
md += `})\n\n`;
|
|
1528
|
+
md += `export function registerAuthProcedures(server: ReturnType<typeof createServer>) {\n`;
|
|
1529
|
+
md += ` server.procedure('auth.authorize').handler(async (_input, ctx) => {\n`;
|
|
1530
|
+
md += ` const state = generateState()\n`;
|
|
1531
|
+
md += ` const nonce = generateState() // prevents replay attacks\n`;
|
|
1532
|
+
if (withSession) {
|
|
1533
|
+
md += ` ctx.session.data.oauthState = state\n`;
|
|
1534
|
+
md += ` ctx.session.data.nonce = nonce\n`;
|
|
1535
|
+
md += ` ctx.session.touch()\n`;
|
|
1536
|
+
}
|
|
1537
|
+
md += ` return { redirect: oidcStrategy.getAuthorizationUrl({ state, nonce }) }\n`;
|
|
1538
|
+
md += ` })\n\n`;
|
|
1539
|
+
md += ` server.procedure('auth.callback').handler(async ({ code, state }, ctx) => {\n`;
|
|
1540
|
+
if (withSession) {
|
|
1541
|
+
md += ` if (state !== ctx.session.data.oauthState)\n`;
|
|
1542
|
+
md += ` throw new RaffelError('INVALID_ARGUMENT', 'Invalid state — possible CSRF')\n`;
|
|
1543
|
+
}
|
|
1544
|
+
md += ` // exchangeCode validates the ID token signature, expiry, nonce, and audience\n`;
|
|
1545
|
+
md += ` const tokens = await oidcStrategy.exchangeCode(String(code))\n`;
|
|
1546
|
+
md += ` const claims = tokens.idTokenClaims // { sub, email, name, nonce, ... }\n`;
|
|
1547
|
+
if (withSession) {
|
|
1548
|
+
md += ` if (claims.nonce !== ctx.session.data.nonce)\n`;
|
|
1549
|
+
md += ` throw new RaffelError('INVALID_ARGUMENT', 'Nonce mismatch')\n`;
|
|
1550
|
+
}
|
|
1551
|
+
md += ` const user = await db.users.upsert({\n`;
|
|
1552
|
+
md += ` where: { email: claims.email },\n`;
|
|
1553
|
+
md += ` create: { email: claims.email, name: claims.name, sub: claims.sub },\n`;
|
|
1554
|
+
md += ` update: { name: claims.name },\n`;
|
|
1555
|
+
md += ` })\n`;
|
|
1556
|
+
if (withSession) {
|
|
1557
|
+
md += ` ctx.session.data.userId = user.id\n ctx.session.touch()\n`;
|
|
1558
|
+
}
|
|
1559
|
+
md += ` return { ok: true, user: { id: user.id, email: user.email } }\n`;
|
|
1560
|
+
md += ` })\n`;
|
|
1561
|
+
md += `}\n\`\`\`\n\n`;
|
|
1562
|
+
md += `## Key difference vs OAuth2\n`;
|
|
1563
|
+
md += `| | OAuth2 | OIDC |\n|---|---|---|\n`;
|
|
1564
|
+
md += `| Returns | access_token | access_token + **id_token** |\n`;
|
|
1565
|
+
md += `| User info | extra HTTP call to /userinfo | embedded in id_token (JWT) |\n`;
|
|
1566
|
+
md += `| Signature | server checks with userinfo EP | server verifies JWT against JWKS |\n`;
|
|
1567
|
+
md += `| Use when | social login with limited claims | enterprise SSO, need verified identity |\n`;
|
|
1568
|
+
return text(md);
|
|
1569
|
+
}
|
|
1570
|
+
case 'session': {
|
|
1571
|
+
let md = `# Implementing Session-Based Authentication\n\n`;
|
|
1572
|
+
md += `Sessions store auth state server-side (memory + custom stores like Redis) and give the client ` +
|
|
1573
|
+
`an opaque cookie. Nothing sensitive leaves the server.\n\n`;
|
|
1574
|
+
md += `## When to use\n- Traditional web apps with cookie-based auth\n` +
|
|
1575
|
+
`- When you need to revoke access instantly (just delete the session)\n` +
|
|
1576
|
+
`- After OAuth2/OIDC to persist the authenticated user across requests\n\n`;
|
|
1577
|
+
md += `## Step 1 — Install (for Redis in production)\n\`\`\`bash\npnpm add redis\n\`\`\`\n\n`;
|
|
1578
|
+
md += `## Step 2 — Environment variables\n\`\`\`bash\nSESSION_SECRET=random-32-char-string # openssl rand -base64 32\nREDIS_URL=redis://localhost:6379 # production only\n\`\`\`\n\n`;
|
|
1579
|
+
md += `## Step 3 — Server setup\n\`\`\`typescript\n`;
|
|
1580
|
+
md += `import { createServer, createSessionInterceptor, RaffelError } from 'raffel'\nimport bcrypt from 'bcrypt'\n\n`;
|
|
1581
|
+
md += `const server = createServer({ port: 3000 })\n\n`;
|
|
1582
|
+
md += `// Development: in-memory (lost on restart)\nserver.use(createSessionInterceptor({\n driver: 'memory',\n ttl: 3600,\n secret: process.env.SESSION_SECRET,\n cookie: { name: 'sid', httpOnly: true, sameSite: 'lax' },\n}))\n\n`;
|
|
1583
|
+
md += `// Production: switch to Redis\n// import { createRedisSessionDriver } from 'raffel'\n// import { createClient } from 'redis'\n// const redis = createClient({ url: process.env.REDIS_URL })\n// await redis.connect()\n// server.use(createSessionInterceptor({\n// driver: createRedisSessionDriver({ client: redis }),\n// ttl: 7200, rolling: true,\n// secret: process.env.SESSION_SECRET,\n// }))\n\n`;
|
|
1584
|
+
md += `server.procedure('auth.login').handler(async ({ email, password }, ctx) => {\n`;
|
|
1585
|
+
md += ` const user = await db.users.findByEmail(email)\n`;
|
|
1586
|
+
md += ` if (!user || !(await bcrypt.compare(password, user.passwordHash)))\n`;
|
|
1587
|
+
md += ` throw new RaffelError('UNAUTHENTICATED', 'Invalid credentials')\n`;
|
|
1588
|
+
md += ` await ctx.session.regenerate() // prevent session fixation\n`;
|
|
1589
|
+
md += ` ctx.session.data.userId = user.id\n`;
|
|
1590
|
+
md += ` ctx.session.data.roles = user.roles\n`;
|
|
1591
|
+
md += ` ctx.session.touch()\n`;
|
|
1592
|
+
md += ` return { ok: true }\n`;
|
|
1593
|
+
md += `})\n\n`;
|
|
1594
|
+
md += `server.procedure('auth.me').handler(async (_input, ctx) => {\n`;
|
|
1595
|
+
md += ` if (!ctx.session.data.userId) throw new RaffelError('UNAUTHENTICATED', 'Not logged in')\n`;
|
|
1596
|
+
md += ` return db.users.findById(ctx.session.data.userId)\n`;
|
|
1597
|
+
md += `})\n\n`;
|
|
1598
|
+
md += `server.procedure('auth.logout').handler(async (_input, ctx) => {\n`;
|
|
1599
|
+
md += ` await ctx.session.destroy() // removes from store + clears cookie\n`;
|
|
1600
|
+
md += ` return { ok: true }\n`;
|
|
1601
|
+
md += `})\n\n`;
|
|
1602
|
+
md += `await server.start()\n\`\`\`\n\n`;
|
|
1603
|
+
md += `## Session API reference\n`;
|
|
1604
|
+
md += `| Method | What it does |\n|--------|-------------|\n`;
|
|
1605
|
+
md += `| \`ctx.session.data.key = value\` + \`touch()\` | Store data and mark dirty |\n`;
|
|
1606
|
+
md += `| \`ctx.session.data.key\` | Read stored value |\n`;
|
|
1607
|
+
md += `| \`ctx.session.destroy()\` | Delete session + clear cookie |\n`;
|
|
1608
|
+
md += `| \`ctx.session.regenerate()\` | New session ID (call after login) |\n`;
|
|
1609
|
+
md += `| \`ctx.session.id\` | Current session ID |\n`;
|
|
1610
|
+
return text(md);
|
|
1611
|
+
}
|
|
1612
|
+
case 'combined': {
|
|
1613
|
+
let md = `# Combined Auth: JWT + API Key + Sessions\n\n`;
|
|
1614
|
+
md += `Use multiple strategies simultaneously — Raffel tries them in order and uses the first that succeeds.\n\n`;
|
|
1615
|
+
md += `## Typical setup for a full-stack API\n\`\`\`typescript\n`;
|
|
1616
|
+
md += `import {\n createServer,\n createAuthMiddleware,\n createBearerStrategy,\n createApiKeyStrategy,\n createSessionInterceptor,\n requireAuth,\n} from 'raffel'\nimport jwt from 'jsonwebtoken'\n\n`;
|
|
1617
|
+
md += `// Strategy 1: JWT Bearer (for SPA/mobile clients)\nconst bearer = createBearerStrategy({\n verify: async (token) => {\n try {\n const p = jwt.verify(token, process.env.JWT_SECRET!) as jwt.JwtPayload\n return { authenticated: true, principal: p.sub!, claims: p }\n } catch { return null }\n },\n})\n\n`;
|
|
1618
|
+
md += `// Strategy 2: API Key (for server-to-server)\nconst apiKey = createApiKeyStrategy({\n validate: async (key) => {\n const record = await db.apiKeys.findByHash(sha256(key))\n if (!record || record.revokedAt) return null\n return { authenticated: true, principal: record.ownerId, claims: { source: 'api-key' } }\n },\n})\n\n`;
|
|
1619
|
+
md += `const server = createServer({ port: 3000 })\n\n`;
|
|
1620
|
+
md += `// Sessions (for OAuth2 callback state persistence)\nserver.use(createSessionInterceptor({ driver: 'memory', ttl: 3600 }))\n\n`;
|
|
1621
|
+
md += `// Auth: try bearer → then api-key → then unauthenticated\nserver.use(createAuthMiddleware({\n strategies: [bearer, apiKey],\n publicProcedures: ['auth.login', 'auth.authorize', 'auth.callback', 'health.check'],\n}))\n\n`;
|
|
1622
|
+
md += `// Protected procedure works for both JWT users and API key clients\nserver.procedure('users.list').handler(async (_input, ctx) => {\n requireAuth(ctx)\n const source = ctx.auth!.claims?.source || 'jwt'\n return { users: [], authenticatedVia: source }\n})\n\`\`\`\n\n`;
|
|
1623
|
+
md += `## Strategy selection guide\n`;
|
|
1624
|
+
md += `| Client type | Best strategy |\n|-------------|---------------|\n`;
|
|
1625
|
+
md += `| Browser SPA | Bearer JWT or Sessions |\n`;
|
|
1626
|
+
md += `| Mobile app | Bearer JWT |\n`;
|
|
1627
|
+
md += `| Server-to-server | API Key |\n`;
|
|
1628
|
+
md += `| CLI tool | API Key or Bearer JWT |\n`;
|
|
1629
|
+
md += `| OAuth2 callback | Sessions (state storage) |\n`;
|
|
1630
|
+
return text(md);
|
|
1631
|
+
}
|
|
1632
|
+
default:
|
|
1633
|
+
return error(`Unknown method "${method}". Valid: bearer-jwt, api-key, oauth2, oidc, session, combined`);
|
|
1634
|
+
}
|
|
1635
|
+
},
|
|
852
1636
|
raffel_version: async (args) => {
|
|
853
1637
|
const checkCompatibility = Boolean(args.checkCompatibility);
|
|
854
1638
|
let md = `# Raffel Version Information\n\n`;
|