@orchestr-sh/orchestr 1.11.0 → 1.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/CODE_OF_CONDUCT.md +43 -0
- package/CONTRIBUTING.md +274 -0
- package/dist/Cache/CacheManager.cjs +1 -1
- package/dist/Cache/CacheManager.mjs +1 -1
- package/dist/Cache/CacheManager.mjs.map +1 -1
- package/dist/Cache/Events/CacheFlushed.cjs +1 -1
- package/dist/Cache/Events/CacheFlushed.mjs +1 -1
- package/dist/Cache/Events/CacheFlushed.mjs.map +1 -1
- package/dist/Cache/Events/CacheHit.cjs +1 -1
- package/dist/Cache/Events/CacheHit.mjs +1 -1
- package/dist/Cache/Events/CacheHit.mjs.map +1 -1
- package/dist/Cache/Events/CacheMissed.cjs +1 -1
- package/dist/Cache/Events/CacheMissed.mjs +1 -1
- package/dist/Cache/Events/CacheMissed.mjs.map +1 -1
- package/dist/Cache/Events/KeyForgotten.cjs +1 -1
- package/dist/Cache/Events/KeyForgotten.mjs +1 -1
- package/dist/Cache/Events/KeyForgotten.mjs.map +1 -1
- package/dist/Cache/Events/KeyWritten.cjs +1 -1
- package/dist/Cache/Events/KeyWritten.mjs +1 -1
- package/dist/Cache/Events/KeyWritten.mjs.map +1 -1
- package/dist/Cache/Locks/CacheLock.cjs +1 -1
- package/dist/Cache/Locks/CacheLock.mjs +1 -1
- package/dist/Cache/Locks/CacheLock.mjs.map +1 -1
- package/dist/Cache/Locks/Lock.cjs +1 -1
- package/dist/Cache/Locks/Lock.mjs +1 -1
- package/dist/Cache/Locks/Lock.mjs.map +1 -1
- package/dist/Cache/Repository.cjs +1 -1
- package/dist/Cache/Repository.mjs +1 -1
- package/dist/Cache/Repository.mjs.map +1 -1
- package/dist/Cache/Stores/ArrayStore.cjs +1 -1
- package/dist/Cache/Stores/ArrayStore.mjs +1 -1
- package/dist/Cache/Stores/ArrayStore.mjs.map +1 -1
- package/dist/Cache/Stores/DatabaseStore.cjs +1 -1
- package/dist/Cache/Stores/DatabaseStore.mjs +1 -1
- package/dist/Cache/Stores/DatabaseStore.mjs.map +1 -1
- package/dist/Cache/Stores/FileStore.cjs +1 -1
- package/dist/Cache/Stores/FileStore.mjs +1 -1
- package/dist/Cache/Stores/FileStore.mjs.map +1 -1
- package/dist/Cache/Tags/TagSet.cjs +1 -1
- package/dist/Cache/Tags/TagSet.mjs +1 -1
- package/dist/Cache/Tags/TagSet.mjs.map +1 -1
- package/dist/Cache/Tags/TaggedCache.cjs +1 -1
- package/dist/Cache/Tags/TaggedCache.mjs +1 -1
- package/dist/Cache/Tags/TaggedCache.mjs.map +1 -1
- package/dist/Console/Commands/CacheClearCommand.cjs +1 -1
- package/dist/Console/Commands/CacheClearCommand.mjs +1 -1
- package/dist/Console/Commands/CacheClearCommand.mjs.map +1 -1
- package/dist/Console/Commands/CacheForgetCommand.cjs +1 -1
- package/dist/Console/Commands/CacheForgetCommand.mjs +1 -1
- package/dist/Console/Commands/CacheForgetCommand.mjs.map +1 -1
- package/dist/Console/Commands/CacheTableCommand.cjs +1 -1
- package/dist/Console/Commands/CacheTableCommand.mjs +1 -1
- package/dist/Console/Commands/CacheTableCommand.mjs.map +1 -1
- package/dist/Console/Commands/DeployCommand.mjs.map +1 -1
- package/dist/Console/Commands/DeployEnvCommand.mjs.map +1 -1
- package/dist/Console/Commands/DeployInitCommand.mjs.map +1 -1
- package/dist/Console/Commands/DeployProvisionCommand.mjs.map +1 -1
- package/dist/Console/Commands/DeployRollbackCommand.mjs.map +1 -1
- package/dist/Console/Commands/DeployServerCommand.mjs.map +1 -1
- package/dist/Console/Commands/DeployStatusCommand.mjs.map +1 -1
- package/dist/Console/Commands/EventCacheCommand.cjs +1 -1
- package/dist/Console/Commands/EventCacheCommand.mjs +1 -1
- package/dist/Console/Commands/EventCacheCommand.mjs.map +1 -1
- package/dist/Console/Commands/EventClearCommand.cjs +1 -1
- package/dist/Console/Commands/EventClearCommand.mjs +1 -1
- package/dist/Console/Commands/EventClearCommand.mjs.map +1 -1
- package/dist/Console/Commands/EventListCommand.cjs +1 -1
- package/dist/Console/Commands/EventListCommand.mjs +1 -1
- package/dist/Console/Commands/EventListCommand.mjs.map +1 -1
- package/dist/Console/Commands/MakeControllerCommand.cjs +1 -1
- package/dist/Console/Commands/MakeControllerCommand.mjs +1 -1
- package/dist/Console/Commands/MakeControllerCommand.mjs.map +1 -1
- package/dist/Console/Commands/MakeEventCommand.cjs +1 -1
- package/dist/Console/Commands/MakeEventCommand.mjs +1 -1
- package/dist/Console/Commands/MakeEventCommand.mjs.map +1 -1
- package/dist/Console/Commands/MakeJobCommand.cjs +1 -1
- package/dist/Console/Commands/MakeJobCommand.mjs +1 -1
- package/dist/Console/Commands/MakeJobCommand.mjs.map +1 -1
- package/dist/Console/Commands/MakeListenerCommand.cjs +1 -1
- package/dist/Console/Commands/MakeListenerCommand.mjs +1 -1
- package/dist/Console/Commands/MakeListenerCommand.mjs.map +1 -1
- package/dist/Console/Commands/MakeMigrationCommand.cjs +1 -1
- package/dist/Console/Commands/MakeMigrationCommand.mjs +1 -1
- package/dist/Console/Commands/MakeMigrationCommand.mjs.map +1 -1
- package/dist/Console/Commands/MakeSeederCommand.cjs +1 -1
- package/dist/Console/Commands/MakeSeederCommand.mjs +1 -1
- package/dist/Console/Commands/MakeSeederCommand.mjs.map +1 -1
- package/dist/Console/Commands/MakeViewCommand.cjs +1 -1
- package/dist/Console/Commands/MakeViewCommand.mjs +1 -1
- package/dist/Console/Commands/MakeViewCommand.mjs.map +1 -1
- package/dist/Console/Commands/MigrateCommand.cjs +1 -1
- package/dist/Console/Commands/MigrateCommand.mjs +1 -1
- package/dist/Console/Commands/MigrateCommand.mjs.map +1 -1
- package/dist/Console/Commands/MigrateFreshCommand.cjs +1 -1
- package/dist/Console/Commands/MigrateFreshCommand.mjs +1 -1
- package/dist/Console/Commands/MigrateFreshCommand.mjs.map +1 -1
- package/dist/Console/Commands/MigrateRefreshCommand.cjs +1 -1
- package/dist/Console/Commands/MigrateRefreshCommand.mjs +1 -1
- package/dist/Console/Commands/MigrateRefreshCommand.mjs.map +1 -1
- package/dist/Console/Commands/MigrateResetCommand.cjs +1 -1
- package/dist/Console/Commands/MigrateResetCommand.mjs +1 -1
- package/dist/Console/Commands/MigrateResetCommand.mjs.map +1 -1
- package/dist/Console/Commands/MigrateRollbackCommand.cjs +1 -1
- package/dist/Console/Commands/MigrateRollbackCommand.mjs +1 -1
- package/dist/Console/Commands/MigrateRollbackCommand.mjs.map +1 -1
- package/dist/Console/Commands/MigrateStatusCommand.cjs +1 -1
- package/dist/Console/Commands/MigrateStatusCommand.mjs +1 -1
- package/dist/Console/Commands/MigrateStatusCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueBatchesTableCommand.cjs +1 -1
- package/dist/Console/Commands/QueueBatchesTableCommand.mjs +1 -1
- package/dist/Console/Commands/QueueBatchesTableCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueClearCommand.cjs +1 -1
- package/dist/Console/Commands/QueueClearCommand.mjs +1 -1
- package/dist/Console/Commands/QueueClearCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueFailedCommand.cjs +1 -1
- package/dist/Console/Commands/QueueFailedCommand.mjs +1 -1
- package/dist/Console/Commands/QueueFailedCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueFailedTableCommand.cjs +1 -1
- package/dist/Console/Commands/QueueFailedTableCommand.mjs +1 -1
- package/dist/Console/Commands/QueueFailedTableCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueFlushCommand.cjs +1 -1
- package/dist/Console/Commands/QueueFlushCommand.mjs +1 -1
- package/dist/Console/Commands/QueueFlushCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueForgetCommand.cjs +1 -1
- package/dist/Console/Commands/QueueForgetCommand.mjs +1 -1
- package/dist/Console/Commands/QueueForgetCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueMonitorCommand.cjs +1 -1
- package/dist/Console/Commands/QueueMonitorCommand.mjs +1 -1
- package/dist/Console/Commands/QueueMonitorCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueuePruneBatchesCommand.cjs +1 -1
- package/dist/Console/Commands/QueuePruneBatchesCommand.mjs +1 -1
- package/dist/Console/Commands/QueuePruneBatchesCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueuePruneFailedCommand.cjs +1 -1
- package/dist/Console/Commands/QueuePruneFailedCommand.mjs +1 -1
- package/dist/Console/Commands/QueuePruneFailedCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueRestartCommand.cjs +1 -1
- package/dist/Console/Commands/QueueRestartCommand.mjs +1 -1
- package/dist/Console/Commands/QueueRestartCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueRetryCommand.cjs +1 -1
- package/dist/Console/Commands/QueueRetryCommand.mjs +1 -1
- package/dist/Console/Commands/QueueRetryCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueTableCommand.cjs +1 -1
- package/dist/Console/Commands/QueueTableCommand.mjs +1 -1
- package/dist/Console/Commands/QueueTableCommand.mjs.map +1 -1
- package/dist/Console/Commands/QueueWorkCommand.cjs +1 -1
- package/dist/Console/Commands/QueueWorkCommand.mjs +1 -1
- package/dist/Console/Commands/QueueWorkCommand.mjs.map +1 -1
- package/dist/Console/Commands/SeedCommand.cjs +1 -1
- package/dist/Console/Commands/SeedCommand.mjs +1 -1
- package/dist/Console/Commands/SeedCommand.mjs.map +1 -1
- package/dist/Console/ConsoleKernel.cjs +1 -1
- package/dist/Console/ConsoleKernel.mjs +1 -1
- package/dist/Console/ConsoleKernel.mjs.map +1 -1
- package/dist/Console/orchestr.cjs +2 -0
- package/dist/Console/orchestr.d.cts +1 -0
- package/dist/Console/orchestr.d.mts +1 -0
- package/dist/Console/orchestr.mjs +3 -0
- package/dist/Console/orchestr.mjs.map +1 -0
- package/dist/Container/Container.mjs.map +1 -1
- package/dist/Database/Adapters/DrizzleAdapter.cjs +1 -1
- package/dist/Database/Adapters/DrizzleAdapter.mjs +1 -1
- package/dist/Database/Adapters/DrizzleAdapter.mjs.map +1 -1
- package/dist/Database/Connection.cjs +1 -1
- package/dist/Database/Connection.mjs +1 -1
- package/dist/Database/Connection.mjs.map +1 -1
- package/dist/Database/DatabaseManager.cjs +1 -1
- package/dist/Database/DatabaseManager.mjs +1 -1
- package/dist/Database/DatabaseManager.mjs.map +1 -1
- package/dist/Database/Ensemble/Ensemble.mjs.map +1 -1
- package/dist/Database/Ensemble/EnsembleBuilder.mjs.map +1 -1
- package/dist/Database/Ensemble/EnsembleCollection.cjs +1 -1
- package/dist/Database/Ensemble/EnsembleCollection.mjs +1 -1
- package/dist/Database/Ensemble/EnsembleCollection.mjs.map +1 -1
- package/dist/Database/Ensemble/Relations/BelongsTo.mjs.map +1 -1
- package/dist/Database/Ensemble/Relations/BelongsToMany.cjs +1 -1
- package/dist/Database/Ensemble/Relations/BelongsToMany.mjs +1 -1
- package/dist/Database/Ensemble/Relations/BelongsToMany.mjs.map +1 -1
- package/dist/Database/Ensemble/Relations/HasMany.mjs.map +1 -1
- package/dist/Database/Ensemble/Relations/MorphTo.mjs.map +1 -1
- package/dist/Database/Ensemble/Relations/MorphToMany.mjs.map +1 -1
- package/dist/Database/Migrations/Blueprint.cjs +1 -1
- package/dist/Database/Migrations/Blueprint.mjs +1 -1
- package/dist/Database/Migrations/Blueprint.mjs.map +1 -1
- package/dist/Database/Migrations/MigrationCreator.cjs +1 -1
- package/dist/Database/Migrations/MigrationCreator.mjs +1 -1
- package/dist/Database/Migrations/MigrationCreator.mjs.map +1 -1
- package/dist/Database/Migrations/MigrationRepository.cjs +1 -1
- package/dist/Database/Migrations/MigrationRepository.mjs +1 -1
- package/dist/Database/Migrations/MigrationRepository.mjs.map +1 -1
- package/dist/Database/Migrations/Migrator.cjs +1 -1
- package/dist/Database/Migrations/Migrator.mjs +1 -1
- package/dist/Database/Migrations/Migrator.mjs.map +1 -1
- package/dist/Database/Migrations/SchemaBuilder.cjs +1 -1
- package/dist/Database/Migrations/SchemaBuilder.mjs +1 -1
- package/dist/Database/Migrations/SchemaBuilder.mjs.map +1 -1
- package/dist/Database/Query/Builder.cjs +1 -1
- package/dist/Database/Query/Builder.mjs +1 -1
- package/dist/Database/Query/Builder.mjs.map +1 -1
- package/dist/Database/Query/Expression.cjs +1 -1
- package/dist/Database/Query/Expression.mjs +1 -1
- package/dist/Database/Query/Expression.mjs.map +1 -1
- package/dist/Database/Seeders/SeederRunner.cjs +1 -1
- package/dist/Database/Seeders/SeederRunner.mjs +1 -1
- package/dist/Database/Seeders/SeederRunner.mjs.map +1 -1
- package/dist/Deploy/Deployer.cjs +1 -1
- package/dist/Deploy/Deployer.mjs +1 -1
- package/dist/Deploy/Deployer.mjs.map +1 -1
- package/dist/Deploy/ProjectConfig.mjs.map +1 -1
- package/dist/Deploy/Provisioner.cjs +1 -1
- package/dist/Deploy/Provisioner.mjs +1 -1
- package/dist/Deploy/Provisioner.mjs.map +1 -1
- package/dist/Deploy/SSHConnection.mjs.map +1 -1
- package/dist/Deploy/SymphonyClient.cjs +1 -1
- package/dist/Deploy/SymphonyClient.mjs +1 -1
- package/dist/Deploy/SymphonyClient.mjs.map +1 -1
- package/dist/Deploy/TarBuilder.mjs.map +1 -1
- package/dist/Deploy/prompt.mjs.map +1 -1
- package/dist/Events/Dispatcher.cjs +1 -1
- package/dist/Events/Dispatcher.d.cts +1 -1
- package/dist/Events/Dispatcher.d.mts +1 -1
- package/dist/Events/Dispatcher.mjs +1 -1
- package/dist/Events/Dispatcher.mjs.map +1 -1
- package/dist/Events/EventServiceProvider.cjs +1 -1
- package/dist/Events/EventServiceProvider.d.cts +1 -1
- package/dist/Events/EventServiceProvider.d.mts +1 -1
- package/dist/Events/EventServiceProvider.mjs.map +1 -1
- package/dist/Events/index.cjs +1 -0
- package/dist/Events/index.d.cts +7 -0
- package/dist/Events/index.d.mts +7 -0
- package/dist/Events/index.mjs +1 -0
- package/dist/Facades/Bus.mjs.map +1 -1
- package/dist/Facades/Cache.mjs.map +1 -1
- package/dist/Facades/Config.mjs.map +1 -1
- package/dist/Facades/DB.mjs.map +1 -1
- package/dist/Facades/Event.d.cts +1 -1
- package/dist/Facades/Event.d.mts +1 -1
- package/dist/Facades/Event.mjs.map +1 -1
- package/dist/Facades/Queue.mjs.map +1 -1
- package/dist/Facades/Route.d.cts +1 -1
- package/dist/Facades/Route.d.mts +1 -1
- package/dist/Facades/Route.mjs.map +1 -1
- package/dist/Facades/View.mjs.map +1 -1
- package/dist/Facades/index.cjs +1 -0
- package/dist/Facades/index.d.cts +9 -0
- package/dist/Facades/index.d.mts +9 -0
- package/dist/Facades/index.mjs +1 -0
- package/dist/Foundation/Application.mjs.map +1 -1
- package/dist/Foundation/Config/ConfigServiceProvider.d.cts +1 -1
- package/dist/Foundation/Config/ConfigServiceProvider.d.mts +1 -1
- package/dist/Foundation/Http/FormRequest.d.cts +1 -1
- package/dist/Foundation/Http/FormRequest.d.mts +1 -1
- package/dist/Foundation/Http/FormRequest.mjs.map +1 -1
- package/dist/Foundation/Http/Rules/AnyOfRule.mjs.map +1 -1
- package/dist/Foundation/Http/Rules/ImageFileRule.mjs.map +1 -1
- package/dist/Foundation/Http/Validator.mjs.map +1 -1
- package/dist/Queue/Batching/Batch.cjs +1 -1
- package/dist/Queue/Batching/Batch.mjs +1 -1
- package/dist/Queue/Batching/Batch.mjs.map +1 -1
- package/dist/Queue/Batching/PendingBatch.cjs +1 -1
- package/dist/Queue/Batching/PendingBatch.mjs +1 -1
- package/dist/Queue/Batching/PendingBatch.mjs.map +1 -1
- package/dist/Queue/Concerns/Dispatchable.mjs.map +1 -1
- package/dist/Queue/Drivers/DatabaseDriver.cjs +1 -1
- package/dist/Queue/Drivers/DatabaseDriver.mjs +1 -1
- package/dist/Queue/Drivers/DatabaseDriver.mjs.map +1 -1
- package/dist/Queue/Drivers/NullDriver.cjs +1 -1
- package/dist/Queue/Drivers/NullDriver.mjs +1 -1
- package/dist/Queue/Drivers/NullDriver.mjs.map +1 -1
- package/dist/Queue/Drivers/SyncDriver.cjs +1 -1
- package/dist/Queue/Drivers/SyncDriver.mjs +1 -1
- package/dist/Queue/Drivers/SyncDriver.mjs.map +1 -1
- package/dist/Queue/Events/JobExceptionOccurred.cjs +1 -1
- package/dist/Queue/Events/JobExceptionOccurred.mjs +1 -1
- package/dist/Queue/Events/JobExceptionOccurred.mjs.map +1 -1
- package/dist/Queue/Events/JobFailed.cjs +1 -1
- package/dist/Queue/Events/JobFailed.mjs +1 -1
- package/dist/Queue/Events/JobFailed.mjs.map +1 -1
- package/dist/Queue/Events/JobProcessed.cjs +1 -1
- package/dist/Queue/Events/JobProcessed.mjs +1 -1
- package/dist/Queue/Events/JobProcessed.mjs.map +1 -1
- package/dist/Queue/Events/JobProcessing.cjs +1 -1
- package/dist/Queue/Events/JobProcessing.mjs +1 -1
- package/dist/Queue/Events/JobProcessing.mjs.map +1 -1
- package/dist/Queue/Events/JobQueued.cjs +1 -1
- package/dist/Queue/Events/JobQueued.mjs +1 -1
- package/dist/Queue/Events/JobQueued.mjs.map +1 -1
- package/dist/Queue/Events/JobRetryRequested.cjs +1 -1
- package/dist/Queue/Events/JobRetryRequested.mjs +1 -1
- package/dist/Queue/Events/JobRetryRequested.mjs.map +1 -1
- package/dist/Queue/Events/WorkerStopping.cjs +1 -1
- package/dist/Queue/Events/WorkerStopping.mjs +1 -1
- package/dist/Queue/Events/WorkerStopping.mjs.map +1 -1
- package/dist/Queue/Failed/DatabaseFailedJobProvider.cjs +1 -1
- package/dist/Queue/Failed/DatabaseFailedJobProvider.mjs +1 -1
- package/dist/Queue/Failed/DatabaseFailedJobProvider.mjs.map +1 -1
- package/dist/Queue/Middleware/RateLimited.cjs +1 -1
- package/dist/Queue/Middleware/RateLimited.mjs +1 -1
- package/dist/Queue/Middleware/RateLimited.mjs.map +1 -1
- package/dist/Queue/Middleware/ThrottlesExceptions.cjs +1 -1
- package/dist/Queue/Middleware/ThrottlesExceptions.mjs +1 -1
- package/dist/Queue/Middleware/ThrottlesExceptions.mjs.map +1 -1
- package/dist/Queue/Middleware/WithoutOverlapping.cjs +1 -1
- package/dist/Queue/Middleware/WithoutOverlapping.mjs +1 -1
- package/dist/Queue/Middleware/WithoutOverlapping.mjs.map +1 -1
- package/dist/Queue/PendingChain.cjs +1 -1
- package/dist/Queue/PendingChain.mjs +1 -1
- package/dist/Queue/PendingChain.mjs.map +1 -1
- package/dist/Queue/PendingDispatch.cjs +1 -1
- package/dist/Queue/PendingDispatch.mjs +1 -1
- package/dist/Queue/PendingDispatch.mjs.map +1 -1
- package/dist/Queue/QueueManager.cjs +1 -1
- package/dist/Queue/QueueManager.mjs +1 -1
- package/dist/Queue/QueueManager.mjs.map +1 -1
- package/dist/Queue/QueueServiceProvider.mjs.map +1 -1
- package/dist/Queue/Workers/Worker.cjs +1 -1
- package/dist/Queue/Workers/Worker.mjs +1 -1
- package/dist/Queue/Workers/Worker.mjs.map +1 -1
- package/dist/Routing/Request.mjs.map +1 -1
- package/dist/Routing/Response.mjs.map +1 -1
- package/dist/Routing/Route.d.cts +1 -1
- package/dist/Routing/Route.d.mts +1 -1
- package/dist/Routing/Router.d.cts +1 -1
- package/dist/Routing/Router.d.mts +1 -1
- package/dist/Support/EventDiscovery.cjs +1 -1
- package/dist/Support/EventDiscovery.mjs +1 -1
- package/dist/Support/EventDiscovery.mjs.map +1 -1
- package/dist/Support/Testing/Fakes/EventFake.cjs +1 -1
- package/dist/Support/Testing/Fakes/EventFake.d.cts +1 -1
- package/dist/Support/Testing/Fakes/EventFake.d.mts +1 -1
- package/dist/Support/Testing/Fakes/EventFake.mjs +1 -1
- package/dist/Support/Testing/Fakes/EventFake.mjs.map +1 -1
- package/dist/Support/helpers.mjs.map +1 -1
- package/dist/View/Engines/TemplateEngine.mjs.map +1 -1
- package/dist/index.d.cts +19 -19
- package/dist/index.d.mts +19 -19
- package/docs/events-typescript-usage.md +126 -0
- package/docs/future-improvements.md +49 -0
- package/docs/validation.md +201 -0
- package/package.json +65 -31
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Worker.mjs","names":[],"sources":["../../../src/Queue/Workers/Worker.ts"],"sourcesContent":["/**\n * Worker\n *\n * The queue worker that processes jobs from the queue.\n * Handles the daemon loop, job processing, retries, failures,\n * memory management, and graceful shutdown.\n *\n * Mirrors Laravel's Illuminate\\Queue\\Worker.\n */\n\nimport type { Application } from '@/Foundation/Application';\nimport type { QueueDriver, QueueDriverJob } from '@/Queue/Contracts/QueueDriver';\nimport type { FailedJobProvider } from '@/Queue/Failed/FailedJobProvider';\nimport type { Job } from '@/Queue/Job';\nimport type { JobMiddleware } from '@/Queue/Middleware/JobMiddleware';\nimport type { QueueManager } from '@/Queue/QueueManager';\nimport { JobPayload, type JobPayloadData } from '@/Queue/JobPayload';\nimport type { WorkerOptions } from './WorkerOptions';\nimport { DEFAULT_WORKER_OPTIONS } from './WorkerOptions';\n\nexport class Worker {\n protected shouldQuit: boolean = false;\n protected paused: boolean = false;\n protected jobsProcessed: number = 0;\n protected startTime: number = 0;\n\n constructor(\n protected manager: QueueManager,\n protected app: Application\n ) {}\n\n /**\n * Run the worker daemon loop\n */\n async daemon(connectionName: string, queues: string, options: WorkerOptions = {}): Promise<void> {\n const opts = { ...DEFAULT_WORKER_OPTIONS, ...options };\n this.startTime = Date.now();\n\n this.listenForSignals();\n\n while (!this.shouldQuit) {\n // Fire looping callbacks\n this.manager.fireLoopingCallbacks();\n\n // Check if paused\n if (this.paused) {\n await this.sleep(opts.sleep);\n continue;\n }\n\n // Process the next job from the queue(s)\n const processed = await this.runNextJob(connectionName, queues, opts);\n\n if (!processed) {\n // No job was available\n if (opts.stopWhenEmpty) {\n this.stop();\n break;\n }\n await this.sleep(opts.sleep);\n } else {\n // Rest between jobs if configured\n if (opts.rest > 0) {\n await this.sleep(opts.rest / 1000);\n }\n }\n\n // Check stop conditions\n if (this.shouldStop(opts)) {\n this.stop();\n }\n }\n }\n\n /**\n * Process a single job (--once mode)\n */\n async runOnce(connectionName: string, queues: string, options: WorkerOptions = {}): Promise<boolean> {\n const opts = { ...DEFAULT_WORKER_OPTIONS, ...options };\n return this.runNextJob(connectionName, queues, opts);\n }\n\n /**\n * Get the next job and process it\n */\n async runNextJob(connectionName: string, queues: string, options: Required<WorkerOptions>): Promise<boolean> {\n const driver = this.manager.connection(connectionName);\n const queueList = queues.split(',').map((q) => q.trim());\n\n // Try each queue in priority order\n for (const queue of queueList) {\n const rawJob = await driver.pop(queue);\n\n if (rawJob) {\n await this.processJob(connectionName, driver, rawJob, options);\n this.jobsProcessed++;\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Process a single job\n */\n protected async processJob(\n connectionName: string,\n driver: QueueDriver,\n rawJob: QueueDriverJob,\n options: Required<WorkerOptions>\n ): Promise<void> {\n let payload: JobPayloadData;\n\n try {\n payload = JobPayload.deserialize(rawJob.payload);\n } catch (error) {\n // Invalid payload, delete the job\n console.error(`[Queue] Invalid job payload, deleting job ${rawJob.id}`);\n await driver.delete(rawJob.id);\n return;\n }\n\n // Resolve the job class from the registry\n const jobClass = this.manager.getJobClass(payload.job);\n\n if (!jobClass) {\n console.error(`[Queue] Job class [${payload.job}] not registered. Deleting job ${rawJob.id}`);\n await driver.delete(rawJob.id);\n return;\n }\n\n // Restore the job instance\n let job: Job;\n try {\n job = (jobClass as any).fromJSON(payload.data);\n } catch (error) {\n console.error(`[Queue] Failed to deserialize job [${payload.job}]:`, error);\n await driver.delete(rawJob.id);\n return;\n }\n\n // Set job metadata\n job.jobId = rawJob.id;\n job.uuid = payload.uuid;\n job.attempts = rawJob.attempts;\n\n // Determine max tries\n const maxTries = job.tries ?? options.tries;\n\n // Fire before callbacks\n this.manager.fireBeforeCallbacks(connectionName, job);\n\n try {\n // Run through middleware pipeline\n await this.runJobWithMiddleware(job, options);\n\n // Check if the job was released back to the queue\n if (job.isReleased()) {\n await driver.release(rawJob.id, job.getReleaseDelay());\n return;\n }\n\n // Check if the job was explicitly deleted\n if (job.isDeleted()) {\n await driver.delete(rawJob.id);\n return;\n }\n\n // Job completed successfully\n await driver.delete(rawJob.id);\n\n // Fire after callbacks\n this.manager.fireAfterCallbacks(connectionName, job);\n } catch (error) {\n await this.handleJobException(connectionName, driver, rawJob, job, payload, maxTries, error as Error);\n }\n }\n\n /**\n * Run a job through its middleware pipeline then execute it\n */\n protected async runJobWithMiddleware(job: Job, options: Required<WorkerOptions>): Promise<void> {\n const middleware: JobMiddleware[] = job.middleware?.() || [];\n const timeout = job.timeout ?? options.timeout;\n\n // Build middleware pipeline\n const pipeline = this.buildMiddlewarePipeline(middleware, async () => {\n // Execute the job with timeout\n await this.executeWithTimeout(job, timeout);\n });\n\n await pipeline();\n }\n\n /**\n * Build a middleware pipeline\n */\n protected buildMiddlewarePipeline(\n middleware: JobMiddleware[],\n destination: () => Promise<void>\n ): () => Promise<void> {\n return middleware.reduceRight((next: () => Promise<void>, mw: JobMiddleware) => {\n return () => mw.handle(middleware[0] as any, next);\n }, destination);\n }\n\n /**\n * Execute a job with a timeout\n */\n protected async executeWithTimeout(job: Job, timeout: number): Promise<void> {\n if (timeout <= 0) {\n await job.handle();\n return;\n }\n\n const timeoutMs = timeout * 1000;\n\n await Promise.race([\n job.handle(),\n new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n `Job [${job.displayName()}] has been attempted too long or run too long. The job may have previously timed out.`\n )\n );\n }, timeoutMs);\n }),\n ]);\n }\n\n /**\n * Handle an exception that occurred while processing a job\n */\n protected async handleJobException(\n connectionName: string,\n driver: QueueDriver,\n rawJob: QueueDriverJob,\n job: Job,\n payload: JobPayloadData,\n maxTries: number,\n error: Error\n ): Promise<void> {\n // Fire failing callbacks\n this.manager.fireFailingCallbacks(connectionName, job, error);\n\n // Check if the job has exceeded max attempts\n if (maxTries > 0 && rawJob.attempts >= maxTries) {\n await this.failJob(connectionName, driver, rawJob, job, payload, error);\n return;\n }\n\n // Check retryUntil\n if (job.retryUntil && new Date() >= job.retryUntil) {\n await this.failJob(connectionName, driver, rawJob, job, payload, error);\n return;\n }\n\n // Release the job back with backoff delay\n const backoffDelay = job.getBackoffDelay(rawJob.attempts);\n await driver.release(rawJob.id, backoffDelay);\n\n console.error(\n `[Queue] Job [${job.displayName()}] failed (attempt ${rawJob.attempts}/${maxTries || 'unlimited'}). ` +\n `Retrying in ${backoffDelay}s. Error: ${error.message}`\n );\n }\n\n /**\n * Mark a job as failed and store it\n */\n protected async failJob(\n connectionName: string,\n driver: QueueDriver,\n rawJob: QueueDriverJob,\n job: Job,\n payload: JobPayloadData,\n error: Error\n ): Promise<void> {\n // Delete from the queue\n await driver.delete(rawJob.id);\n\n // Call the job's failed method\n if (job.failed) {\n try {\n await job.failed(error);\n } catch (failedError) {\n console.error(`[Queue] Error in failed() handler for [${job.displayName()}]:`, failedError);\n }\n }\n\n // Store in failed jobs table\n try {\n const failedProvider = this.getFailedJobProvider();\n if (failedProvider) {\n await failedProvider.log(connectionName, rawJob.queue, rawJob.payload, error);\n }\n } catch (storeError) {\n console.error(`[Queue] Failed to store failed job:`, storeError);\n }\n\n console.error(\n `[Queue] Job [${job.displayName()}] has failed after ${rawJob.attempts} attempt(s). ` + `Error: ${error.message}`\n );\n }\n\n /**\n * Get the failed job provider\n */\n protected getFailedJobProvider(): FailedJobProvider | null {\n try {\n return this.app.make<FailedJobProvider>('queue.failer');\n } catch {\n return null;\n }\n }\n\n /**\n * Determine if the worker should stop\n */\n protected shouldStop(options: Required<WorkerOptions>): boolean {\n // Check max jobs\n if (options.maxJobs > 0 && this.jobsProcessed >= options.maxJobs) {\n return true;\n }\n\n // Check max time\n if (options.maxTime > 0) {\n const elapsed = (Date.now() - this.startTime) / 1000;\n if (elapsed >= options.maxTime) {\n return true;\n }\n }\n\n // Check memory limit\n if (this.memoryExceeded(options.memory)) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Determine if the memory limit has been exceeded\n */\n protected memoryExceeded(memoryLimit: number): boolean {\n const usage = process.memoryUsage();\n const usedMB = usage.rss / 1024 / 1024;\n return usedMB >= memoryLimit;\n }\n\n /**\n * Sleep for the given number of seconds\n */\n protected async sleep(seconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n }\n\n /**\n * Listen for process signals for graceful shutdown\n */\n protected listenForSignals(): void {\n process.on('SIGTERM', () => {\n this.shouldQuit = true;\n });\n\n process.on('SIGINT', () => {\n this.shouldQuit = true;\n });\n\n process.on('SIGUSR2', () => {\n // Restart signal (used by queue:restart)\n this.shouldQuit = true;\n });\n }\n\n /**\n * Stop the worker\n */\n stop(): void {\n this.shouldQuit = true;\n }\n\n /**\n * Pause the worker\n */\n pause(): void {\n this.paused = true;\n }\n\n /**\n * Resume the worker\n */\n resume(): void {\n this.paused = false;\n }\n\n /**\n * Get the number of jobs processed\n */\n getJobsProcessed(): number {\n return this.jobsProcessed;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Worker.mjs","names":[],"sources":["../../../src/Queue/Workers/Worker.ts"],"sourcesContent":["/**\n * Worker\n *\n * The queue worker that processes jobs from the queue.\n * Handles the daemon loop, job processing, retries, failures,\n * memory management, and graceful shutdown.\n *\n * Mirrors Laravel's Illuminate\\Queue\\Worker.\n */\n\nimport type { Application } from '@/Foundation/Application';\nimport type { QueueDriver, QueueDriverJob } from '@/Queue/Contracts/QueueDriver';\nimport type { FailedJobProvider } from '@/Queue/Failed/FailedJobProvider';\nimport type { Job } from '@/Queue/Job';\nimport type { JobMiddleware } from '@/Queue/Middleware/JobMiddleware';\nimport type { QueueManager } from '@/Queue/QueueManager';\nimport { JobPayload, type JobPayloadData } from '@/Queue/JobPayload';\nimport type { WorkerOptions } from './WorkerOptions';\nimport { DEFAULT_WORKER_OPTIONS } from './WorkerOptions';\n\nexport class Worker {\n protected shouldQuit: boolean = false;\n protected paused: boolean = false;\n protected jobsProcessed: number = 0;\n protected startTime: number = 0;\n\n constructor(\n protected manager: QueueManager,\n protected app: Application\n ) {}\n\n /**\n * Run the worker daemon loop\n */\n async daemon(connectionName: string, queues: string, options: WorkerOptions = {}): Promise<void> {\n const opts = { ...DEFAULT_WORKER_OPTIONS, ...options };\n this.startTime = Date.now();\n\n this.listenForSignals();\n\n while (!this.shouldQuit) {\n // Fire looping callbacks\n this.manager.fireLoopingCallbacks();\n\n // Check if paused\n if (this.paused) {\n await this.sleep(opts.sleep);\n continue;\n }\n\n // Process the next job from the queue(s)\n const processed = await this.runNextJob(connectionName, queues, opts);\n\n if (!processed) {\n // No job was available\n if (opts.stopWhenEmpty) {\n this.stop();\n break;\n }\n await this.sleep(opts.sleep);\n } else {\n // Rest between jobs if configured\n if (opts.rest > 0) {\n await this.sleep(opts.rest / 1000);\n }\n }\n\n // Check stop conditions\n if (this.shouldStop(opts)) {\n this.stop();\n }\n }\n }\n\n /**\n * Process a single job (--once mode)\n */\n async runOnce(connectionName: string, queues: string, options: WorkerOptions = {}): Promise<boolean> {\n const opts = { ...DEFAULT_WORKER_OPTIONS, ...options };\n return this.runNextJob(connectionName, queues, opts);\n }\n\n /**\n * Get the next job and process it\n */\n async runNextJob(connectionName: string, queues: string, options: Required<WorkerOptions>): Promise<boolean> {\n const driver = this.manager.connection(connectionName);\n const queueList = queues.split(',').map((q) => q.trim());\n\n // Try each queue in priority order\n for (const queue of queueList) {\n const rawJob = await driver.pop(queue);\n\n if (rawJob) {\n await this.processJob(connectionName, driver, rawJob, options);\n this.jobsProcessed++;\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Process a single job\n */\n protected async processJob(\n connectionName: string,\n driver: QueueDriver,\n rawJob: QueueDriverJob,\n options: Required<WorkerOptions>\n ): Promise<void> {\n let payload: JobPayloadData;\n\n try {\n payload = JobPayload.deserialize(rawJob.payload);\n } catch (error) {\n // Invalid payload, delete the job\n console.error(`[Queue] Invalid job payload, deleting job ${rawJob.id}`);\n await driver.delete(rawJob.id);\n return;\n }\n\n // Resolve the job class from the registry\n const jobClass = this.manager.getJobClass(payload.job);\n\n if (!jobClass) {\n console.error(`[Queue] Job class [${payload.job}] not registered. Deleting job ${rawJob.id}`);\n await driver.delete(rawJob.id);\n return;\n }\n\n // Restore the job instance\n let job: Job;\n try {\n job = (jobClass as any).fromJSON(payload.data);\n } catch (error) {\n console.error(`[Queue] Failed to deserialize job [${payload.job}]:`, error);\n await driver.delete(rawJob.id);\n return;\n }\n\n // Set job metadata\n job.jobId = rawJob.id;\n job.uuid = payload.uuid;\n job.attempts = rawJob.attempts;\n\n // Determine max tries\n const maxTries = job.tries ?? options.tries;\n\n // Fire before callbacks\n this.manager.fireBeforeCallbacks(connectionName, job);\n\n try {\n // Run through middleware pipeline\n await this.runJobWithMiddleware(job, options);\n\n // Check if the job was released back to the queue\n if (job.isReleased()) {\n await driver.release(rawJob.id, job.getReleaseDelay());\n return;\n }\n\n // Check if the job was explicitly deleted\n if (job.isDeleted()) {\n await driver.delete(rawJob.id);\n return;\n }\n\n // Job completed successfully\n await driver.delete(rawJob.id);\n\n // Fire after callbacks\n this.manager.fireAfterCallbacks(connectionName, job);\n } catch (error) {\n await this.handleJobException(connectionName, driver, rawJob, job, payload, maxTries, error as Error);\n }\n }\n\n /**\n * Run a job through its middleware pipeline then execute it\n */\n protected async runJobWithMiddleware(job: Job, options: Required<WorkerOptions>): Promise<void> {\n const middleware: JobMiddleware[] = job.middleware?.() || [];\n const timeout = job.timeout ?? options.timeout;\n\n // Build middleware pipeline\n const pipeline = this.buildMiddlewarePipeline(middleware, async () => {\n // Execute the job with timeout\n await this.executeWithTimeout(job, timeout);\n });\n\n await pipeline();\n }\n\n /**\n * Build a middleware pipeline\n */\n protected buildMiddlewarePipeline(\n middleware: JobMiddleware[],\n destination: () => Promise<void>\n ): () => Promise<void> {\n return middleware.reduceRight((next: () => Promise<void>, mw: JobMiddleware) => {\n return () => mw.handle(middleware[0] as any, next);\n }, destination);\n }\n\n /**\n * Execute a job with a timeout\n */\n protected async executeWithTimeout(job: Job, timeout: number): Promise<void> {\n if (timeout <= 0) {\n await job.handle();\n return;\n }\n\n const timeoutMs = timeout * 1000;\n\n await Promise.race([\n job.handle(),\n new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n `Job [${job.displayName()}] has been attempted too long or run too long. The job may have previously timed out.`\n )\n );\n }, timeoutMs);\n }),\n ]);\n }\n\n /**\n * Handle an exception that occurred while processing a job\n */\n protected async handleJobException(\n connectionName: string,\n driver: QueueDriver,\n rawJob: QueueDriverJob,\n job: Job,\n payload: JobPayloadData,\n maxTries: number,\n error: Error\n ): Promise<void> {\n // Fire failing callbacks\n this.manager.fireFailingCallbacks(connectionName, job, error);\n\n // Check if the job has exceeded max attempts\n if (maxTries > 0 && rawJob.attempts >= maxTries) {\n await this.failJob(connectionName, driver, rawJob, job, payload, error);\n return;\n }\n\n // Check retryUntil\n if (job.retryUntil && new Date() >= job.retryUntil) {\n await this.failJob(connectionName, driver, rawJob, job, payload, error);\n return;\n }\n\n // Release the job back with backoff delay\n const backoffDelay = job.getBackoffDelay(rawJob.attempts);\n await driver.release(rawJob.id, backoffDelay);\n\n console.error(\n `[Queue] Job [${job.displayName()}] failed (attempt ${rawJob.attempts}/${maxTries || 'unlimited'}). ` +\n `Retrying in ${backoffDelay}s. Error: ${error.message}`\n );\n }\n\n /**\n * Mark a job as failed and store it\n */\n protected async failJob(\n connectionName: string,\n driver: QueueDriver,\n rawJob: QueueDriverJob,\n job: Job,\n payload: JobPayloadData,\n error: Error\n ): Promise<void> {\n // Delete from the queue\n await driver.delete(rawJob.id);\n\n // Call the job's failed method\n if (job.failed) {\n try {\n await job.failed(error);\n } catch (failedError) {\n console.error(`[Queue] Error in failed() handler for [${job.displayName()}]:`, failedError);\n }\n }\n\n // Store in failed jobs table\n try {\n const failedProvider = this.getFailedJobProvider();\n if (failedProvider) {\n await failedProvider.log(connectionName, rawJob.queue, rawJob.payload, error);\n }\n } catch (storeError) {\n console.error(`[Queue] Failed to store failed job:`, storeError);\n }\n\n console.error(\n `[Queue] Job [${job.displayName()}] has failed after ${rawJob.attempts} attempt(s). ` + `Error: ${error.message}`\n );\n }\n\n /**\n * Get the failed job provider\n */\n protected getFailedJobProvider(): FailedJobProvider | null {\n try {\n return this.app.make<FailedJobProvider>('queue.failer');\n } catch {\n return null;\n }\n }\n\n /**\n * Determine if the worker should stop\n */\n protected shouldStop(options: Required<WorkerOptions>): boolean {\n // Check max jobs\n if (options.maxJobs > 0 && this.jobsProcessed >= options.maxJobs) {\n return true;\n }\n\n // Check max time\n if (options.maxTime > 0) {\n const elapsed = (Date.now() - this.startTime) / 1000;\n if (elapsed >= options.maxTime) {\n return true;\n }\n }\n\n // Check memory limit\n if (this.memoryExceeded(options.memory)) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Determine if the memory limit has been exceeded\n */\n protected memoryExceeded(memoryLimit: number): boolean {\n const usage = process.memoryUsage();\n const usedMB = usage.rss / 1024 / 1024;\n return usedMB >= memoryLimit;\n }\n\n /**\n * Sleep for the given number of seconds\n */\n protected async sleep(seconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n }\n\n /**\n * Listen for process signals for graceful shutdown\n */\n protected listenForSignals(): void {\n process.on('SIGTERM', () => {\n this.shouldQuit = true;\n });\n\n process.on('SIGINT', () => {\n this.shouldQuit = true;\n });\n\n process.on('SIGUSR2', () => {\n // Restart signal (used by queue:restart)\n this.shouldQuit = true;\n });\n }\n\n /**\n * Stop the worker\n */\n stop(): void {\n this.shouldQuit = true;\n }\n\n /**\n * Pause the worker\n */\n pause(): void {\n this.paused = true;\n }\n\n /**\n * Resume the worker\n */\n resume(): void {\n this.paused = false;\n }\n\n /**\n * Get the number of jobs processed\n */\n getJobsProcessed(): number {\n return this.jobsProcessed;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Request.mjs","names":["parseUrl","parseQuery"],"sources":["../../src/Routing/Request.ts"],"sourcesContent":["import { IncomingMessage } from 'http';\nimport { parse as parseQuery } from 'querystring';\nimport { parse as parseUrl } from 'url';\nimport { Route } from './Route';\n\n/**\n * Request - Laravel's HTTP Request wrapper\n * Illuminate\\Http\\Request\n */\nexport class Request {\n public raw: IncomingMessage;\n public method: string;\n public url: string;\n public path: string;\n public query: Record<string, any> = {};\n public params: Record<string, any> = {};\n public headers: Record<string, string | string[] | undefined>;\n public body: any = {};\n public route?: Route;\n\n private bodyParsed: boolean = false;\n\n constructor(req: IncomingMessage) {\n this.raw = req;\n this.method = req.method || 'GET';\n this.url = req.url || '/';\n this.headers = req.headers as Record<string, string | string[] | undefined>;\n\n // Parse URL and query string\n const parsed = parseUrl(this.url, true);\n this.path = parsed.pathname || '/';\n this.query = parsed.query as Record<string, any>;\n }\n\n /**\n * Parse the request body\n */\n async parseBody(): Promise<void> {\n if (this.bodyParsed) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n let data = '';\n\n this.raw.on('data', (chunk) => {\n data += chunk.toString();\n });\n\n this.raw.on('end', () => {\n try {\n const contentType = this.header('content-type') || '';\n\n if (contentType.includes('application/json')) {\n this.body = data ? JSON.parse(data) : {};\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n this.body = parseQuery(data);\n } else {\n this.body = data;\n }\n\n this.bodyParsed = true;\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n\n this.raw.on('error', reject);\n });\n }\n\n /**\n * Get a header value\n * Laravel: $request->header('content-type')\n */\n header(name: string, defaultValue?: string): string | undefined {\n const value = this.headers[name.toLowerCase()];\n return Array.isArray(value) ? value[0] : value || defaultValue;\n }\n\n /**\n * Get an input value from the request\n * Laravel: $request->input('name')\n */\n input(key: string, defaultValue?: any): any {\n return this.get(key, defaultValue);\n }\n\n /**\n * Get a value from query or body\n * Laravel: $request->get('name')\n */\n get(key: string, defaultValue?: any): any {\n if (this.query[key] !== undefined) {\n return this.query[key];\n }\n\n if (this.body[key] !== undefined) {\n return this.body[key];\n }\n\n return defaultValue;\n }\n\n /**\n * Get all inputs\n * Laravel: $request->all()\n */\n all(): Record<string, any> {\n return { ...this.query, ...this.body };\n }\n\n /**\n * Get only specified inputs\n * Laravel: $request->only(['name', 'email'])\n */\n only(keys: string[]): Record<string, any> {\n const result: Record<string, any> = {};\n const all = this.all();\n\n for (const key of keys) {\n if (all[key] !== undefined) {\n result[key] = all[key];\n }\n }\n\n return result;\n }\n\n /**\n * Get all inputs except specified\n * Laravel: $request->except(['password'])\n */\n except(keys: string[]): Record<string, any> {\n const all = this.all();\n const result = { ...all };\n\n for (const key of keys) {\n delete result[key];\n }\n\n return result;\n }\n\n /**\n * Determine if the request contains a given input\n * Laravel: $request->has('name')\n */\n has(key: string): boolean {\n return this.get(key) !== undefined;\n }\n\n /**\n * Determine if the request contains a non-empty value\n * Laravel: $request->filled('name')\n */\n filled(key: string): boolean {\n const value = this.get(key);\n return value !== undefined && value !== null && value !== '';\n }\n\n /**\n * Get the route parameter\n * Laravel: $request->route('id')\n */\n routeParam(key: string, defaultValue?: string): string | undefined {\n return this.params[key] ?? defaultValue;\n }\n\n /**\n * Determine if the request is an AJAX request\n * Laravel: $request->ajax()\n */\n ajax(): boolean {\n return this.header('x-requested-with')?.toLowerCase() === 'xmlhttprequest';\n }\n\n /**\n * Determine if the request expects JSON\n * Laravel: $request->expectsJson()\n */\n expectsJson(): boolean {\n const accept = this.header('accept') || '';\n return accept.includes('application/json');\n }\n\n /**\n * Determine if the request is sending JSON\n * Laravel: $request->isJson()\n */\n isJson(): boolean {\n const contentType = this.header('content-type') || '';\n return contentType.includes('application/json');\n }\n\n /**\n * Get the request method\n * Laravel: $request->method()\n */\n getMethod(): string {\n return this.method;\n }\n\n /**\n * Determine if the request is a specific method\n * Laravel: $request->isMethod('post')\n */\n isMethod(method: string): boolean {\n return this.method.toLowerCase() === method.toLowerCase();\n }\n\n /**\n * Get the URL for the request\n * Laravel: $request->url()\n */\n getUrl(): string {\n return this.url;\n }\n\n /**\n * Get the path for the request\n * Laravel: $request->path()\n */\n getPath(): string {\n return this.path;\n }\n\n /**\n * Get the IP address of the request\n * Laravel: $request->ip()\n */\n ip(): string | undefined {\n const forwarded = this.header('x-forwarded-for');\n if (forwarded) {\n return Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];\n }\n\n return this.raw.socket.remoteAddress;\n }\n}\n"],"mappings":"gEASA,IAAa,EAAb,KAAqB,CACnB,IACA,OACA,IACA,KACA,MAAoC,EAAE,CACtC,OAAqC,EAAE,CACvC,QACA,KAAmB,EAAE,CACrB,MAEA,WAA8B,GAE9B,YAAY,EAAsB,CAChC,KAAK,IAAM,EACX,KAAK,OAAS,EAAI,QAAU,MAC5B,KAAK,IAAM,EAAI,KAAO,IACtB,KAAK,QAAU,EAAI,QAGnB,IAAM,EAASA,EAAS,KAAK,IAAK,GAAK,CACvC,KAAK,KAAO,EAAO,UAAY,IAC/B,KAAK,MAAQ,EAAO,MAMtB,MAAM,WAA2B,CAC3B,SAAK,WAIT,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAI,EAAO,GAEX,KAAK,IAAI,GAAG,OAAS,GAAU,CAC7B,GAAQ,EAAM,UAAU,EACxB,CAEF,KAAK,IAAI,GAAG,UAAa,CACvB,GAAI,CACF,IAAM,EAAc,KAAK,OAAO,eAAe,EAAI,GAE/C,EAAY,SAAS,mBAAmB,CAC1C,KAAK,KAAO,EAAO,KAAK,MAAM,EAAK,CAAG,EAAE,CAC/B,EAAY,SAAS,oCAAoC,CAClE,KAAK,KAAOC,EAAW,EAAK,CAE5B,KAAK,KAAO,EAGd,KAAK,WAAa,GAClB,GAAS,OACF,EAAO,CACd,EAAO,EAAM,GAEf,CAEF,KAAK,IAAI,GAAG,QAAS,EAAO,EAC5B,CAOJ,OAAO,EAAc,EAA2C,CAC9D,IAAM,EAAQ,KAAK,QAAQ,EAAK,aAAa,EAC7C,OAAO,MAAM,QAAQ,EAAM,CAAG,EAAM,GAAK,GAAS,EAOpD,MAAM,EAAa,EAAyB,CAC1C,OAAO,KAAK,IAAI,EAAK,EAAa,CAOpC,IAAI,EAAa,EAAyB,CASxC,OARI,KAAK,MAAM,KAAS,IAAA,GAIpB,KAAK,KAAK,KAAS,IAAA,GAIhB,EAHE,KAAK,KAAK,GAJV,KAAK,MAAM,GActB,KAA2B,CACzB,MAAO,CAAE,GAAG,KAAK,MAAO,GAAG,KAAK,KAAM,CAOxC,KAAK,EAAqC,CACxC,IAAM,EAA8B,EAAE,CAChC,EAAM,KAAK,KAAK,CAEtB,IAAK,IAAM,KAAO,EACZ,EAAI,KAAS,IAAA,KACf,EAAO,GAAO,EAAI,IAItB,OAAO,EAOT,OAAO,EAAqC,CAE1C,IAAM,EAAS,CAAE,GADL,KAAK,
|
|
1
|
+
{"version":3,"file":"Request.mjs","names":["parseUrl","parseQuery"],"sources":["../../src/Routing/Request.ts"],"sourcesContent":["import { IncomingMessage } from 'http';\nimport { parse as parseQuery } from 'querystring';\nimport { parse as parseUrl } from 'url';\nimport { Route } from './Route';\n\n/**\n * Request - Laravel's HTTP Request wrapper\n * Illuminate\\Http\\Request\n */\nexport class Request {\n public raw: IncomingMessage;\n public method: string;\n public url: string;\n public path: string;\n public query: Record<string, any> = {};\n public params: Record<string, any> = {};\n public headers: Record<string, string | string[] | undefined>;\n public body: any = {};\n public route?: Route;\n\n private bodyParsed: boolean = false;\n\n constructor(req: IncomingMessage) {\n this.raw = req;\n this.method = req.method || 'GET';\n this.url = req.url || '/';\n this.headers = req.headers as Record<string, string | string[] | undefined>;\n\n // Parse URL and query string\n const parsed = parseUrl(this.url, true);\n this.path = parsed.pathname || '/';\n this.query = parsed.query as Record<string, any>;\n }\n\n /**\n * Parse the request body\n */\n async parseBody(): Promise<void> {\n if (this.bodyParsed) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n let data = '';\n\n this.raw.on('data', (chunk) => {\n data += chunk.toString();\n });\n\n this.raw.on('end', () => {\n try {\n const contentType = this.header('content-type') || '';\n\n if (contentType.includes('application/json')) {\n this.body = data ? JSON.parse(data) : {};\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n this.body = parseQuery(data);\n } else {\n this.body = data;\n }\n\n this.bodyParsed = true;\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n\n this.raw.on('error', reject);\n });\n }\n\n /**\n * Get a header value\n * Laravel: $request->header('content-type')\n */\n header(name: string, defaultValue?: string): string | undefined {\n const value = this.headers[name.toLowerCase()];\n return Array.isArray(value) ? value[0] : value || defaultValue;\n }\n\n /**\n * Get an input value from the request\n * Laravel: $request->input('name')\n */\n input(key: string, defaultValue?: any): any {\n return this.get(key, defaultValue);\n }\n\n /**\n * Get a value from query or body\n * Laravel: $request->get('name')\n */\n get(key: string, defaultValue?: any): any {\n if (this.query[key] !== undefined) {\n return this.query[key];\n }\n\n if (this.body[key] !== undefined) {\n return this.body[key];\n }\n\n return defaultValue;\n }\n\n /**\n * Get all inputs\n * Laravel: $request->all()\n */\n all(): Record<string, any> {\n return { ...this.query, ...this.body };\n }\n\n /**\n * Get only specified inputs\n * Laravel: $request->only(['name', 'email'])\n */\n only(keys: string[]): Record<string, any> {\n const result: Record<string, any> = {};\n const all = this.all();\n\n for (const key of keys) {\n if (all[key] !== undefined) {\n result[key] = all[key];\n }\n }\n\n return result;\n }\n\n /**\n * Get all inputs except specified\n * Laravel: $request->except(['password'])\n */\n except(keys: string[]): Record<string, any> {\n const all = this.all();\n const result = { ...all };\n\n for (const key of keys) {\n delete result[key];\n }\n\n return result;\n }\n\n /**\n * Determine if the request contains a given input\n * Laravel: $request->has('name')\n */\n has(key: string): boolean {\n return this.get(key) !== undefined;\n }\n\n /**\n * Determine if the request contains a non-empty value\n * Laravel: $request->filled('name')\n */\n filled(key: string): boolean {\n const value = this.get(key);\n return value !== undefined && value !== null && value !== '';\n }\n\n /**\n * Get the route parameter\n * Laravel: $request->route('id')\n */\n routeParam(key: string, defaultValue?: string): string | undefined {\n return this.params[key] ?? defaultValue;\n }\n\n /**\n * Determine if the request is an AJAX request\n * Laravel: $request->ajax()\n */\n ajax(): boolean {\n return this.header('x-requested-with')?.toLowerCase() === 'xmlhttprequest';\n }\n\n /**\n * Determine if the request expects JSON\n * Laravel: $request->expectsJson()\n */\n expectsJson(): boolean {\n const accept = this.header('accept') || '';\n return accept.includes('application/json');\n }\n\n /**\n * Determine if the request is sending JSON\n * Laravel: $request->isJson()\n */\n isJson(): boolean {\n const contentType = this.header('content-type') || '';\n return contentType.includes('application/json');\n }\n\n /**\n * Get the request method\n * Laravel: $request->method()\n */\n getMethod(): string {\n return this.method;\n }\n\n /**\n * Determine if the request is a specific method\n * Laravel: $request->isMethod('post')\n */\n isMethod(method: string): boolean {\n return this.method.toLowerCase() === method.toLowerCase();\n }\n\n /**\n * Get the URL for the request\n * Laravel: $request->url()\n */\n getUrl(): string {\n return this.url;\n }\n\n /**\n * Get the path for the request\n * Laravel: $request->path()\n */\n getPath(): string {\n return this.path;\n }\n\n /**\n * Get the IP address of the request\n * Laravel: $request->ip()\n */\n ip(): string | undefined {\n const forwarded = this.header('x-forwarded-for');\n if (forwarded) {\n return Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];\n }\n\n return this.raw.socket.remoteAddress;\n }\n}\n"],"mappings":"gEASA,IAAa,EAAb,KAAqB,CACnB,IACA,OACA,IACA,KACA,MAAoC,EAAE,CACtC,OAAqC,EAAE,CACvC,QACA,KAAmB,EAAE,CACrB,MAEA,WAA8B,GAE9B,YAAY,EAAsB,CAChC,KAAK,IAAM,EACX,KAAK,OAAS,EAAI,QAAU,MAC5B,KAAK,IAAM,EAAI,KAAO,IACtB,KAAK,QAAU,EAAI,QAGnB,IAAM,EAASA,EAAS,KAAK,IAAK,GAAK,CACvC,KAAK,KAAO,EAAO,UAAY,IAC/B,KAAK,MAAQ,EAAO,MAMtB,MAAM,WAA2B,CAC3B,SAAK,WAIT,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAI,EAAO,GAEX,KAAK,IAAI,GAAG,OAAS,GAAU,CAC7B,GAAQ,EAAM,UAAU,EACxB,CAEF,KAAK,IAAI,GAAG,UAAa,CACvB,GAAI,CACF,IAAM,EAAc,KAAK,OAAO,eAAe,EAAI,GAE/C,EAAY,SAAS,mBAAmB,CAC1C,KAAK,KAAO,EAAO,KAAK,MAAM,EAAK,CAAG,EAAE,CAC/B,EAAY,SAAS,oCAAoC,CAClE,KAAK,KAAOC,EAAW,EAAK,CAE5B,KAAK,KAAO,EAGd,KAAK,WAAa,GAClB,GAAS,OACF,EAAO,CACd,EAAO,EAAM,GAEf,CAEF,KAAK,IAAI,GAAG,QAAS,EAAO,EAC5B,CAOJ,OAAO,EAAc,EAA2C,CAC9D,IAAM,EAAQ,KAAK,QAAQ,EAAK,aAAa,EAC7C,OAAO,MAAM,QAAQ,EAAM,CAAG,EAAM,GAAK,GAAS,EAOpD,MAAM,EAAa,EAAyB,CAC1C,OAAO,KAAK,IAAI,EAAK,EAAa,CAOpC,IAAI,EAAa,EAAyB,CASxC,OARI,KAAK,MAAM,KAAS,IAAA,GAIpB,KAAK,KAAK,KAAS,IAAA,GAIhB,EAHE,KAAK,KAAK,GAJV,KAAK,MAAM,GActB,KAA2B,CACzB,MAAO,CAAE,GAAG,KAAK,MAAO,GAAG,KAAK,KAAM,CAOxC,KAAK,EAAqC,CACxC,IAAM,EAA8B,EAAE,CAChC,EAAM,KAAK,KAAK,CAEtB,IAAK,IAAM,KAAO,EACZ,EAAI,KAAS,IAAA,KACf,EAAO,GAAO,EAAI,IAItB,OAAO,EAOT,OAAO,EAAqC,CAE1C,IAAM,EAAS,CAAE,GADL,KAAK,KACM,CAAE,CAEzB,IAAK,IAAM,KAAO,EAChB,OAAO,EAAO,GAGhB,OAAO,EAOT,IAAI,EAAsB,CACxB,OAAO,KAAK,IAAI,EAAI,GAAK,IAAA,GAO3B,OAAO,EAAsB,CAC3B,IAAM,EAAQ,KAAK,IAAI,EAAI,CAC3B,OAAO,GAAiC,MAAQ,IAAU,GAO5D,WAAW,EAAa,EAA2C,CACjE,OAAO,KAAK,OAAO,IAAQ,EAO7B,MAAgB,CACd,OAAO,KAAK,OAAO,mBAAmB,EAAE,aAAa,GAAK,iBAO5D,aAAuB,CAErB,OADe,KAAK,OAAO,SAAS,EAAI,IAC1B,SAAS,mBAAmB,CAO5C,QAAkB,CAEhB,OADoB,KAAK,OAAO,eAAe,EAAI,IAChC,SAAS,mBAAmB,CAOjD,WAAoB,CAClB,OAAO,KAAK,OAOd,SAAS,EAAyB,CAChC,OAAO,KAAK,OAAO,aAAa,GAAK,EAAO,aAAa,CAO3D,QAAiB,CACf,OAAO,KAAK,IAOd,SAAkB,CAChB,OAAO,KAAK,KAOd,IAAyB,CACvB,IAAM,EAAY,KAAK,OAAO,kBAAkB,CAKhD,OAJI,EACK,MAAM,QAAQ,EAAU,CAAG,EAAU,GAAK,EAAU,MAAM,IAAI,CAAC,GAGjE,KAAK,IAAI,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Response.mjs","names":[],"sources":["../../src/Routing/Response.ts"],"sourcesContent":["import { ServerResponse } from 'http';\nimport type { ViewFactory } from '@/View/ViewFactory';\nimport { getGlobalApp } from '@/Support/helpers';\n\n// Lazily resolve the ViewFactory from the container at call time\nfunction resolveViewFactory(): ViewFactory | null {\n try {\n const app = getGlobalApp();\n if (app) {\n return app.make('view') as ViewFactory;\n }\n } catch {\n // View system not registered\n }\n return null;\n}\n\n/**\n * Response - Laravel's HTTP Response wrapper\n * Illuminate\\Http\\Response\n */\nexport class Response {\n public raw: ServerResponse;\n public finished: boolean = false;\n private statusCode: number = 200;\n private responseHeaders: Record<string, string> = {};\n private cookies: Array<{ name: string; value: string; options: CookieOptions }> = [];\n\n constructor(res: ServerResponse) {\n this.raw = res;\n }\n\n /**\n * Set the response status code\n * Laravel: response()->status(404)\n */\n status(code: number): this {\n this.statusCode = code;\n return this;\n }\n\n /**\n * Set a header on the response\n * Laravel: response()->header('Content-Type', 'application/json')\n */\n header(name: string, value: string): this {\n this.responseHeaders[name] = value;\n return this;\n }\n\n /**\n * Set multiple headers\n */\n headers(headers: Record<string, string>): this {\n Object.assign(this.responseHeaders, headers);\n return this;\n }\n\n /**\n * Set a cookie on the response\n * Laravel: response()->cookie('name', 'value')\n */\n cookie(name: string, value: string, options: CookieOptions = {}): this {\n this.cookies.push({ name, value, options });\n return this;\n }\n\n /**\n * Write headers to the response\n */\n private writeHeaders(): void {\n this.raw.statusCode = this.statusCode;\n\n // Write custom headers\n for (const [name, value] of Object.entries(this.responseHeaders)) {\n this.raw.setHeader(name, value);\n }\n\n // Write cookies\n if (this.cookies.length > 0) {\n const cookieHeaders = this.cookies.map(({ name, value, options }) => {\n return this.serializeCookie(name, value, options);\n });\n\n this.raw.setHeader('Set-Cookie', cookieHeaders);\n }\n }\n\n /**\n * Send a response\n * Laravel: response('Hello World')\n */\n send(data: any): void {\n if (this.finished) {\n return;\n }\n\n this.writeHeaders();\n\n if (data === null || data === undefined) {\n this.raw.end();\n } else if (typeof data === 'string' || Buffer.isBuffer(data)) {\n this.raw.end(data);\n } else if (typeof data === 'object') {\n this.json(data);\n return;\n } else {\n this.raw.end(String(data));\n }\n\n this.finished = true;\n }\n\n /**\n * Send a JSON response\n * Laravel: response()->json(['key' => 'value'])\n */\n json(data: any, statusCode?: number): void {\n if (this.finished) {\n return;\n }\n\n if (statusCode) {\n this.statusCode = statusCode;\n }\n\n this.header('Content-Type', 'application/json');\n this.writeHeaders();\n\n this.raw.end(JSON.stringify(data));\n this.finished = true;\n }\n\n /**\n * Send a redirect response\n * Laravel: redirect('/path')\n */\n redirect(url: string, statusCode: number = 302): void {\n if (this.finished) {\n return;\n }\n\n this.statusCode = statusCode;\n this.header('Location', url);\n this.writeHeaders();\n\n this.raw.end();\n this.finished = true;\n }\n\n /**\n * Send a download response\n * Laravel: response()->download($path)\n */\n download(data: Buffer | string, filename: string): void {\n if (this.finished) {\n return;\n }\n\n this.header('Content-Disposition', `attachment; filename=\"${filename}\"`);\n this.header('Content-Type', 'application/octet-stream');\n this.send(data);\n }\n\n /**\n * Send a view response.\n * Laravel: response()->view('welcome', ['name' => 'John'])\n *\n * Resolves the view via the ViewFactory registered in the container,\n * then renders it asynchronously and sends the resulting HTML.\n */\n async view(template: string, data: Record<string, any> = {}): Promise<void> {\n const factory = resolveViewFactory();\n\n if (factory) {\n const viewInstance = factory.make(template, data);\n const html = await viewInstance.render();\n this.header('Content-Type', 'text/html');\n this.send(html);\n } else {\n // Fallback when the view system is not registered\n this.header('Content-Type', 'text/html');\n this.send(`<html><body><h1>View: ${template}</h1><pre>${JSON.stringify(data, null, 2)}</pre></body></html>`);\n }\n }\n\n /**\n * Serialize a cookie\n */\n private serializeCookie(name: string, value: string, options: CookieOptions): string {\n let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;\n\n if (options.maxAge) {\n cookie += `; Max-Age=${options.maxAge}`;\n }\n\n if (options.domain) {\n cookie += `; Domain=${options.domain}`;\n }\n\n if (options.path) {\n cookie += `; Path=${options.path}`;\n } else {\n cookie += '; Path=/';\n }\n\n if (options.expires) {\n cookie += `; Expires=${options.expires.toUTCString()}`;\n }\n\n if (options.httpOnly) {\n cookie += '; HttpOnly';\n }\n\n if (options.secure) {\n cookie += '; Secure';\n }\n\n if (options.sameSite) {\n cookie += `; SameSite=${options.sameSite}`;\n }\n\n return cookie;\n }\n}\n\n/**\n * Cookie options\n */\nexport interface CookieOptions {\n maxAge?: number;\n domain?: string;\n path?: string;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'Strict' | 'Lax' | 'None';\n}\n"],"mappings":"sDAKA,SAAS,GAAyC,CAChD,GAAI,CACF,IAAM,EAAM,GAAc,CAC1B,GAAI,EACF,OAAO,EAAI,KAAK,OAAO,MAEnB,EAGR,OAAO,KAOT,IAAa,EAAb,KAAsB,CACpB,IACA,SAA2B,GAC3B,WAA6B,IAC7B,gBAAkD,EAAE,CACpD,QAAkF,EAAE,CAEpF,YAAY,EAAqB,CAC/B,KAAK,IAAM,EAOb,OAAO,EAAoB,CAEzB,MADA,MAAK,WAAa,EACX,KAOT,OAAO,EAAc,EAAqB,CAExC,MADA,MAAK,gBAAgB,GAAQ,EACtB,KAMT,QAAQ,EAAuC,CAE7C,OADA,OAAO,OAAO,KAAK,gBAAiB,EAAQ,CACrC,KAOT,OAAO,EAAc,EAAe,EAAyB,EAAE,CAAQ,CAErE,OADA,KAAK,QAAQ,KAAK,CAAE,OAAM,QAAO,UAAS,CAAC,CACpC,KAMT,cAA6B,CAC3B,KAAK,IAAI,WAAa,KAAK,WAG3B,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,KAAK,gBAAgB,CAC9D,KAAK,IAAI,UAAU,EAAM,EAAM,CAIjC,GAAI,KAAK,QAAQ,OAAS,EAAG,CAC3B,IAAM,EAAgB,KAAK,QAAQ,KAAK,CAAE,OAAM,QAAO,aAC9C,KAAK,gBAAgB,EAAM,EAAO,EAAQ,CACjD,CAEF,KAAK,IAAI,UAAU,aAAc,EAAc,EAQnD,KAAK,EAAiB,CAChB,SAAK,SAMT,IAFA,KAAK,cAAc,CAEf,GAAS,KACX,KAAK,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"Response.mjs","names":[],"sources":["../../src/Routing/Response.ts"],"sourcesContent":["import { ServerResponse } from 'http';\nimport type { ViewFactory } from '@/View/ViewFactory';\nimport { getGlobalApp } from '@/Support/helpers';\n\n// Lazily resolve the ViewFactory from the container at call time\nfunction resolveViewFactory(): ViewFactory | null {\n try {\n const app = getGlobalApp();\n if (app) {\n return app.make('view') as ViewFactory;\n }\n } catch {\n // View system not registered\n }\n return null;\n}\n\n/**\n * Response - Laravel's HTTP Response wrapper\n * Illuminate\\Http\\Response\n */\nexport class Response {\n public raw: ServerResponse;\n public finished: boolean = false;\n private statusCode: number = 200;\n private responseHeaders: Record<string, string> = {};\n private cookies: Array<{ name: string; value: string; options: CookieOptions }> = [];\n\n constructor(res: ServerResponse) {\n this.raw = res;\n }\n\n /**\n * Set the response status code\n * Laravel: response()->status(404)\n */\n status(code: number): this {\n this.statusCode = code;\n return this;\n }\n\n /**\n * Set a header on the response\n * Laravel: response()->header('Content-Type', 'application/json')\n */\n header(name: string, value: string): this {\n this.responseHeaders[name] = value;\n return this;\n }\n\n /**\n * Set multiple headers\n */\n headers(headers: Record<string, string>): this {\n Object.assign(this.responseHeaders, headers);\n return this;\n }\n\n /**\n * Set a cookie on the response\n * Laravel: response()->cookie('name', 'value')\n */\n cookie(name: string, value: string, options: CookieOptions = {}): this {\n this.cookies.push({ name, value, options });\n return this;\n }\n\n /**\n * Write headers to the response\n */\n private writeHeaders(): void {\n this.raw.statusCode = this.statusCode;\n\n // Write custom headers\n for (const [name, value] of Object.entries(this.responseHeaders)) {\n this.raw.setHeader(name, value);\n }\n\n // Write cookies\n if (this.cookies.length > 0) {\n const cookieHeaders = this.cookies.map(({ name, value, options }) => {\n return this.serializeCookie(name, value, options);\n });\n\n this.raw.setHeader('Set-Cookie', cookieHeaders);\n }\n }\n\n /**\n * Send a response\n * Laravel: response('Hello World')\n */\n send(data: any): void {\n if (this.finished) {\n return;\n }\n\n this.writeHeaders();\n\n if (data === null || data === undefined) {\n this.raw.end();\n } else if (typeof data === 'string' || Buffer.isBuffer(data)) {\n this.raw.end(data);\n } else if (typeof data === 'object') {\n this.json(data);\n return;\n } else {\n this.raw.end(String(data));\n }\n\n this.finished = true;\n }\n\n /**\n * Send a JSON response\n * Laravel: response()->json(['key' => 'value'])\n */\n json(data: any, statusCode?: number): void {\n if (this.finished) {\n return;\n }\n\n if (statusCode) {\n this.statusCode = statusCode;\n }\n\n this.header('Content-Type', 'application/json');\n this.writeHeaders();\n\n this.raw.end(JSON.stringify(data));\n this.finished = true;\n }\n\n /**\n * Send a redirect response\n * Laravel: redirect('/path')\n */\n redirect(url: string, statusCode: number = 302): void {\n if (this.finished) {\n return;\n }\n\n this.statusCode = statusCode;\n this.header('Location', url);\n this.writeHeaders();\n\n this.raw.end();\n this.finished = true;\n }\n\n /**\n * Send a download response\n * Laravel: response()->download($path)\n */\n download(data: Buffer | string, filename: string): void {\n if (this.finished) {\n return;\n }\n\n this.header('Content-Disposition', `attachment; filename=\"${filename}\"`);\n this.header('Content-Type', 'application/octet-stream');\n this.send(data);\n }\n\n /**\n * Send a view response.\n * Laravel: response()->view('welcome', ['name' => 'John'])\n *\n * Resolves the view via the ViewFactory registered in the container,\n * then renders it asynchronously and sends the resulting HTML.\n */\n async view(template: string, data: Record<string, any> = {}): Promise<void> {\n const factory = resolveViewFactory();\n\n if (factory) {\n const viewInstance = factory.make(template, data);\n const html = await viewInstance.render();\n this.header('Content-Type', 'text/html');\n this.send(html);\n } else {\n // Fallback when the view system is not registered\n this.header('Content-Type', 'text/html');\n this.send(`<html><body><h1>View: ${template}</h1><pre>${JSON.stringify(data, null, 2)}</pre></body></html>`);\n }\n }\n\n /**\n * Serialize a cookie\n */\n private serializeCookie(name: string, value: string, options: CookieOptions): string {\n let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;\n\n if (options.maxAge) {\n cookie += `; Max-Age=${options.maxAge}`;\n }\n\n if (options.domain) {\n cookie += `; Domain=${options.domain}`;\n }\n\n if (options.path) {\n cookie += `; Path=${options.path}`;\n } else {\n cookie += '; Path=/';\n }\n\n if (options.expires) {\n cookie += `; Expires=${options.expires.toUTCString()}`;\n }\n\n if (options.httpOnly) {\n cookie += '; HttpOnly';\n }\n\n if (options.secure) {\n cookie += '; Secure';\n }\n\n if (options.sameSite) {\n cookie += `; SameSite=${options.sameSite}`;\n }\n\n return cookie;\n }\n}\n\n/**\n * Cookie options\n */\nexport interface CookieOptions {\n maxAge?: number;\n domain?: string;\n path?: string;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'Strict' | 'Lax' | 'None';\n}\n"],"mappings":"sDAKA,SAAS,GAAyC,CAChD,GAAI,CACF,IAAM,EAAM,GAAc,CAC1B,GAAI,EACF,OAAO,EAAI,KAAK,OAAO,MAEnB,EAGR,OAAO,KAOT,IAAa,EAAb,KAAsB,CACpB,IACA,SAA2B,GAC3B,WAA6B,IAC7B,gBAAkD,EAAE,CACpD,QAAkF,EAAE,CAEpF,YAAY,EAAqB,CAC/B,KAAK,IAAM,EAOb,OAAO,EAAoB,CAEzB,MADA,MAAK,WAAa,EACX,KAOT,OAAO,EAAc,EAAqB,CAExC,MADA,MAAK,gBAAgB,GAAQ,EACtB,KAMT,QAAQ,EAAuC,CAE7C,OADA,OAAO,OAAO,KAAK,gBAAiB,EAAQ,CACrC,KAOT,OAAO,EAAc,EAAe,EAAyB,EAAE,CAAQ,CAErE,OADA,KAAK,QAAQ,KAAK,CAAE,OAAM,QAAO,UAAS,CAAC,CACpC,KAMT,cAA6B,CAC3B,KAAK,IAAI,WAAa,KAAK,WAG3B,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,KAAK,gBAAgB,CAC9D,KAAK,IAAI,UAAU,EAAM,EAAM,CAIjC,GAAI,KAAK,QAAQ,OAAS,EAAG,CAC3B,IAAM,EAAgB,KAAK,QAAQ,KAAK,CAAE,OAAM,QAAO,aAC9C,KAAK,gBAAgB,EAAM,EAAO,EAAQ,CACjD,CAEF,KAAK,IAAI,UAAU,aAAc,EAAc,EAQnD,KAAK,EAAiB,CAChB,SAAK,SAMT,IAFA,KAAK,cAAc,CAEf,GAAS,KACX,KAAK,IAAI,KAAK,MACT,GAAI,OAAO,GAAS,UAAY,OAAO,SAAS,EAAK,CAC1D,KAAK,IAAI,IAAI,EAAK,MACb,GAAI,OAAO,GAAS,SAAU,CACnC,KAAK,KAAK,EAAK,CACf,YAEA,KAAK,IAAI,IAAI,OAAO,EAAK,CAAC,CAG5B,KAAK,SAAW,IAOlB,KAAK,EAAW,EAA2B,CACrC,AAYJ,KAAK,YARD,IACF,KAAK,WAAa,GAGpB,KAAK,OAAO,eAAgB,mBAAmB,CAC/C,KAAK,cAAc,CAEnB,KAAK,IAAI,IAAI,KAAK,UAAU,EAAK,CAAC,CAClB,IAOlB,SAAS,EAAa,EAAqB,IAAW,CAChD,AASJ,KAAK,YALL,KAAK,WAAa,EAClB,KAAK,OAAO,WAAY,EAAI,CAC5B,KAAK,cAAc,CAEnB,KAAK,IAAI,KAAK,CACE,IAOlB,SAAS,EAAuB,EAAwB,CAClD,KAAK,WAIT,KAAK,OAAO,sBAAuB,yBAAyB,EAAS,GAAG,CACxE,KAAK,OAAO,eAAgB,2BAA2B,CACvD,KAAK,KAAK,EAAK,EAUjB,MAAM,KAAK,EAAkB,EAA4B,EAAE,CAAiB,CAC1E,IAAM,EAAU,GAAoB,CAEpC,GAAI,EAAS,CAEX,IAAM,EAAO,MADQ,EAAQ,KAAK,EAAU,EACb,CAAC,QAAQ,CACxC,KAAK,OAAO,eAAgB,YAAY,CACxC,KAAK,KAAK,EAAK,MAGf,KAAK,OAAO,eAAgB,YAAY,CACxC,KAAK,KAAK,yBAAyB,EAAS,YAAY,KAAK,UAAU,EAAM,KAAM,EAAE,CAAC,sBAAsB,CAOhH,gBAAwB,EAAc,EAAe,EAAgC,CACnF,IAAI,EAAS,GAAG,mBAAmB,EAAK,CAAC,GAAG,mBAAmB,EAAM,GAgCrE,OA9BI,EAAQ,SACV,GAAU,aAAa,EAAQ,UAG7B,EAAQ,SACV,GAAU,YAAY,EAAQ,UAG5B,EAAQ,KACV,GAAU,UAAU,EAAQ,OAE5B,GAAU,WAGR,EAAQ,UACV,GAAU,aAAa,EAAQ,QAAQ,aAAa,IAGlD,EAAQ,WACV,GAAU,cAGR,EAAQ,SACV,GAAU,YAGR,EAAQ,WACV,GAAU,cAAc,EAAQ,YAG3B"}
|
package/dist/Routing/Route.d.cts
CHANGED
package/dist/Routing/Route.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Application } from "../Foundation/Application.cjs";
|
|
2
|
-
import { Request } from "./Request.cjs";
|
|
3
2
|
import { Response } from "./Response.cjs";
|
|
4
3
|
import { HttpMethod, Middleware, Route, RouteAction } from "./Route.cjs";
|
|
4
|
+
import { Request } from "./Request.cjs";
|
|
5
5
|
|
|
6
6
|
//#region src/Routing/Router.d.ts
|
|
7
7
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Application } from "../Foundation/Application.mjs";
|
|
2
|
-
import { Request } from "./Request.mjs";
|
|
3
2
|
import { Response } from "./Response.mjs";
|
|
4
3
|
import { HttpMethod, Middleware, Route, RouteAction } from "./Route.mjs";
|
|
4
|
+
import { Request } from "./Request.mjs";
|
|
5
5
|
|
|
6
6
|
//#region src/Routing/Router.d.ts
|
|
7
7
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`),require(`reflect-metadata`);let e=require(`path`),t=require(`fs`);var n=class{constructor(e){this.app=e}async discover(e){let t=new Map;for(let n of e){let e=await this.discoverInDirectory(n);for(let[n,r]of e)t.has(n)||t.set(n,[]),t.get(n).push(...r)}return t}async discoverInDirectory(e){let t=new Map;if(!this.directoryExists(e))return t;let n=this.getListenerFiles(e);for(let e of n)try{let n=await this.loadListenerClass(e);if(!n)continue;let r=this.extractEventTypes(n);for(let n of r)t.has(n)||t.set(n,[]),t.get(n).push(this.getListenerName(e))}catch(t){this.app.isDebug()&&console.warn(`Could not load listener from ${e}:`,t)}return t}getListenerFiles(n,r=[]){let i=(0,t.readdirSync)(n);for(let a of i){let i=(0,e.join)(n,a);(0,t.statSync)(i).isDirectory()?this.getListenerFiles(i,r):this.isListenerFile(i)&&r.push(i)}return r}isListenerFile(t){let n=(0,e.extname)(t);return!(n!==`.ts`&&n!==`.js`||t.includes(`.test.`)||t.includes(`.spec.`)||t.endsWith(`index.ts`)||t.endsWith(`index.js`))}async loadListenerClass(e){let t=await import(e);return t.default||Object.values(t).find(e=>typeof e==`function`&&this.isListenerClass(e))}isListenerClass(e){if(typeof e!=`function`)return!1;let t=e.prototype;return typeof t?.handle==`function`||typeof t?.__invoke==`function`}extractEventTypes(e){let t=[];if(!(e.prototype.handle||e.prototype.__invoke))return t;let n=Reflect.getMetadata(`design:paramtypes`,e.prototype,`handle`)||Reflect.getMetadata(`design:paramtypes`,e.prototype,`__invoke`);if(!n||n.length===0)return t;let r=n[0];r&&r.name&&r.name!==`Object`&&t.push(r.name);let i=Reflect.getMetadata(`event:types`,e);return i&&Array.isArray(i)&&t.push(...i.map(e=>e.name)),t}getListenerName(e){let t=e.replace(/\.[jt]s$/,``),n=this.app.path();return t.startsWith(n)&&(t=t.substring(n.length+1)),t}directoryExists(e){try{return(0,t.statSync)(e).isDirectory()}catch{return!1}}};exports.EventDiscovery=n;
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`),require(`reflect-metadata`);let e=require(`path`),t=require(`fs`);var n=class{app;constructor(e){this.app=e}async discover(e){let t=new Map;for(let n of e){let e=await this.discoverInDirectory(n);for(let[n,r]of e)t.has(n)||t.set(n,[]),t.get(n).push(...r)}return t}async discoverInDirectory(e){let t=new Map;if(!this.directoryExists(e))return t;let n=this.getListenerFiles(e);for(let e of n)try{let n=await this.loadListenerClass(e);if(!n)continue;let r=this.extractEventTypes(n);for(let n of r)t.has(n)||t.set(n,[]),t.get(n).push(this.getListenerName(e))}catch(t){this.app.isDebug()&&console.warn(`Could not load listener from ${e}:`,t)}return t}getListenerFiles(n,r=[]){let i=(0,t.readdirSync)(n);for(let a of i){let i=(0,e.join)(n,a);(0,t.statSync)(i).isDirectory()?this.getListenerFiles(i,r):this.isListenerFile(i)&&r.push(i)}return r}isListenerFile(t){let n=(0,e.extname)(t);return!(n!==`.ts`&&n!==`.js`||t.includes(`.test.`)||t.includes(`.spec.`)||t.endsWith(`index.ts`)||t.endsWith(`index.js`))}async loadListenerClass(e){let t=await import(e);return t.default||Object.values(t).find(e=>typeof e==`function`&&this.isListenerClass(e))}isListenerClass(e){if(typeof e!=`function`)return!1;let t=e.prototype;return typeof t?.handle==`function`||typeof t?.__invoke==`function`}extractEventTypes(e){let t=[];if(!(e.prototype.handle||e.prototype.__invoke))return t;let n=Reflect.getMetadata(`design:paramtypes`,e.prototype,`handle`)||Reflect.getMetadata(`design:paramtypes`,e.prototype,`__invoke`);if(!n||n.length===0)return t;let r=n[0];r&&r.name&&r.name!==`Object`&&t.push(r.name);let i=Reflect.getMetadata(`event:types`,e);return i&&Array.isArray(i)&&t.push(...i.map(e=>e.name)),t}getListenerName(e){let t=e.replace(/\.[jt]s$/,``),n=this.app.path();return t.startsWith(n)&&(t=t.substring(n.length+1)),t}directoryExists(e){try{return(0,t.statSync)(e).isDirectory()}catch{return!1}}};exports.EventDiscovery=n;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"reflect-metadata";import{extname as e,join as t}from"path";import{readdirSync as n,statSync as r}from"fs";var i=class{constructor(e){this.app=e}async discover(e){let t=new Map;for(let n of e){let e=await this.discoverInDirectory(n);for(let[n,r]of e)t.has(n)||t.set(n,[]),t.get(n).push(...r)}return t}async discoverInDirectory(e){let t=new Map;if(!this.directoryExists(e))return t;let n=this.getListenerFiles(e);for(let e of n)try{let n=await this.loadListenerClass(e);if(!n)continue;let r=this.extractEventTypes(n);for(let n of r)t.has(n)||t.set(n,[]),t.get(n).push(this.getListenerName(e))}catch(t){this.app.isDebug()&&console.warn(`Could not load listener from ${e}:`,t)}return t}getListenerFiles(e,i=[]){let a=n(e);for(let n of a){let a=t(e,n);r(a).isDirectory()?this.getListenerFiles(a,i):this.isListenerFile(a)&&i.push(a)}return i}isListenerFile(t){let n=e(t);return!(n!==`.ts`&&n!==`.js`||t.includes(`.test.`)||t.includes(`.spec.`)||t.endsWith(`index.ts`)||t.endsWith(`index.js`))}async loadListenerClass(e){let t=await import(e);return t.default||Object.values(t).find(e=>typeof e==`function`&&this.isListenerClass(e))}isListenerClass(e){if(typeof e!=`function`)return!1;let t=e.prototype;return typeof t?.handle==`function`||typeof t?.__invoke==`function`}extractEventTypes(e){let t=[];if(!(e.prototype.handle||e.prototype.__invoke))return t;let n=Reflect.getMetadata(`design:paramtypes`,e.prototype,`handle`)||Reflect.getMetadata(`design:paramtypes`,e.prototype,`__invoke`);if(!n||n.length===0)return t;let r=n[0];r&&r.name&&r.name!==`Object`&&t.push(r.name);let i=Reflect.getMetadata(`event:types`,e);return i&&Array.isArray(i)&&t.push(...i.map(e=>e.name)),t}getListenerName(e){let t=e.replace(/\.[jt]s$/,``),n=this.app.path();return t.startsWith(n)&&(t=t.substring(n.length+1)),t}directoryExists(e){try{return r(e).isDirectory()}catch{return!1}}};export{i as EventDiscovery};
|
|
1
|
+
import"reflect-metadata";import{extname as e,join as t}from"path";import{readdirSync as n,statSync as r}from"fs";var i=class{app;constructor(e){this.app=e}async discover(e){let t=new Map;for(let n of e){let e=await this.discoverInDirectory(n);for(let[n,r]of e)t.has(n)||t.set(n,[]),t.get(n).push(...r)}return t}async discoverInDirectory(e){let t=new Map;if(!this.directoryExists(e))return t;let n=this.getListenerFiles(e);for(let e of n)try{let n=await this.loadListenerClass(e);if(!n)continue;let r=this.extractEventTypes(n);for(let n of r)t.has(n)||t.set(n,[]),t.get(n).push(this.getListenerName(e))}catch(t){this.app.isDebug()&&console.warn(`Could not load listener from ${e}:`,t)}return t}getListenerFiles(e,i=[]){let a=n(e);for(let n of a){let a=t(e,n);r(a).isDirectory()?this.getListenerFiles(a,i):this.isListenerFile(a)&&i.push(a)}return i}isListenerFile(t){let n=e(t);return!(n!==`.ts`&&n!==`.js`||t.includes(`.test.`)||t.includes(`.spec.`)||t.endsWith(`index.ts`)||t.endsWith(`index.js`))}async loadListenerClass(e){let t=await import(e);return t.default||Object.values(t).find(e=>typeof e==`function`&&this.isListenerClass(e))}isListenerClass(e){if(typeof e!=`function`)return!1;let t=e.prototype;return typeof t?.handle==`function`||typeof t?.__invoke==`function`}extractEventTypes(e){let t=[];if(!(e.prototype.handle||e.prototype.__invoke))return t;let n=Reflect.getMetadata(`design:paramtypes`,e.prototype,`handle`)||Reflect.getMetadata(`design:paramtypes`,e.prototype,`__invoke`);if(!n||n.length===0)return t;let r=n[0];r&&r.name&&r.name!==`Object`&&t.push(r.name);let i=Reflect.getMetadata(`event:types`,e);return i&&Array.isArray(i)&&t.push(...i.map(e=>e.name)),t}getListenerName(e){let t=e.replace(/\.[jt]s$/,``),n=this.app.path();return t.startsWith(n)&&(t=t.substring(n.length+1)),t}directoryExists(e){try{return r(e).isDirectory()}catch{return!1}}};export{i as EventDiscovery};
|
|
2
2
|
//# sourceMappingURL=EventDiscovery.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventDiscovery.mjs","names":[],"sources":["../../src/Support/EventDiscovery.ts"],"sourcesContent":["import { readdirSync, statSync } from 'fs';\nimport { join, extname } from 'path';\nimport { Application } from '@/Foundation/Application';\nimport 'reflect-metadata';\n\n/**\n * EventDiscovery\n *\n * Automatically discovers event listeners by scanning directories for listener files\n * and extracting event type information using TypeScript's reflect-metadata.\n *\n * This enables Laravel 11's automatic listener discovery feature where listeners\n * are automatically registered based on their type hints without manual registration.\n *\n * @example\n * ```typescript\n * const discovery = new EventDiscovery(app)\n * const listeners = await discovery.discover([app.path('Listeners')])\n * // Returns: Map<string, string[]> - event names to listener class paths\n * ```\n */\nexport class EventDiscovery {\n constructor(protected app: Application) {}\n\n /**\n * Discover all event listeners within the given directories\n *\n * Scans each directory recursively, loads listener classes, extracts their\n * event type hints, and builds a map of events to their listeners.\n *\n * @param directories Array of absolute paths to directories to scan\n * @returns Map of event names to listener class paths\n *\n * @example\n * ```typescript\n * const listeners = await discovery.discover([\n * '/app/Listeners',\n * '/app/Modules/User/Listeners'\n * ])\n * ```\n */\n async discover(directories: string[]): Promise<Map<string, string[]>> {\n const listeners = new Map<string, string[]>();\n\n for (const directory of directories) {\n const discovered = await this.discoverInDirectory(directory);\n\n // Merge discovered listeners\n for (const [event, listenerList] of discovered) {\n if (!listeners.has(event)) {\n listeners.set(event, []);\n }\n listeners.get(event)!.push(...listenerList);\n }\n }\n\n return listeners;\n }\n\n /**\n * Discover listeners in a single directory\n *\n * Recursively scans the directory for TypeScript/JavaScript files,\n * loads each file, and extracts event-listener mappings.\n *\n * @param directory Absolute path to directory to scan\n * @returns Map of event names to listener class paths for this directory\n */\n protected async discoverInDirectory(directory: string): Promise<Map<string, string[]>> {\n const listeners = new Map<string, string[]>();\n\n if (!this.directoryExists(directory)) {\n return listeners;\n }\n\n const files = this.getListenerFiles(directory);\n\n for (const file of files) {\n try {\n const listenerClass = await this.loadListenerClass(file);\n\n if (!listenerClass) continue;\n\n const events = this.extractEventTypes(listenerClass);\n\n for (const event of events) {\n if (!listeners.has(event)) {\n listeners.set(event, []);\n }\n listeners.get(event)!.push(this.getListenerName(file));\n }\n } catch (error) {\n // Skip files that can't be loaded\n if (this.app.isDebug()) {\n console.warn(`Could not load listener from ${file}:`, error);\n }\n }\n }\n\n return listeners;\n }\n\n /**\n * Get all TypeScript/JavaScript files in directory recursively\n *\n * Walks the directory tree and collects all valid listener files,\n * excluding test files and index files.\n *\n * @param directory Directory to scan\n * @param files Accumulator for recursive scanning\n * @returns Array of absolute file paths\n */\n protected getListenerFiles(directory: string, files: string[] = []): string[] {\n const entries = readdirSync(directory);\n\n for (const entry of entries) {\n const fullPath = join(directory, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n this.getListenerFiles(fullPath, files);\n } else if (this.isListenerFile(fullPath)) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n\n /**\n * Check if file is a valid listener file\n *\n * Valid listener files:\n * - Must be .ts or .js\n * - Cannot be test files (.test.ts, .spec.ts)\n * - Cannot be index files (index.ts, index.js)\n *\n * @param file Absolute path to file\n * @returns True if file should be processed as a listener\n */\n protected isListenerFile(file: string): boolean {\n const ext = extname(file);\n\n // Must be .ts or .js file\n if (ext !== '.ts' && ext !== '.js') {\n return false;\n }\n\n // Exclude test files\n if (file.includes('.test.') || file.includes('.spec.')) {\n return false;\n }\n\n // Exclude index files\n if (file.endsWith('index.ts') || file.endsWith('index.js')) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Load listener class from file\n *\n * Dynamically imports the file and attempts to find a valid listener class\n * from either the default export or named exports.\n *\n * @param file Absolute path to listener file\n * @returns Listener class constructor or undefined if not found\n */\n protected async loadListenerClass(file: string): Promise<any> {\n const module = await import(file);\n\n // Try to find the default export or named export\n return (\n module.default || Object.values(module).find((exp) => typeof exp === 'function' && this.isListenerClass(exp))\n );\n }\n\n /**\n * Check if a class is a listener\n *\n * A valid listener must have either a `handle` method or `__invoke` method\n * on its prototype.\n *\n * @param cls Class constructor to check\n * @returns True if class is a valid listener\n */\n protected isListenerClass(cls: any): boolean {\n if (typeof cls !== 'function') return false;\n\n // Must have a handle method or implement __invoke\n const prototype = cls.prototype;\n return typeof prototype?.handle === 'function' || typeof prototype?.__invoke === 'function';\n }\n\n /**\n * Extract event types from listener class\n *\n * Uses reflect-metadata to inspect the listener's handle method and extract\n * the event type from the first parameter. Also checks for the @HandlesEvents\n * decorator for union types.\n *\n * TypeScript's emitDecoratorMetadata must be enabled in tsconfig.json.\n *\n * @param listenerClass Listener class constructor\n * @returns Array of event class names this listener handles\n *\n * @example\n * ```typescript\n * // For: handle(event: UserRegistered)\n * // Returns: ['UserRegistered']\n *\n * // For: @HandlesEvents([UserRegistered, UserUpdated])\n * // Returns: ['UserRegistered', 'UserUpdated']\n * ```\n */\n protected extractEventTypes(listenerClass: any): string[] {\n const events: string[] = [];\n\n // Get the handle method\n const handleMethod = listenerClass.prototype.handle || listenerClass.prototype.__invoke;\n\n if (!handleMethod) return events;\n\n // Use reflect-metadata to get parameter types\n const paramTypes =\n Reflect.getMetadata('design:paramtypes', listenerClass.prototype, 'handle') ||\n Reflect.getMetadata('design:paramtypes', listenerClass.prototype, '__invoke');\n\n if (!paramTypes || paramTypes.length === 0) return events;\n\n // First parameter should be the event\n const eventType = paramTypes[0];\n\n if (eventType && eventType.name && eventType.name !== 'Object') {\n events.push(eventType.name);\n }\n\n // Check for union types (TypeScript limitation - requires custom decorator)\n // The @HandlesEvents decorator can be used to specify multiple event types\n const unionTypes = Reflect.getMetadata('event:types', listenerClass);\n if (unionTypes && Array.isArray(unionTypes)) {\n events.push(...unionTypes.map((t: any) => t.name));\n }\n\n return events;\n }\n\n /**\n * Get listener name from file path\n *\n * Converts an absolute file path to a listener class reference string\n * that can be used for container resolution.\n *\n * @param file Absolute path to listener file\n * @returns Listener class path relative to app directory\n *\n * @example\n * ```typescript\n * // Input: /app/Listeners/SendWelcomeEmail.ts\n * // Output: Listeners/SendWelcomeEmail\n *\n * // Input: /app/Modules/User/Listeners/NotifyAdmin.ts\n * // Output: Modules/User/Listeners/NotifyAdmin\n * ```\n */\n protected getListenerName(file: string): string {\n // Convert file path to class name\n // Remove extension\n let name = file.replace(/\\.[jt]s$/, '');\n\n // Get relative to app path\n const appPath = this.app.path();\n if (name.startsWith(appPath)) {\n name = name.substring(appPath.length + 1);\n }\n\n // Convert to class path (e.g., Listeners/SendWelcomeEmail)\n return name;\n }\n\n /**\n * Check if directory exists\n *\n * @param directory Absolute path to directory\n * @returns True if directory exists and is accessible\n */\n protected directoryExists(directory: string): boolean {\n try {\n return statSync(directory).isDirectory();\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EventDiscovery.mjs","names":[],"sources":["../../src/Support/EventDiscovery.ts"],"sourcesContent":["import { readdirSync, statSync } from 'fs';\nimport { join, extname } from 'path';\nimport { Application } from '@/Foundation/Application';\nimport 'reflect-metadata';\n\n/**\n * EventDiscovery\n *\n * Automatically discovers event listeners by scanning directories for listener files\n * and extracting event type information using TypeScript's reflect-metadata.\n *\n * This enables Laravel 11's automatic listener discovery feature where listeners\n * are automatically registered based on their type hints without manual registration.\n *\n * @example\n * ```typescript\n * const discovery = new EventDiscovery(app)\n * const listeners = await discovery.discover([app.path('Listeners')])\n * // Returns: Map<string, string[]> - event names to listener class paths\n * ```\n */\nexport class EventDiscovery {\n constructor(protected app: Application) {}\n\n /**\n * Discover all event listeners within the given directories\n *\n * Scans each directory recursively, loads listener classes, extracts their\n * event type hints, and builds a map of events to their listeners.\n *\n * @param directories Array of absolute paths to directories to scan\n * @returns Map of event names to listener class paths\n *\n * @example\n * ```typescript\n * const listeners = await discovery.discover([\n * '/app/Listeners',\n * '/app/Modules/User/Listeners'\n * ])\n * ```\n */\n async discover(directories: string[]): Promise<Map<string, string[]>> {\n const listeners = new Map<string, string[]>();\n\n for (const directory of directories) {\n const discovered = await this.discoverInDirectory(directory);\n\n // Merge discovered listeners\n for (const [event, listenerList] of discovered) {\n if (!listeners.has(event)) {\n listeners.set(event, []);\n }\n listeners.get(event)!.push(...listenerList);\n }\n }\n\n return listeners;\n }\n\n /**\n * Discover listeners in a single directory\n *\n * Recursively scans the directory for TypeScript/JavaScript files,\n * loads each file, and extracts event-listener mappings.\n *\n * @param directory Absolute path to directory to scan\n * @returns Map of event names to listener class paths for this directory\n */\n protected async discoverInDirectory(directory: string): Promise<Map<string, string[]>> {\n const listeners = new Map<string, string[]>();\n\n if (!this.directoryExists(directory)) {\n return listeners;\n }\n\n const files = this.getListenerFiles(directory);\n\n for (const file of files) {\n try {\n const listenerClass = await this.loadListenerClass(file);\n\n if (!listenerClass) continue;\n\n const events = this.extractEventTypes(listenerClass);\n\n for (const event of events) {\n if (!listeners.has(event)) {\n listeners.set(event, []);\n }\n listeners.get(event)!.push(this.getListenerName(file));\n }\n } catch (error) {\n // Skip files that can't be loaded\n if (this.app.isDebug()) {\n console.warn(`Could not load listener from ${file}:`, error);\n }\n }\n }\n\n return listeners;\n }\n\n /**\n * Get all TypeScript/JavaScript files in directory recursively\n *\n * Walks the directory tree and collects all valid listener files,\n * excluding test files and index files.\n *\n * @param directory Directory to scan\n * @param files Accumulator for recursive scanning\n * @returns Array of absolute file paths\n */\n protected getListenerFiles(directory: string, files: string[] = []): string[] {\n const entries = readdirSync(directory);\n\n for (const entry of entries) {\n const fullPath = join(directory, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n this.getListenerFiles(fullPath, files);\n } else if (this.isListenerFile(fullPath)) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n\n /**\n * Check if file is a valid listener file\n *\n * Valid listener files:\n * - Must be .ts or .js\n * - Cannot be test files (.test.ts, .spec.ts)\n * - Cannot be index files (index.ts, index.js)\n *\n * @param file Absolute path to file\n * @returns True if file should be processed as a listener\n */\n protected isListenerFile(file: string): boolean {\n const ext = extname(file);\n\n // Must be .ts or .js file\n if (ext !== '.ts' && ext !== '.js') {\n return false;\n }\n\n // Exclude test files\n if (file.includes('.test.') || file.includes('.spec.')) {\n return false;\n }\n\n // Exclude index files\n if (file.endsWith('index.ts') || file.endsWith('index.js')) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Load listener class from file\n *\n * Dynamically imports the file and attempts to find a valid listener class\n * from either the default export or named exports.\n *\n * @param file Absolute path to listener file\n * @returns Listener class constructor or undefined if not found\n */\n protected async loadListenerClass(file: string): Promise<any> {\n const module = await import(file);\n\n // Try to find the default export or named export\n return (\n module.default || Object.values(module).find((exp) => typeof exp === 'function' && this.isListenerClass(exp))\n );\n }\n\n /**\n * Check if a class is a listener\n *\n * A valid listener must have either a `handle` method or `__invoke` method\n * on its prototype.\n *\n * @param cls Class constructor to check\n * @returns True if class is a valid listener\n */\n protected isListenerClass(cls: any): boolean {\n if (typeof cls !== 'function') return false;\n\n // Must have a handle method or implement __invoke\n const prototype = cls.prototype;\n return typeof prototype?.handle === 'function' || typeof prototype?.__invoke === 'function';\n }\n\n /**\n * Extract event types from listener class\n *\n * Uses reflect-metadata to inspect the listener's handle method and extract\n * the event type from the first parameter. Also checks for the @HandlesEvents\n * decorator for union types.\n *\n * TypeScript's emitDecoratorMetadata must be enabled in tsconfig.json.\n *\n * @param listenerClass Listener class constructor\n * @returns Array of event class names this listener handles\n *\n * @example\n * ```typescript\n * // For: handle(event: UserRegistered)\n * // Returns: ['UserRegistered']\n *\n * // For: @HandlesEvents([UserRegistered, UserUpdated])\n * // Returns: ['UserRegistered', 'UserUpdated']\n * ```\n */\n protected extractEventTypes(listenerClass: any): string[] {\n const events: string[] = [];\n\n // Get the handle method\n const handleMethod = listenerClass.prototype.handle || listenerClass.prototype.__invoke;\n\n if (!handleMethod) return events;\n\n // Use reflect-metadata to get parameter types\n const paramTypes =\n Reflect.getMetadata('design:paramtypes', listenerClass.prototype, 'handle') ||\n Reflect.getMetadata('design:paramtypes', listenerClass.prototype, '__invoke');\n\n if (!paramTypes || paramTypes.length === 0) return events;\n\n // First parameter should be the event\n const eventType = paramTypes[0];\n\n if (eventType && eventType.name && eventType.name !== 'Object') {\n events.push(eventType.name);\n }\n\n // Check for union types (TypeScript limitation - requires custom decorator)\n // The @HandlesEvents decorator can be used to specify multiple event types\n const unionTypes = Reflect.getMetadata('event:types', listenerClass);\n if (unionTypes && Array.isArray(unionTypes)) {\n events.push(...unionTypes.map((t: any) => t.name));\n }\n\n return events;\n }\n\n /**\n * Get listener name from file path\n *\n * Converts an absolute file path to a listener class reference string\n * that can be used for container resolution.\n *\n * @param file Absolute path to listener file\n * @returns Listener class path relative to app directory\n *\n * @example\n * ```typescript\n * // Input: /app/Listeners/SendWelcomeEmail.ts\n * // Output: Listeners/SendWelcomeEmail\n *\n * // Input: /app/Modules/User/Listeners/NotifyAdmin.ts\n * // Output: Modules/User/Listeners/NotifyAdmin\n * ```\n */\n protected getListenerName(file: string): string {\n // Convert file path to class name\n // Remove extension\n let name = file.replace(/\\.[jt]s$/, '');\n\n // Get relative to app path\n const appPath = this.app.path();\n if (name.startsWith(appPath)) {\n name = name.substring(appPath.length + 1);\n }\n\n // Convert to class path (e.g., Listeners/SendWelcomeEmail)\n return name;\n }\n\n /**\n * Check if directory exists\n *\n * @param directory Absolute path to directory\n * @returns True if directory exists and is accessible\n */\n protected directoryExists(directory: string): boolean {\n try {\n return statSync(directory).isDirectory();\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../../../_virtual/_rolldown/runtime.cjs`);var e=class{dispatched=new Map;fakedEvents=[];exceptedEvents=[];originalDispatcher;listeners=new Map;constructor(e){this.container=e}setFakedEvents(e){this.fakedEvents=e}setExceptedEvents(e){this.exceptedEvents=e}setOriginalDispatcher(e){this.originalDispatcher=e}shouldFake(e){return this.exceptedEvents.length>0?!this.exceptedEvents.includes(e):this.fakedEvents.length>0?this.fakedEvents.includes(e):!0}listen(e,t){let n=Array.isArray(e)?e:[e];for(let e of n)this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t)}hasListeners(e){return this.listeners.has(e)&&this.listeners.get(e).length>0}subscribe(e){}dispatch(e,t=[],n=!1){let r=this.getEventName(e);return this.shouldFake(r)?(this.dispatched.has(r)||this.dispatched.set(r,[]),this.dispatched.get(r).push({event:e,payload:t,timestamp:Date.now()}),[]):this.originalDispatcher?this.originalDispatcher.dispatch(e,t,n):[]}until(e,t=[]){return this.dispatch(e,t,!0)[0]||null}push(e,t=[]){}flush(e){}forget(e){this.dispatched.delete(e),this.listeners.delete(e)}forgetPushed(){}getRawListeners(){return this.listeners}getEventName(e){return typeof e==`string`?e:e.constructor.name}assertDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length===0){let e=Array.from(this.dispatched.keys()).join(`, `)||`none`;throw Error(`Event [${n}] was not dispatched.\nDispatched events: ${e}`)}if(t&&r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)}).length===0)throw Error(`Event [${n}] was dispatched ${r.length} time(s) but none matched the provided callback.`)}assertNotDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(!t){if(r.length>0)throw Error(`Event [${n}] was dispatched ${r.length} time(s).`);return}let i=r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)});if(i.length>0)throw Error(`Event [${n}] was unexpectedly dispatched ${i.length} time(s) matching the provided callback.`)}assertDispatchedTimes(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length!==t)throw Error(`Event [${n}] was dispatched ${r.length} time(s) instead of ${t}.`)}assertNothingDispatched(){let e=Array.from(this.dispatched.values()).reduce((e,t)=>e+t.length,0);if(e>0){let t=Array.from(this.dispatched.entries()).map(([e,t])=>` ${e}: ${t.length}`).join(`
|
|
1
|
+
require(`../../../_virtual/_rolldown/runtime.cjs`);var e=class{container;dispatched=new Map;fakedEvents=[];exceptedEvents=[];originalDispatcher;listeners=new Map;constructor(e){this.container=e}setFakedEvents(e){this.fakedEvents=e}setExceptedEvents(e){this.exceptedEvents=e}setOriginalDispatcher(e){this.originalDispatcher=e}shouldFake(e){return this.exceptedEvents.length>0?!this.exceptedEvents.includes(e):this.fakedEvents.length>0?this.fakedEvents.includes(e):!0}listen(e,t){let n=Array.isArray(e)?e:[e];for(let e of n)this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t)}hasListeners(e){return this.listeners.has(e)&&this.listeners.get(e).length>0}subscribe(e){}dispatch(e,t=[],n=!1){let r=this.getEventName(e);return this.shouldFake(r)?(this.dispatched.has(r)||this.dispatched.set(r,[]),this.dispatched.get(r).push({event:e,payload:t,timestamp:Date.now()}),[]):this.originalDispatcher?this.originalDispatcher.dispatch(e,t,n):[]}until(e,t=[]){return this.dispatch(e,t,!0)[0]||null}push(e,t=[]){}flush(e){}forget(e){this.dispatched.delete(e),this.listeners.delete(e)}forgetPushed(){}getRawListeners(){return this.listeners}getEventName(e){return typeof e==`string`?e:e.constructor.name}assertDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length===0){let e=Array.from(this.dispatched.keys()).join(`, `)||`none`;throw Error(`Event [${n}] was not dispatched.\nDispatched events: ${e}`)}if(t&&r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)}).length===0)throw Error(`Event [${n}] was dispatched ${r.length} time(s) but none matched the provided callback.`)}assertNotDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(!t){if(r.length>0)throw Error(`Event [${n}] was dispatched ${r.length} time(s).`);return}let i=r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)});if(i.length>0)throw Error(`Event [${n}] was unexpectedly dispatched ${i.length} time(s) matching the provided callback.`)}assertDispatchedTimes(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length!==t)throw Error(`Event [${n}] was dispatched ${r.length} time(s) instead of ${t}.`)}assertNothingDispatched(){let e=Array.from(this.dispatched.values()).reduce((e,t)=>e+t.length,0);if(e>0){let t=Array.from(this.dispatched.entries()).map(([e,t])=>` ${e}: ${t.length}`).join(`
|
|
2
2
|
`);throw Error(`Expected no events to be dispatched, but ${e} were:\n${t}`)}}assertListening(e,t){let n=typeof e==`string`?e:e.name,r=this.listeners.get(n)||[];if(r.length===0)throw Error(`No listeners are registered for event [${n}].`);if(t){let e=typeof t==`string`?t:this.getListenerName(t);if(!r.some(t=>(typeof t==`string`?t:this.getListenerName(t))===e))throw Error(`Listener [${e}] is not registered for event [${n}].`)}}getListenerName(e){return typeof e==`string`?e:typeof e==`function`?e.name||`<Closure>`:typeof e==`object`&&e.constructor?e.constructor.name:`<Unknown>`}getDispatched(e){let t=typeof e==`string`?e:e.name;return this.dispatched.get(t)||[]}getDispatchedEventNames(){return Array.from(this.dispatched.keys())}getDispatchedCount(){return Array.from(this.dispatched.values()).reduce((e,t)=>e+t.length,0)}clear(){this.dispatched.clear()}clearListeners(){this.listeners.clear()}reset(){this.clear(),this.clearListeners(),this.fakedEvents=[],this.exceptedEvents=[],this.originalDispatcher=void 0}};exports.EventFake=e;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Container } from "../../../Container/Container.cjs";
|
|
2
1
|
import { Event } from "../../../Events/Event.cjs";
|
|
2
|
+
import { Container } from "../../../Container/Container.cjs";
|
|
3
3
|
import { EventListener } from "../../../Events/types.cjs";
|
|
4
4
|
import { DispatcherContract } from "../../../Events/Contracts/Dispatcher.cjs";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Container } from "../../../Container/Container.mjs";
|
|
2
1
|
import { Event } from "../../../Events/Event.mjs";
|
|
2
|
+
import { Container } from "../../../Container/Container.mjs";
|
|
3
3
|
import { EventListener } from "../../../Events/types.mjs";
|
|
4
4
|
import { DispatcherContract } from "../../../Events/Contracts/Dispatcher.mjs";
|
|
5
5
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var e=class{dispatched=new Map;fakedEvents=[];exceptedEvents=[];originalDispatcher;listeners=new Map;constructor(e){this.container=e}setFakedEvents(e){this.fakedEvents=e}setExceptedEvents(e){this.exceptedEvents=e}setOriginalDispatcher(e){this.originalDispatcher=e}shouldFake(e){return this.exceptedEvents.length>0?!this.exceptedEvents.includes(e):this.fakedEvents.length>0?this.fakedEvents.includes(e):!0}listen(e,t){let n=Array.isArray(e)?e:[e];for(let e of n)this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t)}hasListeners(e){return this.listeners.has(e)&&this.listeners.get(e).length>0}subscribe(e){}dispatch(e,t=[],n=!1){let r=this.getEventName(e);return this.shouldFake(r)?(this.dispatched.has(r)||this.dispatched.set(r,[]),this.dispatched.get(r).push({event:e,payload:t,timestamp:Date.now()}),[]):this.originalDispatcher?this.originalDispatcher.dispatch(e,t,n):[]}until(e,t=[]){return this.dispatch(e,t,!0)[0]||null}push(e,t=[]){}flush(e){}forget(e){this.dispatched.delete(e),this.listeners.delete(e)}forgetPushed(){}getRawListeners(){return this.listeners}getEventName(e){return typeof e==`string`?e:e.constructor.name}assertDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length===0){let e=Array.from(this.dispatched.keys()).join(`, `)||`none`;throw Error(`Event [${n}] was not dispatched.\nDispatched events: ${e}`)}if(t&&r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)}).length===0)throw Error(`Event [${n}] was dispatched ${r.length} time(s) but none matched the provided callback.`)}assertNotDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(!t){if(r.length>0)throw Error(`Event [${n}] was dispatched ${r.length} time(s).`);return}let i=r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)});if(i.length>0)throw Error(`Event [${n}] was unexpectedly dispatched ${i.length} time(s) matching the provided callback.`)}assertDispatchedTimes(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length!==t)throw Error(`Event [${n}] was dispatched ${r.length} time(s) instead of ${t}.`)}assertNothingDispatched(){let e=Array.from(this.dispatched.values()).reduce((e,t)=>e+t.length,0);if(e>0){let t=Array.from(this.dispatched.entries()).map(([e,t])=>` ${e}: ${t.length}`).join(`
|
|
1
|
+
var e=class{container;dispatched=new Map;fakedEvents=[];exceptedEvents=[];originalDispatcher;listeners=new Map;constructor(e){this.container=e}setFakedEvents(e){this.fakedEvents=e}setExceptedEvents(e){this.exceptedEvents=e}setOriginalDispatcher(e){this.originalDispatcher=e}shouldFake(e){return this.exceptedEvents.length>0?!this.exceptedEvents.includes(e):this.fakedEvents.length>0?this.fakedEvents.includes(e):!0}listen(e,t){let n=Array.isArray(e)?e:[e];for(let e of n)this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t)}hasListeners(e){return this.listeners.has(e)&&this.listeners.get(e).length>0}subscribe(e){}dispatch(e,t=[],n=!1){let r=this.getEventName(e);return this.shouldFake(r)?(this.dispatched.has(r)||this.dispatched.set(r,[]),this.dispatched.get(r).push({event:e,payload:t,timestamp:Date.now()}),[]):this.originalDispatcher?this.originalDispatcher.dispatch(e,t,n):[]}until(e,t=[]){return this.dispatch(e,t,!0)[0]||null}push(e,t=[]){}flush(e){}forget(e){this.dispatched.delete(e),this.listeners.delete(e)}forgetPushed(){}getRawListeners(){return this.listeners}getEventName(e){return typeof e==`string`?e:e.constructor.name}assertDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length===0){let e=Array.from(this.dispatched.keys()).join(`, `)||`none`;throw Error(`Event [${n}] was not dispatched.\nDispatched events: ${e}`)}if(t&&r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)}).length===0)throw Error(`Event [${n}] was dispatched ${r.length} time(s) but none matched the provided callback.`)}assertNotDispatched(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(!t){if(r.length>0)throw Error(`Event [${n}] was dispatched ${r.length} time(s).`);return}let i=r.filter(e=>{let n=typeof e.event==`string`?null:e.event;return n&&t(n)});if(i.length>0)throw Error(`Event [${n}] was unexpectedly dispatched ${i.length} time(s) matching the provided callback.`)}assertDispatchedTimes(e,t){let n=typeof e==`string`?e:e.name,r=this.dispatched.get(n)||[];if(r.length!==t)throw Error(`Event [${n}] was dispatched ${r.length} time(s) instead of ${t}.`)}assertNothingDispatched(){let e=Array.from(this.dispatched.values()).reduce((e,t)=>e+t.length,0);if(e>0){let t=Array.from(this.dispatched.entries()).map(([e,t])=>` ${e}: ${t.length}`).join(`
|
|
2
2
|
`);throw Error(`Expected no events to be dispatched, but ${e} were:\n${t}`)}}assertListening(e,t){let n=typeof e==`string`?e:e.name,r=this.listeners.get(n)||[];if(r.length===0)throw Error(`No listeners are registered for event [${n}].`);if(t){let e=typeof t==`string`?t:this.getListenerName(t);if(!r.some(t=>(typeof t==`string`?t:this.getListenerName(t))===e))throw Error(`Listener [${e}] is not registered for event [${n}].`)}}getListenerName(e){return typeof e==`string`?e:typeof e==`function`?e.name||`<Closure>`:typeof e==`object`&&e.constructor?e.constructor.name:`<Unknown>`}getDispatched(e){let t=typeof e==`string`?e:e.name;return this.dispatched.get(t)||[]}getDispatchedEventNames(){return Array.from(this.dispatched.keys())}getDispatchedCount(){return Array.from(this.dispatched.values()).reduce((e,t)=>e+t.length,0)}clear(){this.dispatched.clear()}clearListeners(){this.listeners.clear()}reset(){this.clear(),this.clearListeners(),this.fakedEvents=[],this.exceptedEvents=[],this.originalDispatcher=void 0}};export{e as EventFake};
|
|
3
3
|
//# sourceMappingURL=EventFake.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventFake.mjs","names":[],"sources":["../../../../src/Support/Testing/Fakes/EventFake.ts"],"sourcesContent":["import type { Container } from '@/Container/Container';\nimport type { DispatcherContract } from '@/Events/Contracts/Dispatcher';\nimport type { Event } from '@/Events/Event';\nimport type { EventListener } from '@/Events/types';\n\n/**\n * Dispatched Event Record\n *\n * Represents a single event dispatch with metadata\n */\ninterface DispatchedEvent {\n /** The event instance or event name */\n event: Event | string;\n\n /** Additional payload passed to the event */\n payload: any[];\n\n /** Timestamp when the event was dispatched */\n timestamp: number;\n}\n\n/**\n * Event Fake Dispatcher\n *\n * A test double for the event dispatcher that records all dispatched events\n * instead of actually calling listeners. Provides assertion methods to verify\n * event dispatching behavior in tests.\n *\n * @example\n * ```typescript\n * // Replace the real dispatcher with a fake\n * const fake = new EventFake(app);\n * app.instance('events', fake);\n *\n * // Test code that dispatches events\n * dispatch(new UserRegistered(user));\n *\n * // Assert the event was dispatched\n * fake.assertDispatched(UserRegistered);\n * fake.assertDispatchedTimes(UserRegistered, 1);\n * ```\n */\nexport class EventFake implements DispatcherContract {\n /**\n * All dispatched events organized by event name\n */\n protected dispatched: Map<string, DispatchedEvent[]> = new Map();\n\n /**\n * Events that should be faked (empty = fake all)\n */\n protected fakedEvents: string[] = [];\n\n /**\n * Events that should NOT be faked (all others will be)\n */\n protected exceptedEvents: string[] = [];\n\n /**\n * The original dispatcher (for pass-through)\n */\n protected originalDispatcher?: DispatcherContract;\n\n /**\n * Registered listeners (for assertListening)\n */\n protected listeners: Map<string, EventListener[]> = new Map();\n\n constructor(protected container: Container) {}\n\n /**\n * Set which events should be faked\n *\n * @param events - Array of event names or classes to fake\n */\n setFakedEvents(events: string[]): void {\n this.fakedEvents = events;\n }\n\n /**\n * Set which events should NOT be faked (all others will be)\n *\n * @param events - Array of event names or classes to NOT fake\n */\n setExceptedEvents(events: string[]): void {\n this.exceptedEvents = events;\n }\n\n /**\n * Set the original dispatcher for pass-through\n *\n * @param dispatcher - The real dispatcher instance\n */\n setOriginalDispatcher(dispatcher: DispatcherContract): void {\n this.originalDispatcher = dispatcher;\n }\n\n /**\n * Check if an event should be faked\n *\n * @param eventName - The event name to check\n * @returns true if the event should be faked\n */\n protected shouldFake(eventName: string): boolean {\n // If excepted events are set, fake everything except those\n if (this.exceptedEvents.length > 0) {\n return !this.exceptedEvents.includes(eventName);\n }\n\n // If faked events are set, only fake those\n if (this.fakedEvents.length > 0) {\n return this.fakedEvents.includes(eventName);\n }\n\n // Otherwise fake everything\n return true;\n }\n\n /**\n * Register an event listener\n *\n * @param events - Event name(s) to listen for\n * @param listener - The listener to call\n */\n listen(events: string | string[], listener: EventListener): void {\n // Store listeners for assertListening\n const eventArray = Array.isArray(events) ? events : [events];\n\n for (const event of eventArray) {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n\n this.listeners.get(event)!.push(listener);\n }\n }\n\n /**\n * Check if an event has listeners\n *\n * @param eventName - The event name to check\n * @returns true if the event has registered listeners\n */\n hasListeners(eventName: string): boolean {\n return this.listeners.has(eventName) && this.listeners.get(eventName)!.length > 0;\n }\n\n /**\n * Register an event subscriber\n *\n * @param subscriber - The subscriber instance\n */\n subscribe(subscriber: any): void {\n // Faked dispatcher doesn't actually register subscribers\n // but stores them for assertListening\n }\n\n /**\n * Dispatch an event\n *\n * @param event - The event instance or name\n * @param payload - Additional arguments to pass\n * @param halt - Stop on first non-null response\n * @returns Array of listener results\n */\n dispatch(event: string | Event, payload: any[] = [], halt: boolean = false): any[] {\n const eventName = this.getEventName(event);\n\n if (!this.shouldFake(eventName)) {\n // Pass through to original dispatcher if it exists\n if (this.originalDispatcher) {\n return this.originalDispatcher.dispatch(event, payload, halt);\n }\n return [];\n }\n\n // Record the dispatch\n if (!this.dispatched.has(eventName)) {\n this.dispatched.set(eventName, []);\n }\n\n this.dispatched.get(eventName)!.push({\n event,\n payload,\n timestamp: Date.now(),\n });\n\n return [];\n }\n\n /**\n * Dispatch an event until the first non-null response\n *\n * @param event - The event instance or name\n * @param payload - Additional arguments to pass\n * @returns The first non-null listener result\n */\n until(event: string | Event, payload: any[] = []): any {\n return this.dispatch(event, payload, true)[0] || null;\n }\n\n /**\n * Push an event onto the queue for later flushing\n *\n * @param event - The event name\n * @param payload - Additional arguments to pass\n */\n push(event: string, payload: any[] = []): void {\n // Not implemented for faked dispatcher\n }\n\n /**\n * Flush all queued events\n *\n * @param event - The event name to flush\n */\n flush(event: string): void {\n // Not implemented for faked dispatcher\n }\n\n /**\n * Remove all listeners for an event\n *\n * @param event - The event name\n */\n forget(event: string): void {\n this.dispatched.delete(event);\n this.listeners.delete(event);\n }\n\n /**\n * Clear all queued events\n */\n forgetPushed(): void {\n // Not implemented for faked dispatcher\n }\n\n /**\n * Get all registered listeners\n *\n * @returns Map of event names to listeners\n */\n getRawListeners(): Map<string, EventListener[]> {\n return this.listeners;\n }\n\n /**\n * Get the event name from an event instance or string\n *\n * @param event - The event instance or name\n * @returns The event name\n */\n protected getEventName(event: string | Event): string {\n if (typeof event === 'string') {\n return event;\n }\n return event.constructor.name;\n }\n\n // ==================== Assertion Methods ====================\n\n /**\n * Assert that an event was dispatched\n *\n * @param event - The event name or class\n * @param callback - Optional callback to filter events\n * @throws Error if the event was not dispatched\n *\n * @example\n * ```typescript\n * // Assert event was dispatched\n * fake.assertDispatched(UserRegistered);\n *\n * // Assert event with specific properties\n * fake.assertDispatched(UserRegistered, (event) => {\n * return event.user.email === 'test@example.com';\n * });\n * ```\n */\n assertDispatched(event: string | (new (...args: any[]) => Event), callback?: (event: Event) => boolean): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const events = this.dispatched.get(eventName) || [];\n\n if (events.length === 0) {\n const dispatchedList = Array.from(this.dispatched.keys()).join(', ') || 'none';\n throw new Error(`Event [${eventName}] was not dispatched.\\n` + `Dispatched events: ${dispatchedList}`);\n }\n\n if (callback) {\n const matching = events.filter((e) => {\n const eventObj = typeof e.event === 'string' ? null : e.event;\n return eventObj && callback(eventObj);\n });\n\n if (matching.length === 0) {\n throw new Error(\n `Event [${eventName}] was dispatched ${events.length} time(s) ` + `but none matched the provided callback.`\n );\n }\n }\n }\n\n /**\n * Assert that an event was NOT dispatched\n *\n * @param event - The event name or class\n * @param callback - Optional callback to filter events\n * @throws Error if the event was dispatched\n *\n * @example\n * ```typescript\n * // Assert event was not dispatched\n * fake.assertNotDispatched(UserDeleted);\n *\n * // Assert event with specific properties was not dispatched\n * fake.assertNotDispatched(UserRegistered, (event) => {\n * return event.user.email === 'admin@example.com';\n * });\n * ```\n */\n assertNotDispatched(event: string | (new (...args: any[]) => Event), callback?: (event: Event) => boolean): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const events = this.dispatched.get(eventName) || [];\n\n if (!callback) {\n if (events.length > 0) {\n throw new Error(`Event [${eventName}] was dispatched ${events.length} time(s).`);\n }\n return;\n }\n\n const matching = events.filter((e) => {\n const eventObj = typeof e.event === 'string' ? null : e.event;\n return eventObj && callback(eventObj);\n });\n\n if (matching.length > 0) {\n throw new Error(\n `Event [${eventName}] was unexpectedly dispatched ${matching.length} time(s) ` +\n `matching the provided callback.`\n );\n }\n }\n\n /**\n * Assert that an event was dispatched a specific number of times\n *\n * @param event - The event name or class\n * @param times - Expected number of dispatches\n * @throws Error if the dispatch count doesn't match\n *\n * @example\n * ```typescript\n * fake.assertDispatchedTimes(UserRegistered, 3);\n * ```\n */\n assertDispatchedTimes(event: string | (new (...args: any[]) => Event), times: number): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const events = this.dispatched.get(eventName) || [];\n\n if (events.length !== times) {\n throw new Error(`Event [${eventName}] was dispatched ${events.length} time(s) instead of ${times}.`);\n }\n }\n\n /**\n * Assert that no events were dispatched\n *\n * @throws Error if any events were dispatched\n *\n * @example\n * ```typescript\n * fake.assertNothingDispatched();\n * ```\n */\n assertNothingDispatched(): void {\n const totalEvents = Array.from(this.dispatched.values()).reduce((sum, events) => sum + events.length, 0);\n\n if (totalEvents > 0) {\n const dispatched = Array.from(this.dispatched.entries())\n .map(([name, events]) => ` ${name}: ${events.length}`)\n .join('\\n');\n\n throw new Error(`Expected no events to be dispatched, but ${totalEvents} were:\\n${dispatched}`);\n }\n }\n\n /**\n * Assert that a listener is registered for an event\n *\n * @param event - The event name or class\n * @param listener - Optional specific listener to check for\n * @throws Error if the listener is not registered\n *\n * @example\n * ```typescript\n * // Assert any listener is registered\n * fake.assertListening(UserRegistered);\n *\n * // Assert specific listener is registered\n * fake.assertListening(UserRegistered, SendWelcomeEmail);\n * ```\n */\n assertListening(event: string | (new (...args: any[]) => Event), listener?: string | EventListener): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const eventListeners = this.listeners.get(eventName) || [];\n\n if (eventListeners.length === 0) {\n throw new Error(`No listeners are registered for event [${eventName}].`);\n }\n\n if (listener) {\n const listenerName = typeof listener === 'string' ? listener : this.getListenerName(listener);\n const hasListener = eventListeners.some((l) => {\n const lName = typeof l === 'string' ? l : this.getListenerName(l);\n return lName === listenerName;\n });\n\n if (!hasListener) {\n throw new Error(`Listener [${listenerName}] is not registered for event [${eventName}].`);\n }\n }\n }\n\n /**\n * Get the name of a listener\n *\n * @param listener - The listener instance or closure\n * @returns The listener name\n */\n protected getListenerName(listener: EventListener): string {\n if (typeof listener === 'string') {\n return listener;\n }\n\n if (typeof listener === 'function') {\n return listener.name || '<Closure>';\n }\n\n if (typeof listener === 'object' && listener.constructor) {\n return listener.constructor.name;\n }\n\n return '<Unknown>';\n }\n\n // ==================== Helper Methods ====================\n\n /**\n * Get all dispatched events for a given event name\n *\n * @param event - The event name or class\n * @returns Array of dispatched event records\n *\n * @example\n * ```typescript\n * const dispatches = fake.getDispatched(UserRegistered);\n * expect(dispatches).toHaveLength(3);\n * ```\n */\n getDispatched(event: string | (new (...args: any[]) => Event)): DispatchedEvent[] {\n const eventName = typeof event === 'string' ? event : event.name;\n return this.dispatched.get(eventName) || [];\n }\n\n /**\n * Get all dispatched event names\n *\n * @returns Array of event names\n */\n getDispatchedEventNames(): string[] {\n return Array.from(this.dispatched.keys());\n }\n\n /**\n * Get the total count of dispatched events\n *\n * @returns Total number of event dispatches\n */\n getDispatchedCount(): number {\n return Array.from(this.dispatched.values()).reduce((sum, events) => sum + events.length, 0);\n }\n\n /**\n * Clear all recorded dispatches\n *\n * @example\n * ```typescript\n * // Clear between tests\n * fake.clear();\n * ```\n */\n clear(): void {\n this.dispatched.clear();\n }\n\n /**\n * Clear all registered listeners\n */\n clearListeners(): void {\n this.listeners.clear();\n }\n\n /**\n * Reset the fake completely\n */\n reset(): void {\n this.clear();\n this.clearListeners();\n this.fakedEvents = [];\n this.exceptedEvents = [];\n this.originalDispatcher = undefined;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EventFake.mjs","names":[],"sources":["../../../../src/Support/Testing/Fakes/EventFake.ts"],"sourcesContent":["import type { Container } from '@/Container/Container';\nimport type { DispatcherContract } from '@/Events/Contracts/Dispatcher';\nimport type { Event } from '@/Events/Event';\nimport type { EventListener } from '@/Events/types';\n\n/**\n * Dispatched Event Record\n *\n * Represents a single event dispatch with metadata\n */\ninterface DispatchedEvent {\n /** The event instance or event name */\n event: Event | string;\n\n /** Additional payload passed to the event */\n payload: any[];\n\n /** Timestamp when the event was dispatched */\n timestamp: number;\n}\n\n/**\n * Event Fake Dispatcher\n *\n * A test double for the event dispatcher that records all dispatched events\n * instead of actually calling listeners. Provides assertion methods to verify\n * event dispatching behavior in tests.\n *\n * @example\n * ```typescript\n * // Replace the real dispatcher with a fake\n * const fake = new EventFake(app);\n * app.instance('events', fake);\n *\n * // Test code that dispatches events\n * dispatch(new UserRegistered(user));\n *\n * // Assert the event was dispatched\n * fake.assertDispatched(UserRegistered);\n * fake.assertDispatchedTimes(UserRegistered, 1);\n * ```\n */\nexport class EventFake implements DispatcherContract {\n /**\n * All dispatched events organized by event name\n */\n protected dispatched: Map<string, DispatchedEvent[]> = new Map();\n\n /**\n * Events that should be faked (empty = fake all)\n */\n protected fakedEvents: string[] = [];\n\n /**\n * Events that should NOT be faked (all others will be)\n */\n protected exceptedEvents: string[] = [];\n\n /**\n * The original dispatcher (for pass-through)\n */\n protected originalDispatcher?: DispatcherContract;\n\n /**\n * Registered listeners (for assertListening)\n */\n protected listeners: Map<string, EventListener[]> = new Map();\n\n constructor(protected container: Container) {}\n\n /**\n * Set which events should be faked\n *\n * @param events - Array of event names or classes to fake\n */\n setFakedEvents(events: string[]): void {\n this.fakedEvents = events;\n }\n\n /**\n * Set which events should NOT be faked (all others will be)\n *\n * @param events - Array of event names or classes to NOT fake\n */\n setExceptedEvents(events: string[]): void {\n this.exceptedEvents = events;\n }\n\n /**\n * Set the original dispatcher for pass-through\n *\n * @param dispatcher - The real dispatcher instance\n */\n setOriginalDispatcher(dispatcher: DispatcherContract): void {\n this.originalDispatcher = dispatcher;\n }\n\n /**\n * Check if an event should be faked\n *\n * @param eventName - The event name to check\n * @returns true if the event should be faked\n */\n protected shouldFake(eventName: string): boolean {\n // If excepted events are set, fake everything except those\n if (this.exceptedEvents.length > 0) {\n return !this.exceptedEvents.includes(eventName);\n }\n\n // If faked events are set, only fake those\n if (this.fakedEvents.length > 0) {\n return this.fakedEvents.includes(eventName);\n }\n\n // Otherwise fake everything\n return true;\n }\n\n /**\n * Register an event listener\n *\n * @param events - Event name(s) to listen for\n * @param listener - The listener to call\n */\n listen(events: string | string[], listener: EventListener): void {\n // Store listeners for assertListening\n const eventArray = Array.isArray(events) ? events : [events];\n\n for (const event of eventArray) {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n\n this.listeners.get(event)!.push(listener);\n }\n }\n\n /**\n * Check if an event has listeners\n *\n * @param eventName - The event name to check\n * @returns true if the event has registered listeners\n */\n hasListeners(eventName: string): boolean {\n return this.listeners.has(eventName) && this.listeners.get(eventName)!.length > 0;\n }\n\n /**\n * Register an event subscriber\n *\n * @param subscriber - The subscriber instance\n */\n subscribe(subscriber: any): void {\n // Faked dispatcher doesn't actually register subscribers\n // but stores them for assertListening\n }\n\n /**\n * Dispatch an event\n *\n * @param event - The event instance or name\n * @param payload - Additional arguments to pass\n * @param halt - Stop on first non-null response\n * @returns Array of listener results\n */\n dispatch(event: string | Event, payload: any[] = [], halt: boolean = false): any[] {\n const eventName = this.getEventName(event);\n\n if (!this.shouldFake(eventName)) {\n // Pass through to original dispatcher if it exists\n if (this.originalDispatcher) {\n return this.originalDispatcher.dispatch(event, payload, halt);\n }\n return [];\n }\n\n // Record the dispatch\n if (!this.dispatched.has(eventName)) {\n this.dispatched.set(eventName, []);\n }\n\n this.dispatched.get(eventName)!.push({\n event,\n payload,\n timestamp: Date.now(),\n });\n\n return [];\n }\n\n /**\n * Dispatch an event until the first non-null response\n *\n * @param event - The event instance or name\n * @param payload - Additional arguments to pass\n * @returns The first non-null listener result\n */\n until(event: string | Event, payload: any[] = []): any {\n return this.dispatch(event, payload, true)[0] || null;\n }\n\n /**\n * Push an event onto the queue for later flushing\n *\n * @param event - The event name\n * @param payload - Additional arguments to pass\n */\n push(event: string, payload: any[] = []): void {\n // Not implemented for faked dispatcher\n }\n\n /**\n * Flush all queued events\n *\n * @param event - The event name to flush\n */\n flush(event: string): void {\n // Not implemented for faked dispatcher\n }\n\n /**\n * Remove all listeners for an event\n *\n * @param event - The event name\n */\n forget(event: string): void {\n this.dispatched.delete(event);\n this.listeners.delete(event);\n }\n\n /**\n * Clear all queued events\n */\n forgetPushed(): void {\n // Not implemented for faked dispatcher\n }\n\n /**\n * Get all registered listeners\n *\n * @returns Map of event names to listeners\n */\n getRawListeners(): Map<string, EventListener[]> {\n return this.listeners;\n }\n\n /**\n * Get the event name from an event instance or string\n *\n * @param event - The event instance or name\n * @returns The event name\n */\n protected getEventName(event: string | Event): string {\n if (typeof event === 'string') {\n return event;\n }\n return event.constructor.name;\n }\n\n // ==================== Assertion Methods ====================\n\n /**\n * Assert that an event was dispatched\n *\n * @param event - The event name or class\n * @param callback - Optional callback to filter events\n * @throws Error if the event was not dispatched\n *\n * @example\n * ```typescript\n * // Assert event was dispatched\n * fake.assertDispatched(UserRegistered);\n *\n * // Assert event with specific properties\n * fake.assertDispatched(UserRegistered, (event) => {\n * return event.user.email === 'test@example.com';\n * });\n * ```\n */\n assertDispatched(event: string | (new (...args: any[]) => Event), callback?: (event: Event) => boolean): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const events = this.dispatched.get(eventName) || [];\n\n if (events.length === 0) {\n const dispatchedList = Array.from(this.dispatched.keys()).join(', ') || 'none';\n throw new Error(`Event [${eventName}] was not dispatched.\\n` + `Dispatched events: ${dispatchedList}`);\n }\n\n if (callback) {\n const matching = events.filter((e) => {\n const eventObj = typeof e.event === 'string' ? null : e.event;\n return eventObj && callback(eventObj);\n });\n\n if (matching.length === 0) {\n throw new Error(\n `Event [${eventName}] was dispatched ${events.length} time(s) ` + `but none matched the provided callback.`\n );\n }\n }\n }\n\n /**\n * Assert that an event was NOT dispatched\n *\n * @param event - The event name or class\n * @param callback - Optional callback to filter events\n * @throws Error if the event was dispatched\n *\n * @example\n * ```typescript\n * // Assert event was not dispatched\n * fake.assertNotDispatched(UserDeleted);\n *\n * // Assert event with specific properties was not dispatched\n * fake.assertNotDispatched(UserRegistered, (event) => {\n * return event.user.email === 'admin@example.com';\n * });\n * ```\n */\n assertNotDispatched(event: string | (new (...args: any[]) => Event), callback?: (event: Event) => boolean): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const events = this.dispatched.get(eventName) || [];\n\n if (!callback) {\n if (events.length > 0) {\n throw new Error(`Event [${eventName}] was dispatched ${events.length} time(s).`);\n }\n return;\n }\n\n const matching = events.filter((e) => {\n const eventObj = typeof e.event === 'string' ? null : e.event;\n return eventObj && callback(eventObj);\n });\n\n if (matching.length > 0) {\n throw new Error(\n `Event [${eventName}] was unexpectedly dispatched ${matching.length} time(s) ` +\n `matching the provided callback.`\n );\n }\n }\n\n /**\n * Assert that an event was dispatched a specific number of times\n *\n * @param event - The event name or class\n * @param times - Expected number of dispatches\n * @throws Error if the dispatch count doesn't match\n *\n * @example\n * ```typescript\n * fake.assertDispatchedTimes(UserRegistered, 3);\n * ```\n */\n assertDispatchedTimes(event: string | (new (...args: any[]) => Event), times: number): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const events = this.dispatched.get(eventName) || [];\n\n if (events.length !== times) {\n throw new Error(`Event [${eventName}] was dispatched ${events.length} time(s) instead of ${times}.`);\n }\n }\n\n /**\n * Assert that no events were dispatched\n *\n * @throws Error if any events were dispatched\n *\n * @example\n * ```typescript\n * fake.assertNothingDispatched();\n * ```\n */\n assertNothingDispatched(): void {\n const totalEvents = Array.from(this.dispatched.values()).reduce((sum, events) => sum + events.length, 0);\n\n if (totalEvents > 0) {\n const dispatched = Array.from(this.dispatched.entries())\n .map(([name, events]) => ` ${name}: ${events.length}`)\n .join('\\n');\n\n throw new Error(`Expected no events to be dispatched, but ${totalEvents} were:\\n${dispatched}`);\n }\n }\n\n /**\n * Assert that a listener is registered for an event\n *\n * @param event - The event name or class\n * @param listener - Optional specific listener to check for\n * @throws Error if the listener is not registered\n *\n * @example\n * ```typescript\n * // Assert any listener is registered\n * fake.assertListening(UserRegistered);\n *\n * // Assert specific listener is registered\n * fake.assertListening(UserRegistered, SendWelcomeEmail);\n * ```\n */\n assertListening(event: string | (new (...args: any[]) => Event), listener?: string | EventListener): void {\n const eventName = typeof event === 'string' ? event : event.name;\n const eventListeners = this.listeners.get(eventName) || [];\n\n if (eventListeners.length === 0) {\n throw new Error(`No listeners are registered for event [${eventName}].`);\n }\n\n if (listener) {\n const listenerName = typeof listener === 'string' ? listener : this.getListenerName(listener);\n const hasListener = eventListeners.some((l) => {\n const lName = typeof l === 'string' ? l : this.getListenerName(l);\n return lName === listenerName;\n });\n\n if (!hasListener) {\n throw new Error(`Listener [${listenerName}] is not registered for event [${eventName}].`);\n }\n }\n }\n\n /**\n * Get the name of a listener\n *\n * @param listener - The listener instance or closure\n * @returns The listener name\n */\n protected getListenerName(listener: EventListener): string {\n if (typeof listener === 'string') {\n return listener;\n }\n\n if (typeof listener === 'function') {\n return listener.name || '<Closure>';\n }\n\n if (typeof listener === 'object' && listener.constructor) {\n return listener.constructor.name;\n }\n\n return '<Unknown>';\n }\n\n // ==================== Helper Methods ====================\n\n /**\n * Get all dispatched events for a given event name\n *\n * @param event - The event name or class\n * @returns Array of dispatched event records\n *\n * @example\n * ```typescript\n * const dispatches = fake.getDispatched(UserRegistered);\n * expect(dispatches).toHaveLength(3);\n * ```\n */\n getDispatched(event: string | (new (...args: any[]) => Event)): DispatchedEvent[] {\n const eventName = typeof event === 'string' ? event : event.name;\n return this.dispatched.get(eventName) || [];\n }\n\n /**\n * Get all dispatched event names\n *\n * @returns Array of event names\n */\n getDispatchedEventNames(): string[] {\n return Array.from(this.dispatched.keys());\n }\n\n /**\n * Get the total count of dispatched events\n *\n * @returns Total number of event dispatches\n */\n getDispatchedCount(): number {\n return Array.from(this.dispatched.values()).reduce((sum, events) => sum + events.length, 0);\n }\n\n /**\n * Clear all recorded dispatches\n *\n * @example\n * ```typescript\n * // Clear between tests\n * fake.clear();\n * ```\n */\n clear(): void {\n this.dispatched.clear();\n }\n\n /**\n * Clear all registered listeners\n */\n clearListeners(): void {\n this.listeners.clear();\n }\n\n /**\n * Reset the fake completely\n */\n reset(): void {\n this.clear();\n this.clearListeners();\n this.fakedEvents = [];\n this.exceptedEvents = [];\n this.originalDispatcher = undefined;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/Support/helpers.ts"],"sourcesContent":["/**\n * Helper functions - Laravel-style global helpers\n */\n\nimport type { Config as ConfigClass } from '@/Foundation/Config/Config';\nimport type { View } from '@/View/View';\nimport type { ViewFactory } from '@/View/ViewFactory';\nimport { Application } from '@/Foundation/Application';\n\n// Global application instance for helpers\nlet globalApp: Application | null = null;\n\n/**\n * Set the global application instance\n * @internal\n */\nexport function setGlobalApp(app: Application): void {\n globalApp = app;\n}\n\n/**\n * Get the global application instance\n * @internal\n */\nexport function getGlobalApp(): Application | null {\n return globalApp;\n}\n\n/**\n * Get a configuration value\n * Laravel: config('app.name') or config('app.name', 'default')\n *\n * @param {string} key\n * @param {any} defaultValue\n * @returns {any}\n */\nexport function config<T = any>(key?: string, defaultValue?: T): T | ConfigClass {\n if (!globalApp) {\n throw new Error('Application not initialized. Call setGlobalApp() first.');\n }\n\n const configInstance = globalApp.make<ConfigClass>('config');\n\n // If no key provided, return the config instance\n if (key === undefined) {\n return configInstance as any;\n }\n\n return configInstance.get(key, defaultValue);\n}\n\n/**\n * Load routes from a directory\n * This helper allows you to dynamically import route files\n */\nexport async function loadRoutes(routePath: string): Promise<void> {\n try {\n await import(routePath);\n } catch (error) {\n throw new Error(`Failed to load routes from ${routePath}: ${error}`, { cause: error });\n }\n}\n\n/**\n * Resolve a path relative to the base path\n */\nexport function base_path(path: string = ''): string {\n return `${process.cwd()}${path ? '/' + path : ''}`;\n}\n\n/**\n * Get the routes directory path\n */\nexport function routes_path(path: string = ''): string {\n return base_path(`routes${path ? '/' + path : ''}`);\n}\n\n/**\n * Get the resources/views directory path\n */\nexport function resource_path(path: string = ''): string {\n return base_path(`resources${path ? '/' + path : ''}`);\n}\n\n/**\n * Create a new view instance.\n * Laravel: view('welcome', ['name' => 'John'])\n *\n * @param {string} name - Dot-notation view name (e.g. 'welcome', 'layouts.app')\n * @param {Record<string, any>} data - Data to pass to the view\n * @returns {View}\n */\nexport function view(name: string, data: Record<string, any> = {}): View {\n if (!globalApp) {\n throw new Error('Application not initialized. Call setGlobalApp() first.');\n }\n\n const factory = globalApp.make<ViewFactory>('view');\n return factory.make(name, data);\n}\n"],"mappings":"AAUA,IAAI,EAAgC,KAMpC,SAAgB,EAAa,EAAwB,CACnD,EAAY,EAOd,SAAgB,GAAmC,CACjD,OAAO,EAWT,SAAgB,EAAgB,EAAc,EAAmC,CAC/E,GAAI,CAAC,EACH,MAAU,MAAM,0DAA0D,CAG5E,IAAM,EAAiB,EAAU,KAAkB,SAAS,CAO5D,OAJI,IAAQ,IAAA,GACH,EAGF,EAAe,IAAI,EAAK,EAAa,CAO9C,eAAsB,EAAW,EAAkC,CACjE,GAAI,CACF,MAAM,OAAO,SACN,EAAO,CACd,MAAU,MAAM,8BAA8B,EAAU,IAAI,IAAS,CAAE,MAAO,EAAO,CAAC,EAO1F,SAAgB,EAAU,EAAe,GAAY,CACnD,MAAO,GAAG,QAAQ,KAAK,GAAG,EAAO,IAAM,EAAO,KAMhD,SAAgB,EAAY,EAAe,GAAY,CACrD,OAAO,EAAU,SAAS,EAAO,IAAM,EAAO,KAAK,CAMrD,SAAgB,EAAc,EAAe,GAAY,CACvD,OAAO,EAAU,YAAY,EAAO,IAAM,EAAO,KAAK,CAWxD,SAAgB,EAAK,EAAc,EAA4B,EAAE,CAAQ,CACvE,GAAI,CAAC,EACH,MAAU,MAAM,0DAA0D,CAI5E,OADgB,EAAU,KAAkB,
|
|
1
|
+
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/Support/helpers.ts"],"sourcesContent":["/**\n * Helper functions - Laravel-style global helpers\n */\n\nimport type { Config as ConfigClass } from '@/Foundation/Config/Config';\nimport type { View } from '@/View/View';\nimport type { ViewFactory } from '@/View/ViewFactory';\nimport { Application } from '@/Foundation/Application';\n\n// Global application instance for helpers\nlet globalApp: Application | null = null;\n\n/**\n * Set the global application instance\n * @internal\n */\nexport function setGlobalApp(app: Application): void {\n globalApp = app;\n}\n\n/**\n * Get the global application instance\n * @internal\n */\nexport function getGlobalApp(): Application | null {\n return globalApp;\n}\n\n/**\n * Get a configuration value\n * Laravel: config('app.name') or config('app.name', 'default')\n *\n * @param {string} key\n * @param {any} defaultValue\n * @returns {any}\n */\nexport function config<T = any>(key?: string, defaultValue?: T): T | ConfigClass {\n if (!globalApp) {\n throw new Error('Application not initialized. Call setGlobalApp() first.');\n }\n\n const configInstance = globalApp.make<ConfigClass>('config');\n\n // If no key provided, return the config instance\n if (key === undefined) {\n return configInstance as any;\n }\n\n return configInstance.get(key, defaultValue);\n}\n\n/**\n * Load routes from a directory\n * This helper allows you to dynamically import route files\n */\nexport async function loadRoutes(routePath: string): Promise<void> {\n try {\n await import(routePath);\n } catch (error) {\n throw new Error(`Failed to load routes from ${routePath}: ${error}`, { cause: error });\n }\n}\n\n/**\n * Resolve a path relative to the base path\n */\nexport function base_path(path: string = ''): string {\n return `${process.cwd()}${path ? '/' + path : ''}`;\n}\n\n/**\n * Get the routes directory path\n */\nexport function routes_path(path: string = ''): string {\n return base_path(`routes${path ? '/' + path : ''}`);\n}\n\n/**\n * Get the resources/views directory path\n */\nexport function resource_path(path: string = ''): string {\n return base_path(`resources${path ? '/' + path : ''}`);\n}\n\n/**\n * Create a new view instance.\n * Laravel: view('welcome', ['name' => 'John'])\n *\n * @param {string} name - Dot-notation view name (e.g. 'welcome', 'layouts.app')\n * @param {Record<string, any>} data - Data to pass to the view\n * @returns {View}\n */\nexport function view(name: string, data: Record<string, any> = {}): View {\n if (!globalApp) {\n throw new Error('Application not initialized. Call setGlobalApp() first.');\n }\n\n const factory = globalApp.make<ViewFactory>('view');\n return factory.make(name, data);\n}\n"],"mappings":"AAUA,IAAI,EAAgC,KAMpC,SAAgB,EAAa,EAAwB,CACnD,EAAY,EAOd,SAAgB,GAAmC,CACjD,OAAO,EAWT,SAAgB,EAAgB,EAAc,EAAmC,CAC/E,GAAI,CAAC,EACH,MAAU,MAAM,0DAA0D,CAG5E,IAAM,EAAiB,EAAU,KAAkB,SAAS,CAO5D,OAJI,IAAQ,IAAA,GACH,EAGF,EAAe,IAAI,EAAK,EAAa,CAO9C,eAAsB,EAAW,EAAkC,CACjE,GAAI,CACF,MAAM,OAAO,SACN,EAAO,CACd,MAAU,MAAM,8BAA8B,EAAU,IAAI,IAAS,CAAE,MAAO,EAAO,CAAC,EAO1F,SAAgB,EAAU,EAAe,GAAY,CACnD,MAAO,GAAG,QAAQ,KAAK,GAAG,EAAO,IAAM,EAAO,KAMhD,SAAgB,EAAY,EAAe,GAAY,CACrD,OAAO,EAAU,SAAS,EAAO,IAAM,EAAO,KAAK,CAMrD,SAAgB,EAAc,EAAe,GAAY,CACvD,OAAO,EAAU,YAAY,EAAO,IAAM,EAAO,KAAK,CAWxD,SAAgB,EAAK,EAAc,EAA4B,EAAE,CAAQ,CACvE,GAAI,CAAC,EACH,MAAU,MAAM,0DAA0D,CAI5E,OADgB,EAAU,KAAkB,OAC9B,CAAC,KAAK,EAAM,EAAK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TemplateEngine.mjs","names":[],"sources":["../../../src/View/Engines/TemplateEngine.ts"],"sourcesContent":["/**\n * TemplateEngine - Simple template engine\n *\n * Supports a subset of Blade-like directives:\n * - {{ variable }} HTML-escaped output\n * - {!! variable !!} Raw/unescaped output\n * - @if / @elseif / @else / @endif\n * - @foreach / @endforeach\n * - @include('partial', data?)\n * - @extends('layout') + @section('name') ... @endsection + @yield('name')\n *\n * Mirrors Laravel's Illuminate\\View\\Engines\\CompilerEngine (simplified)\n */\n\nimport { readFileSync } from 'fs';\nimport type { ViewEngine } from './ViewEngine';\n\n/**\n * Dependency injection shim so the engine can resolve included views\n * without creating a circular import with ViewFactory.\n */\nexport interface TemplateEngineResolver {\n findView(name: string): string;\n}\n\nexport class TemplateEngine implements ViewEngine {\n protected resolver: TemplateEngineResolver | null = null;\n\n /**\n * Set the resolver used for @include / @extends directives.\n */\n setResolver(resolver: TemplateEngineResolver): void {\n this.resolver = resolver;\n }\n\n /**\n * Get the evaluated contents of the view.\n */\n async get(path: string, data: Record<string, any>): Promise<string> {\n const source = readFileSync(path, 'utf8');\n return this.compile(source, data);\n }\n\n /**\n * Compile a template string with the given data.\n *\n * Processing order:\n * 1. @extends / @section / @yield (layout assembly)\n * 2. @include (async file inclusion)\n * 3. @if / @elseif / @else / @endif (synchronous)\n * 4. @foreach / @endforeach (synchronous)\n * 5. {{ expr }} HTML-escaped\n * 6. {!! expr !!} raw\n */\n async compile(source: string, data: Record<string, any>): Promise<string> {\n // --- Step 1: Layout inheritance (@extends / @section / @yield) ---\n const extendsMatch = source.match(/@extends\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/);\n\n let assembled: string;\n if (extendsMatch) {\n assembled = await this.compileWithLayout(source, extendsMatch[1], data);\n } else {\n assembled = source;\n }\n\n // --- Step 2: Expand all @include directives (async, reads files) ---\n const withIncludes = await this.processIncludes(assembled, data);\n\n // --- Steps 3-6: Synchronous directive processing ---\n return this.processSync(withIncludes, data);\n }\n\n /**\n * Synchronous directive processing: @if, @foreach, {{ }}, {!! !!}.\n * Called both at the top level and recursively from @foreach bodies.\n */\n private processSync(source: string, data: Record<string, any>): string {\n let output = source;\n output = this.processIf(output, data);\n output = this.processForeach(output, data);\n output = this.processEscapedOutput(output, data);\n output = this.processRawOutput(output, data);\n return output;\n }\n\n /**\n * Compile a child template that extends a layout.\n */\n private async compileWithLayout(childSource: string, layoutName: string, data: Record<string, any>): Promise<string> {\n // Extract all @section ... @endsection blocks from the child\n const sections = this.extractSections(childSource);\n\n // Load the layout template\n const layoutPath = this.resolveName(layoutName);\n const layoutSource = readFileSync(layoutPath, 'utf8');\n\n // Replace @yield('name') / @yield('name', 'default') in the layout\n const output = layoutSource.replace(\n /@yield\\(\\s*['\"]([^'\"]+)['\"]\\s*(?:,\\s*['\"]([^'\"]*)['\"]\\s*)?\\)/g,\n (_match: string, sectionName: string, defaultValue: string = '') => {\n return sections[sectionName] !== undefined ? sections[sectionName] : defaultValue;\n }\n );\n\n return output;\n }\n\n /**\n * Extract @section('name') ... @endsection blocks from a template string.\n */\n private extractSections(source: string): Record<string, string> {\n const sections: Record<string, string> = {};\n const sectionRegex = /@section\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)([\\s\\S]*?)@endsection/g;\n let match: RegExpExecArray | null;\n\n while ((match = sectionRegex.exec(source)) !== null) {\n sections[match[1]] = match[2].trim();\n }\n\n return sections;\n }\n\n /**\n * Process @include('view', data?) directives.\n * Each included view is fully compiled (recursively) before insertion.\n */\n private async processIncludes(source: string, data: Record<string, any>): Promise<string> {\n const includeRegex = /@include\\(\\s*['\"]([^'\"]+)['\"]\\s*(?:,\\s*(\\{[^}]*\\}))?\\s*\\)/g;\n const matches: Array<{ full: string; name: string; extraData: string | undefined }> = [];\n\n let match: RegExpExecArray | null;\n while ((match = includeRegex.exec(source)) !== null) {\n matches.push({ full: match[0], name: match[1], extraData: match[2] });\n }\n\n let output = source;\n\n for (const { full, name, extraData } of matches) {\n let includeData = { ...data };\n\n if (extraData) {\n try {\n const parsed = this.parseSimpleObject(extraData);\n includeData = { ...includeData, ...parsed };\n } catch {\n // Ignore malformed extra data\n }\n }\n\n const includePath = this.resolveName(name);\n const includeSource = readFileSync(includePath, 'utf8');\n // Fully compile the included template (handles its own @extends / @include)\n const rendered = await this.compile(includeSource, includeData);\n output = output.replace(full, rendered);\n }\n\n return output;\n }\n\n /**\n * Process @if(condition) ... @elseif(condition) ... @else ... @endif blocks.\n *\n * Uses a character-scanning approach to handle nested @if correctly.\n * Recurses until no more @if directives remain.\n */\n private processIf(source: string, data: Record<string, any>): string {\n const ifStart = source.indexOf('@if(');\n if (ifStart === -1) {\n return source;\n }\n\n // Extract the main @if condition (handles nested parentheses)\n const condStart = ifStart + 4;\n let depth = 1;\n let condEnd = condStart;\n while (condEnd < source.length && depth > 0) {\n if (source[condEnd] === '(') depth++;\n else if (source[condEnd] === ')') depth--;\n if (depth > 0) condEnd++;\n }\n const mainCondition = source.slice(condStart, condEnd);\n\n // Scan for @elseif / @else / @endif at nesting level 1\n let nesting = 1;\n let pos = condEnd + 1;\n\n // Each segment: { condition: string (main/elseif) | 'else' | 'if-body', body: string }\n type Segment = { condition: string; body: string };\n const segments: Segment[] = [];\n let segmentStart = condEnd + 1;\n // The first segment uses the main condition\n let pendingCondition: string = mainCondition;\n\n while (pos < source.length && nesting > 0) {\n if (source.startsWith('@if(', pos)) {\n nesting++;\n pos += 4;\n } else if (source.startsWith('@endif', pos) && nesting === 1) {\n // Close the last segment\n segments.push({ condition: pendingCondition, body: source.slice(segmentStart, pos) });\n pos += 6;\n break;\n } else if (source.startsWith('@endif', pos)) {\n nesting--;\n pos++;\n } else if (nesting === 1 && source.startsWith('@elseif(', pos)) {\n // Close current segment\n segments.push({ condition: pendingCondition, body: source.slice(segmentStart, pos) });\n\n // Extract the @elseif condition\n const elseifCondStart = pos + 8;\n let elseifDepth = 1;\n let elseifCondEnd = elseifCondStart;\n while (elseifCondEnd < source.length && elseifDepth > 0) {\n if (source[elseifCondEnd] === '(') elseifDepth++;\n else if (source[elseifCondEnd] === ')') elseifDepth--;\n if (elseifDepth > 0) elseifCondEnd++;\n }\n pendingCondition = source.slice(elseifCondStart, elseifCondEnd);\n segmentStart = elseifCondEnd + 1;\n pos = elseifCondEnd + 1;\n } else if (nesting === 1 && source.startsWith('@else', pos) && !source.startsWith('@elseif(', pos)) {\n // Close current segment\n segments.push({ condition: pendingCondition, body: source.slice(segmentStart, pos) });\n pendingCondition = '__else__';\n segmentStart = pos + 5;\n pos += 5;\n } else {\n pos++;\n }\n }\n\n // Evaluate the segments to find the first truthy one\n let replacement = '';\n for (const seg of segments) {\n if (seg.condition === '__else__') {\n replacement = seg.body;\n break;\n }\n try {\n if (this.evaluate(seg.condition, data)) {\n replacement = seg.body;\n break;\n }\n } catch {\n // Evaluation error — treat as falsy\n }\n }\n\n const before = source.slice(0, ifStart);\n const after = source.slice(pos);\n const result = before + replacement + after;\n\n // Recurse to process remaining @if blocks\n return this.processIf(result, data);\n }\n\n /**\n * Process @foreach(items as item) ... @endforeach blocks.\n * Each iteration body is processed synchronously (includes already expanded).\n */\n private processForeach(source: string, data: Record<string, any>): string {\n const foreachRegex = /@foreach\\(([^)]+)\\s+as\\s+(\\w+)(?:\\s*,\\s*(\\w+))?\\)([\\s\\S]*?)@endforeach/g;\n\n return source.replace(\n foreachRegex,\n (_match: string, expr: string, valueVar: string, keyVar: string | undefined, body: string) => {\n let items: any;\n try {\n items = this.evaluate(expr.trim(), data);\n } catch {\n return '';\n }\n\n if (!items || typeof items !== 'object') {\n return '';\n }\n\n const entries: Array<[any, any]> = Array.isArray(items)\n ? items.map((v: any, i: number) => [i, v])\n : Object.entries(items);\n\n return entries\n .map(([key, value]) => {\n const loopData: Record<string, any> = { ...data, [valueVar]: value };\n if (keyVar) {\n loopData[keyVar] = key;\n }\n // Use synchronous processing — includes are already expanded\n return this.processSync(body, loopData);\n })\n .join('');\n }\n );\n }\n\n /**\n * Process {{ expression }} — HTML-escaped output.\n */\n private processEscapedOutput(source: string, data: Record<string, any>): string {\n return source.replace(/\\{\\{\\s*([\\s\\S]+?)\\s*\\}\\}/g, (_match: string, expr: string) => {\n try {\n const value = this.evaluate(expr.trim(), data);\n return this.escapeHtml(String(value ?? ''));\n } catch {\n return '';\n }\n });\n }\n\n /**\n * Process {!! expression !!} — raw/unescaped output.\n */\n private processRawOutput(source: string, data: Record<string, any>): string {\n return source.replace(/\\{!!\\s*([\\s\\S]+?)\\s*!!\\}/g, (_match: string, expr: string) => {\n try {\n const value = this.evaluate(expr.trim(), data);\n return String(value ?? '');\n } catch {\n return '';\n }\n });\n }\n\n /**\n * Safely evaluate a JavaScript expression in the context of the given data.\n */\n private evaluate(expr: string, data: Record<string, any>): any {\n const keys = Object.keys(data);\n const values = Object.values(data);\n\n const fn = new Function(...keys, `return (${expr});`);\n return fn(...values);\n }\n\n /**\n * Escape HTML special characters to prevent XSS.\n */\n private escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n /**\n * Resolve a dot-notation view name to an absolute file path via the resolver.\n */\n private resolveName(name: string): string {\n if (!this.resolver) {\n throw new Error(\n `TemplateEngine: No resolver set. Cannot resolve view \"${name}\". ` +\n `Call setResolver() before using @include or @extends.`\n );\n }\n return this.resolver.findView(name);\n }\n\n /**\n * Parse a simple JSON-like object literal.\n * Handles: { key: 'str' }, { key: \"str\" }, { key: true/false }, { key: 123 }\n */\n private parseSimpleObject(str: string): Record<string, any> {\n const inner = str.trim().replace(/^\\{/, '').replace(/\\}$/, '').trim();\n const result: Record<string, any> = {};\n const pairRegex = /(\\w+)\\s*:\\s*(?:'([^']*)'|\"([^\"]*)\"|(true|false|\\d+(?:\\.\\d+)?))/g;\n let match: RegExpExecArray | null;\n\n while ((match = pairRegex.exec(inner)) !== null) {\n const key = match[1];\n const strVal = match[2] ?? match[3];\n const primitiveVal = match[4];\n\n if (strVal !== undefined) {\n result[key] = strVal;\n } else if (primitiveVal === 'true') {\n result[key] = true;\n } else if (primitiveVal === 'false') {\n result[key] = false;\n } else if (primitiveVal !== undefined) {\n result[key] = Number(primitiveVal);\n }\n }\n\n return result;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TemplateEngine.mjs","names":[],"sources":["../../../src/View/Engines/TemplateEngine.ts"],"sourcesContent":["/**\n * TemplateEngine - Simple template engine\n *\n * Supports a subset of Blade-like directives:\n * - {{ variable }} HTML-escaped output\n * - {!! variable !!} Raw/unescaped output\n * - @if / @elseif / @else / @endif\n * - @foreach / @endforeach\n * - @include('partial', data?)\n * - @extends('layout') + @section('name') ... @endsection + @yield('name')\n *\n * Mirrors Laravel's Illuminate\\View\\Engines\\CompilerEngine (simplified)\n */\n\nimport { readFileSync } from 'fs';\nimport type { ViewEngine } from './ViewEngine';\n\n/**\n * Dependency injection shim so the engine can resolve included views\n * without creating a circular import with ViewFactory.\n */\nexport interface TemplateEngineResolver {\n findView(name: string): string;\n}\n\nexport class TemplateEngine implements ViewEngine {\n protected resolver: TemplateEngineResolver | null = null;\n\n /**\n * Set the resolver used for @include / @extends directives.\n */\n setResolver(resolver: TemplateEngineResolver): void {\n this.resolver = resolver;\n }\n\n /**\n * Get the evaluated contents of the view.\n */\n async get(path: string, data: Record<string, any>): Promise<string> {\n const source = readFileSync(path, 'utf8');\n return this.compile(source, data);\n }\n\n /**\n * Compile a template string with the given data.\n *\n * Processing order:\n * 1. @extends / @section / @yield (layout assembly)\n * 2. @include (async file inclusion)\n * 3. @if / @elseif / @else / @endif (synchronous)\n * 4. @foreach / @endforeach (synchronous)\n * 5. {{ expr }} HTML-escaped\n * 6. {!! expr !!} raw\n */\n async compile(source: string, data: Record<string, any>): Promise<string> {\n // --- Step 1: Layout inheritance (@extends / @section / @yield) ---\n const extendsMatch = source.match(/@extends\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/);\n\n let assembled: string;\n if (extendsMatch) {\n assembled = await this.compileWithLayout(source, extendsMatch[1], data);\n } else {\n assembled = source;\n }\n\n // --- Step 2: Expand all @include directives (async, reads files) ---\n const withIncludes = await this.processIncludes(assembled, data);\n\n // --- Steps 3-6: Synchronous directive processing ---\n return this.processSync(withIncludes, data);\n }\n\n /**\n * Synchronous directive processing: @if, @foreach, {{ }}, {!! !!}.\n * Called both at the top level and recursively from @foreach bodies.\n */\n private processSync(source: string, data: Record<string, any>): string {\n let output = source;\n output = this.processIf(output, data);\n output = this.processForeach(output, data);\n output = this.processEscapedOutput(output, data);\n output = this.processRawOutput(output, data);\n return output;\n }\n\n /**\n * Compile a child template that extends a layout.\n */\n private async compileWithLayout(childSource: string, layoutName: string, data: Record<string, any>): Promise<string> {\n // Extract all @section ... @endsection blocks from the child\n const sections = this.extractSections(childSource);\n\n // Load the layout template\n const layoutPath = this.resolveName(layoutName);\n const layoutSource = readFileSync(layoutPath, 'utf8');\n\n // Replace @yield('name') / @yield('name', 'default') in the layout\n const output = layoutSource.replace(\n /@yield\\(\\s*['\"]([^'\"]+)['\"]\\s*(?:,\\s*['\"]([^'\"]*)['\"]\\s*)?\\)/g,\n (_match: string, sectionName: string, defaultValue: string = '') => {\n return sections[sectionName] !== undefined ? sections[sectionName] : defaultValue;\n }\n );\n\n return output;\n }\n\n /**\n * Extract @section('name') ... @endsection blocks from a template string.\n */\n private extractSections(source: string): Record<string, string> {\n const sections: Record<string, string> = {};\n const sectionRegex = /@section\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)([\\s\\S]*?)@endsection/g;\n let match: RegExpExecArray | null;\n\n while ((match = sectionRegex.exec(source)) !== null) {\n sections[match[1]] = match[2].trim();\n }\n\n return sections;\n }\n\n /**\n * Process @include('view', data?) directives.\n * Each included view is fully compiled (recursively) before insertion.\n */\n private async processIncludes(source: string, data: Record<string, any>): Promise<string> {\n const includeRegex = /@include\\(\\s*['\"]([^'\"]+)['\"]\\s*(?:,\\s*(\\{[^}]*\\}))?\\s*\\)/g;\n const matches: Array<{ full: string; name: string; extraData: string | undefined }> = [];\n\n let match: RegExpExecArray | null;\n while ((match = includeRegex.exec(source)) !== null) {\n matches.push({ full: match[0], name: match[1], extraData: match[2] });\n }\n\n let output = source;\n\n for (const { full, name, extraData } of matches) {\n let includeData = { ...data };\n\n if (extraData) {\n try {\n const parsed = this.parseSimpleObject(extraData);\n includeData = { ...includeData, ...parsed };\n } catch {\n // Ignore malformed extra data\n }\n }\n\n const includePath = this.resolveName(name);\n const includeSource = readFileSync(includePath, 'utf8');\n // Fully compile the included template (handles its own @extends / @include)\n const rendered = await this.compile(includeSource, includeData);\n output = output.replace(full, rendered);\n }\n\n return output;\n }\n\n /**\n * Process @if(condition) ... @elseif(condition) ... @else ... @endif blocks.\n *\n * Uses a character-scanning approach to handle nested @if correctly.\n * Recurses until no more @if directives remain.\n */\n private processIf(source: string, data: Record<string, any>): string {\n const ifStart = source.indexOf('@if(');\n if (ifStart === -1) {\n return source;\n }\n\n // Extract the main @if condition (handles nested parentheses)\n const condStart = ifStart + 4;\n let depth = 1;\n let condEnd = condStart;\n while (condEnd < source.length && depth > 0) {\n if (source[condEnd] === '(') depth++;\n else if (source[condEnd] === ')') depth--;\n if (depth > 0) condEnd++;\n }\n const mainCondition = source.slice(condStart, condEnd);\n\n // Scan for @elseif / @else / @endif at nesting level 1\n let nesting = 1;\n let pos = condEnd + 1;\n\n // Each segment: { condition: string (main/elseif) | 'else' | 'if-body', body: string }\n type Segment = { condition: string; body: string };\n const segments: Segment[] = [];\n let segmentStart = condEnd + 1;\n // The first segment uses the main condition\n let pendingCondition: string = mainCondition;\n\n while (pos < source.length && nesting > 0) {\n if (source.startsWith('@if(', pos)) {\n nesting++;\n pos += 4;\n } else if (source.startsWith('@endif', pos) && nesting === 1) {\n // Close the last segment\n segments.push({ condition: pendingCondition, body: source.slice(segmentStart, pos) });\n pos += 6;\n break;\n } else if (source.startsWith('@endif', pos)) {\n nesting--;\n pos++;\n } else if (nesting === 1 && source.startsWith('@elseif(', pos)) {\n // Close current segment\n segments.push({ condition: pendingCondition, body: source.slice(segmentStart, pos) });\n\n // Extract the @elseif condition\n const elseifCondStart = pos + 8;\n let elseifDepth = 1;\n let elseifCondEnd = elseifCondStart;\n while (elseifCondEnd < source.length && elseifDepth > 0) {\n if (source[elseifCondEnd] === '(') elseifDepth++;\n else if (source[elseifCondEnd] === ')') elseifDepth--;\n if (elseifDepth > 0) elseifCondEnd++;\n }\n pendingCondition = source.slice(elseifCondStart, elseifCondEnd);\n segmentStart = elseifCondEnd + 1;\n pos = elseifCondEnd + 1;\n } else if (nesting === 1 && source.startsWith('@else', pos) && !source.startsWith('@elseif(', pos)) {\n // Close current segment\n segments.push({ condition: pendingCondition, body: source.slice(segmentStart, pos) });\n pendingCondition = '__else__';\n segmentStart = pos + 5;\n pos += 5;\n } else {\n pos++;\n }\n }\n\n // Evaluate the segments to find the first truthy one\n let replacement = '';\n for (const seg of segments) {\n if (seg.condition === '__else__') {\n replacement = seg.body;\n break;\n }\n try {\n if (this.evaluate(seg.condition, data)) {\n replacement = seg.body;\n break;\n }\n } catch {\n // Evaluation error — treat as falsy\n }\n }\n\n const before = source.slice(0, ifStart);\n const after = source.slice(pos);\n const result = before + replacement + after;\n\n // Recurse to process remaining @if blocks\n return this.processIf(result, data);\n }\n\n /**\n * Process @foreach(items as item) ... @endforeach blocks.\n * Each iteration body is processed synchronously (includes already expanded).\n */\n private processForeach(source: string, data: Record<string, any>): string {\n const foreachRegex = /@foreach\\(([^)]+)\\s+as\\s+(\\w+)(?:\\s*,\\s*(\\w+))?\\)([\\s\\S]*?)@endforeach/g;\n\n return source.replace(\n foreachRegex,\n (_match: string, expr: string, valueVar: string, keyVar: string | undefined, body: string) => {\n let items: any;\n try {\n items = this.evaluate(expr.trim(), data);\n } catch {\n return '';\n }\n\n if (!items || typeof items !== 'object') {\n return '';\n }\n\n const entries: Array<[any, any]> = Array.isArray(items)\n ? items.map((v: any, i: number) => [i, v])\n : Object.entries(items);\n\n return entries\n .map(([key, value]) => {\n const loopData: Record<string, any> = { ...data, [valueVar]: value };\n if (keyVar) {\n loopData[keyVar] = key;\n }\n // Use synchronous processing — includes are already expanded\n return this.processSync(body, loopData);\n })\n .join('');\n }\n );\n }\n\n /**\n * Process {{ expression }} — HTML-escaped output.\n */\n private processEscapedOutput(source: string, data: Record<string, any>): string {\n return source.replace(/\\{\\{\\s*([\\s\\S]+?)\\s*\\}\\}/g, (_match: string, expr: string) => {\n try {\n const value = this.evaluate(expr.trim(), data);\n return this.escapeHtml(String(value ?? ''));\n } catch {\n return '';\n }\n });\n }\n\n /**\n * Process {!! expression !!} — raw/unescaped output.\n */\n private processRawOutput(source: string, data: Record<string, any>): string {\n return source.replace(/\\{!!\\s*([\\s\\S]+?)\\s*!!\\}/g, (_match: string, expr: string) => {\n try {\n const value = this.evaluate(expr.trim(), data);\n return String(value ?? '');\n } catch {\n return '';\n }\n });\n }\n\n /**\n * Safely evaluate a JavaScript expression in the context of the given data.\n */\n private evaluate(expr: string, data: Record<string, any>): any {\n const keys = Object.keys(data);\n const values = Object.values(data);\n\n const fn = new Function(...keys, `return (${expr});`);\n return fn(...values);\n }\n\n /**\n * Escape HTML special characters to prevent XSS.\n */\n private escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n /**\n * Resolve a dot-notation view name to an absolute file path via the resolver.\n */\n private resolveName(name: string): string {\n if (!this.resolver) {\n throw new Error(\n `TemplateEngine: No resolver set. Cannot resolve view \"${name}\". ` +\n `Call setResolver() before using @include or @extends.`\n );\n }\n return this.resolver.findView(name);\n }\n\n /**\n * Parse a simple JSON-like object literal.\n * Handles: { key: 'str' }, { key: \"str\" }, { key: true/false }, { key: 123 }\n */\n private parseSimpleObject(str: string): Record<string, any> {\n const inner = str.trim().replace(/^\\{/, '').replace(/\\}$/, '').trim();\n const result: Record<string, any> = {};\n const pairRegex = /(\\w+)\\s*:\\s*(?:'([^']*)'|\"([^\"]*)\"|(true|false|\\d+(?:\\.\\d+)?))/g;\n let match: RegExpExecArray | null;\n\n while ((match = pairRegex.exec(inner)) !== null) {\n const key = match[1];\n const strVal = match[2] ?? match[3];\n const primitiveVal = match[4];\n\n if (strVal !== undefined) {\n result[key] = strVal;\n } else if (primitiveVal === 'true') {\n result[key] = true;\n } else if (primitiveVal === 'false') {\n result[key] = false;\n } else if (primitiveVal !== undefined) {\n result[key] = Number(primitiveVal);\n }\n }\n\n return result;\n }\n}\n"],"mappings":"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"}
|