@beeblock/svelar 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/dist/actions/index.d.ts +101 -0
  4. package/dist/actions/index.js +1 -0
  5. package/dist/api-keys/index.d.ts +58 -0
  6. package/dist/api-keys/index.js +1 -0
  7. package/dist/audit/index.d.ts +52 -0
  8. package/dist/audit/index.js +1 -0
  9. package/dist/auth/Auth.d.ts +283 -0
  10. package/dist/auth/Gate.d.ts +166 -0
  11. package/dist/auth/index.d.ts +2 -0
  12. package/dist/auth/index.js +80 -0
  13. package/dist/broadcasting/client.d.ts +195 -0
  14. package/dist/broadcasting/client.js +1 -0
  15. package/dist/broadcasting/index.d.ts +318 -0
  16. package/dist/broadcasting/index.js +20 -0
  17. package/dist/cache/index.d.ts +77 -0
  18. package/dist/cache/index.js +1 -0
  19. package/dist/cli/Cli.d.ts +23 -0
  20. package/dist/cli/Command.d.ts +36 -0
  21. package/dist/cli/bin.d.ts +8 -0
  22. package/dist/cli/bin.js +5856 -0
  23. package/dist/cli/commands/KeyGenerateCommand.d.ts +16 -0
  24. package/dist/cli/commands/MakeActionCommand.d.ts +15 -0
  25. package/dist/cli/commands/MakeBroadcastingCommand.d.ts +29 -0
  26. package/dist/cli/commands/MakeChannelCommand.d.ts +18 -0
  27. package/dist/cli/commands/MakeCommandCommand.d.ts +16 -0
  28. package/dist/cli/commands/MakeConfigCommand.d.ts +13 -0
  29. package/dist/cli/commands/MakeControllerCommand.d.ts +28 -0
  30. package/dist/cli/commands/MakeDashboardCommand.d.ts +34 -0
  31. package/dist/cli/commands/MakeDockerCommand.d.ts +32 -0
  32. package/dist/cli/commands/MakeEventCommand.d.ts +11 -0
  33. package/dist/cli/commands/MakeJobCommand.d.ts +11 -0
  34. package/dist/cli/commands/MakeListenerCommand.d.ts +16 -0
  35. package/dist/cli/commands/MakeMiddlewareCommand.d.ts +11 -0
  36. package/dist/cli/commands/MakeMigrationCommand.d.ts +17 -0
  37. package/dist/cli/commands/MakeModelCommand.d.ts +25 -0
  38. package/dist/cli/commands/MakeObserverCommand.d.ts +23 -0
  39. package/dist/cli/commands/MakePluginCommand.d.ts +11 -0
  40. package/dist/cli/commands/MakeProviderCommand.d.ts +11 -0
  41. package/dist/cli/commands/MakeRepositoryCommand.d.ts +22 -0
  42. package/dist/cli/commands/MakeRequestCommand.d.ts +15 -0
  43. package/dist/cli/commands/MakeResourceCommand.d.ts +30 -0
  44. package/dist/cli/commands/MakeRouteCommand.d.ts +42 -0
  45. package/dist/cli/commands/MakeSchemaCommand.d.ts +20 -0
  46. package/dist/cli/commands/MakeSeederCommand.d.ts +11 -0
  47. package/dist/cli/commands/MakeServiceCommand.d.ts +28 -0
  48. package/dist/cli/commands/MakeTaskCommand.d.ts +12 -0
  49. package/dist/cli/commands/MigrateCommand.d.ts +26 -0
  50. package/dist/cli/commands/NewCommand.d.ts +21 -0
  51. package/dist/cli/commands/NewCommandTemplates.d.ts +123 -0
  52. package/dist/cli/commands/PluginInstallCommand.d.ts +16 -0
  53. package/dist/cli/commands/PluginListCommand.d.ts +11 -0
  54. package/dist/cli/commands/PluginPublishCommand.d.ts +22 -0
  55. package/dist/cli/commands/QueueFailedCommand.d.ts +9 -0
  56. package/dist/cli/commands/QueueFlushCommand.d.ts +9 -0
  57. package/dist/cli/commands/QueueRetryCommand.d.ts +16 -0
  58. package/dist/cli/commands/QueueWorkCommand.d.ts +25 -0
  59. package/dist/cli/commands/RoutesListCommand.d.ts +30 -0
  60. package/dist/cli/commands/ScheduleRunCommand.d.ts +15 -0
  61. package/dist/cli/commands/SeedCommand.d.ts +14 -0
  62. package/dist/cli/commands/TinkerCommand.d.ts +10 -0
  63. package/dist/cli/index.d.ts +36 -0
  64. package/dist/cli/index.js +1973 -0
  65. package/dist/cli/ts-resolve-hook.mjs +74 -0
  66. package/dist/cli/ts-resolver.mjs +8 -0
  67. package/dist/config/Config.d.ts +65 -0
  68. package/dist/config/index.d.ts +1 -0
  69. package/dist/config/index.js +1 -0
  70. package/dist/container/Application.d.ts +33 -0
  71. package/dist/container/Container.d.ts +70 -0
  72. package/dist/container/ServiceProvider.d.ts +21 -0
  73. package/dist/container/index.d.ts +3 -0
  74. package/dist/container/index.js +1 -0
  75. package/dist/dashboard/index.d.ts +123 -0
  76. package/dist/dashboard/index.js +5 -0
  77. package/dist/database/Connection.d.ts +80 -0
  78. package/dist/database/Migration.d.ts +76 -0
  79. package/dist/database/SchemaBuilder.d.ts +91 -0
  80. package/dist/database/Seeder.d.ts +9 -0
  81. package/dist/database/index.d.ts +4 -0
  82. package/dist/database/index.js +4 -0
  83. package/dist/email-templates/index.d.ts +51 -0
  84. package/dist/email-templates/index.js +57 -0
  85. package/dist/errors/Handler.d.ts +100 -0
  86. package/dist/errors/index.d.ts +1 -0
  87. package/dist/errors/index.js +5 -0
  88. package/dist/events/EventServiceProvider.d.ts +82 -0
  89. package/dist/events/Listener.d.ts +28 -0
  90. package/dist/events/index.d.ts +80 -0
  91. package/dist/events/index.js +1 -0
  92. package/dist/excel/index.d.ts +154 -0
  93. package/dist/excel/index.js +1 -0
  94. package/dist/feature-flags/index.d.ts +158 -0
  95. package/dist/feature-flags/index.js +59 -0
  96. package/dist/forms/index.d.ts +81 -0
  97. package/dist/forms/index.js +1 -0
  98. package/dist/hashing/Hash.d.ts +51 -0
  99. package/dist/hashing/index.d.ts +1 -0
  100. package/dist/hashing/index.js +1 -0
  101. package/dist/hooks/index.d.ts +135 -0
  102. package/dist/hooks/index.js +5 -0
  103. package/dist/http/index.d.ts +201 -0
  104. package/dist/http/index.js +2 -0
  105. package/dist/i18n/index.d.ts +81 -0
  106. package/dist/i18n/index.js +1 -0
  107. package/dist/index.d.ts +54 -0
  108. package/dist/index.js +127 -0
  109. package/dist/logging/LogViewer.d.ts +95 -0
  110. package/dist/logging/LogViewer.js +1 -0
  111. package/dist/logging/index.d.ts +83 -0
  112. package/dist/logging/index.js +3 -0
  113. package/dist/mail/index.d.ts +149 -0
  114. package/dist/mail/index.js +1 -0
  115. package/dist/middleware/Middleware.d.ts +208 -0
  116. package/dist/middleware/index.d.ts +1 -0
  117. package/dist/middleware/index.js +1 -0
  118. package/dist/notifications/index.d.ts +85 -0
  119. package/dist/notifications/index.js +2 -0
  120. package/dist/orm/Model.d.ts +123 -0
  121. package/dist/orm/Observer.d.ts +34 -0
  122. package/dist/orm/QueryBuilder.d.ts +119 -0
  123. package/dist/orm/Relationship.d.ts +58 -0
  124. package/dist/orm/index.d.ts +4 -0
  125. package/dist/orm/index.js +1 -0
  126. package/dist/pagination/index.d.ts +8 -0
  127. package/dist/pagination/index.js +0 -0
  128. package/dist/pdf/GeneratePdfJob.d.ts +99 -0
  129. package/dist/pdf/GeneratePdfJob.js +41 -0
  130. package/dist/pdf/index.d.ts +328 -0
  131. package/dist/pdf/index.js +41 -0
  132. package/dist/permissions/index.d.ts +161 -0
  133. package/dist/permissions/index.js +60 -0
  134. package/dist/plugins/BootstrapPlugins.d.ts +11 -0
  135. package/dist/plugins/PluginInstaller.d.ts +30 -0
  136. package/dist/plugins/PluginInstaller.js +1 -0
  137. package/dist/plugins/PluginPublisher.d.ts +32 -0
  138. package/dist/plugins/PluginPublisher.js +1 -0
  139. package/dist/plugins/PluginRegistry.d.ts +55 -0
  140. package/dist/plugins/PluginRegistry.js +1 -0
  141. package/dist/plugins/index.d.ts +206 -0
  142. package/dist/plugins/index.js +1 -0
  143. package/dist/queue/JobMonitor.d.ts +109 -0
  144. package/dist/queue/JobMonitor.js +5 -0
  145. package/dist/queue/index.d.ts +279 -0
  146. package/dist/queue/index.js +5 -0
  147. package/dist/repositories/index.d.ts +147 -0
  148. package/dist/repositories/index.js +1 -0
  149. package/dist/routing/Controller.d.ts +115 -0
  150. package/dist/routing/FormRequest.d.ts +94 -0
  151. package/dist/routing/Resource.d.ts +213 -0
  152. package/dist/routing/Response.d.ts +138 -0
  153. package/dist/routing/index.d.ts +4 -0
  154. package/dist/routing/index.js +5 -0
  155. package/dist/scheduler/ScheduleMonitor.d.ts +141 -0
  156. package/dist/scheduler/ScheduleMonitor.js +1 -0
  157. package/dist/scheduler/SchedulerLock.d.ts +33 -0
  158. package/dist/scheduler/index.d.ts +208 -0
  159. package/dist/scheduler/index.js +34 -0
  160. package/dist/services/index.d.ts +79 -0
  161. package/dist/services/index.js +1 -0
  162. package/dist/session/Session.d.ts +166 -0
  163. package/dist/session/index.d.ts +1 -0
  164. package/dist/session/index.js +16 -0
  165. package/dist/storage/index.d.ts +154 -0
  166. package/dist/storage/index.js +1 -0
  167. package/dist/support/Pipeline.d.ts +65 -0
  168. package/dist/support/date.d.ts +136 -0
  169. package/dist/support/date.js +1 -0
  170. package/dist/support/index.d.ts +8 -0
  171. package/dist/support/index.js +1 -0
  172. package/dist/support/singleton.d.ts +10 -0
  173. package/dist/support/uuid.d.ts +40 -0
  174. package/dist/teams/index.d.ts +91 -0
  175. package/dist/teams/index.js +78 -0
  176. package/dist/uploads/index.d.ts +63 -0
  177. package/dist/uploads/index.js +2 -0
  178. package/dist/validation/index.d.ts +46 -0
  179. package/dist/validation/index.js +1 -0
  180. package/dist/webhooks/index.d.ts +66 -0
  181. package/dist/webhooks/index.js +1 -0
  182. package/package.json +338 -0
  183. package/src/i18n/LanguageSwitcher.svelte +47 -0
  184. package/src/i18n/index.ts +113 -0
  185. package/src/ui/Alert.svelte +22 -0
  186. package/src/ui/Avatar.svelte +18 -0
  187. package/src/ui/AvatarFallback.svelte +18 -0
  188. package/src/ui/AvatarImage.svelte +12 -0
  189. package/src/ui/Badge.svelte +27 -0
  190. package/src/ui/Button.svelte +51 -0
  191. package/src/ui/Card.svelte +15 -0
  192. package/src/ui/CardContent.svelte +15 -0
  193. package/src/ui/CardDescription.svelte +15 -0
  194. package/src/ui/CardFooter.svelte +15 -0
  195. package/src/ui/CardHeader.svelte +15 -0
  196. package/src/ui/CardTitle.svelte +15 -0
  197. package/src/ui/Icon.svelte +81 -0
  198. package/src/ui/Input.svelte +40 -0
  199. package/src/ui/Label.svelte +20 -0
  200. package/src/ui/Separator.svelte +10 -0
  201. package/src/ui/Tabs.svelte +23 -0
  202. package/src/ui/TabsContent.svelte +27 -0
  203. package/src/ui/TabsList.svelte +19 -0
  204. package/src/ui/TabsTrigger.svelte +28 -0
  205. package/src/ui/Toaster.svelte +279 -0
  206. package/src/ui/index.ts +31 -0
  207. package/src/ui/toast.ts +212 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Queue Job Monitor
