@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,1171 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `ship-api migrate-orm <path>` — codemod CLI for v3 → v4 ORM
|
|
8
|
+
* migration. Best-effort translation from TypeORM `AbstractEntity`
|
|
9
|
+
* subclasses to Drizzle `pgTable` schemas.
|
|
10
|
+
*
|
|
11
|
+
* This first slice ships **`--analyze` mode** (default): walks the
|
|
12
|
+
* given path (single file or directory tree), parses each `.ts` file
|
|
13
|
+
* with the TypeScript compiler API, and classifies every TypeORM
|
|
14
|
+
* decorator it finds as one of:
|
|
15
|
+
*
|
|
16
|
+
* - "auto" — codemod will translate cleanly
|
|
17
|
+
* - "partial" — codemod handles the common case but the user must
|
|
18
|
+
* review (e.g., complex column types, custom transformers)
|
|
19
|
+
* - "manual" — the codemod can't translate; the user rewrites
|
|
20
|
+
* (lifecycle hooks, computed properties, table inheritance)
|
|
21
|
+
*
|
|
22
|
+
* Output is a per-file report with counts and an aggregate summary.
|
|
23
|
+
* Emits a `// TODO[ship-api migrate-orm]:` line per "manual" finding
|
|
24
|
+
* the consumer can grep for.
|
|
25
|
+
*
|
|
26
|
+
* The actual `--write` transformation lands in a follow-up commit.
|
|
27
|
+
* Shipping `--analyze` first lets v3 holdouts scope the migration
|
|
28
|
+
* before committing to the codemod.
|
|
29
|
+
*
|
|
30
|
+
* Implementation: regex-light, AST-heavy. Walks the TS AST
|
|
31
|
+
* (`ts.forEachChild`) and inspects every `Decorator` node's
|
|
32
|
+
* `expression` for the canonical TypeORM names. Classification table
|
|
33
|
+
* is the source of truth — adding a new decorator means one entry
|
|
34
|
+
* here, not three.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const SUPPORT_TABLE = {
|
|
38
|
+
// Class-level
|
|
39
|
+
Entity: { kind: "auto", note: "→ pgTable(name, {...})" },
|
|
40
|
+
Index: { kind: "partial", note: "review composite-key cases" },
|
|
41
|
+
Unique: { kind: "auto", note: "→ unique() chain on column" },
|
|
42
|
+
TableInheritance: { kind: "manual", note: "no Drizzle equivalent — flatten" },
|
|
43
|
+
// Property-level: simple columns
|
|
44
|
+
Column: { kind: "partial", note: "primitives auto; custom types manual" },
|
|
45
|
+
PrimaryColumn: { kind: "auto", note: "→ pgColumn().primaryKey()" },
|
|
46
|
+
PrimaryGeneratedColumn: {
|
|
47
|
+
kind: "auto",
|
|
48
|
+
note: '→ uuid("id").primaryKey().defaultRandom()',
|
|
49
|
+
},
|
|
50
|
+
ObjectIdColumn: { kind: "manual", note: "MongoDB-only; no Drizzle equivalent" },
|
|
51
|
+
// Property-level: audit columns (codemod emits baseFields shorthand)
|
|
52
|
+
CreateDateColumn: {
|
|
53
|
+
kind: "auto",
|
|
54
|
+
note: "→ baseFields.createdAt (note rename from createdDate)",
|
|
55
|
+
},
|
|
56
|
+
UpdateDateColumn: {
|
|
57
|
+
kind: "auto",
|
|
58
|
+
note: "→ baseFields.updatedAt (note rename from updatedDate)",
|
|
59
|
+
},
|
|
60
|
+
DeleteDateColumn: {
|
|
61
|
+
kind: "auto",
|
|
62
|
+
note: "→ baseFields.deletedAt (note rename from deletedDate)",
|
|
63
|
+
},
|
|
64
|
+
VersionColumn: { kind: "manual", note: "no Drizzle equivalent — drop or hand-roll" },
|
|
65
|
+
// Property-level: relations
|
|
66
|
+
OneToOne: { kind: "partial", note: "review @JoinColumn ownership" },
|
|
67
|
+
OneToMany: { kind: "partial", note: "→ relations() block; review eager-load" },
|
|
68
|
+
ManyToOne: { kind: "partial", note: "→ foreign-key column + relations()" },
|
|
69
|
+
ManyToMany: {
|
|
70
|
+
kind: "partial",
|
|
71
|
+
note: "→ join-table; review @JoinTable + custom names",
|
|
72
|
+
},
|
|
73
|
+
JoinColumn: { kind: "auto", note: "merged into ManyToOne fk" },
|
|
74
|
+
JoinTable: { kind: "auto", note: "merged into ManyToMany join-table" },
|
|
75
|
+
// Lifecycle (manual rewrite to LifecycleHooks)
|
|
76
|
+
BeforeInsert: { kind: "manual", note: "→ DrizzleEntityService.beforeCreate" },
|
|
77
|
+
AfterInsert: { kind: "manual", note: "→ DrizzleEntityService.afterCreate" },
|
|
78
|
+
BeforeUpdate: { kind: "manual", note: "→ DrizzleEntityService.beforeUpdate" },
|
|
79
|
+
AfterUpdate: { kind: "manual", note: "→ DrizzleEntityService.afterUpdate" },
|
|
80
|
+
BeforeRemove: { kind: "manual", note: "→ DrizzleEntityService.beforeDelete" },
|
|
81
|
+
AfterRemove: { kind: "manual", note: "→ DrizzleEntityService.afterDelete" },
|
|
82
|
+
AfterLoad: { kind: "manual", note: "no Drizzle equivalent — move to service" },
|
|
83
|
+
// Other
|
|
84
|
+
Generated: { kind: "manual", note: "review uuid/identity behavior" },
|
|
85
|
+
Tree: { kind: "manual", note: "tree-pattern; no direct Drizzle helper" },
|
|
86
|
+
ViewEntity: { kind: "manual", note: "→ pgView definition" },
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* v3 ShipAPI-specific decorators the codemod doesn't auto-rewrite (they
|
|
91
|
+
* live on controllers / methods, not entities), but knows the migration
|
|
92
|
+
* shape for. Surface as `kind: "hint"` findings — actionable v3→v4
|
|
93
|
+
* translation guidance instead of a generic "manual review".
|
|
94
|
+
*
|
|
95
|
+
* Each entry is `(slug, node) => string` returning the message body.
|
|
96
|
+
*/
|
|
97
|
+
const HINT_DECORATORS = {
|
|
98
|
+
HasPermission: (slug) =>
|
|
99
|
+
slug
|
|
100
|
+
? `in routes.ts: add \`requirePermission(${JSON.stringify(slug)})\` middleware to the route registering this controller method`
|
|
101
|
+
: "v3 controller-method auth — migrate to `requirePermission(<slug>)` middleware on the matching route in routes.ts",
|
|
102
|
+
HasContextPermission: (slug) =>
|
|
103
|
+
slug
|
|
104
|
+
? `in routes.ts: add \`requireContextPermission(${JSON.stringify(slug)})\` middleware to the route registering this controller method`
|
|
105
|
+
: "v3 context-aware auth — migrate to `requireContextPermission(<slug>)` middleware on the matching route in routes.ts",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const TS_EXTENSIONS = new Set([".ts", ".tsx"]);
|
|
109
|
+
|
|
110
|
+
export async function migrateOrmCommand(opts = {}) {
|
|
111
|
+
const target = opts.path ?? ".";
|
|
112
|
+
const writeMode = opts.write === true;
|
|
113
|
+
|
|
114
|
+
if (writeMode) {
|
|
115
|
+
return runWriteMode(target, opts);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const entries = await collectFiles(target);
|
|
119
|
+
if (entries.length === 0) {
|
|
120
|
+
console.log(chalk.yellow(`No .ts files found at ${target} — nothing to analyze.`));
|
|
121
|
+
return { files: [], summary: { auto: 0, partial: 0, manual: 0 } };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const fileReports = [];
|
|
125
|
+
for (const file of entries) {
|
|
126
|
+
fileReports.push(await analyzeFile(file));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
printAnalysisReport(fileReports, target);
|
|
130
|
+
|
|
131
|
+
const summary = aggregate(fileReports);
|
|
132
|
+
return { files: fileReports, summary };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function collectFiles(target) {
|
|
136
|
+
const stat = await fs.stat(target).catch(() => null);
|
|
137
|
+
if (!stat) return [];
|
|
138
|
+
if (stat.isFile()) {
|
|
139
|
+
return TS_EXTENSIONS.has(path.extname(target)) ? [target] : [];
|
|
140
|
+
}
|
|
141
|
+
const out = [];
|
|
142
|
+
await walkDir(target, out);
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function walkDir(dir, out) {
|
|
147
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
150
|
+
const full = path.join(dir, entry.name);
|
|
151
|
+
if (entry.isDirectory()) {
|
|
152
|
+
await walkDir(full, out);
|
|
153
|
+
} else if (TS_EXTENSIONS.has(path.extname(entry.name))) {
|
|
154
|
+
out.push(full);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function analyzeFile(filePath) {
|
|
160
|
+
const source = await fs.readFile(filePath, "utf8");
|
|
161
|
+
const sourceFile = ts.createSourceFile(
|
|
162
|
+
filePath,
|
|
163
|
+
source,
|
|
164
|
+
ts.ScriptTarget.Latest,
|
|
165
|
+
/* setParentNodes */ true,
|
|
166
|
+
ts.ScriptKind.TS,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const findings = [];
|
|
170
|
+
const visit = (node) => {
|
|
171
|
+
const decorators = ts.canHaveDecorators?.(node) ? ts.getDecorators?.(node) : undefined;
|
|
172
|
+
if (decorators) {
|
|
173
|
+
for (const d of decorators) {
|
|
174
|
+
const name = decoratorName(d);
|
|
175
|
+
if (!name) continue;
|
|
176
|
+
const support = SUPPORT_TABLE[name];
|
|
177
|
+
const line = sourceFile.getLineAndCharacterOfPosition(d.getStart(sourceFile)).line + 1;
|
|
178
|
+
if (support) {
|
|
179
|
+
findings.push({
|
|
180
|
+
decorator: name,
|
|
181
|
+
kind: support.kind,
|
|
182
|
+
note: support.note,
|
|
183
|
+
line,
|
|
184
|
+
target: nodeKindLabel(node),
|
|
185
|
+
});
|
|
186
|
+
} else if (HINT_DECORATORS[name]) {
|
|
187
|
+
// v3 ShipAPI-specific decorators the codemod doesn't auto-fix
|
|
188
|
+
// but knows the migration shape for. Surface an actionable
|
|
189
|
+
// hint with the slug + target method baked in, instead of a
|
|
190
|
+
// generic "manual review".
|
|
191
|
+
const slug = extractFirstStringArg(d);
|
|
192
|
+
findings.push({
|
|
193
|
+
decorator: name,
|
|
194
|
+
kind: "hint",
|
|
195
|
+
note: HINT_DECORATORS[name](slug, node),
|
|
196
|
+
line,
|
|
197
|
+
target: nodeKindLabel(node),
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
findings.push({
|
|
201
|
+
decorator: name,
|
|
202
|
+
kind: "unknown",
|
|
203
|
+
note: "decorator not in support table — manual review",
|
|
204
|
+
line,
|
|
205
|
+
target: nodeKindLabel(node),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
ts.forEachChild(node, visit);
|
|
211
|
+
};
|
|
212
|
+
visit(sourceFile);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
file: filePath,
|
|
216
|
+
findings,
|
|
217
|
+
counts: countByKind(findings),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function decoratorName(decorator) {
|
|
222
|
+
const expr = decorator.expression;
|
|
223
|
+
if (ts.isCallExpression(expr)) {
|
|
224
|
+
if (ts.isIdentifier(expr.expression)) return expr.expression.text;
|
|
225
|
+
if (ts.isPropertyAccessExpression(expr.expression)) return expr.expression.name.text;
|
|
226
|
+
} else if (ts.isIdentifier(expr)) {
|
|
227
|
+
return expr.text;
|
|
228
|
+
} else if (ts.isPropertyAccessExpression(expr)) {
|
|
229
|
+
return expr.name.text;
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function nodeKindLabel(node) {
|
|
235
|
+
if (ts.isClassDeclaration(node)) return `class ${node.name?.text ?? "?"}`;
|
|
236
|
+
if (ts.isPropertyDeclaration(node)) return `property ${node.name.getText()}`;
|
|
237
|
+
if (ts.isMethodDeclaration(node)) return `method ${node.name.getText()}`;
|
|
238
|
+
if (ts.isParameter(node)) return `parameter ${node.name.getText?.() ?? "?"}`;
|
|
239
|
+
return ts.SyntaxKind[node.kind];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function countByKind(findings) {
|
|
243
|
+
const counts = { auto: 0, partial: 0, manual: 0, unknown: 0, hint: 0 };
|
|
244
|
+
for (const f of findings) counts[f.kind] = (counts[f.kind] ?? 0) + 1;
|
|
245
|
+
return counts;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function aggregate(fileReports) {
|
|
249
|
+
const summary = { auto: 0, partial: 0, manual: 0, unknown: 0, hint: 0 };
|
|
250
|
+
for (const r of fileReports) {
|
|
251
|
+
for (const k of Object.keys(summary)) {
|
|
252
|
+
summary[k] += r.counts[k] ?? 0;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return summary;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function printAnalysisReport(fileReports, target) {
|
|
259
|
+
const symbol = (kind) =>
|
|
260
|
+
kind === "auto"
|
|
261
|
+
? chalk.green("✔ auto ")
|
|
262
|
+
: kind === "partial"
|
|
263
|
+
? chalk.cyan("~ partial")
|
|
264
|
+
: kind === "manual"
|
|
265
|
+
? chalk.yellow("! manual ")
|
|
266
|
+
: kind === "hint"
|
|
267
|
+
? chalk.magenta("→ hint ")
|
|
268
|
+
: chalk.red("? unknown");
|
|
269
|
+
|
|
270
|
+
console.log("");
|
|
271
|
+
console.log(chalk.bold(`ship-api migrate-orm — analyze ${target}`));
|
|
272
|
+
console.log("");
|
|
273
|
+
|
|
274
|
+
const filesWithFindings = fileReports.filter((r) => r.findings.length > 0);
|
|
275
|
+
if (filesWithFindings.length === 0) {
|
|
276
|
+
console.log(chalk.dim(" (no TypeORM decorators detected)"));
|
|
277
|
+
console.log("");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (const report of filesWithFindings) {
|
|
282
|
+
console.log(chalk.dim(report.file));
|
|
283
|
+
for (const f of report.findings) {
|
|
284
|
+
const loc = chalk.dim(`L${f.line}`);
|
|
285
|
+
const decorator = chalk.bold(`@${f.decorator}`);
|
|
286
|
+
console.log(` ${symbol(f.kind)} ${loc} ${decorator} on ${f.target} — ${f.note}`);
|
|
287
|
+
}
|
|
288
|
+
console.log("");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const summary = aggregate(fileReports);
|
|
292
|
+
console.log(
|
|
293
|
+
chalk.dim(
|
|
294
|
+
` summary: ${summary.auto} auto · ${summary.partial} partial · ${summary.hint} hint · ${summary.manual} manual · ${summary.unknown} unknown`,
|
|
295
|
+
),
|
|
296
|
+
);
|
|
297
|
+
console.log("");
|
|
298
|
+
console.log(
|
|
299
|
+
chalk.dim(
|
|
300
|
+
" Run with `--write` to emit Drizzle pgTable schemas next to each entity (Account.ts → Account.drizzle.ts).",
|
|
301
|
+
),
|
|
302
|
+
);
|
|
303
|
+
console.log("");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ───────────────────── Write mode ─────────────────────
|
|
307
|
+
|
|
308
|
+
const COLUMN_DECORATORS = new Set([
|
|
309
|
+
"Column",
|
|
310
|
+
"PrimaryColumn",
|
|
311
|
+
"PrimaryGeneratedColumn",
|
|
312
|
+
"CreateDateColumn",
|
|
313
|
+
"UpdateDateColumn",
|
|
314
|
+
"DeleteDateColumn",
|
|
315
|
+
]);
|
|
316
|
+
const RELATION_DECORATORS = new Set(["OneToOne", "OneToMany", "ManyToOne", "ManyToMany"]);
|
|
317
|
+
const AUDIT_DECORATORS = new Set(["CreateDateColumn", "UpdateDateColumn", "DeleteDateColumn"]);
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Compute the `*.drizzle.ts` output path for an entity source file.
|
|
321
|
+
*
|
|
322
|
+
* Rule: strip a trailing `.entity` segment (if present) before appending
|
|
323
|
+
* `.drizzle.ts`, so `token.entity.ts` → `token.drizzle.ts` (not the
|
|
324
|
+
* awkward stacked `token.entity.drizzle.ts`). Files that don't follow
|
|
325
|
+
* the `*.entity.ts` convention keep their stem unchanged:
|
|
326
|
+
* `Account.ts` → `Account.drizzle.ts`.
|
|
327
|
+
*/
|
|
328
|
+
export function entityToOutputPath(file) {
|
|
329
|
+
return file.replace(/(?:\.entity)?\.ts$/, ".drizzle.ts");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function runWriteMode(target, opts) {
|
|
333
|
+
const entries = await collectFiles(target);
|
|
334
|
+
if (entries.length === 0) {
|
|
335
|
+
console.log(chalk.yellow(`No .ts files found at ${target} — nothing to transform.`));
|
|
336
|
+
return { files: [] };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const written = [];
|
|
340
|
+
const skipped = [];
|
|
341
|
+
for (const file of entries) {
|
|
342
|
+
const result = await transformFile(file);
|
|
343
|
+
if (!result.ok) {
|
|
344
|
+
skipped.push({ file, reason: result.reason });
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const outPath = entityToOutputPath(file);
|
|
348
|
+
await fs.writeFile(outPath, result.content);
|
|
349
|
+
written.push(outPath);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!opts.silent) {
|
|
353
|
+
printWriteReport(target, written, skipped);
|
|
354
|
+
}
|
|
355
|
+
return { files: written, skipped };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Transform a single TypeORM entity file's source text into a Drizzle
|
|
360
|
+
* `pgTable` schema file's source text. Returns `{ ok: true, content }`
|
|
361
|
+
* on success, `{ ok: false, reason }` when the file has no @Entity
|
|
362
|
+
* class to translate (skipped silently in directory walks).
|
|
363
|
+
*/
|
|
364
|
+
export async function transformText(source, opts = {}) {
|
|
365
|
+
const fileName = opts.fileName ?? "entity.ts";
|
|
366
|
+
const sourceFile = ts.createSourceFile(
|
|
367
|
+
fileName,
|
|
368
|
+
source,
|
|
369
|
+
ts.ScriptTarget.Latest,
|
|
370
|
+
/* setParentNodes */ true,
|
|
371
|
+
ts.ScriptKind.TS,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const entityClass = findEntityClass(sourceFile);
|
|
375
|
+
if (!entityClass) {
|
|
376
|
+
return { ok: false, reason: "no @Entity class found" };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const description = await describeEntity(entityClass, sourceFile, fileName);
|
|
380
|
+
const content = renderDrizzleSchema(description);
|
|
381
|
+
return { ok: true, content };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export async function transformFile(filePath) {
|
|
385
|
+
const source = await fs.readFile(filePath, "utf8");
|
|
386
|
+
return transformText(source, { fileName: filePath });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function findEntityClass(sourceFile) {
|
|
390
|
+
let entityClass;
|
|
391
|
+
function visit(node) {
|
|
392
|
+
if (entityClass) return;
|
|
393
|
+
if (ts.isClassDeclaration(node)) {
|
|
394
|
+
const decorators = ts.getDecorators?.(node) ?? [];
|
|
395
|
+
const hasEntity = decorators.some((d) => decoratorName(d) === "Entity");
|
|
396
|
+
if (hasEntity) {
|
|
397
|
+
entityClass = node;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
ts.forEachChild(node, visit);
|
|
402
|
+
}
|
|
403
|
+
visit(sourceFile);
|
|
404
|
+
return entityClass;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function describeEntity(classNode, sourceFile, sourceFilePath) {
|
|
408
|
+
const className = classNode.name?.text ?? "Entity";
|
|
409
|
+
const classDecorators = ts.getDecorators?.(classNode) ?? [];
|
|
410
|
+
|
|
411
|
+
const entityDecorator = classDecorators.find((d) => decoratorName(d) === "Entity");
|
|
412
|
+
const explicitTableName = entityDecorator ? extractFirstStringArg(entityDecorator) : undefined;
|
|
413
|
+
const tableName = explicitTableName ?? defaultTableName(className);
|
|
414
|
+
|
|
415
|
+
// Class-level @Index — emitted as a positional `(table) => [...]`
|
|
416
|
+
// arg on pgTable, so collect them now.
|
|
417
|
+
const classIndexes = classDecorators
|
|
418
|
+
.filter((d) => decoratorName(d) === "Index")
|
|
419
|
+
.map((d) => extractIndexInfo(d));
|
|
420
|
+
|
|
421
|
+
// Walk the heritage chain (extends Y extends Z ...) and pull decorated
|
|
422
|
+
// properties from base classes. Without this, `class Proposal extends
|
|
423
|
+
// TenantScopedEntity { ... }` silently drops every field on the parent
|
|
424
|
+
// (id, tenantId, audit columns) and emits an unusable Drizzle table.
|
|
425
|
+
const inheritance = await walkInheritanceChain(classNode, sourceFile, sourceFilePath);
|
|
426
|
+
|
|
427
|
+
// Collect this class's own decorated properties.
|
|
428
|
+
const ownProperties = collectDecoratedProperties(classNode, sourceFile);
|
|
429
|
+
|
|
430
|
+
// Prepend inherited (parent fields appear first in the table; child
|
|
431
|
+
// fields can shadow if same propertyName — but valid TypeORM doesn't
|
|
432
|
+
// re-declare an inherited @Column, so this almost never happens).
|
|
433
|
+
const properties = [...inheritance.properties, ...ownProperties];
|
|
434
|
+
|
|
435
|
+
// Class-level @Index from base classes are merged in too.
|
|
436
|
+
const allClassIndexes = [...inheritance.classIndexes, ...classIndexes];
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
className,
|
|
440
|
+
tableName,
|
|
441
|
+
classIndexes: allClassIndexes,
|
|
442
|
+
properties,
|
|
443
|
+
inheritedFrom: inheritance.inheritedFrom,
|
|
444
|
+
inheritanceTodos: inheritance.todos,
|
|
445
|
+
sourceFilePath,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Pull decorated property descriptors from a class declaration (any
|
|
451
|
+
* class — used both for the @Entity class itself and for ancestors
|
|
452
|
+
* during the heritage walk).
|
|
453
|
+
*/
|
|
454
|
+
function collectDecoratedProperties(classNode, sourceFile) {
|
|
455
|
+
const properties = [];
|
|
456
|
+
for (const member of classNode.members) {
|
|
457
|
+
if (!ts.isPropertyDeclaration(member)) continue;
|
|
458
|
+
const decorators = ts.getDecorators?.(member) ?? [];
|
|
459
|
+
if (decorators.length === 0) continue;
|
|
460
|
+
const desc = describeProperty(member, decorators, sourceFile);
|
|
461
|
+
if (desc) properties.push(desc);
|
|
462
|
+
}
|
|
463
|
+
return properties;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Recursively walk `extends X extends Y` and merge in each ancestor's
|
|
468
|
+
* decorated properties. Stops at:
|
|
469
|
+
* - Framework-only base classes (e.g. `@sixthsummit/ship-api/app/entity/AbstractEntity`)
|
|
470
|
+
* — silent skip (no TODO; documented v3-only artifact, expected).
|
|
471
|
+
* - Already-visited class names — circular inheritance, surfaced as TODO.
|
|
472
|
+
* - Unresolvable imports — surfaced as TODO so the user can chase the
|
|
473
|
+
* missing parent manually.
|
|
474
|
+
*/
|
|
475
|
+
async function walkInheritanceChain(classNode, sourceFile, sourceFilePath, visited = new Set()) {
|
|
476
|
+
const result = {
|
|
477
|
+
properties: [],
|
|
478
|
+
classIndexes: [],
|
|
479
|
+
inheritedFrom: [],
|
|
480
|
+
todos: [],
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const extendedName = getExtendedClassName(classNode);
|
|
484
|
+
if (!extendedName) return result;
|
|
485
|
+
if (visited.has(extendedName)) {
|
|
486
|
+
result.todos.push(`circular inheritance detected at \`${extendedName}\` — skipped recursion`);
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
visited.add(extendedName);
|
|
490
|
+
|
|
491
|
+
const resolved = await resolveExtendedClassImport(extendedName, sourceFile, sourceFilePath);
|
|
492
|
+
|
|
493
|
+
if (resolved.kind === "framework-only") {
|
|
494
|
+
// v3 AbstractEntity (etc.) is a documented no-translate boundary.
|
|
495
|
+
// Don't TODO; the user already knows.
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
if (resolved.kind === "unresolvable") {
|
|
499
|
+
result.todos.push(
|
|
500
|
+
`parent class \`${extendedName}\` could not be resolved (${resolved.reason}) — inherited fields not merged`,
|
|
501
|
+
);
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Locate the parent class declaration.
|
|
506
|
+
let parentClass;
|
|
507
|
+
let parentSourceFile;
|
|
508
|
+
let parentFilePath;
|
|
509
|
+
if (resolved.kind === "same-file") {
|
|
510
|
+
parentClass = findClassByName(sourceFile, extendedName);
|
|
511
|
+
parentSourceFile = sourceFile;
|
|
512
|
+
parentFilePath = sourceFilePath;
|
|
513
|
+
} else {
|
|
514
|
+
// External file — read + parse.
|
|
515
|
+
let raw;
|
|
516
|
+
try {
|
|
517
|
+
raw = await fs.readFile(resolved.absPath, "utf8");
|
|
518
|
+
} catch (err) {
|
|
519
|
+
result.todos.push(
|
|
520
|
+
`parent class \`${extendedName}\` file at ${resolved.absPath} could not be read (${err.code ?? err.message}) — inherited fields not merged`,
|
|
521
|
+
);
|
|
522
|
+
return result;
|
|
523
|
+
}
|
|
524
|
+
parentSourceFile = ts.createSourceFile(
|
|
525
|
+
resolved.absPath,
|
|
526
|
+
raw,
|
|
527
|
+
ts.ScriptTarget.Latest,
|
|
528
|
+
/* setParentNodes */ true,
|
|
529
|
+
ts.ScriptKind.TS,
|
|
530
|
+
);
|
|
531
|
+
parentClass = findClassByName(parentSourceFile, extendedName);
|
|
532
|
+
parentFilePath = resolved.absPath;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (!parentClass) {
|
|
536
|
+
result.todos.push(
|
|
537
|
+
`parent class \`${extendedName}\` not found in resolved file — inherited fields not merged`,
|
|
538
|
+
);
|
|
539
|
+
return result;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Single Table Inheritance (parent itself is @Entity-decorated): rare,
|
|
543
|
+
// not auto-translated. Surface as TODO; do not merge fields (each entity
|
|
544
|
+
// gets its own table in v4).
|
|
545
|
+
const parentDecorators = ts.getDecorators?.(parentClass) ?? [];
|
|
546
|
+
const parentIsEntity = parentDecorators.some((d) => decoratorName(d) === "Entity");
|
|
547
|
+
if (parentIsEntity) {
|
|
548
|
+
result.todos.push(
|
|
549
|
+
`parent class \`${extendedName}\` is itself \`@Entity\`-decorated (Single Table Inheritance) — not auto-translated; each entity needs its own pgTable`,
|
|
550
|
+
);
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Recurse first (grandparent fields go before parent fields).
|
|
555
|
+
const grandparent = await walkInheritanceChain(
|
|
556
|
+
parentClass,
|
|
557
|
+
parentSourceFile,
|
|
558
|
+
parentFilePath,
|
|
559
|
+
visited,
|
|
560
|
+
);
|
|
561
|
+
result.properties.push(...grandparent.properties);
|
|
562
|
+
result.classIndexes.push(...grandparent.classIndexes);
|
|
563
|
+
result.inheritedFrom.push(...grandparent.inheritedFrom);
|
|
564
|
+
result.todos.push(...grandparent.todos);
|
|
565
|
+
|
|
566
|
+
// Then this parent's own fields.
|
|
567
|
+
const parentProperties = collectDecoratedProperties(parentClass, parentSourceFile);
|
|
568
|
+
result.properties.push(...parentProperties);
|
|
569
|
+
|
|
570
|
+
// Parent's class-level @Index decorators.
|
|
571
|
+
const parentClassIndexes = parentDecorators
|
|
572
|
+
.filter((d) => decoratorName(d) === "Index")
|
|
573
|
+
.map((d) => extractIndexInfo(d));
|
|
574
|
+
result.classIndexes.push(...parentClassIndexes);
|
|
575
|
+
|
|
576
|
+
result.inheritedFrom.push({
|
|
577
|
+
className: extendedName,
|
|
578
|
+
sourcePath: parentFilePath,
|
|
579
|
+
fieldCount: parentProperties.length,
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
return result;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/** Walk the AST for a class declaration with a matching name. */
|
|
586
|
+
function findClassByName(sourceFile, className) {
|
|
587
|
+
let target;
|
|
588
|
+
function visit(node) {
|
|
589
|
+
if (target) return;
|
|
590
|
+
if (ts.isClassDeclaration(node) && node.name?.text === className) {
|
|
591
|
+
target = node;
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
ts.forEachChild(node, visit);
|
|
595
|
+
}
|
|
596
|
+
visit(sourceFile);
|
|
597
|
+
return target;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** Read the `extends X` (or `extends Foo.X`) class name. */
|
|
601
|
+
function getExtendedClassName(classNode) {
|
|
602
|
+
const heritageClauses = classNode.heritageClauses ?? [];
|
|
603
|
+
for (const clause of heritageClauses) {
|
|
604
|
+
if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
|
|
605
|
+
const firstType = clause.types?.[0];
|
|
606
|
+
if (!firstType) continue;
|
|
607
|
+
const expr = firstType.expression;
|
|
608
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
609
|
+
if (ts.isPropertyAccessExpression(expr)) return expr.name.text;
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const FRAMEWORK_PACKAGE_PREFIXES = ["@sixthsummit/ship-api", "typeorm"];
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Look up the extended class's source location.
|
|
618
|
+
*
|
|
619
|
+
* Returns one of:
|
|
620
|
+
* { kind: "same-file" }
|
|
621
|
+
* { kind: "external-file", absPath }
|
|
622
|
+
* { kind: "framework-only", path } — silent skip
|
|
623
|
+
* { kind: "unresolvable", reason } — TODO emission
|
|
624
|
+
*/
|
|
625
|
+
async function resolveExtendedClassImport(extendedName, sourceFile, sourceFilePath) {
|
|
626
|
+
// Same-file check first — many projects define the abstract base
|
|
627
|
+
// alongside the entity (or use a single shared entities/ file).
|
|
628
|
+
if (findClassByName(sourceFile, extendedName)) {
|
|
629
|
+
return { kind: "same-file" };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Walk imports. Match by local name (default import or named import).
|
|
633
|
+
for (const stmt of sourceFile.statements) {
|
|
634
|
+
if (!ts.isImportDeclaration(stmt)) continue;
|
|
635
|
+
const moduleSpecifier = stmt.moduleSpecifier;
|
|
636
|
+
if (!moduleSpecifier || !ts.isStringLiteral(moduleSpecifier)) continue;
|
|
637
|
+
const specText = moduleSpecifier.text;
|
|
638
|
+
|
|
639
|
+
let matches = false;
|
|
640
|
+
const importClause = stmt.importClause;
|
|
641
|
+
if (importClause?.name?.text === extendedName) matches = true;
|
|
642
|
+
if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) {
|
|
643
|
+
for (const elem of importClause.namedBindings.elements) {
|
|
644
|
+
if (elem.name.text === extendedName) {
|
|
645
|
+
matches = true;
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (!matches) continue;
|
|
651
|
+
|
|
652
|
+
// Framework / external package. Don't try to recurse; framework
|
|
653
|
+
// base classes (AbstractEntity) are documented v3-only.
|
|
654
|
+
if (
|
|
655
|
+
FRAMEWORK_PACKAGE_PREFIXES.some((p) => specText === p || specText.startsWith(p + "/")) ||
|
|
656
|
+
(!specText.startsWith(".") && !specText.startsWith("/"))
|
|
657
|
+
) {
|
|
658
|
+
return { kind: "framework-only", path: specText };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Relative path — try to resolve to an absolute file path.
|
|
662
|
+
const resolved = await resolveRelativeImport(specText, sourceFilePath);
|
|
663
|
+
if (!resolved) {
|
|
664
|
+
return {
|
|
665
|
+
kind: "unresolvable",
|
|
666
|
+
reason: `could not resolve "${specText}" relative to ${sourceFilePath}`,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
return { kind: "external-file", absPath: resolved };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return {
|
|
673
|
+
kind: "unresolvable",
|
|
674
|
+
reason: `no import for \`${extendedName}\` found`,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Resolve a relative import specifier to an absolute file path, trying
|
|
680
|
+
* common TS extensions + index.ts fallback.
|
|
681
|
+
*/
|
|
682
|
+
async function resolveRelativeImport(spec, fromFile) {
|
|
683
|
+
const stripped = spec.replace(/\.(js|jsx|ts|tsx|mjs|cjs)$/, "");
|
|
684
|
+
const baseDir = path.dirname(fromFile);
|
|
685
|
+
const candidates = [
|
|
686
|
+
path.resolve(baseDir, stripped + ".ts"),
|
|
687
|
+
path.resolve(baseDir, stripped + ".tsx"),
|
|
688
|
+
path.resolve(baseDir, stripped, "index.ts"),
|
|
689
|
+
path.resolve(baseDir, stripped, "index.tsx"),
|
|
690
|
+
];
|
|
691
|
+
for (const c of candidates) {
|
|
692
|
+
try {
|
|
693
|
+
await fs.stat(c);
|
|
694
|
+
return c;
|
|
695
|
+
} catch {
|
|
696
|
+
/* try next candidate */
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function describeProperty(propertyNode, decorators, sourceFile) {
|
|
703
|
+
const propertyName = propertyNode.name.getText(sourceFile);
|
|
704
|
+
const decoratorList = decorators.map((d) => ({
|
|
705
|
+
name: decoratorName(d),
|
|
706
|
+
args: d.expression && ts.isCallExpression(d.expression) ? d.expression.arguments : [],
|
|
707
|
+
raw: d,
|
|
708
|
+
}));
|
|
709
|
+
|
|
710
|
+
const isAudit = decoratorList.some((d) => AUDIT_DECORATORS.has(d.name));
|
|
711
|
+
const columnDecorator = decoratorList.find((d) => COLUMN_DECORATORS.has(d.name));
|
|
712
|
+
const relationDecorator = decoratorList.find((d) => RELATION_DECORATORS.has(d.name));
|
|
713
|
+
|
|
714
|
+
if (isAudit) {
|
|
715
|
+
// Detect the v3 audit-column trio. Each property contributes the
|
|
716
|
+
// audit-column kind so the renderer can emit `...baseFields` once
|
|
717
|
+
// instead of three separate columns.
|
|
718
|
+
return {
|
|
719
|
+
kind: "audit",
|
|
720
|
+
propertyName,
|
|
721
|
+
auditKind: decoratorList.find((d) => AUDIT_DECORATORS.has(d.name)).name,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (relationDecorator) {
|
|
726
|
+
return {
|
|
727
|
+
kind: "relation-todo",
|
|
728
|
+
propertyName,
|
|
729
|
+
decorator: relationDecorator.name,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (!columnDecorator) {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return describeColumn(propertyName, columnDecorator, decoratorList);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function describeColumn(propertyName, columnDecorator, allDecorators) {
|
|
741
|
+
if (columnDecorator.name === "PrimaryGeneratedColumn") {
|
|
742
|
+
const generationKind = extractFirstStringArg(columnDecorator.raw);
|
|
743
|
+
return {
|
|
744
|
+
kind: "primary",
|
|
745
|
+
propertyName,
|
|
746
|
+
generationKind: generationKind ?? "uuid",
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (columnDecorator.name === "PrimaryColumn") {
|
|
751
|
+
const sqlType = extractFirstStringArg(columnDecorator.raw);
|
|
752
|
+
return {
|
|
753
|
+
kind: "primary-explicit",
|
|
754
|
+
propertyName,
|
|
755
|
+
sqlType: sqlType ?? "text",
|
|
756
|
+
options: extractObjectArg(columnDecorator.raw, 1),
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Column or one of its variants the codemod doesn't yet handle.
|
|
761
|
+
const sqlType = extractFirstStringArg(columnDecorator.raw);
|
|
762
|
+
const options = extractObjectArg(columnDecorator.raw, sqlType ? 1 : 0);
|
|
763
|
+
return {
|
|
764
|
+
kind: "column",
|
|
765
|
+
propertyName,
|
|
766
|
+
sqlType: sqlType ?? "text",
|
|
767
|
+
options,
|
|
768
|
+
hasUnique: allDecorators.some((d) => d.name === "Unique"),
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function extractFirstStringArg(decorator) {
|
|
773
|
+
if (!decorator.expression || !ts.isCallExpression(decorator.expression)) return undefined;
|
|
774
|
+
const first = decorator.expression.arguments[0];
|
|
775
|
+
if (!first) return undefined;
|
|
776
|
+
if (ts.isStringLiteral(first) || ts.isNoSubstitutionTemplateLiteral(first)) return first.text;
|
|
777
|
+
return undefined;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function extractObjectArg(decorator, index) {
|
|
781
|
+
if (!decorator.expression || !ts.isCallExpression(decorator.expression)) return {};
|
|
782
|
+
const arg = decorator.expression.arguments[index];
|
|
783
|
+
if (!arg || !ts.isObjectLiteralExpression(arg)) return {};
|
|
784
|
+
const out = {};
|
|
785
|
+
for (const prop of arg.properties) {
|
|
786
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
787
|
+
const key = prop.name.getText();
|
|
788
|
+
out[key] = readLiteral(prop.initializer);
|
|
789
|
+
}
|
|
790
|
+
return out;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function extractIndexInfo(decorator) {
|
|
794
|
+
if (!decorator.expression || !ts.isCallExpression(decorator.expression))
|
|
795
|
+
return { name: undefined, columns: [], options: {} };
|
|
796
|
+
const args = decorator.expression.arguments;
|
|
797
|
+
let name;
|
|
798
|
+
let columns = [];
|
|
799
|
+
let options = {};
|
|
800
|
+
for (const a of args) {
|
|
801
|
+
if (ts.isStringLiteral(a)) {
|
|
802
|
+
name = a.text;
|
|
803
|
+
} else if (ts.isArrayLiteralExpression(a)) {
|
|
804
|
+
columns = a.elements.filter((e) => ts.isStringLiteral(e)).map((e) => e.text);
|
|
805
|
+
} else if (ts.isObjectLiteralExpression(a)) {
|
|
806
|
+
options = readObjectLiteral(a);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return { name, columns, options };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function readLiteral(node) {
|
|
813
|
+
if (!node) return undefined;
|
|
814
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
815
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
816
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
817
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
818
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
819
|
+
if (ts.isObjectLiteralExpression(node)) return readObjectLiteral(node);
|
|
820
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
821
|
+
// Array literals — common as `default: []` on jsonb columns. Try to
|
|
822
|
+
// parse as JSON so we can serialize back out for the `sql\`'...'::jsonb\``
|
|
823
|
+
// template; fall back to __raw if the array contains non-literal
|
|
824
|
+
// expressions (identifiers, function calls, etc.).
|
|
825
|
+
const text = node.getText();
|
|
826
|
+
try {
|
|
827
|
+
return JSON.parse(text);
|
|
828
|
+
} catch {
|
|
829
|
+
return { __raw: text };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
833
|
+
// `default: () => "now()"` etc. — preserve the source text so the
|
|
834
|
+
// emitter can re-render verbatim. Drizzle defaults use either a
|
|
835
|
+
// sql`` template or a literal value.
|
|
836
|
+
return { __raw: node.getText() };
|
|
837
|
+
}
|
|
838
|
+
return undefined;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function readObjectLiteral(node) {
|
|
842
|
+
const out = {};
|
|
843
|
+
for (const prop of node.properties) {
|
|
844
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
845
|
+
out[prop.name.getText()] = readLiteral(prop.initializer);
|
|
846
|
+
}
|
|
847
|
+
return out;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ───────────────────── Rendering ─────────────────────
|
|
851
|
+
|
|
852
|
+
function renderDrizzleSchema(desc) {
|
|
853
|
+
const auditProperties = desc.properties.filter((p) => p.kind === "audit");
|
|
854
|
+
const useBaseFields = auditProperties.length >= 2; // any 2+ of the trio → use baseFields
|
|
855
|
+
const todos = collectRenderTodos(desc);
|
|
856
|
+
const columnLines = [];
|
|
857
|
+
const importsNeeded = new Set(["pgTable"]);
|
|
858
|
+
// Imports outside `drizzle-orm/pg-core` (e.g. the `sql` template for
|
|
859
|
+
// jsonb defaults, which lives in `drizzle-orm`). renderColumnLine adds
|
|
860
|
+
// to this set when needed.
|
|
861
|
+
const ctx = { extraImports: new Set() };
|
|
862
|
+
|
|
863
|
+
for (const prop of desc.properties) {
|
|
864
|
+
if (prop.kind === "audit") continue; // handled by ...baseFields
|
|
865
|
+
if (prop.kind === "relation-todo") continue; // surfaced in todos block
|
|
866
|
+
const line = renderColumnLine(prop, importsNeeded, ctx);
|
|
867
|
+
if (line) columnLines.push(line);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (useBaseFields) {
|
|
871
|
+
columnLines.push(" ...baseFields,");
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Render @Index entries on the table.
|
|
875
|
+
const indexLines = desc.classIndexes.map((idx) => renderIndex(idx)).filter(Boolean);
|
|
876
|
+
if (indexLines.length > 0) importsNeeded.add("index");
|
|
877
|
+
|
|
878
|
+
const importParts = [...importsNeeded].sort().join(", ");
|
|
879
|
+
const baseFieldsImport = useBaseFields
|
|
880
|
+
? `import { baseFields } from "@sixthsummit/ship-api/app/db/baseFields";\n`
|
|
881
|
+
: "";
|
|
882
|
+
const drizzleOrmImport = ctx.extraImports.has("sql")
|
|
883
|
+
? `import { sql } from "drizzle-orm";\n`
|
|
884
|
+
: "";
|
|
885
|
+
|
|
886
|
+
const body =
|
|
887
|
+
`export const ${camelize(desc.tableName)} = pgTable(\n` +
|
|
888
|
+
` "${desc.tableName}",\n` +
|
|
889
|
+
` {\n${columnLines.join("\n")}\n }` +
|
|
890
|
+
(indexLines.length > 0
|
|
891
|
+
? `,\n (table) => [\n${indexLines.map((l) => " " + l).join(",\n")},\n ]`
|
|
892
|
+
: "") +
|
|
893
|
+
`,\n);\n\n`;
|
|
894
|
+
|
|
895
|
+
const types =
|
|
896
|
+
`export type ${desc.className} = typeof ${camelize(desc.tableName)}.$inferSelect;\n` +
|
|
897
|
+
`export type New${desc.className} = typeof ${camelize(desc.tableName)}.$inferInsert;\n`;
|
|
898
|
+
|
|
899
|
+
const todosBlock =
|
|
900
|
+
todos.length > 0
|
|
901
|
+
? `\n/*\n * TODO[ship-api migrate-orm]: review the following items\n * — they need manual translation:\n${todos.map((t) => ` * • ${t}`).join("\n")}\n */\n\n`
|
|
902
|
+
: "\n";
|
|
903
|
+
|
|
904
|
+
// Inherits-from header — informational, NOT a TODO. Tells the user
|
|
905
|
+
// which fields came from the heritage chain so they don't get
|
|
906
|
+
// surprised by an `id` / `tenantId` they didn't declare on the entity.
|
|
907
|
+
const inheritsHeader =
|
|
908
|
+
desc.inheritedFrom && desc.inheritedFrom.length > 0
|
|
909
|
+
? desc.inheritedFrom
|
|
910
|
+
.map(
|
|
911
|
+
(i) =>
|
|
912
|
+
`// Inherits ${i.className} (${i.fieldCount} field${i.fieldCount === 1 ? "" : "s"} merged from ${path.relative(path.dirname(desc.sourceFilePath ?? ""), i.sourcePath) || i.sourcePath}).\n`,
|
|
913
|
+
)
|
|
914
|
+
.join("")
|
|
915
|
+
: "";
|
|
916
|
+
|
|
917
|
+
return (
|
|
918
|
+
`// Generated by \`ship-api migrate-orm --write\`.\n` +
|
|
919
|
+
`// Original entity: ${desc.className}.\n` +
|
|
920
|
+
inheritsHeader +
|
|
921
|
+
`// Review the TODO[ship-api migrate-orm] markers below before relying on this file.\n\n` +
|
|
922
|
+
`import { ${importParts} } from "drizzle-orm/pg-core";\n` +
|
|
923
|
+
drizzleOrmImport +
|
|
924
|
+
baseFieldsImport +
|
|
925
|
+
todosBlock +
|
|
926
|
+
body +
|
|
927
|
+
types
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function renderColumnLine(prop, importsNeeded, ctx = { extraImports: new Set() }) {
|
|
932
|
+
if (prop.kind === "primary") {
|
|
933
|
+
importsNeeded.add("uuid");
|
|
934
|
+
return ` id: uuid("id").primaryKey().defaultRandom(),`;
|
|
935
|
+
}
|
|
936
|
+
if (prop.kind === "primary-explicit") {
|
|
937
|
+
const drizzleType = mapTypeOrmTypeToDrizzle(prop.sqlType);
|
|
938
|
+
importsNeeded.add(drizzleType.fn);
|
|
939
|
+
const colName = snakeize(prop.propertyName);
|
|
940
|
+
return ` ${prop.propertyName}: ${drizzleType.fn}("${colName}"${drizzleType.argSuffix}).primaryKey(),`;
|
|
941
|
+
}
|
|
942
|
+
if (prop.kind !== "column") return null;
|
|
943
|
+
|
|
944
|
+
const drizzleType = mapTypeOrmTypeToDrizzle(prop.sqlType);
|
|
945
|
+
importsNeeded.add(drizzleType.fn);
|
|
946
|
+
const colName = snakeize(prop.propertyName);
|
|
947
|
+
const opts = prop.options ?? {};
|
|
948
|
+
|
|
949
|
+
// Compute the argSuffix dynamically. The static map uses sane defaults
|
|
950
|
+
// for each SQL type (e.g. varchar gets `{ length: 255 }`); decimal /
|
|
951
|
+
// numeric override that with the entity's precision/scale when present.
|
|
952
|
+
let argSuffix = drizzleType.argSuffix;
|
|
953
|
+
if (
|
|
954
|
+
(prop.sqlType === "decimal" || prop.sqlType === "numeric") &&
|
|
955
|
+
(opts.precision !== undefined || opts.scale !== undefined)
|
|
956
|
+
) {
|
|
957
|
+
const parts = [];
|
|
958
|
+
if (opts.precision !== undefined) parts.push(`precision: ${opts.precision}`);
|
|
959
|
+
if (opts.scale !== undefined) parts.push(`scale: ${opts.scale}`);
|
|
960
|
+
argSuffix = `, { ${parts.join(", ")} }`;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
let chain = `${drizzleType.fn}("${colName}"${argSuffix})`;
|
|
964
|
+
|
|
965
|
+
// .notNull() — TypeORM defaults to NOT NULL unless `nullable: true`.
|
|
966
|
+
// Drizzle defaults to NULLABLE. Match TypeORM's default.
|
|
967
|
+
if (opts.nullable !== true) chain += ".notNull()";
|
|
968
|
+
|
|
969
|
+
// .default(...) — best-effort. Strings/numbers/booleans pass through;
|
|
970
|
+
// jsonb/json arrays + objects get re-serialized via `sql\`'...'::<type>\``.
|
|
971
|
+
if (opts.default !== undefined) {
|
|
972
|
+
const defaultExpr = renderDefaultExpression(opts.default, prop.sqlType, ctx);
|
|
973
|
+
if (defaultExpr !== null) {
|
|
974
|
+
chain += `.default(${defaultExpr})`;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// .unique() — column-level
|
|
979
|
+
if (opts.unique === true || prop.hasUnique) chain += ".unique()";
|
|
980
|
+
|
|
981
|
+
return ` ${prop.propertyName}: ${chain},`;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Render a Drizzle `.default(...)` expression for a v3 `@Column({ default: X })`
|
|
986
|
+
* value. Returns the expression string (e.g. `"draft"`, `0`,
|
|
987
|
+
* `sql\`'[]'::jsonb\``), or null if the value can't be expressed and
|
|
988
|
+
* should be skipped (renderer drops the .default call).
|
|
989
|
+
*
|
|
990
|
+
* For arrays + plain objects on jsonb/json columns, emits a `sql` tag
|
|
991
|
+
* — sets `ctx.extraImports.add("sql")` so the renderer pulls in
|
|
992
|
+
* `import { sql } from "drizzle-orm";` at the top of the file.
|
|
993
|
+
*/
|
|
994
|
+
function renderDefaultExpression(value, sqlType, ctx) {
|
|
995
|
+
// Array or non-empty object literal — jsonb / json expects a
|
|
996
|
+
// `sql\`'<json>'::<type>\`` template. Other column types fall through
|
|
997
|
+
// to the legacy primitive renderer (with TODO if it can't represent).
|
|
998
|
+
const isComplexLiteral =
|
|
999
|
+
Array.isArray(value) || (value !== null && typeof value === "object" && !("__raw" in value));
|
|
1000
|
+
|
|
1001
|
+
if (isComplexLiteral && (sqlType === "jsonb" || sqlType === "json")) {
|
|
1002
|
+
ctx.extraImports.add("sql");
|
|
1003
|
+
const json = JSON.stringify(value);
|
|
1004
|
+
return `sql\`'${json}'::${sqlType}\``;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Arrow-function default expressions — `() => "<sql>"` — TypeORM's
|
|
1008
|
+
// documented "raw SQL default" idiom. Used heavily for jsonb defaults
|
|
1009
|
+
// and SQL functions like `() => "now()"`. Emits as `sql\`<inner>\``
|
|
1010
|
+
// so Drizzle treats the body as a SQL fragment, preserving v3's
|
|
1011
|
+
// exact column-level semantics.
|
|
1012
|
+
if (value && typeof value === "object" && "__raw" in value) {
|
|
1013
|
+
const innerSql = parseArrowFunctionRawSql(value.__raw);
|
|
1014
|
+
if (innerSql !== null) {
|
|
1015
|
+
ctx.extraImports.add("sql");
|
|
1016
|
+
return `sql\`${innerSql}\``;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Primitives (string / number / boolean / null) and unrecognized
|
|
1021
|
+
// __raw shapes fall through to the legacy renderer (which emits a
|
|
1022
|
+
// TODO marker for the latter).
|
|
1023
|
+
return renderDefaultValue(value);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Parse the raw text of a v3 TypeORM arrow-function default —
|
|
1028
|
+
* `() => "<sql>"` — and return the inner SQL expression. Returns
|
|
1029
|
+
* null if the shape isn't a recognized arrow-fn-returning-string-literal.
|
|
1030
|
+
*
|
|
1031
|
+
* Examples (input → return):
|
|
1032
|
+
* () => "'{}'" → '{}'
|
|
1033
|
+
* () => "'{}'::jsonb" → '{}'::jsonb
|
|
1034
|
+
* () => "now()" → now()
|
|
1035
|
+
* () => `now()` → now() (template-literal body)
|
|
1036
|
+
* (x) => "..." → null (parameterized — invalid for default)
|
|
1037
|
+
* () => 42 → null (non-string body — fall through)
|
|
1038
|
+
*/
|
|
1039
|
+
function parseArrowFunctionRawSql(rawText) {
|
|
1040
|
+
if (typeof rawText !== "string") return null;
|
|
1041
|
+
const trimmed = rawText.trim();
|
|
1042
|
+
// Match: () [: type]? => <quoted-string-body>
|
|
1043
|
+
// The quote can be ", ', or `. Captures the inner body verbatim.
|
|
1044
|
+
const match = /^\(\s*\)(?:\s*:\s*[^=]+?)?\s*=>\s*(["'`])([\s\S]*)\1\s*$/.exec(trimmed);
|
|
1045
|
+
if (!match) return null;
|
|
1046
|
+
return match[2];
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function renderDefaultValue(v) {
|
|
1050
|
+
if (v && typeof v === "object" && "__raw" in v) {
|
|
1051
|
+
// Function expression like `() => "now()"` — preserve via sql tag.
|
|
1052
|
+
return "/* TODO[ship-api migrate-orm]: review default */ undefined";
|
|
1053
|
+
}
|
|
1054
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
1055
|
+
if (typeof v === "number") return String(v);
|
|
1056
|
+
if (typeof v === "boolean") return String(v);
|
|
1057
|
+
if (v === null) return "null";
|
|
1058
|
+
return "/* TODO */ undefined";
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function renderIndex(idx) {
|
|
1062
|
+
const cols = idx.columns.map((c) => `table.${c}`).join(", ");
|
|
1063
|
+
const name = idx.name ? JSON.stringify(idx.name) : '""';
|
|
1064
|
+
return `index(${name}).on(${cols})`;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function collectRenderTodos(desc) {
|
|
1068
|
+
const todos = [];
|
|
1069
|
+
for (const prop of desc.properties) {
|
|
1070
|
+
if (prop.kind === "relation-todo") {
|
|
1071
|
+
todos.push(
|
|
1072
|
+
`relation property \`${prop.propertyName}\` (@${prop.decorator}) — restore via Drizzle relations() block + a foreign-key column`,
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (prop.kind === "column" && prop.options?.transformer) {
|
|
1076
|
+
todos.push(
|
|
1077
|
+
`column \`${prop.propertyName}\` had a custom \`transformer\` — re-implement at the service layer`,
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
// Inheritance-walk TODOs (circular / unresolvable / Single Table
|
|
1082
|
+
// Inheritance — anything the walk couldn't auto-translate).
|
|
1083
|
+
if (desc.inheritanceTodos && desc.inheritanceTodos.length > 0) {
|
|
1084
|
+
todos.push(...desc.inheritanceTodos);
|
|
1085
|
+
}
|
|
1086
|
+
return todos;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const TYPEORM_TO_DRIZZLE = {
|
|
1090
|
+
varchar: { fn: "varchar", argSuffix: ", { length: 255 }" },
|
|
1091
|
+
text: { fn: "text", argSuffix: "" },
|
|
1092
|
+
uuid: { fn: "uuid", argSuffix: "" },
|
|
1093
|
+
bigint: { fn: "bigint", argSuffix: ', { mode: "number" }' },
|
|
1094
|
+
int: { fn: "integer", argSuffix: "" },
|
|
1095
|
+
integer: { fn: "integer", argSuffix: "" },
|
|
1096
|
+
smallint: { fn: "smallint", argSuffix: "" },
|
|
1097
|
+
boolean: { fn: "boolean", argSuffix: "" },
|
|
1098
|
+
bool: { fn: "boolean", argSuffix: "" },
|
|
1099
|
+
timestamp: { fn: "timestamp", argSuffix: "" },
|
|
1100
|
+
timestamptz: {
|
|
1101
|
+
fn: "timestamp",
|
|
1102
|
+
argSuffix: ", { withTimezone: true }",
|
|
1103
|
+
},
|
|
1104
|
+
date: { fn: "date", argSuffix: "" },
|
|
1105
|
+
jsonb: { fn: "jsonb", argSuffix: "" },
|
|
1106
|
+
json: { fn: "json", argSuffix: "" },
|
|
1107
|
+
numeric: { fn: "numeric", argSuffix: "" },
|
|
1108
|
+
decimal: { fn: "decimal", argSuffix: "" },
|
|
1109
|
+
real: { fn: "real", argSuffix: "" },
|
|
1110
|
+
double: { fn: "doublePrecision", argSuffix: "" },
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
function mapTypeOrmTypeToDrizzle(t) {
|
|
1114
|
+
return TYPEORM_TO_DRIZZLE[t] ?? { fn: "text", argSuffix: "" };
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function defaultTableName(className) {
|
|
1118
|
+
// TypeORM 0.3 default strategy: snake_case_pluralized class name.
|
|
1119
|
+
const snake = className.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
1120
|
+
// Naive pluralization: if ends in s/x/z/ch/sh add es; consonant+y → ies; else add s.
|
|
1121
|
+
if (/(s|x|z|ch|sh)$/.test(snake)) return snake + "es";
|
|
1122
|
+
if (/[^aeiou]y$/.test(snake)) return snake.slice(0, -1) + "ies";
|
|
1123
|
+
return snake + "s";
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function snakeize(camel) {
|
|
1127
|
+
return camel.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function camelize(snake) {
|
|
1131
|
+
return snake
|
|
1132
|
+
.split(/[_-]/)
|
|
1133
|
+
.map((p, i) => (i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)))
|
|
1134
|
+
.join("");
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function printWriteReport(target, written, skipped) {
|
|
1138
|
+
console.log("");
|
|
1139
|
+
console.log(chalk.bold(`ship-api migrate-orm --write ${target}`));
|
|
1140
|
+
console.log("");
|
|
1141
|
+
if (written.length > 0) {
|
|
1142
|
+
for (const file of written) {
|
|
1143
|
+
console.log(" ", chalk.green("✚"), file);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (skipped.length > 0) {
|
|
1147
|
+
console.log("");
|
|
1148
|
+
for (const { file, reason } of skipped) {
|
|
1149
|
+
console.log(" ", chalk.yellow("⤳"), file, chalk.dim(`(${reason})`));
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
console.log("");
|
|
1153
|
+
console.log(chalk.dim(" Review the TODO[ship-api migrate-orm] markers in each generated file."));
|
|
1154
|
+
console.log("");
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Exposed for testing.
|
|
1158
|
+
export const __test = {
|
|
1159
|
+
analyzeFile,
|
|
1160
|
+
decoratorName,
|
|
1161
|
+
countByKind,
|
|
1162
|
+
aggregate,
|
|
1163
|
+
SUPPORT_TABLE,
|
|
1164
|
+
transformText,
|
|
1165
|
+
describeEntity,
|
|
1166
|
+
defaultTableName,
|
|
1167
|
+
snakeize,
|
|
1168
|
+
camelize,
|
|
1169
|
+
mapTypeOrmTypeToDrizzle,
|
|
1170
|
+
entityToOutputPath,
|
|
1171
|
+
};
|