@nextsparkjs/core 0.1.0-beta.91 → 0.1.0-beta.94

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 (341) hide show
  1. package/dist/components/dashboard/block-editor/array-field.d.ts.map +1 -1
  2. package/dist/components/dashboard/block-editor/array-field.js +55 -3
  3. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  4. package/dist/components/dashboard/block-editor/dynamic-form.js +82 -2
  5. package/dist/components/dashboard/navigation/DynamicNavigation.d.ts.map +1 -1
  6. package/dist/components/dashboard/navigation/DynamicNavigation.js +7 -1
  7. package/dist/components/devtools/scheduled-actions/actions-table.d.ts +1 -0
  8. package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
  9. package/dist/components/devtools/scheduled-actions/actions-table.js +182 -46
  10. package/dist/components/devtools/scheduled-actions/types.d.ts +1 -0
  11. package/dist/components/devtools/scheduled-actions/types.d.ts.map +1 -1
  12. package/dist/components/media/MediaCard.d.ts +23 -0
  13. package/dist/components/media/MediaCard.d.ts.map +1 -0
  14. package/dist/components/media/MediaCard.js +154 -0
  15. package/dist/components/media/MediaDetailPanel.d.ts +17 -0
  16. package/dist/components/media/MediaDetailPanel.d.ts.map +1 -0
  17. package/dist/components/media/MediaDetailPanel.js +331 -0
  18. package/dist/components/media/MediaGrid.d.ts +26 -0
  19. package/dist/components/media/MediaGrid.d.ts.map +1 -0
  20. package/dist/components/media/MediaGrid.js +77 -0
  21. package/dist/components/media/MediaLibrary.d.ts +20 -0
  22. package/dist/components/media/MediaLibrary.d.ts.map +1 -0
  23. package/dist/components/media/MediaLibrary.js +229 -0
  24. package/dist/components/media/MediaList.d.ts +24 -0
  25. package/dist/components/media/MediaList.d.ts.map +1 -0
  26. package/dist/components/media/MediaList.js +181 -0
  27. package/dist/components/media/MediaSelector.d.ts +19 -0
  28. package/dist/components/media/MediaSelector.d.ts.map +1 -0
  29. package/dist/components/media/MediaSelector.js +145 -0
  30. package/dist/components/media/MediaTagFilter.d.ts +16 -0
  31. package/dist/components/media/MediaTagFilter.d.ts.map +1 -0
  32. package/dist/components/media/MediaTagFilter.js +122 -0
  33. package/dist/components/media/MediaToolbar.d.ts +25 -0
  34. package/dist/components/media/MediaToolbar.d.ts.map +1 -0
  35. package/dist/components/media/MediaToolbar.js +136 -0
  36. package/dist/components/media/MediaUploadZone.d.ts +19 -0
  37. package/dist/components/media/MediaUploadZone.d.ts.map +1 -0
  38. package/dist/components/media/MediaUploadZone.js +248 -0
  39. package/dist/components/media/index.d.ts +15 -0
  40. package/dist/components/media/index.d.ts.map +1 -0
  41. package/dist/components/media/index.js +20 -0
  42. package/dist/contexts/TeamContext.js +1 -1
  43. package/dist/hooks/index.d.ts +2 -0
  44. package/dist/hooks/index.d.ts.map +1 -1
  45. package/dist/hooks/index.js +2 -0
  46. package/dist/hooks/useEnsureUserMetadata.d.ts +4 -0
  47. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  48. package/dist/hooks/useEnsureUserMetadata.js +85 -60
  49. package/dist/hooks/useEntityMutations.d.ts.map +1 -1
  50. package/dist/hooks/useEntityMutations.js +5 -9
  51. package/dist/hooks/useMedia.d.ts +56 -0
  52. package/dist/hooks/useMedia.d.ts.map +1 -0
  53. package/dist/hooks/useMedia.js +181 -0
  54. package/dist/hooks/useMediaUpload.d.ts +27 -0
  55. package/dist/hooks/useMediaUpload.d.ts.map +1 -0
  56. package/dist/hooks/useMediaUpload.js +36 -0
  57. package/dist/hooks/useUserSettings.d.ts +5 -4
  58. package/dist/hooks/useUserSettings.d.ts.map +1 -1
  59. package/dist/hooks/useUserSettings.js +42 -40
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +2 -3
  63. package/dist/lib/api/auth/dual-auth.d.ts +6 -2
  64. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  65. package/dist/lib/api/auth/dual-auth.js +5 -9
  66. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  67. package/dist/lib/api/entity/generic-handler.js +3 -3
  68. package/dist/lib/config/app.config.d.ts.map +1 -1
  69. package/dist/lib/config/app.config.js +37 -0
  70. package/dist/lib/config/config-sync.d.ts +1 -0
  71. package/dist/lib/config/config-sync.d.ts.map +1 -1
  72. package/dist/lib/config/config-sync.js +2 -0
  73. package/dist/lib/config/types.d.ts +29 -0
  74. package/dist/lib/config/types.d.ts.map +1 -1
  75. package/dist/lib/media/schemas.d.ts +39 -0
  76. package/dist/lib/media/schemas.d.ts.map +1 -0
  77. package/dist/lib/media/schemas.js +32 -0
  78. package/dist/lib/media/types.d.ts +69 -0
  79. package/dist/lib/media/types.d.ts.map +1 -0
  80. package/dist/lib/media/types.js +0 -0
  81. package/dist/lib/media/utils.d.ts +26 -0
  82. package/dist/lib/media/utils.d.ts.map +1 -0
  83. package/dist/lib/media/utils.js +33 -0
  84. package/dist/lib/rate-limit-redis.d.ts.map +1 -1
  85. package/dist/lib/rate-limit-redis.js +13 -4
  86. package/dist/lib/scheduled-actions/initializer.d.ts +6 -3
  87. package/dist/lib/scheduled-actions/initializer.d.ts.map +1 -1
  88. package/dist/lib/scheduled-actions/initializer.js +11 -6
  89. package/dist/lib/scheduled-actions/processor.d.ts +20 -4
  90. package/dist/lib/scheduled-actions/processor.d.ts.map +1 -1
  91. package/dist/lib/scheduled-actions/processor.js +128 -34
  92. package/dist/lib/scheduled-actions/registry.d.ts +3 -0
  93. package/dist/lib/scheduled-actions/registry.d.ts.map +1 -1
  94. package/dist/lib/scheduled-actions/registry.js +2 -1
  95. package/dist/lib/scheduled-actions/scheduler.d.ts +1 -1
  96. package/dist/lib/scheduled-actions/scheduler.d.ts.map +1 -1
  97. package/dist/lib/scheduled-actions/scheduler.js +76 -38
  98. package/dist/lib/scheduled-actions/types.d.ts +73 -0
  99. package/dist/lib/scheduled-actions/types.d.ts.map +1 -1
  100. package/dist/lib/selectors/core-selectors.d.ts +102 -0
  101. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  102. package/dist/lib/selectors/core-selectors.js +3 -1
  103. package/dist/lib/selectors/domains/block-editor.selectors.d.ts +8 -0
  104. package/dist/lib/selectors/domains/block-editor.selectors.d.ts.map +1 -1
  105. package/dist/lib/selectors/domains/block-editor.selectors.js +9 -0
  106. package/dist/lib/selectors/domains/devtools.selectors.d.ts +6 -0
  107. package/dist/lib/selectors/domains/devtools.selectors.d.ts.map +1 -1
  108. package/dist/lib/selectors/domains/devtools.selectors.js +6 -0
  109. package/dist/lib/selectors/domains/index.d.ts +1 -0
  110. package/dist/lib/selectors/domains/index.d.ts.map +1 -1
  111. package/dist/lib/selectors/domains/index.js +2 -0
  112. package/dist/lib/selectors/domains/media.selectors.d.ts +96 -0
  113. package/dist/lib/selectors/domains/media.selectors.d.ts.map +1 -0
  114. package/dist/lib/selectors/domains/media.selectors.js +103 -0
  115. package/dist/lib/selectors/selectors.d.ts +204 -0
  116. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  117. package/dist/lib/services/index.d.ts +2 -0
  118. package/dist/lib/services/index.d.ts.map +1 -1
  119. package/dist/lib/services/index.js +2 -0
  120. package/dist/lib/services/media.service.d.ts +158 -0
  121. package/dist/lib/services/media.service.d.ts.map +1 -0
  122. package/dist/lib/services/media.service.js +410 -0
  123. package/dist/messages/de/devtools.json +16 -0
  124. package/dist/messages/de/index.d.ts +16 -0
  125. package/dist/messages/de/index.d.ts.map +1 -1
  126. package/dist/messages/en/admin.json +4 -1
  127. package/dist/messages/en/devtools.json +16 -0
  128. package/dist/messages/en/index.d.ts +167 -0
  129. package/dist/messages/en/index.d.ts.map +1 -1
  130. package/dist/messages/en/index.js +2 -0
  131. package/dist/messages/en/index.ts +2 -0
  132. package/dist/messages/en/media.json +147 -0
  133. package/dist/messages/en/navigation.json +1 -0
  134. package/dist/messages/es/admin.json +4 -1
  135. package/dist/messages/es/devtools.json +16 -0
  136. package/dist/messages/es/index.d.ts +167 -0
  137. package/dist/messages/es/index.d.ts.map +1 -1
  138. package/dist/messages/es/index.js +2 -0
  139. package/dist/messages/es/index.ts +2 -0
  140. package/dist/messages/es/media.json +147 -0
  141. package/dist/messages/es/navigation.json +1 -0
  142. package/dist/messages/fr/devtools.json +16 -0
  143. package/dist/messages/fr/index.d.ts +16 -0
  144. package/dist/messages/fr/index.d.ts.map +1 -1
  145. package/dist/messages/it/devtools.json +16 -0
  146. package/dist/messages/it/index.d.ts +16 -0
  147. package/dist/messages/it/index.d.ts.map +1 -1
  148. package/dist/messages/pt/devtools.json +16 -0
  149. package/dist/messages/pt/index.d.ts +16 -0
  150. package/dist/messages/pt/index.d.ts.map +1 -1
  151. package/dist/migrations/017_scheduled_actions_table.sql +21 -0
  152. package/dist/migrations/021_media.sql +154 -0
  153. package/dist/migrations/090_sample_data.sql +53 -0
  154. package/dist/styles/classes.json +36 -3
  155. package/dist/styles/ui.css +1 -1
  156. package/dist/templates/app/api/devtools/config/entities/route.ts +18 -11
  157. package/dist/templates/app/api/devtools/config/theme/route.ts +5 -4
  158. package/dist/templates/app/api/devtools/tests/[...path]/route.ts +6 -5
  159. package/dist/templates/app/api/devtools/tests/route.ts +5 -4
  160. package/dist/templates/app/api/health/route.ts +6 -4
  161. package/dist/templates/app/api/internal/user-metadata/route.ts +3 -2
  162. package/dist/templates/app/api/superadmin/subscriptions/route.ts +5 -6
  163. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -7
  164. package/dist/templates/app/api/superadmin/teams/route.ts +5 -6
  165. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +11 -16
  166. package/dist/templates/app/api/superadmin/users/route.ts +9 -10
  167. package/dist/templates/app/api/user/delete-account/route.ts +3 -2
  168. package/dist/templates/app/api/user/plan-flags/route.ts +11 -24
  169. package/dist/templates/app/api/user/profile/route.ts +7 -6
  170. package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +16 -18
  171. package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +17 -19
  172. package/dist/templates/app/api/v1/[entity]/[id]/route.ts +10 -12
  173. package/dist/templates/app/api/v1/[entity]/route.ts +9 -11
  174. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +9 -8
  175. package/dist/templates/app/api/v1/api-keys/route.ts +7 -6
  176. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +3 -2
  177. package/dist/templates/app/api/v1/billing/cancel/route.ts +15 -14
  178. package/dist/templates/app/api/v1/billing/change-plan/route.ts +10 -9
  179. package/dist/templates/app/api/v1/billing/check-action/route.ts +8 -7
  180. package/dist/templates/app/api/v1/billing/checkout/route.ts +10 -9
  181. package/dist/templates/app/api/v1/billing/plans/route.ts +5 -4
  182. package/dist/templates/app/api/v1/billing/portal/route.ts +9 -8
  183. package/dist/templates/app/api/v1/blocks/[slug]/route.ts +4 -3
  184. package/dist/templates/app/api/v1/blocks/route.ts +3 -2
  185. package/dist/templates/app/api/v1/blocks/validate/route.ts +5 -3
  186. package/dist/templates/app/api/v1/cron/process/route.ts +4 -6
  187. package/dist/templates/app/api/v1/devtools/blocks/route.ts +3 -2
  188. package/dist/templates/app/api/v1/devtools/docs/route.ts +3 -2
  189. package/dist/templates/app/api/v1/devtools/features/route.ts +3 -2
  190. package/dist/templates/app/api/v1/devtools/flows/route.ts +3 -2
  191. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +125 -3
  192. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +110 -0
  193. package/dist/templates/app/api/v1/devtools/testing/route.ts +3 -2
  194. package/dist/templates/app/api/v1/media/[id]/route.ts +144 -0
  195. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +154 -0
  196. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +56 -0
  197. package/dist/templates/app/api/v1/media/route.ts +56 -0
  198. package/dist/templates/app/api/v1/media/upload/route.ts +157 -33
  199. package/dist/templates/app/api/v1/media-tags/route.ts +65 -0
  200. package/dist/templates/app/api/v1/plugin/[...path]/route.ts +16 -15
  201. package/dist/templates/app/api/v1/plugin/route.ts +3 -2
  202. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +10 -9
  203. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  204. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +3 -3
  205. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +3 -3
  206. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +3 -2
  207. package/dist/templates/app/api/v1/team-invitations/route.ts +3 -2
  208. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +5 -4
  209. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +3 -2
  210. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +3 -2
  211. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +5 -4
  212. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +5 -5
  213. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +31 -58
  214. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -2
  215. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +5 -4
  216. package/dist/templates/app/api/v1/teams/route.ts +18 -17
  217. package/dist/templates/app/api/v1/teams/switch/route.ts +3 -2
  218. package/dist/templates/app/api/v1/theme/[...path]/route.ts +16 -15
  219. package/dist/templates/app/api/v1/theme/route.ts +3 -2
  220. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -6
  221. package/dist/templates/app/api/v1/users/[id]/route.ts +9 -8
  222. package/dist/templates/app/api/v1/users/route.ts +7 -6
  223. package/dist/templates/app/dashboard/(main)/media/page.tsx +607 -0
  224. package/dist/templates/contents/themes/starter/messages/de/dev.json +106 -0
  225. package/dist/templates/contents/themes/starter/messages/de/index.ts +2 -0
  226. package/dist/templates/contents/themes/starter/messages/en/dev.json +106 -0
  227. package/dist/templates/contents/themes/starter/messages/en/index.ts +2 -0
  228. package/dist/templates/contents/themes/starter/messages/es/dev.json +106 -0
  229. package/dist/templates/contents/themes/starter/messages/es/index.ts +2 -0
  230. package/dist/templates/contents/themes/starter/messages/fr/dev.json +106 -0
  231. package/dist/templates/contents/themes/starter/messages/fr/index.ts +2 -0
  232. package/dist/templates/contents/themes/starter/messages/it/dev.json +106 -0
  233. package/dist/templates/contents/themes/starter/messages/it/index.ts +2 -0
  234. package/dist/templates/contents/themes/starter/messages/pt/dev.json +106 -0
  235. package/dist/templates/contents/themes/starter/messages/pt/index.ts +2 -0
  236. package/dist/templates/contents/themes/starter/styles/globals.css +14 -0
  237. package/dist/templates/instrumentation.ts +33 -0
  238. package/dist/types/blocks.d.ts +1 -1
  239. package/dist/types/blocks.d.ts.map +1 -1
  240. package/migrations/017_scheduled_actions_table.sql +21 -0
  241. package/migrations/021_media.sql +154 -0
  242. package/migrations/090_sample_data.sql +53 -0
  243. package/package.json +3 -2
  244. package/scripts/build/registry/config.mjs +41 -0
  245. package/scripts/build/registry/discovery/templates.mjs +0 -1
  246. package/scripts/build/registry/generators/entity-registry.mjs +16 -6
  247. package/scripts/build/registry/generators/route-handlers.mjs +8 -2
  248. package/scripts/build/registry/generators/template-registry.mjs +16 -4
  249. package/scripts/build/registry/post-build/route-cleanup.mjs +0 -1
  250. package/scripts/build/registry/validate-env.test.mjs +92 -0
  251. package/scripts/build/registry.mjs +18 -1
  252. package/scripts/deploy/vercel-deploy.mjs +1 -1
  253. package/templates/app/api/devtools/config/entities/route.ts +18 -11
  254. package/templates/app/api/devtools/config/theme/route.ts +5 -4
  255. package/templates/app/api/devtools/tests/[...path]/route.ts +6 -5
  256. package/templates/app/api/devtools/tests/route.ts +5 -4
  257. package/templates/app/api/health/route.ts +6 -4
  258. package/templates/app/api/internal/user-metadata/route.ts +3 -2
  259. package/templates/app/api/superadmin/subscriptions/route.ts +5 -6
  260. package/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -7
  261. package/templates/app/api/superadmin/teams/route.ts +5 -6
  262. package/templates/app/api/superadmin/users/[userId]/route.ts +11 -16
  263. package/templates/app/api/superadmin/users/route.ts +9 -10
  264. package/templates/app/api/user/delete-account/route.ts +3 -2
  265. package/templates/app/api/user/plan-flags/route.ts +11 -24
  266. package/templates/app/api/user/profile/route.ts +7 -6
  267. package/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +16 -18
  268. package/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +17 -19
  269. package/templates/app/api/v1/[entity]/[id]/route.ts +10 -12
  270. package/templates/app/api/v1/[entity]/route.ts +9 -11
  271. package/templates/app/api/v1/api-keys/[id]/route.ts +9 -8
  272. package/templates/app/api/v1/api-keys/route.ts +7 -6
  273. package/templates/app/api/v1/auth/signup-with-invite/route.ts +3 -2
  274. package/templates/app/api/v1/billing/cancel/route.ts +15 -14
  275. package/templates/app/api/v1/billing/change-plan/route.ts +10 -9
  276. package/templates/app/api/v1/billing/check-action/route.ts +8 -7
  277. package/templates/app/api/v1/billing/checkout/route.ts +10 -9
  278. package/templates/app/api/v1/billing/plans/route.ts +5 -4
  279. package/templates/app/api/v1/billing/portal/route.ts +9 -8
  280. package/templates/app/api/v1/blocks/[slug]/route.ts +4 -3
  281. package/templates/app/api/v1/blocks/route.ts +3 -2
  282. package/templates/app/api/v1/blocks/validate/route.ts +5 -3
  283. package/templates/app/api/v1/cron/process/route.ts +4 -6
  284. package/templates/app/api/v1/devtools/blocks/route.ts +3 -2
  285. package/templates/app/api/v1/devtools/docs/route.ts +3 -2
  286. package/templates/app/api/v1/devtools/features/route.ts +3 -2
  287. package/templates/app/api/v1/devtools/flows/route.ts +3 -2
  288. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +125 -3
  289. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +110 -0
  290. package/templates/app/api/v1/devtools/testing/route.ts +3 -2
  291. package/templates/app/api/v1/media/[id]/route.ts +144 -0
  292. package/templates/app/api/v1/media/[id]/tags/route.ts +154 -0
  293. package/templates/app/api/v1/media/check-duplicates/route.ts +56 -0
  294. package/templates/app/api/v1/media/route.ts +56 -0
  295. package/templates/app/api/v1/media/upload/route.ts +157 -33
  296. package/templates/app/api/v1/media-tags/route.ts +65 -0
  297. package/templates/app/api/v1/plugin/[...path]/route.ts +16 -15
  298. package/templates/app/api/v1/plugin/route.ts +3 -2
  299. package/templates/app/api/v1/post-categories/[id]/route.ts +10 -9
  300. package/templates/app/api/v1/post-categories/route.ts +5 -4
  301. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +3 -3
  302. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +3 -3
  303. package/templates/app/api/v1/team-invitations/[token]/route.ts +3 -2
  304. package/templates/app/api/v1/team-invitations/route.ts +3 -2
  305. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +5 -4
  306. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +3 -2
  307. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +3 -2
  308. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +5 -4
  309. package/templates/app/api/v1/teams/[teamId]/members/route.ts +5 -5
  310. package/templates/app/api/v1/teams/[teamId]/route.ts +31 -58
  311. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -2
  312. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +5 -4
  313. package/templates/app/api/v1/teams/route.ts +18 -17
  314. package/templates/app/api/v1/teams/switch/route.ts +3 -2
  315. package/templates/app/api/v1/theme/[...path]/route.ts +16 -15
  316. package/templates/app/api/v1/theme/route.ts +3 -2
  317. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -6
  318. package/templates/app/api/v1/users/[id]/route.ts +9 -8
  319. package/templates/app/api/v1/users/route.ts +7 -6
  320. package/templates/app/dashboard/(main)/media/page.tsx +607 -0
  321. package/templates/contents/themes/starter/messages/de/dev.json +106 -0
  322. package/templates/contents/themes/starter/messages/de/index.ts +2 -0
  323. package/templates/contents/themes/starter/messages/en/dev.json +106 -0
  324. package/templates/contents/themes/starter/messages/en/index.ts +2 -0
  325. package/templates/contents/themes/starter/messages/es/dev.json +106 -0
  326. package/templates/contents/themes/starter/messages/es/index.ts +2 -0
  327. package/templates/contents/themes/starter/messages/fr/dev.json +106 -0
  328. package/templates/contents/themes/starter/messages/fr/index.ts +2 -0
  329. package/templates/contents/themes/starter/messages/it/dev.json +106 -0
  330. package/templates/contents/themes/starter/messages/it/index.ts +2 -0
  331. package/templates/contents/themes/starter/messages/pt/dev.json +106 -0
  332. package/templates/contents/themes/starter/messages/pt/index.ts +2 -0
  333. package/templates/contents/themes/starter/styles/globals.css +14 -0
  334. package/templates/instrumentation.ts +33 -0
  335. package/dist/presets/plugin/.env.example.template +0 -19
  336. package/dist/presets/plugin/entities/.gitkeep +0 -18
  337. package/dist/presets/theme/blocks/.gitkeep +0 -17
  338. package/dist/presets/theme/public/brand/.gitkeep +0 -8
  339. package/dist/presets/theme/tests/cypress/.gitkeep +0 -10
  340. package/dist/templates/contents/plugins/starter/plugin/.env.example.template +0 -19
  341. package/templates/contents/plugins/starter/plugin/.env.example.template +0 -19
