@dxos/plugin-routine 0.0.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 (403) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +475 -0
  3. package/README.md +15 -0
  4. package/dist/lib/neutral/RoutineArticle-UNTMV4CC.mjs +95 -0
  5. package/dist/lib/neutral/RoutineArticle-UNTMV4CC.mjs.map +7 -0
  6. package/dist/lib/neutral/RoutineCompanion-ZZ6WMY7I.mjs +249 -0
  7. package/dist/lib/neutral/RoutineCompanion-ZZ6WMY7I.mjs.map +7 -0
  8. package/dist/lib/neutral/RoutinePlugin.mjs +10 -0
  9. package/dist/lib/neutral/RoutinePlugin.mjs.map +7 -0
  10. package/dist/lib/neutral/RoutinePlugin.node.mjs +1133 -0
  11. package/dist/lib/neutral/RoutinePlugin.node.mjs.map +7 -0
  12. package/dist/lib/neutral/RoutinePlugin.workerd.mjs +36 -0
  13. package/dist/lib/neutral/RoutinePlugin.workerd.mjs.map +7 -0
  14. package/dist/lib/neutral/RoutineSettings-ZJOM4F3S.mjs +48 -0
  15. package/dist/lib/neutral/RoutineSettings-ZJOM4F3S.mjs.map +7 -0
  16. package/dist/lib/neutral/RoutineTraceCompanion-5U6R7LJE.mjs +236 -0
  17. package/dist/lib/neutral/RoutineTraceCompanion-5U6R7LJE.mjs.map +7 -0
  18. package/dist/lib/neutral/SkillArticle-WWDNKW2Z.mjs +27 -0
  19. package/dist/lib/neutral/SkillArticle-WWDNKW2Z.mjs.map +7 -0
  20. package/dist/lib/neutral/app-graph-builder-FALWLZTI.mjs +90 -0
  21. package/dist/lib/neutral/app-graph-builder-FALWLZTI.mjs.map +7 -0
  22. package/dist/lib/neutral/capabilities/index.mjs +25 -0
  23. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  24. package/dist/lib/neutral/capabilities/node.mjs +19 -0
  25. package/dist/lib/neutral/capabilities/node.mjs.map +7 -0
  26. package/dist/lib/neutral/chunk-674LDIUJ.mjs +9 -0
  27. package/dist/lib/neutral/chunk-674LDIUJ.mjs.map +7 -0
  28. package/dist/lib/neutral/chunk-CNYAPXUW.mjs +65 -0
  29. package/dist/lib/neutral/chunk-CNYAPXUW.mjs.map +7 -0
  30. package/dist/lib/neutral/chunk-HLO4LYXT.mjs +44 -0
  31. package/dist/lib/neutral/chunk-HLO4LYXT.mjs.map +7 -0
  32. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  33. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  34. package/dist/lib/neutral/chunk-ODQDVFMZ.mjs +8 -0
  35. package/dist/lib/neutral/chunk-ODQDVFMZ.mjs.map +7 -0
  36. package/dist/lib/neutral/chunk-R7J2MU2M.mjs +263 -0
  37. package/dist/lib/neutral/chunk-R7J2MU2M.mjs.map +7 -0
  38. package/dist/lib/neutral/chunk-WLNL77C7.mjs +14 -0
  39. package/dist/lib/neutral/chunk-WLNL77C7.mjs.map +7 -0
  40. package/dist/lib/neutral/components/index.mjs +1921 -0
  41. package/dist/lib/neutral/components/index.mjs.map +7 -0
  42. package/dist/lib/neutral/containers/index.mjs +17 -0
  43. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  44. package/dist/lib/neutral/create-object-V23KVDMJ.mjs +25 -0
  45. package/dist/lib/neutral/create-object-V23KVDMJ.mjs.map +7 -0
  46. package/dist/lib/neutral/create-routine-I67BYJA2.mjs +38 -0
  47. package/dist/lib/neutral/create-routine-I67BYJA2.mjs.map +7 -0
  48. package/dist/lib/neutral/create-trigger-from-template-U3KNS77V.mjs +70 -0
  49. package/dist/lib/neutral/create-trigger-from-template-U3KNS77V.mjs.map +7 -0
  50. package/dist/lib/neutral/hooks/index.mjs +47 -0
  51. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  52. package/dist/lib/neutral/index.mjs +27 -0
  53. package/dist/lib/neutral/index.mjs.map +7 -0
  54. package/dist/lib/neutral/layer-specs-ADQCTTRT.mjs +135 -0
  55. package/dist/lib/neutral/layer-specs-ADQCTTRT.mjs.map +7 -0
  56. package/dist/lib/neutral/meta.json +1 -0
  57. package/dist/lib/neutral/meta.mjs +8 -0
  58. package/dist/lib/neutral/meta.mjs.map +7 -0
  59. package/dist/lib/neutral/navigation-resolver-KQIWUH6F.mjs +18 -0
  60. package/dist/lib/neutral/navigation-resolver-KQIWUH6F.mjs.map +7 -0
  61. package/dist/lib/neutral/operation-handler-QKLJSDCQ.mjs +13 -0
  62. package/dist/lib/neutral/operation-handler-QKLJSDCQ.mjs.map +7 -0
  63. package/dist/lib/neutral/operations/index.mjs +8 -0
  64. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  65. package/dist/lib/neutral/plugin.mjs +16 -0
  66. package/dist/lib/neutral/plugin.mjs.map +7 -0
  67. package/dist/lib/neutral/react-surface-GMO7I7YE.mjs +67 -0
  68. package/dist/lib/neutral/react-surface-GMO7I7YE.mjs.map +7 -0
  69. package/dist/lib/neutral/registry-sync-PHBU6CH2.mjs +86 -0
  70. package/dist/lib/neutral/registry-sync-PHBU6CH2.mjs.map +7 -0
  71. package/dist/lib/neutral/run-routine-HJWXIWLJ.mjs +37 -0
  72. package/dist/lib/neutral/run-routine-HJWXIWLJ.mjs.map +7 -0
  73. package/dist/lib/neutral/templates-IIUCEBAX.mjs +49 -0
  74. package/dist/lib/neutral/templates-IIUCEBAX.mjs.map +7 -0
  75. package/dist/lib/neutral/testing.mjs +8 -0
  76. package/dist/lib/neutral/testing.mjs.map +7 -0
  77. package/dist/lib/neutral/translations.mjs +115 -0
  78. package/dist/lib/neutral/translations.mjs.map +7 -0
  79. package/dist/lib/neutral/trigger-runtime-controller-7FOTH2NP.mjs +77 -0
  80. package/dist/lib/neutral/trigger-runtime-controller-7FOTH2NP.mjs.map +7 -0
  81. package/dist/lib/neutral/types/index.mjs +17 -0
  82. package/dist/lib/neutral/types/index.mjs.map +7 -0
  83. package/dist/types/dx.config.d.ts +28 -0
  84. package/dist/types/dx.config.d.ts.map +1 -0
  85. package/dist/types/src/RoutinePlugin.d.ts +4 -0
  86. package/dist/types/src/RoutinePlugin.d.ts.map +1 -0
  87. package/dist/types/src/RoutinePlugin.node.d.ts +4 -0
  88. package/dist/types/src/RoutinePlugin.node.d.ts.map +1 -0
  89. package/dist/types/src/RoutinePlugin.test.d.ts +2 -0
  90. package/dist/types/src/RoutinePlugin.test.d.ts.map +1 -0
  91. package/dist/types/src/RoutinePlugin.workerd.d.ts +4 -0
  92. package/dist/types/src/RoutinePlugin.workerd.d.ts.map +1 -0
  93. package/dist/types/src/capabilities/app-graph-builder.d.ts +5 -0
  94. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  95. package/dist/types/src/capabilities/create-object.d.ts +5 -0
  96. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  97. package/dist/types/src/capabilities/create-object.test.d.ts +2 -0
  98. package/dist/types/src/capabilities/create-object.test.d.ts.map +1 -0
  99. package/dist/types/src/capabilities/index.d.ts +12 -0
  100. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  101. package/dist/types/src/capabilities/layer-specs.d.ts +5 -0
  102. package/dist/types/src/capabilities/layer-specs.d.ts.map +1 -0
  103. package/dist/types/src/capabilities/navigation-resolver.d.ts +5 -0
  104. package/dist/types/src/capabilities/navigation-resolver.d.ts.map +1 -0
  105. package/dist/types/src/capabilities/node.d.ts +9 -0
  106. package/dist/types/src/capabilities/node.d.ts.map +1 -0
  107. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  108. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  109. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  110. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  111. package/dist/types/src/capabilities/registry-sync.d.ts +21 -0
  112. package/dist/types/src/capabilities/registry-sync.d.ts.map +1 -0
  113. package/dist/types/src/capabilities/templates.d.ts +5 -0
  114. package/dist/types/src/capabilities/templates.d.ts.map +1 -0
  115. package/dist/types/src/capabilities/trigger-runtime-controller.d.ts +5 -0
  116. package/dist/types/src/capabilities/trigger-runtime-controller.d.ts.map +1 -0
  117. package/dist/types/src/capabilities/trigger-runtime-controller.test.d.ts +2 -0
  118. package/dist/types/src/capabilities/trigger-runtime-controller.test.d.ts.map +1 -0
  119. package/dist/types/src/commands/index.d.ts +2 -0
  120. package/dist/types/src/commands/index.d.ts.map +1 -0
  121. package/dist/types/src/commands/trigger/create/index.d.ts +25 -0
  122. package/dist/types/src/commands/trigger/create/index.d.ts.map +1 -0
  123. package/dist/types/src/commands/trigger/create/queue.d.ts +13 -0
  124. package/dist/types/src/commands/trigger/create/queue.d.ts.map +1 -0
  125. package/dist/types/src/commands/trigger/create/subscription.d.ts +15 -0
  126. package/dist/types/src/commands/trigger/create/subscription.d.ts.map +1 -0
  127. package/dist/types/src/commands/trigger/create/timer.d.ts +13 -0
  128. package/dist/types/src/commands/trigger/create/timer.d.ts.map +1 -0
  129. package/dist/types/src/commands/trigger/index.d.ts +57 -0
  130. package/dist/types/src/commands/trigger/index.d.ts.map +1 -0
  131. package/dist/types/src/commands/trigger/list.d.ts +7 -0
  132. package/dist/types/src/commands/trigger/list.d.ts.map +1 -0
  133. package/dist/types/src/commands/trigger/options.d.ts +10 -0
  134. package/dist/types/src/commands/trigger/options.d.ts.map +1 -0
  135. package/dist/types/src/commands/trigger/remove.d.ts +8 -0
  136. package/dist/types/src/commands/trigger/remove.d.ts.map +1 -0
  137. package/dist/types/src/commands/trigger/update/index.d.ts +28 -0
  138. package/dist/types/src/commands/trigger/update/index.d.ts.map +1 -0
  139. package/dist/types/src/commands/trigger/update/queue.d.ts +13 -0
  140. package/dist/types/src/commands/trigger/update/queue.d.ts.map +1 -0
  141. package/dist/types/src/commands/trigger/update/subscription.d.ts +15 -0
  142. package/dist/types/src/commands/trigger/update/subscription.d.ts.map +1 -0
  143. package/dist/types/src/commands/trigger/update/timer.d.ts +13 -0
  144. package/dist/types/src/commands/trigger/update/timer.d.ts.map +1 -0
  145. package/dist/types/src/commands/trigger/util.d.ts +46 -0
  146. package/dist/types/src/commands/trigger/util.d.ts.map +1 -0
  147. package/dist/types/src/components/CreateRoutinePanel/CreateRoutinePanel.d.ts +14 -0
  148. package/dist/types/src/components/CreateRoutinePanel/CreateRoutinePanel.d.ts.map +1 -0
  149. package/dist/types/src/components/CreateRoutinePanel/index.d.ts +2 -0
  150. package/dist/types/src/components/CreateRoutinePanel/index.d.ts.map +1 -0
  151. package/dist/types/src/components/InstructionsEditor/InstructionsEditor.d.ts +16 -0
  152. package/dist/types/src/components/InstructionsEditor/InstructionsEditor.d.ts.map +1 -0
  153. package/dist/types/src/components/InstructionsEditor/InstructionsEditor.stories.d.ts +17 -0
  154. package/dist/types/src/components/InstructionsEditor/InstructionsEditor.stories.d.ts.map +1 -0
  155. package/dist/types/src/components/InstructionsEditor/index.d.ts +2 -0
  156. package/dist/types/src/components/InstructionsEditor/index.d.ts.map +1 -0
  157. package/dist/types/src/components/MasterDetail/MasterDetail.d.ts +51 -0
  158. package/dist/types/src/components/MasterDetail/MasterDetail.d.ts.map +1 -0
  159. package/dist/types/src/components/MasterDetail/MasterDetail.stories.d.ts +16 -0
  160. package/dist/types/src/components/MasterDetail/MasterDetail.stories.d.ts.map +1 -0
  161. package/dist/types/src/components/MasterDetail/index.d.ts +2 -0
  162. package/dist/types/src/components/MasterDetail/index.d.ts.map +1 -0
  163. package/dist/types/src/components/RoutineForm/RoutineForm.d.ts +25 -0
  164. package/dist/types/src/components/RoutineForm/RoutineForm.d.ts.map +1 -0
  165. package/dist/types/src/components/RoutineForm/index.d.ts +2 -0
  166. package/dist/types/src/components/RoutineForm/index.d.ts.map +1 -0
  167. package/dist/types/src/components/Schedule/Schedule.d.ts +80 -0
  168. package/dist/types/src/components/Schedule/Schedule.d.ts.map +1 -0
  169. package/dist/types/src/components/Schedule/Schedule.stories.d.ts +23 -0
  170. package/dist/types/src/components/Schedule/Schedule.stories.d.ts.map +1 -0
  171. package/dist/types/src/components/Schedule/cron.d.ts +44 -0
  172. package/dist/types/src/components/Schedule/cron.d.ts.map +1 -0
  173. package/dist/types/src/components/Schedule/cron.test.d.ts +2 -0
  174. package/dist/types/src/components/Schedule/cron.test.d.ts.map +1 -0
  175. package/dist/types/src/components/Schedule/index.d.ts +4 -0
  176. package/dist/types/src/components/Schedule/index.d.ts.map +1 -0
  177. package/dist/types/src/components/Schedule/types.d.ts +66 -0
  178. package/dist/types/src/components/Schedule/types.d.ts.map +1 -0
  179. package/dist/types/src/components/TemplateEditor/TemplateEditor.d.ts +13 -0
  180. package/dist/types/src/components/TemplateEditor/TemplateEditor.d.ts.map +1 -0
  181. package/dist/types/src/components/TemplateEditor/TemplateEditor.stories.d.ts +19 -0
  182. package/dist/types/src/components/TemplateEditor/TemplateEditor.stories.d.ts.map +1 -0
  183. package/dist/types/src/components/TemplateEditor/TemplateForm.d.ts +21 -0
  184. package/dist/types/src/components/TemplateEditor/TemplateForm.d.ts.map +1 -0
  185. package/dist/types/src/components/TemplateEditor/TemplateForm.stories.d.ts +16 -0
  186. package/dist/types/src/components/TemplateEditor/TemplateForm.stories.d.ts.map +1 -0
  187. package/dist/types/src/components/TemplateEditor/extensions/handlebars-extension.d.ts +12 -0
  188. package/dist/types/src/components/TemplateEditor/extensions/handlebars-extension.d.ts.map +1 -0
  189. package/dist/types/src/components/TemplateEditor/extensions/index.d.ts +3 -0
  190. package/dist/types/src/components/TemplateEditor/extensions/index.d.ts.map +1 -0
  191. package/dist/types/src/components/TemplateEditor/extensions/xml-extension.d.ts +8 -0
  192. package/dist/types/src/components/TemplateEditor/extensions/xml-extension.d.ts.map +1 -0
  193. package/dist/types/src/components/TemplateEditor/index.d.ts +2 -0
  194. package/dist/types/src/components/TemplateEditor/index.d.ts.map +1 -0
  195. package/dist/types/src/components/TemplateEditor/types.d.ts +18 -0
  196. package/dist/types/src/components/TemplateEditor/types.d.ts.map +1 -0
  197. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts +24 -0
  198. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts.map +1 -0
  199. package/dist/types/src/components/TriggerEditor/TriggerEditor.stories.d.ts +25 -0
  200. package/dist/types/src/components/TriggerEditor/TriggerEditor.stories.d.ts.map +1 -0
  201. package/dist/types/src/components/TriggerEditor/TriggerKindSelector.d.ts +15 -0
  202. package/dist/types/src/components/TriggerEditor/TriggerKindSelector.d.ts.map +1 -0
  203. package/dist/types/src/components/TriggerEditor/TriggerKindSelector.stories.d.ts +15 -0
  204. package/dist/types/src/components/TriggerEditor/TriggerKindSelector.stories.d.ts.map +1 -0
  205. package/dist/types/src/components/TriggerEditor/index.d.ts +2 -0
  206. package/dist/types/src/components/TriggerEditor/index.d.ts.map +1 -0
  207. package/dist/types/src/components/index.d.ts +8 -0
  208. package/dist/types/src/components/index.d.ts.map +1 -0
  209. package/dist/types/src/containers/RoutineArticle/RoutineArticle.d.ts +14 -0
  210. package/dist/types/src/containers/RoutineArticle/RoutineArticle.d.ts.map +1 -0
  211. package/dist/types/src/containers/RoutineArticle/RoutineArticle.stories.d.ts +20 -0
  212. package/dist/types/src/containers/RoutineArticle/RoutineArticle.stories.d.ts.map +1 -0
  213. package/dist/types/src/containers/RoutineArticle/index.d.ts +2 -0
  214. package/dist/types/src/containers/RoutineArticle/index.d.ts.map +1 -0
  215. package/dist/types/src/containers/RoutineCompanion/RoutineCompanion.d.ts +17 -0
  216. package/dist/types/src/containers/RoutineCompanion/RoutineCompanion.d.ts.map +1 -0
  217. package/dist/types/src/containers/RoutineCompanion/RoutineCompanion.stories.d.ts +15 -0
  218. package/dist/types/src/containers/RoutineCompanion/RoutineCompanion.stories.d.ts.map +1 -0
  219. package/dist/types/src/containers/RoutineCompanion/index.d.ts +3 -0
  220. package/dist/types/src/containers/RoutineCompanion/index.d.ts.map +1 -0
  221. package/dist/types/src/containers/RoutineSettings/RoutineSettings.d.ts +10 -0
  222. package/dist/types/src/containers/RoutineSettings/RoutineSettings.d.ts.map +1 -0
  223. package/dist/types/src/containers/RoutineSettings/index.d.ts +2 -0
  224. package/dist/types/src/containers/RoutineSettings/index.d.ts.map +1 -0
  225. package/dist/types/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.d.ts +9 -0
  226. package/dist/types/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.d.ts.map +1 -0
  227. package/dist/types/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.stories.d.ts +15 -0
  228. package/dist/types/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.stories.d.ts.map +1 -0
  229. package/dist/types/src/containers/RoutineTraceCompanion/index.d.ts +2 -0
  230. package/dist/types/src/containers/RoutineTraceCompanion/index.d.ts.map +1 -0
  231. package/dist/types/src/containers/RoutineTraceCompanion/runs.d.ts +34 -0
  232. package/dist/types/src/containers/RoutineTraceCompanion/runs.d.ts.map +1 -0
  233. package/dist/types/src/containers/RoutineTraceCompanion/runs.test.d.ts +2 -0
  234. package/dist/types/src/containers/RoutineTraceCompanion/runs.test.d.ts.map +1 -0
  235. package/dist/types/src/containers/RoutineTraceCompanion/useRoutineRuns.d.ts +11 -0
  236. package/dist/types/src/containers/RoutineTraceCompanion/useRoutineRuns.d.ts.map +1 -0
  237. package/dist/types/src/containers/SkillArticle/SkillArticle.d.ts +6 -0
  238. package/dist/types/src/containers/SkillArticle/SkillArticle.d.ts.map +1 -0
  239. package/dist/types/src/containers/SkillArticle/index.d.ts +2 -0
  240. package/dist/types/src/containers/SkillArticle/index.d.ts.map +1 -0
  241. package/dist/types/src/containers/index.d.ts +7 -0
  242. package/dist/types/src/containers/index.d.ts.map +1 -0
  243. package/dist/types/src/hooks/index.d.ts +2 -0
  244. package/dist/types/src/hooks/index.d.ts.map +1 -0
  245. package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts +12 -0
  246. package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts.map +1 -0
  247. package/dist/types/src/index.d.ts +4 -0
  248. package/dist/types/src/index.d.ts.map +1 -0
  249. package/dist/types/src/meta.d.ts +33 -0
  250. package/dist/types/src/meta.d.ts.map +1 -0
  251. package/dist/types/src/operations/create-routine.d.ts +5 -0
  252. package/dist/types/src/operations/create-routine.d.ts.map +1 -0
  253. package/dist/types/src/operations/create-trigger-from-template.d.ts +5 -0
  254. package/dist/types/src/operations/create-trigger-from-template.d.ts.map +1 -0
  255. package/dist/types/src/operations/index.d.ts +3 -0
  256. package/dist/types/src/operations/index.d.ts.map +1 -0
  257. package/dist/types/src/operations/run-routine.d.ts +5 -0
  258. package/dist/types/src/operations/run-routine.d.ts.map +1 -0
  259. package/dist/types/src/paths.d.ts +5 -0
  260. package/dist/types/src/paths.d.ts.map +1 -0
  261. package/dist/types/src/plugin.d.ts +4 -0
  262. package/dist/types/src/plugin.d.ts.map +1 -0
  263. package/dist/types/src/templates/index.d.ts +11 -0
  264. package/dist/types/src/templates/index.d.ts.map +1 -0
  265. package/dist/types/src/testing/index.d.ts +2 -0
  266. package/dist/types/src/testing/index.d.ts.map +1 -0
  267. package/dist/types/src/testing/test-functions.d.ts +328 -0
  268. package/dist/types/src/testing/test-functions.d.ts.map +1 -0
  269. package/dist/types/src/testing.d.ts +2 -0
  270. package/dist/types/src/testing.d.ts.map +1 -0
  271. package/dist/types/src/translations.d.ts +3 -0
  272. package/dist/types/src/translations.d.ts.map +1 -0
  273. package/dist/types/src/types/Routine.d.ts +55 -0
  274. package/dist/types/src/types/Routine.d.ts.map +1 -0
  275. package/dist/types/src/types/Routine.test.d.ts +2 -0
  276. package/dist/types/src/types/Routine.test.d.ts.map +1 -0
  277. package/dist/types/src/types/RoutineCapabilities.d.ts +44 -0
  278. package/dist/types/src/types/RoutineCapabilities.d.ts.map +1 -0
  279. package/dist/types/src/types/RoutineOperation.d.ts +45 -0
  280. package/dist/types/src/types/RoutineOperation.d.ts.map +1 -0
  281. package/dist/types/src/types/index.d.ts +5 -0
  282. package/dist/types/src/types/index.d.ts.map +1 -0
  283. package/dist/types/src/types/schema.d.ts +33 -0
  284. package/dist/types/src/types/schema.d.ts.map +1 -0
  285. package/dist/types/src/util/index.d.ts +3 -0
  286. package/dist/types/src/util/index.d.ts.map +1 -0
  287. package/dist/types/src/util/routines-for-object.d.ts +22 -0
  288. package/dist/types/src/util/routines-for-object.d.ts.map +1 -0
  289. package/dist/types/src/util/routines-for-object.test.d.ts +2 -0
  290. package/dist/types/src/util/routines-for-object.test.d.ts.map +1 -0
  291. package/dist/types/src/util/run-instructions.d.ts +5 -0
  292. package/dist/types/src/util/run-instructions.d.ts.map +1 -0
  293. package/dist/types/src/util/run-instructions.test.d.ts +2 -0
  294. package/dist/types/src/util/run-instructions.test.d.ts.map +1 -0
  295. package/dist/types/tsconfig.tsbuildinfo +1 -0
  296. package/dx.config.ts +33 -0
  297. package/package.json +196 -0
  298. package/src/RoutinePlugin.node.ts +48 -0
  299. package/src/RoutinePlugin.test.ts +27 -0
  300. package/src/RoutinePlugin.tsx +58 -0
  301. package/src/RoutinePlugin.workerd.ts +24 -0
  302. package/src/capabilities/app-graph-builder.ts +80 -0
  303. package/src/capabilities/create-object.test.ts +18 -0
  304. package/src/capabilities/create-object.ts +26 -0
  305. package/src/capabilities/index.ts +22 -0
  306. package/src/capabilities/layer-specs.ts +190 -0
  307. package/src/capabilities/navigation-resolver.ts +21 -0
  308. package/src/capabilities/node.ts +19 -0
  309. package/src/capabilities/operation-handler.ts +16 -0
  310. package/src/capabilities/react-surface.tsx +66 -0
  311. package/src/capabilities/registry-sync.ts +114 -0
  312. package/src/capabilities/templates.ts +17 -0
  313. package/src/capabilities/trigger-runtime-controller.test.ts +107 -0
  314. package/src/capabilities/trigger-runtime-controller.ts +126 -0
  315. package/src/commands/index.ts +5 -0
  316. package/src/commands/trigger/create/index.ts +14 -0
  317. package/src/commands/trigger/create/queue.ts +87 -0
  318. package/src/commands/trigger/create/subscription.ts +126 -0
  319. package/src/commands/trigger/create/timer.ts +90 -0
  320. package/src/commands/trigger/index.ts +16 -0
  321. package/src/commands/trigger/list.ts +69 -0
  322. package/src/commands/trigger/options.ts +47 -0
  323. package/src/commands/trigger/remove.ts +45 -0
  324. package/src/commands/trigger/update/index.ts +14 -0
  325. package/src/commands/trigger/update/queue.ts +191 -0
  326. package/src/commands/trigger/update/subscription.ts +278 -0
  327. package/src/commands/trigger/update/timer.ts +194 -0
  328. package/src/commands/trigger/util.ts +412 -0
  329. package/src/components/CreateRoutinePanel/CreateRoutinePanel.tsx +68 -0
  330. package/src/components/CreateRoutinePanel/index.ts +5 -0
  331. package/src/components/InstructionsEditor/InstructionsEditor.stories.tsx +69 -0
  332. package/src/components/InstructionsEditor/InstructionsEditor.tsx +107 -0
  333. package/src/components/InstructionsEditor/index.ts +5 -0
  334. package/src/components/MasterDetail/MasterDetail.stories.tsx +100 -0
  335. package/src/components/MasterDetail/MasterDetail.tsx +175 -0
  336. package/src/components/MasterDetail/index.ts +5 -0
  337. package/src/components/RoutineForm/RoutineForm.tsx +293 -0
  338. package/src/components/RoutineForm/index.ts +5 -0
  339. package/src/components/Schedule/Schedule.stories.tsx +73 -0
  340. package/src/components/Schedule/Schedule.tsx +527 -0
  341. package/src/components/Schedule/cron.test.ts +230 -0
  342. package/src/components/Schedule/cron.ts +337 -0
  343. package/src/components/Schedule/index.ts +8 -0
  344. package/src/components/Schedule/types.ts +124 -0
  345. package/src/components/TemplateEditor/TemplateEditor.stories.tsx +93 -0
  346. package/src/components/TemplateEditor/TemplateEditor.tsx +77 -0
  347. package/src/components/TemplateEditor/TemplateForm.stories.tsx +76 -0
  348. package/src/components/TemplateEditor/TemplateForm.tsx +204 -0
  349. package/src/components/TemplateEditor/extensions/handlebars-extension.ts +274 -0
  350. package/src/components/TemplateEditor/extensions/index.ts +6 -0
  351. package/src/components/TemplateEditor/extensions/xml-extension.ts +64 -0
  352. package/src/components/TemplateEditor/index.ts +5 -0
  353. package/src/components/TemplateEditor/types.tsx +28 -0
  354. package/src/components/TriggerEditor/TriggerEditor.stories.tsx +122 -0
  355. package/src/components/TriggerEditor/TriggerEditor.tsx +365 -0
  356. package/src/components/TriggerEditor/TriggerKindSelector.stories.tsx +42 -0
  357. package/src/components/TriggerEditor/TriggerKindSelector.tsx +87 -0
  358. package/src/components/TriggerEditor/index.ts +5 -0
  359. package/src/components/index.ts +11 -0
  360. package/src/containers/RoutineArticle/RoutineArticle.stories.tsx +146 -0
  361. package/src/containers/RoutineArticle/RoutineArticle.tsx +109 -0
  362. package/src/containers/RoutineArticle/index.ts +5 -0
  363. package/src/containers/RoutineCompanion/RoutineCompanion.stories.tsx +82 -0
  364. package/src/containers/RoutineCompanion/RoutineCompanion.tsx +342 -0
  365. package/src/containers/RoutineCompanion/index.ts +6 -0
  366. package/src/containers/RoutineSettings/RoutineSettings.tsx +65 -0
  367. package/src/containers/RoutineSettings/index.ts +5 -0
  368. package/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.stories.tsx +133 -0
  369. package/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.tsx +125 -0
  370. package/src/containers/RoutineTraceCompanion/index.ts +5 -0
  371. package/src/containers/RoutineTraceCompanion/runs.test.ts +140 -0
  372. package/src/containers/RoutineTraceCompanion/runs.ts +135 -0
  373. package/src/containers/RoutineTraceCompanion/useRoutineRuns.ts +50 -0
  374. package/src/containers/SkillArticle/SkillArticle.tsx +29 -0
  375. package/src/containers/SkillArticle/index.ts +5 -0
  376. package/src/containers/index.ts +11 -0
  377. package/src/hooks/index.ts +6 -0
  378. package/src/hooks/useTriggerRuntimeControls.ts +70 -0
  379. package/src/index.ts +7 -0
  380. package/src/meta.ts +9 -0
  381. package/src/operations/create-routine.ts +39 -0
  382. package/src/operations/create-trigger-from-template.ts +68 -0
  383. package/src/operations/index.ts +11 -0
  384. package/src/operations/run-routine.ts +45 -0
  385. package/src/paths.ts +18 -0
  386. package/src/plugin.ts +11 -0
  387. package/src/templates/index.ts +48 -0
  388. package/src/testing/index.ts +5 -0
  389. package/src/testing/test-functions.ts +76 -0
  390. package/src/testing.ts +7 -0
  391. package/src/translations.ts +125 -0
  392. package/src/types/Routine.test.ts +51 -0
  393. package/src/types/Routine.ts +144 -0
  394. package/src/types/RoutineCapabilities.ts +51 -0
  395. package/src/types/RoutineOperation.ts +104 -0
  396. package/src/types/index.ts +9 -0
  397. package/src/types/schema.ts +31 -0
  398. package/src/util/index.ts +6 -0
  399. package/src/util/routines-for-object.test.ts +109 -0
  400. package/src/util/routines-for-object.ts +89 -0
  401. package/src/util/run-instructions.test.ts +23 -0
  402. package/src/util/run-instructions.ts +12 -0
  403. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,230 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import {
8
+ clampSchedule,
9
+ cronIntervalSeconds,
10
+ cronToSchedule,
11
+ fromCron,
12
+ scheduleIntervalSeconds,
13
+ scheduleToCron,
14
+ toCron,
15
+ } from './cron';
16
+ import { type ScheduleValue } from './Schedule';
17
+
18
+ describe('toCron', () => {
19
+ test('minutely: every 15 minutes', ({ expect }) => {
20
+ expect(toCron({ frequency: 'minutely', interval: 15 })).toBe('*/15 * * * *');
21
+ });
22
+
23
+ test('minutely: every 1 minute', ({ expect }) => {
24
+ expect(toCron({ frequency: 'minutely', interval: 1 })).toBe('*/1 * * * *');
25
+ });
26
+
27
+ test('hourly: every hour at minute 0', ({ expect }) => {
28
+ expect(toCron({ frequency: 'hourly', interval: 1, minute: 0 })).toBe('0 */1 * * *');
29
+ });
30
+
31
+ test('hourly: every 6 hours at minute 30', ({ expect }) => {
32
+ expect(toCron({ frequency: 'hourly', interval: 6, minute: 30 })).toBe('30 */6 * * *');
33
+ });
34
+
35
+ test('daily: at 09:00', ({ expect }) => {
36
+ expect(toCron({ frequency: 'daily', hour: 9, minute: 0 })).toBe('0 9 * * *');
37
+ });
38
+
39
+ test('daily: at 22:30', ({ expect }) => {
40
+ expect(toCron({ frequency: 'daily', hour: 22, minute: 30 })).toBe('30 22 * * *');
41
+ });
42
+
43
+ test('weekly: Monday at 09:00', ({ expect }) => {
44
+ expect(toCron({ frequency: 'weekly', daysOfWeek: ['mon'], hour: 9, minute: 0 })).toBe('0 9 * * 1');
45
+ });
46
+
47
+ test('weekly: Mon, Wed, Fri at 08:00', ({ expect }) => {
48
+ expect(toCron({ frequency: 'weekly', daysOfWeek: ['mon', 'wed', 'fri'], hour: 8, minute: 0 })).toBe(
49
+ '0 8 * * 1,3,5',
50
+ );
51
+ });
52
+
53
+ test('weekly: Sunday (DOW=0) at midnight', ({ expect }) => {
54
+ expect(toCron({ frequency: 'weekly', daysOfWeek: ['sun'], hour: 0, minute: 0 })).toBe('0 0 * * 0');
55
+ });
56
+
57
+ test('weekly: empty daysOfWeek falls back to wildcard', ({ expect }) => {
58
+ expect(toCron({ frequency: 'weekly', daysOfWeek: [], hour: 9, minute: 0 })).toBe('0 9 * * *');
59
+ });
60
+
61
+ test('monthly: 1st at 09:00', ({ expect }) => {
62
+ expect(toCron({ frequency: 'monthly', daysOfMonth: [1], hour: 9, minute: 0 })).toBe('0 9 1 * *');
63
+ });
64
+
65
+ test('monthly: 1st and 15th at 12:00', ({ expect }) => {
66
+ expect(toCron({ frequency: 'monthly', daysOfMonth: [1, 15], hour: 12, minute: 0 })).toBe('0 12 1,15 * *');
67
+ });
68
+
69
+ test('monthly: empty daysOfMonth falls back to wildcard', ({ expect }) => {
70
+ expect(toCron({ frequency: 'monthly', daysOfMonth: [], hour: 9, minute: 0 })).toBe('0 9 * * *');
71
+ });
72
+
73
+ test('custom: pass-through full cron expression', ({ expect }) => {
74
+ expect(toCron({ frequency: 'custom', cronExpression: '0 9 * * MON-FRI' })).toBe('0 9 * * MON-FRI');
75
+ });
76
+
77
+ test('custom: empty cronExpression defaults to wildcard', ({ expect }) => {
78
+ expect(toCron({ frequency: 'custom', cronExpression: '' })).toBe('* * * * *');
79
+ });
80
+
81
+ test('custom: step expression preserved', ({ expect }) => {
82
+ expect(toCron({ frequency: 'custom', cronExpression: '0/15 * * * *' })).toBe('0/15 * * * *');
83
+ });
84
+ });
85
+
86
+ describe('fromCron', () => {
87
+ test('minutely', ({ expect }) => {
88
+ expect(fromCron('*/15 * * * *')).toEqual({ frequency: 'minutely', interval: 15 });
89
+ });
90
+
91
+ test('hourly', ({ expect }) => {
92
+ expect(fromCron('30 */6 * * *')).toEqual({ frequency: 'hourly', interval: 6, minute: 30 });
93
+ });
94
+
95
+ test('daily', ({ expect }) => {
96
+ expect(fromCron('0 9 * * *')).toEqual({ frequency: 'daily', hour: 9, minute: 0 });
97
+ });
98
+
99
+ test('weekly single day', ({ expect }) => {
100
+ expect(fromCron('0 9 * * 1')).toEqual({ frequency: 'weekly', daysOfWeek: ['mon'], hour: 9, minute: 0 });
101
+ });
102
+
103
+ test('weekly multiple days', ({ expect }) => {
104
+ expect(fromCron('0 8 * * 1,3,5')).toEqual({
105
+ frequency: 'weekly',
106
+ daysOfWeek: ['mon', 'wed', 'fri'],
107
+ hour: 8,
108
+ minute: 0,
109
+ });
110
+ });
111
+
112
+ test('monthly single day', ({ expect }) => {
113
+ expect(fromCron('0 9 1 * *')).toEqual({ frequency: 'monthly', daysOfMonth: [1], hour: 9, minute: 0 });
114
+ });
115
+
116
+ test('monthly multiple days', ({ expect }) => {
117
+ expect(fromCron('0 12 1,15 * *')).toEqual({ frequency: 'monthly', daysOfMonth: [1, 15], hour: 12, minute: 0 });
118
+ });
119
+
120
+ test('custom: unknown pattern', ({ expect }) => {
121
+ expect(fromCron('0 9 * * MON-FRI')).toEqual({ frequency: 'custom', cronExpression: '0 9 * * MON-FRI' });
122
+ });
123
+
124
+ test('custom: wrong field count', ({ expect }) => {
125
+ expect(fromCron('0 9 *')).toEqual({ frequency: 'custom', cronExpression: '0 9 *' });
126
+ });
127
+
128
+ test('round-trip: toCron → fromCron', ({ expect }) => {
129
+ const specs = [
130
+ { frequency: 'minutely' as const, interval: 15 },
131
+ { frequency: 'hourly' as const, interval: 4, minute: 30 },
132
+ { frequency: 'daily' as const, hour: 22, minute: 30 },
133
+ { frequency: 'weekly' as const, daysOfWeek: ['mon', 'wed'] as const, hour: 9, minute: 0 },
134
+ { frequency: 'monthly' as const, daysOfMonth: [1, 15], hour: 12, minute: 0 },
135
+ ];
136
+ for (const spec of specs) {
137
+ expect(fromCron(toCron(spec))).toEqual(spec);
138
+ }
139
+ });
140
+ });
141
+
142
+ describe('Schedule <-> cron bridge', () => {
143
+ test('recurring kinds round-trip through cron', ({ expect }) => {
144
+ const cases: ScheduleValue[] = [
145
+ { kind: 'hourly', minute: 5 },
146
+ { kind: 'daily', time: '09:00' },
147
+ { kind: 'weekly', time: '09:30', days: ['mon', 'wed'] },
148
+ { kind: 'monthly', day: 15, time: '08:00' },
149
+ ];
150
+
151
+ for (const value of cases) {
152
+ const cron = scheduleToCron(value);
153
+ expect(cron).toBeDefined();
154
+ expect(cronToSchedule(cron!)).toEqual(value);
155
+ }
156
+ });
157
+
158
+ test('custom passes the expression through', ({ expect }) => {
159
+ expect(scheduleToCron({ kind: 'custom', cron: '15 9 * * 1-5' })).toBe('15 9 * * 1-5');
160
+ });
161
+
162
+ test('cron patterns Schedule cannot model fall back to custom', ({ expect }) => {
163
+ // Sub-hourly interval.
164
+ expect(cronToSchedule('*/15 * * * *')).toEqual({ kind: 'custom', cron: '*/15 * * * *' });
165
+ // Multiple days of the month.
166
+ expect(cronToSchedule('0 8 1,15 * *')).toEqual({ kind: 'custom', cron: '0 8 1,15 * *' });
167
+ });
168
+ });
169
+
170
+ describe('minimum interval', () => {
171
+ test('cronIntervalSeconds reads common cadences', ({ expect }) => {
172
+ expect(cronIntervalSeconds('* * * * *')).toBe(60);
173
+ expect(cronIntervalSeconds('*/1 * * * *')).toBe(60);
174
+ expect(cronIntervalSeconds('*/5 * * * *')).toBe(300);
175
+ expect(cronIntervalSeconds('0,30 * * * *')).toBe(1800);
176
+ // Offset/range step forms carry the same cadence as `*\/N`.
177
+ expect(cronIntervalSeconds('0/15 * * * *')).toBe(900);
178
+ expect(cronIntervalSeconds('0-30/10 * * * *')).toBe(600);
179
+ // Cyclic wrap-around: 59 -> 0 of the next hour is a 1-minute gap, not 59.
180
+ expect(cronIntervalSeconds('0,59 * * * *')).toBe(60);
181
+ // Single minute, every hour.
182
+ expect(cronIntervalSeconds('0 * * * *')).toBe(3600);
183
+ // Hour-step narrows the daily cadence.
184
+ expect(cronIntervalSeconds('0 */6 * * *')).toBe(6 * 3600);
185
+ // Single minute, every day (a weekday restriction only widens gaps, so the daily floor stands).
186
+ expect(cronIntervalSeconds('0 9 * * *')).toBe(86400);
187
+ expect(cronIntervalSeconds('0 9 * * 1-5')).toBe(86400);
188
+ // Expressions whose minute field cannot be analysed are treated as satisfying any minimum.
189
+ expect(cronIntervalSeconds('not a cron')).toBe(Number.POSITIVE_INFINITY);
190
+ expect(cronIntervalSeconds('JAN * * * *')).toBe(Number.POSITIVE_INFINITY);
191
+ });
192
+
193
+ test('scheduleIntervalSeconds covers structured kinds', ({ expect }) => {
194
+ expect(scheduleIntervalSeconds({ kind: 'hourly', minute: 0 })).toBe(3600);
195
+ expect(scheduleIntervalSeconds({ kind: 'daily', time: '09:00' })).toBe(86400);
196
+ expect(scheduleIntervalSeconds({ kind: 'custom', cron: '* * * * *' })).toBe(60);
197
+ });
198
+
199
+ test('clampSchedule rewrites a too-frequent custom cron', ({ expect }) => {
200
+ expect(clampSchedule({ kind: 'custom', cron: '* * * * *' }, 300)).toEqual({ kind: 'custom', cron: '*/5 * * * *' });
201
+ expect(clampSchedule({ kind: 'custom', cron: '*/1 * * * *' }, 300)).toEqual({
202
+ kind: 'custom',
203
+ cron: '*/5 * * * *',
204
+ });
205
+ // An hourly minimum collapses a sub-hourly step to the top of the hour.
206
+ expect(clampSchedule({ kind: 'custom', cron: '*/15 * * * *' }, 3600)).toEqual({
207
+ kind: 'custom',
208
+ cron: '0 * * * *',
209
+ });
210
+ // Wrap-around list (real cadence 60s) is caught and clamped.
211
+ expect(clampSchedule({ kind: 'custom', cron: '0,59 * * * *' }, 300)).toEqual({
212
+ kind: 'custom',
213
+ cron: '*/5 * * * *',
214
+ });
215
+ // Offset-step form (every 15m) is caught when below an hourly minimum.
216
+ expect(clampSchedule({ kind: 'custom', cron: '0/15 * * * *' }, 3600)).toEqual({
217
+ kind: 'custom',
218
+ cron: '0 * * * *',
219
+ });
220
+ });
221
+
222
+ test('clampSchedule leaves satisfying schedules untouched', ({ expect }) => {
223
+ expect(clampSchedule({ kind: 'custom', cron: '*/15 * * * *' }, 300)).toEqual({
224
+ kind: 'custom',
225
+ cron: '*/15 * * * *',
226
+ });
227
+ expect(clampSchedule({ kind: 'custom', cron: '0 9 * * 1' }, 300)).toEqual({ kind: 'custom', cron: '0 9 * * 1' });
228
+ expect(clampSchedule({ kind: 'hourly', minute: 0 }, 3600)).toEqual({ kind: 'hourly', minute: 0 });
229
+ });
230
+ });
@@ -0,0 +1,337 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import cronstrue from 'cronstrue';
6
+
7
+ import type { ScheduleValue } from './Schedule';
8
+ import type { CronSpecType, DayOfWeek } from './types';
9
+
10
+ const DOW_MAP: Record<DayOfWeek, number> = {
11
+ sun: 0,
12
+ mon: 1,
13
+ tue: 2,
14
+ wed: 3,
15
+ thu: 4,
16
+ fri: 5,
17
+ sat: 6,
18
+ };
19
+
20
+ /**
21
+ * Derives a 5-field standard cron expression from a decomposed CronSpec value.
22
+ * Fields: minute hour day-of-month month day-of-week
23
+ */
24
+ export const toCron = (value: CronSpecType): string => {
25
+ switch (value.frequency) {
26
+ case 'minutely':
27
+ return `*/${value.interval} * * * *`;
28
+
29
+ case 'hourly':
30
+ return `${value.minute} */${value.interval} * * *`;
31
+
32
+ case 'daily':
33
+ return `${value.minute} ${value.hour} * * *`;
34
+
35
+ case 'weekly': {
36
+ const dow = value.daysOfWeek.length > 0 ? value.daysOfWeek.map((d) => DOW_MAP[d]).join(',') : '*';
37
+ return `${value.minute} ${value.hour} * * ${dow}`;
38
+ }
39
+
40
+ case 'monthly': {
41
+ const dom = value.daysOfMonth.length > 0 ? value.daysOfMonth.join(',') : '*';
42
+ return `${value.minute} ${value.hour} ${dom} * *`;
43
+ }
44
+
45
+ case 'custom':
46
+ return value.cronExpression || '* * * * *';
47
+ }
48
+ };
49
+
50
+ const DOW_REVERSE: Partial<Record<string, DayOfWeek>> = {
51
+ '0': 'sun',
52
+ '1': 'mon',
53
+ '2': 'tue',
54
+ '3': 'wed',
55
+ '4': 'thu',
56
+ '5': 'fri',
57
+ '6': 'sat',
58
+ // Both 0 and 7 represent Sunday in standard cron.
59
+ '7': 'sun',
60
+ };
61
+
62
+ /** Returns a non-negative integer if the token is a valid decimal integer, otherwise undefined. */
63
+ const parseUInt = (token: string): number | undefined => (/^\d+$/.test(token) ? Number(token) : undefined);
64
+
65
+ /** Returns the parsed integer if it falls within [min, max], otherwise undefined. */
66
+ const parseBoundedUInt = (token: string, min: number, max: number): number | undefined => {
67
+ const value = parseUInt(token);
68
+ return value !== undefined && value >= min && value <= max ? value : undefined;
69
+ };
70
+
71
+ /**
72
+ * Parses a 5-field cron expression into a CronSpec value.
73
+ * Recognises common patterns (minutely, hourly, daily, weekly, monthly) and falls back to `custom`.
74
+ */
75
+ export const fromCron = (cron: string): CronSpecType => {
76
+ const parts = cron.trim().split(/\s+/);
77
+ if (parts.length !== 5) {
78
+ return { frequency: 'custom', cronExpression: cron };
79
+ }
80
+
81
+ const [minute, hour, dom, month, dow] = parts;
82
+
83
+ // minutely: */N * * * *
84
+ const minutelyMatch = minute.match(/^\*\/(\d+)$/);
85
+ if (minutelyMatch && hour === '*' && dom === '*' && month === '*' && dow === '*') {
86
+ return { frequency: 'minutely', interval: parseInt(minutelyMatch[1]) };
87
+ }
88
+
89
+ // hourly: M */N * * *
90
+ const hourlyMatch = hour.match(/^\*\/(\d+)$/);
91
+ const minuteNum = parseBoundedUInt(minute, 0, 59);
92
+ if (hourlyMatch && dom === '*' && month === '*' && dow === '*' && minuteNum !== undefined) {
93
+ return { frequency: 'hourly', interval: parseInt(hourlyMatch[1]), minute: minuteNum };
94
+ }
95
+
96
+ const hourNum = parseBoundedUInt(hour, 0, 23);
97
+ if (minuteNum === undefined || hourNum === undefined) {
98
+ return { frequency: 'custom', cronExpression: cron };
99
+ }
100
+
101
+ // weekly: M H * * DOW[,DOW...] (numeric DOW values only; all tokens must be valid)
102
+ if (dom === '*' && month === '*' && dow !== '*') {
103
+ const tokens = dow.split(',');
104
+ const days = tokens.map((d) => DOW_REVERSE[d]).filter((d): d is DayOfWeek => d != null);
105
+ if (days.length === tokens.length && days.length > 0) {
106
+ return { frequency: 'weekly', daysOfWeek: days, hour: hourNum, minute: minuteNum };
107
+ }
108
+ return { frequency: 'custom', cronExpression: cron };
109
+ }
110
+
111
+ // monthly: M H DOM[,DOM...] * * (all tokens must be valid integers in 1-31)
112
+ if (dom !== '*' && month === '*' && dow === '*') {
113
+ const tokens = dom.split(',');
114
+ const days = tokens.map((d) => parseBoundedUInt(d, 1, 31)).filter((d): d is number => d !== undefined);
115
+ if (days.length === tokens.length && days.length > 0) {
116
+ return { frequency: 'monthly', daysOfMonth: days, hour: hourNum, minute: minuteNum };
117
+ }
118
+ return { frequency: 'custom', cronExpression: cron };
119
+ }
120
+
121
+ // daily: M H * * *
122
+ if (dom === '*' && month === '*' && dow === '*') {
123
+ return { frequency: 'daily', hour: hourNum, minute: minuteNum };
124
+ }
125
+
126
+ return { frequency: 'custom', cronExpression: cron };
127
+ };
128
+
129
+ /**
130
+ * Returns a human-readable description of a cron expression, e.g. "At 09:00 on Monday".
131
+ * Falls back to the raw expression string on parse errors.
132
+ */
133
+ export const describeCron = (cron: string): string => {
134
+ try {
135
+ return cronstrue.toString(cron, { throwExceptionOnParseError: true });
136
+ } catch {
137
+ return cron;
138
+ }
139
+ };
140
+
141
+ //
142
+ // Schedule <-> cron bridge.
143
+ //
144
+
145
+ const pad = (value: number): string => String(value).padStart(2, '0');
146
+
147
+ const splitTime = (time: string): { hour: number; minute: number } => {
148
+ const [hour, minute] = time.split(':').map(Number);
149
+ return { hour: Number.isFinite(hour) ? hour : 0, minute: Number.isFinite(minute) ? minute : 0 };
150
+ };
151
+
152
+ const joinTime = (hour: number, minute: number): string => `${pad(hour)}:${pad(minute)}`;
153
+
154
+ /**
155
+ * Convert a recurring {@link ScheduleValue} to a 5-field cron expression. The one-time `once` kind has no
156
+ * cron representation and returns `undefined`.
157
+ */
158
+ export const scheduleToCron = (value: ScheduleValue): string | undefined => {
159
+ switch (value.kind) {
160
+ case 'hourly':
161
+ return toCron({ frequency: 'hourly', interval: 1, minute: value.minute });
162
+ case 'daily': {
163
+ const { hour, minute } = splitTime(value.time);
164
+ return toCron({ frequency: 'daily', hour, minute });
165
+ }
166
+ case 'weekly': {
167
+ const { hour, minute } = splitTime(value.time);
168
+ return toCron({ frequency: 'weekly', daysOfWeek: value.days, hour, minute });
169
+ }
170
+ case 'monthly': {
171
+ const { hour, minute } = splitTime(value.time);
172
+ return toCron({ frequency: 'monthly', daysOfMonth: [value.day], hour, minute });
173
+ }
174
+ case 'custom':
175
+ return toCron({ frequency: 'custom', cronExpression: value.cron });
176
+ }
177
+ };
178
+
179
+ //
180
+ // Minimum-interval constraint.
181
+ //
182
+
183
+ const MINUTE_SECONDS = 60;
184
+ const HOUR_SECONDS = 60 * 60;
185
+ const DAY_SECONDS = 24 * HOUR_SECONDS;
186
+
187
+ /** Upper bound for a Schedule minimum interval: a 5-field cron's coarsest single-field cadence is hourly. */
188
+ export const MAX_MIN_INTERVAL_SECONDS = HOUR_SECONDS;
189
+
190
+ /** Parses a comma-separated list of bounded integers; returns undefined if any token is not a plain integer in range. */
191
+ const parseList = (field: string, min: number, max: number): number[] | undefined => {
192
+ const tokens = field.split(',');
193
+ const values = tokens
194
+ .map((token) => parseBoundedUInt(token, min, max))
195
+ .filter((value): value is number => value !== undefined);
196
+ return values.length === tokens.length && values.length > 0 ? values : undefined;
197
+ };
198
+
199
+ /**
200
+ * Smallest cyclic gap between values in a field whose domain wraps at `span` (60 for minutes, 24 for hours),
201
+ * or Infinity for a single value. The wrap is required so e.g. minutes `0,59` register their 1-unit gap
202
+ * (59 → 0 of the next cycle), not just the 59-unit one.
203
+ */
204
+ const minGap = (values: number[], span: number): number => {
205
+ const sorted = [...new Set(values)].sort((lhs, rhs) => lhs - rhs);
206
+ if (sorted.length <= 1) {
207
+ return Number.POSITIVE_INFINITY;
208
+ }
209
+ let gap = Number.POSITIVE_INFINITY;
210
+ for (let index = 0; index < sorted.length; index++) {
211
+ const next = index === sorted.length - 1 ? sorted[0] + span : sorted[index + 1];
212
+ gap = Math.min(gap, next - sorted[index]);
213
+ }
214
+ return gap;
215
+ };
216
+
217
+ /** Step value of a `*\/N`, `M/N`, or `A-B/N` field (the cadence between fires), or undefined if not a step form. */
218
+ const parseStep = (field: string, max: number): number | undefined => {
219
+ const match = field.match(/^(?:\*|\d+(?:-\d+)?)\/(\d+)$/);
220
+ return match ? parseBoundedUInt(match[1], 1, max) : undefined;
221
+ };
222
+
223
+ /** Minimum seconds between fires implied by a cron's hour field (independent of minute granularity). */
224
+ const hourIntervalSeconds = (hour: string): number => {
225
+ const step = parseStep(hour, 23);
226
+ if (step !== undefined) {
227
+ return step * HOUR_SECONDS;
228
+ }
229
+ if (hour === '*') {
230
+ return HOUR_SECONDS;
231
+ }
232
+ const hours = parseList(hour, 0, 23);
233
+ if (hours) {
234
+ return hours.length > 1 ? minGap(hours, 24) * HOUR_SECONDS : DAY_SECONDS;
235
+ }
236
+ return Number.POSITIVE_INFINITY;
237
+ };
238
+
239
+ /**
240
+ * Best-effort minimum seconds between consecutive fires of a 5-field cron expression. Recognises the patterns
241
+ * a too-frequent schedule can take (`* …`, `*\/N …`, explicit minute/hour lists); returns Infinity for
242
+ * expressions it cannot analyse, so unrecognised crons are treated as satisfying any minimum.
243
+ */
244
+ export const cronIntervalSeconds = (cron: string): number => {
245
+ const parts = cron.trim().split(/\s+/);
246
+ if (parts.length !== 5) {
247
+ return Number.POSITIVE_INFINITY;
248
+ }
249
+
250
+ const [minute, hour] = parts;
251
+ const minuteStep = parseStep(minute, 59);
252
+ if (minuteStep !== undefined) {
253
+ return minuteStep * MINUTE_SECONDS;
254
+ }
255
+ if (minute === '*') {
256
+ return MINUTE_SECONDS;
257
+ }
258
+
259
+ const minutes = parseList(minute, 0, 59);
260
+ if (minutes) {
261
+ const minuteGap = minutes.length > 1 ? minGap(minutes, 60) * MINUTE_SECONDS : Number.POSITIVE_INFINITY;
262
+ return Math.min(minuteGap, hourIntervalSeconds(hour));
263
+ }
264
+
265
+ return Number.POSITIVE_INFINITY;
266
+ };
267
+
268
+ /**
269
+ * Rewrites a cron's minute field so it fires no more often than `minIntervalSeconds`. Used to clamp a custom
270
+ * expression; leaves non-5-field expressions untouched since their cadence cannot be reasoned about.
271
+ */
272
+ const clampCronMinute = (cron: string, minIntervalSeconds: number): string => {
273
+ const parts = cron.trim().split(/\s+/);
274
+ if (parts.length !== 5) {
275
+ return cron;
276
+ }
277
+ const stepMinutes = Math.ceil(minIntervalSeconds / MINUTE_SECONDS);
278
+ // A step that spans the whole minute range fires only at minute 0; express that as the hourly cadence.
279
+ parts[0] = stepMinutes >= 60 ? '0' : `*/${stepMinutes}`;
280
+ return parts.join(' ');
281
+ };
282
+
283
+ /** Minimum seconds between fires for a recurring {@link ScheduleValue}. */
284
+ export const scheduleIntervalSeconds = (value: ScheduleValue): number => {
285
+ switch (value.kind) {
286
+ case 'hourly':
287
+ return HOUR_SECONDS;
288
+ case 'daily':
289
+ return DAY_SECONDS;
290
+ // Consecutive selected weekdays sit one day apart at closest.
291
+ case 'weekly':
292
+ return DAY_SECONDS;
293
+ // Approximate the shortest month to stay conservative.
294
+ case 'monthly':
295
+ return 28 * DAY_SECONDS;
296
+ case 'custom':
297
+ return cronIntervalSeconds(value.cron);
298
+ }
299
+ };
300
+
301
+ /**
302
+ * Coerces a schedule so it fires no more often than `minIntervalSeconds`. Only `custom` expressions can exceed
303
+ * the limit (structured kinds are hourly or slower), so clamping rewrites the cron's minute field; all other
304
+ * values pass through unchanged.
305
+ */
306
+ export const clampSchedule = (value: ScheduleValue, minIntervalSeconds: number): ScheduleValue => {
307
+ if (value.kind !== 'custom' || cronIntervalSeconds(value.cron) >= minIntervalSeconds) {
308
+ return value;
309
+ }
310
+ return { kind: 'custom', cron: clampCronMinute(value.cron, minIntervalSeconds) };
311
+ };
312
+
313
+ /**
314
+ * Convert a cron expression to a recurring {@link ScheduleValue}. Patterns that Schedule cannot model exactly
315
+ * (sub-hourly intervals, multi-day months, etc.) fall back to the `custom` kind preserving the raw expression.
316
+ */
317
+ export const cronToSchedule = (cron: string): ScheduleValue => {
318
+ const spec = fromCron(cron);
319
+ switch (spec.frequency) {
320
+ case 'hourly':
321
+ // Schedule's hourly runs at the top of every hour; a non-unit interval can't be modelled.
322
+ return spec.interval === 1 ? { kind: 'hourly', minute: spec.minute } : { kind: 'custom', cron };
323
+ case 'daily':
324
+ return { kind: 'daily', time: joinTime(spec.hour, spec.minute) };
325
+ case 'weekly':
326
+ return { kind: 'weekly', time: joinTime(spec.hour, spec.minute), days: [...spec.daysOfWeek] };
327
+ case 'monthly':
328
+ // Schedule models a single day-of-month; multiple days remain custom.
329
+ return spec.daysOfMonth.length === 1
330
+ ? { kind: 'monthly', day: spec.daysOfMonth[0], time: joinTime(spec.hour, spec.minute) }
331
+ : { kind: 'custom', cron };
332
+ case 'minutely':
333
+ case 'custom':
334
+ default:
335
+ return { kind: 'custom', cron };
336
+ }
337
+ };
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './cron';
6
+ export * from './types';
7
+
8
+ export * from './Schedule';