@shohaghinfo/aerojs 0.1.0
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/AEROJS.md +12 -0
- package/AGENTS.md +709 -0
- package/LICENSE +21 -0
- package/README.md +1186 -0
- package/bin/aero.js +24 -0
- package/dist/application.d.ts +251 -0
- package/dist/application.d.ts.map +1 -0
- package/dist/application.js +658 -0
- package/dist/application.js.map +1 -0
- package/dist/audit/audit-trail.d.ts +122 -0
- package/dist/audit/audit-trail.d.ts.map +1 -0
- package/dist/audit/audit-trail.js +154 -0
- package/dist/audit/audit-trail.js.map +1 -0
- package/dist/auth/auth-manager.d.ts +95 -0
- package/dist/auth/auth-manager.d.ts.map +1 -0
- package/dist/auth/auth-manager.js +130 -0
- package/dist/auth/auth-manager.js.map +1 -0
- package/dist/auth/bouncer.d.ts +143 -0
- package/dist/auth/bouncer.d.ts.map +1 -0
- package/dist/auth/bouncer.js +243 -0
- package/dist/auth/bouncer.js.map +1 -0
- package/dist/auth/guards/api-token-guard.d.ts +68 -0
- package/dist/auth/guards/api-token-guard.d.ts.map +1 -0
- package/dist/auth/guards/api-token-guard.js +107 -0
- package/dist/auth/guards/api-token-guard.js.map +1 -0
- package/dist/auth/guards/guard.d.ts +35 -0
- package/dist/auth/guards/guard.d.ts.map +1 -0
- package/dist/auth/guards/guard.js +7 -0
- package/dist/auth/guards/guard.js.map +1 -0
- package/dist/auth/guards/jwt-guard.d.ts +64 -0
- package/dist/auth/guards/jwt-guard.d.ts.map +1 -0
- package/dist/auth/guards/jwt-guard.js +104 -0
- package/dist/auth/guards/jwt-guard.js.map +1 -0
- package/dist/auth/guards/session-guard.d.ts +57 -0
- package/dist/auth/guards/session-guard.d.ts.map +1 -0
- package/dist/auth/guards/session-guard.js +101 -0
- package/dist/auth/guards/session-guard.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/policy.d.ts +20 -0
- package/dist/auth/policy.d.ts.map +1 -0
- package/dist/auth/policy.js +49 -0
- package/dist/auth/policy.js.map +1 -0
- package/dist/body-parser.d.ts +40 -0
- package/dist/body-parser.d.ts.map +1 -0
- package/dist/body-parser.js +99 -0
- package/dist/body-parser.js.map +1 -0
- package/dist/cache/cache-driver.d.ts +43 -0
- package/dist/cache/cache-driver.d.ts.map +1 -0
- package/dist/cache/cache-driver.js +6 -0
- package/dist/cache/cache-driver.js.map +1 -0
- package/dist/cache/cache-manager.d.ts +44 -0
- package/dist/cache/cache-manager.d.ts.map +1 -0
- package/dist/cache/cache-manager.js +160 -0
- package/dist/cache/cache-manager.js.map +1 -0
- package/dist/cache/drivers/database-cache-driver.d.ts +18 -0
- package/dist/cache/drivers/database-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/database-cache-driver.js +77 -0
- package/dist/cache/drivers/database-cache-driver.js.map +1 -0
- package/dist/cache/drivers/file-cache-driver.d.ts +21 -0
- package/dist/cache/drivers/file-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/file-cache-driver.js +95 -0
- package/dist/cache/drivers/file-cache-driver.js.map +1 -0
- package/dist/cache/drivers/memory-cache-driver.d.ts +21 -0
- package/dist/cache/drivers/memory-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/memory-cache-driver.js +82 -0
- package/dist/cache/drivers/memory-cache-driver.js.map +1 -0
- package/dist/cache/drivers/redis-cache-driver.d.ts +18 -0
- package/dist/cache/drivers/redis-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/redis-cache-driver.js +95 -0
- package/dist/cache/drivers/redis-cache-driver.js.map +1 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +7 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +10 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +471 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/make.d.ts +16 -0
- package/dist/cli/commands/make.d.ts.map +1 -0
- package/dist/cli/commands/make.js +157 -0
- package/dist/cli/commands/make.js.map +1 -0
- package/dist/cli/commands/new.d.ts +7 -0
- package/dist/cli/commands/new.d.ts.map +1 -0
- package/dist/cli/commands/new.js +175 -0
- package/dist/cli/commands/new.js.map +1 -0
- package/dist/cli/commands/vault.d.ts +7 -0
- package/dist/cli/commands/vault.d.ts.map +1 -0
- package/dist/cli/commands/vault.js +39 -0
- package/dist/cli/commands/vault.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/runner.d.ts +10 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +207 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli/templates/ecommerce-erp.d.ts +6 -0
- package/dist/cli/templates/ecommerce-erp.d.ts.map +1 -0
- package/dist/cli/templates/ecommerce-erp.js +1760 -0
- package/dist/cli/templates/ecommerce-erp.js.map +1 -0
- package/dist/cli/templates/fullstack-templates.d.ts +17 -0
- package/dist/cli/templates/fullstack-templates.d.ts.map +1 -0
- package/dist/cli/templates/fullstack-templates.js +17 -0
- package/dist/cli/templates/fullstack-templates.js.map +1 -0
- package/dist/config/config.d.ts +25 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +53 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/env-schema.d.ts +62 -0
- package/dist/config/env-schema.d.ts.map +1 -0
- package/dist/config/env-schema.js +203 -0
- package/dist/config/env-schema.js.map +1 -0
- package/dist/config/env.d.ts +31 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +153 -0
- package/dist/config/env.js.map +1 -0
- package/dist/context.d.ts +181 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +277 -0
- package/dist/context.js.map +1 -0
- package/dist/core/application-core.d.ts +79 -0
- package/dist/core/application-core.d.ts.map +1 -0
- package/dist/core/application-core.js +560 -0
- package/dist/core/application-core.js.map +1 -0
- package/dist/core/application.d.ts +121 -0
- package/dist/core/application.d.ts.map +1 -0
- package/dist/core/application.js +179 -0
- package/dist/core/application.js.map +1 -0
- package/dist/core/body-parser.d.ts +12 -0
- package/dist/core/body-parser.d.ts.map +1 -0
- package/dist/core/body-parser.js +85 -0
- package/dist/core/body-parser.js.map +1 -0
- package/dist/core/context.d.ts +94 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +278 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/error-dashboard.d.ts +35 -0
- package/dist/core/error-dashboard.d.ts.map +1 -0
- package/dist/core/error-dashboard.js +481 -0
- package/dist/core/error-dashboard.js.map +1 -0
- package/dist/core/errors.d.ts +83 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +117 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/hooks.d.ts +39 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +65 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/middleware.d.ts +15 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/middleware.js +45 -0
- package/dist/core/middleware.js.map +1 -0
- package/dist/core/request.d.ts +50 -0
- package/dist/core/request.d.ts.map +1 -0
- package/dist/core/request.js +94 -0
- package/dist/core/request.js.map +1 -0
- package/dist/core/response.d.ts +49 -0
- package/dist/core/response.d.ts.map +1 -0
- package/dist/core/response.js +302 -0
- package/dist/core/response.js.map +1 -0
- package/dist/core/router.d.ts +58 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +298 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/types.d.ts +70 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/utils.d.ts +36 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +108 -0
- package/dist/core/utils.js.map +1 -0
- package/dist/database/connection.d.ts +97 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +251 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/drizzle.d.ts +10 -0
- package/dist/database/drizzle.d.ts.map +1 -0
- package/dist/database/drizzle.js +13 -0
- package/dist/database/drizzle.js.map +1 -0
- package/dist/database/factory.d.ts +39 -0
- package/dist/database/factory.d.ts.map +1 -0
- package/dist/database/factory.js +74 -0
- package/dist/database/factory.js.map +1 -0
- package/dist/database/index.d.ts +16 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +16 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/knex.d.ts +33 -0
- package/dist/database/knex.d.ts.map +1 -0
- package/dist/database/knex.js +123 -0
- package/dist/database/knex.js.map +1 -0
- package/dist/database/migrator.d.ts +29 -0
- package/dist/database/migrator.d.ts.map +1 -0
- package/dist/database/migrator.js +83 -0
- package/dist/database/migrator.js.map +1 -0
- package/dist/database/model-hooks.d.ts +61 -0
- package/dist/database/model-hooks.d.ts.map +1 -0
- package/dist/database/model-hooks.js +126 -0
- package/dist/database/model-hooks.js.map +1 -0
- package/dist/database/model.d.ts +279 -0
- package/dist/database/model.d.ts.map +1 -0
- package/dist/database/model.js +960 -0
- package/dist/database/model.js.map +1 -0
- package/dist/database/prisma.d.ts +13 -0
- package/dist/database/prisma.d.ts.map +1 -0
- package/dist/database/prisma.js +28 -0
- package/dist/database/prisma.js.map +1 -0
- package/dist/database/query-builder.d.ts +178 -0
- package/dist/database/query-builder.d.ts.map +1 -0
- package/dist/database/query-builder.js +607 -0
- package/dist/database/query-builder.js.map +1 -0
- package/dist/database/schema.d.ts +38 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +134 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/database/seeder.d.ts +24 -0
- package/dist/database/seeder.d.ts.map +1 -0
- package/dist/database/seeder.js +27 -0
- package/dist/database/seeder.js.map +1 -0
- package/dist/di/container.d.ts +52 -0
- package/dist/di/container.d.ts.map +1 -0
- package/dist/di/container.js +124 -0
- package/dist/di/container.js.map +1 -0
- package/dist/di/decorators.d.ts +17 -0
- package/dist/di/decorators.d.ts.map +1 -0
- package/dist/di/decorators.js +63 -0
- package/dist/di/decorators.js.map +1 -0
- package/dist/di/inject.d.ts +11 -0
- package/dist/di/inject.d.ts.map +1 -0
- package/dist/di/inject.js +30 -0
- package/dist/di/inject.js.map +1 -0
- package/dist/di/provider.d.ts +32 -0
- package/dist/di/provider.d.ts.map +1 -0
- package/dist/di/provider.js +30 -0
- package/dist/di/provider.js.map +1 -0
- package/dist/diagnostics/diagnostics-plugin.d.ts +9 -0
- package/dist/diagnostics/diagnostics-plugin.d.ts.map +1 -0
- package/dist/diagnostics/diagnostics-plugin.js +49 -0
- package/dist/diagnostics/diagnostics-plugin.js.map +1 -0
- package/dist/diagnostics/health-check.d.ts +51 -0
- package/dist/diagnostics/health-check.d.ts.map +1 -0
- package/dist/diagnostics/health-check.js +122 -0
- package/dist/diagnostics/health-check.js.map +1 -0
- package/dist/diagnostics/index.d.ts +4 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +4 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/diagnostics/prometheus.d.ts +19 -0
- package/dist/diagnostics/prometheus.d.ts.map +1 -0
- package/dist/diagnostics/prometheus.js +99 -0
- package/dist/diagnostics/prometheus.js.map +1 -0
- package/dist/errors.d.ts +139 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +173 -0
- package/dist/errors.js.map +1 -0
- package/dist/events/event-bus.d.ts +178 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +182 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/graphql/adapter.d.ts +48 -0
- package/dist/graphql/adapter.d.ts.map +1 -0
- package/dist/graphql/adapter.js +109 -0
- package/dist/graphql/adapter.js.map +1 -0
- package/dist/graphql/executor.d.ts +24 -0
- package/dist/graphql/executor.d.ts.map +1 -0
- package/dist/graphql/executor.js +124 -0
- package/dist/graphql/executor.js.map +1 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/lexer.d.ts +52 -0
- package/dist/graphql/lexer.d.ts.map +1 -0
- package/dist/graphql/lexer.js +188 -0
- package/dist/graphql/lexer.js.map +1 -0
- package/dist/graphql/parser.d.ts +29 -0
- package/dist/graphql/parser.d.ts.map +1 -0
- package/dist/graphql/parser.js +206 -0
- package/dist/graphql/parser.js.map +1 -0
- package/dist/graphql/plugin.d.ts +13 -0
- package/dist/graphql/plugin.d.ts.map +1 -0
- package/dist/graphql/plugin.js +103 -0
- package/dist/graphql/plugin.js.map +1 -0
- package/dist/graphql/schema.d.ts +15 -0
- package/dist/graphql/schema.d.ts.map +1 -0
- package/dist/graphql/schema.js +17 -0
- package/dist/graphql/schema.js.map +1 -0
- package/dist/graphql/types.d.ts +65 -0
- package/dist/graphql/types.d.ts.map +1 -0
- package/dist/graphql/types.js +74 -0
- package/dist/graphql/types.js.map +1 -0
- package/dist/hooks.d.ts +67 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +115 -0
- package/dist/hooks.js.map +1 -0
- package/dist/i18n/i18n.d.ts +42 -0
- package/dist/i18n/i18n.d.ts.map +1 -0
- package/dist/i18n/i18n.js +158 -0
- package/dist/i18n/i18n.js.map +1 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/inertia/inertia.d.ts +62 -0
- package/dist/inertia/inertia.d.ts.map +1 -0
- package/dist/inertia/inertia.js +159 -0
- package/dist/inertia/inertia.js.map +1 -0
- package/dist/logging/index.d.ts +7 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +7 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +62 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +197 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/request-context.d.ts +29 -0
- package/dist/logging/request-context.d.ts.map +1 -0
- package/dist/logging/request-context.js +29 -0
- package/dist/logging/request-context.js.map +1 -0
- package/dist/mail/drivers/log-mail-driver.d.ts +11 -0
- package/dist/mail/drivers/log-mail-driver.d.ts.map +1 -0
- package/dist/mail/drivers/log-mail-driver.js +23 -0
- package/dist/mail/drivers/log-mail-driver.js.map +1 -0
- package/dist/mail/drivers/mail-driver.d.ts +14 -0
- package/dist/mail/drivers/mail-driver.d.ts.map +1 -0
- package/dist/mail/drivers/mail-driver.js +6 -0
- package/dist/mail/drivers/mail-driver.js.map +1 -0
- package/dist/mail/drivers/memory-mail-driver.d.ts +14 -0
- package/dist/mail/drivers/memory-mail-driver.d.ts.map +1 -0
- package/dist/mail/drivers/memory-mail-driver.js +24 -0
- package/dist/mail/drivers/memory-mail-driver.js.map +1 -0
- package/dist/mail/index.d.ts +10 -0
- package/dist/mail/index.d.ts.map +1 -0
- package/dist/mail/index.js +10 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/mail/mailer.d.ts +48 -0
- package/dist/mail/mailer.d.ts.map +1 -0
- package/dist/mail/mailer.js +111 -0
- package/dist/mail/mailer.js.map +1 -0
- package/dist/mail/message.d.ts +30 -0
- package/dist/mail/message.d.ts.map +1 -0
- package/dist/mail/message.js +77 -0
- package/dist/mail/message.js.map +1 -0
- package/dist/middleware/cors.d.ts +16 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +76 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware.d.ts +26 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +45 -0
- package/dist/middleware.js.map +1 -0
- package/dist/pdf/index.d.ts +2 -0
- package/dist/pdf/index.d.ts.map +1 -0
- package/dist/pdf/index.js +2 -0
- package/dist/pdf/index.js.map +1 -0
- package/dist/pdf/pdf.d.ts +86 -0
- package/dist/pdf/pdf.d.ts.map +1 -0
- package/dist/pdf/pdf.js +237 -0
- package/dist/pdf/pdf.js.map +1 -0
- package/dist/plugins/plugin.d.ts +25 -0
- package/dist/plugins/plugin.d.ts.map +1 -0
- package/dist/plugins/plugin.js +17 -0
- package/dist/plugins/plugin.js.map +1 -0
- package/dist/queue/drivers/database-queue-driver.d.ts +21 -0
- package/dist/queue/drivers/database-queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/database-queue-driver.js +84 -0
- package/dist/queue/drivers/database-queue-driver.js.map +1 -0
- package/dist/queue/drivers/memory-queue-driver.d.ts +17 -0
- package/dist/queue/drivers/memory-queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/memory-queue-driver.js +71 -0
- package/dist/queue/drivers/memory-queue-driver.js.map +1 -0
- package/dist/queue/drivers/queue-driver.d.ts +44 -0
- package/dist/queue/drivers/queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/queue-driver.js +6 -0
- package/dist/queue/drivers/queue-driver.js.map +1 -0
- package/dist/queue/drivers/redis-queue-driver.d.ts +16 -0
- package/dist/queue/drivers/redis-queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/redis-queue-driver.js +92 -0
- package/dist/queue/drivers/redis-queue-driver.js.map +1 -0
- package/dist/queue/index.d.ts +12 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +12 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/job.d.ts +20 -0
- package/dist/queue/job.d.ts.map +1 -0
- package/dist/queue/job.js +19 -0
- package/dist/queue/job.js.map +1 -0
- package/dist/queue/queue-manager.d.ts +41 -0
- package/dist/queue/queue-manager.d.ts.map +1 -0
- package/dist/queue/queue-manager.js +98 -0
- package/dist/queue/queue-manager.js.map +1 -0
- package/dist/queue/worker.d.ts +57 -0
- package/dist/queue/worker.d.ts.map +1 -0
- package/dist/queue/worker.js +156 -0
- package/dist/queue/worker.js.map +1 -0
- package/dist/redis/index.d.ts +5 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +5 -0
- package/dist/redis/index.js.map +1 -0
- package/dist/redis/redis-client.d.ts +44 -0
- package/dist/redis/redis-client.d.ts.map +1 -0
- package/dist/redis/redis-client.js +131 -0
- package/dist/redis/redis-client.js.map +1 -0
- package/dist/redis/redis-connection.d.ts +32 -0
- package/dist/redis/redis-connection.d.ts.map +1 -0
- package/dist/redis/redis-connection.js +151 -0
- package/dist/redis/redis-connection.js.map +1 -0
- package/dist/redis/redis-manager.d.ts +28 -0
- package/dist/redis/redis-manager.d.ts.map +1 -0
- package/dist/redis/redis-manager.js +68 -0
- package/dist/redis/redis-manager.js.map +1 -0
- package/dist/redis/resp-parser.d.ts +29 -0
- package/dist/redis/resp-parser.d.ts.map +1 -0
- package/dist/redis/resp-parser.js +113 -0
- package/dist/redis/resp-parser.js.map +1 -0
- package/dist/request.d.ts +122 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +171 -0
- package/dist/request.js.map +1 -0
- package/dist/response.d.ts +208 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +375 -0
- package/dist/response.js.map +1 -0
- package/dist/router.d.ts +146 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +341 -0
- package/dist/router.js.map +1 -0
- package/dist/routing/controller.d.ts +18 -0
- package/dist/routing/controller.d.ts.map +1 -0
- package/dist/routing/controller.js +49 -0
- package/dist/routing/controller.js.map +1 -0
- package/dist/routing/named-middleware.d.ts +27 -0
- package/dist/routing/named-middleware.d.ts.map +1 -0
- package/dist/routing/named-middleware.js +41 -0
- package/dist/routing/named-middleware.js.map +1 -0
- package/dist/routing/route-builder.d.ts +54 -0
- package/dist/routing/route-builder.d.ts.map +1 -0
- package/dist/routing/route-builder.js +95 -0
- package/dist/routing/route-builder.js.map +1 -0
- package/dist/routing/route-group.d.ts +39 -0
- package/dist/routing/route-group.d.ts.map +1 -0
- package/dist/routing/route-group.js +79 -0
- package/dist/routing/route-group.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +85 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +216 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/security/csrf.d.ts +19 -0
- package/dist/security/csrf.d.ts.map +1 -0
- package/dist/security/csrf.js +69 -0
- package/dist/security/csrf.js.map +1 -0
- package/dist/security/hash.d.ts +30 -0
- package/dist/security/hash.d.ts.map +1 -0
- package/dist/security/hash.js +86 -0
- package/dist/security/hash.js.map +1 -0
- package/dist/security/headers.d.ts +56 -0
- package/dist/security/headers.d.ts.map +1 -0
- package/dist/security/headers.js +81 -0
- package/dist/security/headers.js.map +1 -0
- package/dist/security/index.d.ts +13 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +13 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/jwt.d.ts +96 -0
- package/dist/security/jwt.d.ts.map +1 -0
- package/dist/security/jwt.js +308 -0
- package/dist/security/jwt.js.map +1 -0
- package/dist/security/rate-limiter.d.ts +80 -0
- package/dist/security/rate-limiter.d.ts.map +1 -0
- package/dist/security/rate-limiter.js +127 -0
- package/dist/security/rate-limiter.js.map +1 -0
- package/dist/security/totp.d.ts +43 -0
- package/dist/security/totp.d.ts.map +1 -0
- package/dist/security/totp.js +122 -0
- package/dist/security/totp.js.map +1 -0
- package/dist/security/vault.d.ts +28 -0
- package/dist/security/vault.d.ts.map +1 -0
- package/dist/security/vault.js +73 -0
- package/dist/security/vault.js.map +1 -0
- package/dist/session/session-driver.d.ts +53 -0
- package/dist/session/session-driver.d.ts.map +1 -0
- package/dist/session/session-driver.js +132 -0
- package/dist/session/session-driver.js.map +1 -0
- package/dist/session/session-manager.d.ts +100 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +186 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/sse/index.d.ts +2 -0
- package/dist/sse/index.d.ts.map +1 -0
- package/dist/sse/index.js +2 -0
- package/dist/sse/index.js.map +1 -0
- package/dist/sse/sse.d.ts +32 -0
- package/dist/sse/sse.d.ts.map +1 -0
- package/dist/sse/sse.js +87 -0
- package/dist/sse/sse.js.map +1 -0
- package/dist/ssr/engine.d.ts +58 -0
- package/dist/ssr/engine.d.ts.map +1 -0
- package/dist/ssr/engine.js +121 -0
- package/dist/ssr/engine.js.map +1 -0
- package/dist/ssr/index.d.ts +6 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +6 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/static/static.d.ts +22 -0
- package/dist/static/static.d.ts.map +1 -0
- package/dist/static/static.js +134 -0
- package/dist/static/static.js.map +1 -0
- package/dist/storage/chunked-uploader.d.ts +53 -0
- package/dist/storage/chunked-uploader.d.ts.map +1 -0
- package/dist/storage/chunked-uploader.js +135 -0
- package/dist/storage/chunked-uploader.js.map +1 -0
- package/dist/storage/drivers/local-driver.d.ts +25 -0
- package/dist/storage/drivers/local-driver.d.ts.map +1 -0
- package/dist/storage/drivers/local-driver.js +75 -0
- package/dist/storage/drivers/local-driver.js.map +1 -0
- package/dist/storage/drivers/memory-driver.d.ts +23 -0
- package/dist/storage/drivers/memory-driver.d.ts.map +1 -0
- package/dist/storage/drivers/memory-driver.js +55 -0
- package/dist/storage/drivers/memory-driver.js.map +1 -0
- package/dist/storage/drivers/s3-driver.d.ts +34 -0
- package/dist/storage/drivers/s3-driver.d.ts.map +1 -0
- package/dist/storage/drivers/s3-driver.js +186 -0
- package/dist/storage/drivers/s3-driver.js.map +1 -0
- package/dist/storage/drivers/storage-driver.d.ts +40 -0
- package/dist/storage/drivers/storage-driver.d.ts.map +1 -0
- package/dist/storage/drivers/storage-driver.js +6 -0
- package/dist/storage/drivers/storage-driver.js.map +1 -0
- package/dist/storage/index.d.ts +13 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/multipart.d.ts +12 -0
- package/dist/storage/multipart.d.ts.map +1 -0
- package/dist/storage/multipart.js +79 -0
- package/dist/storage/multipart.js.map +1 -0
- package/dist/storage/storage-manager.d.ts +48 -0
- package/dist/storage/storage-manager.d.ts.map +1 -0
- package/dist/storage/storage-manager.js +106 -0
- package/dist/storage/storage-manager.js.map +1 -0
- package/dist/storage/uploaded-file.d.ts +40 -0
- package/dist/storage/uploaded-file.d.ts.map +1 -0
- package/dist/storage/uploaded-file.js +73 -0
- package/dist/storage/uploaded-file.js.map +1 -0
- package/dist/swagger/generator.d.ts +44 -0
- package/dist/swagger/generator.d.ts.map +1 -0
- package/dist/swagger/generator.js +117 -0
- package/dist/swagger/generator.js.map +1 -0
- package/dist/swagger/index.d.ts +8 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/index.js +8 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/swagger/swagger.d.ts +9 -0
- package/dist/swagger/swagger.d.ts.map +1 -0
- package/dist/swagger/swagger.js +32 -0
- package/dist/swagger/swagger.js.map +1 -0
- package/dist/swagger/ui.d.ts +11 -0
- package/dist/swagger/ui.d.ts.map +1 -0
- package/dist/swagger/ui.js +93 -0
- package/dist/swagger/ui.js.map +1 -0
- package/dist/tcp/hl7.d.ts +19 -0
- package/dist/tcp/hl7.d.ts.map +1 -0
- package/dist/tcp/hl7.js +42 -0
- package/dist/tcp/hl7.js.map +1 -0
- package/dist/tcp/index.d.ts +4 -0
- package/dist/tcp/index.d.ts.map +1 -0
- package/dist/tcp/index.js +4 -0
- package/dist/tcp/index.js.map +1 -0
- package/dist/tcp/mllp.d.ts +22 -0
- package/dist/tcp/mllp.d.ts.map +1 -0
- package/dist/tcp/mllp.js +44 -0
- package/dist/tcp/mllp.js.map +1 -0
- package/dist/tcp/tcp-server.d.ts +20 -0
- package/dist/tcp/tcp-server.d.ts.map +1 -0
- package/dist/tcp/tcp-server.js +59 -0
- package/dist/tcp/tcp-server.js.map +1 -0
- package/dist/tenancy/index.d.ts +2 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +2 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/tenancy/tenant.d.ts +22 -0
- package/dist/tenancy/tenant.d.ts.map +1 -0
- package/dist/tenancy/tenant.js +32 -0
- package/dist/tenancy/tenant.js.map +1 -0
- package/dist/testing/test-client.d.ts +36 -0
- package/dist/testing/test-client.d.ts.map +1 -0
- package/dist/testing/test-client.js +128 -0
- package/dist/testing/test-client.js.map +1 -0
- package/dist/validation/index.d.ts +10 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +10 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/rules-validator.d.ts +65 -0
- package/dist/validation/rules-validator.d.ts.map +1 -0
- package/dist/validation/rules-validator.js +301 -0
- package/dist/validation/rules-validator.js.map +1 -0
- package/dist/validation/schema.d.ts +34 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/schema.js +174 -0
- package/dist/validation/schema.js.map +1 -0
- package/dist/validation/vine.d.ts +23 -0
- package/dist/validation/vine.d.ts.map +1 -0
- package/dist/validation/vine.js +97 -0
- package/dist/validation/vine.js.map +1 -0
- package/dist/views/view.d.ts +51 -0
- package/dist/views/view.d.ts.map +1 -0
- package/dist/views/view.js +91 -0
- package/dist/views/view.js.map +1 -0
- package/dist/views/vite.d.ts +19 -0
- package/dist/views/vite.d.ts.map +1 -0
- package/dist/views/vite.js +60 -0
- package/dist/views/vite.js.map +1 -0
- package/dist/webhook/index.d.ts +2 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +2 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/webhook.d.ts +30 -0
- package/dist/webhook/webhook.d.ts.map +1 -0
- package/dist/webhook/webhook.js +124 -0
- package/dist/webhook/webhook.js.map +1 -0
- package/dist/ws/broadcaster.d.ts +43 -0
- package/dist/ws/broadcaster.d.ts.map +1 -0
- package/dist/ws/broadcaster.js +81 -0
- package/dist/ws/broadcaster.js.map +1 -0
- package/dist/ws/channel.d.ts +23 -0
- package/dist/ws/channel.d.ts.map +1 -0
- package/dist/ws/channel.js +27 -0
- package/dist/ws/channel.js.map +1 -0
- package/dist/ws/index.d.ts +5 -0
- package/dist/ws/index.d.ts.map +1 -0
- package/dist/ws/index.js +5 -0
- package/dist/ws/index.js.map +1 -0
- package/dist/ws/websocket-hub.d.ts +52 -0
- package/dist/ws/websocket-hub.d.ts.map +1 -0
- package/dist/ws/websocket-hub.js +187 -0
- package/dist/ws/websocket-hub.js.map +1 -0
- package/dist/ws/websocket.d.ts +43 -0
- package/dist/ws/websocket.d.ts.map +1 -0
- package/dist/ws/websocket.js +214 -0
- package/dist/ws/websocket.js.map +1 -0
- package/package.json +141 -0
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
import { TenancyContext } from '../tenancy/tenant.js';
|
|
2
|
+
/**
|
|
3
|
+
* @file model.ts
|
|
4
|
+
* @description Lucid-inspired Active Record ORM for AeroJS. Completely rebuilt with:
|
|
5
|
+
*
|
|
6
|
+
* ✅ EXISTING: hasOne, hasMany, belongsTo, softDelete, eager loading
|
|
7
|
+
* 🆕 NEW: manyToMany (pivot table), hasManyThrough
|
|
8
|
+
* 🆕 NEW: Static Lifecycle Hooks (@beforeSave, @afterCreate, etc.)
|
|
9
|
+
* 🆕 NEW: Query Scopes (Model.query().active().byBranch(1))
|
|
10
|
+
* 🆕 NEW: Computed Properties ($get prefix)
|
|
11
|
+
* 🆕 NEW: $dirty tracking (know exactly what changed)
|
|
12
|
+
* 🆕 NEW: $original + isDirty(field) helpers
|
|
13
|
+
*
|
|
14
|
+
* Hospital uses:
|
|
15
|
+
* - Doctor ↔ Specialization (manyToMany via doctor_specializations)
|
|
16
|
+
* - Hospital → Wards → Beds (hasManyThrough)
|
|
17
|
+
* - Patient.query().active().opd().byBranch(1).paginate(1, 20)
|
|
18
|
+
* - @beforeCreate() → generate MRN
|
|
19
|
+
* - @beforeDelete() → block hard-delete on medical records
|
|
20
|
+
*/
|
|
21
|
+
import { Database } from './connection.js';
|
|
22
|
+
import { QueryBuilder } from './query-builder.js';
|
|
23
|
+
import { NotFoundError } from '../core/errors.js';
|
|
24
|
+
import { getHookRegistry } from './model-hooks.js';
|
|
25
|
+
// ─── Relation Class ───────────────────────────────────────────────────────────
|
|
26
|
+
export class Relation {
|
|
27
|
+
type;
|
|
28
|
+
RelatedModel;
|
|
29
|
+
foreignKey;
|
|
30
|
+
localKey;
|
|
31
|
+
parentInstance;
|
|
32
|
+
pivotTable;
|
|
33
|
+
pivotForeignKey;
|
|
34
|
+
pivotRelatedKey;
|
|
35
|
+
pivotColumns;
|
|
36
|
+
ThroughModel;
|
|
37
|
+
throughForeignKey;
|
|
38
|
+
throughLocalKey;
|
|
39
|
+
constructor(def) {
|
|
40
|
+
this.type = def.type;
|
|
41
|
+
this.RelatedModel = def.RelatedModel;
|
|
42
|
+
this.foreignKey = def.foreignKey;
|
|
43
|
+
this.localKey = def.localKey;
|
|
44
|
+
this.parentInstance = def.parentInstance;
|
|
45
|
+
this.pivotTable = def.pivotTable;
|
|
46
|
+
this.pivotForeignKey = def.pivotForeignKey;
|
|
47
|
+
this.pivotRelatedKey = def.pivotRelatedKey;
|
|
48
|
+
this.pivotColumns = def.pivotColumns;
|
|
49
|
+
this.ThroughModel = def.ThroughModel;
|
|
50
|
+
this.throughForeignKey = def.throughForeignKey;
|
|
51
|
+
this.throughLocalKey = def.throughLocalKey;
|
|
52
|
+
}
|
|
53
|
+
async then(onfulfilled, onrejected) {
|
|
54
|
+
try {
|
|
55
|
+
const result = await this._resolve();
|
|
56
|
+
return onfulfilled ? onfulfilled(result) : result;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (onrejected)
|
|
60
|
+
return onrejected(err);
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async _resolve() {
|
|
65
|
+
const qb = this.RelatedModel.query();
|
|
66
|
+
switch (this.type) {
|
|
67
|
+
case 'belongsTo': {
|
|
68
|
+
const foreignVal = this.parentInstance.get(this.foreignKey);
|
|
69
|
+
if (foreignVal === undefined || foreignVal === null)
|
|
70
|
+
return null;
|
|
71
|
+
return qb.where(this.localKey, foreignVal).first();
|
|
72
|
+
}
|
|
73
|
+
case 'hasOne': {
|
|
74
|
+
const localVal = this.parentInstance.get(this.localKey);
|
|
75
|
+
if (localVal === undefined || localVal === null)
|
|
76
|
+
return null;
|
|
77
|
+
return qb.where(this.foreignKey, localVal).first();
|
|
78
|
+
}
|
|
79
|
+
case 'hasMany': {
|
|
80
|
+
const localVal = this.parentInstance.get(this.localKey);
|
|
81
|
+
if (localVal === undefined || localVal === null)
|
|
82
|
+
return [];
|
|
83
|
+
return qb.where(this.foreignKey, localVal).get();
|
|
84
|
+
}
|
|
85
|
+
case 'manyToMany': {
|
|
86
|
+
return this._resolveManyToMany();
|
|
87
|
+
}
|
|
88
|
+
case 'hasManyThrough': {
|
|
89
|
+
return this._resolveHasManyThrough();
|
|
90
|
+
}
|
|
91
|
+
default:
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async _resolveManyToMany() {
|
|
96
|
+
const localVal = this.parentInstance.get(this.localKey);
|
|
97
|
+
if (localVal === undefined || localVal === null)
|
|
98
|
+
return [];
|
|
99
|
+
const pivotTable = this.pivotTable;
|
|
100
|
+
const pivotFK = this.pivotForeignKey;
|
|
101
|
+
const pivotRK = this.pivotRelatedKey;
|
|
102
|
+
const relatedTable = this.RelatedModel.getTable();
|
|
103
|
+
const relatedPK = this.RelatedModel.primaryKey;
|
|
104
|
+
const pivotExtra = this.pivotColumns ?? [];
|
|
105
|
+
// SELECT related.*, pivot.col1, pivot.col2 FROM related
|
|
106
|
+
// INNER JOIN pivot ON related.id = pivot.pivot_related_key
|
|
107
|
+
// WHERE pivot.pivot_foreign_key = localVal
|
|
108
|
+
const selectCols = [
|
|
109
|
+
`${relatedTable}.*`,
|
|
110
|
+
...pivotExtra.map((c) => `${pivotTable}.${c} as pivot_${c}`),
|
|
111
|
+
];
|
|
112
|
+
const rows = await Database.table(relatedTable)
|
|
113
|
+
.select(...selectCols)
|
|
114
|
+
.join(pivotTable, `${relatedTable}.${relatedPK}`, '=', `${pivotTable}.${pivotRK}`)
|
|
115
|
+
.where(`${pivotTable}.${pivotFK}`, localVal)
|
|
116
|
+
.get();
|
|
117
|
+
return rows.map((r) => {
|
|
118
|
+
const inst = new this.RelatedModel(r);
|
|
119
|
+
inst._exists = true;
|
|
120
|
+
inst._original = { ...r };
|
|
121
|
+
// Attach pivot data
|
|
122
|
+
if (pivotExtra.length > 0) {
|
|
123
|
+
const pivot = {};
|
|
124
|
+
for (const c of pivotExtra) {
|
|
125
|
+
pivot[c] = r[`pivot_${c}`];
|
|
126
|
+
delete inst._attributes[`pivot_${c}`];
|
|
127
|
+
}
|
|
128
|
+
inst._relations['$pivot'] = pivot;
|
|
129
|
+
}
|
|
130
|
+
return inst;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async _resolveHasManyThrough() {
|
|
134
|
+
const localVal = this.parentInstance.get(this.localKey);
|
|
135
|
+
if (localVal === undefined || localVal === null)
|
|
136
|
+
return [];
|
|
137
|
+
const throughTable = this.ThroughModel.getTable();
|
|
138
|
+
const throughFK = this.throughForeignKey; // FK on through model → parent
|
|
139
|
+
const throughLK = this.throughLocalKey; // FK on related model → through
|
|
140
|
+
const relatedTable = this.RelatedModel.getTable();
|
|
141
|
+
const relatedPK = this.RelatedModel.primaryKey;
|
|
142
|
+
// SELECT related.* FROM related
|
|
143
|
+
// INNER JOIN through ON related.through_fk = through.id
|
|
144
|
+
// WHERE through.parent_fk = localVal
|
|
145
|
+
const rows = await Database.table(relatedTable)
|
|
146
|
+
.select(`${relatedTable}.*`)
|
|
147
|
+
.join(throughTable, `${relatedTable}.${throughLK}`, '=', `${throughTable}.${this.ThroughModel.primaryKey}`)
|
|
148
|
+
.where(`${throughTable}.${throughFK}`, localVal)
|
|
149
|
+
.get();
|
|
150
|
+
return rows.map((r) => {
|
|
151
|
+
const inst = new this.RelatedModel(r);
|
|
152
|
+
inst._exists = true;
|
|
153
|
+
inst._original = { ...r };
|
|
154
|
+
return inst;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ─── Computed Property Decorator ──────────────────────────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Marks a getter as a "computed property" — included in toJSON() output.
|
|
161
|
+
* Computed properties are derived from other attributes (not stored in DB).
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* class Patient extends Model {
|
|
165
|
+
* @computed()
|
|
166
|
+
* get age(): number {
|
|
167
|
+
* const dob = new Date(this.date_of_birth);
|
|
168
|
+
* return Math.floor((Date.now() - dob.getTime()) / (365.25 * 24 * 3600 * 1000));
|
|
169
|
+
* }
|
|
170
|
+
*
|
|
171
|
+
* @computed()
|
|
172
|
+
* get fullName(): string {
|
|
173
|
+
* return `${this.first_name} ${this.last_name}`;
|
|
174
|
+
* }
|
|
175
|
+
* }
|
|
176
|
+
*/
|
|
177
|
+
export function computed() {
|
|
178
|
+
return function (target, propertyKey) {
|
|
179
|
+
const ModelProto = target;
|
|
180
|
+
if (!ModelProto._computedProperties) {
|
|
181
|
+
ModelProto._computedProperties = [];
|
|
182
|
+
}
|
|
183
|
+
ModelProto._computedProperties.push(String(propertyKey));
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// ─── Model Base Class ─────────────────────────────────────────────────────────
|
|
187
|
+
export class Model {
|
|
188
|
+
// ── Static Configuration ────────────────────────────────────────────────────
|
|
189
|
+
static table = '';
|
|
190
|
+
static primaryKey = 'id';
|
|
191
|
+
static hidden = [];
|
|
192
|
+
static fillable = [];
|
|
193
|
+
static tenanted = false;
|
|
194
|
+
static tenantColumn = 'tenant_id';
|
|
195
|
+
static softDeletes = false;
|
|
196
|
+
static timestamps = true;
|
|
197
|
+
static connection = 'default';
|
|
198
|
+
/**
|
|
199
|
+
* Global query scopes automatically applied to every query.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* class Patient extends Model {
|
|
203
|
+
* public static override globalScopes = [BranchScope, ActiveScope];
|
|
204
|
+
* }
|
|
205
|
+
*/
|
|
206
|
+
static globalScopes = [];
|
|
207
|
+
// ── Instance State ──────────────────────────────────────────────────────────
|
|
208
|
+
_attributes;
|
|
209
|
+
_original;
|
|
210
|
+
_dirty;
|
|
211
|
+
_exists = false;
|
|
212
|
+
_relations = {};
|
|
213
|
+
constructor(attributes = {}) {
|
|
214
|
+
this._attributes = {};
|
|
215
|
+
this._original = {};
|
|
216
|
+
this._dirty = {};
|
|
217
|
+
this._exists = false;
|
|
218
|
+
this._relations = {};
|
|
219
|
+
this.fill(attributes);
|
|
220
|
+
return new Proxy(this, {
|
|
221
|
+
get(target, prop, receiver) {
|
|
222
|
+
if (typeof prop === 'symbol')
|
|
223
|
+
return Reflect.get(target, prop, receiver);
|
|
224
|
+
if (target._relations && prop in target._relations)
|
|
225
|
+
return target._relations[prop];
|
|
226
|
+
if (prop in target) {
|
|
227
|
+
const val = Reflect.get(target, prop, receiver);
|
|
228
|
+
if (typeof val === 'function' && prop !== 'constructor')
|
|
229
|
+
return val.bind(target);
|
|
230
|
+
return val;
|
|
231
|
+
}
|
|
232
|
+
return target.get(String(prop));
|
|
233
|
+
},
|
|
234
|
+
set(target, prop, value, receiver) {
|
|
235
|
+
if (typeof prop === 'symbol')
|
|
236
|
+
return Reflect.set(target, prop, value, receiver);
|
|
237
|
+
if (prop in target &&
|
|
238
|
+
!['_attributes', '_original', '_dirty', '_relations', '_exists'].includes(String(prop))) {
|
|
239
|
+
const desc = Object.getOwnPropertyDescriptor(target, prop) ||
|
|
240
|
+
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop);
|
|
241
|
+
if (desc && (desc.get || desc.set))
|
|
242
|
+
return Reflect.set(target, prop, value, receiver);
|
|
243
|
+
}
|
|
244
|
+
target.set(String(prop), value);
|
|
245
|
+
return true;
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
fill(attributes) {
|
|
250
|
+
const raw = attributes instanceof Model ? attributes.getAttributes() : attributes;
|
|
251
|
+
const Ctor = this.constructor;
|
|
252
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
253
|
+
if (Ctor.fillable.length === 0 || Ctor.fillable.includes(key)) {
|
|
254
|
+
this._attributes[key] = value;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
get(key) {
|
|
260
|
+
return this._attributes[key] !== undefined ? this._attributes[key] : null;
|
|
261
|
+
}
|
|
262
|
+
set(key, value) {
|
|
263
|
+
if (this._exists && this._original[key] !== value) {
|
|
264
|
+
this._dirty[key] = true;
|
|
265
|
+
}
|
|
266
|
+
this._attributes[key] = value;
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
/** Returns true if the given field has changed since last save. */
|
|
270
|
+
isDirty(field) {
|
|
271
|
+
if (field)
|
|
272
|
+
return !!this._dirty[field];
|
|
273
|
+
return Object.keys(this._dirty).length > 0;
|
|
274
|
+
}
|
|
275
|
+
/** Returns all changed attributes since last save. */
|
|
276
|
+
getDirty() {
|
|
277
|
+
const result = {};
|
|
278
|
+
for (const key of Object.keys(this._dirty)) {
|
|
279
|
+
result[key] = this._attributes[key];
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
getAttributes() {
|
|
284
|
+
return { ...this._attributes };
|
|
285
|
+
}
|
|
286
|
+
get attributes() {
|
|
287
|
+
return this.getAttributes();
|
|
288
|
+
}
|
|
289
|
+
static getTable() {
|
|
290
|
+
if (this.table)
|
|
291
|
+
return this.table;
|
|
292
|
+
const name = this.name || 'Model';
|
|
293
|
+
const snake = name.replace(/([A-Z])/g, (m, p1, offset) => offset > 0 ? '_' + p1.toLowerCase() : p1.toLowerCase());
|
|
294
|
+
return snake.endsWith('s') ? snake : `${snake}s`;
|
|
295
|
+
}
|
|
296
|
+
// ─── Relationships ──────────────────────────────────────────────────────────
|
|
297
|
+
hasMany(RelatedModel, foreignKey, localKey) {
|
|
298
|
+
return new Relation({
|
|
299
|
+
type: 'hasMany',
|
|
300
|
+
RelatedModel,
|
|
301
|
+
foreignKey: foreignKey || `${this.constructor.name.toLowerCase()}_id`,
|
|
302
|
+
localKey: localKey || this.constructor.primaryKey,
|
|
303
|
+
parentInstance: this,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
hasOne(RelatedModel, foreignKey, localKey) {
|
|
307
|
+
return new Relation({
|
|
308
|
+
type: 'hasOne',
|
|
309
|
+
RelatedModel,
|
|
310
|
+
foreignKey: foreignKey || `${this.constructor.name.toLowerCase()}_id`,
|
|
311
|
+
localKey: localKey || this.constructor.primaryKey,
|
|
312
|
+
parentInstance: this,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
belongsTo(RelatedModel, foreignKey, ownerKey) {
|
|
316
|
+
return new Relation({
|
|
317
|
+
type: 'belongsTo',
|
|
318
|
+
RelatedModel,
|
|
319
|
+
foreignKey: foreignKey || `${RelatedModel.name.toLowerCase()}_id`,
|
|
320
|
+
localKey: ownerKey || RelatedModel.primaryKey,
|
|
321
|
+
parentInstance: this,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* ManyToMany Relationship via a pivot table.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* // Doctor ↔ Specialization (pivot: doctor_specializations)
|
|
329
|
+
* public specializations() {
|
|
330
|
+
* return this.manyToMany(Specialization, {
|
|
331
|
+
* pivotTable: 'doctor_specializations',
|
|
332
|
+
* pivotForeignKey: 'doctor_id',
|
|
333
|
+
* pivotRelatedKey: 'specialization_id',
|
|
334
|
+
* pivotColumns: ['certified_at'], // extra pivot columns
|
|
335
|
+
* });
|
|
336
|
+
* }
|
|
337
|
+
*
|
|
338
|
+
* // Patient ↔ ICD-11 Diagnosis (pivot: patient_diagnoses)
|
|
339
|
+
* public diagnoses() {
|
|
340
|
+
* return this.manyToMany(Diagnosis, {
|
|
341
|
+
* pivotTable: 'patient_diagnoses',
|
|
342
|
+
* pivotForeignKey: 'patient_id',
|
|
343
|
+
* pivotRelatedKey: 'diagnosis_code',
|
|
344
|
+
* });
|
|
345
|
+
* }
|
|
346
|
+
*
|
|
347
|
+
* // OT Surgery ↔ Surgeon team (pivot: ot_surgery_doctors)
|
|
348
|
+
* public surgeons() {
|
|
349
|
+
* return this.manyToMany(Doctor, {
|
|
350
|
+
* pivotTable: 'ot_surgery_doctors',
|
|
351
|
+
* pivotForeignKey: 'surgery_id',
|
|
352
|
+
* pivotRelatedKey: 'doctor_id',
|
|
353
|
+
* pivotColumns: ['role'], // primary_surgeon, assistant, anesthesiologist
|
|
354
|
+
* });
|
|
355
|
+
* }
|
|
356
|
+
*/
|
|
357
|
+
manyToMany(RelatedModel, options = {}) {
|
|
358
|
+
const Ctor = this.constructor;
|
|
359
|
+
const thisName = Ctor.name.toLowerCase();
|
|
360
|
+
const relatedName = RelatedModel.name.toLowerCase();
|
|
361
|
+
const pivotTable = options.pivotTable ??
|
|
362
|
+
[thisName, relatedName].sort().join('_') + 's';
|
|
363
|
+
return new Relation({
|
|
364
|
+
type: 'manyToMany',
|
|
365
|
+
RelatedModel,
|
|
366
|
+
foreignKey: options.localKey || Ctor.primaryKey,
|
|
367
|
+
localKey: options.localKey || Ctor.primaryKey,
|
|
368
|
+
parentInstance: this,
|
|
369
|
+
pivotTable,
|
|
370
|
+
pivotForeignKey: options.pivotForeignKey ?? `${thisName}_id`,
|
|
371
|
+
pivotRelatedKey: options.pivotRelatedKey ?? `${relatedName}_id`,
|
|
372
|
+
pivotColumns: options.pivotColumns ?? [],
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* HasManyThrough Relationship.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* // Hospital has many Beds through Wards
|
|
380
|
+
* public beds() {
|
|
381
|
+
* return this.hasManyThrough(Bed, Ward, {
|
|
382
|
+
* foreignKey: 'hospital_id', // FK on Ward pointing to Hospital
|
|
383
|
+
* throughForeignKey: 'ward_id', // FK on Bed pointing to Ward
|
|
384
|
+
* });
|
|
385
|
+
* }
|
|
386
|
+
*
|
|
387
|
+
* // Country has many Patients through Hospitals
|
|
388
|
+
* public patients() {
|
|
389
|
+
* return this.hasManyThrough(Patient, Hospital, {
|
|
390
|
+
* foreignKey: 'country_id',
|
|
391
|
+
* throughForeignKey: 'hospital_id',
|
|
392
|
+
* });
|
|
393
|
+
* }
|
|
394
|
+
*/
|
|
395
|
+
hasManyThrough(RelatedModel, ThroughModel, options = {}) {
|
|
396
|
+
const Ctor = this.constructor;
|
|
397
|
+
const throughName = ThroughModel.name.toLowerCase();
|
|
398
|
+
const relatedName = RelatedModel.name.toLowerCase();
|
|
399
|
+
return new Relation({
|
|
400
|
+
type: 'hasManyThrough',
|
|
401
|
+
RelatedModel,
|
|
402
|
+
ThroughModel,
|
|
403
|
+
foreignKey: options.foreignKey ?? `${Ctor.name.toLowerCase()}_id`,
|
|
404
|
+
localKey: options.localKey ?? Ctor.primaryKey,
|
|
405
|
+
parentInstance: this,
|
|
406
|
+
throughForeignKey: options.foreignKey ?? `${Ctor.name.toLowerCase()}_id`,
|
|
407
|
+
throughLocalKey: options.throughForeignKey ?? `${throughName}_id`,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
// ─── ManyToMany Pivot Helpers ───────────────────────────────────────────────
|
|
411
|
+
/**
|
|
412
|
+
* Attach related records to the ManyToMany pivot table.
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* // Attach specializations to doctor
|
|
416
|
+
* await doctor.attach('specializations', [1, 3, 5]);
|
|
417
|
+
*
|
|
418
|
+
* // Attach with pivot data (e.g. certified_at date)
|
|
419
|
+
* await doctor.attach('specializations', { 1: { certified_at: '2024-01-01' } });
|
|
420
|
+
*/
|
|
421
|
+
async attach(relationName, idsOrMap, connectionOrAdapter) {
|
|
422
|
+
const rel = this[relationName]();
|
|
423
|
+
if (rel.type !== 'manyToMany')
|
|
424
|
+
throw new Error(`attach() only works on manyToMany relations`);
|
|
425
|
+
const Ctor = this.constructor;
|
|
426
|
+
const localVal = this.get(Ctor.primaryKey);
|
|
427
|
+
const conn = connectionOrAdapter || Ctor.connection;
|
|
428
|
+
const table = rel.pivotTable;
|
|
429
|
+
const rows = [];
|
|
430
|
+
if (Array.isArray(idsOrMap)) {
|
|
431
|
+
for (const relatedId of idsOrMap) {
|
|
432
|
+
rows.push({ [rel.pivotForeignKey]: localVal, [rel.pivotRelatedKey]: relatedId });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
for (const [relatedId, pivotData] of Object.entries(idsOrMap)) {
|
|
437
|
+
rows.push({ [rel.pivotForeignKey]: localVal, [rel.pivotRelatedKey]: relatedId, ...pivotData });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
for (const row of rows) {
|
|
441
|
+
await Database.table(table, conn).insert(row);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Detach related records from the ManyToMany pivot table.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* await doctor.detach('specializations', [3]); // detach specific
|
|
449
|
+
* await doctor.detach('specializations'); // detach all
|
|
450
|
+
*/
|
|
451
|
+
async detach(relationName, ids, connectionOrAdapter) {
|
|
452
|
+
const rel = this[relationName]();
|
|
453
|
+
if (rel.type !== 'manyToMany')
|
|
454
|
+
throw new Error(`detach() only works on manyToMany relations`);
|
|
455
|
+
const Ctor = this.constructor;
|
|
456
|
+
const localVal = this.get(Ctor.primaryKey);
|
|
457
|
+
const conn = connectionOrAdapter || Ctor.connection;
|
|
458
|
+
let qb = Database.table(rel.pivotTable, conn).where(rel.pivotForeignKey, localVal);
|
|
459
|
+
if (ids && ids.length > 0) {
|
|
460
|
+
qb = qb.whereIn(rel.pivotRelatedKey, ids);
|
|
461
|
+
}
|
|
462
|
+
await qb.delete();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Sync pivot table — detach all then re-attach.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* // Sync a doctor's specializations (replaces all existing)
|
|
469
|
+
* await doctor.sync('specializations', [1, 3, 5]);
|
|
470
|
+
*/
|
|
471
|
+
async sync(relationName, idsOrMap, connectionOrAdapter) {
|
|
472
|
+
await this.detach(relationName, undefined, connectionOrAdapter);
|
|
473
|
+
await this.attach(relationName, idsOrMap, connectionOrAdapter);
|
|
474
|
+
}
|
|
475
|
+
// ─── Static Query Builder ───────────────────────────────────────────────────
|
|
476
|
+
/**
|
|
477
|
+
* Starts a model-level QueryBuilder with full support for:
|
|
478
|
+
* - Eager loading (.with())
|
|
479
|
+
* - Soft deletes (.withTrashed(), .onlyTrashed())
|
|
480
|
+
* - Query scopes (.active(), .byBranch(), etc.)
|
|
481
|
+
* - Global scopes (auto-applied)
|
|
482
|
+
*/
|
|
483
|
+
static query(connectionOrAdapter) {
|
|
484
|
+
const table = this.getTable();
|
|
485
|
+
const qb = Database.table(table, connectionOrAdapter || this.connection);
|
|
486
|
+
const ModelClass = this;
|
|
487
|
+
// Apply multi-tenancy auto-scope
|
|
488
|
+
if (this.tenanted) {
|
|
489
|
+
const tenantId = TenancyContext.getStore();
|
|
490
|
+
if (tenantId !== undefined) {
|
|
491
|
+
qb.where(this.tenantColumn, tenantId);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
qb._eagerLoads = [];
|
|
495
|
+
qb._withTrashed = false;
|
|
496
|
+
qb._onlyTrashed = false;
|
|
497
|
+
qb.with = (...relations) => {
|
|
498
|
+
qb._eagerLoads.push(...relations);
|
|
499
|
+
return qb;
|
|
500
|
+
};
|
|
501
|
+
qb.preload = (...relations) => qb.with(...relations);
|
|
502
|
+
qb.withTrashed = () => { qb._withTrashed = true; return qb; };
|
|
503
|
+
qb.onlyTrashed = () => { qb._onlyTrashed = true; return qb; };
|
|
504
|
+
// ── Query Scope Binding ─────────────────────────────────────────────────
|
|
505
|
+
// Auto-bind static scope methods: Model.scopeActive() → qb.active()
|
|
506
|
+
const proto = ModelClass;
|
|
507
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
508
|
+
if (key.startsWith('scope') && typeof proto[key] === 'function') {
|
|
509
|
+
const scopeName = key.charAt(5).toLowerCase() + key.slice(6); // scopeActive → active
|
|
510
|
+
qb[scopeName] = (...args) => {
|
|
511
|
+
proto[key](qb, ...args);
|
|
512
|
+
return qb;
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// ── Apply global scopes ─────────────────────────────────────────────────
|
|
517
|
+
for (const scope of ModelClass.globalScopes) {
|
|
518
|
+
scope(qb);
|
|
519
|
+
}
|
|
520
|
+
const applySoftDeleteFilter = () => {
|
|
521
|
+
if (ModelClass.softDeletes) {
|
|
522
|
+
if (qb._onlyTrashed) {
|
|
523
|
+
qb.whereNotNull('deleted_at');
|
|
524
|
+
}
|
|
525
|
+
else if (!qb._withTrashed) {
|
|
526
|
+
qb.whereNull('deleted_at');
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
const hydrateRow = (r) => {
|
|
531
|
+
const inst = new ModelClass(r);
|
|
532
|
+
inst._exists = true;
|
|
533
|
+
inst._original = { ...r };
|
|
534
|
+
inst._dirty = {};
|
|
535
|
+
return inst;
|
|
536
|
+
};
|
|
537
|
+
const originalGet = qb.get.bind(qb);
|
|
538
|
+
const originalFirst = qb.first.bind(qb);
|
|
539
|
+
qb.get = async () => {
|
|
540
|
+
applySoftDeleteFilter();
|
|
541
|
+
// beforeFetch hook
|
|
542
|
+
const registry = getHookRegistry(ModelClass);
|
|
543
|
+
await registry.execute('beforeFetch', qb);
|
|
544
|
+
const rows = await originalGet();
|
|
545
|
+
const instances = rows.map(hydrateRow);
|
|
546
|
+
// afterFetch hook
|
|
547
|
+
await registry.execute('afterFetch', instances);
|
|
548
|
+
// Eager Load
|
|
549
|
+
if (qb._eagerLoads.length > 0 && instances.length > 0) {
|
|
550
|
+
for (const relationName of qb._eagerLoads) {
|
|
551
|
+
await ModelClass.eagerLoadRelation(instances, relationName);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return instances;
|
|
555
|
+
};
|
|
556
|
+
qb.first = async () => {
|
|
557
|
+
const prevLimit = qb.limitCount;
|
|
558
|
+
qb.limitCount = 1;
|
|
559
|
+
const rows = await qb.get();
|
|
560
|
+
qb.limitCount = prevLimit;
|
|
561
|
+
const inst = rows[0] || null;
|
|
562
|
+
if (inst) {
|
|
563
|
+
const registry = getHookRegistry(ModelClass);
|
|
564
|
+
await registry.execute('afterFind', inst);
|
|
565
|
+
}
|
|
566
|
+
return inst;
|
|
567
|
+
};
|
|
568
|
+
qb.firstOrFail = async () => {
|
|
569
|
+
const inst = await qb.first();
|
|
570
|
+
if (!inst)
|
|
571
|
+
throw new NotFoundError(`${ModelClass.name} not found`);
|
|
572
|
+
return inst;
|
|
573
|
+
};
|
|
574
|
+
return qb;
|
|
575
|
+
}
|
|
576
|
+
// ─── Eager Loading ──────────────────────────────────────────────────────────
|
|
577
|
+
static async eagerLoadRelation(instances, relationName) {
|
|
578
|
+
if (instances.length === 0)
|
|
579
|
+
return;
|
|
580
|
+
const sample = instances[0];
|
|
581
|
+
if (typeof sample[relationName] !== 'function')
|
|
582
|
+
return;
|
|
583
|
+
const relation = sample[relationName]();
|
|
584
|
+
if (relation.type === 'manyToMany') {
|
|
585
|
+
await this._eagerLoadManyToMany(instances, relationName, relation);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (relation.type === 'hasManyThrough') {
|
|
589
|
+
await this._eagerLoadHasManyThrough(instances, relationName, relation);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (relation.type === 'belongsTo') {
|
|
593
|
+
const foreignKeys = instances
|
|
594
|
+
.map((i) => i.get(relation.foreignKey))
|
|
595
|
+
.filter((k) => k !== null && k !== undefined);
|
|
596
|
+
if (foreignKeys.length === 0) {
|
|
597
|
+
for (const inst of instances)
|
|
598
|
+
inst._relations[relationName] = null;
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const relatedRows = await relation.RelatedModel.query()
|
|
602
|
+
.whereIn(relation.localKey, foreignKeys)
|
|
603
|
+
.get();
|
|
604
|
+
for (const inst of instances) {
|
|
605
|
+
const fv = inst.get(relation.foreignKey);
|
|
606
|
+
inst._relations[relationName] =
|
|
607
|
+
relatedRows.find((r) => r.get(relation.localKey) === fv) || null;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
const parentKeys = instances
|
|
612
|
+
.map((i) => i.get(relation.localKey))
|
|
613
|
+
.filter((k) => k !== null && k !== undefined);
|
|
614
|
+
if (parentKeys.length === 0) {
|
|
615
|
+
for (const inst of instances) {
|
|
616
|
+
inst._relations[relationName] = relation.type === 'hasMany' ? [] : null;
|
|
617
|
+
}
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const relatedRows = await relation.RelatedModel.query()
|
|
621
|
+
.whereIn(relation.foreignKey, parentKeys)
|
|
622
|
+
.get();
|
|
623
|
+
for (const inst of instances) {
|
|
624
|
+
const pv = inst.get(relation.localKey);
|
|
625
|
+
if (relation.type === 'hasMany') {
|
|
626
|
+
inst._relations[relationName] = relatedRows.filter((r) => r.get(relation.foreignKey) === pv);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
inst._relations[relationName] =
|
|
630
|
+
relatedRows.find((r) => r.get(relation.foreignKey) === pv) || null;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
static async _eagerLoadManyToMany(instances, relationName, rel) {
|
|
636
|
+
const localKey = rel.localKey;
|
|
637
|
+
const parentKeys = instances.map((i) => i.get(localKey)).filter((k) => k != null);
|
|
638
|
+
if (parentKeys.length === 0) {
|
|
639
|
+
for (const inst of instances)
|
|
640
|
+
inst._relations[relationName] = [];
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const relatedTable = rel.RelatedModel.getTable();
|
|
644
|
+
const relatedPK = rel.RelatedModel.primaryKey;
|
|
645
|
+
const pivotExtra = rel.pivotColumns ?? [];
|
|
646
|
+
const selectCols = [
|
|
647
|
+
`${relatedTable}.*`,
|
|
648
|
+
`${rel.pivotTable}.${rel.pivotForeignKey} as __pivot_fk`,
|
|
649
|
+
...pivotExtra.map((c) => `${rel.pivotTable}.${c} as pivot_${c}`),
|
|
650
|
+
];
|
|
651
|
+
const rows = await Database.table(relatedTable)
|
|
652
|
+
.select(...selectCols)
|
|
653
|
+
.join(rel.pivotTable, `${relatedTable}.${relatedPK}`, '=', `${rel.pivotTable}.${rel.pivotRelatedKey}`)
|
|
654
|
+
.whereIn(`${rel.pivotTable}.${rel.pivotForeignKey}`, parentKeys)
|
|
655
|
+
.get();
|
|
656
|
+
const map = new Map();
|
|
657
|
+
for (const inst of instances) {
|
|
658
|
+
map.set(inst.get(localKey), []);
|
|
659
|
+
}
|
|
660
|
+
for (const row of rows) {
|
|
661
|
+
const parentKey = row.__pivot_fk;
|
|
662
|
+
const inst = new rel.RelatedModel(row);
|
|
663
|
+
inst._exists = true;
|
|
664
|
+
inst._original = { ...row };
|
|
665
|
+
delete inst._attributes['__pivot_fk'];
|
|
666
|
+
if (pivotExtra.length > 0) {
|
|
667
|
+
const pivot = {};
|
|
668
|
+
for (const c of pivotExtra) {
|
|
669
|
+
pivot[c] = row[`pivot_${c}`];
|
|
670
|
+
delete inst._attributes[`pivot_${c}`];
|
|
671
|
+
}
|
|
672
|
+
inst._relations['$pivot'] = pivot;
|
|
673
|
+
}
|
|
674
|
+
const list = map.get(parentKey);
|
|
675
|
+
if (list)
|
|
676
|
+
list.push(inst);
|
|
677
|
+
}
|
|
678
|
+
for (const inst of instances) {
|
|
679
|
+
inst._relations[relationName] = map.get(inst.get(localKey)) ?? [];
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
static async _eagerLoadHasManyThrough(instances, relationName, rel) {
|
|
683
|
+
const parentKeys = instances.map((i) => i.get(rel.localKey)).filter((k) => k != null);
|
|
684
|
+
if (parentKeys.length === 0) {
|
|
685
|
+
for (const inst of instances)
|
|
686
|
+
inst._relations[relationName] = [];
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const relatedTable = rel.RelatedModel.getTable();
|
|
690
|
+
const throughTable = rel.ThroughModel.getTable();
|
|
691
|
+
const rows = await Database.table(relatedTable)
|
|
692
|
+
.select(`${relatedTable}.*`, `${throughTable}.${rel.throughForeignKey} as __through_fk`)
|
|
693
|
+
.join(throughTable, `${relatedTable}.${rel.throughLocalKey}`, '=', `${throughTable}.${rel.ThroughModel.primaryKey}`)
|
|
694
|
+
.whereIn(`${throughTable}.${rel.throughForeignKey}`, parentKeys)
|
|
695
|
+
.get();
|
|
696
|
+
const map = new Map();
|
|
697
|
+
for (const inst of instances)
|
|
698
|
+
map.set(inst.get(rel.localKey), []);
|
|
699
|
+
for (const row of rows) {
|
|
700
|
+
const parentKey = row.__through_fk;
|
|
701
|
+
const inst = new rel.RelatedModel(row);
|
|
702
|
+
inst._exists = true;
|
|
703
|
+
inst._original = { ...row };
|
|
704
|
+
delete inst._attributes['__through_fk'];
|
|
705
|
+
const list = map.get(parentKey);
|
|
706
|
+
if (list)
|
|
707
|
+
list.push(inst);
|
|
708
|
+
}
|
|
709
|
+
for (const inst of instances) {
|
|
710
|
+
inst._relations[relationName] = map.get(inst.get(rel.localKey)) ?? [];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
// ─── Static Finders ─────────────────────────────────────────────────────────
|
|
714
|
+
static async find(id) {
|
|
715
|
+
return this.query().where(this.primaryKey, id).first();
|
|
716
|
+
}
|
|
717
|
+
static async findOrFail(id) {
|
|
718
|
+
const inst = await this.find(id);
|
|
719
|
+
if (!inst)
|
|
720
|
+
throw new NotFoundError(`${this.name} with id ${id} not found`);
|
|
721
|
+
return inst;
|
|
722
|
+
}
|
|
723
|
+
static async findBy(column, value) {
|
|
724
|
+
return this.query().where(column, value).first();
|
|
725
|
+
}
|
|
726
|
+
static async all() {
|
|
727
|
+
return this.query().get();
|
|
728
|
+
}
|
|
729
|
+
static where(column, operatorOrValue, value) {
|
|
730
|
+
return this.query().where(column, operatorOrValue, value);
|
|
731
|
+
}
|
|
732
|
+
static whereIn(column, values) {
|
|
733
|
+
return this.query().whereIn(column, values);
|
|
734
|
+
}
|
|
735
|
+
static whereNotIn(column, values) {
|
|
736
|
+
return this.query().whereNotIn(column, values);
|
|
737
|
+
}
|
|
738
|
+
static whereBetween(column, range) {
|
|
739
|
+
return this.query().whereBetween(column, range);
|
|
740
|
+
}
|
|
741
|
+
static whereNotBetween(column, range) {
|
|
742
|
+
return this.query().whereNotBetween(column, range);
|
|
743
|
+
}
|
|
744
|
+
static whereLike(column, pattern) {
|
|
745
|
+
return this.query().whereLike(column, pattern);
|
|
746
|
+
}
|
|
747
|
+
static whereNull(column) {
|
|
748
|
+
return this.query().whereNull(column);
|
|
749
|
+
}
|
|
750
|
+
static whereNotNull(column) {
|
|
751
|
+
return this.query().whereNotNull(column);
|
|
752
|
+
}
|
|
753
|
+
static whereRaw(sql, bindings = []) {
|
|
754
|
+
return this.query().whereRaw(sql, bindings);
|
|
755
|
+
}
|
|
756
|
+
static orderBy(column, direction = 'ASC') {
|
|
757
|
+
return this.query().orderBy(column, direction);
|
|
758
|
+
}
|
|
759
|
+
static limit(count) {
|
|
760
|
+
return this.query().limit(count);
|
|
761
|
+
}
|
|
762
|
+
static async paginate(page = 1, perPage = 15) {
|
|
763
|
+
return this.query().paginate(page, perPage);
|
|
764
|
+
}
|
|
765
|
+
static with(...relations) {
|
|
766
|
+
return this.query().with(...relations);
|
|
767
|
+
}
|
|
768
|
+
static preload(...relations) {
|
|
769
|
+
return this.query().preload(...relations);
|
|
770
|
+
}
|
|
771
|
+
static async count(column = '*') {
|
|
772
|
+
return this.query().count(column);
|
|
773
|
+
}
|
|
774
|
+
static async sum(column) {
|
|
775
|
+
return this.query().sum(column);
|
|
776
|
+
}
|
|
777
|
+
static async avg(column) {
|
|
778
|
+
return this.query().avg(column);
|
|
779
|
+
}
|
|
780
|
+
static async create(attributes, connectionOrAdapter) {
|
|
781
|
+
const instance = new this(attributes);
|
|
782
|
+
await instance.save(connectionOrAdapter);
|
|
783
|
+
return instance;
|
|
784
|
+
}
|
|
785
|
+
static async firstOrCreate(search, attributes = {}, connectionOrAdapter) {
|
|
786
|
+
let q = this.query(connectionOrAdapter);
|
|
787
|
+
for (const [k, v] of Object.entries(search)) {
|
|
788
|
+
q = q.where(k, v);
|
|
789
|
+
}
|
|
790
|
+
const found = await q.first();
|
|
791
|
+
if (found)
|
|
792
|
+
return found;
|
|
793
|
+
return this.create({ ...search, ...attributes }, connectionOrAdapter);
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Update or create a record.
|
|
797
|
+
*
|
|
798
|
+
* @example
|
|
799
|
+
* await Patient.updateOrCreate(
|
|
800
|
+
* { mrn: 'AKMMCH-2024-0001' },
|
|
801
|
+
* { name: 'Rahim', phone: '01700000000' }
|
|
802
|
+
* );
|
|
803
|
+
*/
|
|
804
|
+
static async updateOrCreate(search, attributes, connectionOrAdapter) {
|
|
805
|
+
let q = this.query(connectionOrAdapter);
|
|
806
|
+
for (const [k, v] of Object.entries(search))
|
|
807
|
+
q = q.where(k, v);
|
|
808
|
+
const found = await q.first();
|
|
809
|
+
if (found) {
|
|
810
|
+
found.fill(attributes);
|
|
811
|
+
await found.save(connectionOrAdapter);
|
|
812
|
+
return found;
|
|
813
|
+
}
|
|
814
|
+
return this.create({ ...search, ...attributes }, connectionOrAdapter);
|
|
815
|
+
}
|
|
816
|
+
// ─── Save / Delete ──────────────────────────────────────────────────────────
|
|
817
|
+
async save(connectionOrAdapter) {
|
|
818
|
+
const Ctor = this.constructor;
|
|
819
|
+
const pk = Ctor.primaryKey;
|
|
820
|
+
const table = Ctor.getTable();
|
|
821
|
+
const conn = connectionOrAdapter || Ctor.connection;
|
|
822
|
+
const registry = getHookRegistry(Ctor);
|
|
823
|
+
await registry.execute('beforeSave', this);
|
|
824
|
+
const formatSqlDate = () => new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
825
|
+
if (!this._exists) {
|
|
826
|
+
await registry.execute('beforeCreate', this);
|
|
827
|
+
const createdCol = typeof Ctor.timestamps === 'object'
|
|
828
|
+
? (Ctor.timestamps.createdAt || null)
|
|
829
|
+
: (Ctor.timestamps ? 'created_at' : null);
|
|
830
|
+
const updatedCol = typeof Ctor.timestamps === 'object'
|
|
831
|
+
? (Ctor.timestamps.updatedAt || null)
|
|
832
|
+
: (Ctor.timestamps ? 'updated_at' : null);
|
|
833
|
+
if (createdCol && !this._attributes[createdCol])
|
|
834
|
+
this._attributes[createdCol] = formatSqlDate();
|
|
835
|
+
if (updatedCol && !this._attributes[updatedCol])
|
|
836
|
+
this._attributes[updatedCol] = formatSqlDate();
|
|
837
|
+
const res = await Database.table(table, conn).insert(this._attributes);
|
|
838
|
+
if (res.insertId && !this._attributes[pk])
|
|
839
|
+
this._attributes[pk] = res.insertId;
|
|
840
|
+
this._exists = true;
|
|
841
|
+
this._original = { ...this._attributes };
|
|
842
|
+
this._dirty = {};
|
|
843
|
+
await registry.execute('afterCreate', this);
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
await registry.execute('beforeUpdate', this);
|
|
847
|
+
const updatedCol = typeof Ctor.timestamps === 'object'
|
|
848
|
+
? (Ctor.timestamps.updatedAt || null)
|
|
849
|
+
: (Ctor.timestamps ? 'updated_at' : null);
|
|
850
|
+
if (updatedCol)
|
|
851
|
+
this._attributes[updatedCol] = formatSqlDate();
|
|
852
|
+
const id = this._attributes[pk];
|
|
853
|
+
await Database.table(table, conn).where(pk, id).update(this._attributes);
|
|
854
|
+
this._original = { ...this._attributes };
|
|
855
|
+
this._dirty = {};
|
|
856
|
+
await registry.execute('afterUpdate', this);
|
|
857
|
+
}
|
|
858
|
+
await registry.execute('afterSave', this);
|
|
859
|
+
return this;
|
|
860
|
+
}
|
|
861
|
+
async delete(connectionOrAdapter) {
|
|
862
|
+
if (!this._exists)
|
|
863
|
+
return false;
|
|
864
|
+
const Ctor = this.constructor;
|
|
865
|
+
const pk = Ctor.primaryKey;
|
|
866
|
+
const id = this._attributes[pk];
|
|
867
|
+
const conn = connectionOrAdapter || Ctor.connection;
|
|
868
|
+
const registry = getHookRegistry(Ctor);
|
|
869
|
+
await registry.execute('beforeDelete', this);
|
|
870
|
+
if (Ctor.softDeletes) {
|
|
871
|
+
const now = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
872
|
+
this.set('deleted_at', now);
|
|
873
|
+
await Database.table(Ctor.getTable(), conn).where(pk, id).update({ deleted_at: now });
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
await Database.table(Ctor.getTable(), conn).where(pk, id).delete();
|
|
877
|
+
this._exists = false;
|
|
878
|
+
}
|
|
879
|
+
await registry.execute('afterDelete', this);
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
async restore(connectionOrAdapter) {
|
|
883
|
+
const Ctor = this.constructor;
|
|
884
|
+
if (!Ctor.softDeletes || !this._exists)
|
|
885
|
+
return false;
|
|
886
|
+
const pk = Ctor.primaryKey;
|
|
887
|
+
const id = this._attributes[pk];
|
|
888
|
+
const conn = connectionOrAdapter || Ctor.connection;
|
|
889
|
+
this.set('deleted_at', null);
|
|
890
|
+
await Database.table(Ctor.getTable(), conn).where(pk, id).update({ deleted_at: null });
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
async forceDelete(connectionOrAdapter) {
|
|
894
|
+
if (!this._exists)
|
|
895
|
+
return false;
|
|
896
|
+
const Ctor = this.constructor;
|
|
897
|
+
const pk = Ctor.primaryKey;
|
|
898
|
+
const id = this._attributes[pk];
|
|
899
|
+
const conn = connectionOrAdapter || Ctor.connection;
|
|
900
|
+
await Database.table(Ctor.getTable(), conn).where(pk, id).delete();
|
|
901
|
+
this._exists = false;
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
// ─── Serialization ──────────────────────────────────────────────────────────
|
|
905
|
+
/**
|
|
906
|
+
* Converts the model to a plain JSON object.
|
|
907
|
+
* Hidden attributes are excluded. Relations and computed properties are included.
|
|
908
|
+
*/
|
|
909
|
+
toJSON() {
|
|
910
|
+
const Ctor = this.constructor;
|
|
911
|
+
const json = {};
|
|
912
|
+
for (const [key, val] of Object.entries(this._attributes)) {
|
|
913
|
+
if (!Ctor.hidden.includes(key))
|
|
914
|
+
json[key] = val;
|
|
915
|
+
}
|
|
916
|
+
// Include eager-loaded relations
|
|
917
|
+
for (const [rel, val] of Object.entries(this._relations)) {
|
|
918
|
+
if (rel === '$pivot')
|
|
919
|
+
continue; // handled separately
|
|
920
|
+
if (Array.isArray(val)) {
|
|
921
|
+
json[rel] = val.map((item) => (item?.toJSON ? item.toJSON() : item));
|
|
922
|
+
}
|
|
923
|
+
else if (val?.toJSON) {
|
|
924
|
+
json[rel] = val.toJSON();
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
json[rel] = val;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// Include computed properties (registered via @computed decorator)
|
|
931
|
+
const computedProps = Object.getPrototypeOf(this)._computedProperties ?? [];
|
|
932
|
+
for (const prop of computedProps) {
|
|
933
|
+
try {
|
|
934
|
+
const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), prop);
|
|
935
|
+
if (descriptor?.get) {
|
|
936
|
+
json[prop] = descriptor.get.call(this);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
catch {
|
|
940
|
+
// Computed property threw — skip silently
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return json;
|
|
944
|
+
}
|
|
945
|
+
// ─── Instance Lifecycle Hooks (override-style, kept for backward compat) ────
|
|
946
|
+
/** @deprecated Use @beforeSave() decorator or registerHook() instead */
|
|
947
|
+
async beforeCreate() { }
|
|
948
|
+
/** @deprecated Use @afterCreate() decorator or registerHook() instead */
|
|
949
|
+
async afterCreate() { }
|
|
950
|
+
/** @deprecated Use @beforeSave() decorator or registerHook() instead */
|
|
951
|
+
async beforeSave() { }
|
|
952
|
+
/** @deprecated Use @afterSave() decorator or registerHook() instead */
|
|
953
|
+
async afterSave() { }
|
|
954
|
+
/** @deprecated Use @beforeDelete() decorator or registerHook() instead */
|
|
955
|
+
async beforeDelete() { }
|
|
956
|
+
/** @deprecated Use @afterDelete() decorator or registerHook() instead */
|
|
957
|
+
async afterDelete() { }
|
|
958
|
+
}
|
|
959
|
+
export default Model;
|
|
960
|
+
//# sourceMappingURL=model.js.map
|