@@ -1,11 +1,22 @@
1
1
  /**
2
2
  * Scheduled Actions - Processor
3
- * Processes pending actions and manages their lifecycle
3
+ * Processes pending actions with parallel execution and lock-group based locking
4
+ *
5
+ * Features:
6
+ * - Controlled parallelism with configurable concurrency limit
7
+ * - Lock-group based locking to prevent race conditions on same resource
8
+ * - SELECT FOR UPDATE SKIP LOCKED for safe concurrent processing
9
+ * - Timeout protection per action
4
10
  */
5
- import type { ProcessResult } from './types';
11
+ import type { ScheduledAction, ProcessResult } from './types';
6
12
  /**
7
- * Process pending actions
8
- * Fetches pending actions, executes them, and updates their status
13
+ * Process pending actions with parallel execution
14
+ * Uses SELECT FOR UPDATE SKIP LOCKED for safe concurrent processing
15
+ *
16
+ * Lock Group Behavior:
17
+ * - Actions with same lockGroup are processed sequentially
18
+ * - Actions with NULL lockGroup can run in parallel with any other action
19
+ * - Uses PostgreSQL row-level locking to prevent race conditions
9
20
  *
10
21
  * @param batchSize - Maximum number of actions to process (uses config default if not provided)
11
22
  * @returns Result with counts and errors
@@ -15,4 +26,9 @@ import type { ProcessResult } from './types';
15
26
  * console.log(`Processed ${result.processed}, succeeded ${result.succeeded}, failed ${result.failed}`)
16
27
  */