3
+ *
4
+ * Driver-agnostic utilities for querying queue job state.
5
+ * Used by the admin dashboard to display job metrics and manage jobs.
6
+ */
7
+ export interface JobInfo {
8
+ id: string;
9
+ jobClass: string;
10
+ queue: string;
11
+ status: 'waiting' | 'active' | 'completed' | 'failed' | 'delayed';
12
+ attempts: number;
13
+ maxAttempts: number;
14
+ payload: string;
15
+ error?: string;
16
+ createdAt: number;
17
+ processedAt?: number;
18
+ failedAt?: number;
19
+ delayedUntil?: number;
20
+ }
21
+ export interface JobCounts {
22
+ waiting: number;
23
+ active: number;
24
+ completed: number;
25
+ failed: number;
26
+ delayed: number;
27
+ total: number;
28
+ }
29
+ export interface JobFilter {
30
+ status?: JobInfo['status'];
31
+ queue?: string;
32
+ jobClass?: string;
33
+ limit?: number;
34
+ offset?: number;
35
+ }
36
+ export interface QueueHealth {
37
+ driver: string;
38
+ queues: Record<string, JobCounts>;
39
+ oldestWaitingJob?: number;
40
+ failureRate: number;
41
+ throughput: number;
42
+ }
43
+ declare class JobMonitorService {
44
+ private _config;
45
+ private _bullmq;
46
+ private _metrics;
47
+ configure(config: {
48
+ driver: string;
49
+ default: string;
50
+ connections: Record<string, any>;
51
+ }): void;
52
+ /** Record a processed job (called internally by queue system) */
53
+ recordProcessed(): void;
54
+ /** Record a failed job (called internally by queue system) */
55
+ recordFailed(): void;
56
+ /**
57
+ * Get job counts for a queue (or all queues)
58
+ */
59
+ getCounts(queueName?: string): Promise<JobCounts>;
60
+ /**
61
+ * List jobs with filtering and pagination
62
+ */
63
+ listJobs(filter?: JobFilter): Promise<JobInfo[]>;
64
+ /**
65
+ * Get details for a single job
66
+ */
67
+ getJob(jobId: string): Promise<JobInfo | null>;
68
+ /**
69
+ * Retry a failed job
70
+ */
71
+ retryJob(jobId: string): Promise<boolean>;
72
+ /**
73
+ * Delete a job from the queue
74
+ */
75
+ deleteJob(jobId: string): Promise<boolean>;
76
+ /**
77
+ * Get overall queue health metrics
78
+ */
79
+ getHealth(): Promise<QueueHealth>;
80
+ /**
81
+ * Flush all completed jobs (cleanup)
82
+ */
83
+ flushCompleted(queueName?: string): Promise<number>;
84
+ /**
85
+ * Flush all failed jobs
86
+ */
87
+ flushFailed(queueName?: string): Promise<number>;
88
+ private getBullMQ;
89
+ private getRedisConnection;
90
+ private getRedisQueue;
91
+ private _getRedisJobCounts;
92
+ private _listRedisJobs;
93
+ private _getRedisJob;
94
+ private _retryRedisJob;
95
+ private _deleteRedisJob;
96
+ private _flushRedisCompleted;
97
+ private _flushRedisFailed;
98
+ private _bullmqJobToInfo;
99
+ private getDbConnection;
100
+ private getDbTable;
101
+ private _getDatabaseJobCounts;
102
+ private _listDatabaseJobs;
103
+ private _getDatabaseJob;
104
+ private _retryDatabaseJob;
105
+ private _deleteDatabaseJob;
106
+ private _flushDatabaseFailed;
107
+ }
108
+ export declare const JobMonitor: JobMonitorService;
109
+ export {};
@@ -0,0 +1,5 @@
1
+ var C=Object.defineProperty;var p=(d,t)=>()=>(d&&(t=d(d=0)),t);var _=(d,t)=>{for(var e in t)C(d,e,{get:t[e],enumerable:!0})};function u(d,t){let e=Symbol.for(d),n=globalThis;return n[e]||(n[e]=t()),n[e]}var f=p(()=>{"use strict"});var w={};_(w,{Connection:()=>D});var b,D,y=p(()=>{"use strict";f();b=class{connections=new Map;config=null;defaultName="default";configure(t){this.config=t,this.defaultName=t.default}async connection(t){let e=t??this.defaultName;if(this.connections.has(e))return this.connections.get(e).drizzle;if(!this.config)throw new Error("Database not configured. Call Connection.configure() first, or register DatabaseServiceProvider.");let n=this.config.connections[e];if(!n)throw new Error(`Database connection "${e}" is not defined in configuration.`);let s=await this.createConnection(n);return this.connections.set(e,s),s.drizzle}async rawClient(t){let e=t??this.defaultName;return await this.connection(e),this.connections.get(e).rawClient}async raw(t,e=[],n){let s=await this.connection(n),a=this.getConfig(n);switch(a.driver){case"sqlite":{let i=await this.rawClient(n),r=e.map(o=>typeof o=="boolean"?o?1:0:o instanceof Date?o.toISOString():o),c=i.prepare(t),l=t.trimStart().toUpperCase();return l.startsWith("SELECT")||l.startsWith("PRAGMA")||l.startsWith("WITH")?c.all(...r):c.run(...r)}case"postgres":return(await this.rawClient(n))(t,...e);case"mysql":{let i=await this.rawClient(n),[r]=await i.execute(t,e);return r}default:throw new Error(`Unsupported driver: ${a.driver}`)}}getDriver(t){return this.getConfig(t).driver}getConfig(t){let e=t??this.defaultName;if(!this.config)throw new Error("Database not configured.");let n=this.config.connections[e];if(!n)throw new Error(`Database connection "${e}" is not defined.`);return n}async disconnect(t){if(t){let e=this.connections.get(t);e&&(await this.closeConnection(e),this.connections.delete(t))}else{for(let[e,n]of this.connections)await this.closeConnection(n);this.connections.clear()}}isConnected(t){return this.connections.has(t??this.defaultName)}async transaction(t,e){let n=this.getConfig(e),s=await this.rawClient(e);switch(n.driver){case"sqlite":{s.exec("BEGIN");try{let a=await t();return s.exec("COMMIT"),a}catch(a){throw s.exec("ROLLBACK"),a}}case"postgres":{await s`BEGIN`;try{let a=await t();return await s`COMMIT`,a}catch(a){throw await s`ROLLBACK`,a}}case"mysql":{let a=await s.getConnection();await a.beginTransaction();try{let i=await t();return await a.commit(),a.release(),i}catch(i){throw await a.rollback(),a.release(),i}}default:throw new Error(`Unsupported driver: ${n.driver}`)}}async createConnection(t){switch(t.driver){case"sqlite":return this.createSQLiteConnection(t);case"postgres":return this.createPostgresConnection(t);case"mysql":return this.createMySQLConnection(t);default:throw new Error(`Unsupported database driver: ${t.driver}`)}}async createSQLiteConnection(t){let e=t.filename??t.database??":memory:";try{let n=(await import("better-sqlite3")).default,{drizzle:s}=await import("drizzle-orm/better-sqlite3"),a=new n(e);return a.pragma("journal_mode = WAL"),a.pragma("foreign_keys = ON"),{drizzle:s(a),config:t,rawClient:a}}catch(n){let s;try{s=(await new Function("mod","return import(mod)")("node:sqlite")).DatabaseSync}catch{throw new Error(`No SQLite driver available. Install better-sqlite3 (npm install better-sqlite3) or use Node.js v22+ which includes built-in SQLite support. Original error: ${n instanceof Error?n.message:String(n)}`)}let a=new s(e);a.exec("PRAGMA journal_mode = WAL"),a.exec("PRAGMA foreign_keys = ON");let i={prepare(c){let l=a.prepare(c);return{all(...o){return l.all(...o)},run(...o){return l.run(...o)},get(...o){return l.get(...o)}}},exec(c){a.exec(c)},pragma(c){return a.prepare(`PRAGMA ${c}`).all()},close(){a.close()}},r;try{let{drizzle:c}=await import("drizzle-orm/better-sqlite3");r=c(i)}catch{r=i}return{drizzle:r,config:t,rawClient:i}}}async createPostgresConnection(t){let e=(await import("postgres")).default,{drizzle:n}=await import("drizzle-orm/postgres-js"),s=t.url??`postgres://${t.user}:${t.password}@${t.host??"localhost"}:${t.port??5432}/${t.database}`,a=e(s);return{drizzle:n(a),config:t,rawClient:a}}async createMySQLConnection(t){let e=await import("mysql2/promise"),{drizzle:n}=await import("drizzle-orm/mysql2"),s=e.createPool({host:t.host??"localhost",port:t.port??3306,database:t.database,user:t.user,password:t.password,uri:t.url});return{drizzle:n(s),config:t,rawClient:s}}async closeConnection(t){try{switch(t.config.driver){case"sqlite":t.rawClient.close();break;case"postgres":await t.rawClient.end();break;case"mysql":await t.rawClient.end();break}}catch{}}},D=u("svelar.connection",()=>new b)});f();var g=class{_config=null;_bullmq=null;_metrics={processed:[],failed:[]};configure(t){this._config=t}recordProcessed(){this._metrics.processed.push(Date.now());let t=Date.now()-36e5;this._metrics.processed=this._metrics.processed.filter(e=>e>t)}recordFailed(){this._metrics.failed.push(Date.now());let t=Date.now()-36e5;this._metrics.failed=this._metrics.failed.filter(e=>e>t)}async getCounts(t="default"){let e=this._config?.connections[this._config.default]?.driver;return e==="redis"?this._getRedisJobCounts(t):e==="database"?this._getDatabaseJobCounts(t):{waiting:0,active:0,completed:0,failed:0,delayed:0,total:0}}async listJobs(t={}){let e=this._config?.connections[this._config.default]?.driver;return e==="redis"?this._listRedisJobs(t):e==="database"?this._listDatabaseJobs(t):[]}async getJob(t){let e=this._config?.connections[this._config.default]?.driver;return e==="redis"?this._getRedisJob(t):e==="database"?this._getDatabaseJob(t):null}async retryJob(t){let e=this._config?.connections[this._config.default]?.driver;return e==="redis"?this._retryRedisJob(t):e==="database"?this._retryDatabaseJob(t):!1}async deleteJob(t){let e=this._config?.connections[this._config.default]?.driver;return e==="redis"?this._deleteRedisJob(t):e==="database"?this._deleteDatabaseJob(t):!1}async getHealth(){let t=this._config?.connections[this._config.default]?.driver??"sync",e=await this.getCounts("default"),n=Date.now()-36e5,s=this._metrics.processed.filter(r=>r>n).length,a=this._metrics.failed.filter(r=>r>n).length,i=s+a;return{driver:t,queues:{default:e},failureRate:i>0?a/i*100:0,throughput:s}}async flushCompleted(t="default"){return this._config?.connections[this._config.default]?.driver==="redis"?this._flushRedisCompleted(t):0}async flushFailed(t="default"){let e=this._config?.connections[this._config.default]?.driver;return e==="redis"?this._flushRedisFailed(t):e==="database"?this._flushDatabaseFailed(t):0}async getBullMQ(){if(this._bullmq)return this._bullmq;try{return this._bullmq=await Function('return import("bullmq")')(),this._bullmq}catch{throw new Error("bullmq is required for the Redis queue monitor.")}}getRedisConnection(){let t=this._config?.connections[this._config.default]??{};if(t.url){let e=new URL(t.url);return{host:e.hostname||"localhost",port:parseInt(e.port)||6379,password:e.password||t.password||void 0,db:parseInt(e.pathname?.slice(1)||"0")||t.db||0}}return{host:t.host??"localhost",port:t.port??6379,password:t.password,db:t.db??0}}async getRedisQueue(t){let e=await this.getBullMQ(),n=this.getRedisConnection(),s=this._config?.connections[this._config.default]??{};return new e.Queue(t,{connection:n,prefix:s.prefix??"svelar"})}async _getRedisJobCounts(t){let e=await this.getRedisQueue(t);try{let n=await e.getJobCounts("waiting","active","completed","failed","delayed");return{waiting:n.waiting??0,active:n.active??0,completed:n.completed??0,failed:n.failed??0,delayed:n.delayed??0,total:(n.waiting??0)+(n.active??0)+(n.completed??0)+(n.failed??0)+(n.delayed??0)}}finally{await e.close()}}async _listRedisJobs(t){let e=await this.getRedisQueue(t.queue??"default"),n=t.limit??50,s=t.offset??0;try{let a;return t.status?a=await e.getJobs([t.status],s,s+n-1):a=await e.getJobs(["waiting","active","completed","failed","delayed"],s,s+n-1),a.filter(i=>i!=null).filter(i=>!t.jobClass||i.name===t.jobClass).map(i=>this._bullmqJobToInfo(i))}finally{await e.close()}}async _getRedisJob(t){let e=await this.getRedisQueue("default");try{let n=await e.getJob(t);return n?this._bullmqJobToInfo(n):null}finally{await e.close()}}async _retryRedisJob(t){let e=await this.getRedisQueue("default");try{let n=await e.getJob(t);return n?(await n.retry(),!0):!1}catch{return!1}finally{await e.close()}}async _deleteRedisJob(t){let e=await this.getRedisQueue("default");try{let n=await e.getJob(t);return n?(await n.remove(),!0):!1}catch{return!1}finally{await e.close()}}async _flushRedisCompleted(t){let e=await this.getRedisQueue(t);try{let n=await e.getJobs(["completed"]),s=0;for(let a of n)a&&(await a.remove(),s++);return s}finally{await e.close()}}async _flushRedisFailed(t){let e=await this.getRedisQueue(t);try{let n=await e.getJobs(["failed"]),s=0;for(let a of n)a&&(await a.remove(),s++);return s}finally{await e.close()}}_bullmqJobToInfo(t){let e=t.finishedOn?t.failedReason?"failed":"completed":t.delay&&t.delay>0&&!t.processedOn?"delayed":t.processedOn?"active":"waiting";return{id:t.id,jobClass:t.name??t.data?.jobClass??"Unknown",queue:t.queueName??"default",status:e,attempts:t.attemptsMade??0,maxAttempts:t.opts?.attempts??3,payload:JSON.stringify(t.data??{}),error:t.failedReason??void 0,createdAt:t.timestamp??Date.now(),processedAt:t.processedOn??void 0,failedAt:t.failedReason?t.finishedOn:void 0,delayedUntil:t.delay?t.timestamp+t.delay:void 0}}async getDbConnection(){let{Connection:t}=await Promise.resolve().then(()=>(y(),w));return t}getDbTable(){return this._config?.connections[this._config.default]?.table??"svelar_jobs"}async _getDatabaseJobCounts(t){let e=await this.getDbConnection(),n=this.getDbTable(),s=Math.floor(Date.now()/1e3),i=(await e.raw(`SELECT
2
+ SUM(CASE WHEN reserved_at IS NULL AND available_at <= ? THEN 1 ELSE 0 END) as waiting,
3
+ SUM(CASE WHEN reserved_at IS NOT NULL THEN 1 ELSE 0 END) as active,
4
+ SUM(CASE WHEN reserved_at IS NULL AND available_at > ? THEN 1 ELSE 0 END) as delayed
5
+ FROM ${n} WHERE queue = ?`,[s,s,t]))?.[0]??{},r=Number(i.waiting)||0,c=Number(i.active)||0,l=Number(i.delayed)||0;return{waiting:r,active:c,completed:0,failed:0,delayed:l,total:r+c+l}}async _listDatabaseJobs(t){let e=await this.getDbConnection(),n=this.getDbTable(),s=t.limit??50,a=t.offset??0,i=Math.floor(Date.now()/1e3),r="WHERE 1=1",c=[];return t.queue&&(r+=" AND queue = ?",c.push(t.queue)),t.status==="waiting"?(r+=" AND reserved_at IS NULL AND available_at <= ?",c.push(i)):t.status==="active"?r+=" AND reserved_at IS NOT NULL":t.status==="delayed"&&(r+=" AND reserved_at IS NULL AND available_at > ?",c.push(i)),c.push(s,a),(await e.raw(`SELECT * FROM ${n} ${r} ORDER BY created_at DESC LIMIT ? OFFSET ?`,c)??[]).map(o=>{let v=JSON.parse(o.payload??"{}"),h=o.reserved_at!=null,m=!h&&o.available_at>i;return{id:o.id,jobClass:v.jobClass??"Unknown",queue:o.queue,status:h?"active":m?"delayed":"waiting",attempts:o.attempts??0,maxAttempts:o.max_attempts??3,payload:o.payload,createdAt:(o.created_at??0)*1e3,delayedUntil:m?o.available_at*1e3:void 0}})}async _getDatabaseJob(t){let e=await this.getDbConnection(),n=this.getDbTable(),s=await e.raw(`SELECT * FROM ${n} WHERE id = ?`,[t]);if(!s||s.length===0)return null;let a=s[0],i=JSON.parse(a.payload??"{}"),r=Math.floor(Date.now()/1e3);return{id:a.id,jobClass:i.jobClass??"Unknown",queue:a.queue,status:a.reserved_at?"active":a.available_at>r?"delayed":"waiting",attempts:a.attempts??0,maxAttempts:a.max_attempts??3,payload:a.payload,createdAt:(a.created_at??0)*1e3}}async _retryDatabaseJob(t){let e=await this.getDbConnection(),n=this.getDbTable(),s=Math.floor(Date.now()/1e3);try{return await e.raw(`UPDATE ${n} SET reserved_at = NULL, available_at = ?, attempts = 0 WHERE id = ?`,[s,t]),!0}catch{return!1}}async _deleteDatabaseJob(t){let e=await this.getDbConnection(),n=this.getDbTable();try{return await e.raw(`DELETE FROM ${n} WHERE id = ?`,[t]),!0}catch{return!1}}async _flushDatabaseFailed(t){let e=await this.getDbConnection(),n=this.getDbTable();return 0}},A=u("svelar.jobMonitor",()=>new g);export{A as JobMonitor};
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Svelar Queue / Jobs
3
+ *
4
+ * Background job processing with sync, in-memory, and database-backed queues.
5
+ * Dispatch jobs from anywhere — controllers, services, model hooks, middleware,
6
+ * other jobs, or CLI commands.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Queue, Job } from 'svelar/queue';
11
+ *
12
+ * class SendWelcomeEmail extends Job {
13
+ * constructor(private userId: number) { super(); }
14
+ *
15
+ * async handle(): Promise<void> {
16
+ * const user = await User.findOrFail(this.userId);
17
+ * await Mailer.send({ to: user.email, template: 'welcome' });
18
+ * }
19
+ *
20
+ * failed(error: Error): void {
21
+ * console.error('Failed to send welcome email:', error);
22
+ * }
23
+ * }
24
+ *
25
+ * // Register the job class (required for database driver deserialization)
26
+ * Queue.register(SendWelcomeEmail);
27
+ *
28
+ * // Dispatch to queue (async — processed by a worker)
29
+ * await Queue.dispatch(new SendWelcomeEmail(user.id));
30
+ *
31
+ * // Dispatch with delay (seconds)
32
+ * await Queue.dispatch(new SendWelcomeEmail(user.id), { delay: 60 });
33
+ *
34
+ * // Run synchronously regardless of configured driver
35
+ * await Queue.dispatchSync(new SendWelcomeEmail(user.id));
36
+ *
37
+ * // Chain jobs — run in sequence, stop on failure
38
+ * await Queue.chain([
39
+ * new ProcessPayment(orderId),
40
+ * new SendReceipt(orderId),
41
+ * new UpdateInventory(orderId),
42
+ * ]);
43
+ *
44
+ * // Process jobs (in a worker)
45
+ * await Queue.work();
46
+ * ```
47
+ */
48
+ export type QueueDriver = 'sync' | 'memory' | 'database' | 'redis';
49
+ export interface QueueConfig {
50
+ default: string;
51
+ connections: Record<string, QueueConnectionConfig>;
52
+ }
53
+ export interface QueueConnectionConfig {
54
+ driver: QueueDriver;
55
+ /** Queue name (default: 'default') */
56
+ queue?: string;
57
+ /** Max retry attempts */
58
+ maxAttempts?: number;
59
+ /** Retry delay in seconds */
60
+ retryDelay?: number;
61
+ /** Database table for database driver */
62
+ table?: string;
63
+ /** Redis connection URL for redis driver (default: redis://localhost:6379) */
64
+ url?: string;
65
+ /** Redis host (alternative to url) */
66
+ host?: string;
67
+ /** Redis port (default: 6379) */
68
+ port?: number;
69
+ /** Redis password */
70
+ password?: string;
71
+ /** Redis database index (default: 0) */
72
+ db?: number;
73
+ /** BullMQ queue prefix (default: 'svelar') */
74
+ prefix?: string;
75
+ /** Default job options for BullMQ */
76
+ defaultJobOptions?: {
77
+ removeOnComplete?: boolean | number;
78
+ removeOnFail?: boolean | number;
79
+ };
80
+ }
81
+ export interface DispatchOptions {
82
+ /** Queue name */
83
+ queue?: string;
84
+ /** Delay in seconds before processing */
85
+ delay?: number;
86
+ /** Max retry attempts */
87
+ maxAttempts?: number;
88
+ }
89
+ export declare abstract class Job {
90
+ /** Number of times this job has been attempted */
91
+ attempts: number;
92
+ /** Maximum attempts before failing permanently */
93
+ maxAttempts: number;
94
+ /** Delay before retry (in seconds) */
95
+ retryDelay: number;
96
+ /** Queue to dispatch to */
97
+ queue: string;
98
+ /**
99
+ * Handle the job — implement your logic here
100
+ */
101
+ abstract handle(): Promise<void>;
102
+ /**
103
+ * Called when the job has failed after all retries
104
+ */
105
+ failed(error: Error): void;
106
+ /**
107
+ * Called before each retry
108
+ */
109
+ retrying(attempt: number): void;
110
+ /**
111
+ * Serialize the job for storage.
112
+ * Override this if your job needs custom serialization.
113
+ */
114
+ serialize(): string;
115
+ /**
116
+ * Restore job properties from deserialized data.
117
+ * Called when reconstructing a job from the database.
118
+ * Override this if your job needs custom deserialization.
119
+ */
120
+ restore(data: Record<string, any>): void;
121
+ }
122
+ export interface FailedJobRecord {
123
+ id: string;
124
+ queue: string;
125
+ jobClass: string;
126
+ payload: string;
127
+ exception: string;
128
+ failedAt: number;
129
+ }
130
+ declare class QueueManager {
131
+ private config;
132
+ private drivers;
133
+ private processing;
134
+ private jobRegistry;
135
+ private failedStore;
136
+ private _activeWorker;
137
+ configure(config: QueueConfig): void;
138
+ /**
139
+ * Register a job class for database driver deserialization.
140
+ *
141
+ * @example
142
+ * Queue.register(SendWelcomeEmail);
143
+ * Queue.register(ProcessPayment);
144
+ */
145
+ register(JobClass: new (...args: any[]) => Job): void;
146
+ /**
147
+ * Register multiple job classes at once.
148
+ *
149
+ * @example
150
+ * Queue.registerAll([SendWelcomeEmail, ProcessPayment, GenerateReport]);
151
+ */
152
+ registerAll(classes: Array<new (...args: any[]) => Job>): void;
153
+ /**
154
+ * Dispatch a job to the configured queue driver.
155
+ * The job will be processed asynchronously by a worker (unless using the sync driver).
156
+ *
157
+ * @example
158
+ * // Basic dispatch
159
+ * await Queue.dispatch(new SendWelcomeEmail(user.id));
160
+ *
161
+ * // Dispatch with options
162
+ * await Queue.dispatch(new SendWelcomeEmail(user.id), {
163
+ * queue: 'emails',
164
+ * delay: 60, // Wait 60 seconds before processing
165
+ * maxAttempts: 5,
166
+ * });
167
+ */
168
+ dispatch(job: Job, options?: DispatchOptions): Promise<void>;
169
+ /**
170
+ * Dispatch a job synchronously, bypassing the configured queue driver.
171
+ * The job runs immediately in the current process and the promise
172
+ * resolves when the job completes (or rejects if it fails all retries).
173
+ *
174
+ * Use this when you need the result right away, in tests, or for
175
+ * jobs that must complete before the response is sent.
176
+ *
177
+ * @example
178
+ * // Run immediately regardless of configured driver
179
+ * await Queue.dispatchSync(new SendWelcomeEmail(user.id));
180
+ *
181
+ * // Useful in tests
182
+ * await Queue.dispatchSync(new ProcessPayment(orderId));
183
+ */
184
+ dispatchSync(job: Job): Promise<void>;
185
+ /**
186
+ * Chain multiple jobs to run in sequence.
187
+ * Each job runs after the previous one completes successfully.
188
+ * If any job fails (after all retries), the chain stops.
189
+ *
190
+ * @example
191
+ * await Queue.chain([
192
+ * new ProcessPayment(orderId),
193
+ * new SendReceipt(orderId),
194
+ * new UpdateInventory(orderId),
195
+ * ]);
196
+ *
197
+ * // Chain with options
198
+ * await Queue.chain([
199
+ * new ProcessPayment(orderId),
200
+ * new SendReceipt(orderId),
201
+ * ], { queue: 'orders' });
202
+ */
203
+ chain(jobs: Job[], options?: DispatchOptions): Promise<void>;
204
+ /**
205
+ * Process jobs from the queue (worker loop).
206
+ *
207
+ * For sync/memory/database drivers this runs a poll loop.
208
+ * For the redis driver this starts a BullMQ Worker that processes
209
+ * jobs natively — no polling required.
210
+ *
211
+ * @returns Number of processed jobs (poll drivers) or 0 (redis — the
212
+ * worker runs indefinitely until stop() is called).
213
+ */
214
+ work(options?: {
215
+ queue?: string;
216
+ maxJobs?: number;
217
+ sleep?: number;
218
+ /** BullMQ concurrency (redis driver only, default: 1) */
219
+ concurrency?: number;
220
+ }): Promise<number>;
221
+ /**
222
+ * Stop the worker loop (poll drivers) or close the BullMQ Worker (redis).
223
+ */
224
+ stop(): Promise<void>;
225
+ /**
226
+ * Get the size of a queue
227
+ */
228
+ size(queue?: string): Promise<number>;
229
+ /**
230
+ * Clear all jobs from a queue
231
+ */
232
+ clear(queue?: string): Promise<void>;
233
+ /**
234
+ * List all failed jobs.
235
+ *
236
+ * @example
237
+ * const failures = await Queue.failed();
238
+ * for (const f of failures) {
239
+ * console.log(f.jobClass, f.exception);
240
+ * }
241
+ */
242
+ failed(): Promise<FailedJobRecord[]>;
243
+ /**
244
+ * Retry a failed job by its ID.
245
+ * The job is re-dispatched to the queue and removed from the failed_jobs table.
246
+ *
247
+ * @example
248
+ * await Queue.retry('some-uuid');
249
+ */
250
+ retry(failedJobId: string): Promise<boolean>;
251
+ /**
252
+ * Retry all failed jobs.
253
+ *
254
+ * @example
255
+ * const count = await Queue.retryAll();
256
+ * console.log(`Retried ${count} jobs`);
257
+ */
258
+ retryAll(): Promise<number>;
259
+ /**
260
+ * Remove a single failed job record.
261
+ *
262
+ * @example
263
+ * await Queue.forgetFailed('some-uuid');
264
+ */
265
+ forgetFailed(failedJobId: string): Promise<boolean>;
266
+ /**
267
+ * Remove all failed job records.
268
+ *
269
+ * @example
270
+ * const count = await Queue.flushFailed();
271
+ */
272
+ flushFailed(): Promise<number>;
273
+ private resolveDriver;
274
+ }
275
+ /**
276
+ * Global Queue singleton
277
+ */
278
+ export declare const Queue: QueueManager;
279
+ export {};
@@ -0,0 +1,5 @@
1
+ var R=Object.defineProperty;var P=(l,e)=>()=>(l&&(e=l(l=0)),e);var Q=(l,e)=>{for(var t in e)R(l,t,{get:e[t],enumerable:!0})};function p(l,e){let t=Symbol.for(l),r=globalThis;return r[t]||(r[t]=e()),r[t]}var g=P(()=>{"use strict"});var b={};Q(b,{Connection:()=>j});var w,j,y=P(()=>{"use strict";g();w=class{connections=new Map;config=null;defaultName="default";configure(e){this.config=e,this.defaultName=e.default}async connection(e){let t=e??this.defaultName;if(this.connections.has(t))return this.connections.get(t).drizzle;if(!this.config)throw new Error("Database not configured. Call Connection.configure() first, or register DatabaseServiceProvider.");let r=this.config.connections[t];if(!r)throw new Error(`Database connection "${t}" is not defined in configuration.`);let a=await this.createConnection(r);return this.connections.set(t,a),a.drizzle}async rawClient(e){let t=e??this.defaultName;return await this.connection(t),this.connections.get(t).rawClient}async raw(e,t=[],r){let a=await this.connection(r),s=this.getConfig(r);switch(s.driver){case"sqlite":{let i=await this.rawClient(r),u=t.map(c=>typeof c=="boolean"?c?1:0:c instanceof Date?c.toISOString():c),n=i.prepare(e),o=e.trimStart().toUpperCase();return o.startsWith("SELECT")||o.startsWith("PRAGMA")||o.startsWith("WITH")?n.all(...u):n.run(...u)}case"postgres":return(await this.rawClient(r))(e,...t);case"mysql":{let i=await this.rawClient(r),[u]=await i.execute(e,t);return u}default:throw new Error(`Unsupported driver: ${s.driver}`)}}getDriver(e){return this.getConfig(e).driver}getConfig(e){let t=e??this.defaultName;if(!this.config)throw new Error("Database not configured.");let r=this.config.connections[t];if(!r)throw new Error(`Database connection "${t}" is not defined.`);return r}async disconnect(e){if(e){let t=this.connections.get(e);t&&(await this.closeConnection(t),this.connections.delete(e))}else{for(let[t,r]of this.connections)await this.closeConnection(r);this.connections.clear()}}isConnected(e){return this.connections.has(e??this.defaultName)}async transaction(e,t){let r=this.getConfig(t),a=await this.rawClient(t);switch(r.driver){case"sqlite":{a.exec("BEGIN");try{let s=await e();return a.exec("COMMIT"),s}catch(s){throw a.exec("ROLLBACK"),s}}case"postgres":{await a`BEGIN`;try{let s=await e();return await a`COMMIT`,s}catch(s){throw await a`ROLLBACK`,s}}case"mysql":{let s=await a.getConnection();await s.beginTransaction();try{let i=await e();return await s.commit(),s.release(),i}catch(i){throw await s.rollback(),s.release(),i}}default:throw new Error(`Unsupported driver: ${r.driver}`)}}async createConnection(e){switch(e.driver){case"sqlite":return this.createSQLiteConnection(e);case"postgres":return this.createPostgresConnection(e);case"mysql":return this.createMySQLConnection(e);default:throw new Error(`Unsupported database driver: ${e.driver}`)}}async createSQLiteConnection(e){let t=e.filename??e.database??":memory:";try{let r=(await import("better-sqlite3")).default,{drizzle:a}=await import("drizzle-orm/better-sqlite3"),s=new r(t);return s.pragma("journal_mode = WAL"),s.pragma("foreign_keys = ON"),{drizzle:a(s),config:e,rawClient:s}}catch(r){let a;try{a=(await new Function("mod","return import(mod)")("node:sqlite")).DatabaseSync}catch{throw new Error(`No SQLite driver available. Install better-sqlite3 (npm install better-sqlite3) or use Node.js v22+ which includes built-in SQLite support. Original error: ${r instanceof Error?r.message:String(r)}`)}let s=new a(t);s.exec("PRAGMA journal_mode = WAL"),s.exec("PRAGMA foreign_keys = ON");let i={prepare(n){let o=s.prepare(n);return{all(...c){return o.all(...c)},run(...c){return o.run(...c)},get(...c){return o.get(...c)}}},exec(n){s.exec(n)},pragma(n){return s.prepare(`PRAGMA ${n}`).all()},close(){s.close()}},u;try{let{drizzle:n}=await import("drizzle-orm/better-sqlite3");u=n(i)}catch{u=i}return{drizzle:u,config:e,rawClient:i}}}async createPostgresConnection(e){let t=(await import("postgres")).default,{drizzle:r}=await import("drizzle-orm/postgres-js"),a=e.url??`postgres://${e.user}:${e.password}@${e.host??"localhost"}:${e.port??5432}/${e.database}`,s=t(a);return{drizzle:r(s),config:e,rawClient:s}}async createMySQLConnection(e){let t=await import("mysql2/promise"),{drizzle:r}=await import("drizzle-orm/mysql2"),a=t.createPool({host:e.host??"localhost",port:e.port??3306,database:e.database,user:e.user,password:e.password,uri:e.url});return{drizzle:r(a),config:e,rawClient:a}}async closeConnection(e){try{switch(e.config.driver){case"sqlite":e.rawClient.close();break;case"postgres":await e.rawClient.end();break;case"mysql":await e.rawClient.end();break}}catch{}}},j=p("svelar.connection",()=>new w)});g();var v=class{attempts=0;maxAttempts=3;retryDelay=60;queue="default";failed(e){console.error(`[Queue] Job ${this.constructor.name} permanently failed:`,e.message)}retrying(e){}serialize(){let e={};for(let[t,r]of Object.entries(this))typeof r!="function"&&(e[t]=r);return JSON.stringify(e)}restore(e){for(let[t,r]of Object.entries(e))t!=="attempts"&&t!=="maxAttempts"&&t!=="retryDelay"&&t!=="queue"&&(this[t]=r)}},h=class{async push(e){try{e.job.attempts=1,await e.job.handle()}catch(t){if(e.attempts+1<e.maxAttempts)return e.attempts++,e.job.attempts=e.attempts+1,e.job.retrying(e.job.attempts),this.push(e);e.job.failed(t)}}async pop(){return null}async size(){return 0}async clear(){}},C=class{queues=new Map;async push(e){let t=e.queue;this.queues.has(t)||this.queues.set(t,[]),this.queues.get(t).push(e)}async pop(e="default"){let t=this.queues.get(e)??[],r=Date.now(),a=t.findIndex(s=>s.availableAt<=r);return a===-1?null:t.splice(a,1)[0]}async size(e="default"){return this.queues.get(e)?.length??0}async clear(e){e?this.queues.delete(e):this.queues.clear()}},m=class{constructor(e,t){this.table=e;this.registry=t}async getConnection(){let{Connection:e}=await Promise.resolve().then(()=>(y(),b));return e}async push(e){await(await this.getConnection()).raw(`INSERT INTO ${this.table} (id, queue, payload, attempts, max_attempts, available_at, created_at)
2
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,[e.id,e.queue,JSON.stringify({jobClass:e.jobClass,payload:e.payload}),e.attempts,e.maxAttempts,Math.floor(e.availableAt/1e3),Math.floor(e.createdAt/1e3)])}async pop(e="default"){let t=await this.getConnection(),r=Math.floor(Date.now()/1e3),a=await t.raw(`SELECT * FROM ${this.table}
3
+ WHERE queue = ? AND available_at <= ? AND reserved_at IS NULL
4
+ ORDER BY created_at ASC LIMIT 1`,[e,r]);if(!a||a.length===0)return null;let s=a[0];await t.raw(`UPDATE ${this.table} SET reserved_at = ?, attempts = attempts + 1 WHERE id = ?`,[r,s.id]);let i=JSON.parse(s.payload),u=this.registry.resolve(i.jobClass,i.payload);return{id:s.id,jobClass:i.jobClass,payload:i.payload,queue:s.queue,attempts:s.attempts+1,maxAttempts:s.max_attempts,availableAt:s.available_at*1e3,createdAt:s.created_at*1e3,job:u}}async size(e="default"){return(await(await this.getConnection()).raw(`SELECT COUNT(*) as count FROM ${this.table} WHERE queue = ? AND reserved_at IS NULL`,[e]))?.[0]?.count??0}async clear(e){let t=await this.getConnection();e?await t.raw(`DELETE FROM ${this.table} WHERE queue = ?`,[e]):await t.raw(`DELETE FROM ${this.table}`,[])}async delete(e){await(await this.getConnection()).raw(`DELETE FROM ${this.table} WHERE id = ?`,[e])}async release(e,t=0){let r=await this.getConnection(),a=Math.floor(Date.now()/1e3)+t;await r.raw(`UPDATE ${this.table} SET reserved_at = NULL, available_at = ? WHERE id = ?`,[a,e])}},f=class{queues=new Map;config;registry;_bullmq=null;constructor(e,t){this.config=e,this.registry=t}async getBullMQ(){if(this._bullmq)return this._bullmq;try{return this._bullmq=await Function('return import("bullmq")')(),this._bullmq}catch{throw new Error("bullmq is required for the Redis queue driver. Install it with: npm install bullmq")}}getRedisConnection(){if(this.config.url){let e=new URL(this.config.url);return{host:e.hostname||"localhost",port:parseInt(e.port)||6379,password:e.password||this.config.password||void 0,db:parseInt(e.pathname?.slice(1)||"0")||this.config.db||0}}return{host:this.config.host??"localhost",port:this.config.port??6379,password:this.config.password,db:this.config.db??0}}async getQueue(e){if(this.queues.has(e))return this.queues.get(e);let t=await this.getBullMQ(),r=this.getRedisConnection(),a=this.config.prefix??"svelar",s=new t.Queue(e,{connection:r,prefix:a,defaultJobOptions:{removeOnComplete:this.config.defaultJobOptions?.removeOnComplete??100,removeOnFail:this.config.defaultJobOptions?.removeOnFail??500}});return this.queues.set(e,s),s}async push(e){let t=await this.getQueue(e.queue),r=Math.max(0,e.availableAt-Date.now());await t.add(e.jobClass,{jobClass:e.jobClass,payload:e.payload},{jobId:e.id,delay:r>0?r:void 0,attempts:e.maxAttempts,backoff:{type:"fixed",delay:(e.job.retryDelay??60)*1e3}})}async pop(e){return null}async size(e="default"){let r=await(await this.getQueue(e)).getJobCounts("waiting","delayed","active");return r.waiting+r.delayed+r.active}async clear(e){if(e)await(await this.getQueue(e)).obliterate({force:!0});else for(let t of this.queues.values())await t.obliterate({force:!0})}async createWorker(e,t,r,a){let s=await this.getBullMQ(),i=this.getRedisConnection(),u=this.config.prefix??"svelar",n=new s.Worker(e,async o=>{let c=o.data,d=t.resolve(c.jobClass,c.payload);d.attempts=o.attemptsMade+1,await d.handle()},{connection:i,prefix:u,concurrency:a?.concurrency??1});return n.on("failed",async(o,c)=>{let d=o?.data;if(d)try{let x=t.resolve(d.jobClass,d.payload);o.attemptsMade>=(o.opts?.attempts??3)&&(x.failed(c),await r.store({id:o.id,jobClass:d.jobClass,payload:d.payload,queue:e,attempts:o.attemptsMade,maxAttempts:o.opts?.attempts??3,availableAt:Date.now(),createdAt:o.timestamp??Date.now(),job:x},c))}catch{console.error("[Queue] Failed to resolve job for failure handler:",c.message)}}),n}},D=class{table="svelar_failed_jobs";async getConnection(){let{Connection:e}=await Promise.resolve().then(()=>(y(),b));return e}async store(e,t){try{await(await this.getConnection()).raw(`INSERT INTO ${this.table} (id, queue, job_class, payload, exception, failed_at)
5
+ VALUES (?, ?, ?, ?, ?, ?)`,[crypto.randomUUID(),e.queue,e.jobClass,e.payload,t.stack??t.message,Math.floor(Date.now()/1e3)])}catch{console.error("[Queue] Could not persist failed job (run migration to create svelar_failed_jobs table)")}}async all(){return(await(await this.getConnection()).raw(`SELECT * FROM ${this.table} ORDER BY failed_at DESC`,[])??[]).map(r=>({id:r.id,queue:r.queue,jobClass:r.job_class,payload:r.payload,exception:r.exception,failedAt:r.failed_at}))}async find(e){let r=await(await this.getConnection()).raw(`SELECT * FROM ${this.table} WHERE id = ? LIMIT 1`,[e]);if(!r||r.length===0)return null;let a=r[0];return{id:a.id,queue:a.queue,jobClass:a.job_class,payload:a.payload,exception:a.exception,failedAt:a.failed_at}}async forget(e){return await(await this.getConnection()).raw(`DELETE FROM ${this.table} WHERE id = ?`,[e]),!0}async flush(){let e=await this.getConnection(),r=(await e.raw(`SELECT COUNT(*) as count FROM ${this.table}`,[]))?.[0]?.count??0;return await e.raw(`DELETE FROM ${this.table}`,[]),r}},q=class{jobs=new Map;register(e){this.jobs.set(e.name,e)}registerAll(e){for(let t of e)this.register(t)}resolve(e,t){let r=this.jobs.get(e);if(!r)throw new Error(`Job class "${e}" is not registered. Call Queue.register(${e}) in your app bootstrap. Registered jobs: [${[...this.jobs.keys()].join(", ")}]`);let a=Object.create(r.prototype);a.attempts=0,a.maxAttempts=3,a.retryDelay=60,a.queue="default";try{let s=JSON.parse(t);a.restore(s)}catch{}return a}has(e){return this.jobs.has(e)}},E=class{config={default:"sync",connections:{sync:{driver:"sync"}}};drivers=new Map;processing=!1;jobRegistry=new q;failedStore=new D;_activeWorker=null;configure(e){this.config=e,this.drivers.clear()}register(e){this.jobRegistry.register(e)}registerAll(e){this.jobRegistry.registerAll(e)}async dispatch(e,t){let r=this.config.default,a=this.config.connections[r],s=this.resolveDriver(r);t?.queue&&(e.queue=t.queue),t?.maxAttempts!==void 0&&(e.maxAttempts=t.maxAttempts);let i={id:crypto.randomUUID(),jobClass:e.constructor.name,payload:e.serialize(),queue:e.queue??a?.queue??"default",attempts:0,maxAttempts:e.maxAttempts,availableAt:Date.now()+(t?.delay??0)*1e3,createdAt:Date.now(),job:e};await s.push(i)}async dispatchSync(e){let t=new h,r={id:crypto.randomUUID(),jobClass:e.constructor.name,payload:e.serialize(),queue:e.queue,attempts:0,maxAttempts:e.maxAttempts,availableAt:Date.now(),createdAt:Date.now(),job:e};await t.push(r)}async chain(e,t){if(e.length===0)return;let r=new A(e);t?.queue&&(r.queue=t.queue),t?.maxAttempts!==void 0&&(r.maxAttempts=t.maxAttempts),await this.dispatch(r,t)}async work(e){let t=this.config.default,r=this.resolveDriver(t),a=e?.queue??"default";if(r instanceof f){let n=await r.createWorker(a,this.jobRegistry,this.failedStore,{concurrency:e?.concurrency??1});return this.processing=!0,this._activeWorker=n,await new Promise(o=>{let c=()=>{this.processing?setTimeout(c,500):n.close().then(o).catch(o)};c()}),0}let s=e?.maxJobs??1/0,i=(e?.sleep??1)*1e3,u=0;for(this.processing=!0;this.processing&&u<s;){let n=await r.pop(a);if(!n){if(s===1/0){await new Promise(o=>setTimeout(o,i));continue}break}n.attempts++,n.job.attempts=n.attempts;try{await n.job.handle(),u++,r instanceof m&&await r.delete(n.id)}catch(o){if(n.attempts<n.maxAttempts){n.job.retrying(n.attempts);let c=n.job.retryDelay??60;r instanceof m?await r.release(n.id,c):(n.availableAt=Date.now()+c*1e3,await r.push(n))}else n.job.failed(o),await this.failedStore.store(n,o),r instanceof m&&await r.delete(n.id)}}return u}async stop(){this.processing=!1,this._activeWorker&&(await this._activeWorker.close(),this._activeWorker=null)}async size(e){return this.resolveDriver(this.config.default).size(e)}async clear(e){return this.resolveDriver(this.config.default).clear(e)}async failed(){return this.failedStore.all()}async retry(e){let t=await this.failedStore.find(e);if(!t)return!1;let r=this.jobRegistry.resolve(t.jobClass,t.payload);return r.queue=t.queue,await this.dispatch(r,{queue:t.queue}),await this.failedStore.forget(e),!0}async retryAll(){let e=await this.failedStore.all(),t=0;for(let r of e)try{let a=this.jobRegistry.resolve(r.jobClass,r.payload);a.queue=r.queue,await this.dispatch(a,{queue:r.queue}),await this.failedStore.forget(r.id),t++}catch{}return t}async forgetFailed(e){return this.failedStore.forget(e)}async flushFailed(){return this.failedStore.flush()}resolveDriver(e){if(this.drivers.has(e))return this.drivers.get(e);let t=this.config.connections[e];if(!t)throw new Error(`Queue connection "${e}" is not defined.`);let r;switch(t.driver){case"sync":r=new h;break;case"memory":r=new C;break;case"database":r=new m(t.table??"svelar_jobs",this.jobRegistry);break;case"redis":r=new f(t,this.jobRegistry);break;default:throw new Error(`Unknown queue driver: ${t.driver}`)}return this.drivers.set(e,r),r}},A=class extends v{remainingJobs;constructor(e){super(),this.remainingJobs=[...e],this.maxAttempts=1}async handle(){for(let e of this.remainingJobs){let t=null,r=!1;for(let a=1;a<=e.maxAttempts;a++){e.attempts=a;try{await e.handle(),r=!0;break}catch(s){t=s,a<e.maxAttempts&&(e.retrying(a),e.retryDelay>0&&await new Promise(i=>setTimeout(i,e.retryDelay*1e3)))}}if(!r&&t)throw e.failed(t),new Error(`Chain stopped: ${e.constructor.name} failed after ${e.maxAttempts} attempt(s). Remaining jobs: [${this.remainingJobs.slice(this.remainingJobs.indexOf(e)+1).map(a=>a.constructor.name).join(", ")}]`)}}serialize(){return JSON.stringify({jobs:this.remainingJobs.map(e=>({jobClass:e.constructor.name,payload:e.serialize()}))})}},J=p("svelar.queue",()=>new E);export{v as Job,J as Queue};
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Svelar Repository
3
+ *
4
+ * Base repository classes that abstract data access from business logic.
5
+ * Repositories wrap Model queries and provide a clean interface for
6
+ * services and actions to work with.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Repository } from 'svelar/repositories';
11
+ * import { User } from '../models/User';
12
+ *
13
+ * class UserRepository extends Repository<User> {
14
+ * model() {
15
+ * return User;
16
+ * }
17
+ *
18
+ * async findByEmail(email: string): Promise<User | null> {
19
+ * return this.query().where('email', email).first();
20
+ * }
21
+ *
22
+ * async findActive(): Promise<User[]> {
23
+ * return this.query()
24
+ * .where('active', true)
25
+ * .orderBy('name')
26
+ * .get();
27
+ * }
28
+ *
29
+ * async findWithPosts(id: number): Promise<User | null> {
30
+ * return this.query()
31
+ * .with('posts')
32
+ * .find(id);
33
+ * }
34
+ * }
35
+ * ```
36
+ */
37
+ export interface PaginationMeta {
38
+ total: number;
39
+ page: number;
40
+ perPage: number;
41
+ lastPage: number;
42
+ hasNextPage: boolean;
43
+ hasPrevPage: boolean;
44
+ }
45
+ export interface PaginatedResult<T> {
46
+ data: T[];
47
+ meta: PaginationMeta;
48
+ }
49
+ export interface SortOption {
50
+ column: string;
51
+ direction: 'asc' | 'desc';
52
+ }
53
+ export interface FilterCriteria {
54
+ column: string;
55
+ operator: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'like' | 'in' | 'not_in';
56
+ value: any;
57
+ }
58
+ export declare abstract class Repository<TModel = any> {
59
+ /**
60
+ * Return the Model class this repository works with
61
+ */
62
+ abstract model(): any;
63
+ /**
64
+ * Get a fresh query builder for the model
65
+ */
66
+ protected query(): any;
67
+ /**
68
+ * Get all records
69
+ */
70
+ all(): Promise<TModel[]>;
71
+ /**
72
+ * Find a record by primary key
73
+ */
74
+ findById(id: any): Promise<TModel | null>;
75
+ /**
76
+ * Find a record by primary key or throw
77
+ */
78
+ findByIdOrFail(id: any): Promise<TModel>;
79
+ /**
80
+ * Find records matching a where clause
81
+ */
82
+ findWhere(column: string, value: any): Promise<TModel[]>;
83
+ /**
84
+ * Find the first record matching a where clause
85
+ */
86
+ findFirstWhere(column: string, value: any): Promise<TModel | null>;
87
+ /**
88
+ * Create a new record
89
+ */
90
+ create(attributes: Record<string, any>): Promise<TModel>;
91
+ /**
92
+ * Update a record by primary key
93
+ */
94
+ update(id: any, attributes: Record<string, any>): Promise<TModel>;
95
+ /**
96
+ * Delete a record by primary key
97
+ */
98
+ delete(id: any): Promise<void>;
99
+ /**
100
+ * Create multiple records
101
+ */
102
+ createMany(items: Record<string, any>[]): Promise<TModel[]>;
103
+ /**
104
+ * Delete records matching a where clause
105
+ */
106
+ deleteWhere(column: string, value: any): Promise<number>;
107
+ /**
108
+ * Count all records
109
+ */
110
+ count(): Promise<number>;
111
+ /**
112
+ * Count records matching a where clause
113
+ */
114
+ countWhere(column: string, value: any): Promise<number>;
115
+ /**
116
+ * Check if a record exists with the given criteria
117
+ */
118
+ exists(column: string, value: any): Promise<boolean>;
119
+ /**
120
+ * Paginate results
121
+ */
122
+ paginate(page?: number, perPage?: number): Promise<PaginatedResult<TModel>>;
123
+ /**
124
+ * Get the latest records
125
+ */
126
+ latest(limit?: number, column?: string): Promise<TModel[]>;
127
+ /**
128
+ * Get the oldest records
129
+ */
130
+ oldest(limit?: number, column?: string): Promise<TModel[]>;
131
+ /**
132
+ * Find or create a record
133
+ */
134
+ firstOrCreate(searchAttributes: Record<string, any>, createAttributes?: Record<string, any>): Promise<TModel>;
135
+ /**
136
+ * Update or create a record
137
+ */
138
+ updateOrCreate(searchAttributes: Record<string, any>, updateAttributes: Record<string, any>): Promise<TModel>;
139
+ /**
140
+ * Apply multiple filter criteria
141
+ */
142
+ filter(criteria: FilterCriteria[], sort?: SortOption): Promise<TModel[]>;
143
+ /**
144
+ * Pluck a single column from all records
145
+ */
146
+ pluck(column: string): Promise<any[]>;
147
+ }
@@ -0,0 +1 @@
1
+ var o=class{query(){return this.model().query()}async all(){return this.model().all()}async findById(e){return this.model().find(e)}async findByIdOrFail(e){return this.model().findOrFail(e)}async findWhere(e,t){return this.model().where(e,t).get()}async findFirstWhere(e,t){return this.model().where(e,t).first()}async create(e){return this.model().create(e)}async update(e,t){let r=await this.findByIdOrFail(e);return await r.update(t),r}async delete(e){await(await this.findByIdOrFail(e)).delete()}async createMany(e){let t=[];for(let r of e)t.push(await this.create(r));return t}async deleteWhere(e,t){return this.query().where(e,t).delete()}async count(){return this.model().count()}async countWhere(e,t){return this.query().where(e,t).count()}async exists(e,t){return this.query().where(e,t).exists()}async paginate(e=1,t=15){let r=await this.query().paginate(e,t);return{data:r.data,meta:{total:r.total,page:r.currentPage,perPage:r.perPage,lastPage:r.lastPage,hasNextPage:r.currentPage<r.lastPage,hasPrevPage:r.currentPage>1}}}async latest(e,t="created_at"){let r=this.query().orderBy(t,"desc");return e&&r.limit(e),r.get()}async oldest(e,t="created_at"){let r=this.query().orderBy(t,"asc");return e&&r.limit(e),r.get()}async firstOrCreate(e,t){let r=Object.entries(e),a=this.query();for(let[i,s]of r)a=a.where(i,s);let n=await a.first();return n||this.create({...e,...t})}async updateOrCreate(e,t){let r=Object.entries(e),a=this.query();for(let[i,s]of r)a=a.where(i,s);let n=await a.first();return n?(await n.update(t),n):this.create({...e,...t})}async filter(e,t){let r=this.query();for(let{column:a,operator:n,value:i}of e)switch(n){case"in":r=r.whereIn(a,i);break;case"not_in":r=r.whereNotIn(a,i);break;case"like":r=r.where(a,"like",i);break;default:r=r.where(a,n,i)}return t&&(r=r.orderBy(t.column,t.direction)),r.get()}async pluck(e){return this.query().pluck(e)}};export{o as Repository};