@orchestr-sh/orchestr 1.8.0 → 1.9.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.
Files changed (169) hide show
  1. package/README.md +809 -0
  2. package/dist/Cache/CacheManager.d.ts +109 -0
  3. package/dist/Cache/CacheManager.d.ts.map +1 -0
  4. package/dist/Cache/CacheManager.js +181 -0
  5. package/dist/Cache/CacheManager.js.map +1 -0
  6. package/dist/Cache/CacheServiceProvider.d.ts +19 -0
  7. package/dist/Cache/CacheServiceProvider.d.ts.map +1 -0
  8. package/dist/Cache/CacheServiceProvider.js +71 -0
  9. package/dist/Cache/CacheServiceProvider.js.map +1 -0
  10. package/dist/Cache/Contracts/Lock.d.ts +31 -0
  11. package/dist/Cache/Contracts/Lock.d.ts.map +1 -0
  12. package/dist/Cache/Contracts/Lock.js +3 -0
  13. package/dist/Cache/Contracts/Lock.js.map +1 -0
  14. package/dist/Cache/Contracts/Repository.d.ts +59 -0
  15. package/dist/Cache/Contracts/Repository.d.ts.map +1 -0
  16. package/dist/Cache/Contracts/Repository.js +9 -0
  17. package/dist/Cache/Contracts/Repository.js.map +1 -0
  18. package/dist/Cache/Contracts/Store.d.ts +51 -0
  19. package/dist/Cache/Contracts/Store.d.ts.map +1 -0
  20. package/dist/Cache/Contracts/Store.js +3 -0
  21. package/dist/Cache/Contracts/Store.js.map +1 -0
  22. package/dist/Cache/Events/CacheFlushed.d.ts +11 -0
  23. package/dist/Cache/Events/CacheFlushed.d.ts.map +1 -0
  24. package/dist/Cache/Events/CacheFlushed.js +18 -0
  25. package/dist/Cache/Events/CacheFlushed.js.map +1 -0
  26. package/dist/Cache/Events/CacheHit.d.ts +13 -0
  27. package/dist/Cache/Events/CacheHit.d.ts.map +1 -0
  28. package/dist/Cache/Events/CacheHit.js +22 -0
  29. package/dist/Cache/Events/CacheHit.js.map +1 -0
  30. package/dist/Cache/Events/CacheMissed.d.ts +12 -0
  31. package/dist/Cache/Events/CacheMissed.d.ts.map +1 -0
  32. package/dist/Cache/Events/CacheMissed.js +20 -0
  33. package/dist/Cache/Events/CacheMissed.js.map +1 -0
  34. package/dist/Cache/Events/KeyForgotten.d.ts +12 -0
  35. package/dist/Cache/Events/KeyForgotten.d.ts.map +1 -0
  36. package/dist/Cache/Events/KeyForgotten.js +20 -0
  37. package/dist/Cache/Events/KeyForgotten.js.map +1 -0
  38. package/dist/Cache/Events/KeyWritten.d.ts +14 -0
  39. package/dist/Cache/Events/KeyWritten.d.ts.map +1 -0
  40. package/dist/Cache/Events/KeyWritten.js +24 -0
  41. package/dist/Cache/Events/KeyWritten.js.map +1 -0
  42. package/dist/Cache/Events/index.d.ts +6 -0
  43. package/dist/Cache/Events/index.d.ts.map +1 -0
  44. package/dist/Cache/Events/index.js +14 -0
  45. package/dist/Cache/Events/index.js.map +1 -0
  46. package/dist/Cache/Locks/CacheLock.d.ts +19 -0
  47. package/dist/Cache/Locks/CacheLock.d.ts.map +1 -0
  48. package/dist/Cache/Locks/CacheLock.js +49 -0
  49. package/dist/Cache/Locks/CacheLock.js.map +1 -0
  50. package/dist/Cache/Locks/Lock.d.ts +51 -0
  51. package/dist/Cache/Locks/Lock.d.ts.map +1 -0
  52. package/dist/Cache/Locks/Lock.js +83 -0
  53. package/dist/Cache/Locks/Lock.js.map +1 -0
  54. package/dist/Cache/Locks/LockTimeoutException.d.ts +9 -0
  55. package/dist/Cache/Locks/LockTimeoutException.d.ts.map +1 -0
  56. package/dist/Cache/Locks/LockTimeoutException.js +16 -0
  57. package/dist/Cache/Locks/LockTimeoutException.js.map +1 -0
  58. package/dist/Cache/Locks/index.d.ts +4 -0
  59. package/dist/Cache/Locks/index.d.ts.map +1 -0
  60. package/dist/Cache/Locks/index.js +10 -0
  61. package/dist/Cache/Locks/index.js.map +1 -0
  62. package/dist/Cache/Repository.d.ts +61 -0
  63. package/dist/Cache/Repository.d.ts.map +1 -0
  64. package/dist/Cache/Repository.js +207 -0
  65. package/dist/Cache/Repository.js.map +1 -0
  66. package/dist/Cache/Stores/ArrayStore.d.ts +44 -0
  67. package/dist/Cache/Stores/ArrayStore.d.ts.map +1 -0
  68. package/dist/Cache/Stores/ArrayStore.js +118 -0
  69. package/dist/Cache/Stores/ArrayStore.js.map +1 -0
  70. package/dist/Cache/Stores/DatabaseStore.d.ts +44 -0
  71. package/dist/Cache/Stores/DatabaseStore.d.ts.map +1 -0
  72. package/dist/Cache/Stores/DatabaseStore.js +165 -0
  73. package/dist/Cache/Stores/DatabaseStore.js.map +1 -0
  74. package/dist/Cache/Stores/FileStore.d.ts +50 -0
  75. package/dist/Cache/Stores/FileStore.d.ts.map +1 -0
  76. package/dist/Cache/Stores/FileStore.js +194 -0
  77. package/dist/Cache/Stores/FileStore.js.map +1 -0
  78. package/dist/Cache/Stores/NullStore.d.ts +22 -0
  79. package/dist/Cache/Stores/NullStore.d.ts.map +1 -0
  80. package/dist/Cache/Stores/NullStore.js +49 -0
  81. package/dist/Cache/Stores/NullStore.js.map +1 -0
  82. package/dist/Cache/Stores/index.d.ts +5 -0
  83. package/dist/Cache/Stores/index.d.ts.map +1 -0
  84. package/dist/Cache/Stores/index.js +12 -0
  85. package/dist/Cache/Stores/index.js.map +1 -0
  86. package/dist/Cache/Tags/TagSet.d.ts +39 -0
  87. package/dist/Cache/Tags/TagSet.d.ts.map +1 -0
  88. package/dist/Cache/Tags/TagSet.js +72 -0
  89. package/dist/Cache/Tags/TagSet.js.map +1 -0
  90. package/dist/Cache/Tags/TaggedCache.d.ts +54 -0
  91. package/dist/Cache/Tags/TaggedCache.d.ts.map +1 -0
  92. package/dist/Cache/Tags/TaggedCache.js +125 -0
  93. package/dist/Cache/Tags/TaggedCache.js.map +1 -0
  94. package/dist/Cache/Tags/index.d.ts +3 -0
  95. package/dist/Cache/Tags/index.d.ts.map +1 -0
  96. package/dist/Cache/Tags/index.js +8 -0
  97. package/dist/Cache/Tags/index.js.map +1 -0
  98. package/dist/Cache/index.d.ts +27 -0
  99. package/dist/Cache/index.d.ts.map +1 -0
  100. package/dist/Cache/index.js +48 -0
  101. package/dist/Cache/index.js.map +1 -0
  102. package/dist/Console/Commands/CacheClearCommand.d.ts +16 -0
  103. package/dist/Console/Commands/CacheClearCommand.d.ts.map +1 -0
  104. package/dist/Console/Commands/CacheClearCommand.js +33 -0
  105. package/dist/Console/Commands/CacheClearCommand.js.map +1 -0
  106. package/dist/Console/Commands/CacheForgetCommand.d.ts +16 -0
  107. package/dist/Console/Commands/CacheForgetCommand.d.ts.map +1 -0
  108. package/dist/Console/Commands/CacheForgetCommand.js +39 -0
  109. package/dist/Console/Commands/CacheForgetCommand.js.map +1 -0
  110. package/dist/Console/Commands/CacheTableCommand.d.ts +17 -0
  111. package/dist/Console/Commands/CacheTableCommand.d.ts.map +1 -0
  112. package/dist/Console/Commands/CacheTableCommand.js +92 -0
  113. package/dist/Console/Commands/CacheTableCommand.js.map +1 -0
  114. package/dist/Console/Commands/MakeViewCommand.d.ts +19 -0
  115. package/dist/Console/Commands/MakeViewCommand.d.ts.map +1 -0
  116. package/dist/Console/Commands/MakeViewCommand.js +132 -0
  117. package/dist/Console/Commands/MakeViewCommand.js.map +1 -0
  118. package/dist/Facades/Cache.d.ts +41 -0
  119. package/dist/Facades/Cache.d.ts.map +1 -0
  120. package/dist/Facades/Cache.js +78 -0
  121. package/dist/Facades/Cache.js.map +1 -0
  122. package/dist/Facades/View.d.ts +28 -0
  123. package/dist/Facades/View.d.ts.map +1 -0
  124. package/dist/Facades/View.js +57 -0
  125. package/dist/Facades/View.js.map +1 -0
  126. package/dist/Facades/index.d.ts +1 -0
  127. package/dist/Facades/index.d.ts.map +1 -1
  128. package/dist/Facades/index.js +3 -1
  129. package/dist/Facades/index.js.map +1 -1
  130. package/dist/Routing/Response.d.ts +6 -3
  131. package/dist/Routing/Response.d.ts.map +1 -1
  132. package/dist/Routing/Response.js +32 -7
  133. package/dist/Routing/Response.js.map +1 -1
  134. package/dist/Routing/Router.d.ts.map +1 -1
  135. package/dist/Routing/Router.js +9 -1
  136. package/dist/Routing/Router.js.map +1 -1
  137. package/dist/Support/helpers.d.ts +19 -0
  138. package/dist/Support/helpers.d.ts.map +1 -1
  139. package/dist/Support/helpers.js +31 -0
  140. package/dist/Support/helpers.js.map +1 -1
  141. package/dist/View/Engines/FileEngine.d.ts +15 -0
  142. package/dist/View/Engines/FileEngine.d.ts.map +1 -0
  143. package/dist/View/Engines/FileEngine.js +21 -0
  144. package/dist/View/Engines/FileEngine.js.map +1 -0
  145. package/dist/View/Engines/TemplateEngine.d.ts +100 -0
  146. package/dist/View/Engines/TemplateEngine.d.ts.map +1 -0
  147. package/dist/View/Engines/TemplateEngine.js +350 -0
  148. package/dist/View/Engines/TemplateEngine.js.map +1 -0
  149. package/dist/View/Engines/ViewEngine.d.ts +12 -0
  150. package/dist/View/Engines/ViewEngine.d.ts.map +1 -0
  151. package/dist/View/Engines/ViewEngine.js +8 -0
  152. package/dist/View/Engines/ViewEngine.js.map +1 -0
  153. package/dist/View/View.d.ts +44 -0
  154. package/dist/View/View.d.ts.map +1 -0
  155. package/dist/View/View.js +72 -0
  156. package/dist/View/View.js.map +1 -0
  157. package/dist/View/ViewFactory.d.ts +55 -0
  158. package/dist/View/ViewFactory.d.ts.map +1 -0
  159. package/dist/View/ViewFactory.js +101 -0
  160. package/dist/View/ViewFactory.js.map +1 -0
  161. package/dist/View/ViewServiceProvider.d.ts +30 -0
  162. package/dist/View/ViewServiceProvider.d.ts.map +1 -0
  163. package/dist/View/ViewServiceProvider.js +53 -0
  164. package/dist/View/ViewServiceProvider.js.map +1 -0
  165. package/dist/index.d.ts +36 -1
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +70 -3
  168. package/dist/index.js.map +1 -1
  169. package/package.json +1 -1
