@sixthsummit/ship-api 0.0.0-stage → 4.1.1
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/CHANGELOG.md +605 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +58 -2
- package/cli/commands/add.js +537 -0
- package/cli/commands/contract.js +2 -0
- package/cli/commands/create.js +250 -0
- package/cli/commands/dev.js +227 -0
- package/cli/commands/doctor.js +286 -0
- package/cli/commands/generate.js +937 -0
- package/cli/commands/graph.js +2 -0
- package/cli/commands/migrate-orm.js +1171 -0
- package/cli/commands/update.js +37 -0
- package/cli/ship-api.js +285 -0
- package/lib/app/auth/AuthProvider.d.ts +91 -0
- package/lib/app/auth/AuthProvider.d.ts.map +1 -0
- package/lib/app/auth/AuthProvider.js +13 -0
- package/lib/app/auth/AuthProvider.js.map +1 -0
- package/lib/app/auth/adapters/firebase.d.ts +79 -0
- package/lib/app/auth/adapters/firebase.d.ts.map +1 -0
- package/lib/app/auth/adapters/firebase.js +122 -0
- package/lib/app/auth/adapters/firebase.js.map +1 -0
- package/lib/app/auth/adapters/jwtCookie.d.ts +55 -0
- package/lib/app/auth/adapters/jwtCookie.d.ts.map +1 -0
- package/lib/app/auth/adapters/jwtCookie.js +120 -0
- package/lib/app/auth/adapters/jwtCookie.js.map +1 -0
- package/lib/app/auth/index.d.ts +8 -0
- package/lib/app/auth/index.d.ts.map +1 -0
- package/lib/app/auth/index.js +7 -0
- package/lib/app/auth/index.js.map +1 -0
- package/lib/app/auth/jwt/AuthFlow.d.ts +94 -0
- package/lib/app/auth/jwt/AuthFlow.d.ts.map +1 -0
- package/lib/app/auth/jwt/AuthFlow.js +127 -0
- package/lib/app/auth/jwt/AuthFlow.js.map +1 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts +80 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts.map +1 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js +134 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js.map +1 -0
- package/lib/app/auth/jwt/JwtSigner.d.ts +123 -0
- package/lib/app/auth/jwt/JwtSigner.d.ts.map +1 -0
- package/lib/app/auth/jwt/JwtSigner.js +118 -0
- package/lib/app/auth/jwt/JwtSigner.js.map +1 -0
- package/lib/app/auth/jwt/RefreshTokenStore.d.ts +97 -0
- package/lib/app/auth/jwt/RefreshTokenStore.d.ts.map +1 -0
- package/lib/app/auth/jwt/RefreshTokenStore.js +122 -0
- package/lib/app/auth/jwt/RefreshTokenStore.js.map +1 -0
- package/lib/app/auth/permissionDefinition.d.ts +20 -0
- package/lib/app/auth/permissionDefinition.d.ts.map +1 -0
- package/lib/app/auth/permissionDefinition.js +2 -0
- package/lib/app/auth/permissionDefinition.js.map +1 -0
- package/lib/app/context/RequestContext.d.ts +59 -0
- package/lib/app/context/RequestContext.d.ts.map +1 -0
- package/lib/app/context/RequestContext.js +25 -0
- package/lib/app/context/RequestContext.js.map +1 -0
- package/lib/app/db/QueryOptions.d.ts +82 -0
- package/lib/app/db/QueryOptions.d.ts.map +1 -0
- package/lib/app/db/QueryOptions.js +37 -0
- package/lib/app/db/QueryOptions.js.map +1 -0
- package/lib/app/db/baseFields.d.ts +60 -0
- package/lib/app/db/baseFields.d.ts.map +1 -0
- package/lib/app/db/baseFields.js +54 -0
- package/lib/app/db/baseFields.js.map +1 -0
- package/lib/app/db/buildWhere.d.ts +22 -0
- package/lib/app/db/buildWhere.d.ts.map +1 -0
- package/lib/app/db/buildWhere.js +75 -0
- package/lib/app/db/buildWhere.js.map +1 -0
- package/lib/app/db/client.d.ts +111 -0
- package/lib/app/db/client.d.ts.map +1 -0
- package/lib/app/db/client.js +99 -0
- package/lib/app/db/client.js.map +1 -0
- package/lib/app/db/tenantScoped.d.ts +59 -0
- package/lib/app/db/tenantScoped.d.ts.map +1 -0
- package/lib/app/db/tenantScoped.js +54 -0
- package/lib/app/db/tenantScoped.js.map +1 -0
- package/lib/app/db/transaction.d.ts +58 -0
- package/lib/app/db/transaction.d.ts.map +1 -0
- package/lib/app/db/transaction.js +106 -0
- package/lib/app/db/transaction.js.map +1 -0
- package/lib/app/health/HealthCheck.d.ts +121 -0
- package/lib/app/health/HealthCheck.d.ts.map +1 -0
- package/lib/app/health/HealthCheck.js +151 -0
- package/lib/app/health/HealthCheck.js.map +1 -0
- package/lib/app/health/redisHealthCheck.d.ts +14 -0
- package/lib/app/health/redisHealthCheck.d.ts.map +1 -0
- package/lib/app/health/redisHealthCheck.js +21 -0
- package/lib/app/health/redisHealthCheck.js.map +1 -0
- package/lib/app/lifecycle/Bootstrapper.d.ts +92 -0
- package/lib/app/lifecycle/Bootstrapper.d.ts.map +1 -0
- package/lib/app/lifecycle/Bootstrapper.js +99 -0
- package/lib/app/lifecycle/Bootstrapper.js.map +1 -0
- package/lib/app/registry/routes.d.ts +89 -0
- package/lib/app/registry/routes.d.ts.map +1 -0
- package/lib/app/registry/routes.js +72 -0
- package/lib/app/registry/routes.js.map +1 -0
- package/lib/app/services/DrizzleEntityService.d.ts +123 -0
- package/lib/app/services/DrizzleEntityService.d.ts.map +1 -0
- package/lib/app/services/DrizzleEntityService.js +342 -0
- package/lib/app/services/DrizzleEntityService.js.map +1 -0
- package/lib/app/services/RedisProvider.d.ts +52 -0
- package/lib/app/services/RedisProvider.d.ts.map +1 -0
- package/lib/app/services/RedisProvider.js +75 -0
- package/lib/app/services/RedisProvider.js.map +1 -0
- package/lib/app/services/cache/Cache.d.ts +70 -0
- package/lib/app/services/cache/Cache.d.ts.map +1 -0
- package/lib/app/services/cache/Cache.js +18 -0
- package/lib/app/services/cache/Cache.js.map +1 -0
- package/lib/app/services/cache/InMemoryCache.d.ts +35 -0
- package/lib/app/services/cache/InMemoryCache.d.ts.map +1 -0
- package/lib/app/services/cache/InMemoryCache.js +76 -0
- package/lib/app/services/cache/InMemoryCache.js.map +1 -0
- package/lib/app/services/cache/RedisCache.d.ts +99 -0
- package/lib/app/services/cache/RedisCache.d.ts.map +1 -0
- package/lib/app/services/cache/RedisCache.js +150 -0
- package/lib/app/services/cache/RedisCache.js.map +1 -0
- package/lib/app/services/cache/cached.d.ts +30 -0
- package/lib/app/services/cache/cached.d.ts.map +1 -0
- package/lib/app/services/cache/cached.js +38 -0
- package/lib/app/services/cache/cached.js.map +1 -0
- package/lib/app/services/cache/index.d.ts +5 -0
- package/lib/app/services/cache/index.d.ts.map +1 -0
- package/lib/app/services/cache/index.js +4 -0
- package/lib/app/services/cache/index.js.map +1 -0
- package/lib/app/services/cron/defineCron.d.ts +57 -0
- package/lib/app/services/cron/defineCron.d.ts.map +1 -0
- package/lib/app/services/cron/defineCron.js +15 -0
- package/lib/app/services/cron/defineCron.js.map +1 -0
- package/lib/app/services/cron/index.d.ts +2 -0
- package/lib/app/services/cron/index.d.ts.map +1 -0
- package/lib/app/services/cron/index.js +2 -0
- package/lib/app/services/cron/index.js.map +1 -0
- package/lib/app/services/hooks/LifecycleHooks.d.ts +61 -0
- package/lib/app/services/hooks/LifecycleHooks.d.ts.map +1 -0
- package/lib/app/services/hooks/LifecycleHooks.js +85 -0
- package/lib/app/services/hooks/LifecycleHooks.js.map +1 -0
- package/lib/app/services/jobs/JobsManager.d.ts +67 -0
- package/lib/app/services/jobs/JobsManager.d.ts.map +1 -0
- package/lib/app/services/jobs/JobsManager.js +121 -0
- package/lib/app/services/jobs/JobsManager.js.map +1 -0
- package/lib/app/services/jobs/bullBoard.d.ts +72 -0
- package/lib/app/services/jobs/bullBoard.d.ts.map +1 -0
- package/lib/app/services/jobs/bullBoard.js +96 -0
- package/lib/app/services/jobs/bullBoard.js.map +1 -0
- package/lib/app/services/jobs/defineJob.d.ts +81 -0
- package/lib/app/services/jobs/defineJob.d.ts.map +1 -0
- package/lib/app/services/jobs/defineJob.js +49 -0
- package/lib/app/services/jobs/defineJob.js.map +1 -0
- package/lib/app/services/jobs/index.d.ts +4 -0
- package/lib/app/services/jobs/index.d.ts.map +1 -0
- package/lib/app/services/jobs/index.js +4 -0
- package/lib/app/services/jobs/index.js.map +1 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts +23 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts.map +1 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js +51 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js.map +1 -0
- package/lib/app/services/rateLimit/RateLimitBackend.d.ts +41 -0
- package/lib/app/services/rateLimit/RateLimitBackend.d.ts.map +1 -0
- package/lib/app/services/rateLimit/RateLimitBackend.js +19 -0
- package/lib/app/services/rateLimit/RateLimitBackend.js.map +1 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts +16 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts.map +1 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js +80 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js.map +1 -0
- package/lib/app/services/rateLimit/index.d.ts +4 -0
- package/lib/app/services/rateLimit/index.d.ts.map +1 -0
- package/lib/app/services/rateLimit/index.js +3 -0
- package/lib/app/services/rateLimit/index.js.map +1 -0
- package/lib/app/services/storage/DiskStorageAdapter.d.ts +62 -0
- package/lib/app/services/storage/DiskStorageAdapter.d.ts.map +1 -0
- package/lib/app/services/storage/DiskStorageAdapter.js +295 -0
- package/lib/app/services/storage/DiskStorageAdapter.js.map +1 -0
- package/lib/app/services/storage/GCSStorageAdapter.d.ts +44 -0
- package/lib/app/services/storage/GCSStorageAdapter.d.ts.map +1 -0
- package/lib/app/services/storage/GCSStorageAdapter.js +133 -0
- package/lib/app/services/storage/GCSStorageAdapter.js.map +1 -0
- package/lib/app/services/storage/S3StorageAdapter.d.ts +57 -0
- package/lib/app/services/storage/S3StorageAdapter.d.ts.map +1 -0
- package/lib/app/services/storage/S3StorageAdapter.js +162 -0
- package/lib/app/services/storage/S3StorageAdapter.js.map +1 -0
- package/lib/app/services/storage/StorageService.d.ts +88 -0
- package/lib/app/services/storage/StorageService.d.ts.map +1 -0
- package/lib/app/services/storage/StorageService.js +18 -0
- package/lib/app/services/storage/StorageService.js.map +1 -0
- package/lib/app/services/storage/index.d.ts +5 -0
- package/lib/app/services/storage/index.d.ts.map +1 -0
- package/lib/app/services/storage/index.js +4 -0
- package/lib/app/services/storage/index.js.map +1 -0
- package/lib/app/utils/ContextValidators.d.ts +46 -0
- package/lib/app/utils/ContextValidators.d.ts.map +1 -0
- package/lib/app/utils/ContextValidators.js +81 -0
- package/lib/app/utils/ContextValidators.js.map +1 -0
- package/lib/app/utils/defineEnv.d.ts +80 -0
- package/lib/app/utils/defineEnv.d.ts.map +1 -0
- package/lib/app/utils/defineEnv.js +95 -0
- package/lib/app/utils/defineEnv.js.map +1 -0
- package/lib/client.d.ts +139 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +98 -0
- package/lib/client.js.map +1 -0
- package/lib/contract/application.d.ts +3 -0
- package/lib/contract/application.d.ts.map +1 -0
- package/lib/contract/application.js +2 -0
- package/lib/contract/application.js.map +1 -0
- package/lib/contract/client.d.ts +3 -0
- package/lib/contract/client.d.ts.map +1 -0
- package/lib/contract/client.js +3 -0
- package/lib/contract/client.js.map +1 -0
- package/lib/contract/index.d.ts +3 -0
- package/lib/contract/index.d.ts.map +1 -0
- package/lib/contract/index.js +3 -0
- package/lib/contract/index.js.map +1 -0
- package/lib/contract/manifest.d.ts +3 -0
- package/lib/contract/manifest.d.ts.map +1 -0
- package/lib/contract/manifest.js +2 -0
- package/lib/contract/manifest.js.map +1 -0
- package/lib/contract/openapi.d.ts +3 -0
- package/lib/contract/openapi.d.ts.map +1 -0
- package/lib/contract/openapi.js +3 -0
- package/lib/contract/openapi.js.map +1 -0
- package/lib/contract/project.d.ts +3 -0
- package/lib/contract/project.d.ts.map +1 -0
- package/lib/contract/project.js +3 -0
- package/lib/contract/project.js.map +1 -0
- package/lib/contract/schema.d.ts +3 -0
- package/lib/contract/schema.d.ts.map +1 -0
- package/lib/contract/schema.js +3 -0
- package/lib/contract/schema.js.map +1 -0
- package/lib/drizzle/index.d.ts +3 -0
- package/lib/drizzle/index.d.ts.map +1 -0
- package/lib/drizzle/index.js +3 -0
- package/lib/drizzle/index.js.map +1 -0
- package/lib/hono/auth.d.ts +6 -0
- package/lib/hono/auth.d.ts.map +1 -0
- package/lib/hono/auth.js +54 -0
- package/lib/hono/auth.js.map +1 -0
- package/lib/hono/index.d.ts +4 -0
- package/lib/hono/index.d.ts.map +1 -0
- package/lib/hono/index.js +4 -0
- package/lib/hono/index.js.map +1 -0
- package/lib/http/app/createApp.d.ts +132 -0
- package/lib/http/app/createApp.d.ts.map +1 -0
- package/lib/http/app/createApp.js +215 -0
- package/lib/http/app/createApp.js.map +1 -0
- package/lib/http/app/devRoutes.d.ts +31 -0
- package/lib/http/app/devRoutes.d.ts.map +1 -0
- package/lib/http/app/devRoutes.js +145 -0
- package/lib/http/app/devRoutes.js.map +1 -0
- package/lib/http/app/errors.d.ts +45 -0
- package/lib/http/app/errors.d.ts.map +1 -0
- package/lib/http/app/errors.js +93 -0
- package/lib/http/app/errors.js.map +1 -0
- package/lib/http/app/health.d.ts +66 -0
- package/lib/http/app/health.d.ts.map +1 -0
- package/lib/http/app/health.js +104 -0
- package/lib/http/app/health.js.map +1 -0
- package/lib/http/errors/AppError.d.ts +10 -0
- package/lib/http/errors/AppError.d.ts.map +1 -0
- package/lib/http/errors/AppError.js +19 -0
- package/lib/http/errors/AppError.js.map +1 -0
- package/lib/http/errors/AuthenticationError.d.ts +8 -0
- package/lib/http/errors/AuthenticationError.d.ts.map +1 -0
- package/lib/http/errors/AuthenticationError.js +10 -0
- package/lib/http/errors/AuthenticationError.js.map +1 -0
- package/lib/http/errors/AuthorizationError.d.ts +8 -0
- package/lib/http/errors/AuthorizationError.d.ts.map +1 -0
- package/lib/http/errors/AuthorizationError.js +10 -0
- package/lib/http/errors/AuthorizationError.js.map +1 -0
- package/lib/http/errors/BadRequestError.d.ts +8 -0
- package/lib/http/errors/BadRequestError.d.ts.map +1 -0
- package/lib/http/errors/BadRequestError.js +10 -0
- package/lib/http/errors/BadRequestError.js.map +1 -0
- package/lib/http/errors/ConflictError.d.ts +8 -0
- package/lib/http/errors/ConflictError.d.ts.map +1 -0
- package/lib/http/errors/ConflictError.js +10 -0
- package/lib/http/errors/ConflictError.js.map +1 -0
- package/lib/http/errors/InternalServerError.d.ts +8 -0
- package/lib/http/errors/InternalServerError.d.ts.map +1 -0
- package/lib/http/errors/InternalServerError.js +10 -0
- package/lib/http/errors/InternalServerError.js.map +1 -0
- package/lib/http/errors/NotFoundError.d.ts +8 -0
- package/lib/http/errors/NotFoundError.d.ts.map +1 -0
- package/lib/http/errors/NotFoundError.js +10 -0
- package/lib/http/errors/NotFoundError.js.map +1 -0
- package/lib/http/errors/RateLimitError.d.ts +13 -0
- package/lib/http/errors/RateLimitError.d.ts.map +1 -0
- package/lib/http/errors/RateLimitError.js +16 -0
- package/lib/http/errors/RateLimitError.js.map +1 -0
- package/lib/http/errors/ServiceUnavailableError.d.ts +8 -0
- package/lib/http/errors/ServiceUnavailableError.d.ts.map +1 -0
- package/lib/http/errors/ServiceUnavailableError.js +10 -0
- package/lib/http/errors/ServiceUnavailableError.js.map +1 -0
- package/lib/http/errors/ValidationError.d.ts +9 -0
- package/lib/http/errors/ValidationError.d.ts.map +1 -0
- package/lib/http/errors/ValidationError.js +12 -0
- package/lib/http/errors/ValidationError.js.map +1 -0
- package/lib/http/errors/index.d.ts +11 -0
- package/lib/http/errors/index.d.ts.map +1 -0
- package/lib/http/errors/index.js +12 -0
- package/lib/http/errors/index.js.map +1 -0
- package/lib/http/middleware/apiKey.d.ts +9 -0
- package/lib/http/middleware/apiKey.d.ts.map +1 -0
- package/lib/http/middleware/apiKey.js +71 -0
- package/lib/http/middleware/apiKey.js.map +1 -0
- package/lib/http/middleware/csrf.d.ts +63 -0
- package/lib/http/middleware/csrf.d.ts.map +1 -0
- package/lib/http/middleware/csrf.js +169 -0
- package/lib/http/middleware/csrf.js.map +1 -0
- package/lib/http/middleware/httpLogger.d.ts +39 -0
- package/lib/http/middleware/httpLogger.d.ts.map +1 -0
- package/lib/http/middleware/httpLogger.js +118 -0
- package/lib/http/middleware/httpLogger.js.map +1 -0
- package/lib/http/middleware/idempotency.d.ts +48 -0
- package/lib/http/middleware/idempotency.d.ts.map +1 -0
- package/lib/http/middleware/idempotency.js +180 -0
- package/lib/http/middleware/idempotency.js.map +1 -0
- package/lib/http/middleware/rateLimit.d.ts +105 -0
- package/lib/http/middleware/rateLimit.d.ts.map +1 -0
- package/lib/http/middleware/rateLimit.js +84 -0
- package/lib/http/middleware/rateLimit.js.map +1 -0
- package/lib/http/middleware/requireAuth.d.ts +58 -0
- package/lib/http/middleware/requireAuth.d.ts.map +1 -0
- package/lib/http/middleware/requireAuth.js +63 -0
- package/lib/http/middleware/requireAuth.js.map +1 -0
- package/lib/http/middleware/requirePermission.d.ts +23 -0
- package/lib/http/middleware/requirePermission.d.ts.map +1 -0
- package/lib/http/middleware/requirePermission.js +50 -0
- package/lib/http/middleware/requirePermission.js.map +1 -0
- package/lib/http/middleware/resolveTenant.d.ts +41 -0
- package/lib/http/middleware/resolveTenant.d.ts.map +1 -0
- package/lib/http/middleware/resolveTenant.js +17 -0
- package/lib/http/middleware/resolveTenant.js.map +1 -0
- package/lib/http/streaming/sse.d.ts +31 -0
- package/lib/http/streaming/sse.d.ts.map +1 -0
- package/lib/http/streaming/sse.js +11 -0
- package/lib/http/streaming/sse.js.map +1 -0
- package/lib/http/streaming/streamLLM.d.ts +94 -0
- package/lib/http/streaming/streamLLM.d.ts.map +1 -0
- package/lib/http/streaming/streamLLM.js +94 -0
- package/lib/http/streaming/streamLLM.js.map +1 -0
- package/lib/http/utils/authCookies.d.ts +74 -0
- package/lib/http/utils/authCookies.d.ts.map +1 -0
- package/lib/http/utils/authCookies.js +102 -0
- package/lib/http/utils/authCookies.js.map +1 -0
- package/lib/http/utils/clientIp.d.ts +85 -0
- package/lib/http/utils/clientIp.d.ts.map +1 -0
- package/lib/http/utils/clientIp.js +249 -0
- package/lib/http/utils/clientIp.js.map +1 -0
- package/lib/http/utils/index.d.ts +4 -0
- package/lib/http/utils/index.d.ts.map +1 -0
- package/lib/http/utils/index.js +5 -0
- package/lib/http/utils/index.js.map +1 -0
- package/lib/http/utils/metaTags.d.ts +49 -0
- package/lib/http/utils/metaTags.d.ts.map +1 -0
- package/lib/http/utils/metaTags.js +32 -0
- package/lib/http/utils/metaTags.js.map +1 -0
- package/lib/http/utils/publicSuffix.d.ts +34 -0
- package/lib/http/utils/publicSuffix.d.ts.map +1 -0
- package/lib/http/utils/publicSuffix.js +276 -0
- package/lib/http/utils/publicSuffix.js.map +1 -0
- package/lib/http/utils/uploads.d.ts +92 -0
- package/lib/http/utils/uploads.d.ts.map +1 -0
- package/lib/http/utils/uploads.js +126 -0
- package/lib/http/utils/uploads.js.map +1 -0
- package/lib/http/utils/validate.d.ts +41 -0
- package/lib/http/utils/validate.d.ts.map +1 -0
- package/lib/http/utils/validate.js +55 -0
- package/lib/http/utils/validate.js.map +1 -0
- package/lib/http/utils/webhooks.d.ts +63 -0
- package/lib/http/utils/webhooks.d.ts.map +1 -0
- package/lib/http/utils/webhooks.js +133 -0
- package/lib/http/utils/webhooks.js.map +1 -0
- package/lib/http/ws/auth.d.ts +46 -0
- package/lib/http/ws/auth.d.ts.map +1 -0
- package/lib/http/ws/auth.js +123 -0
- package/lib/http/ws/auth.js.map +1 -0
- package/lib/index.d.ts +80 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +61 -0
- package/lib/index.js.map +1 -0
- package/lib/kernel/index.d.ts +3 -0
- package/lib/kernel/index.d.ts.map +1 -0
- package/lib/kernel/index.js +3 -0
- package/lib/kernel/index.js.map +1 -0
- package/lib/libraries/.gitkeep +0 -0
- package/lib/libraries/ai/CHANGELOG.md +17 -0
- package/lib/libraries/ai/README.md +164 -0
- package/lib/libraries/ai/adapters/AnthropicAdapter.ts +382 -0
- package/lib/libraries/ai/adapters/LLMProvider.ts +76 -0
- package/lib/libraries/ai/adapters/OpenAIAdapter.ts +466 -0
- package/lib/libraries/ai/experimental/index.ts +20 -0
- package/lib/libraries/ai/index.ts +128 -0
- package/lib/libraries/ai/library.json +19 -0
- package/lib/libraries/ai/loop/agentLoop.ts +377 -0
- package/lib/libraries/ai/prompts/definePrompt.ts +0 -0
- package/lib/libraries/ai/rag/retrieveAndAnswer.ts +177 -0
- package/lib/libraries/ai/streaming/streamLLM.ts +94 -0
- package/lib/libraries/ai/structured/completeStructured.ts +143 -0
- package/lib/libraries/ai/tokens/Tokenizer.ts +216 -0
- package/lib/libraries/ai/tools/defineTool.ts +208 -0
- package/lib/libraries/ai/types.ts +260 -0
- package/lib/libraries/chunking/ChunkingService.ts +103 -0
- package/lib/libraries/chunking/README.md +104 -0
- package/lib/libraries/chunking/docs/api-reference.md +549 -0
- package/lib/libraries/chunking/docs/best-practices.md +321 -0
- package/lib/libraries/chunking/docs/code-organization.md +538 -0
- package/lib/libraries/chunking/docs/configuration.md +418 -0
- package/lib/libraries/chunking/docs/index.md +236 -0
- package/lib/libraries/chunking/docs/quick-start.md +298 -0
- package/lib/libraries/chunking/docs/strategies.md +407 -0
- package/lib/libraries/chunking/docs/summary.md +258 -0
- package/lib/libraries/chunking/docs/troubleshooting.md +527 -0
- package/lib/libraries/chunking/index.ts +17 -0
- package/lib/libraries/chunking/library.json +16 -0
- package/lib/libraries/chunking/strategies/ChunkingStrategy.ts +13 -0
- package/lib/libraries/chunking/strategies/FixedLengthChunkingStrategy.ts +88 -0
- package/lib/libraries/chunking/strategies/SemanticChunkingStrategy.ts +389 -0
- package/lib/libraries/chunking/strategies/SlidingWindowChunkingStrategy.ts +258 -0
- package/lib/libraries/chunking/types/index.ts +63 -0
- package/lib/libraries/email/EmailService.ts +591 -0
- package/lib/libraries/email/README.md +777 -0
- package/lib/libraries/email/TemplateRegistry.ts +148 -0
- package/lib/libraries/email/TemplateRenderer.ts +273 -0
- package/lib/libraries/email/index.ts +4 -0
- package/lib/libraries/email/initializeEmailService.ts +60 -0
- package/lib/libraries/email/library.json +12 -0
- package/lib/libraries/email/types/EmailAddress.ts +4 -0
- package/lib/libraries/email/types/EmailAttachment.ts +11 -0
- package/lib/libraries/email/types/EmailTag.ts +9 -0
- package/lib/libraries/email/types/EmailTemplate.ts +124 -0
- package/lib/libraries/email/types/SendBatchEmailsOptions.ts +9 -0
- package/lib/libraries/email/types/SendBatchEmailsResult.ts +9 -0
- package/lib/libraries/email/types/SendEmailOptions.ts +33 -0
- package/lib/libraries/email/types/SendEmailResult.ts +9 -0
- package/lib/libraries/email/types/index.ts +8 -0
- package/lib/libraries/weaviate/QueryBuilder.ts +292 -0
- package/lib/libraries/weaviate/README.md +284 -0
- package/lib/libraries/weaviate/Weaviate.ts +1419 -0
- package/lib/libraries/weaviate/WeaviateConfig.ts +268 -0
- package/lib/libraries/weaviate/docs/.gitkeep +1 -0
- package/lib/libraries/weaviate/docs/API_REFERENCE.md +1067 -0
- package/lib/libraries/weaviate/docs/CONFIGURATION.md +320 -0
- package/lib/libraries/weaviate/docs/ERROR_HANDLING.md +271 -0
- package/lib/libraries/weaviate/docs/MULTI_TENANCY.md +348 -0
- package/lib/libraries/weaviate/docs/PERFORMANCE.md +602 -0
- package/lib/libraries/weaviate/docs/PLUGIN_DEVELOPMENT.md +547 -0
- package/lib/libraries/weaviate/docs/QUICK_START.md +443 -0
- package/lib/libraries/weaviate/docs/README.md +269 -0
- package/lib/libraries/weaviate/docs/SCHEMA_GUIDE.md +630 -0
- package/lib/libraries/weaviate/docs/WEAVIATE_README.md +520 -0
- package/lib/libraries/weaviate/errors/WeaviateError.ts +92 -0
- package/lib/libraries/weaviate/errors/index.ts +1 -0
- package/lib/libraries/weaviate/index.ts +35 -0
- package/lib/libraries/weaviate/library.json +26 -0
- package/lib/libraries/weaviate/plugins/index.ts +2 -0
- package/lib/libraries/weaviate/schemas/SchemaRegistry.ts +284 -0
- package/lib/libraries/weaviate/schemas/index.ts +2 -0
- package/lib/libraries/weaviate/types/BM25SearchOptions.ts +9 -0
- package/lib/libraries/weaviate/types/ExtendedWeaviateClient.ts +7 -0
- package/lib/libraries/weaviate/types/HybridSearchOptions.ts +11 -0
- package/lib/libraries/weaviate/types/MultiTenancy.ts +81 -0
- package/lib/libraries/weaviate/types/Plugin.ts +17 -0
- package/lib/libraries/weaviate/types/SchemaConfig.ts +25 -0
- package/lib/libraries/weaviate/types/SearchMethod.ts +8 -0
- package/lib/libraries/weaviate/types/SearchOptions.ts +16 -0
- package/lib/libraries/weaviate/types/SearchRequest.ts +19 -0
- package/lib/libraries/weaviate/types/SearchResult.ts +16 -0
- package/lib/libraries/weaviate/types/SearchableField.ts +87 -0
- package/lib/libraries/weaviate/types/VectorIndexConfig.ts +19 -0
- package/lib/libraries/weaviate/types/VectorSearchOptions.ts +18 -0
- package/lib/libraries/weaviate/types/VectorizerConfig.ts +8 -0
- package/lib/libraries/weaviate/types/index.ts +29 -0
- package/lib/libraries/weaviate/utils/TenantContextManager.ts +94 -0
- package/lib/modules/accounts/index.ts +47 -0
- package/lib/modules/accounts/module.json +15 -0
- package/lib/modules/accounts/permissions.ts +33 -0
- package/lib/modules/accounts/routes.ts +200 -0
- package/lib/modules/accounts/schema.ts +219 -0
- package/lib/modules/accounts/services/AccountService.ts +44 -0
- package/lib/modules/accounts/validation/schemas.ts +70 -0
- package/lib/modules/oauth/adapters/GmailOAuthAdapter.ts +35 -0
- package/lib/modules/oauth/adapters/GoogleOAuthAdapter.ts +278 -0
- package/lib/modules/oauth/adapters/MicrosoftOAuthAdapter.ts +269 -0
- package/lib/modules/oauth/adapters/MockOAuthAdapter.ts +138 -0
- package/lib/modules/oauth/adapters/OAuthAdapter.ts +211 -0
- package/lib/modules/oauth/adapters/OAuthAdapterRegistry.ts +77 -0
- package/lib/modules/oauth/adapters/OutlookOAuthAdapter.ts +38 -0
- package/lib/modules/oauth/index.ts +79 -0
- package/lib/modules/oauth/module.json +23 -0
- package/lib/modules/oauth/permissions.ts +39 -0
- package/lib/modules/oauth/routes.ts +497 -0
- package/lib/modules/oauth/schema.ts +127 -0
- package/lib/modules/oauth/services/OAuthConnectionService.ts +147 -0
- package/lib/modules/oauth/state.ts +234 -0
- package/lib/modules/oauth/stateJtiCache.ts +85 -0
- package/lib/modules/oauth/verifyIdToken.ts +178 -0
- package/lib/modules/outbox/EventEmitter.ts +106 -0
- package/lib/modules/outbox/OutboxDrainer.ts +163 -0
- package/lib/modules/outbox/handlers/EventHandler.ts +115 -0
- package/lib/modules/outbox/index.ts +43 -0
- package/lib/modules/outbox/module.json +15 -0
- package/lib/modules/outbox/permissions.ts +36 -0
- package/lib/modules/outbox/routes.ts +219 -0
- package/lib/modules/outbox/schema.ts +207 -0
- package/lib/modules/outbox/services/OutboxEventService.ts +223 -0
- package/lib/observability/logger.d.ts +30 -0
- package/lib/observability/logger.d.ts.map +1 -0
- package/lib/observability/logger.js +110 -0
- package/lib/observability/logger.js.map +1 -0
- package/lib/observability/metrics.d.ts +3 -0
- package/lib/observability/metrics.d.ts.map +1 -0
- package/lib/observability/metrics.js +3 -0
- package/lib/observability/metrics.js.map +1 -0
- package/lib/observability/otel.d.ts +3 -0
- package/lib/observability/otel.d.ts.map +1 -0
- package/lib/observability/otel.js +3 -0
- package/lib/observability/otel.js.map +1 -0
- package/lib/observability/safeErrorFields.d.ts +14 -0
- package/lib/observability/safeErrorFields.d.ts.map +1 -0
- package/lib/observability/safeErrorFields.js +14 -0
- package/lib/observability/safeErrorFields.js.map +1 -0
- package/lib/observability/tracing.d.ts +3 -0
- package/lib/observability/tracing.d.ts.map +1 -0
- package/lib/observability/tracing.js +3 -0
- package/lib/observability/tracing.js.map +1 -0
- package/lib/package.json +4 -0
- package/lib/testing.d.ts +3 -0
- package/lib/testing.d.ts.map +1 -0
- package/lib/testing.js +3 -0
- package/lib/testing.js.map +1 -0
- package/package.json +193 -3
- package/templates/default/.claude/agents/qa-brief.md +106 -0
- package/templates/default/.claude/commands/sherpa-brief.md +23 -0
- package/templates/default/.cursor/rules/api.mdc +80 -0
- package/templates/default/.cursor/rules/commits.mdc +88 -0
- package/templates/default/.cursor/rules/security.mdc +92 -0
- package/templates/default/.cursor/rules/testing.mdc +123 -0
- package/templates/default/.cursor/rules/typescript.mdc +86 -0
- package/templates/default/.env.example +79 -0
- package/templates/default/.github/workflows/sherpa-brief.yml +180 -0
- package/templates/default/AGENTS.md +166 -0
- package/templates/default/CLAUDE.md +17 -0
- package/templates/default/README.md +97 -0
- package/templates/default/_gitignore +27 -0
- package/templates/default/docker-compose.yml +83 -0
- package/templates/default/drizzle.config.ts +34 -0
- package/templates/default/eslint.config.mjs +106 -0
- package/templates/default/package.json +57 -0
- package/templates/default/ship-api.config.ts +21 -0
- package/templates/default/src/client.ts +34 -0
- package/templates/default/src/config/modules.ts +23 -0
- package/templates/default/src/db/schema.ts +26 -0
- package/templates/default/src/environment.ts +81 -0
- package/templates/default/src/index.test.ts +53 -0
- package/templates/default/src/index.ts +11 -0
- package/templates/default/src/instrumentation.ts +13 -0
- package/templates/default/src/server.ts +90 -0
- package/templates/default/tsconfig.json +24 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { getCookie, setCookie } from "hono/cookie";
|
|
3
|
+
import { AuthorizationError } from "../errors/AuthorizationError.js";
|
|
4
|
+
import { checkCookieDomainAgainstPublicSuffix } from "../utils/publicSuffix.js";
|
|
5
|
+
/**
|
|
6
|
+
* `csrf({ allowedOrigins, doubleSubmit? })` — declared CSRF stance.
|
|
7
|
+
*
|
|
8
|
+
* Default mode (`doubleSubmit: false`):
|
|
9
|
+
* - Mutating requests (POST/PUT/PATCH/DELETE) must carry an `Origin` (or
|
|
10
|
+
* `Referer`) header whose origin matches one of `allowedOrigins`.
|
|
11
|
+
* - `Sec-Fetch-Site: cross-site` is rejected outright when the header is
|
|
12
|
+
* present (modern browsers; gracefully ignored on older clients).
|
|
13
|
+
* - This protects against the typical "user follows a link / submits a
|
|
14
|
+
* form on attacker.com that hits api.yours.com" scenario when paired
|
|
15
|
+
* with `httpOnly, secure, sameSite=lax` session/JWT cookies.
|
|
16
|
+
*
|
|
17
|
+
* Hardened mode (`doubleSubmit: true`):
|
|
18
|
+
* - Issues a `csrfToken` cookie on safe (GET/HEAD/OPTIONS) requests when
|
|
19
|
+
* none is present.
|
|
20
|
+
* - On mutating requests, requires the client to echo the cookie value
|
|
21
|
+
* in the `X-CSRF-Token` header (the "double-submit cookie" pattern).
|
|
22
|
+
* - Cookie comparison uses `crypto.timingSafeEqual`.
|
|
23
|
+
* - Recommended for: third-party-iframed apps, multi-subdomain consumers
|
|
24
|
+
* where SameSite=lax doesn't fully protect.
|
|
25
|
+
*
|
|
26
|
+
* Documented in `.cursor/rules/security.mdc` (Why subprotocol over alternatives,
|
|
27
|
+
* and the CSRF stance).
|
|
28
|
+
*/
|
|
29
|
+
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
30
|
+
const COOKIE_NAME = "ship_csrf";
|
|
31
|
+
const HEADER_NAME = "x-csrf-token";
|
|
32
|
+
/**
|
|
33
|
+
* Extract a canonical origin from `Origin` (preferred) or `Referer`
|
|
34
|
+
* (fallback). F24: both paths are validated via `new URL(...)` so a
|
|
35
|
+
* malformed value (`javascript:alert()`, garbage, empty-after-trim)
|
|
36
|
+
* never makes it to the allowlist membership check. Returned origin
|
|
37
|
+
* is lowercased so the allowlist comparison is case-insensitive (per
|
|
38
|
+
* RFC 6454 §4 hosts MUST compare ASCII-case-insensitively).
|
|
39
|
+
*/
|
|
40
|
+
function originFromHeaders(c) {
|
|
41
|
+
const origin = c.req.header("origin");
|
|
42
|
+
if (origin) {
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(origin);
|
|
45
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
46
|
+
return undefined;
|
|
47
|
+
return `${url.protocol}//${url.host}`.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const referer = c.req.header("referer");
|
|
54
|
+
if (!referer)
|
|
55
|
+
return undefined;
|
|
56
|
+
try {
|
|
57
|
+
const url = new URL(referer);
|
|
58
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
59
|
+
return undefined;
|
|
60
|
+
return `${url.protocol}//${url.host}`.toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Token length in characters. `randomBytes(24).toString('base64url')`
|
|
68
|
+
* always produces exactly 32 base64url characters. F25: the
|
|
69
|
+
* double-submit verifier checks both cookie + header against this
|
|
70
|
+
* length before the constant-time compare, so an attacker who can
|
|
71
|
+
* only forge a wrong-length value (e.g. truncated by a buggy proxy,
|
|
72
|
+
* or a deliberate short-circuit attempt) gets a fast rejection
|
|
73
|
+
* without revealing per-byte timing of the compare.
|
|
74
|
+
*/
|
|
75
|
+
const TOKEN_LENGTH = 32;
|
|
76
|
+
function generateToken() {
|
|
77
|
+
return randomBytes(24).toString("base64url");
|
|
78
|
+
}
|
|
79
|
+
function safeEqual(a, b) {
|
|
80
|
+
const aBuf = Buffer.from(a);
|
|
81
|
+
const bBuf = Buffer.from(b);
|
|
82
|
+
if (aBuf.length !== bBuf.length) {
|
|
83
|
+
timingSafeEqual(aBuf, aBuf); // dummy to keep timing constant-ish
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
87
|
+
}
|
|
88
|
+
export function csrf(opts) {
|
|
89
|
+
if (!opts.allowedOrigins || opts.allowedOrigins.length === 0) {
|
|
90
|
+
throw new Error("csrf(): `allowedOrigins` must be a non-empty list");
|
|
91
|
+
}
|
|
92
|
+
// F5: defend the consumer's double-submit cookie against public-
|
|
93
|
+
// suffix domain misconfiguration at middleware-construction time.
|
|
94
|
+
const domainFailure = checkCookieDomainAgainstPublicSuffix(opts.cookie?.domain);
|
|
95
|
+
if (domainFailure) {
|
|
96
|
+
throw new Error(`csrf(): invalid cookie.domain — ${domainFailure}`);
|
|
97
|
+
}
|
|
98
|
+
// F24: lowercase the allowlist so origin comparison is
|
|
99
|
+
// case-insensitive per RFC 6454 §4 (hosts compare ASCII-case-
|
|
100
|
+
// insensitively). Without this, `https://App.Example.com` in the
|
|
101
|
+
// config wouldn't match `https://app.example.com` from a browser.
|
|
102
|
+
const allowed = new Set(opts.allowedOrigins.map((o) => o.toLowerCase()));
|
|
103
|
+
const doubleSubmit = opts.doubleSubmit ?? false;
|
|
104
|
+
const headerOnly = opts.headerOnly ?? false;
|
|
105
|
+
const cookieName = opts.cookieName ?? COOKIE_NAME;
|
|
106
|
+
const headerName = (opts.headerName ?? HEADER_NAME).toLowerCase();
|
|
107
|
+
const cookieAttrs = opts.cookie ?? {};
|
|
108
|
+
const middleware = async (c, next) => {
|
|
109
|
+
const method = c.req.method.toUpperCase();
|
|
110
|
+
const isSafe = SAFE_METHODS.has(method);
|
|
111
|
+
// F27 — header-only mode skips cookie issuance. Consumer fetches
|
|
112
|
+
// tokens out-of-band and stores in memory only. For double-submit
|
|
113
|
+
// mode without `headerOnly`, ensure a token cookie exists; we
|
|
114
|
+
// issue on safe requests so the SPA picks it up before the first
|
|
115
|
+
// mutation.
|
|
116
|
+
if (doubleSubmit && !headerOnly && isSafe && !getCookie(c, cookieName)) {
|
|
117
|
+
const token = generateToken();
|
|
118
|
+
setCookie(c, cookieName, token, {
|
|
119
|
+
httpOnly: false, // client JS must read it to echo into the header
|
|
120
|
+
secure: cookieAttrs.secure ?? true,
|
|
121
|
+
sameSite: cookieAttrs.sameSite ?? "Lax",
|
|
122
|
+
domain: cookieAttrs.domain,
|
|
123
|
+
maxAge: cookieAttrs.maxAge ?? 60 * 60 * 24 * 7,
|
|
124
|
+
path: "/",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (isSafe)
|
|
128
|
+
return next();
|
|
129
|
+
// Mutating request — apply Origin check.
|
|
130
|
+
const origin = originFromHeaders(c);
|
|
131
|
+
if (!origin || !allowed.has(origin)) {
|
|
132
|
+
throw new AuthorizationError(origin
|
|
133
|
+
? `CSRF: origin "${origin}" is not allowed`
|
|
134
|
+
: "CSRF: missing Origin / Referer header on mutating request");
|
|
135
|
+
}
|
|
136
|
+
// Modern browsers tag cross-site initiations explicitly.
|
|
137
|
+
const fetchSite = c.req.header("sec-fetch-site");
|
|
138
|
+
if (fetchSite === "cross-site") {
|
|
139
|
+
throw new AuthorizationError("CSRF: Sec-Fetch-Site is cross-site");
|
|
140
|
+
}
|
|
141
|
+
// Hardened: double-submit cookie ↔ header.
|
|
142
|
+
if (doubleSubmit) {
|
|
143
|
+
const cookie = getCookie(c, cookieName);
|
|
144
|
+
const header = c.req.header(headerName);
|
|
145
|
+
if (!cookie) {
|
|
146
|
+
throw new AuthorizationError(`CSRF: missing ${cookieName} cookie`);
|
|
147
|
+
}
|
|
148
|
+
if (!header) {
|
|
149
|
+
throw new AuthorizationError(`CSRF: missing ${headerName} header`);
|
|
150
|
+
}
|
|
151
|
+
// F25: reject wrong-length values before the constant-time
|
|
152
|
+
// compare. An empty cookie + empty header would otherwise
|
|
153
|
+
// short-circuit safeEqual (both buffers length 0, equal), and
|
|
154
|
+
// a truncated cookie matched against a truncated header would
|
|
155
|
+
// pass against an attacker who controls both sides. Tokens are
|
|
156
|
+
// exactly TOKEN_LENGTH chars by construction; any deviation is
|
|
157
|
+
// either tampering or a buggy proxy.
|
|
158
|
+
if (cookie.length !== TOKEN_LENGTH || header.length !== TOKEN_LENGTH) {
|
|
159
|
+
throw new AuthorizationError("CSRF: cookie / header has wrong length");
|
|
160
|
+
}
|
|
161
|
+
if (!safeEqual(cookie, header)) {
|
|
162
|
+
throw new AuthorizationError("CSRF: cookie / header mismatch");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return next();
|
|
166
|
+
};
|
|
167
|
+
return middleware;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=csrf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../../../src/http/middleware/csrf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
import { logger as defaultLogger } from "../../observability/logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* `httpLogger()` — request-scoped logging + RequestContext binding.
|
|
5
|
+
*
|
|
6
|
+
* On each request:
|
|
7
|
+
* 1. Resolves a request ID (incoming `X-Request-Id` header, else a fresh
|
|
8
|
+
* UUID). Mirrors the value back on the response as `X-Request-Id`.
|
|
9
|
+
* 2. Wraps `next()` inside `runWithRequestContext({ requestId, ... })` so
|
|
10
|
+
* the framework-wide pino logger auto-attaches `requestId` to every
|
|
11
|
+
* log line emitted by downstream middleware / handlers / services /
|
|
12
|
+
* DrizzleEntityService lifecycle hooks. No callsite changes.
|
|
13
|
+
* 3. Logs one structured line per request at completion:
|
|
14
|
+
* `info` for 2xx/3xx/4xx, `error` for 5xx, with `{method, path, status,
|
|
15
|
+
* durationMs}` plus the bound `requestId`.
|
|
16
|
+
*
|
|
17
|
+
* Replaces v3's `RequestIdMiddleware` + ad-hoc console logging. Designed
|
|
18
|
+
* to run *first* in the middleware chain (before auth, validation, etc.)
|
|
19
|
+
* so every log line generated downstream gets the request ID.
|
|
20
|
+
*/
|
|
21
|
+
export interface HttpLoggerOptions {
|
|
22
|
+
/** Override the framework default pino logger. */
|
|
23
|
+
logger?: typeof defaultLogger;
|
|
24
|
+
/** Header name to read for the incoming request ID. Default `"x-request-id"`. */
|
|
25
|
+
headerName?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Override request-ID generation. Default is `crypto.randomUUID()`.
|
|
28
|
+
* Useful for tests that want deterministic IDs.
|
|
29
|
+
*/
|
|
30
|
+
generateId?: () => string;
|
|
31
|
+
/**
|
|
32
|
+
* Log a "request received" line at debug level on entry. Default `false`
|
|
33
|
+
* because it doubles log volume; enable when you need to see timing of
|
|
34
|
+
* slow upstream middleware (auth, body parsing).
|
|
35
|
+
*/
|
|
36
|
+
logStart?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function httpLogger(opts?: HttpLoggerOptions): MiddlewareHandler;
|
|
39
|
+
//# sourceMappingURL=httpLogger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpLogger.d.ts","sourceRoot":"","sources":["../../../src/http/middleware/httpLogger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,EAAE,MAAM,IAAI,aAAa,EAAuB,MAAM,+BAA+B,CAAC;AAE7F;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,aAAa,CAAC;IAC9B,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAoED,wBAAgB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,iBAAiB,CAsD1E"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { runWithRequestContext } from "../../app/context/RequestContext.js";
|
|
2
|
+
import { logger as defaultLogger } from "../../observability/logger.js";
|
|
3
|
+
const DEFAULT_HEADER = "x-request-id";
|
|
4
|
+
/**
|
|
5
|
+
* Query-string keys whose VALUES are redacted in log emission. Case-
|
|
6
|
+
* insensitive match — `Token`, `TOKEN`, `token` all redact.
|
|
7
|
+
*
|
|
8
|
+
* The pino redactor (`packages/core/src/observability/logger.ts`)
|
|
9
|
+
* covers structured fields like `headers.authorization` and
|
|
10
|
+
* `body.password`, but URL query parameters arrive as a single
|
|
11
|
+
* string that the redactor can't see into. This list closes that
|
|
12
|
+
* gap defensively — consumers SHOULDN'T put secrets in query strings,
|
|
13
|
+
* but recovery / reset / signed-URL / share / OAuth flows commonly
|
|
14
|
+
* do anyway. Anything that lands here also leaks to observability
|
|
15
|
+
* stacks (Stackdriver, Datadog, Loki) by default.
|
|
16
|
+
*
|
|
17
|
+
* Documented in `.cursor/rules/security.mdc`. Keep in sync with the
|
|
18
|
+
* pino redactor's body-side list.
|
|
19
|
+
*/
|
|
20
|
+
const SENSITIVE_QUERY_KEYS = new Set([
|
|
21
|
+
"token",
|
|
22
|
+
"signature",
|
|
23
|
+
"sig",
|
|
24
|
+
"key",
|
|
25
|
+
"secret",
|
|
26
|
+
"code",
|
|
27
|
+
"state",
|
|
28
|
+
"password",
|
|
29
|
+
"passwd",
|
|
30
|
+
"pwd",
|
|
31
|
+
"apikey",
|
|
32
|
+
"api_key",
|
|
33
|
+
"auth",
|
|
34
|
+
"authorization",
|
|
35
|
+
"access_token",
|
|
36
|
+
"refresh_token",
|
|
37
|
+
"id_token",
|
|
38
|
+
"client_secret",
|
|
39
|
+
"session",
|
|
40
|
+
"sessionid",
|
|
41
|
+
"session_id",
|
|
42
|
+
]);
|
|
43
|
+
const REDACTED = "<redacted>";
|
|
44
|
+
/**
|
|
45
|
+
* Parse the URL's query string into a redacted key→value map. Returns
|
|
46
|
+
* `undefined` when there's no query, so the caller can omit the field
|
|
47
|
+
* entirely (cleaner log lines than `query: {}` everywhere).
|
|
48
|
+
*/
|
|
49
|
+
function parseQueryRedacted(rawUrl) {
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = new URL(rawUrl, "http://localhost.invalid");
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
if (parsed.search.length <= 1)
|
|
58
|
+
return undefined; // "" or "?"
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const [k, v] of parsed.searchParams) {
|
|
61
|
+
out[k] = SENSITIVE_QUERY_KEYS.has(k.toLowerCase()) ? REDACTED : v;
|
|
62
|
+
}
|
|
63
|
+
if (Object.keys(out).length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
export function httpLogger(opts = {}) {
|
|
68
|
+
const log = opts.logger ?? defaultLogger;
|
|
69
|
+
const headerName = (opts.headerName ?? DEFAULT_HEADER).toLowerCase();
|
|
70
|
+
const generateId = opts.generateId ?? (() => globalThis.crypto.randomUUID());
|
|
71
|
+
const middleware = async (c, next) => {
|
|
72
|
+
const requestId = c.req.header(headerName) ?? generateId();
|
|
73
|
+
c.header("X-Request-Id", requestId);
|
|
74
|
+
c.set("requestId", requestId);
|
|
75
|
+
const ctx = { requestId };
|
|
76
|
+
const start = performance.now();
|
|
77
|
+
if (opts.logStart) {
|
|
78
|
+
runWithRequestContext(ctx, () => {
|
|
79
|
+
log.debug({ method: c.req.method, path: c.req.path }, "http.request.start");
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
let thrown;
|
|
83
|
+
try {
|
|
84
|
+
await runWithRequestContext(ctx, async () => {
|
|
85
|
+
await next();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
thrown = err;
|
|
90
|
+
}
|
|
91
|
+
const durationMs = Math.round(performance.now() - start);
|
|
92
|
+
const status = c.res?.status ?? (thrown ? 500 : 200);
|
|
93
|
+
const query = parseQueryRedacted(c.req.url);
|
|
94
|
+
const fields = {
|
|
95
|
+
method: c.req.method,
|
|
96
|
+
path: c.req.path,
|
|
97
|
+
...(query ? { query } : {}),
|
|
98
|
+
status,
|
|
99
|
+
durationMs,
|
|
100
|
+
requestId,
|
|
101
|
+
};
|
|
102
|
+
runWithRequestContext(ctx, () => {
|
|
103
|
+
if (thrown !== undefined) {
|
|
104
|
+
log.error({ ...fields, err: thrown }, "http.request.error");
|
|
105
|
+
}
|
|
106
|
+
else if (status >= 500) {
|
|
107
|
+
log.error(fields, "http.request");
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
log.info(fields, "http.request");
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (thrown !== undefined)
|
|
114
|
+
throw thrown;
|
|
115
|
+
};
|
|
116
|
+
return middleware;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=httpLogger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpLogger.js","sourceRoot":"","sources":["../../../src/http/middleware/httpLogger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler } from "hono";
|
|
2
|
+
import type { Cache } from "../../app/services/cache/Cache.js";
|
|
3
|
+
import { logger as defaultLogger } from "../../observability/logger.js";
|
|
4
|
+
import { AppError } from "../errors/AppError.js";
|
|
5
|
+
export interface StoredIdempotencyEntry {
|
|
6
|
+
/** Hash of the canonical request — used to detect "key reused with different payload". */
|
|
7
|
+
requestHash: string;
|
|
8
|
+
status: number;
|
|
9
|
+
headers: Record<string, string>;
|
|
10
|
+
/** Response body as text. `null` if the body was too large to cache. */
|
|
11
|
+
body: string | null;
|
|
12
|
+
bodyTooLarge?: boolean;
|
|
13
|
+
/** Wall-clock ms when this entry was recorded. Surfaced in replay headers. */
|
|
14
|
+
recordedAt: number;
|
|
15
|
+
}
|
|
16
|
+
export interface IdempotencyOptions {
|
|
17
|
+
/** Cache backend. Production: RedisCache. Tests: InMemoryCache. */
|
|
18
|
+
cache: Cache;
|
|
19
|
+
/** Window for replay. Stripe defaults to 24 h. */
|
|
20
|
+
ttlSec?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Bucket namespace appended after `idempotency:`. Default `"global"`.
|
|
23
|
+
*/
|
|
24
|
+
scope?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Compose the actor portion of the cache key. Defaults to
|
|
27
|
+
* `c.get('user').id` or `'anon'`. Override for API-key-based clients.
|
|
28
|
+
*/
|
|
29
|
+
keyGenerator?: (c: Context) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Cap on the request/response body size that will be cached + replayed.
|
|
32
|
+
* Defaults to 256 KB. Larger bodies still execute the request once but
|
|
33
|
+
* the response isn't stored for replay (a flag is recorded so the second
|
|
34
|
+
* request gets a 200 with a `X-Idempotency-Replay-Lost: true` header).
|
|
35
|
+
*/
|
|
36
|
+
maxBodyBytes?: number;
|
|
37
|
+
/** Override the framework default pino logger. */
|
|
38
|
+
logger?: typeof defaultLogger;
|
|
39
|
+
}
|
|
40
|
+
export declare function idempotency(opts: IdempotencyOptions): MiddlewareHandler;
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when an Idempotency-Key is reused with a different request payload.
|
|
43
|
+
* Stripe returns 422 here; we match.
|
|
44
|
+
*/
|
|
45
|
+
export declare class IdempotencyKeyReusedError extends AppError {
|
|
46
|
+
constructor(message?: string);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../../src/http/middleware/idempotency.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAC;AAC/D,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAiDjD,MAAM,WAAW,sBAAsB;IACrC,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,KAAK,EAAE,KAAK,CAAC;IACb,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;IACtC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,aAAa,CAAC;CAC/B;AAsCD,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,CAkHvE;AAED;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,QAAQ;gBACzC,OAAO,SAAgE;CAGpF"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { logger as defaultLogger } from "../../observability/logger.js";
|
|
3
|
+
import { AppError } from "../errors/AppError.js";
|
|
4
|
+
import { ConflictError } from "../errors/ConflictError.js";
|
|
5
|
+
import { IDEMPOTENCY_META, setMetaTag } from "../utils/metaTags.js";
|
|
6
|
+
/**
|
|
7
|
+
* `idempotency({ cache, ttlSec, scope?, keyGenerator? })` — Stripe-style
|
|
8
|
+
* `Idempotency-Key` header dedup.
|
|
9
|
+
*
|
|
10
|
+
* Usage: clients send `Idempotency-Key: <uuid>` on a mutating request. If the
|
|
11
|
+
* server has already processed a request with that key (in the last `ttlSec`
|
|
12
|
+
* seconds), the previously-recorded response is replayed verbatim — same
|
|
13
|
+
* status, same headers, same body. Safely retryable network failures, in
|
|
14
|
+
* other words.
|
|
15
|
+
*
|
|
16
|
+
* Pipeline (per request bearing an Idempotency-Key):
|
|
17
|
+
*
|
|
18
|
+
* 1. Compose cache key: `idempotency:<scope>:<actor>:<idempotency-key>`.
|
|
19
|
+
* 2. Hash the canonical request: sha256(method + path + sortedQuery + body).
|
|
20
|
+
* 3. Look up the cache:
|
|
21
|
+
* - If a finalized entry exists for the same hash → replay it.
|
|
22
|
+
* - If a finalized entry exists for a *different* hash → 422
|
|
23
|
+
* (key reused with different payload — Stripe behavior).
|
|
24
|
+
* - If a "pending" sentinel exists → 409 (concurrent in-flight). The
|
|
25
|
+
* client should retry; the typical SDK auto-retries.
|
|
26
|
+
* - If absent → claim the key with a pending sentinel via
|
|
27
|
+
* `cache.setIfAbsent`, then run the handler.
|
|
28
|
+
* 4. After the handler completes (success or AppError), capture the
|
|
29
|
+
* response (status, headers, body) and replace the pending sentinel
|
|
30
|
+
* with the finalized entry.
|
|
31
|
+
*
|
|
32
|
+
* Requests without an Idempotency-Key header pass through unchanged. Apply
|
|
33
|
+
* this middleware to mutating routes that should be idempotent (POST that
|
|
34
|
+
* creates resources, payment flows, anything not safely retried otherwise).
|
|
35
|
+
*
|
|
36
|
+
* Tagged `IDEMPOTENCY_META` so `createApp` populates the route registry.
|
|
37
|
+
*
|
|
38
|
+
* Limitations (documented for adopters):
|
|
39
|
+
* - Response body is captured as text. Streamed / very-large bodies will
|
|
40
|
+
* bloat the cache; consider `maxBodyBytes` (TODO future). Default rejects
|
|
41
|
+
* bodies > 256 KB silently (the request runs once but isn't replayable).
|
|
42
|
+
* - The pending-sentinel concurrency policy returns 409 immediately
|
|
43
|
+
* instead of blocking. Stripe blocks; we don't, to keep the dependency
|
|
44
|
+
* surface narrow. Most SDKs handle 409 with retry/backoff.
|
|
45
|
+
*/
|
|
46
|
+
const HEADER = "idempotency-key";
|
|
47
|
+
const SENTINEL_PENDING = "__pending__";
|
|
48
|
+
const DEFAULT_MAX_BODY_BYTES = 256 * 1024;
|
|
49
|
+
function defaultKeyGenerator(c) {
|
|
50
|
+
const user = c.get("user");
|
|
51
|
+
return user?.id ? `user:${user.id}` : "anon";
|
|
52
|
+
}
|
|
53
|
+
function hashRequest(method, path, query, body) {
|
|
54
|
+
const h = createHash("sha256");
|
|
55
|
+
h.update(method);
|
|
56
|
+
h.update("|");
|
|
57
|
+
h.update(path);
|
|
58
|
+
h.update("|");
|
|
59
|
+
h.update(query);
|
|
60
|
+
h.update("|");
|
|
61
|
+
h.update(body);
|
|
62
|
+
return h.digest("hex");
|
|
63
|
+
}
|
|
64
|
+
function sortedQuery(c) {
|
|
65
|
+
const url = new URL(c.req.url);
|
|
66
|
+
const entries = [...url.searchParams.entries()];
|
|
67
|
+
entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
68
|
+
return entries.map(([k, v]) => `${k}=${v}`).join("&");
|
|
69
|
+
}
|
|
70
|
+
async function readRequestBody(c, max) {
|
|
71
|
+
if (c.req.method === "GET" || c.req.method === "HEAD") {
|
|
72
|
+
return { text: "", tooLarge: false };
|
|
73
|
+
}
|
|
74
|
+
// Hono caches the body — calling text() multiple times is safe.
|
|
75
|
+
const text = await c.req.text();
|
|
76
|
+
return { text, tooLarge: text.length > max };
|
|
77
|
+
}
|
|
78
|
+
export function idempotency(opts) {
|
|
79
|
+
const ttlSec = opts.ttlSec ?? 24 * 60 * 60;
|
|
80
|
+
const scope = opts.scope ?? "global";
|
|
81
|
+
const keyGen = opts.keyGenerator ?? defaultKeyGenerator;
|
|
82
|
+
const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
83
|
+
const log = opts.logger ?? defaultLogger;
|
|
84
|
+
const middleware = async (c, next) => {
|
|
85
|
+
const idemKey = c.req.header(HEADER);
|
|
86
|
+
if (!idemKey)
|
|
87
|
+
return next();
|
|
88
|
+
const actor = keyGen(c);
|
|
89
|
+
const cacheKey = `idempotency:${scope}:${actor}:${idemKey}`;
|
|
90
|
+
const { text: bodyText, tooLarge: requestTooLarge } = await readRequestBody(c, maxBodyBytes);
|
|
91
|
+
const requestHash = hashRequest(c.req.method, c.req.path, sortedQuery(c), bodyText);
|
|
92
|
+
// 1. Look up existing entry
|
|
93
|
+
const existing = await opts.cache.get(cacheKey);
|
|
94
|
+
if (existing === SENTINEL_PENDING) {
|
|
95
|
+
// Concurrent request with same key still in flight
|
|
96
|
+
throw new ConflictError("Idempotent request already in flight for this Idempotency-Key. Retry after a short delay.");
|
|
97
|
+
}
|
|
98
|
+
if (existing) {
|
|
99
|
+
const entry = existing;
|
|
100
|
+
if (entry.requestHash !== requestHash) {
|
|
101
|
+
// Different payload under the same key — Stripe returns 422
|
|
102
|
+
throw new IdempotencyKeyReusedError("Idempotency-Key was reused with a different request payload");
|
|
103
|
+
}
|
|
104
|
+
// Replay
|
|
105
|
+
log.debug({ idemKey: idemKey.slice(0, 8) }, "idempotency.replay");
|
|
106
|
+
const headers = new Headers(entry.headers);
|
|
107
|
+
headers.set("X-Idempotency-Replay", "true");
|
|
108
|
+
headers.set("X-Idempotency-Recorded-At", new Date(entry.recordedAt).toISOString());
|
|
109
|
+
if (entry.bodyTooLarge)
|
|
110
|
+
headers.set("X-Idempotency-Replay-Lost", "true");
|
|
111
|
+
return new Response(entry.body ?? "", {
|
|
112
|
+
status: entry.status,
|
|
113
|
+
headers,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// 2. Try to claim with a pending sentinel. Use a short TTL on the
|
|
117
|
+
// sentinel so a crashed handler doesn't permanently block retries.
|
|
118
|
+
const claimed = await opts.cache.setIfAbsent(cacheKey, SENTINEL_PENDING, { ttlSec: 60 });
|
|
119
|
+
if (!claimed) {
|
|
120
|
+
// Lost the race — another request just landed. Treat as concurrent.
|
|
121
|
+
throw new ConflictError("Idempotent request already in flight for this Idempotency-Key. Retry after a short delay.");
|
|
122
|
+
}
|
|
123
|
+
// 3. Execute the handler. Hono's compose intercepts thrown errors and
|
|
124
|
+
// routes them through `app.onError`, so by the time `await next()`
|
|
125
|
+
// returns, c.res is the (possibly error-) response and c.error is
|
|
126
|
+
// the original throw if any.
|
|
127
|
+
try {
|
|
128
|
+
await next();
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
// Only reached if NO error handler is registered (vanilla Hono throw).
|
|
132
|
+
// Drop the sentinel so a retry can run again.
|
|
133
|
+
await opts.cache.del(cacheKey);
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
const downstreamErr = c.error;
|
|
137
|
+
// Non-AppError downstream failure: this was an unexpected exception
|
|
138
|
+
// (probably a code defect). Don't replay it — drop the sentinel so a
|
|
139
|
+
// retry can run fresh, and let Hono surface the error response normally.
|
|
140
|
+
if (downstreamErr !== undefined && !(downstreamErr instanceof AppError)) {
|
|
141
|
+
await opts.cache.del(cacheKey);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Capture c.res for storage. AppErrors that flowed through
|
|
145
|
+
// `registerErrorHandlers` produce a real Response in c.res, so this
|
|
146
|
+
// works for both success and AppError paths — failures can be idempotent.
|
|
147
|
+
const responseClone = c.res.clone();
|
|
148
|
+
const bodyBuf = Buffer.from(await responseClone.arrayBuffer());
|
|
149
|
+
const responseTooLarge = bodyBuf.length > maxBodyBytes;
|
|
150
|
+
const headers = {};
|
|
151
|
+
c.res.headers.forEach((v, k) => {
|
|
152
|
+
headers[k] = v;
|
|
153
|
+
});
|
|
154
|
+
const entry = {
|
|
155
|
+
requestHash,
|
|
156
|
+
status: c.res.status,
|
|
157
|
+
headers,
|
|
158
|
+
body: responseTooLarge || requestTooLarge ? null : bodyBuf.toString("utf8"),
|
|
159
|
+
bodyTooLarge: responseTooLarge || requestTooLarge,
|
|
160
|
+
recordedAt: Date.now(),
|
|
161
|
+
};
|
|
162
|
+
// Replace the pending sentinel with the finalized entry.
|
|
163
|
+
await opts.cache.set(cacheKey, entry, { ttlSec });
|
|
164
|
+
// c.res already holds the handler's response.
|
|
165
|
+
return undefined;
|
|
166
|
+
};
|
|
167
|
+
const tag = { ttlSec, scope };
|
|
168
|
+
setMetaTag(middleware, IDEMPOTENCY_META, tag);
|
|
169
|
+
return middleware;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Thrown when an Idempotency-Key is reused with a different request payload.
|
|
173
|
+
* Stripe returns 422 here; we match.
|
|
174
|
+
*/
|
|
175
|
+
export class IdempotencyKeyReusedError extends AppError {
|
|
176
|
+
constructor(message = "Idempotency-Key was reused with a different request payload") {
|
|
177
|
+
super(message, 422, "IDEMPOTENCY_KEY_REUSED");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/http/middleware/idempotency.ts"],"names":[],"mappings":"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"}
|