@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 @@
|
|
|
1
|
+
{"version":3,"file":"ecommerce-erp.js","sourceRoot":"","sources":["../../../src/cli/templates/ecommerce-erp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,SAAS,gBAAgB,CAAC,QAAgB,EAAE,OAAe;IACzD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAE,WAAmB;IACnE,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,OAAe,EAAE,EAAE;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACzC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACpC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,kBAAkB;IAClB,OAAO,CAAC,cAAc,EAAE;aACb,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BvB,CAAC,CAAC;IAED,mBAAmB;IACnB,OAAO,CAAC,eAAe,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwB1B,CAAC,CAAC;IAED,oBAAoB;IACpB,OAAO,CAAC,gBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;CAyB3B,CAAC,CAAC;IAED,yBAAyB;IACzB,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBpB,CAAC;IACA,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC5B,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAEpC,kBAAkB;IAClB,OAAO,CAAC,eAAe,EAAE;;;;;;;;CAQ1B,CAAC,CAAC;IAED,OAAO,CAAC,oBAAoB,EAAE;;;;;;;;;;;;;;;;;;;;CAoB/B,CAAC,CAAC;IAED,OAAO,CAAC,oBAAoB,EAAE;;;;;;CAM/B,CAAC,CAAC;IAED,gCAAgC;IAChC,OAAO,CAAC,wBAAwB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CnC,CAAC,CAAC;IAED,OAAO,CAAC,kBAAkB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC7B,CAAC,CAAC;IAED,0BAA0B;IAC1B,OAAO,CAAC,uBAAuB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsClC,CAAC,CAAC;IAED,OAAO,CAAC,kCAAkC,EAAE;;;;;;;;;;;;;;;;;;;;;CAqB7C,CAAC,CAAC;IAED,OAAO,CAAC,+BAA+B,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsB1C,CAAC,CAAC;IAED,OAAO,CAAC,2BAA2B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;CAyBtC,CAAC,CAAC;IAED,OAAO,CAAC,wBAAwB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCnC,CAAC,CAAC;IAED,OAAO,CAAC,0BAA0B,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsBrC,CAAC,CAAC;IAED,OAAO,CAAC,+BAA+B,EAAE;;;;;;;;;;;;;;;;;;;;CAoB1C,CAAC,CAAC;IAED,OAAO,CAAC,kCAAkC,EAAE;;;;;;;;;;;;;;;;;CAiB7C,CAAC,CAAC;IAED,OAAO,CAAC,2BAA2B,EAAE;;;;;;;;;;;;;;;;CAgBtC,CAAC,CAAC;IAED,OAAO,CAAC,sBAAsB,EAAE;;;;;;;;;;;;;;;;;;;;;;;CAuBjC,CAAC,CAAC;IAED,OAAO,CAAC,iCAAiC,EAAE;;;;;;;;;;;;;;;;;;;;;CAqB5C,CAAC,CAAC;IAED,OAAO,CAAC,wBAAwB,EAAE;;;;;;;;;;;;;;;;;;CAkBnC,CAAC,CAAC;IAED,OAAO,CAAC,qBAAqB,EAAE;;;;;;;;;;;;CAYhC,CAAC,CAAC;IAED,cAAc;IACd,OAAO,CAAC,gCAAgC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgC3C,CAAC,CAAC;IAED,OAAO,CAAC,8BAA8B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8IzC,CAAC,CAAC;IAED,OAAO,CAAC,+BAA+B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkI1C,CAAC,CAAC;IAED,OAAO,CAAC,gCAAgC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwC3C,CAAC,CAAC;IAED,iBAAiB;IACjB,OAAO,CAAC,wCAAwC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwBnD,CAAC,CAAC;IAED,OAAO,CAAC,sCAAsC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BjD,CAAC,CAAC;IAED,OAAO,CAAC,0CAA0C,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwBrD,CAAC,CAAC;IAED,OAAO,CAAC,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsC/C,CAAC,CAAC;IAED,OAAO,CAAC,qCAAqC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;CAyBhD,CAAC,CAAC;IAED,OAAO,CAAC,6CAA6C,EAAE;;;;;;;;;;;;;;;;CAgBxD,CAAC,CAAC;IAED,OAAO,CAAC,mCAAmC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;CAyB9C,CAAC,CAAC;IAED,aAAa;IACb,OAAO,CAAC,eAAe,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;CA0B1B,CAAC,CAAC;IAED,OAAO,CAAC,eAAe,EAAE;;;;;;;;;;;;;;;CAe1B,CAAC,CAAC;IAED,OAAO,CAAC,gBAAgB,EAAE;;;;;;;;;CAS3B,CAAC,CAAC;IAED,iCAAiC;IACjC,OAAO,CAAC,qBAAqB,EAAE;;;;;;;;;;;;;;;;;;;CAmBhC,CAAC,CAAC;IAED,OAAO,CAAC,uBAAuB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwBlC,CAAC,CAAC;IAED,OAAO,CAAC,kCAAkC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+E7C,CAAC,CAAC;IAED,OAAO,CAAC,uCAAuC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8ClD,CAAC,CAAC;IAED,OAAO,CAAC,qCAAqC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DhD,CAAC,CAAC;IAED,OAAO,CAAC,qCAAqC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+DhD,CAAC,CAAC;IAED,OAAO,CAAC,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyF/C,CAAC,CAAC;IAED,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFnB,CAAC,CAAC;IAED,sCAAsC;IACtC,OAAO,CAAC,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgEtB,CAAC,CAAC;IAED,8BAA8B;IAC9B,OAAO,CAAC,mBAAmB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC9B,CAAC,CAAC;IAED,gBAAgB;IAChB,OAAO,CAAC,WAAW,EAAE,KAAK,WAAW;;;;;;;;;;;;;;;;;;CAkBtC,CAAC,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file fullstack-templates.ts
|
|
3
|
+
* @description Canonical fullstack application templates matching my-aero-app.
|
|
4
|
+
*/
|
|
5
|
+
export declare const engineTsContent: string;
|
|
6
|
+
export declare const shellsTsContent: string;
|
|
7
|
+
export declare const productsEdgeContent: string;
|
|
8
|
+
export declare const productsEjsContent: string;
|
|
9
|
+
export declare const productsReactContent: string;
|
|
10
|
+
export declare const productsVueContent: string;
|
|
11
|
+
export declare const productModelContent: string;
|
|
12
|
+
export declare const initDbContent: string;
|
|
13
|
+
export declare const webRoutesContent: string;
|
|
14
|
+
export declare const apiRoutesContent: string;
|
|
15
|
+
export declare const serverTsContent: string;
|
|
16
|
+
export declare const databaseConfigContent: string;
|
|
17
|
+
//# sourceMappingURL=fullstack-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fullstack-templates.d.ts","sourceRoot":"","sources":["../../../src/cli/templates/fullstack-templates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,eAAe,EAAE,MAAuuB,CAAC;AAEtwB,eAAO,MAAM,eAAe,EAAE,MAAwujC,CAAC;AAEvwjC,eAAO,MAAM,mBAAmB,EAAE,MAAgwZ,CAAC;AAEnyZ,eAAO,MAAM,kBAAkB,EAAE,MAAkxZ,CAAC;AAEpzZ,eAAO,MAAM,oBAAoB,EAAE,MAAupL,CAAC;AAE3rL,eAAO,MAAM,kBAAkB,EAAE,MAA84J,CAAC;AAEh7J,eAAO,MAAM,mBAAmB,EAAE,MAA4hB,CAAC;AAE/jB,eAAO,MAAM,aAAa,EAAE,MAA+iG,CAAC;AAE5kG,eAAO,MAAM,gBAAgB,EAAE,MAAym3B,CAAC;AAEzo3B,eAAO,MAAM,gBAAgB,EAAE,MAA+4B,CAAC;AAE/6B,eAAO,MAAM,eAAe,EAAE,MAAi2C,CAAC;AAEh4C,eAAO,MAAM,qBAAqB,EAAE,MAAqmE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file fullstack-templates.ts
|
|
3
|
+
* @description Canonical fullstack application templates matching my-aero-app.
|
|
4
|
+
*/
|
|
5
|
+
export const engineTsContent = "import { Edge } from 'edge.js';\nimport ejs from 'ejs';\nimport { join } from 'node:path';\n\n// Initialize Edge.js instance and mount views directory\nconst edge = Edge.create();\nedge.mount(join(process.cwd(), 'views'));\n\n/**\n * Render template with AdonisJS's Edge template engine\n */\nexport async function renderEdge(templateName: string, data: Record<string, unknown> = {}): Promise<string> {\n return edge.render(templateName, data);\n}\n\n/**\n * Render template with EJS template engine\n */\nexport async function renderEjs(templateRelPath: string, data: Record<string, unknown> = {}): Promise<string> {\n const fullPath = join(process.cwd(), 'views', templateRelPath);\n return ejs.renderFile(fullPath, data);\n}\n";
|
|
6
|
+
export const shellsTsContent = "import type { InertiaPage } from 'aerojs';\n\nconst sharedStyles = `\n :root {\n --bg-base: #060913;\n --bg-card: #0d1322;\n --bg-card-hover: #131c31;\n --border-subtle: #1e293b;\n --text-main: #f8fafc;\n --text-muted: #94a3b8;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n html {\n overflow-y: scroll;\n scroll-behavior: smooth;\n }\n body {\n font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--bg-base);\n color: var(--text-main);\n min-height: 100vh;\n line-height: 1.6;\n padding-bottom: 4rem;\n -webkit-font-smoothing: antialiased;\n }\n .ambient-glow {\n position: fixed;\n top: -220px;\n left: 50%;\n transform: translateX(-50%);\n width: 900px;\n height: 480px;\n pointer-events: none;\n z-index: 0;\n }\n .container {\n max-width: 1180px;\n margin: 0 auto;\n padding: 0 1.5rem;\n position: relative;\n z-index: 1;\n }\n nav {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem 0;\n border-bottom: 1px solid rgba(30, 41, 59, 0.6);\n margin-bottom: 2.5rem;\n }\n .nav-brand {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n text-decoration: none;\n font-size: 1.25rem;\n font-weight: 800;\n color: #fff;\n }\n .brand-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.25rem 0.75rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n }\n .engine-switcher {\n display: flex;\n gap: 0.5rem;\n background: #0a0f1d;\n padding: 0.3rem;\n border-radius: 0.75rem;\n border: 1px solid var(--border-subtle);\n }\n .engine-tab {\n padding: 0.45rem 0.95rem;\n border-radius: 0.5rem;\n font-size: 0.85rem;\n font-weight: 600;\n text-decoration: none;\n color: #94a3b8;\n transition: all 0.2s ease;\n }\n .engine-tab:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.05);\n }\n .engine-tab.active {\n color: #fff;\n }\n .header-banner {\n display: flex;\n justify-content: space-between;\n align-items: flex-end;\n flex-wrap: wrap;\n gap: 1.5rem;\n margin-bottom: 2.5rem;\n }\n .header-info h1 {\n font-size: 2.4rem;\n font-weight: 800;\n letter-spacing: -0.03em;\n line-height: 1.2;\n margin-bottom: 0.5rem;\n }\n .header-info p {\n color: var(--text-muted);\n font-size: 1.05rem;\n }\n .db-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.6rem 1.2rem;\n border-radius: 0.75rem;\n background: rgba(16, 185, 129, 0.1);\n border: 1px solid rgba(16, 185, 129, 0.25);\n color: #34d399;\n font-size: 0.85rem;\n font-weight: 600;\n font-family: 'JetBrains Mono', monospace;\n }\n .dot-live {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 10px #10b981;\n }\n /* Controls bar */\n .controls-bar {\n display: flex;\n gap: 1rem;\n align-items: center;\n flex-wrap: wrap;\n background: var(--bg-card);\n border: 1px solid var(--border-subtle);\n padding: 1rem 1.25rem;\n border-radius: 0.85rem;\n margin-bottom: 2rem;\n }\n .search-input {\n flex: 1;\n min-width: 240px;\n background: #060913;\n border: 1px solid #1e293b;\n border-radius: 0.5rem;\n padding: 0.6rem 1rem;\n color: #fff;\n font-size: 0.9rem;\n outline: none;\n transition: border-color 0.2s;\n }\n .search-input:focus {\n border-color: #38bdf8;\n }\n .category-pills {\n display: flex;\n gap: 0.4rem;\n flex-wrap: wrap;\n }\n .pill-btn {\n background: #090e1c;\n border: 1px solid #1e293b;\n color: #94a3b8;\n padding: 0.4rem 0.85rem;\n border-radius: 0.45rem;\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n }\n .pill-btn:hover {\n color: #fff;\n border-color: #334155;\n }\n .pill-btn.active {\n color: #fff;\n border-color: transparent;\n }\n .sort-select {\n background: #060913;\n border: 1px solid #1e293b;\n border-radius: 0.5rem;\n padding: 0.6rem 1rem;\n color: #e2e8f0;\n font-size: 0.85rem;\n outline: none;\n }\n /* Cart Pill */\n .cart-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid var(--border-subtle);\n padding: 0.6rem 1.2rem;\n border-radius: 0.75rem;\n font-size: 0.9rem;\n color: #fff;\n }\n /* Products Grid - Stable height to prevent layout jumps */\n .products-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.5rem;\n min-height: 520px;\n align-content: start;\n }\n .product-card {\n background: var(--bg-card);\n border: 1px solid var(--border-subtle);\n border-radius: 1rem;\n padding: 1.6rem;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;\n position: relative;\n overflow: hidden;\n min-height: 290px;\n }\n .product-card:hover {\n transform: translateY(-3px);\n background: var(--bg-card-hover);\n box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);\n }\n .card-top {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 1rem;\n }\n .cat-tag {\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n padding: 0.25rem 0.65rem;\n border-radius: 6px;\n }\n .stock-ok {\n font-size: 0.75rem;\n font-weight: 600;\n padding: 0.2rem 0.6rem;\n border-radius: 9999px;\n background: rgba(16, 185, 129, 0.12);\n color: #34d399;\n border: 1px solid rgba(16, 185, 129, 0.25);\n }\n .stock-low {\n font-size: 0.75rem;\n font-weight: 600;\n padding: 0.2rem 0.6rem;\n border-radius: 9999px;\n background: rgba(245, 158, 11, 0.12);\n color: #fbbf24;\n border: 1px solid rgba(245, 158, 11, 0.25);\n }\n .product-name {\n font-size: 1.2rem;\n font-weight: 700;\n color: #fff;\n margin-bottom: 0.5rem;\n line-height: 1.35;\n }\n .product-desc {\n color: var(--text-muted);\n font-size: 0.9rem;\n line-height: 1.6;\n margin-bottom: 1.5rem;\n flex-grow: 1;\n }\n .card-bottom {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 1rem;\n border-top: 1px solid rgba(30, 41, 59, 0.6);\n }\n .price-tag {\n font-size: 1.5rem;\n font-weight: 800;\n color: #fff;\n font-family: 'JetBrains Mono', monospace;\n }\n .actions {\n display: flex;\n gap: 0.5rem;\n }\n .btn-details {\n background: #1e293b;\n color: #cbd5e1;\n border: 1px solid #334155;\n padding: 0.55rem 0.9rem;\n border-radius: 0.5rem;\n font-size: 0.85rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-details:hover {\n color: #fff;\n background: #334155;\n }\n .btn-add {\n color: #fff;\n border: none;\n padding: 0.55rem 1.1rem;\n border-radius: 0.5rem;\n font-size: 0.85rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-add:hover {\n transform: translateY(-1px);\n }\n /* Modal */\n .modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.75);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n padding: 1.5rem;\n }\n .modal-content {\n background: #0d1322;\n border: 1px solid #334155;\n border-radius: 1rem;\n padding: 2rem;\n max-width: 520px;\n width: 100%;\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8);\n }\n .modal-content h2 {\n font-size: 1.5rem;\n font-weight: 700;\n margin-bottom: 0.5rem;\n }\n .modal-cat {\n color: #38bdf8;\n font-size: 0.85rem;\n font-weight: 600;\n margin-bottom: 1rem;\n }\n .modal-desc {\n color: #94a3b8;\n margin-bottom: 1.5rem;\n line-height: 1.7;\n }\n .modal-price {\n font-size: 2rem;\n font-weight: 800;\n font-family: 'JetBrains Mono', monospace;\n color: #fff;\n margin-bottom: 1.5rem;\n }\n .modal-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n }\n .btn-close {\n background: #1e293b;\n color: #cbd5e1;\n border: 1px solid #334155;\n padding: 0.6rem 1.2rem;\n border-radius: 0.5rem;\n font-size: 0.9rem;\n cursor: pointer;\n }\n .syntax-box {\n margin-top: 3.5rem;\n background: #080d1a;\n border: 1px solid var(--border-subtle);\n border-radius: 1rem;\n padding: 1.75rem;\n }\n pre {\n background: #040711;\n border: 1px solid #1e293b;\n border-radius: 0.5rem;\n padding: 1rem;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.82rem;\n color: #e2e8f0;\n overflow-x: auto;\n }\n`;\n\n/**\n * Pre-renders the exact initial catalog HTML directly on the server.\n * This completely eliminates initial layout jumps and blank/loading flashes.\n */\nfunction renderInitialCatalogHtml(\n engineBadgeText: string,\n badgeClass: string,\n engineHeading: string,\n engineSubheading: string,\n products: any[]\n): string {\n const categories = ['All', ...new Set(products.map((p) => p.category))];\n const categoryPillsHtml = categories\n .map(\n (cat, idx) =>\n `<button class=\"pill-btn${idx === 0 ? ' active' : ''}\">${cat}</button>`\n )\n .join('');\n\n const productCardsHtml = products\n .map((p) => {\n const stockBadge =\n p.stock > 10\n ? `<span class=\"stock-ok\">In Stock (${p.stock})</span>`\n : `<span class=\"stock-low\">Low Stock (${p.stock})</span>`;\n return `\n <div class=\"product-card\">\n <div>\n <div class=\"card-top\">\n <span class=\"cat-tag\">${p.category}</span>\n ${stockBadge}\n </div>\n <div style=\"color: #fbbf24; font-size: 0.85rem; margin-bottom: 0.3rem;\">\n ★★★★★ (${p.rating}.0)\n </div>\n <h3 class=\"product-name\">${p.name}</h3>\n <p class=\"product-desc\">${p.description}</p>\n </div>\n <div class=\"card-bottom\">\n <div class=\"price-tag\">$${p.price}</div>\n <div class=\"actions\">\n <button class=\"btn-details\">Quick View</button>\n <button class=\"btn-add\">+ Add to Cart</button>\n </div>\n </div>\n </div>\n `;\n })\n .join('');\n\n return `\n <div>\n <div class=\"header-banner\">\n <div class=\"header-info\">\n <span class=\"${badgeClass}\">${engineBadgeText}</span>\n <h1>${engineHeading}</h1>\n <p>${engineSubheading}</p>\n </div>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div class=\"cart-pill\">\n 🛒 Cart: <strong>0 items</strong> ($0)\n </div>\n <div class=\"db-badge\">\n <span class=\"dot-live\"></span>\n MySQL 8 • aerojs_app\n </div>\n </div>\n </div>\n\n <div class=\"controls-bar\">\n <input\n type=\"text\"\n placeholder=\"Search products in MySQL in real-time...\"\n class=\"search-input\"\n />\n <div class=\"category-pills\">\n ${categoryPillsHtml}\n </div>\n <select class=\"sort-select\">\n <option value=\"name\">Sort by Name</option>\n <option value=\"price-asc\">Price: Low to High</option>\n <option value=\"price-desc\">Price: High to Low</option>\n </select>\n </div>\n\n <div class=\"products-grid\">\n ${productCardsHtml}\n </div>\n </div>\n `;\n}\n\n/**\n * Returns React Inertia HTML Shell with pre-rendered initial server markup\n * and smooth, zero-layout-shift React 18 client hydration.\n */\nexport function getReactShell(page: InertiaPage): string {\n const pageJson = JSON.stringify(page).replace(/\"/g, '"');\n const products = (page.props?.products as any[]) || [];\n const initialHtml = renderInitialCatalogHtml(\n '⚛️ Inertia.js + React 18 SPA',\n 'badge-react',\n 'React <span>Interactive Catalog</span>',\n 'Live client-side reactivity powered by Inertia protocol & MySQL database records.',\n products\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>React 18 (Inertia.js) • AeroJS + MySQL</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <link rel=\"modulepreload\" href=\"https://esm.sh/react@18.3.1\">\n <link rel=\"modulepreload\" href=\"https://esm.sh/react-dom@18.3.1/client\">\n <style>\n ${sharedStyles}\n .ambient-glow {\n background: radial-gradient(circle, rgba(56, 189, 248, 0.16) 0%, rgba(99, 102, 241, 0.08) 50%, transparent 70%);\n }\n .brand-pill {\n background: rgba(56, 189, 248, 0.15);\n color: #38bdf8;\n border: 1px solid rgba(56, 189, 248, 0.3);\n }\n .engine-tab.active {\n background: linear-gradient(135deg, #0284c7, #2563eb);\n box-shadow: 0 2px 10px rgba(2, 132, 199, 0.4);\n }\n .header-info h1 span {\n background: linear-gradient(135deg, #38bdf8, #818cf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .pill-btn.active {\n background: #0284c7;\n }\n .cat-tag {\n color: #38bdf8;\n background: rgba(56, 189, 248, 0.12);\n border: 1px solid rgba(56, 189, 248, 0.25);\n }\n .btn-add {\n background: linear-gradient(135deg, #0284c7, #2563eb);\n }\n .btn-add:hover {\n box-shadow: 0 4px 15px rgba(2, 132, 199, 0.4);\n }\n .badge-react {\n font-size: 0.8rem;\n font-weight: 700;\n color: #38bdf8;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n }\n </style>\n <script type=\"importmap\">\n {\n \"imports\": {\n \"react\": \"https://esm.sh/react@18.3.1\",\n \"react-dom/client\": \"https://esm.sh/react-dom@18.3.1/client\"\n }\n }\n </script>\n</head>\n<body>\n <div class=\"ambient-glow\"></div>\n <div class=\"container\">\n <!-- Navbar -->\n <nav>\n <a href=\"/\" class=\"nav-brand\">\n <span>🚀 AeroJS</span>\n <span class=\"brand-pill\">Engine: React (Inertia.js)</span>\n </a>\n <div class=\"engine-switcher\">\n <a href=\"/views/edge\" class=\"engine-tab\">Edge.js</a>\n <a href=\"/views/ejs\" class=\"engine-tab\">EJS</a>\n <a href=\"/views/react\" class=\"engine-tab active\">React</a>\n <a href=\"/views/vue\" class=\"engine-tab\">Vue 3</a>\n </div>\n </nav>\n\n <!-- Inertia Mount Root with Pre-rendered HTML (Zero Layout Shift) -->\n <div id=\"app\" data-page=\"${pageJson}\">${initialHtml}</div>\n\n <!-- Inertia Explainer -->\n <div class=\"syntax-box\">\n <h4 style=\"color: #38bdf8; margin-bottom: 0.75rem; font-weight: 700;\">⚛️ Inertia.js Protocol Lifecycle in AeroJS</h4>\n <pre><code>// 1. Controller renders React component via Inertia adapter:\nawait ctx.inertia.render('ProductsReact', { products });\n\n// 2. Initial visit returns pre-rendered HTML shell with data-page JSON payload:\n<div id=\"app\" data-page='{\"component\":\"ProductsReact\",\"props\":{\"products\":[...]}}'>...</div>\n\n// 3. Subsequent client visits with 'X-Inertia: true' header receive blazing-fast JSON:\n// AeroJS automatically skips HTML rendering and sends pure JSON props!</code></pre>\n </div>\n </div>\n\n <!-- Client-side React 18 Mounting Script -->\n <script type=\"module\">\n import React, { useState, useMemo } from 'react';\n import { createRoot } from 'react-dom/client';\n\n const rootElement = document.getElementById('app');\n const pageData = JSON.parse(rootElement.getAttribute('data-page') || '{}');\n const initialProducts = pageData.props?.products || [];\n\n function ReactCatalog({ products }) {\n const [search, setSearch] = useState('');\n const [selectedCat, setSelectedCat] = useState('All');\n const [sortBy, setSortBy] = useState('name');\n const [cart, setCart] = useState([]);\n const [activeModal, setActiveModal] = useState(null);\n\n const categories = useMemo(() => {\n return ['All', ...new Set(products.map(p => p.category))];\n }, [products]);\n\n const filtered = useMemo(() => {\n return products\n .filter(p => {\n const matchesSearch = p.name.toLowerCase().includes(search.toLowerCase()) ||\n p.description.toLowerCase().includes(search.toLowerCase());\n const matchesCat = selectedCat === 'All' || p.category === selectedCat;\n return matchesSearch && matchesCat;\n })\n .sort((a, b) => {\n if (sortBy === 'price-asc') return a.price - b.price;\n if (sortBy === 'price-desc') return b.price - a.price;\n return a.name.localeCompare(b.name);\n });\n }, [products, search, selectedCat, sortBy]);\n\n const cartTotal = useMemo(() => cart.reduce((s, i) => s + i.price, 0), [cart]);\n\n const addToCart = (product) => {\n setCart(prev => [...prev, product]);\n };\n\n return React.createElement('div', null,\n // Header Banner\n React.createElement('div', { className: 'header-banner' },\n React.createElement('div', { className: 'header-info' },\n React.createElement('span', { className: 'badge-react' }, '⚛️ Inertia.js + React 18 SPA'),\n React.createElement('h1', null, 'React ', React.createElement('span', null, 'Interactive Catalog')),\n React.createElement('p', null, 'Live client-side reactivity powered by Inertia protocol & MySQL database records.')\n ),\n React.createElement('div', { style: { display: 'flex', gap: '1rem', alignItems: 'center' } },\n React.createElement('div', { className: 'cart-pill' },\n '🛒 Cart: ', React.createElement('strong', null, cart.length, ' items'), ' ($' + cartTotal + ')'\n ),\n React.createElement('div', { className: 'db-badge' },\n React.createElement('span', { className: 'dot-live' }),\n 'MySQL 8 \\\\u2022 aerojs_app'\n )\n )\n ),\n\n // Controls bar\n React.createElement('div', { className: 'controls-bar' },\n React.createElement('input', {\n type: 'text',\n placeholder: 'Search products in MySQL in real-time...',\n value: search,\n onChange: (e) => setSearch(e.target.value),\n className: 'search-input'\n }),\n React.createElement('div', { className: 'category-pills' },\n categories.map(cat => React.createElement('button', {\n key: cat,\n onClick: () => setSelectedCat(cat),\n className: 'pill-btn' + (selectedCat === cat ? ' active' : '')\n }, cat))\n ),\n React.createElement('select', {\n value: sortBy,\n onChange: (e) => setSortBy(e.target.value),\n className: 'sort-select'\n },\n React.createElement('option', { value: 'name' }, 'Sort by Name'),\n React.createElement('option', { value: 'price-asc' }, 'Price: Low to High'),\n React.createElement('option', { value: 'price-desc' }, 'Price: High to Low')\n )\n ),\n\n // Products Grid\n React.createElement('div', { className: 'products-grid' },\n filtered.length > 0\n ? filtered.map(product => React.createElement('div', { key: product.id, className: 'product-card' },\n React.createElement('div', null,\n React.createElement('div', { className: 'card-top' },\n React.createElement('span', { className: 'cat-tag' }, product.category),\n React.createElement('span', { className: product.stock > 10 ? 'stock-ok' : 'stock-low' },\n product.stock > 10 ? 'In Stock (' + product.stock + ')' : 'Low Stock (' + product.stock + ')'\n )\n ),\n React.createElement('div', { style: { color: '#fbbf24', fontSize: '0.85rem', marginBottom: '0.3rem' } },\n '★★★★★ (' + product.rating + '.0)'\n ),\n React.createElement('h3', { className: 'product-name' }, product.name),\n React.createElement('p', { className: 'product-desc' }, product.description)\n ),\n React.createElement('div', { className: 'card-bottom' },\n React.createElement('div', { className: 'price-tag' }, '$' + product.price),\n React.createElement('div', { className: 'actions' },\n React.createElement('button', {\n className: 'btn-details',\n onClick: () => setActiveModal(product)\n }, 'Quick View'),\n React.createElement('button', {\n className: 'btn-add',\n onClick: () => addToCart(product)\n }, '+ Add to Cart')\n )\n )\n ))\n : React.createElement('div', {\n style: { gridColumn: '1 / -1', textAlign: 'center', padding: '4rem', color: '#94a3b8' }\n }, 'No products matching \"' + search + '\"')\n ),\n\n // Modal\n activeModal && React.createElement('div', { className: 'modal-backdrop', onClick: () => setActiveModal(null) },\n React.createElement('div', { className: 'modal-content', onClick: (e) => e.stopPropagation() },\n React.createElement('h2', null, activeModal.name),\n React.createElement('p', { className: 'modal-cat' }, 'Category: ' + activeModal.category + ' \\\\u2022 Rating: ' + activeModal.rating + '/5'),\n React.createElement('p', { className: 'modal-desc' }, activeModal.description),\n React.createElement('div', { className: 'modal-price' }, '$' + activeModal.price),\n React.createElement('div', { className: 'modal-actions' },\n React.createElement('button', {\n className: 'btn-add',\n onClick: () => { addToCart(activeModal); setActiveModal(null); }\n }, 'Add to Cart'),\n React.createElement('button', {\n className: 'btn-close',\n onClick: () => setActiveModal(null)\n }, 'Close')\n )\n )\n )\n );\n }\n\n const root = createRoot(rootElement);\n root.render(React.createElement(ReactCatalog, { products: initialProducts }));\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Returns Vue 3 Inertia HTML Shell with pre-rendered initial server markup\n * and smooth, zero-layout-shift Vue 3 Composition API hydration.\n */\nexport function getVueShell(page: InertiaPage): string {\n const pageJson = JSON.stringify(page).replace(/\"/g, '"');\n const products = (page.props?.products as any[]) || [];\n const initialHtml = renderInitialCatalogHtml(\n '💚 Inertia.js + Vue 3 (Composition API)',\n 'badge-vue',\n 'Vue 3 <span>Reactive Catalog</span>',\n 'Deep reactivity with ref() & computed() powered by Inertia protocol & MySQL database.',\n products\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Vue 3 (Inertia.js) • AeroJS + MySQL</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <link rel=\"modulepreload\" href=\"https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js\">\n <style>\n ${sharedStyles}\n .ambient-glow {\n background: radial-gradient(circle, rgba(16, 185, 129, 0.16) 0%, rgba(6, 182, 212, 0.08) 50%, transparent 70%);\n }\n .brand-pill {\n background: rgba(16, 185, 129, 0.15);\n color: #34d399;\n border: 1px solid rgba(16, 185, 129, 0.3);\n }\n .engine-tab.active {\n background: linear-gradient(135deg, #059669, #10b981);\n box-shadow: 0 2px 10px rgba(16, 185, 129, 0.4);\n }\n .header-info h1 span {\n background: linear-gradient(135deg, #34d399, #10b981);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .pill-btn.active {\n background: #059669;\n }\n .cat-tag {\n color: #34d399;\n background: rgba(16, 185, 129, 0.12);\n border: 1px solid rgba(16, 185, 129, 0.25);\n }\n .btn-add {\n background: linear-gradient(135deg, #059669, #10b981);\n }\n .btn-add:hover {\n box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);\n }\n .badge-vue {\n font-size: 0.8rem;\n font-weight: 700;\n color: #34d399;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n }\n </style>\n <script type=\"importmap\">\n {\n \"imports\": {\n \"vue\": \"https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js\"\n }\n }\n </script>\n</head>\n<body>\n <div class=\"ambient-glow\"></div>\n <div class=\"container\">\n <!-- Navbar -->\n <nav>\n <a href=\"/\" class=\"nav-brand\">\n <span>🚀 AeroJS</span>\n <span class=\"brand-pill\">Engine: Vue 3 (Inertia.js)</span>\n </a>\n <div class=\"engine-switcher\">\n <a href=\"/views/edge\" class=\"engine-tab\">Edge.js</a>\n <a href=\"/views/ejs\" class=\"engine-tab\">EJS</a>\n <a href=\"/views/react\" class=\"engine-tab\">React</a>\n <a href=\"/views/vue\" class=\"engine-tab active\">Vue 3</a>\n </div>\n </nav>\n\n <!-- Inertia Mount Root with Pre-rendered HTML (Zero Layout Shift) -->\n <div id=\"app\" data-page=\"${pageJson}\">${initialHtml}</div>\n\n <!-- Inertia Explainer -->\n <div class=\"syntax-box\">\n <h4 style=\"color: #34d399; margin-bottom: 0.75rem; font-weight: 700;\">💚 Inertia.js Protocol Lifecycle in AeroJS</h4>\n <pre><code>// 1. Controller renders Vue 3 component via Inertia adapter:\nawait ctx.inertia.render('ProductsVue', { products });\n\n// 2. Initial visit returns pre-rendered HTML shell with data-page JSON payload:\n<div id=\"app\" data-page='{\"component\":\"ProductsVue\",\"props\":{\"products\":[...]}}'>...</div>\n\n// 3. Subsequent client visits with 'X-Inertia: true' header receive blazing-fast JSON:\n// AeroJS automatically skips HTML rendering and sends pure JSON props!</code></pre>\n </div>\n </div>\n\n <!-- Client-side Vue 3 Mounting Script -->\n <script type=\"module\">\n import { createApp, ref, computed, h } from 'vue';\n\n const rootElement = document.getElementById('app');\n const pageData = JSON.parse(rootElement.getAttribute('data-page') || '{}');\n const initialProducts = pageData.props?.products || [];\n\n const App = {\n setup() {\n const products = ref(initialProducts);\n const search = ref('');\n const selectedCat = ref('All');\n const sortBy = ref('name');\n const cart = ref([]);\n const activeModal = ref(null);\n\n const categories = computed(() => {\n return ['All', ...new Set(products.value.map(p => p.category))];\n });\n\n const filtered = computed(() => {\n return products.value\n .filter(p => {\n const matchesSearch = p.name.toLowerCase().includes(search.value.toLowerCase()) ||\n p.description.toLowerCase().includes(search.value.toLowerCase());\n const matchesCat = selectedCat.value === 'All' || p.category === selectedCat.value;\n return matchesSearch && matchesCat;\n })\n .sort((a, b) => {\n if (sortBy.value === 'price-asc') return a.price - b.price;\n if (sortBy.value === 'price-desc') return b.price - a.price;\n return a.name.localeCompare(b.name);\n });\n });\n\n const cartTotal = computed(() => cart.value.reduce((s, i) => s + i.price, 0));\n\n const addToCart = (product) => {\n cart.value.push(product);\n };\n\n return () => h('div', [\n // Header Banner\n h('div', { class: 'header-banner' }, [\n h('div', { class: 'header-info' }, [\n h('span', { class: 'badge-vue' }, '💚 Inertia.js + Vue 3 (Composition API)'),\n h('h1', ['Vue 3 ', h('span', 'Reactive Catalog')]),\n h('p', 'Deep reactivity with ref() & computed() powered by Inertia protocol & MySQL database.')\n ]),\n h('div', { style: { display: 'flex', gap: '1rem', alignItems: 'center' } }, [\n h('div', { class: 'cart-pill' }, [\n '🛒 Cart: ', h('strong', cart.value.length + ' items'), ' ($' + cartTotal.value + ')'\n ]),\n h('div', { class: 'db-badge' }, [\n h('span', { class: 'dot-live' }),\n 'MySQL 8 • aerojs_app'\n ])\n ])\n ]),\n\n // Controls Bar\n h('div', { class: 'controls-bar' }, [\n h('input', {\n type: 'text',\n placeholder: 'Filter MySQL products in real time...',\n value: search.value,\n onInput: (e) => search.value = e.target.value,\n class: 'search-input'\n }),\n h('div', { class: 'category-pills' },\n categories.value.map(cat => h('button', {\n key: cat,\n onClick: () => selectedCat.value = cat,\n class: 'pill-btn' + (selectedCat.value === cat ? ' active' : '')\n }, cat))\n ),\n h('select', {\n value: sortBy.value,\n onChange: (e) => sortBy.value = e.target.value,\n class: 'sort-select'\n }, [\n h('option', { value: 'name' }, 'Sort by Name'),\n h('option', { value: 'price-asc' }, 'Price: Low to High'),\n h('option', { value: 'price-desc' }, 'Price: High to Low')\n ])\n ]),\n\n // Products Grid\n h('div', { class: 'products-grid' },\n filtered.value.length > 0\n ? filtered.value.map(product => h('div', { key: product.id, class: 'product-card' }, [\n h('div', [\n h('div', { class: 'card-top' }, [\n h('span', { class: 'cat-tag' }, product.category),\n h('span', { class: product.stock > 10 ? 'stock-ok' : 'stock-low' },\n product.stock > 10 ? ('In Stock (' + product.stock + ')') : ('Low Stock (' + product.stock + ')')\n )\n ]),\n h('div', { style: { color: '#fbbf24', fontSize: '0.85rem', marginBottom: '0.3rem' } },\n '★★★★★ (' + product.rating + '.0)'\n ),\n h('h3', { class: 'product-name' }, product.name),\n h('p', { class: 'product-desc' }, product.description)\n ]),\n h('div', { class: 'card-bottom' }, [\n h('div', { class: 'price-tag' }, '$' + product.price),\n h('div', { class: 'actions' }, [\n h('button', {\n class: 'btn-details',\n onClick: () => activeModal.value = product\n }, 'Quick View'),\n h('button', {\n class: 'btn-add',\n onClick: () => addToCart(product)\n }, '+ Add to Cart')\n ])\n ])\n ]))\n : [\n h('div', {\n style: { gridColumn: '1 / -1', textAlign: 'center', padding: '4rem', color: '#94a3b8' }\n }, 'No products matching \"' + search.value + '\"')\n ]\n ),\n\n // Modal\n activeModal.value ? h('div', {\n class: 'modal-backdrop',\n onClick: () => activeModal.value = null\n }, [\n h('div', {\n class: 'modal-content',\n onClick: (e) => e.stopPropagation()\n }, [\n h('h2', activeModal.value.name),\n h('p', { class: 'modal-cat' }, 'Category: ' + activeModal.value.category + ' • Rating: ' + activeModal.value.rating + '/5'),\n h('p', { class: 'modal-desc' }, activeModal.value.description),\n h('div', { class: 'modal-price' }, '$' + activeModal.value.price),\n h('div', { class: 'modal-actions' }, [\n h('button', {\n class: 'btn-add',\n onClick: () => { addToCart(activeModal.value); activeModal.value = null; }\n }, 'Add to Cart'),\n h('button', {\n class: 'btn-close',\n onClick: () => activeModal.value = null\n }, 'Close')\n ])\n ])\n ]) : null\n ]);\n }\n };\n\n createApp(App).mount('#app');\n </script>\n</body>\n</html>`;\n}\n";
|
|
7
|
+
export const productsEdgeContent = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Edge.js View Engine • AeroJS + MySQL</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --bg-base: #060913;\n --bg-card: #0d1322;\n --bg-card-hover: #131c31;\n --border-subtle: #1e293b;\n --border-highlight: #a855f7;\n --text-main: #f8fafc;\n --text-muted: #94a3b8;\n --brand-edge: #a855f7;\n --brand-edge-light: #c084fc;\n --brand-cyan: #38bdf8;\n --brand-emerald: #10b981;\n --brand-amber: #f59e0b;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--bg-base);\n color: var(--text-main);\n min-height: 100vh;\n line-height: 1.6;\n padding-bottom: 4rem;\n }\n .ambient-glow {\n position: fixed;\n top: -220px;\n left: 50%;\n transform: translateX(-50%);\n width: 900px;\n height: 480px;\n background: radial-gradient(circle, rgba(168, 85, 247, 0.16) 0%, rgba(56, 189, 248, 0.08) 50%, transparent 70%);\n pointer-events: none;\n z-index: 0;\n }\n .container {\n max-width: 1180px;\n margin: 0 auto;\n padding: 0 1.5rem;\n position: relative;\n z-index: 1;\n }\n /* Top Navbar */\n nav {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem 0;\n border-bottom: 1px solid rgba(30, 41, 59, 0.6);\n margin-bottom: 2.5rem;\n }\n .nav-brand {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n text-decoration: none;\n font-size: 1.25rem;\n font-weight: 800;\n color: #fff;\n }\n .brand-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.25rem 0.75rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n background: rgba(168, 85, 247, 0.15);\n color: var(--brand-edge-light);\n border: 1px solid rgba(168, 85, 247, 0.3);\n }\n .engine-switcher {\n display: flex;\n gap: 0.5rem;\n background: #0a0f1d;\n padding: 0.3rem;\n border-radius: 0.75rem;\n border: 1px solid var(--border-subtle);\n }\n .engine-tab {\n padding: 0.45rem 0.95rem;\n border-radius: 0.5rem;\n font-size: 0.85rem;\n font-weight: 600;\n text-decoration: none;\n color: #94a3b8;\n transition: all 0.2s;\n }\n .engine-tab:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.05);\n }\n .engine-tab.active {\n color: #fff;\n background: linear-gradient(135deg, #9333ea, #7e22ce);\n box-shadow: 0 2px 10px rgba(147, 51, 234, 0.4);\n }\n\n /* Page Header */\n .header-banner {\n display: flex;\n justify-content: space-between;\n align-items: flex-end;\n flex-wrap: wrap;\n gap: 1.5rem;\n margin-bottom: 2.5rem;\n }\n .header-info h1 {\n font-size: 2.4rem;\n font-weight: 800;\n letter-spacing: -0.03em;\n line-height: 1.2;\n margin-bottom: 0.5rem;\n }\n .header-info h1 span {\n background: linear-gradient(135deg, #c084fc, #e879f9);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .header-info p {\n color: var(--text-muted);\n font-size: 1.05rem;\n }\n .db-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.6rem 1.2rem;\n border-radius: 0.75rem;\n background: rgba(16, 185, 129, 0.1);\n border: 1px solid rgba(16, 185, 129, 0.25);\n color: #34d399;\n font-size: 0.85rem;\n font-weight: 600;\n font-family: 'JetBrains Mono', monospace;\n }\n .dot-live {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 10px #10b981;\n }\n\n /* Metric stats */\n .stats-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n gap: 1rem;\n margin-bottom: 2.5rem;\n }\n .stat-card {\n background: var(--bg-card);\n border: 1px solid var(--border-subtle);\n border-radius: 0.85rem;\n padding: 1.25rem;\n }\n .stat-label {\n color: var(--text-muted);\n font-size: 0.82rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 0.35rem;\n }\n .stat-val {\n font-size: 1.8rem;\n font-weight: 800;\n color: #fff;\n }\n\n /* Products Grid */\n .catalog-title {\n font-size: 1.35rem;\n font-weight: 700;\n letter-spacing: -0.02em;\n margin-bottom: 1.25rem;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n }\n .products-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.5rem;\n }\n .product-card {\n background: var(--bg-card);\n border: 1px solid var(--border-subtle);\n border-radius: 1rem;\n padding: 1.6rem;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n transition: all 0.25s ease;\n position: relative;\n overflow: hidden;\n }\n .product-card::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: linear-gradient(90deg, #9333ea, #38bdf8);\n opacity: 0;\n transition: opacity 0.25s ease;\n }\n .product-card:hover {\n transform: translateY(-3px);\n border-color: rgba(168, 85, 247, 0.4);\n background: var(--bg-card-hover);\n box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);\n }\n .product-card:hover::before {\n opacity: 1;\n }\n .card-top {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 1rem;\n }\n .category-tag {\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--brand-edge-light);\n background: rgba(168, 85, 247, 0.12);\n border: 1px solid rgba(168, 85, 247, 0.25);\n padding: 0.25rem 0.65rem;\n border-radius: 6px;\n }\n .stock-badge {\n font-size: 0.75rem;\n font-weight: 600;\n padding: 0.2rem 0.6rem;\n border-radius: 9999px;\n }\n .stock-in {\n background: rgba(16, 185, 129, 0.12);\n color: #34d399;\n border: 1px solid rgba(16, 185, 129, 0.25);\n }\n .stock-low {\n background: rgba(245, 158, 11, 0.12);\n color: #fbbf24;\n border: 1px solid rgba(245, 158, 11, 0.25);\n }\n .product-name {\n font-size: 1.2rem;\n font-weight: 700;\n color: #fff;\n margin-bottom: 0.5rem;\n line-height: 1.35;\n }\n .product-desc {\n color: var(--text-muted);\n font-size: 0.9rem;\n line-height: 1.6;\n margin-bottom: 1.5rem;\n flex-grow: 1;\n }\n .card-bottom {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 1rem;\n border-top: 1px solid rgba(30, 41, 59, 0.6);\n }\n .price-box {\n display: flex;\n flex-direction: column;\n }\n .price-label {\n font-size: 0.72rem;\n color: #64748b;\n text-transform: uppercase;\n font-weight: 600;\n }\n .price-val {\n font-size: 1.5rem;\n font-weight: 800;\n color: #fff;\n font-family: 'JetBrains Mono', monospace;\n }\n .rating-stars {\n color: #fbbf24;\n font-size: 0.85rem;\n margin-bottom: 0.3rem;\n }\n .btn-buy {\n background: linear-gradient(135deg, #9333ea, #7e22ce);\n color: #fff;\n border: none;\n padding: 0.6rem 1.2rem;\n border-radius: 0.6rem;\n font-size: 0.88rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-buy:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 15px rgba(147, 51, 234, 0.4);\n }\n\n /* Template Snippet Box */\n .syntax-box {\n margin-top: 3.5rem;\n background: #080d1a;\n border: 1px solid var(--border-subtle);\n border-radius: 1rem;\n padding: 1.75rem;\n }\n .syntax-box h4 {\n font-size: 1rem;\n font-weight: 700;\n margin-bottom: 0.75rem;\n color: var(--brand-edge-light);\n display: flex;\n align-items: center;\n gap: 0.5rem;\n }\n pre {\n background: #040711;\n border: 1px solid #1e293b;\n border-radius: 0.5rem;\n padding: 1rem;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.82rem;\n color: #e2e8f0;\n overflow-x: auto;\n }\n </style>\n</head>\n<body>\n <div class=\"ambient-glow\"></div>\n <div class=\"container\">\n <!-- Navbar -->\n <nav>\n <a href=\"/\" class=\"nav-brand\">\n <span>🚀 AeroJS</span>\n <span class=\"brand-pill\">Engine: Edge.js</span>\n </a>\n <div class=\"engine-switcher\">\n <a href=\"/views/edge\" class=\"engine-tab active\">Edge.js</a>\n <a href=\"/views/ejs\" class=\"engine-tab\">EJS</a>\n <a href=\"/views/react\" class=\"engine-tab\">React</a>\n <a href=\"/views/vue\" class=\"engine-tab\">Vue 3</a>\n </div>\n </nav>\n\n <!-- Header Banner -->\n <div class=\"header-banner\">\n <div class=\"header-info\">\n <h1><span>Edge.js</span> Template Engine</h1>\n <p>Rendered on the server with official AdonisJS Edge templating syntax, fetching live from MySQL.</p>\n </div>\n <div class=\"db-badge\">\n <span class=\"dot-live\"></span>\n MySQL 8 • aerojs_app.products\n </div>\n </div>\n\n <!-- Quick Stats -->\n <div class=\"stats-row\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">Total Catalog Items</div>\n <div class=\"stat-val\">{{ products.length }}</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">Template Driver</div>\n <div class=\"stat-val\" style=\"font-size: 1.4rem; color: var(--brand-edge-light);\">@edge-js/edge</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">Database Engine</div>\n <div class=\"stat-val\" style=\"font-size: 1.4rem; color: #38bdf8;\">MySQL2 + Knex</div>\n </div>\n </div>\n\n <!-- Products Catalog Section -->\n <div class=\"catalog-title\">\n <span>📦</span> Live Products from MySQL Database\n </div>\n\n <div class=\"products-grid\">\n @each(product in products)\n <div class=\"product-card\">\n <div>\n <div class=\"card-top\">\n <span class=\"category-tag\">{{ product.category }}</span>\n @if(product.stock > 10)\n <span class=\"stock-badge stock-in\">In Stock ({{ product.stock }})</span>\n @else\n <span class=\"stock-badge stock-low\">Low Stock ({{ product.stock }})</span>\n @endif\n </div>\n\n <div class=\"rating-stars\">\n ★★★★★ ({{ product.rating }}.0)\n </div>\n\n <h3 class=\"product-name\">{{ product.name }}</h3>\n <p class=\"product-desc\">{{ product.description }}</p>\n </div>\n\n <div class=\"card-bottom\">\n <div class=\"price-box\">\n <span class=\"price-label\">Price</span>\n <span class=\"price-val\">${{ product.price }}</span>\n </div>\n <button class=\"btn-buy\" onclick=\"alert('Selected: {{ product.name }} - ${{ product.price }}')\">\n Select\n </button>\n </div>\n </div>\n @endeach\n </div>\n\n <!-- Edge Syntax Explainer -->\n <div class=\"syntax-box\">\n <h4><span>💡</span> How Edge.js Renders This Page</h4>\n <pre><code>// Edge.js loop syntax used directly above:\n@each(product in products)\n <div class=\"product-card\">\n <h3>{{ product.name }}</h3>\n <span>${{ product.price }}</span>\n @if(product.stock > 10)\n <span class=\"stock-in\">In Stock</span>\n @else\n <span class=\"stock-low\">Low Stock</span>\n @endif\n </div>\n@endeach</code></pre>\n </div>\n </div>\n</body>\n</html>\n";
|
|
8
|
+
export const productsEjsContent = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>EJS View Engine • AeroJS + MySQL</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --bg-base: #060913;\n --bg-card: #0d1322;\n --bg-card-hover: #131c31;\n --border-subtle: #1e293b;\n --border-highlight: #06b6d4;\n --text-main: #f8fafc;\n --text-muted: #94a3b8;\n --brand-ejs: #06b6d4;\n --brand-ejs-light: #22d3ee;\n --brand-emerald: #10b981;\n --brand-amber: #f59e0b;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--bg-base);\n color: var(--text-main);\n min-height: 100vh;\n line-height: 1.6;\n padding-bottom: 4rem;\n }\n .ambient-glow {\n position: fixed;\n top: -220px;\n left: 50%;\n transform: translateX(-50%);\n width: 900px;\n height: 480px;\n background: radial-gradient(circle, rgba(6, 182, 212, 0.16) 0%, rgba(14, 165, 233, 0.08) 50%, transparent 70%);\n pointer-events: none;\n z-index: 0;\n }\n .container {\n max-width: 1180px;\n margin: 0 auto;\n padding: 0 1.5rem;\n position: relative;\n z-index: 1;\n }\n /* Top Navbar */\n nav {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem 0;\n border-bottom: 1px solid rgba(30, 41, 59, 0.6);\n margin-bottom: 2.5rem;\n }\n .nav-brand {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n text-decoration: none;\n font-size: 1.25rem;\n font-weight: 800;\n color: #fff;\n }\n .brand-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.25rem 0.75rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n background: rgba(6, 182, 212, 0.15);\n color: var(--brand-ejs-light);\n border: 1px solid rgba(6, 182, 212, 0.3);\n }\n .engine-switcher {\n display: flex;\n gap: 0.5rem;\n background: #0a0f1d;\n padding: 0.3rem;\n border-radius: 0.75rem;\n border: 1px solid var(--border-subtle);\n }\n .engine-tab {\n padding: 0.45rem 0.95rem;\n border-radius: 0.5rem;\n font-size: 0.85rem;\n font-weight: 600;\n text-decoration: none;\n color: #94a3b8;\n transition: all 0.2s;\n }\n .engine-tab:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.05);\n }\n .engine-tab.active {\n color: #fff;\n background: linear-gradient(135deg, #0891b2, #0284c7);\n box-shadow: 0 2px 10px rgba(8, 145, 178, 0.4);\n }\n\n /* Page Header */\n .header-banner {\n display: flex;\n justify-content: space-between;\n align-items: flex-end;\n flex-wrap: wrap;\n gap: 1.5rem;\n margin-bottom: 2.5rem;\n }\n .header-info h1 {\n font-size: 2.4rem;\n font-weight: 800;\n letter-spacing: -0.03em;\n line-height: 1.2;\n margin-bottom: 0.5rem;\n }\n .header-info h1 span {\n background: linear-gradient(135deg, #22d3ee, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .header-info p {\n color: var(--text-muted);\n font-size: 1.05rem;\n }\n .db-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.6rem 1.2rem;\n border-radius: 0.75rem;\n background: rgba(16, 185, 129, 0.1);\n border: 1px solid rgba(16, 185, 129, 0.25);\n color: #34d399;\n font-size: 0.85rem;\n font-weight: 600;\n font-family: 'JetBrains Mono', monospace;\n }\n .dot-live {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 10px #10b981;\n }\n\n /* Metric stats */\n .stats-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n gap: 1rem;\n margin-bottom: 2.5rem;\n }\n .stat-card {\n background: var(--bg-card);\n border: 1px solid var(--border-subtle);\n border-radius: 0.85rem;\n padding: 1.25rem;\n }\n .stat-label {\n color: var(--text-muted);\n font-size: 0.82rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 0.35rem;\n }\n .stat-val {\n font-size: 1.8rem;\n font-weight: 800;\n color: #fff;\n }\n\n /* Products Grid */\n .catalog-title {\n font-size: 1.35rem;\n font-weight: 700;\n letter-spacing: -0.02em;\n margin-bottom: 1.25rem;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n }\n .products-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.5rem;\n }\n .product-card {\n background: var(--bg-card);\n border: 1px solid var(--border-subtle);\n border-radius: 1rem;\n padding: 1.6rem;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n transition: all 0.25s ease;\n position: relative;\n overflow: hidden;\n }\n .product-card::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: linear-gradient(90deg, #06b6d4, #10b981);\n opacity: 0;\n transition: opacity 0.25s ease;\n }\n .product-card:hover {\n transform: translateY(-3px);\n border-color: rgba(6, 182, 212, 0.4);\n background: var(--bg-card-hover);\n box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);\n }\n .product-card:hover::before {\n opacity: 1;\n }\n .card-top {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 1rem;\n }\n .category-tag {\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--brand-ejs-light);\n background: rgba(6, 182, 212, 0.12);\n border: 1px solid rgba(6, 182, 212, 0.25);\n padding: 0.25rem 0.65rem;\n border-radius: 6px;\n }\n .stock-badge {\n font-size: 0.75rem;\n font-weight: 600;\n padding: 0.2rem 0.6rem;\n border-radius: 9999px;\n }\n .stock-in {\n background: rgba(16, 185, 129, 0.12);\n color: #34d399;\n border: 1px solid rgba(16, 185, 129, 0.25);\n }\n .stock-low {\n background: rgba(245, 158, 11, 0.12);\n color: #fbbf24;\n border: 1px solid rgba(245, 158, 11, 0.25);\n }\n .product-name {\n font-size: 1.2rem;\n font-weight: 700;\n color: #fff;\n margin-bottom: 0.5rem;\n line-height: 1.35;\n }\n .product-desc {\n color: var(--text-muted);\n font-size: 0.9rem;\n line-height: 1.6;\n margin-bottom: 1.5rem;\n flex-grow: 1;\n }\n .card-bottom {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 1rem;\n border-top: 1px solid rgba(30, 41, 59, 0.6);\n }\n .price-box {\n display: flex;\n flex-direction: column;\n }\n .price-label {\n font-size: 0.72rem;\n color: #64748b;\n text-transform: uppercase;\n font-weight: 600;\n }\n .price-val {\n font-size: 1.5rem;\n font-weight: 800;\n color: #fff;\n font-family: 'JetBrains Mono', monospace;\n }\n .rating-stars {\n color: #fbbf24;\n font-size: 0.85rem;\n margin-bottom: 0.3rem;\n }\n .btn-buy {\n background: linear-gradient(135deg, #0891b2, #0284c7);\n color: #fff;\n border: none;\n padding: 0.6rem 1.2rem;\n border-radius: 0.6rem;\n font-size: 0.88rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-buy:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 15px rgba(8, 145, 178, 0.4);\n }\n\n /* Template Snippet Box */\n .syntax-box {\n margin-top: 3.5rem;\n background: #080d1a;\n border: 1px solid var(--border-subtle);\n border-radius: 1rem;\n padding: 1.75rem;\n }\n .syntax-box h4 {\n font-size: 1rem;\n font-weight: 700;\n margin-bottom: 0.75rem;\n color: var(--brand-ejs-light);\n display: flex;\n align-items: center;\n gap: 0.5rem;\n }\n pre {\n background: #040711;\n border: 1px solid #1e293b;\n border-radius: 0.5rem;\n padding: 1rem;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.82rem;\n color: #e2e8f0;\n overflow-x: auto;\n }\n </style>\n</head>\n<body>\n <div class=\"ambient-glow\"></div>\n <div class=\"container\">\n <!-- Navbar -->\n <nav>\n <a href=\"/\" class=\"nav-brand\">\n <span>🚀 AeroJS</span>\n <span class=\"brand-pill\">Engine: EJS</span>\n </a>\n <div class=\"engine-switcher\">\n <a href=\"/views/edge\" class=\"engine-tab\">Edge.js</a>\n <a href=\"/views/ejs\" class=\"engine-tab active\">EJS</a>\n <a href=\"/views/react\" class=\"engine-tab\">React</a>\n <a href=\"/views/vue\" class=\"engine-tab\">Vue 3</a>\n </div>\n </nav>\n\n <!-- Header Banner -->\n <div class=\"header-banner\">\n <div class=\"header-info\">\n <h1><span>EJS</span> Embedded JavaScript</h1>\n <p>Classic Node.js templating engine rendered on the server with real MySQL database rows.</p>\n </div>\n <div class=\"db-badge\">\n <span class=\"dot-live\"></span>\n MySQL 8 • aerojs_app.products\n </div>\n </div>\n\n <!-- Quick Stats -->\n <div class=\"stats-row\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">Total Catalog Items</div>\n <div class=\"stat-val\"><%= products.length %></div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">Template Driver</div>\n <div class=\"stat-val\" style=\"font-size: 1.4rem; color: var(--brand-ejs-light);\">ejs (v3.1.10)</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">Database Engine</div>\n <div class=\"stat-val\" style=\"font-size: 1.4rem; color: #38bdf8;\">MySQL2 + Knex</div>\n </div>\n </div>\n\n <!-- Products Catalog Section -->\n <div class=\"catalog-title\">\n <span>📦</span> Live Products from MySQL Database\n </div>\n\n <div class=\"products-grid\">\n <% products.forEach(function(product) { %>\n <div class=\"product-card\">\n <div>\n <div class=\"card-top\">\n <span class=\"category-tag\"><%= product.category %></span>\n <% if (product.stock > 10) { %>\n <span class=\"stock-badge stock-in\">In Stock (<%= product.stock %>)</span>\n <% } else { %>\n <span class=\"stock-badge stock-low\">Low Stock (<%= product.stock %>)</span>\n <% } %>\n </div>\n\n <div class=\"rating-stars\">\n ★★★★★ (<%= product.rating %>.0)\n </div>\n\n <h3 class=\"product-name\"><%= product.name %></h3>\n <p class=\"product-desc\"><%= product.description %></p>\n </div>\n\n <div class=\"card-bottom\">\n <div class=\"price-box\">\n <span class=\"price-label\">Price</span>\n <span class=\"price-val\">$<%= product.price %></span>\n </div>\n <button class=\"btn-buy\" onclick=\"alert('Selected: <%= product.name %> - $<%= product.price %>')\">\n Select\n </button>\n </div>\n </div>\n <% }); %>\n </div>\n\n <!-- EJS Syntax Explainer -->\n <div class=\"syntax-box\">\n <h4><span>💡</span> How EJS Renders This Page</h4>\n <pre><code>// EJS scriptlet and interpolation syntax used directly above:\n<% products.forEach(function(product) { %>\n <div class=\"product-card\">\n <h3><%= product.name %></h3>\n <span>$<%= product.price %></span>\n <% if (product.stock > 10) { %>\n <span class=\"stock-in\">In Stock</span>\n <% } else { %>\n <span class=\"stock-low\">Low Stock</span>\n <% } %>\n </div>\n<% }); %></code></pre>\n </div>\n </div>\n</body>\n</html>\n";
|
|
9
|
+
export const productsReactContent = "import React, { useState, useMemo } from 'react';\n\nexport interface Product {\n id: number;\n name: string;\n description: string;\n price: number;\n category: string;\n stock: number;\n rating: number;\n created_at: string;\n}\n\nexport interface ProductsReactProps {\n products: Product[];\n}\n\nexport default function ProductsReact({ products = [] }: ProductsReactProps) {\n const [search, setSearch] = useState('');\n const [selectedCategory, setSelectedCategory] = useState('All');\n const [sortBy, setSortBy] = useState<'name' | 'price-asc' | 'price-desc'>('name');\n const [cart, setCart] = useState<{ id: number; name: string; price: number }[]>([]);\n const [activeModalProduct, setActiveModalProduct] = useState<Product | null>(null);\n\n // Categories\n const categories = useMemo(() => {\n const cats = ['All', ...new Set(products.map((p) => p.category))];\n return cats;\n }, [products]);\n\n // Filtered & Sorted products\n const filteredProducts = useMemo(() => {\n return products\n .filter((p) => {\n const matchesSearch =\n p.name.toLowerCase().includes(search.toLowerCase()) ||\n p.description.toLowerCase().includes(search.toLowerCase());\n const matchesCategory =\n selectedCategory === 'All' || p.category === selectedCategory;\n return matchesSearch && matchesCategory;\n })\n .sort((a, b) => {\n if (sortBy === 'price-asc') return a.price - b.price;\n if (sortBy === 'price-desc') return b.price - a.price;\n return a.name.localeCompare(b.name);\n });\n }, [products, search, selectedCategory, sortBy]);\n\n const addToCart = (product: Product) => {\n setCart((prev) => [...prev, { id: product.id, name: product.name, price: product.price }]);\n };\n\n const cartTotal = useMemo(() => cart.reduce((sum, item) => sum + item.price, 0), [cart]);\n\n return (\n <div className=\"react-container\">\n <header className=\"page-header\">\n <div>\n <span className=\"badge-react\">⚛️ Inertia.js + React 18</span>\n <h1>React Interactive Catalog</h1>\n <p>Powered by MySQL2 connection pool and real-time state management.</p>\n </div>\n <div className=\"cart-pill\">\n 🛒 Cart: <strong>{cart.length} items</strong> (${cartTotal})\n </div>\n </header>\n\n {/* Filter & Search Bar */}\n <div className=\"controls-bar\">\n <input\n type=\"text\"\n placeholder=\"Search products in MySQL...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"search-input\"\n />\n\n <div className=\"category-pills\">\n {categories.map((cat) => (\n <button\n key={cat}\n onClick={() => setSelectedCategory(cat)}\n className={`pill-btn ${selectedCategory === cat ? 'active' : ''}`}\n >\n {cat}\n </button>\n ))}\n </div>\n\n <select\n value={sortBy}\n onChange={(e) => setSortBy(e.target.value as any)}\n className=\"sort-select\"\n >\n <option value=\"name\">Sort by Name</option>\n <option value=\"price-asc\">Price: Low to High</option>\n <option value=\"price-desc\">Price: High to Low</option>\n </select>\n </div>\n\n {/* Grid */}\n <div className=\"products-grid\">\n {filteredProducts.map((product) => (\n <div key={product.id} className=\"product-card\">\n <div className=\"card-top\">\n <span className=\"cat-tag\">{product.category}</span>\n <span className={product.stock > 10 ? 'stock-ok' : 'stock-low'}>\n {product.stock > 10 ? `In Stock (${product.stock})` : `Low Stock (${product.stock})`}\n </span>\n </div>\n <h3>{product.name}</h3>\n <p>{product.description}</p>\n <div className=\"card-bottom\">\n <div className=\"price-tag\">${product.price}</div>\n <div className=\"actions\">\n <button\n className=\"btn-details\"\n onClick={() => setActiveModalProduct(product)}\n >\n Quick View\n </button>\n <button className=\"btn-add\" onClick={() => addToCart(product)}>\n + Add to Cart\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Quick View Modal */}\n {activeModalProduct && (\n <div className=\"modal-backdrop\" onClick={() => setActiveModalProduct(null)}>\n <div className=\"modal-content\" onClick={(e) => e.stopPropagation()}>\n <h2>{activeModalProduct.name}</h2>\n <p className=\"modal-cat\">Category: {activeModalProduct.category} • Rating: {activeModalProduct.rating}/5</p>\n <p className=\"modal-desc\">{activeModalProduct.description}</p>\n <div className=\"modal-price\">${activeModalProduct.price}</div>\n <div className=\"modal-actions\">\n <button\n className=\"btn-add\"\n onClick={() => {\n addToCart(activeModalProduct);\n setActiveModalProduct(null);\n }}\n >\n Add to Cart\n </button>\n <button\n className=\"btn-close\"\n onClick={() => setActiveModalProduct(null)}\n >\n Close\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n";
|
|
10
|
+
export const productsVueContent = "<script setup lang=\"ts\">\nimport { ref, computed } from 'vue';\n\nexport interface Product {\n id: number;\n name: string;\n description: string;\n price: number;\n category: string;\n stock: number;\n rating: number;\n created_at: string;\n}\n\nconst props = defineProps<{\n products: Product[];\n}>();\n\nconst search = ref('');\nconst selectedCategory = ref('All');\nconst sortBy = ref<'name' | 'price-asc' | 'price-desc'>('name');\nconst cart = ref<{ id: number; name: string; price: number }[]>([]);\nconst activeModalProduct = ref<Product | null>(null);\n\nconst categories = computed(() => {\n const cats = ['All', ...new Set((props.products || []).map((p) => p.category))];\n return cats;\n});\n\nconst filteredProducts = computed(() => {\n return (props.products || [])\n .filter((p) => {\n const matchesSearch =\n p.name.toLowerCase().includes(search.value.toLowerCase()) ||\n p.description.toLowerCase().includes(search.value.toLowerCase());\n const matchesCategory =\n selectedCategory.value === 'All' || p.category === selectedCategory.value;\n return matchesSearch && matchesCategory;\n })\n .sort((a, b) => {\n if (sortBy.value === 'price-asc') return a.price - b.price;\n if (sortBy.value === 'price-desc') return b.price - a.price;\n return a.name.localeCompare(b.name);\n });\n});\n\nconst cartTotal = computed(() =>\n cart.value.reduce((sum, item) => sum + item.price, 0)\n);\n\nfunction addToCart(product: Product) {\n cart.value.push({ id: product.id, name: product.name, price: product.price });\n}\n\nfunction openModal(product: Product) {\n activeModalProduct.value = product;\n}\n\nfunction closeModal() {\n activeModalProduct.value = null;\n}\n</script>\n\n<template>\n <div class=\"vue-container\">\n <header class=\"page-header\">\n <div>\n <span class=\"badge-vue\">💚 Inertia.js + Vue 3 (Composition API)</span>\n <h1>Vue 3 Reactive Catalog</h1>\n <p>Ultra-responsive reactive frontend wired directly to MySQL database records.</p>\n </div>\n <div class=\"cart-pill\">\n 🛒 Cart: <strong>{{ cart.length }} items</strong> (${{ cartTotal }})\n </div>\n </header>\n\n <!-- Controls Bar -->\n <div class=\"controls-bar\">\n <input\n v-model=\"search\"\n type=\"text\"\n placeholder=\"Filter MySQL products in real time...\"\n className=\"search-input\"\n />\n\n <div class=\"category-pills\">\n <button\n v-for=\"cat in categories\"\n :key=\"cat\"\n @click=\"selectedCategory = cat\"\n :class=\"['pill-btn', { active: selectedCategory === cat }]\"\n >\n {{ cat }}\n </button>\n </div>\n\n <select v-model=\"sortBy\" className=\"sort-select\">\n <option value=\"name\">Sort by Name</option>\n <option value=\"price-asc\">Price: Low to High</option>\n <option value=\"price-desc\">Price: High to Low</option>\n </select>\n </div>\n\n <!-- Products Grid -->\n <div class=\"products-grid\">\n <div\n v-for=\"product in filteredProducts\"\n :key=\"product.id\"\n class=\"product-card\"\n >\n <div class=\"card-top\">\n <span class=\"cat-tag\">{{ product.category }}</span>\n <span :class=\"product.stock > 10 ? 'stock-ok' : 'stock-low'\">\n {{ product.stock > 10 ? `In Stock (${product.stock})` : `Low Stock (${product.stock})` }}\n </span>\n </div>\n <h3>{{ product.name }}</h3>\n <p>{{ product.description }}</p>\n <div class=\"card-bottom\">\n <div class=\"price-tag\">${{ product.price }}</div>\n <div class=\"actions\">\n <button class=\"btn-details\" @click=\"openModal(product)\">\n Quick View\n </button>\n <button class=\"btn-add\" @click=\"addToCart(product)\">\n + Add to Cart\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Quick View Modal -->\n <div\n v-if=\"activeModalProduct\"\n class=\"modal-backdrop\"\n @click=\"closeModal\"\n >\n <div class=\"modal-content\" @click.stop>\n <h2>{{ activeModalProduct.name }}</h2>\n <p class=\"modal-cat\">\n Category: {{ activeModalProduct.category }} • Rating: {{ activeModalProduct.rating }}/5\n </p>\n <p class=\"modal-desc\">{{ activeModalProduct.description }}</p>\n <div class=\"modal-price\">${{ activeModalProduct.price }}</div>\n <div class=\"modal-actions\">\n <button\n class=\"btn-add\"\n @click=\"addToCart(activeModalProduct); closeModal()\"\n >\n Add to Cart\n </button>\n <button class=\"btn-close\" @click=\"closeModal\">\n Close\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n";
|
|
11
|
+
export const productModelContent = "import { Model } from 'aerojs';\n\nexport interface ProductAttributes {\n id?: number;\n name: string;\n description: string;\n price: number;\n category: string;\n stock: number;\n rating: number;\n created_at?: string;\n updated_at?: string;\n}\n\nexport class Product extends Model {\n public static override table = 'products';\n public static override primaryKey = 'id';\n public static override fillable = [\n 'name',\n 'description',\n 'price',\n 'category',\n 'stock',\n 'rating',\n ];\n}\n";
|
|
12
|
+
export const initDbContent = "import knex from 'knex';\nimport { useKnex, DB } from 'aerojs';\nimport { databaseConfig } from '../config/database.js';\n\nexport async function initDatabase(): Promise<void> {\n // Initialize Knex with MySQL connection pool\n const db = knex({\n client: databaseConfig.connections.mysql.client,\n connection: databaseConfig.connections.mysql.connection,\n pool: databaseConfig.connections.mysql.pool,\n });\n\n useKnex(db);\n\n // Ensure products table exists\n const hasTable = await db.schema.hasTable('products');\n if (!hasTable) {\n await db.schema.createTable('products', (table) => {\n table.increments('id').primary();\n table.string('name', 255).notNullable();\n table.text('description');\n table.integer('price').notNullable().defaultTo(0);\n table.string('category', 100).notNullable().defaultTo('General');\n table.integer('stock').notNullable().defaultTo(0);\n table.integer('rating').notNullable().defaultTo(5);\n table.timestamps(true, true);\n });\n console.log('✅ Created MySQL table: products');\n }\n\n // Seed initial products if table has fewer than 5 products\n const countResult = await DB.table('products').count();\n if (countResult < 5) {\n await DB.table('products').delete();\n await DB.table('products').insert([\n {\n name: 'MacBook Pro M3 Max',\n description: 'Apple M3 Max chip with 16-core CPU and 40-core GPU, 36GB Unified Memory.',\n price: 3499,\n category: 'Laptops',\n stock: 14,\n rating: 5,\n },\n {\n name: 'Sony WH-1000XM5 Wireless Headphones',\n description: 'Industry-leading noise cancellation with two processors and 8 microphones.',\n price: 399,\n category: 'Audio',\n stock: 35,\n rating: 5,\n },\n {\n name: 'Dell UltraSharp 32\" 4K QD-OLED Monitor',\n description: 'Stunning 4K resolution with Quantum Dot OLED panel and 99% DCI-P3 color.',\n price: 1199,\n category: 'Monitors',\n stock: 8,\n rating: 5,\n },\n {\n name: 'Keychron Q1 Pro Custom Mechanical Keyboard',\n description: 'Wireless custom mechanical keyboard with CNC aluminum body and hot-swappable switches.',\n price: 199,\n category: 'Keyboards',\n stock: 50,\n rating: 4,\n },\n {\n name: 'Logitech MX Master 3S Wireless Mouse',\n description: 'Quiet clicks and 8K DPI track-on-glass sensor for ultimate precision.',\n price: 99,\n category: 'Accessories',\n stock: 80,\n rating: 5,\n },\n ]);\n console.log('🌱 Seeded 5 initial products into MySQL!');\n }\n}\n\n// Run immediately if executed directly\nif (process.argv[1]?.endsWith('init-db.ts') || process.argv[1]?.endsWith('init-db.js')) {\n initDatabase()\n .then(() => {\n console.log('🎉 Database initialization complete!');\n process.exit(0);\n })\n .catch((err) => {\n console.error('❌ Database initialization error:', err);\n process.exit(1);\n });\n}\n";
|
|
13
|
+
export const webRoutesContent = "import type { Router, AeroContext } from 'aerojs';\nimport { DB } from 'aerojs';\nimport { renderEdge, renderEjs } from '../app/views/engine.js';\n\nexport function registerWebRoutes(router: Router): void {\n router.get('/', async (ctx: AeroContext) => {\n ctx.html(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Welcome to AeroJS — High Performance Full-Stack Framework</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --bg-base: #060913;\n --bg-card: #0d1322;\n --bg-card-hover: #131c31;\n --border-subtle: #1e293b;\n --border-highlight: #38bdf8;\n --text-main: #f8fafc;\n --text-muted: #94a3b8;\n --brand-primary: #38bdf8;\n --brand-cyan: #06b6d4;\n --brand-indigo: #6366f1;\n --brand-emerald: #10b981;\n --brand-amber: #f59e0b;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--bg-base);\n color: var(--text-main);\n min-height: 100vh;\n line-height: 1.6;\n overflow-x: hidden;\n padding-bottom: 4rem;\n }\n /* Subtle Ambient Glow */\n .glow-bg {\n position: fixed;\n top: -200px;\n left: 50%;\n transform: translateX(-50%);\n width: 800px;\n height: 450px;\n background: radial-gradient(circle, rgba(56, 189, 248, 0.15) 0%, rgba(99, 102, 241, 0.08) 50%, transparent 70%);\n pointer-events: none;\n z-index: 0;\n }\n .container {\n max-width: 1140px;\n margin: 0 auto;\n padding: 0 1.5rem;\n position: relative;\n z-index: 1;\n }\n /* Navbar */\n nav {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem 0;\n border-bottom: 1px solid rgba(30, 41, 59, 0.6);\n margin-bottom: 3.5rem;\n }\n .logo {\n display: flex;\n align-items: center;\n gap: 0.65rem;\n font-size: 1.35rem;\n font-weight: 800;\n letter-spacing: -0.03em;\n color: #fff;\n text-decoration: none;\n }\n .logo span {\n background: linear-gradient(135deg, var(--brand-primary), var(--brand-indigo));\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .badge-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.25rem 0.75rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n background: rgba(56, 189, 248, 0.1);\n color: var(--brand-primary);\n border: 1px solid rgba(56, 189, 248, 0.25);\n }\n .status-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--brand-emerald);\n box-shadow: 0 0 8px var(--brand-emerald);\n }\n /* Hero */\n .hero {\n text-align: center;\n max-width: 820px;\n margin: 0 auto 4rem;\n }\n .hero h1 {\n font-size: 3rem;\n font-weight: 800;\n letter-spacing: -0.04em;\n line-height: 1.15;\n margin: 1rem 0;\n }\n .hero h1 .gradient-text {\n background: linear-gradient(135deg, #ffffff 30%, #94a3b8 100%);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .hero h1 .accent-text {\n background: linear-gradient(135deg, var(--brand-primary) 0%, #818cf8 100%);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .hero p {\n color: var(--text-muted);\n font-size: 1.15rem;\n margin-bottom: 2rem;\n line-height: 1.7;\n }\n .feature-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 0.6rem;\n justify-content: center;\n margin-bottom: 2rem;\n }\n .tag {\n font-size: 0.8rem;\n font-weight: 500;\n padding: 0.35rem 0.85rem;\n border-radius: 6px;\n background: rgba(30, 41, 59, 0.7);\n color: #cbd5e1;\n border: 1px solid rgba(51, 65, 85, 0.6);\n }\n /* Action Buttons */\n .hero-cta {\n display: flex;\n gap: 1rem;\n justify-content: center;\n align-items: center;\n }\n .btn {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1.6rem;\n border-radius: 0.6rem;\n font-size: 0.95rem;\n font-weight: 600;\n text-decoration: none;\n transition: all 0.2s ease;\n cursor: pointer;\n border: 1px solid transparent;\n }\n .btn-primary {\n background: linear-gradient(135deg, #0284c7, #2563eb);\n color: #fff;\n box-shadow: 0 4px 20px rgba(2, 132, 199, 0.35);\n }\n .btn-primary:hover {\n background: linear-gradient(135deg, #0369a1, #1d4ed8);\n transform: translateY(-1px);\n box-shadow: 0 6px 24px rgba(2, 132, 199, 0.5);\n }\n .btn-secondary {\n background: #131c2e;\n color: #e2e8f0;\n border-color: #1e293b;\n }\n .btn-secondary:hover {\n background: #1e293b;\n color: #fff;\n transform: translateY(-1px);\n }\n\n /* Sections Grid */\n .section-title {\n font-size: 1.5rem;\n font-weight: 700;\n letter-spacing: -0.02em;\n margin-bottom: 1.5rem;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n }\n .grid-2 {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 1.5rem;\n margin-bottom: 3.5rem;\n }\n .grid-3 {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n gap: 1.25rem;\n margin-bottom: 3.5rem;\n }\n .card {\n background: var(--bg-card);\n border: 1px solid var(--border-subtle);\n border-radius: 1rem;\n padding: 1.75rem;\n transition: border-color 0.2s, background 0.2s;\n }\n .card:hover {\n border-color: rgba(56, 189, 248, 0.3);\n background: var(--bg-card-hover);\n }\n .card-header {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 1rem;\n }\n .card-icon {\n width: 38px;\n height: 38px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.25rem;\n background: rgba(56, 189, 248, 0.1);\n color: var(--brand-primary);\n border: 1px solid rgba(56, 189, 248, 0.2);\n }\n .card h3 {\n font-size: 1.15rem;\n font-weight: 700;\n color: #f1f5f9;\n }\n .card p {\n color: var(--text-muted);\n font-size: 0.92rem;\n line-height: 1.6;\n margin-bottom: 1.25rem;\n }\n\n /* Code Snippets */\n pre {\n background: #060913;\n border: 1px solid #1e293b;\n border-radius: 0.65rem;\n padding: 1rem;\n overflow-x: auto;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.82rem;\n color: #e2e8f0;\n line-height: 1.6;\n }\n .code-keyword { color: #f472b6; }\n .code-func { color: #38bdf8; }\n .code-str { color: #34d399; }\n .code-comment { color: #64748b; font-style: italic; }\n\n /* Interactive API Sandbox */\n .sandbox-card {\n background: linear-gradient(180deg, #0e172a 0%, #060913 100%);\n border: 1px solid #1e293b;\n border-radius: 1rem;\n padding: 2rem;\n margin-bottom: 3.5rem;\n }\n .sandbox-controls {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n margin-bottom: 1.25rem;\n }\n .sandbox-btn {\n background: #1e293b;\n color: #e2e8f0;\n border: 1px solid #334155;\n padding: 0.6rem 1.2rem;\n border-radius: 0.5rem;\n font-size: 0.88rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n }\n .sandbox-btn:hover {\n background: #0284c7;\n border-color: #0284c7;\n color: #fff;\n }\n .terminal-window {\n background: #030712;\n border: 1px solid #1f2937;\n border-radius: 0.75rem;\n padding: 1.25rem;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.85rem;\n color: #10b981;\n min-height: 120px;\n white-space: pre-wrap;\n overflow-x: auto;\n }\n\n /* AI Assistant Banner */\n .ai-banner {\n background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(56, 189, 248, 0.15) 100%);\n border: 1px solid rgba(99, 102, 241, 0.3);\n border-radius: 1rem;\n padding: 2rem;\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n text-align: center;\n align-items: center;\n }\n .ai-banner h3 {\n font-size: 1.3rem;\n font-weight: 700;\n color: #fff;\n }\n .ai-banner p {\n color: #cbd5e1;\n max-width: 680px;\n font-size: 0.95rem;\n }\n footer {\n text-align: center;\n color: #64748b;\n font-size: 0.85rem;\n margin-top: 3.5rem;\n border-top: 1px solid #1e293b;\n padding-top: 2rem;\n }\n </style>\n </head>\n <body>\n <div class=\"glow-bg\"></div>\n <div class=\"container\">\n <!-- Navbar -->\n <nav>\n <a href=\"/\" class=\"logo\">\n <span>🚀 AeroJS</span>\n </a>\n <div style=\"display: flex; align-items: center; gap: 1rem;\">\n <span class=\"badge-pill\">\n <span class=\"status-dot\"></span>\n v0.1.0 Ready\n </span>\n <a href=\"/api/health\" class=\"tag\" style=\"text-decoration: none;\">API Status</a>\n </div>\n </nav>\n\n <!-- Hero Section -->\n <section class=\"hero\">\n <div class=\"badge-pill\" style=\"margin-bottom: 1.25rem;\">\n ⚡ High-Performance Full-Stack TypeScript Framework\n </div>\n <h1>\n <span class=\"gradient-text\">Welcome to </span>\n <span class=\"accent-text\">AeroJS</span>\n </h1>\n <p>\n Your enterprise-grade application is scaffolded and running with full MVC architecture,\n Active Record ORM, multi-dialect database connection pooling, and multi-frontend view drivers.\n </p>\n\n <div class=\"feature-tags\">\n <span class=\"tag\">Zero Core Dependencies</span>\n <span class=\"tag\">Active Record ORM</span>\n <span class=\"tag\">MySQL & PostgreSQL Pool</span>\n <span class=\"tag\">Inertia.js (Vue & React)</span>\n <span class=\"tag\">Edge.js & EJS</span>\n <span class=\"tag\">Background Queue & Mail</span>\n <span class=\"tag\">AI-Native AGENTS.md</span>\n </div>\n\n <div class=\"hero-cta\">\n <a href=\"#view-engines\" class=\"btn btn-primary\" style=\"background: linear-gradient(135deg, #a855f7, #0284c7);\">⚡ Test 4 Frontend Engines</a>\n <a href=\"#sandbox\" class=\"btn btn-secondary\">Test Live APIs</a>\n </div>\n </section>\n\n <!-- 4 Frontend & View Engines Showcase -->\n <section id=\"view-engines\" style=\"margin-bottom: 3.5rem;\">\n <div class=\"section-title\">\n <span>🎨</span> 4 Frontend & View Engines (MySQL Powered)\n </div>\n <p style=\"color: var(--text-muted); margin-bottom: 1.5rem;\">\n Each engine below fetches real products directly from your <strong>MySQL (aerojs_app)</strong> database and tests rendering with its unique syntax and client interactivity:\n </p>\n\n <div class=\"grid-2\">\n <!-- Engine 1: Edge.js -->\n <div class=\"card\" style=\"border-color: rgba(168, 85, 247, 0.3);\">\n <div class=\"card-header\">\n <div class=\"card-icon\" style=\"background: rgba(168, 85, 247, 0.15); color: #c084fc; border-color: rgba(168, 85, 247, 0.3);\">🚀</div>\n <div>\n <h3>1. Edge.js (AdonisJS Engine)</h3>\n <small style=\"color: #c084fc;\">Server-Side Template Engine</small>\n </div>\n </div>\n <p>Features <code>@each(product in products)</code>, <code>@if/@endif</code>, layouts, and components with blazing-fast Node.js compilation.</p>\n <div style=\"margin-top: 1rem;\">\n <a href=\"/views/edge\" target=\"_blank\" class=\"btn btn-primary\" style=\"background: linear-gradient(135deg, #9333ea, #7e22ce); font-size: 0.85rem; padding: 0.55rem 1.1rem;\">\n Launch Edge.js View →\n </a>\n </div>\n </div>\n\n <!-- Engine 2: EJS -->\n <div class=\"card\" style=\"border-color: rgba(6, 182, 212, 0.3);\">\n <div class=\"card-header\">\n <div class=\"card-icon\" style=\"background: rgba(6, 182, 212, 0.15); color: #22d3ee; border-color: rgba(6, 182, 212, 0.3);\">⚡</div>\n <div>\n <h3>2. EJS (Embedded JavaScript)</h3>\n <small style=\"color: #22d3ee;\">Classic Express / Node Template Engine</small>\n </div>\n </div>\n <p>Standard JavaScript scriptlets with <code><% products.forEach(...) %></code> and <code><%= product.name %></code> variable outputs.</p>\n <div style=\"margin-top: 1rem;\">\n <a href=\"/views/ejs\" target=\"_blank\" class=\"btn btn-primary\" style=\"background: linear-gradient(135deg, #0891b2, #0284c7); font-size: 0.85rem; padding: 0.55rem 1.1rem;\">\n Launch EJS View →\n </a>\n </div>\n </div>\n\n <!-- Engine 3: React 18 -->\n <div class=\"card\" style=\"border-color: rgba(56, 189, 248, 0.3);\">\n <div class=\"card-header\">\n <div class=\"card-icon\" style=\"background: rgba(56, 189, 248, 0.15); color: #38bdf8; border-color: rgba(56, 189, 248, 0.3);\">⚛️</div>\n <div>\n <h3>3. React 18 (Inertia.js SPA)</h3>\n <small style=\"color: #38bdf8;\">Client React Component + Server Hydration</small>\n </div>\n </div>\n <p>Interactive single-page catalog with live client-side search, category filter pills, price sorting, cart counter, and modal.</p>\n <div style=\"margin-top: 1rem;\">\n <a href=\"/views/react\" target=\"_blank\" class=\"btn btn-primary\" style=\"background: linear-gradient(135deg, #0284c7, #2563eb); font-size: 0.85rem; padding: 0.55rem 1.1rem;\">\n Launch React View →\n </a>\n </div>\n </div>\n\n <!-- Engine 4: Vue 3 -->\n <div class=\"card\" style=\"border-color: rgba(16, 185, 129, 0.3);\">\n <div class=\"card-header\">\n <div class=\"card-icon\" style=\"background: rgba(16, 185, 129, 0.15); color: #34d399; border-color: rgba(16, 185, 129, 0.3);\">💚</div>\n <div>\n <h3>4. Vue 3 (Composition API)</h3>\n <small style=\"color: #34d399;\">Inertia Protocol + Vue 3 Reactivity</small>\n </div>\n </div>\n <p>Composition API with <code>ref()</code> and <code>computed()</code>, reactive filtering, interactive modal details, and toast notifications.</p>\n <div style=\"margin-top: 1rem;\">\n <a href=\"/views/vue\" target=\"_blank\" class=\"btn btn-primary\" style=\"background: linear-gradient(135deg, #059669, #10b981); font-size: 0.85rem; padding: 0.55rem 1.1rem;\">\n Launch Vue 3 View →\n </a>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Core Feature Cards -->\n <section id=\"features\">\n <div class=\"section-title\">\n <span>💎</span> Framework Capabilities & Usage Guide\n </div>\n\n <div class=\"grid-2\">\n <!-- Feature 1: Layered Architecture -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"card-icon\">🏛️</div>\n <div>\n <h3>Layered MVC + Service Architecture</h3>\n <small style=\"color: #64748b;\">Clean Separation of Concerns</small>\n </div>\n </div>\n <p>Clean request pipelines with Controller Tuples and decoupled business logic inside services:</p>\n <pre><code><span class=\"code-comment\">// routes/api.ts</span>\nrouter.<span class=\"code-func\">group</span>(<span class=\"code-str\">'/api'</span>, (api) => {\n api.<span class=\"code-func\">get</span>(<span class=\"code-str\">'/users'</span>, [UserController, <span class=\"code-str\">'index'</span>]);\n api.<span class=\"code-func\">post</span>(<span class=\"code-str\">'/users'</span>, [UserController, <span class=\"code-str\">'store'</span>])\n .<span class=\"code-func\">schema</span>(createUserSchema);\n});</code></pre>\n </div>\n\n <!-- Feature 2: Database & Connection Pooling -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"card-icon\">🗄️</div>\n <div>\n <h3>Multi-Dialect Database & Pooling</h3>\n <small style=\"color: #64748b;\">MySQL • PostgreSQL • SQLite • Memory</small>\n </div>\n </div>\n <p>Configured with Knex pooling (<span style=\"color:#38bdf8;\">DB_POOL_MIN</span> & <span style=\"color:#38bdf8;\">DB_POOL_MAX</span>) for high-concurrency production workloads:</p>\n <pre><code><span class=\"code-comment\">// config/database.ts</span>\n<span class=\"code-keyword\">const</span> poolMin = parseInt(process.env.DB_POOL_MIN || <span class=\"code-str\">'2'</span>, 10);\n<span class=\"code-keyword\">const</span> poolMax = parseInt(process.env.DB_POOL_MAX || <span class=\"code-str\">'20'</span>, 10);\n\n<span class=\"code-keyword\">export const</span> databaseConfig = {\n <span class=\"code-func\">default</span>: process.env.DB_CONNECTION || <span class=\"code-str\">'mysql'</span>,\n pool: { min: poolMin, max: poolMax }\n};</code></pre>\n </div>\n\n <!-- Feature 3: Active Record ORM & QueryBuilder -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"card-icon\">⚡</div>\n <div>\n <h3>Active Record ORM & Migrations</h3>\n <small style=\"color: #64748b;\">Fluent Models & Schema Blueprint</small>\n </div>\n </div>\n <p>Laravel/Adonis style models, relationships, soft deletes, and timestamped schema migrations:</p>\n <pre><code><span class=\"code-comment\">// Find or mutate active record</span>\n<span class=\"code-keyword\">const</span> user = <span class=\"code-keyword\">await</span> User.<span class=\"code-func\">where</span>(<span class=\"code-str\">'role'</span>, <span class=\"code-str\">'admin'</span>).<span class=\"code-func\">first</span>();\nuser.name = <span class=\"code-str\">'Lead Architect'</span>;\n<span class=\"code-keyword\">await</span> user.<span class=\"code-func\">save</span>();\n\n<span class=\"code-comment\">// Fluent QueryBuilder</span>\n<span class=\"code-keyword\">const</span> paged = <span class=\"code-keyword\">await</span> DB.<span class=\"code-func\">table</span>(<span class=\"code-str\">'users'</span>).<span class=\"code-func\">paginate</span>(1, 15);</code></pre>\n </div>\n\n <!-- Feature 4: Frontend View Adapters -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"card-icon\">🎨</div>\n <div>\n <h3>Frontend Views: Inertia, Edge, EJS</h3>\n <small style=\"color: #64748b;\">React, Vue 3, or Template SSR</small>\n </div>\n </div>\n <p>Support for Modern SPAs with Inertia.js or server-rendered HTML using Edge.js or EJS:</p>\n <pre><code><span class=\"code-comment\">// Inertia.js (React / Vue 3)</span>\n<span class=\"code-keyword\">await</span> ctx.inertia.<span class=\"code-func\">render</span>(<span class=\"code-str\">'Users/Index'</span>, { users });\n\n<span class=\"code-comment\">// Edge.js or EJS Template</span>\n<span class=\"code-keyword\">await</span> ctx.<span class=\"code-func\">view</span>(<span class=\"code-str\">'welcome'</span>, { title: <span class=\"code-str\">'Dashboard'</span> });</code></pre>\n </div>\n </div>\n </section>\n\n <!-- Interactive Live API Sandbox -->\n <section id=\"sandbox\">\n <div class=\"section-title\">\n <span>🧪</span> Interactive API Sandbox\n </div>\n <div class=\"sandbox-card\">\n <p style=\"color: #94a3b8; margin-bottom: 1.25rem;\">\n Click below to send live requests to your AeroJS application and inspect the server response:\n </p>\n <div class=\"sandbox-controls\">\n <button class=\"sandbox-btn\" id=\"btn-health\">GET /api/health</button>\n <button class=\"sandbox-btn\" id=\"btn-users\">GET /api/users</button>\n <a href=\"/demo-error\" target=\"_blank\" class=\"sandbox-btn\" style=\"background: rgba(239, 68, 68, 0.2); color: #f87171; border-color: rgba(239, 68, 68, 0.4); text-decoration: none;\">💥 Test Error Dashboard</a>\n <button class=\"sandbox-btn\" id=\"btn-clear\" style=\"background: transparent; border-color: #334155;\">Clear Console</button>\n </div>\n <div class=\"terminal-window\" id=\"terminal-output\">// Click a button above to run live API test...</div>\n </div>\n </section>\n\n <!-- AI Assistant Guidelines Banner -->\n <section class=\"ai-banner\">\n <h3>🤖 AI-Native Engineering with AGENTS.md</h3>\n <p>\n Your project comes with an exhaustive <strong>AGENTS.md</strong> and <strong>AEROJS.md</strong> in the project root.\n Any AI agent (Gemini, Claude, Cursor, Windsurf, Copilot) reading this file will instantly know how to write 100% correct AeroJS code.\n </p>\n <div style=\"font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; color: #38bdf8; background: rgba(0,0,0,0.3); padding: 0.4rem 1rem; border-radius: 6px;\">\n d:\\\\nodejs\\\\my-aero-app\\\\AGENTS.md\n </div>\n </section>\n\n <!-- Footer -->\n <footer>\n <p>AeroJS Framework • Designed for maximum developer speed, zero core bloat, and enterprise scalability.</p>\n </footer>\n </div>\n\n <script>\n const terminal = document.getElementById('terminal-output');\n\n async function callApi(url) {\n terminal.innerText = 'Connecting to ' + url + '...';\n try {\n const start = performance.now();\n const res = await fetch(url);\n const data = await res.json();\n const elapsed = Math.round(performance.now() - start);\n terminal.innerText = 'HTTP ' + res.status + ' OK (' + elapsed + 'ms)\\\\n' + JSON.stringify(data, null, 2);\n } catch (err) {\n terminal.innerText = 'Request Error: ' + err.message;\n }\n }\n\n document.getElementById('btn-health').addEventListener('click', () => callApi('/api/health'));\n document.getElementById('btn-users').addEventListener('click', () => callApi('/api/users'));\n document.getElementById('btn-clear').addEventListener('click', () => {\n terminal.innerText = '// Console cleared.';\n });\n </script>\n </body>\n </html>\n `);\n });\n\n // 1. Edge.js View Engine Route (MySQL Powered)\n router.get('/views/edge', async (ctx: AeroContext) => {\n const products = await DB.table('products').get();\n const html = await renderEdge('edge/products', { products });\n ctx.html(html);\n });\n\n // 2. EJS View Engine Route (MySQL Powered)\n router.get('/views/ejs', async (ctx: AeroContext) => {\n const products = await DB.table('products').get();\n const html = await renderEjs('ejs/products.ejs', { products });\n ctx.html(html);\n });\n\n // 3. React 18 (Inertia.js SPA) Route (MySQL Powered)\n router.get('/views/react', async (ctx: AeroContext) => {\n const products = await DB.table('products').get();\n await ctx.inertia.render('ProductsReact', { products });\n });\n\n // 4. Vue 3 (Inertia.js SPA) Route (MySQL Powered)\n router.get('/views/vue', async (ctx: AeroContext) => {\n const products = await DB.table('products').get();\n await ctx.inertia.render('ProductsVue', { products });\n });\n\n // Demo route intentionally triggering an unhandled exception to demonstrate the Next.js style Error Dashboard\n router.get('/demo-error', () => {\n const user: any = undefined;\n // This will trigger: TypeError: Cannot read properties of undefined (reading 'profile')\n return user.profile.getSettings();\n });\n}\n";
|
|
14
|
+
export const apiRoutesContent = "import type { Router, AeroContext } from 'aerojs';\nimport { HealthController } from '../app/controllers/HealthController.js';\nimport { UserController } from '../app/controllers/UserController.js';\nimport { createUserSchema } from '../app/validators/UserValidator.js';\nimport { Product } from '../app/models/Product.js';\n\nexport function registerApiRoutes(router: Router): void {\n router.group('/api', (api) => {\n api.get('/health', [HealthController, 'check']);\n\n api.get('/products', async (ctx: AeroContext) => {\n const products = await Product.all();\n ctx.json({ success: true, count: products.length, data: products });\n });\n\n api.group('/users', (users) => {\n users.get('/', [UserController, 'index']);\n users.get('/:id', [UserController, 'show']);\n users.post('/', [UserController, 'store']).schema(createUserSchema);\n });\n });\n}\n";
|
|
15
|
+
export const serverTsContent = "import { AeroJS } from 'aerojs';\nimport { appConfig } from './config/app.js';\nimport { registerApiRoutes } from './routes/api.js';\nimport { registerWebRoutes } from './routes/web.js';\nimport { join } from 'node:path';\nimport { initDatabase } from './database/init-db.js';\nimport { getReactShell, getVueShell } from './app/views/shells.js';\n\n// Initialize Database connection and ensure tables exist\nawait initDatabase();\n\nconst app = new AeroJS({\n debug: appConfig.debug,\n trustProxy: true,\n});\n\n// Middlewares\napp.useCors();\napp.useSecurityHeaders();\napp.serveStatic('/public', join(process.cwd(), 'public'));\n\n// Configure Inertia for React & Vue 3 full-stack SPAs\napp.useInertia({\n rootView: (page) => {\n if (page.component === 'ProductsVue') {\n return getVueShell(page);\n }\n return getReactShell(page);\n },\n});\n\n// Register Routes\nregisterWebRoutes(app.router);\nregisterApiRoutes(app.router);\n\n// Start server (only when not in test runner)\nif (process.env.NODE_ENV !== 'test') {\n app.listen(appConfig.port, () => {\n console.log(`\\n🚀 AeroJS Server running at http://localhost:${appConfig.port}`);\n console.log(`👉 Health Check: http://localhost:${appConfig.port}/api/health`);\n console.log(`👉 Users API: http://localhost:${appConfig.port}/api/users\\n`);\n });\n}\n\nexport default app;\n";
|
|
16
|
+
export const databaseConfigContent = "/**\n * Database Configuration for AeroJS\n * Supports MySQL, PostgreSQL, SQLite, and In-Memory out-of-the-box.\n *\n * Connection pooling is configured via DB_POOL_MIN and DB_POOL_MAX:\n */\nconst poolMin = parseInt(process.env.DB_POOL_MIN || '2', 10);\nconst poolMax = parseInt(process.env.DB_POOL_MAX || '20', 10);\n\nexport const databaseConfig = {\n // Active connection dialect: 'mysql' | 'postgres' | 'sqlite' | 'memory'\n default: (process.env.DB_CONNECTION || 'mysql').toLowerCase(),\n\n // Global connection pool configuration\n pool: {\n min: poolMin,\n max: poolMax,\n },\n\n connections: {\n // MySQL / MariaDB (Driver: mysql2)\n mysql: {\n client: 'mysql2',\n connection: {\n host: process.env.DB_HOST || '127.0.0.1',\n port: parseInt(process.env.DB_PORT || '3306', 10),\n user: process.env.DB_USER || 'root',\n password: process.env.DB_PASSWORD !== undefined ? process.env.DB_PASSWORD : (process.env.DB_PASS || ''),\n database: process.env.DB_DATABASE || process.env.DB_NAME || 'aerojs_app',\n },\n pool: { min: poolMin, max: poolMax },\n },\n\n // PostgreSQL (Driver: pg)\n postgres: {\n client: 'pg',\n connection: {\n host: process.env.DB_HOST || '127.0.0.1',\n port: parseInt(process.env.DB_PORT || '5432', 10),\n user: process.env.DB_USER || 'postgres',\n password: process.env.DB_PASSWORD !== undefined ? process.env.DB_PASSWORD : (process.env.DB_PASS || ''),\n database: process.env.DB_DATABASE || process.env.DB_NAME || 'aerojs_app',\n ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false,\n },\n pool: { min: poolMin, max: poolMax },\n },\n\n // SQLite (Driver: better-sqlite3 or sqlite3)\n sqlite: {\n client: 'better-sqlite3',\n connection: {\n filename: process.env.DB_FILENAME || process.env.DB_DATABASE || 'storage/database.sqlite',\n },\n useNullAsDefault: true,\n },\n\n // In-Memory Database (Zero-dependency, ideal for fast unit tests)\n memory: {\n client: 'memory',\n },\n },\n};\n\n";
|
|
17
|
+
//# sourceMappingURL=fullstack-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fullstack-templates.js","sourceRoot":"","sources":["../../../src/cli/templates/fullstack-templates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAW,8tBAA8tB,CAAC;AAEtwB,MAAM,CAAC,MAAM,eAAe,GAAW,+tjCAA+tjC,CAAC;AAEvwjC,MAAM,CAAC,MAAM,mBAAmB,GAAW,uvZAAuvZ,CAAC;AAEnyZ,MAAM,CAAC,MAAM,kBAAkB,GAAW,ywZAAywZ,CAAC;AAEpzZ,MAAM,CAAC,MAAM,oBAAoB,GAAW,8oLAA8oL,CAAC;AAE3rL,MAAM,CAAC,MAAM,kBAAkB,GAAW,q4JAAq4J,CAAC;AAEh7J,MAAM,CAAC,MAAM,mBAAmB,GAAW,mhBAAmhB,CAAC;AAE/jB,MAAM,CAAC,MAAM,aAAa,GAAW,siGAAsiG,CAAC;AAE5kG,MAAM,CAAC,MAAM,gBAAgB,GAAW,gm3BAAgm3B,CAAC;AAEzo3B,MAAM,CAAC,MAAM,gBAAgB,GAAW,s4BAAs4B,CAAC;AAE/6B,MAAM,CAAC,MAAM,eAAe,GAAW,w1CAAw1C,CAAC;AAEh4C,MAAM,CAAC,MAAM,qBAAqB,GAAW,4lEAA4lE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file config.ts
|
|
3
|
+
* @description Typed configuration store supporting nested dot-notation access.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Config {
|
|
6
|
+
private values;
|
|
7
|
+
constructor(initialValues?: Record<string, unknown>);
|
|
8
|
+
/**
|
|
9
|
+
* Retrieves a configuration property using dot-notation (e.g., 'database.connection.host').
|
|
10
|
+
*/
|
|
11
|
+
get<T = unknown>(path: string, defaultValue?: T): T;
|
|
12
|
+
/**
|
|
13
|
+
* Sets a configuration value using dot-notation.
|
|
14
|
+
*/
|
|
15
|
+
set(path: string, value: unknown): void;
|
|
16
|
+
/**
|
|
17
|
+
* Checks whether a configuration key exists.
|
|
18
|
+
*/
|
|
19
|
+
has(path: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Returns a copy of the entire configuration tree.
|
|
22
|
+
*/
|
|
23
|
+
all(): Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAA0B;gBAE5B,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;IAIvD;;OAEG;IACI,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC;IAc1D;;OAEG;IACI,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAgB9C;;OAEG;IACI,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC;;OAEG;IACI,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAGtC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file config.ts
|
|
3
|
+
* @description Typed configuration store supporting nested dot-notation access.
|
|
4
|
+
*/
|
|
5
|
+
export class Config {
|
|
6
|
+
values;
|
|
7
|
+
constructor(initialValues = {}) {
|
|
8
|
+
this.values = initialValues;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves a configuration property using dot-notation (e.g., 'database.connection.host').
|
|
12
|
+
*/
|
|
13
|
+
get(path, defaultValue) {
|
|
14
|
+
const keys = path.split('.');
|
|
15
|
+
let current = this.values;
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
if (current === null || typeof current !== 'object' || !(key in current)) {
|
|
18
|
+
return defaultValue;
|
|
19
|
+
}
|
|
20
|
+
current = current[key];
|
|
21
|
+
}
|
|
22
|
+
return (current !== undefined ? current : defaultValue);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sets a configuration value using dot-notation.
|
|
26
|
+
*/
|
|
27
|
+
set(path, value) {
|
|
28
|
+
const keys = path.split('.');
|
|
29
|
+
let current = this.values;
|
|
30
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
31
|
+
const key = keys[i];
|
|
32
|
+
if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
|
|
33
|
+
current[key] = {};
|
|
34
|
+
}
|
|
35
|
+
current = current[key];
|
|
36
|
+
}
|
|
37
|
+
const lastKey = keys[keys.length - 1];
|
|
38
|
+
current[lastKey] = value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Checks whether a configuration key exists.
|
|
42
|
+
*/
|
|
43
|
+
has(path) {
|
|
44
|
+
return this.get(path) !== undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns a copy of the entire configuration tree.
|
|
48
|
+
*/
|
|
49
|
+
all() {
|
|
50
|
+
return { ...this.values };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,MAAM;IACT,MAAM,CAA0B;IAExC,YAAY,gBAAyC,EAAE;QACrD,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC;IAC9B,CAAC;IAED;;OAEG;IACI,GAAG,CAAc,IAAY,EAAE,YAAgB;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,OAAO,GAAQ,IAAI,CAAC,MAAM,CAAC;QAE/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE,CAAC;gBACzE,OAAO,YAAiB,CAAC;YAC3B,CAAC;YACD,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAED,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAM,CAAC;IAC/D,CAAC;IAED;;OAEG;IACI,GAAG,CAAC,IAAY,EAAE,KAAc;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,OAAO,GAAQ,IAAI,CAAC,MAAM,CAAC;QAE/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;YACrB,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBACnF,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YACpB,CAAC;YACD,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;QACvC,OAAO,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;IAC3B,CAAC;IAED;;OAEG;IACI,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC;IACtC,CAAC;IAED;;OAEG;IACI,GAAG;QACR,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file env-schema.ts
|
|
3
|
+
* @description Strongly-typed Environment Schema Validation for AeroJS.
|
|
4
|
+
* Validates process.env on boot and throws EnvValidationError on missing or invalid configurations.
|
|
5
|
+
*/
|
|
6
|
+
export declare class EnvValidationError extends Error {
|
|
7
|
+
errors: string[];
|
|
8
|
+
constructor(errors: string[]);
|
|
9
|
+
}
|
|
10
|
+
export declare abstract class EnvField<T> {
|
|
11
|
+
protected isRequired: boolean;
|
|
12
|
+
protected defaultValue?: T;
|
|
13
|
+
required(): this;
|
|
14
|
+
optional(): this;
|
|
15
|
+
default(val: T): this;
|
|
16
|
+
abstract parse(key: string, val: string | undefined): T;
|
|
17
|
+
}
|
|
18
|
+
export declare class EnvStringField extends EnvField<string> {
|
|
19
|
+
private minLen?;
|
|
20
|
+
private maxLen?;
|
|
21
|
+
private exactLen?;
|
|
22
|
+
min(len: number): this;
|
|
23
|
+
max(len: number): this;
|
|
24
|
+
length(len: number): this;
|
|
25
|
+
parse(key: string, val: string | undefined): string;
|
|
26
|
+
}
|
|
27
|
+
export declare class EnvNumberField extends EnvField<number> {
|
|
28
|
+
private minVal?;
|
|
29
|
+
private maxVal?;
|
|
30
|
+
min(val: number): this;
|
|
31
|
+
max(val: number): this;
|
|
32
|
+
parse(key: string, val: string | undefined): number;
|
|
33
|
+
}
|
|
34
|
+
export declare class EnvBooleanField extends EnvField<boolean> {
|
|
35
|
+
parse(key: string, val: string | undefined): boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare class EnvEnumField<T extends string> extends EnvField<T> {
|
|
38
|
+
private allowed;
|
|
39
|
+
constructor(allowed: T[]);
|
|
40
|
+
parse(key: string, val: string | undefined): T;
|
|
41
|
+
}
|
|
42
|
+
export declare class EnvUrlField extends EnvField<string> {
|
|
43
|
+
parse(key: string, val: string | undefined): string;
|
|
44
|
+
}
|
|
45
|
+
export declare class EnvEmailField extends EnvField<string> {
|
|
46
|
+
parse(key: string, val: string | undefined): string;
|
|
47
|
+
}
|
|
48
|
+
export declare class TypedEnv {
|
|
49
|
+
static string(): EnvStringField;
|
|
50
|
+
static number(): EnvNumberField;
|
|
51
|
+
static boolean(): EnvBooleanField;
|
|
52
|
+
static enum<T extends string>(allowed: T[]): EnvEnumField<T>;
|
|
53
|
+
static url(): EnvUrlField;
|
|
54
|
+
static email(): EnvEmailField;
|
|
55
|
+
/**
|
|
56
|
+
* Validates environment variables according to the provided schema definition.
|
|
57
|
+
*/
|
|
58
|
+
static schema<T extends Record<string, EnvField<any>>>(definition: T, source?: Record<string, string | undefined>): {
|
|
59
|
+
[K in keyof T]: ReturnType<T[K]['parse']>;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=env-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-schema.d.ts","sourceRoot":"","sources":["../../src/config/env-schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,qBAAa,kBAAmB,SAAQ,KAAK;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE;CAK7B;AAED,8BAAsB,QAAQ,CAAC,CAAC;IAC9B,SAAS,CAAC,UAAU,UAAS;IAC7B,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAEpB,QAAQ,IAAI,IAAI;IAKhB,QAAQ,IAAI,IAAI;IAKhB,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI;aAMZ,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC;CAC/D;AAED,qBAAa,cAAe,SAAQ,QAAQ,CAAC,MAAM,CAAC;IAClD,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,CAAS;IAEnB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKtB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKzB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAsB3D;AAED,qBAAa,cAAe,SAAQ,QAAQ,CAAC,MAAM,CAAC;IAClD,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,CAAS;IAEjB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKtB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAuB3D;AAED,qBAAa,eAAgB,SAAQ,QAAQ,CAAC,OAAO,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO;CAc5D;AAED,qBAAa,YAAY,CAAC,CAAC,SAAS,MAAM,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC7D,OAAO,CAAC,OAAO,CAAM;gBAET,OAAO,EAAE,CAAC,EAAE;IAKjB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC;CAgBtD;AAED,qBAAa,WAAY,SAAQ,QAAQ,CAAC,MAAM,CAAC;IACxC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAe3D;AAED,qBAAa,aAAc,SAAQ,QAAQ,CAAC,MAAM,CAAC;IAC1C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAe3D;AAED,qBAAa,QAAQ;WACL,MAAM,IAAI,cAAc;WAIxB,MAAM,IAAI,cAAc;WAIxB,OAAO,IAAI,eAAe;WAI1B,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC;WAIrD,GAAG,IAAI,WAAW;WAIlB,KAAK,IAAI,aAAa;IAIpC;;OAEG;WACW,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC1D,UAAU,EAAE,CAAC,EACb,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACvD;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;KAAE;CAkBjD"}
|