package/README.md CHANGED
@@ -858,6 +858,812 @@ npx orchestr make:listener <name> --queued # Create queued listener
858
858
  npx orchestr event:list # List all registered events
859
859
  npx orchestr event:cache # Cache discovered events
860
860
  npx orchestr event:clear # Clear event cache
861
+
862
+ # Queue - Jobs
863
+ npx orchestr make:job <name> # Create job class
864
+ npx orchestr make:job <name> --sync # Create synchronous job
865
+
866
+ # Queue - Workers
867
+ npx orchestr queue:work [connection] # Start queue worker daemon
868
+ npx orchestr queue:work --once # Process single job and exit
869
+ npx orchestr queue:work --queue=high,default # Process specific queues
870
+ npx orchestr queue:work --tries=3 # Set max attempts
871
+ npx orchestr queue:work --timeout=90 # Set job timeout
872
+ npx orchestr queue:work --sleep=3 # Seconds between checks
873
+ npx orchestr queue:work --max-jobs=1000 # Stop after N jobs
874
+ npx orchestr queue:work --max-time=3600 # Stop after N seconds
875
+ npx orchestr queue:work --memory=128 # Memory limit in MB
876
+ npx orchestr queue:work --rest=0 # Rest between jobs (ms)
877
+ npx orchestr queue:work --stop-when-empty # Stop when queue is empty
878
+ npx orchestr queue:restart # Gracefully restart all workers
879
+
880
+ # Queue - Failed Jobs
881
+ npx orchestr queue:failed # List all failed jobs
882
+ npx orchestr queue:retry <id> # Retry specific failed job
883
+ npx orchestr queue:retry all # Retry all failed jobs
884
+ npx orchestr queue:forget <id> # Delete failed job by ID
885
+ npx orchestr queue:flush # Delete all failed jobs
886
+ npx orchestr queue:prune-failed --hours=48 # Prune failed jobs older than N hours
887
+
888
+ # Queue - Monitoring
889
+ npx orchestr queue:monitor <queue> --max=100 # Alert if queue exceeds size
890
+ npx orchestr queue:clear [connection] --queue=default # Clear queue
891
+
892
+ # Queue - Database Setup
893
+ npx orchestr queue:table # Create jobs table migration
894
+ npx orchestr queue:failed-table # Create failed_jobs table migration
895
+ npx orchestr queue:batches-table # Create job_batches table migration
896
+ npx orchestr queue:prune-batches --hours=24 # Prune batches older than N hours
897
+
898
+ # Cache
899
+ npx orchestr cache:clear [--store=redis] # Clear all cache or specific store
900
+ npx orchestr cache:forget <key> [--store=redis] # Forget specific cache key
901
+ npx orchestr cache:table # Create cache table migration
902
+
903
+ # Views
904
+ npx orchestr make:view <name> # Create view template
905
+ ```
906
+
907
+ ## Queue System
908
+
909
+ Orchestr provides a powerful queue system for deferring time-intensive tasks. Jobs can be pushed to various drivers (sync, database), retried on failure, organized into chains and batches, and monitored through a comprehensive CLI.
910
+
911
+ ### Configuration
912
+
913
+ Create a queue configuration in your application:
914
+
915
+ ```typescript
916
+ import { QueueServiceProvider } from '@orchestr-sh/orchestr';
917
+
918
+ app.register(new QueueServiceProvider(app));
919
+
920
+ // Configure in ConfigServiceProvider
921
+ {
922
+ queue: {
923
+ default: 'database',
924
+ connections: {
925
+ sync: {
926
+ driver: 'sync',
927
+ },
928
+ database: {
929
+ driver: 'database',
930
+ table: 'jobs',
931
+ queue: 'default',
932
+ retry_after: 90,
933
+ after_commit: false,
934
+ },
935
+ },
936
+ failed: {
937
+ driver: 'database',
938
+ database: 'sqlite',
939
+ table: 'failed_jobs',
940
+ },
941
+ batching: {
942
+ database: 'sqlite',
943
+ table: 'job_batches',
944
+ },
945
+ },
946
+ }
947
+ ```
948
+
949
+ ### Creating Jobs
950
+
951
+ Jobs are classes that extend the `Job` base class:
952
+
953
+ ```typescript
954
+ import { Job } from '@orchestr-sh/orchestr';
955
+
956
+ export class ProcessPodcast extends Job {
957
+ public tries = 5;
958
+ public timeout = 120;
959
+ public backoff = [10, 30, 60];
960
+
961
+ constructor(public podcastId: number) {
962
+ super();
963
+ }
964
+
965
+ async handle(): Promise<void> {
966
+ // Process the podcast
967
+ const podcast = await Podcast.find(this.podcastId);
968
+ await podcast.process();
969
+ }
970
+
971
+ async failed(error: Error): Promise<void> {
972
+ // Handle job failure
973
+ console.error(`Failed to process podcast ${this.podcastId}:`, error);
974
+ }
975
+ }
976
+
977
+ // Create via CLI
978
+ npx orchestr make:job ProcessPodcast
979
+ npx orchestr make:job SendEmail --sync // Synchronous job
980
+ ```
981
+
982
+ ### Dispatching Jobs
983
+
984
+ ```typescript
985
+ // Basic dispatch
986
+ await ProcessPodcast.dispatch(podcastId);
987
+
988
+ // Fluent dispatch API
989
+ await ProcessPodcast.dispatch(podcastId)
990
+ .onQueue('high-priority')
991
+ .onConnection('redis')
992
+ .delay(60)
993
+ .tries(3)
994
+ .timeout(300)
995
+ .backoff([30, 60, 120]);
996
+
997
+ // Conditional dispatch
998
+ await ProcessPodcast.dispatchIf(podcast.needsProcessing, podcastId);
999
+ await ProcessPodcast.dispatchUnless(podcast.isProcessed, podcastId);
1000
+
1001
+ // Synchronous dispatch (runs immediately)
1002
+ await ProcessPodcast.dispatchSync(podcastId);
1003
+
1004
+ // Using Queue/Bus facades
1005
+ import { Queue, Bus } from '@orchestr-sh/orchestr';
1006
+
1007
+ await Queue.push(new ProcessPodcast(podcastId));
1008
+ await Queue.pushOn('high-priority', new ProcessPodcast(podcastId));
1009
+ await Queue.later(60, new ProcessPodcast(podcastId));
1010
+
1011
+ await Bus.dispatch(new ProcessPodcast(podcastId));
1012
+ await Bus.dispatchSync(new ProcessPodcast(podcastId));
1013
+ ```
1014
+
1015
+ ### Job Middleware
1016
+
1017
+ Middleware can be applied to jobs for rate limiting, preventing overlaps, and throttling exceptions:
1018
+
1019
+ ```typescript
1020
+ import { RateLimited, WithoutOverlapping, ThrottlesExceptions } from '@orchestr-sh/orchestr';
1021
+
1022
+ export class ProcessPodcast extends Job {
1023
+ constructor(public podcastId: number) {
1024
+ super();
1025
+ }
1026
+
1027
+ middleware() {
1028
+ return [
1029
+ // Allow 10 jobs per minute
1030
+ new RateLimited('podcasts', 10, 60).releaseAfter(30),
1031
+
1032
+ // Prevent overlapping jobs for the same podcast
1033
+ new WithoutOverlapping(this.podcastId)
1034
+ .releaseAfter(10)
1035
+ .expireAfter(300),
1036
+
1037
+ // Throttle on too many exceptions
1038
+ new ThrottlesExceptions(5, 10).backoff(5),
1039
+ ];
1040
+ }
1041
+
1042
+ async handle(): Promise<void> {
1043
+ // Process podcast
1044
+ }
1045
+ }
1046
+ ```
1047
+
1048
+ ### Job Chaining
1049
+
1050
+ Execute jobs sequentially - if one fails, the chain stops:
1051
+
1052
+ ```typescript
1053
+ import { Bus } from '@orchestr-sh/orchestr';
1054
+
1055
+ await Bus.chain([
1056
+ new ProcessPodcast(podcastId),
1057
+ new OptimizePodcast(podcastId),
1058
+ new PublishPodcast(podcastId),
1059
+ new NotifySubscribers(podcastId),
1060
+ ])
1061
+ .onQueue('processing')
1062
+ .onConnection('redis')
1063
+ .delay(300)
1064
+ .catch((error) => {
1065
+ console.error('Chain failed:', error);
1066
+ })
1067
+ .dispatch();
1068
+ ```
1069
+
1070
+ ### Job Batching
1071
+
1072
+ Execute jobs concurrently with progress tracking:
1073
+
1074
+ ```typescript
1075
+ import { Bus } from '@orchestr-sh/orchestr';
1076
+
1077
+ const batch = await Bus.batch([
1078
+ new ImportRow(1),
1079
+ new ImportRow(2),
1080
+ new ImportRow(3),
1081
+ new ImportRow(4),
1082
+ ])
1083
+ .name('CSV Import')
1084
+ .then((batch) => {
1085
+ console.log('All imports complete!');
1086
+ })
1087
+ .catch((batch, error) => {
1088
+ console.error('A job failed:', error);
1089
+ })
1090
+ .finally((batch) => {
1091
+ console.log(`Processed ${batch.processedJobs}/${batch.totalJobs} jobs`);
1092
+ })
1093
+ .allowFailures()
1094
+ .onQueue('imports')
1095
+ .dispatch();
1096
+
1097
+ // Check batch progress
1098
+ console.log(`Progress: ${batch.progress()}%`);
1099
+ console.log(`Pending: ${batch.pendingJobs}`);
1100
+ console.log(`Failed: ${batch.failedJobs}`);
1101
+ ```
1102
+
1103
+ ### Running Workers
1104
+
1105
+ Process queued jobs with the worker daemon:
1106
+
1107
+ ```bash
1108
+ # Start a worker
1109
+ npx orchestr queue:work
1110
+
1111
+ # Specify connection and queue
1112
+ npx orchestr queue:work database --queue=high-priority,default
1113
+
1114
+ # Worker options
1115
+ npx orchestr queue:work database \
1116
+ --queue=high,default \
1117
+ --tries=3 \
1118
+ --timeout=90 \
1119
+ --sleep=3 \
1120
+ --max-jobs=1000 \
1121
+ --max-time=3600 \
1122
+ --memory=128 \
1123
+ --rest=0
1124
+
1125
+ # Process a single job (--once)
1126
+ npx orchestr queue:work --once
1127
+
1128
+ # Stop when queue is empty
1129
+ npx orchestr queue:work --stop-when-empty
1130
+
1131
+ # Restart all workers gracefully
1132
+ npx orchestr queue:restart
1133
+ ```
1134
+
1135
+ ### Failed Jobs
1136
+
1137
+ Manage failed jobs through the CLI or programmatically:
1138
+
1139
+ ```bash
1140
+ # List all failed jobs
1141
+ npx orchestr queue:failed
1142
+
1143
+ # Retry a specific failed job
1144
+ npx orchestr queue:retry 5
1145
+
1146
+ # Retry all failed jobs
1147
+ npx orchestr queue:retry all
1148
+
1149
+ # Forget (delete) a failed job
1150
+ npx orchestr queue:forget 5
1151
+
1152
+ # Flush all failed jobs
1153
+ npx orchestr queue:flush
1154
+
1155
+ # Prune failed jobs older than 48 hours
1156
+ npx orchestr queue:prune-failed --hours=48
1157
+ ```
1158
+
1159
+ ### Queue Monitoring
1160
+
1161
+ ```bash
1162
+ # Monitor queue for jobs exceeding thresholds
1163
+ npx orchestr queue:monitor database:default --max=100
1164
+
1165
+ # Clear all jobs from a queue
1166
+ npx orchestr queue:clear database --queue=default
1167
+ ```
1168
+
1169
+ ### Database Setup
1170
+
1171
+ ```bash
1172
+ # Create queue tables migration
1173
+ npx orchestr queue:table
1174
+
1175
+ # Create failed jobs table migration
1176
+ npx orchestr queue:failed-table
1177
+
1178
+ # Create job batches table migration
1179
+ npx orchestr queue:batches-table
1180
+
1181
+ # Run migrations
1182
+ npx orchestr migrate
1183
+ ```
1184
+
1185
+ ### Job Events
1186
+
1187
+ Register callbacks for job lifecycle events:
1188
+
1189
+ ```typescript
1190
+ import { Queue } from '@orchestr-sh/orchestr';
1191
+
1192
+ // Before job processing
1193
+ Queue.before((connectionName, job) => {
1194
+ console.log(`Processing: ${job.displayName()}`);
1195
+ });
1196
+
1197
+ // After job processing
1198
+ Queue.after((connectionName, job) => {
1199
+ console.log(`Completed: ${job.displayName()}`);
1200
+ });
1201
+
1202
+ // When a job fails
1203
+ Queue.failing((connectionName, job, error) => {
1204
+ console.error(`Failed: ${job.displayName()}`, error);
1205
+ });
1206
+
1207
+ // On each worker loop iteration
1208
+ Queue.looping(() => {
1209
+ // Perform maintenance tasks
1210
+ });
1211
+ ```
1212
+
1213
+ ### Custom Queue Drivers
1214
+
1215
+ Extend the queue system with custom drivers:
1216
+
1217
+ ```typescript
1218
+ import { QueueDriver } from '@orchestr-sh/orchestr';
1219
+
1220
+ class RedisDriver implements QueueDriver {
1221
+ async push(job: Job, queue?: string): Promise<string> {
1222
+ // Push job to Redis
1223
+ }
1224
+
1225
+ async pop(queue?: string): Promise<QueueDriverJob | null> {
1226
+ // Pop job from Redis
1227
+ }
1228
+
1229
+ // Implement other methods...
1230
+ }
1231
+
1232
+ // Register the driver
1233
+ const manager = app.make<QueueManager>('queue');
1234
+ manager.registerDriver('redis', (config) => new RedisDriver(config));
1235
+ ```
1236
+
1237
+ ## Cache System
1238
+
1239
+ Orchestr provides a flexible caching system with multiple drivers, tags, locks, and stale-while-revalidate support.
1240
+
1241
+ ### Configuration
1242
+
1243
+ Configure cache stores in your application:
1244
+
1245
+ ```typescript
1246
+ import { CacheServiceProvider } from '@orchestr-sh/orchestr';
1247
+
1248
+ app.register(new CacheServiceProvider(app));
1249
+
1250
+ // Configure in ConfigServiceProvider
1251
+ {
1252
+ cache: {
1253
+ default: 'file',
1254
+ prefix: 'app_cache_',
1255
+ stores: {
1256
+ array: {
1257
+ driver: 'array',
1258
+ serialize: false,
1259
+ },
1260
+ file: {
1261
+ driver: 'file',
1262
+ path: 'storage/framework/cache/data',
1263
+ },
1264
+ database: {
1265
+ driver: 'database',
1266
+ connection: null,
1267
+ table: 'cache',
1268
+ },
1269
+ null: {
1270
+ driver: 'null',
1271
+ },
1272
+ },
1273
+ },
1274
+ }
1275
+ ```
1276
+
1277
+ ### Basic Usage
1278
+
1279
+ ```typescript
1280
+ import { Cache } from '@orchestr-sh/orchestr';
1281
+
1282
+ // Store items
1283
+ await Cache.put('key', 'value', 3600); // TTL in seconds
1284
+ await Cache.put('key', 'value', new Date('2024-12-31')); // TTL as Date
1285
+ await Cache.forever('key', 'value'); // Store forever
1286
+
1287
+ // Retrieve items
1288
+ const value = await Cache.get('key');
1289
+ const value = await Cache.get('key', 'default value');
1290
+ const value = await Cache.get('key', () => 'computed default');
1291
+
1292
+ // Multiple items
1293
+ await Cache.putMany({ key1: 'value1', key2: 'value2' }, 3600);
1294
+ const values = await Cache.many(['key1', 'key2']);
1295
+
1296
+ // Check existence
1297
+ if (await Cache.has('key')) {
1298
+ // Key exists
1299
+ }
1300
+
1301
+ if (await Cache.missing('key')) {
1302
+ // Key does not exist
1303
+ }
1304
+
1305
+ // Remove items
1306
+ await Cache.forget('key');
1307
+ await Cache.flush(); // Clear all cache
1308
+
1309
+ // Retrieve and delete
1310
+ const value = await Cache.pull('key', 'default');
1311
+ ```
1312
+
1313
+ ### Remember Pattern
1314
+
1315
+ Cache the result of expensive operations:
1316
+
1317
+ ```typescript
1318
+ // Cache for 1 hour if not exists
1319
+ const user = await Cache.remember('user:1', 3600, async () => {
1320
+ return await User.find(1);
1321
+ });
1322
+
1323
+ // Cache forever if not exists
1324
+ const settings = await Cache.rememberForever('settings', async () => {
1325
+ return await fetchSettings();
1326
+ });
1327
+
1328
+ // Add only if key doesn't exist
1329
+ await Cache.add('key', 'value', 3600);
1330
+ ```
1331
+
1332
+ ### Flexible Caching (Stale-While-Revalidate)
1333
+
1334
+ Serve stale content while revalidating in the background:
1335
+
1336
+ ```typescript
1337
+ // Fresh for 30s, stale for up to 300s
1338
+ const data = await Cache.flexible('api:data', [30, 300], async () => {
1339
+ return await fetchFromAPI();
1340
+ });
1341
+
1342
+ // First 30 seconds: serves fresh data
1343
+ // After 30 seconds: serves stale data, triggers background refresh
1344
+ // After 300 seconds: fetches fresh data synchronously
1345
+ ```
1346
+
1347
+ ### Incrementing and Decrementing
1348
+
1349
+ ```typescript
1350
+ // Increment
1351
+ await Cache.increment('views'); // +1
1352
+ await Cache.increment('views', 5); // +5
1353
+
1354
+ // Decrement
1355
+ await Cache.decrement('views'); // -1
1356
+ await Cache.decrement('views', 3); // -3
1357
+ ```
1358
+
1359
+ ### Multiple Stores
1360
+
1361
+ Switch between different cache stores:
1362
+
1363
+ ```typescript
1364
+ // Use specific store
1365
+ await Cache.store('redis').put('key', 'value', 3600);
1366
+ await Cache.store('file').put('key', 'value', 3600);
1367
+ await Cache.store('database').put('key', 'value', 3600);
1368
+
1369
+ // Chain operations
1370
+ const value = await Cache.store('redis').remember('expensive', 3600, async () => {
1371
+ return await computeExpensiveValue();
1372
+ });
1373
+ ```
1374
+
1375
+ ### Cache Tags
1376
+
1377
+ Group related cache entries for easy invalidation:
1378
+
1379
+ ```typescript
1380
+ // Store tagged items
1381
+ await Cache.tags(['people', 'artists']).put('John', johnData, 3600);
1382
+ await Cache.tags(['people', 'authors']).put('Anne', anneData, 3600);
1383
+ await Cache.tags('products').put('product:1', productData, 3600);
1384
+
1385
+ // Retrieve tagged items
1386
+ const john = await Cache.tags(['people', 'artists']).get('John');
1387
+
1388
+ // Flush by tag (removes all tagged entries)
1389
+ await Cache.tags('people').flush(); // Removes John and Anne
1390
+ await Cache.tags(['people', 'artists']).flush();
1391
+
1392
+ // Remember with tags
1393
+ const user = await Cache.tags(['users', 'premium']).remember('user:1', 3600, async () => {
1394
+ return await User.find(1);
1395
+ });
1396
+ ```
1397
+
1398
+ ### Cache Locks
1399
+
1400
+ Atomic locks for preventing race conditions:
1401
+
1402
+ ```typescript
1403
+ // Basic lock usage
1404
+ const lock = Cache.lock('processing', 120);
1405
+
1406
+ if (await lock.get()) {
1407
+ try {
1408
+ // Critical section
1409
+ await processExpensiveOperation();
1410
+ } finally {
1411
+ await lock.release();
1412
+ }
1413
+ }
1414
+
1415
+ // Auto-release with callback
1416
+ await Cache.lock('processing', 120).get(async () => {
1417
+ // Lock is automatically released after callback
1418
+ await processExpensiveOperation();
1419
+ });
1420
+
1421
+ // Block until lock is acquired
1422
+ try {
1423
+ await Cache.lock('processing', 120).block(10, async () => {
1424
+ // Wait up to 10 seconds for lock
1425
+ await processExpensiveOperation();
1426
+ });
1427
+ } catch (error) {
1428
+ // LockTimeoutException after 10 seconds
1429
+ }
1430
+
1431
+ // Check lock ownership
1432
+ const lock = Cache.lock('processing', 120);
1433
+ await lock.get();
1434
+
1435
+ if (await lock.isOwnedByCurrentProcess()) {
1436
+ await lock.release();
1437
+ }
1438
+
1439
+ // Force release (ignores ownership)
1440
+ await lock.forceRelease();
1441
+ ```
1442
+
1443
+ ### Database Setup
1444
+
1445
+ ```bash
1446
+ # Create cache table migration
1447
+ npx orchestr cache:table
1448
+
1449
+ # Run migration
1450
+ npx orchestr migrate
1451
+ ```
1452
+
1453
+ ### Cache Commands
1454
+
1455
+ ```bash
1456
+ # Clear all cache
1457
+ npx orchestr cache:clear
1458
+
1459
+ # Clear specific store
1460
+ npx orchestr cache:clear --store=redis
1461
+
1462
+ # Forget a specific key
1463
+ npx orchestr cache:forget key-name
1464
+
1465
+ # Forget from specific store
1466
+ npx orchestr cache:forget key-name --store=redis
1467
+ ```
1468
+
1469
+ ### Custom Cache Drivers
1470
+
1471
+ Create custom cache drivers for Redis, Memcached, etc.:
1472
+
1473
+ ```typescript
1474
+ import { Store } from '@orchestr-sh/orchestr';
1475
+
1476
+ class RedisStore implements Store {
1477
+ async get(key: string): Promise<any> {
1478
+ // Get from Redis
1479
+ }
1480
+
1481
+ async put(key: string, value: any, seconds: number): Promise<boolean> {
1482
+ // Put to Redis
1483
+ }
1484
+
1485
+ // Implement other methods...
1486
+ }
1487
+
1488
+ // Register the driver
1489
+ const manager = app.make<CacheManager>('cache');
1490
+ manager.registerDriver('redis', (config) => new RedisStore(config));
1491
+ ```
1492
+
1493
+ ### Repository Methods
1494
+
1495
+ The cache repository provides these methods:
1496
+
1497
+ ```typescript
1498
+ // Basic operations
1499
+ await Cache.get(key, defaultValue?)
1500
+ await Cache.many(keys)
1501
+ await Cache.put(key, value, ttl?)
1502
+ await Cache.putMany(values, ttl?)
1503
+ await Cache.forever(key, value)
1504
+ await Cache.forget(key)
1505
+ await Cache.flush()
1506
+
1507
+ // High-level operations
1508
+ await Cache.has(key)
1509
+ await Cache.missing(key)
1510
+ await Cache.pull(key, defaultValue?)
1511
+ await Cache.add(key, value, ttl?)
1512
+ await Cache.remember(key, ttl, callback)
1513
+ await Cache.rememberForever(key, callback)
1514
+ await Cache.flexible(key, [freshTtl, staleTtl], callback)
1515
+
1516
+ // Numeric operations
1517
+ await Cache.increment(key, value?)
1518
+ await Cache.decrement(key, value?)
1519
+
1520
+ // Tags and locks
1521
+ Cache.tags(names)
1522
+ Cache.lock(name, seconds?, owner?)
1523
+ Cache.restoreLock(name, owner)
1524
+
1525
+ // Store operations
1526
+ Cache.store(name?)
1527
+ Cache.getPrefix()
1528
+ ```
1529
+
1530
+ ## View System
1531
+
1532
+ Orchestr includes a view system for rendering HTML templates, mirroring Laravel's Blade. Views are stored in `resources/views/` and use dot-notation for resolution (e.g., `'layouts.app'` resolves to `resources/views/layouts/app.html`).
1533
+
1534
+ ### Configuration
1535
+
1536
+ ```typescript
1537
+ import { ViewServiceProvider } from '@orchestr-sh/orchestr';
1538
+
1539
+ app.register(new ViewServiceProvider(app));
1540
+
1541
+ // Configure in ConfigServiceProvider
1542
+ {
1543
+ view: {
1544
+ paths: ['resources/views'],
1545
+ extensions: ['.html', '.orchestr.html'],
1546
+ },
1547
+ }
1548
+ ```
1549
+
1550
+ ### Basic Usage
1551
+
1552
+ Return views directly from route handlers using the `view()` helper:
1553
+
1554
+ ```typescript
1555
+ import { Route, view } from '@orchestr-sh/orchestr';
1556
+
1557
+ Route.get('/', () => {
1558
+ return view('welcome', { name: 'World' });
1559
+ });
1560
+ ```
1561
+
1562
+ ### View Facade
1563
+
1564
+ ```typescript
1565
+ import { View } from '@orchestr-sh/orchestr';
1566
+
1567
+ // Create a view instance
1568
+ const v = View.make('welcome', { name: 'World' });
1569
+ const html = await v.render();
1570
+
1571
+ // Check if a view exists
1572
+ if (View.exists('emails.invoice')) {
1573
+ // ...
1574
+ }
1575
+
1576
+ // Share data with all views
1577
+ View.share('appName', 'My App');
1578
+ ```
1579
+
1580
+ ### Chaining Data
1581
+
1582
+ ```typescript
1583
+ const v = view('welcome')
1584
+ .with('name', 'John')
1585
+ .with({ role: 'admin', active: true });
1586
+ ```
1587
+
1588
+ ### Template Syntax
1589
+
1590
+ **Output:**
1591
+
1592
+ ```html
1593
+ {{ variable }} <!-- HTML-escaped (XSS-safe) -->
1594
+ {!! variable !!} <!-- Raw/unescaped output -->
1595
+ ```
1596
+
1597
+ **Conditionals:**
1598
+
1599
+ ```html
1600
+ @if(user.isAdmin)
1601
+ <p>Welcome, admin!</p>
1602
+ @elseif(user.isMember)
1603
+ <p>Welcome, member!</p>
1604
+ @else
1605
+ <p>Welcome, guest!</p>
1606
+ @endif
1607
+ ```
1608
+
1609
+ **Loops:**
1610
+
1611
+ ```html
1612
+ @foreach(users as user)
1613
+ <li>{{ user.name }}</li>
1614
+ @endforeach
1615
+ ```
1616
+
1617
+ **Includes:**
1618
+
1619
+ ```html
1620
+ @include('partials.nav')
1621
+ @include('partials.alert', { type: 'success' })
1622
+ ```
1623
+
1624
+ ### Layouts
1625
+
1626
+ Define a layout file (`resources/views/layouts/app.html`):
1627
+
1628
+ ```html
1629
+ <!DOCTYPE html>
1630
+ <html>
1631
+ <head>
1632
+ <title>@yield('title', 'My App')</title>
1633
+ </head>
1634
+ <body>
1635
+ @yield('content')
1636
+ </body>
1637
+ </html>
1638
+ ```
1639
+
1640
+ Extend it from a child template (`resources/views/welcome.html`):
1641
+
1642
+ ```html
1643
+ @extends('layouts.app')
1644
+
1645
+ @section('title')Welcome@endsection
1646
+
1647
+ @section('content')
1648
+ <h1>Hello, {{ name }}!</h1>
1649
+ @endsection
1650
+ ```
1651
+
1652
+ ### Response Integration
1653
+
1654
+ ```typescript
1655
+ // From a route handler
1656
+ Route.get('/', (req, res) => {
1657
+ return res.view('welcome', { name: 'World' });
1658
+ });
1659
+ ```
1660
+
1661
+ ### View Commands
1662
+
1663
+ ```bash
1664
+ # Create a new view template
1665
+ npx orchestr make:view welcome
1666
+ npx orchestr make:view emails.invoice # Creates resources/views/emails/invoice.html
861
1667
  ```
862
1668
 
863
1669
  ## Features
@@ -877,6 +1683,9 @@ npx orchestr event:clear # Clear event cache
877
1683
  - ✅ Event Subscribers
878
1684
  - ✅ Model Lifecycle Events
879
1685
  - ✅ Event Testing (Fakes & Assertions)
1686
+ - ✅ Queue System (Jobs, Chains, Batches, Workers)
1687
+ - ✅ Cache System (Tags, Locks, Flexible Caching)
1688
+ - ✅ View System (Templates, Layouts, Directives)
880
1689
  - ✅ Soft Deletes
881
1690
  - ✅ Attribute Casting
882
1691
  - ✅ Timestamps