ai-workflows 2.1.1 → 2.3.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 (211) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -1
  3. package/README.md +305 -184
  4. package/dist/barrier.d.ts +159 -0
  5. package/dist/barrier.d.ts.map +1 -0
  6. package/dist/barrier.js +377 -0
  7. package/dist/barrier.js.map +1 -0
  8. package/dist/cascade-context.d.ts +149 -0
  9. package/dist/cascade-context.d.ts.map +1 -0
  10. package/dist/cascade-context.js +324 -0
  11. package/dist/cascade-context.js.map +1 -0
  12. package/dist/cascade-executor.d.ts +196 -0
  13. package/dist/cascade-executor.d.ts.map +1 -0
  14. package/dist/cascade-executor.js +384 -0
  15. package/dist/cascade-executor.js.map +1 -0
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +27 -8
  18. package/dist/context.js.map +1 -1
  19. package/dist/cron-parser.d.ts +65 -0
  20. package/dist/cron-parser.d.ts.map +1 -0
  21. package/dist/cron-parser.js +294 -0
  22. package/dist/cron-parser.js.map +1 -0
  23. package/dist/cron-scheduler.d.ts +117 -0
  24. package/dist/cron-scheduler.d.ts.map +1 -0
  25. package/dist/cron-scheduler.js +176 -0
  26. package/dist/cron-scheduler.js.map +1 -0
  27. package/dist/database-context.d.ts +184 -0
  28. package/dist/database-context.d.ts.map +1 -0
  29. package/dist/database-context.js +428 -0
  30. package/dist/database-context.js.map +1 -0
  31. package/dist/dependency-graph.d.ts +157 -0
  32. package/dist/dependency-graph.d.ts.map +1 -0
  33. package/dist/dependency-graph.js +382 -0
  34. package/dist/dependency-graph.js.map +1 -0
  35. package/dist/digital-objects-adapter.d.ts +159 -0
  36. package/dist/digital-objects-adapter.d.ts.map +1 -0
  37. package/dist/digital-objects-adapter.js +229 -0
  38. package/dist/digital-objects-adapter.js.map +1 -0
  39. package/dist/durable-execution-cloudflare.d.ts +427 -0
  40. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  41. package/dist/durable-execution-cloudflare.js +510 -0
  42. package/dist/durable-execution-cloudflare.js.map +1 -0
  43. package/dist/durable-execution.d.ts +482 -0
  44. package/dist/durable-execution.d.ts.map +1 -0
  45. package/dist/durable-execution.js +594 -0
  46. package/dist/durable-execution.js.map +1 -0
  47. package/dist/durable-workflow.d.ts +176 -0
  48. package/dist/durable-workflow.d.ts.map +1 -0
  49. package/dist/durable-workflow.js +552 -0
  50. package/dist/durable-workflow.js.map +1 -0
  51. package/dist/every.d.ts +31 -2
  52. package/dist/every.d.ts.map +1 -1
  53. package/dist/every.js +63 -32
  54. package/dist/every.js.map +1 -1
  55. package/dist/graph/index.d.ts +8 -0
  56. package/dist/graph/index.d.ts.map +1 -0
  57. package/dist/graph/index.js +8 -0
  58. package/dist/graph/index.js.map +1 -0
  59. package/dist/graph/topological-sort.d.ts +121 -0
  60. package/dist/graph/topological-sort.d.ts.map +1 -0
  61. package/dist/graph/topological-sort.js +292 -0
  62. package/dist/graph/topological-sort.js.map +1 -0
  63. package/dist/index.d.ts +10 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/logger.d.ts +101 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +115 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/on.d.ts +35 -10
  72. package/dist/on.d.ts.map +1 -1
  73. package/dist/on.js +53 -19
  74. package/dist/on.js.map +1 -1
  75. package/dist/runtime.d.ts +169 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +275 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/send.d.ts.map +1 -1
  80. package/dist/send.js +4 -3
  81. package/dist/send.js.map +1 -1
  82. package/dist/telemetry.d.ts +150 -0
  83. package/dist/telemetry.d.ts.map +1 -0
  84. package/dist/telemetry.js +388 -0
  85. package/dist/telemetry.js.map +1 -0
  86. package/dist/timer-registry.d.ts +77 -0
  87. package/dist/timer-registry.d.ts.map +1 -0
  88. package/dist/timer-registry.js +154 -0
  89. package/dist/timer-registry.js.map +1 -0
  90. package/dist/types.d.ts +105 -6
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +17 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/worker/durable-step.d.ts +481 -0
  95. package/dist/worker/durable-step.d.ts.map +1 -0
  96. package/dist/worker/durable-step.js +606 -0
  97. package/dist/worker/durable-step.js.map +1 -0
  98. package/dist/worker/index.d.ts +106 -0
  99. package/dist/worker/index.d.ts.map +1 -0
  100. package/dist/worker/index.js +124 -0
  101. package/dist/worker/index.js.map +1 -0
  102. package/dist/worker/state-adapter.d.ts +230 -0
  103. package/dist/worker/state-adapter.d.ts.map +1 -0
  104. package/dist/worker/state-adapter.js +409 -0
  105. package/dist/worker/state-adapter.js.map +1 -0
  106. package/dist/worker/topological-executor.d.ts +282 -0
  107. package/dist/worker/topological-executor.d.ts.map +1 -0
  108. package/dist/worker/topological-executor.js +396 -0
  109. package/dist/worker/topological-executor.js.map +1 -0
  110. package/dist/worker/workflow-builder.d.ts +286 -0
  111. package/dist/worker/workflow-builder.d.ts.map +1 -0
  112. package/dist/worker/workflow-builder.js +565 -0
  113. package/dist/worker/workflow-builder.js.map +1 -0
  114. package/dist/worker.d.ts +800 -0
  115. package/dist/worker.d.ts.map +1 -0
  116. package/dist/worker.js +2428 -0
  117. package/dist/worker.js.map +1 -0
  118. package/dist/workflow-builder.d.ts +287 -0
  119. package/dist/workflow-builder.d.ts.map +1 -0
  120. package/dist/workflow-builder.js +762 -0
  121. package/dist/workflow-builder.js.map +1 -0
  122. package/dist/workflow.d.ts +14 -30
  123. package/dist/workflow.d.ts.map +1 -1
  124. package/dist/workflow.js +136 -292
  125. package/dist/workflow.js.map +1 -1
  126. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  127. package/examples/02-content-moderation-cascade.ts +454 -0
  128. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  129. package/examples/04-database-persistence.ts +518 -0
  130. package/examples/README.md +173 -0
  131. package/package.json +21 -4
  132. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  133. package/src/__tests__/durable-workflow.test.ts +297 -0
  134. package/src/barrier.ts +507 -0
  135. package/src/cascade-context.ts +495 -0
  136. package/src/cascade-executor.ts +588 -0
  137. package/src/context.ts +51 -17
  138. package/src/cron-parser.ts +347 -0
  139. package/src/cron-scheduler.ts +239 -0
  140. package/src/database-context.ts +658 -0
  141. package/src/dependency-graph.ts +518 -0
  142. package/src/digital-objects-adapter.ts +351 -0
  143. package/src/durable-execution-cloudflare.ts +855 -0
  144. package/src/durable-execution.ts +1042 -0
  145. package/src/durable-workflow.ts +717 -0
  146. package/src/every.ts +104 -35
  147. package/src/graph/index.ts +19 -0
  148. package/src/graph/topological-sort.ts +412 -0
  149. package/src/index.ts +147 -0
  150. package/src/logger.ts +148 -0
  151. package/src/on.ts +81 -26
  152. package/src/runtime.ts +436 -0
  153. package/src/send.ts +4 -5
  154. package/src/telemetry.ts +577 -0
  155. package/src/timer-registry.ts +179 -0
  156. package/src/types.ts +146 -10
  157. package/src/worker/durable-step.ts +976 -0
  158. package/src/worker/index.ts +216 -0
  159. package/src/worker/state-adapter.ts +589 -0
  160. package/src/worker/topological-executor.ts +625 -0
  161. package/src/worker/workflow-builder.ts +871 -0
  162. package/src/worker.ts +2906 -0
  163. package/src/workflow-builder.ts +1068 -0
  164. package/src/workflow.ts +199 -355
  165. package/test/barrier-join.test.ts +442 -0
  166. package/test/barrier-unhandled-rejections.test.ts +359 -0
  167. package/test/cascade-context.test.ts +390 -0
  168. package/test/cascade-executor.test.ts +852 -0
  169. package/test/cron-parser.test.ts +314 -0
  170. package/test/cron-scheduler.test.ts +291 -0
  171. package/test/database-context.test.ts +770 -0
  172. package/test/db-provider-adapter.test.ts +862 -0
  173. package/test/dependency-graph.test.ts +512 -0
  174. package/test/durable-execution-cloudflare.test.ts +606 -0
  175. package/test/durable-execution-in-process.test.ts +286 -0
  176. package/test/durable-execution.test.ts +247 -0
  177. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  178. package/test/graph/topological-sort.test.ts +586 -0
  179. package/test/integration.test.ts +442 -0
  180. package/test/rpc-surface.test.ts +946 -0
  181. package/test/runtime.test.ts +262 -0
  182. package/test/schedule-timer-cleanup.test.ts +353 -0
  183. package/test/send-race-conditions.test.ts +400 -0
  184. package/test/type-safety-every.test.ts +303 -0
  185. package/test/worker/durable-cascade.test.ts +1117 -0
  186. package/test/worker/durable-step.test.ts +723 -0
  187. package/test/worker/topological-executor.test.ts +1240 -0
  188. package/test/worker/workflow-builder.test.ts +1067 -0
  189. package/test/worker.test.ts +608 -0
  190. package/test/workflow-builder.test.ts +1670 -0
  191. package/test/workflow-cron.test.ts +256 -0
  192. package/test/workflow-state-adapter.test.ts +923 -0
  193. package/test/workflow.test.ts +25 -22
  194. package/tsconfig.json +3 -1
  195. package/vitest.config.ts +38 -1
  196. package/vitest.workers.config.ts +44 -0
  197. package/wrangler.jsonc +22 -0
  198. package/.turbo/turbo-test.log +0 -7
  199. package/src/context.js +0 -83
  200. package/src/every.js +0 -267
  201. package/src/index.js +0 -71
  202. package/src/on.js +0 -79
  203. package/src/send.js +0 -111
  204. package/src/types.js +0 -4
  205. package/src/workflow.js +0 -455
  206. package/test/context.test.js +0 -116
  207. package/test/every.test.js +0 -282
  208. package/test/on.test.js +0 -80
  209. package/test/send.test.js +0 -89
  210. package/test/workflow.test.js +0 -224
  211. package/vitest.config.js +0 -7
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Global timer registry for workflow timers
3
+ *
4
+ * This module tracks all active timers across workflows to enable:
5
+ * - Timer cleanup when workflows are destroyed
6
+ * - Global timer count for debugging
7
+ * - Process exit cleanup
8
+ */
9
+
10
+ interface TimerEntry {
11
+ timerId: NodeJS.Timeout
12
+ workflowId: string
13
+ registeredAt: number
14
+ }
15
+
16
+ /**
17
+ * Global registry of active timers
18
+ */
19
+ const activeTimers: Map<string, TimerEntry> = new Map()
20
+
21
+ /**
22
+ * Counter for generating unique timer IDs
23
+ */
24
+ let timerCounter = 0
25
+
26
+ /**
27
+ * Generate a unique timer ID
28
+ */
29
+ function generateTimerId(workflowId: string): string {
30
+ return `${workflowId}-timer-${++timerCounter}`
31
+ }
32
+
33
+ /**
34
+ * Register a timer in the global registry
35
+ */
36
+ export function registerTimer(workflowId: string, timerId: NodeJS.Timeout): string {
37
+ const id = generateTimerId(workflowId)
38
+ activeTimers.set(id, {
39
+ timerId,
40
+ workflowId,
41
+ registeredAt: Date.now(),
42
+ })
43
+ return id
44
+ }
45
+
46
+ /**
47
+ * Unregister a timer from the global registry
48
+ */
49
+ export function unregisterTimer(id: string): boolean {
50
+ const entry = activeTimers.get(id)
51
+ if (entry) {
52
+ clearInterval(entry.timerId)
53
+ activeTimers.delete(id)
54
+ return true
55
+ }
56
+ return false
57
+ }
58
+
59
+ /**
60
+ * Get all timer IDs for a specific workflow
61
+ */
62
+ export function getTimerIdsForWorkflow(workflowId: string): string[] {
63
+ const ids: string[] = []
64
+ for (const [id, entry] of activeTimers) {
65
+ if (entry.workflowId === workflowId) {
66
+ ids.push(id)
67
+ }
68
+ }
69
+ return ids
70
+ }
71
+
72
+ /**
73
+ * Clear all timers for a specific workflow
74
+ */
75
+ export function clearTimersForWorkflow(workflowId: string): number {
76
+ const ids = getTimerIdsForWorkflow(workflowId)
77
+ for (const id of ids) {
78
+ unregisterTimer(id)
79
+ }
80
+ return ids.length
81
+ }
82
+
83
+ /**
84
+ * Get the count of all active timers
85
+ */
86
+ export function getActiveTimerCount(): number {
87
+ return activeTimers.size
88
+ }
89
+
90
+ /**
91
+ * Clear all timers from all workflows
92
+ */
93
+ export function clearAllTimers(): void {
94
+ for (const [id, entry] of activeTimers) {
95
+ clearInterval(entry.timerId)
96
+ activeTimers.delete(id)
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Timer registry object for external access
102
+ */
103
+ export const timerRegistry = {
104
+ register: registerTimer,
105
+ unregister: unregisterTimer,
106
+ getTimerIdsForWorkflow,
107
+ clearForWorkflow: clearTimersForWorkflow,
108
+ getActiveCount: getActiveTimerCount,
109
+ clearAll: clearAllTimers,
110
+ getAll: () => Array.from(activeTimers.entries()),
111
+ }
112
+
113
+ // Global registration is opt-in to avoid namespace pollution
114
+ declare const global: typeof globalThis
115
+
116
+ let globalRegistrationEnabled = false
117
+
118
+ function registerGlobalFunctions(target: typeof globalThis) {
119
+ ;(target as unknown as Record<string, unknown>)['getActiveWorkflowTimerCount'] =
120
+ getActiveTimerCount
121
+ ;(target as unknown as Record<string, unknown>)['clearAllWorkflowTimers'] = clearAllTimers
122
+ }
123
+
124
+ /**
125
+ * Enable global timer registry functions.
126
+ *
127
+ * This opt-in function registers `getActiveWorkflowTimerCount` and `clearAllWorkflowTimers`
128
+ * on the global scope for debugging and cleanup purposes.
129
+ *
130
+ * Call this function explicitly if you need global access to timer management:
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * import { enableGlobalTimerRegistry } from 'ai-workflows'
135
+ *
136
+ * // Enable global registration
137
+ * enableGlobalTimerRegistry()
138
+ *
139
+ * // Now these are available globally:
140
+ * // globalThis.getActiveWorkflowTimerCount()
141
+ * // globalThis.clearAllWorkflowTimers()
142
+ * ```
143
+ */
144
+ export function enableGlobalTimerRegistry(): void {
145
+ if (globalRegistrationEnabled) return
146
+ globalRegistrationEnabled = true
147
+
148
+ // Register on globalThis (standard)
149
+ if (typeof globalThis !== 'undefined') {
150
+ registerGlobalFunctions(globalThis)
151
+ }
152
+
153
+ // Also register on global (Node.js specific, used in some test environments)
154
+ if (typeof global !== 'undefined' && global !== globalThis) {
155
+ registerGlobalFunctions(global)
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Check if global timer registry is enabled
161
+ */
162
+ export function isGlobalTimerRegistryEnabled(): boolean {
163
+ return globalRegistrationEnabled
164
+ }
165
+
166
+ // Register process exit handlers for cleanup
167
+ let cleanupRegistered = false
168
+
169
+ export function registerProcessCleanup(): void {
170
+ if (cleanupRegistered) return
171
+ cleanupRegistered = true
172
+
173
+ const cleanup = () => {
174
+ clearAllTimers()
175
+ }
176
+
177
+ process.on('exit', cleanup)
178
+ process.on('beforeExit', cleanup)
179
+ }
package/src/types.ts CHANGED
@@ -2,12 +2,14 @@
2
2
  * Types for ai-workflows
3
3
  */
4
4
 
5
+ import type { WorkflowContextType } from '@org.ai/types'
6
+
5
7
  /**
6
8
  * Handler function with source code for remote execution
7
9
  */
8
10
  export interface HandlerFunction<T = unknown> {
9
11
  /** The actual function */
10
- fn: (...args: any[]) => void | Promise<void>
12
+ fn: (...args: unknown[]) => void | Promise<void>
11
13
  /** Source code string for remote execution */
12
14
  source: string
13
15
  /** Handler name (for debugging) */
@@ -21,6 +23,9 @@ export interface HandlerFunction<T = unknown> {
21
23
  * Generic order follows Promise<T> convention:
22
24
  * - TOutput (first) is what the handler returns
23
25
  * - TInput (second) is what the handler receives
26
+ *
27
+ * Note: Uses WorkflowContext (extends WorkflowContextType from @org.ai/types)
28
+ * for workflow-specific features like OnProxy, EveryProxy, getState, log.
24
29
  */
25
30
  export type EventHandler<TOutput = unknown, TInput = unknown> = (
26
31
  data: TInput,
@@ -30,19 +35,25 @@ export type EventHandler<TOutput = unknown, TInput = unknown> = (
30
35
  /**
31
36
  * Schedule handler function type
32
37
  */
33
- export type ScheduleHandler = (
34
- $: WorkflowContext
35
- ) => void | Promise<void>
38
+ export type ScheduleHandler = ($: WorkflowContext) => void | Promise<void>
36
39
 
37
40
  /**
38
- * Workflow context ($) passed to handlers
41
+ * Workflow context ($) passed to handlers.
42
+ * Extends WorkflowContextType from @org.ai/types with workflow-specific features.
39
43
  */
40
- export interface WorkflowContext {
44
+ export interface WorkflowContext extends WorkflowContextType {
41
45
  /**
42
- * Send an event (fire and forget, durable)
43
- * Confirms receipt but doesn't wait for result
46
+ * Track an event (fire and forget)
47
+ * Best effort, no confirmation, swallows errors
48
+ * Use for telemetry and non-critical events
44
49
  */
45
- send: <T = unknown>(event: string, data: T) => Promise<void>
50
+ track: (event: string, data: unknown) => void
51
+
52
+ /**
53
+ * Send an event (durable)
54
+ * Guaranteed delivery, returns trackable EventId
55
+ */
56
+ send: <T = unknown>(event: string, data: T) => string
46
57
 
47
58
  /**
48
59
  * Do an action (durable, waits for result)
@@ -245,7 +256,62 @@ export type EveryProxy = {
245
256
  weeks: (value: number) => (handler: ScheduleHandler) => void
246
257
 
247
258
  // Index signature for dynamic patterns
248
- [key: string]: ((handler: ScheduleHandler) => void) | ((value: number) => (handler: ScheduleHandler) => void) | DayScheduleProxy
259
+ [key: string]:
260
+ | ((handler: ScheduleHandler) => void)
261
+ | ((value: number) => (handler: ScheduleHandler) => void)
262
+ | DayScheduleProxy
263
+ }
264
+
265
+ /**
266
+ * Callable target type for EveryProxy
267
+ * Used as a properly-typed Proxy target that supports both call and property access
268
+ */
269
+ export type EveryProxyTarget = {
270
+ (description: string, handler: ScheduleHandler): void
271
+ }
272
+
273
+ /**
274
+ * ProxyHandler type for OnProxy
275
+ * Provides proper typing for the two-level noun.event proxy pattern
276
+ */
277
+ export interface OnProxyHandler extends ProxyHandler<Record<string, NounEventProxy>> {
278
+ get(target: Record<string, NounEventProxy>, noun: string, receiver: unknown): NounEventProxy
279
+ }
280
+
281
+ /**
282
+ * ProxyHandler type for the inner noun level (event accessors)
283
+ */
284
+ export interface NounEventProxyHandler
285
+ extends ProxyHandler<
286
+ Record<string, (handler: EventHandler, dependencies?: DependencyConfig) => void>
287
+ > {
288
+ get(
289
+ target: Record<string, (handler: EventHandler, dependencies?: DependencyConfig) => void>,
290
+ event: string,
291
+ receiver: unknown
292
+ ): (handler: EventHandler, dependencies?: DependencyConfig) => void
293
+ }
294
+
295
+ /**
296
+ * ProxyHandler type for EveryProxy
297
+ * Handles both function calls and property access for schedule patterns
298
+ */
299
+ export interface EveryProxyHandler extends ProxyHandler<EveryProxyTarget> {
300
+ get(target: EveryProxyTarget, prop: string, receiver: unknown): unknown
301
+ apply(target: EveryProxyTarget, thisArg: unknown, args: [string, ScheduleHandler]): void
302
+ }
303
+
304
+ /**
305
+ * ProxyHandler type for day schedule patterns with time modifiers
306
+ * Handles $.every.Monday.at9am pattern
307
+ */
308
+ export interface DayScheduleProxyHandler extends ProxyHandler<(handler: ScheduleHandler) => void> {
309
+ get(
310
+ target: (handler: ScheduleHandler) => void,
311
+ timeKey: string,
312
+ receiver: unknown
313
+ ): ((handler: ScheduleHandler) => void) | undefined
314
+ apply(target: (handler: ScheduleHandler) => void, thisArg: unknown, args: [ScheduleHandler]): void
249
315
  }
250
316
 
251
317
  /**
@@ -270,6 +336,30 @@ export interface WorkflowHistoryEntry {
270
336
  data?: unknown
271
337
  }
272
338
 
339
+ /**
340
+ * Dependency type: hard (must succeed) or soft (can proceed on failure)
341
+ */
342
+ export type DependencyType = 'hard' | 'soft'
343
+
344
+ /**
345
+ * Configuration for step dependencies
346
+ */
347
+ export interface DependencyConfig {
348
+ /**
349
+ * Step(s) that must complete before this step runs
350
+ * Can be a single step ID or array of step IDs
351
+ * Format: 'Noun.event' (e.g., 'Step1.complete')
352
+ */
353
+ dependsOn: string | string[]
354
+
355
+ /**
356
+ * Type of dependency (default: 'hard')
357
+ * - 'hard': Dependency must complete successfully
358
+ * - 'soft': Step can proceed even if dependency fails
359
+ */
360
+ type?: DependencyType
361
+ }
362
+
273
363
  /**
274
364
  * Event registration with source
275
365
  */
@@ -278,6 +368,8 @@ export interface EventRegistration {
278
368
  event: string
279
369
  handler: EventHandler
280
370
  source: string
371
+ /** Optional dependency configuration for workflow step ordering */
372
+ dependencies?: DependencyConfig
281
373
  }
282
374
 
283
375
  /**
@@ -289,6 +381,50 @@ export interface ScheduleRegistration {
289
381
  source: string
290
382
  }
291
383
 
384
+ /**
385
+ * Time-based interval types (singular form)
386
+ * Used as discriminant values in ScheduleInterval
387
+ */
388
+ export type TimeIntervalType = 'second' | 'minute' | 'hour' | 'day' | 'week'
389
+
390
+ /**
391
+ * Mapping from plural unit names to their singular interval types
392
+ * Used for type-safe conversion in every.units(value) patterns
393
+ */
394
+ export type PluralUnitMapping = {
395
+ seconds: 'second'
396
+ minutes: 'minute'
397
+ hours: 'hour'
398
+ days: 'day'
399
+ weeks: 'week'
400
+ }
401
+
402
+ /**
403
+ * Plural unit keys
404
+ */
405
+ export type PluralUnitKey = keyof PluralUnitMapping
406
+
407
+ /**
408
+ * Type guard to check if a string is a valid plural unit key
409
+ */
410
+ export function isPluralUnitKey(key: string): key is PluralUnitKey {
411
+ return (
412
+ key === 'seconds' || key === 'minutes' || key === 'hours' || key === 'days' || key === 'weeks'
413
+ )
414
+ }
415
+
416
+ /**
417
+ * Constant mapping object with strict typing
418
+ * Maps plural forms to their singular interval type values
419
+ */
420
+ export const PLURAL_UNITS: Readonly<PluralUnitMapping> = {
421
+ seconds: 'second',
422
+ minutes: 'minute',
423
+ hours: 'hour',
424
+ days: 'day',
425
+ weeks: 'week',
426
+ } as const
427
+
292
428
  /**
293
429
  * Schedule intervals
294
430
  */