17
28
  export declare function processPendingActions(batchSize?: number): Promise<ProcessResult>;
29
+ /**
30
+ * Execute a single action
31
+ * Handles the full lifecycle of action execution
32
+ */
33
+ export declare function executeAction(action: ScheduledAction): Promise<void>;
18
34
  //# sourceMappingURL=processor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/processor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,SAAS,CAAA;AAM7D;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CA+CxB"}
1
+ {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAO7D;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CAkExB;AAuFD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA4D1E"}
@@ -4,9 +4,11 @@ import { getActionHandler } from "./registry.js";
4
4
  import { scheduleAction } from "./scheduler.js";
5
5
  const DEFAULT_BATCH_SIZE = 10;
6
6
  const DEFAULT_TIMEOUT = 3e4;
7
+ const DEFAULT_CONCURRENCY_LIMIT = 1;
7
8
  async function processPendingActions(batchSize) {
8
- var _a;
9
+ var _a, _b;
9
10
  const effectiveBatchSize = batchSize ?? ((_a = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _a.batchSize) ?? DEFAULT_BATCH_SIZE;
11
+ const concurrencyLimit = ((_b = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _b.concurrencyLimit) ?? DEFAULT_CONCURRENCY_LIMIT;
10
12
  const result = {
11
13
  processed: 0,
12
14
  succeeded: 0,
@@ -14,28 +16,39 @@ async function processPendingActions(batchSize) {
14
16
  errors: []
15
17
  };
16
18
  try {
17
- const actions = await queryWithRLS(
18
- `SELECT * FROM "scheduled_actions"
19
- WHERE status = 'pending'
20
- AND "scheduledAt" <= NOW()
21
- ORDER BY "scheduledAt" ASC
22
- LIMIT $1`,
23
- [effectiveBatchSize],
24
- null
25
- // System operation
26
- );
27
- console.log(`[ScheduledActions] Found ${actions.length} pending action(s) to process`);
28
- for (const action of actions) {
29
- result.processed++;
30
- try {
31
- await executeAction(action);
32
- result.succeeded++;
33
- } catch (error) {
34
- result.failed++;
35
- result.errors.push({
36
- actionId: action.id,
37
- error: error instanceof Error ? error.message : "Unknown error"
38
- });
19
+ const actions = await fetchAndLockPendingActions(effectiveBatchSize);
20
+ if (actions.length === 0) {
21
+ console.log("[ScheduledActions] No pending actions to process");
22
+ return result;
23
+ }
24
+ console.log(`[ScheduledActions] Found ${actions.length} pending action(s) to process (concurrency: ${concurrencyLimit})`);
25
+ if (concurrencyLimit === 1) {
26
+ for (const action of actions) {
27
+ result.processed++;
28
+ try {
29
+ await executeAction(action);
30
+ result.succeeded++;
31
+ } catch (error) {
32
+ result.failed++;
33
+ result.errors.push({
34
+ actionId: action.id,
35
+ error: error instanceof Error ? error.message : "Unknown error"
36
+ });
37
+ }
38
+ }
39
+ } else {
40
+ const results = await processWithConcurrencyLimit(actions, concurrencyLimit);
41
+ for (const r of results) {
42
+ result.processed++;
43
+ if (r.status === "fulfilled") {
44
+ result.succeeded++;
45
+ } else {
46
+ result.failed++;
47
+ result.errors.push({
48
+ actionId: r.actionId,
49
+ error: r.reason instanceof Error ? r.reason.message : "Unknown error"
50
+ });
51
+ }
39
52
  }
40
53
  }
41
54
  console.log(`[ScheduledActions] Processing complete: ${result.succeeded} succeeded, ${result.failed} failed`);
@@ -45,11 +58,57 @@ async function processPendingActions(batchSize) {
45
58
  }
46
59
  return result;
47
60
  }
61
+ async function fetchAndLockPendingActions(batchSize) {
62
+ const actions = await queryWithRLS(
63
+ `WITH ranked_actions AS (
64
+ SELECT *,
65
+ ROW_NUMBER() OVER (
66
+ PARTITION BY "lockGroup"
67
+ ORDER BY "scheduledAt" ASC
68
+ ) as rn
69
+ FROM "scheduled_actions"
70
+ WHERE status = 'pending'
71
+ AND "scheduledAt" <= NOW()
72
+ )
73
+ SELECT id, "actionType", status, payload, "teamId", "scheduledAt",
74
+ "startedAt", "completedAt", "errorMessage", attempts,
75
+ "recurringInterval", "recurrenceType", "lockGroup", "maxRetries",
76
+ "createdAt", "updatedAt"
77
+ FROM ranked_actions
78
+ WHERE rn = 1 OR "lockGroup" IS NULL
79
+ ORDER BY "scheduledAt" ASC
80
+ LIMIT $1
81
+ FOR UPDATE SKIP LOCKED`,
82
+ [batchSize],
83
+ null
84
+ // System operation
85
+ );
86
+ return actions;
87
+ }
88
+ async function processWithConcurrencyLimit(actions, limit) {
89
+ const results = [];
90
+ const executing = /* @__PURE__ */ new Set();
91
+ for (const action of actions) {
92
+ const promise = executeAction(action).then(() => {
93
+ results.push({ actionId: action.id, status: "fulfilled" });
94
+ }).catch((error) => {
95
+ results.push({ actionId: action.id, status: "rejected", reason: error });
96
+ }).finally(() => {
97
+ executing.delete(promise);
98
+ });
99
+ executing.add(promise);
100
+ if (executing.size >= limit) {
101
+ await Promise.race(executing);
102
+ }
103
+ }
104
+ await Promise.all(executing);
105
+ return results;
106
+ }
48
107
  async function executeAction(action) {
49
108
  var _a;
50
109
  const startTime = Date.now();
51
110
  const configDefaultTimeout = ((_a = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _a.defaultTimeout) ?? DEFAULT_TIMEOUT;
52
- console.log(`[ScheduledActions] Executing action ${action.id} (${action.actionType})`);
111
+ console.log(`[ScheduledActions] Executing action ${action.id} (${action.actionType})${action.lockGroup ? ` [lockGroup: ${action.lockGroup}]` : ""}`);
53
112
  await markActionRunning(action.id);
54
113
  try {
55
114
  const actionDef = getActionHandler(action.actionType);
@@ -72,7 +131,14 @@ async function executeAction(action) {
72
131
  const executionTime = Date.now() - startTime;
73
132
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
74
133
  console.error(`[ScheduledActions] Action ${action.id} failed after ${executionTime}ms:`, errorMessage);
75
- await markActionFailed(action.id, errorMessage);
134
+ if (action.attempts < action.maxRetries) {
135
+ const retryDelayMinutes = action.attempts * 5;
136
+ console.log(`[ScheduledActions] Will retry action ${action.id} in ${retryDelayMinutes} minutes (attempt ${action.attempts}/${action.maxRetries})`);
137
+ await rescheduleFailedAction(action, errorMessage, retryDelayMinutes);
138
+ } else {
139
+ console.error(`[ScheduledActions] Max retries (${action.maxRetries}) reached for action ${action.id}`);
140
+ await markActionFailed(action.id, errorMessage);
141
+ }
76
142
  throw error;
77
143
  }
78
144
  }
@@ -117,36 +183,64 @@ async function markActionFailed(actionId, errorMessage) {
117
183
  null
118
184
  );
119
185
  }
186
+ async function rescheduleFailedAction(action, errorMessage, delayMinutes) {
187
+ const nextAttemptTime = new Date(Date.now() + delayMinutes * 60 * 1e3);
188
+ await mutateWithRLS(
189
+ `UPDATE "scheduled_actions"
190
+ SET status = 'pending',
191
+ "scheduledAt" = $2,
192
+ "errorMessage" = $3,
193
+ "startedAt" = NULL,
194
+ "completedAt" = NULL,
195
+ "updatedAt" = NOW()
196
+ WHERE id = $1`,
197
+ [action.id, nextAttemptTime, errorMessage],
198
+ null
199
+ );
200
+ console.log(`[ScheduledActions] Rescheduled action ${action.id} for ${nextAttemptTime.toISOString()}`);
201
+ }
120
202
  async function rescheduleRecurringAction(action) {
121
203
  if (!action.recurringInterval) {
122
204
  return;
123
205
  }
124
- const nextScheduledAt = calculateNextScheduledTime(action.recurringInterval);
206
+ let nextScheduledAt;
207
+ if (action.recurrenceType === "rolling") {
208
+ nextScheduledAt = calculateNextScheduledTime(action.recurringInterval, /* @__PURE__ */ new Date());
209
+ } else {
210
+ nextScheduledAt = calculateNextScheduledTime(action.recurringInterval, action.scheduledAt);
211
+ }
125
212
  await scheduleAction(
126
213
  action.actionType,
127
214
  action.payload,
128
215
  {
129
216
  scheduledAt: nextScheduledAt,
130
217
  teamId: action.teamId ?? void 0,
131
- recurringInterval: action.recurringInterval ?? void 0
218
+ recurringInterval: action.recurringInterval ?? void 0,
219
+ recurrenceType: action.recurrenceType ?? void 0,
220
+ lockGroup: action.lockGroup ?? void 0
132
221
  }
133
222
  );
134
- console.log(`[ScheduledActions] Rescheduled recurring action ${action.actionType} for ${nextScheduledAt.toISOString()}`);
223
+ console.log(
224
+ `[ScheduledActions] Rescheduled recurring action ${action.actionType} for ${nextScheduledAt.toISOString()} (type: ${action.recurrenceType || "fixed"})`
225
+ );
135
226
  }
136
- function calculateNextScheduledTime(interval) {
137
- const now = /* @__PURE__ */ new Date();
227
+ function calculateNextScheduledTime(interval, baseTime) {
228
+ const base = new Date(baseTime);
138
229
  switch (interval) {
230
+ case "every-30-minutes":
231
+ return new Date(base.getTime() + 30 * 60 * 1e3);
139
232
  case "hourly":
140
- return new Date(now.getTime() + 60 * 60 * 1e3);
233
+ return new Date(base.getTime() + 60 * 60 * 1e3);
141
234
  case "daily":
142
- return new Date(now.getTime() + 24 * 60 * 60 * 1e3);
235
+ return new Date(base.getTime() + 24 * 60 * 60 * 1e3);
143
236
  case "weekly":
144
- return new Date(now.getTime() + 7 * 24 * 60 * 60 * 1e3);
237
+ return new Date(base.getTime() + 7 * 24 * 60 * 60 * 1e3);
145
238
  default:
146
239
  console.warn(`[ScheduledActions] Unknown interval '${interval}', defaulting to 1 hour`);
147
- return new Date(now.getTime() + 60 * 60 * 1e3);
240
+ return new Date(base.getTime() + 60 * 60 * 1e3);
148
241
  }
149
242
  }
150
243
  export {
244
+ executeAction,
151
245
  processPendingActions
152
246
  };
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * Scheduled Actions - Registry
3
3
  * Manages registration and lookup of action handlers
4
+ *
5
+ * Uses globalThis to persist across HMR in development mode.
6
+ * Same pattern as Next.js recommends for database connections.
4
7
  */
5
8
  import type { ScheduledActionDefinition, ScheduledActionHandler } from './types';
6
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAShF;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,EAC/B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,IAAI,CAaN;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAEpF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAehF;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,EAC/B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,IAAI,CAaN;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAEpF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
@@ -1,4 +1,5 @@
1
- const actionRegistry = /* @__PURE__ */ new Map();
1
+ const globalForScheduledActions = globalThis;
2
+ const actionRegistry = globalForScheduledActions.__scheduledActionsRegistry ??= /* @__PURE__ */ new Map();
2
3
  function registerScheduledAction(name, handler, options) {
3
4
  if (actionRegistry.has(name)) {
4
5
  console.warn(`[ScheduledActions] Action '${name}' is already registered. Overwriting.`);
@@ -28,7 +28,7 @@ import type { ScheduleOptions } from './types';
28
28
  * data: { title: 'New Task' }
29
29
  * }, { teamId: 'team-456' })
30
30
  */
31
- export declare function scheduleAction(actionType: string, payload: unknown, options?: ScheduleOptions): Promise<string | null>;
31
+ export declare function scheduleAction(actionType: string, payload: unknown, options?: ScheduleOptions): Promise<string>;
32
32
  /**
33
33
  * Schedule a recurring action
34
34
  * Convenience function that sets recurringInterval
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsFxB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,mBAAmB,CAAC,GACnD,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoB9E"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,CAAC,CAkIjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,mBAAmB,CAAC,GACnD,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoB9E"}
@@ -1,4 +1,4 @@
1
- import { mutateWithRLS, queryWithRLS } from "../db.js";
1
+ import { mutateWithRLS, getTransactionClient } from "../db.js";
2
2
  import { APP_CONFIG_MERGED } from "../config/config-sync.js";
3
3
  async function scheduleAction(actionType, payload, options) {
4
4
  var _a;
@@ -8,41 +8,74 @@ async function scheduleAction(actionType, payload, options) {
8
8
  const entityId = payloadObj == null ? void 0 : payloadObj.entityId;
9
9
  const entityType = payloadObj == null ? void 0 : payloadObj.entityType;
10
10
  const shouldDeduplicate = windowSeconds > 0 && entityId && !(options == null ? void 0 : options.recurringInterval);
11
+ const actionId = globalThis.crypto.randomUUID();
12
+ const scheduledAt = (options == null ? void 0 : options.scheduledAt) || /* @__PURE__ */ new Date();
13
+ const teamId = (options == null ? void 0 : options.teamId) || null;
14
+ const recurringInterval = (options == null ? void 0 : options.recurringInterval) || null;
15
+ const lockGroup = (options == null ? void 0 : options.lockGroup) || null;
16
+ const maxRetries = (options == null ? void 0 : options.maxRetries) ?? 3;
17
+ const recurrenceType = (options == null ? void 0 : options.recurrenceType) || null;
11
18
  if (shouldDeduplicate) {
12
19
  const dedupKey = `${actionType}:${entityId}:${entityType || ""}`;
13
- await queryWithRLS(
14
- `SELECT pg_advisory_xact_lock(hashtext($1))`,
15
- [dedupKey],
16
- null
17
- );
18
- const existing = await queryWithRLS(
19
- `SELECT id
20
- FROM "scheduled_actions"
21
- WHERE "actionType" = $1
22
- AND status = 'pending'
23
- AND payload->>'entityId' = $2
24
- AND payload->>'entityType' = $3
25
- AND "createdAt" > NOW() - INTERVAL '1 second' * $4
26
- LIMIT 1`,
27
- [actionType, entityId, entityType || "", windowSeconds],
28
- null
29
- );
30
- if (existing.length > 0) {
31
- await mutateWithRLS(
32
- `UPDATE "scheduled_actions"
33
- SET payload = $1, "updatedAt" = NOW()
34
- WHERE id = $2 AND status = 'pending'`,
35
- [JSON.stringify(payload), existing[0].id],
36
- null
20
+ const client = await getTransactionClient(null);
21
+ try {
22
+ await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [dedupKey]);
23
+ const existing = await client.query(
24
+ `SELECT id
25
+ FROM "scheduled_actions"
26
+ WHERE "actionType" = $1
27
+ AND status = 'pending'
28
+ AND payload->>'entityId' = $2
29
+ AND payload->>'entityType' = $3
30
+ AND "createdAt" > NOW() - INTERVAL '1 second' * $4
31
+ LIMIT 1`,
32
+ [actionType, entityId, entityType || "", windowSeconds]
33
+ );
34
+ if (existing.length > 0) {
35
+ await client.query(
36
+ `UPDATE "scheduled_actions"
37
+ SET payload = $1, "updatedAt" = NOW()
38
+ WHERE id = $2 AND status = 'pending'`,
39
+ [payload, existing[0].id]
40
+ );
41
+ await client.commit();
42
+ console.log(`[ScheduledActions] Duplicate detected, updated payload: ${existing[0].id}`);
43
+ return existing[0].id;
44
+ }
45
+ await client.query(
46
+ `INSERT INTO "scheduled_actions" (
47
+ id,
48
+ "actionType",
49
+ status,
50
+ payload,
51
+ "teamId",
52
+ "scheduledAt",
53
+ "recurringInterval",
54
+ "lockGroup",
55
+ "maxRetries",
56
+ "recurrenceType"
57
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
58
+ [
59
+ actionId,
60
+ actionType,
61
+ "pending",
62
+ payload,
63
+ teamId,
64
+ scheduledAt.toISOString(),
65
+ recurringInterval,
66
+ lockGroup,
67
+ maxRetries,
68
+ recurrenceType
69
+ ]
37
70
  );
38
- console.log(`[ScheduledActions] Duplicate detected, updated payload: ${existing[0].id}`);
39
- return existing[0].id;
71
+ await client.commit();
72
+ console.log(`[ScheduledActions] Scheduled action '${actionType}' with ID: ${actionId}${lockGroup ? ` (lockGroup: ${lockGroup})` : ""}`);
73
+ return actionId;
74
+ } catch (error) {
75
+ await client.rollback();
76
+ throw error;
40
77
  }
41
78
  }
42
- const actionId = globalThis.crypto.randomUUID();
43
- const scheduledAt = (options == null ? void 0 : options.scheduledAt) || /* @__PURE__ */ new Date();
44
- const teamId = (options == null ? void 0 : options.teamId) || null;
45
- const recurringInterval = (options == null ? void 0 : options.recurringInterval) || null;
46
79
  await mutateWithRLS(
47
80
  `INSERT INTO "scheduled_actions" (
48
81
  id,
@@ -51,29 +84,34 @@ async function scheduleAction(actionType, payload, options) {
51
84
  payload,
52
85
  "teamId",
53
86
  "scheduledAt",
54
- "recurringInterval"
55
- ) VALUES ($1, $2, $3, $4, $5, $6, $7)`,
87
+ "recurringInterval",
88
+ "lockGroup",
89
+ "maxRetries",
90
+ "recurrenceType"
91
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
56
92
  [
57
93
  actionId,
58
94
  actionType,
59
95
  "pending",
60
- JSON.stringify(payload),
96
+ payload,
61
97
  teamId,
62
98
  scheduledAt.toISOString(),
63
- recurringInterval
99
+ recurringInterval,
100
+ lockGroup,
101
+ maxRetries,
102
+ recurrenceType
64
103
  ],
65
104
  null
66
105
  // System operation, no RLS context needed
67
106
  );
68
- console.log(`[ScheduledActions] Scheduled action '${actionType}' with ID: ${actionId}`);
107
+ console.log(`[ScheduledActions] Scheduled action '${actionType}' with ID: ${actionId}${lockGroup ? ` (lockGroup: ${lockGroup})` : ""}`);
69
108
  return actionId;
70
109
  }
71
110
  async function scheduleRecurringAction(actionType, payload, interval, options) {
72
- const result = await scheduleAction(actionType, payload, {
111
+ return scheduleAction(actionType, payload, {
73
112
  ...options,
74
113
  recurringInterval: interval
75
114
  });
76
- return result;
77
115
  }
78
116
  async function cancelScheduledAction(actionId) {
79
117
  const result = await mutateWithRLS(
@@ -3,6 +3,17 @@
3
3
  * Core types for the scheduled actions system
4
4
  */
5
5
  export type ScheduledActionStatus = 'pending' | 'running' | 'completed' | 'failed';
6
+ /**
7
+ * Determines how next execution time is calculated for recurring actions
8
+ *
9
+ * @example
10
+ * 'fixed' - Maintains exact schedule times (e.g., daily at 12:00)
11
+ * If action scheduled at 12:00 runs at 12:05, next is still 12:00 tomorrow
12
+ *
13
+ * 'rolling' - Intervals from actual completion time (e.g., 30min after execution finishes)
14
+ * If action scheduled at 12:00 runs at 12:15, next is 12:45
15
+ */
16
+ export type RecurrenceType = 'fixed' | 'rolling';
6
17
  /**
7
18
  * Scheduled action record from database
8
19
  * NOTE: Database uses camelCase (e.g., actionType, teamId, scheduledAt)
@@ -18,7 +29,34 @@ export interface ScheduledAction {
18
29
  completedAt: Date | null;
19
30
  errorMessage: string | null;
20
31
  attempts: number;
32
+ /**
33
+ * Maximum value for the attempts counter before marking action as failed.
34
+ * Represents total attempts (initial execution + retries), not retry count.
35
+ * Default: 3
36
+ *
37
+ * @example
38
+ * maxRetries = 0 -> Fail immediately, no retries (1 total attempt)
39
+ * maxRetries = 3 -> 3 total attempts (1 initial + 2 retries)
40
+ * maxRetries = 5 -> 5 total attempts (1 initial + 4 retries)
41
+ */
42
+ maxRetries: number;
21
43
  recurringInterval: string | null;
44
+ /**
45
+ * Determines how next execution time is calculated for recurring actions.
46
+ * Only relevant when recurringInterval is set.
47
+ * Default: 'fixed'
48
+ */
49
+ recurrenceType: RecurrenceType | null;
50
+ /**
51
+ * Lock group key for parallel execution control.
52
+ * Actions with the same lockGroup will be processed sequentially.
53
+ * Actions with NULL lockGroup can run in parallel with any other action.
54
+ *
55
+ * @example
56
+ * lockGroup = 'client:123' -> All actions for client 123 run sequentially
57
+ * lockGroup = 'content:456' -> All actions for content 456 run sequentially
58
+ */
59
+ lockGroup: string | null;
22
60
  createdAt: Date;
23
61
  updatedAt: Date;
24
62
  }
@@ -46,6 +84,41 @@ export interface ScheduleOptions {
46
84
  scheduledAt?: Date;
47
85
  teamId?: string;
48
86
  recurringInterval?: 'hourly' | 'daily' | 'weekly' | string;
87
+ /**
88
+ * Maximum value for the attempts counter before marking action as failed.
89
+ * Represents total attempts (initial execution + retries), not retry count.
90
+ * Default: 3
91
+ *
92
+ * @example
93
+ * { maxRetries: 0 } -> Fail immediately, no retries (1 total attempt)
94
+ * { maxRetries: 3 } -> 3 total attempts (1 initial + 2 retries)
95
+ * { maxRetries: 5 } -> 5 total attempts (1 initial + 4 retries) for critical operations
96
+ */
97
+ maxRetries?: number;
98
+ /**
99
+ * Lock group key for parallel execution control.
100
+ * Actions with the same lockGroup will be processed sequentially.
101
+ *
102
+ * @example
103
+ * { lockGroup: 'client:123' } -> All actions for client 123 run sequentially
104
+ * { lockGroup: `content:${contentId}` } -> Prevents concurrent content publishing
105
+ */
106
+ lockGroup?: string;
107
+ /**
108
+ * Determines how next execution time is calculated for recurring actions.
109
+ * Only relevant when recurringInterval is set.
110
+ * Default: 'fixed'
111
+ *
112
+ * @example
113
+ * // Daily report at 12:00 (fixed schedule)
114
+ * { recurringInterval: 'daily', recurrenceType: 'fixed' }
115
+ * // If runs at 12:05, next is still 12:00 tomorrow
116
+ *
117
+ * // Token refresh every 30 minutes (rolling interval)
118
+ * { recurringInterval: 'every-30-minutes', recurrenceType: 'rolling' }
119
+ * // If scheduled at 12:00 but runs at 12:15, next is 12:45
120
+ */
121
+ recurrenceType?: RecurrenceType;
49
122
  }
50
123
  /**
51
124
  * Result of processing actions
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAA;AAElF;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,WAAW,EAAE,IAAI,CAAA;IACjB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,sBAAsB,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAA;AAElF;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,CAAA;AAEhD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,WAAW,EAAE,IAAI,CAAA;IACjB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;;;;OASG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC;;;;OAIG;IACH,cAAc,EAAE,cAAc,GAAG,IAAI,CAAA;IACrC;;;;;;;;OAQG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,sBAAsB,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAC1D;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD"}