@rebasepro/server 0.23.0 → 0.24.1-canary.g19596ef

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 (287) hide show
  1. package/README.md +28 -3
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/GCSStorageController-DEga1ULx.js +402 -0
  4. package/dist/GCSStorageController-DEga1ULx.js.map +1 -0
  5. package/dist/S3StorageController-Btx-6bJ5.js +480 -0
  6. package/dist/S3StorageController-Btx-6bJ5.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +41 -0
  9. package/dist/api/live-schema-routes.d.ts +64 -10
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/mount-path.d.ts +30 -0
  12. package/dist/api/mount.d.ts +6 -13
  13. package/dist/api/openapi-generator.d.ts +17 -0
  14. package/dist/api/rest/api-generator.d.ts +97 -11
  15. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  16. package/dist/api/rest/field-access-query.d.ts +28 -8
  17. package/dist/api/rest/field-ops.d.ts +19 -7
  18. package/dist/api/rest/query-body.d.ts +10 -0
  19. package/dist/api/rest/write-validation.d.ts +9 -7
  20. package/dist/api/schema-editor-routes.d.ts +10 -1
  21. package/dist/api/sql-export-routes.d.ts +8 -0
  22. package/dist/api/types.d.ts +52 -1
  23. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-mCeSC03x.js} +310 -12
  24. package/dist/ast-schema-editor-mCeSC03x.js.map +1 -0
  25. package/dist/auth/access.d.ts +156 -0
  26. package/dist/auth/adapter-middleware.d.ts +2 -1
  27. package/dist/auth/address-ownership.d.ts +16 -1
  28. package/dist/auth/admin-roles-route.d.ts +4 -2
  29. package/dist/auth/admin-roles.d.ts +17 -20
  30. package/dist/auth/admin-user-ops.d.ts +24 -2
  31. package/dist/auth/admin-users-route.d.ts +2 -0
  32. package/dist/auth/api-keys/api-key-middleware.d.ts +64 -56
  33. package/dist/auth/api-keys/api-key-routes.d.ts +44 -11
  34. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  35. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  36. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  37. package/dist/auth/api-keys/index.d.ts +11 -11
  38. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  39. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  40. package/dist/auth/auth-hooks.d.ts +46 -7
  41. package/dist/auth/builtin-auth-adapter.d.ts +21 -0
  42. package/dist/auth/cookie-utils.d.ts +7 -0
  43. package/dist/auth/deliverable-address.d.ts +6 -0
  44. package/dist/auth/email-change-routes.d.ts +44 -0
  45. package/dist/auth/expired-token-sweep.d.ts +71 -0
  46. package/dist/auth/impersonation.d.ts +110 -0
  47. package/dist/auth/index.d.ts +4 -2
  48. package/dist/auth/interfaces.d.ts +120 -59
  49. package/dist/auth/jwks-routes.d.ts +5 -1
  50. package/dist/auth/jwt-crypto.d.ts +7 -0
  51. package/dist/auth/jwt-keys.d.ts +4 -0
  52. package/dist/auth/jwt.d.ts +319 -157
  53. package/dist/auth/magic-link-routes.d.ts +5 -6
  54. package/dist/auth/mfa-crypto.d.ts +9 -3
  55. package/dist/auth/mfa-gate.d.ts +4 -2
  56. package/dist/auth/mfa-routes.d.ts +8 -9
  57. package/dist/auth/middleware.d.ts +26 -7
  58. package/dist/auth/otp-routes.d.ts +5 -6
  59. package/dist/auth/passwordless-signup.d.ts +27 -0
  60. package/dist/auth/platform-token.d.ts +122 -0
  61. package/dist/auth/rate-limiter.d.ts +99 -31
  62. package/dist/auth/registration-policy.d.ts +17 -0
  63. package/dist/auth/require-auth.d.ts +1 -1
  64. package/dist/auth/routes.d.ts +61 -0
  65. package/dist/auth/scope-routes.d.ts +22 -0
  66. package/dist/auth/session-routes.d.ts +17 -6
  67. package/dist/auth/token-revocation.d.ts +56 -1
  68. package/dist/auth/user-picking.d.ts +37 -0
  69. package/dist/auth/verify-credential.d.ts +49 -0
  70. package/dist/{auth-B-GIMpDG.js → auth-CSBky1mt.js} +4564 -1929
  71. package/dist/auth-CSBky1mt.js.map +1 -0
  72. package/dist/auth_adapter-CIItCTc8.js +21 -0
  73. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  74. package/dist/backend/create-backend.d.ts +281 -0
  75. package/dist/backend/default-backend.d.ts +35 -0
  76. package/dist/backend/no-collections.d.ts +19 -0
  77. package/dist/backend/options.d.ts +912 -0
  78. package/dist/backend/scope.d.ts +23 -0
  79. package/dist/backend/services.d.ts +54 -0
  80. package/dist/backend-DTAOsLQc.js.map +1 -1
  81. package/dist/backup/backup-common.d.ts +24 -3
  82. package/dist/backup/backup-routes.d.ts +46 -5
  83. package/dist/backup/backup-schedule.d.ts +33 -0
  84. package/dist/backup/backup-storage.d.ts +16 -0
  85. package/dist/backup/index.d.ts +5 -1
  86. package/dist/backup-DBGRUVTS.js +585 -0
  87. package/dist/backup-DBGRUVTS.js.map +1 -0
  88. package/dist/boot/boot.d.ts +2 -2
  89. package/dist/boot/bundle.d.ts +19 -0
  90. package/dist/boot/driver.d.ts +3 -0
  91. package/dist/boot/env.d.ts +58 -6
  92. package/dist/boot/http-server.d.ts +22 -0
  93. package/dist/boot/options.d.ts +16 -1
  94. package/dist/boot/provision.d.ts +4 -2
  95. package/dist/boot/resource-adapters.d.ts +11 -14
  96. package/dist/boot/runtime-cors.d.ts +17 -0
  97. package/dist/boot/security-headers.d.ts +26 -0
  98. package/dist/boot/sources.d.ts +9 -0
  99. package/dist/boot/static-routing.d.ts +65 -0
  100. package/dist/collection_patch-BRu-BvDv.js +472 -0
  101. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  102. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-DiFwiLg2.js} +5 -8
  103. package/dist/contract-routes-DiFwiLg2.js.map +1 -0
  104. package/dist/cron/cron-loader.d.ts +77 -18
  105. package/dist/cron/cron-routes.d.ts +1 -1
  106. package/dist/cron/cron-scheduler.d.ts +94 -23
  107. package/dist/cron/cron-store.d.ts +37 -5
  108. package/dist/cron/define-cron.d.ts +6 -2
  109. package/dist/cron/index.d.ts +3 -3
  110. package/dist/cron-loader-NHqRfKF0.js +202 -0
  111. package/dist/cron-loader-NHqRfKF0.js.map +1 -0
  112. package/dist/{cron-routes-eE8nif_b.js → cron-routes-DsH8Jwn5.js} +34 -8
  113. package/dist/cron-routes-DsH8Jwn5.js.map +1 -0
  114. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-D1DT0nw5.js} +349 -65
  115. package/dist/cron-scheduler-D1DT0nw5.js.map +1 -0
  116. package/dist/cron-store-DEpVstUU.js +577 -0
  117. package/dist/cron-store-DEpVstUU.js.map +1 -0
  118. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BNqi2NZe.js} +2 -2
  119. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BNqi2NZe.js.map} +1 -1
  120. package/dist/email/index.d.ts +2 -2
  121. package/dist/email/smtp-email-service.d.ts +23 -0
  122. package/dist/email/templates.d.ts +22 -0
  123. package/dist/email/types.d.ts +44 -0
  124. package/dist/env.d.ts +1 -39
  125. package/dist/{errors-DWsX4yTd.js → errors-Cqp86KnA.js} +263 -10
  126. package/dist/errors-Cqp86KnA.js.map +1 -0
  127. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  128. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  129. package/dist/{function-loader-xnbDAPfa.js → function-loader-DbCSoNJj.js} +69 -39
  130. package/dist/function-loader-DbCSoNJj.js.map +1 -0
  131. package/dist/{function-routes-Chet4-lB.js → function-routes-BSTClQNa.js} +28 -15
  132. package/dist/function-routes-BSTClQNa.js.map +1 -0
  133. package/dist/functions/context.d.ts +26 -6
  134. package/dist/functions/define-function.d.ts +10 -6
  135. package/dist/functions/function-loader.d.ts +75 -18
  136. package/dist/functions/function-routes.d.ts +2 -1
  137. package/dist/functions/guards.d.ts +22 -5
  138. package/dist/functions/index.d.ts +6 -5
  139. package/dist/functions/index.js +148 -50
  140. package/dist/functions/index.js.map +1 -1
  141. package/dist/functions/internal.d.ts +2 -2
  142. package/dist/functions/proxy.d.ts +6 -2
  143. package/dist/functions/request-timeout.d.ts +7 -5
  144. package/dist/history/history-routes.d.ts +9 -0
  145. package/dist/history/retention-sweep.d.ts +81 -0
  146. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-RZ1Ow4YY.js} +4 -4
  147. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-RZ1Ow4YY.js.map} +1 -1
  148. package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
  149. package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
  150. package/dist/index.d.ts +32 -13
  151. package/dist/index.es.js +25329 -18393
  152. package/dist/index.es.js.map +1 -1
  153. package/dist/init/docs.d.ts +35 -10
  154. package/dist/init/health.d.ts +17 -2
  155. package/dist/init/process-safety.d.ts +7 -2
  156. package/dist/init/shutdown.d.ts +26 -1
  157. package/dist/init.d.ts +49 -811
  158. package/dist/internal-tables-L2t39RdT.js +136 -0
  159. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  160. package/dist/jobs/job-queue.d.ts +8 -0
  161. package/dist/jobs/job-store.d.ts +24 -0
  162. package/dist/jobs/types.d.ts +25 -4
  163. package/dist/{jobs-CazMYhyy.js → jobs-8G-_agld.js} +235 -25
  164. package/dist/jobs-8G-_agld.js.map +1 -0
  165. package/dist/{jwt-DnQHNFCl.js → jwt-CBaFc34Y.js} +546 -447
  166. package/dist/jwt-CBaFc34Y.js.map +1 -0
  167. package/dist/{keys-CogCQpxG.js → keys-COzQ2aE8.js} +3 -17
  168. package/dist/{keys-CogCQpxG.js.map → keys-COzQ2aE8.js.map} +1 -1
  169. package/dist/load-failure-L5hvfDoG.js +80 -0
  170. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  171. package/dist/{logger-DO2PZc4i.js → logger-Bt32sHFw.js} +41 -5
  172. package/dist/logger-Bt32sHFw.js.map +1 -0
  173. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DQJhrf-b.js} +48 -8
  174. package/dist/logs-routes-DQJhrf-b.js.map +1 -0
  175. package/dist/mcp/consent-page.d.ts +1 -1
  176. package/dist/mcp/mcp-routes.d.ts +13 -0
  177. package/dist/mcp/mcp-tools.d.ts +23 -9
  178. package/dist/mcp/oauth-metadata.d.ts +21 -16
  179. package/dist/mcp/oauth-routes.d.ts +14 -1
  180. package/dist/metrics/index.d.ts +18 -0
  181. package/dist/metrics/pool-wait.d.ts +50 -0
  182. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  183. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  184. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  185. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  186. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-CeGW2dLC.js} +455 -80
  187. package/dist/openapi-generator-CeGW2dLC.js.map +1 -0
  188. package/dist/policy-PdzX-Opd.js +121 -0
  189. package/dist/policy-PdzX-Opd.js.map +1 -0
  190. package/dist/project_manifest-R6y_KHms.js +59 -0
  191. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  192. package/dist/{proxy-Czngl3p9.js → proxy-BEKbsil6.js} +5 -4
  193. package/dist/proxy-BEKbsil6.js.map +1 -0
  194. package/dist/{query-parser-DGRVFNM3.js → query-parser-ECxP0nDw.js} +497 -52
  195. package/dist/query-parser-ECxP0nDw.js.map +1 -0
  196. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-BiRelY-U.js} +17 -10
  197. package/dist/request-timeout-BiRelY-U.js.map +1 -0
  198. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  199. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  200. package/dist/schema-edit/apply-schema-change.d.ts +73 -3
  201. package/dist/schema-edit/local-source.d.ts +29 -0
  202. package/dist/schema-edit/project-root.d.ts +3 -2
  203. package/dist/schema-edit/remote-source.d.ts +20 -5
  204. package/dist/schema-edit/source-editor.d.ts +28 -0
  205. package/dist/schema-editor-routes-DRXBAbZy.js +136 -0
  206. package/dist/schema-editor-routes-DRXBAbZy.js.map +1 -0
  207. package/dist/serve-spa.d.ts +58 -0
  208. package/dist/services/outbound-post.d.ts +50 -0
  209. package/dist/services/outbound-url-guard.d.ts +34 -5
  210. package/dist/services/routed-realtime-service.d.ts +16 -0
  211. package/dist/services/webhook-service.d.ts +66 -14
  212. package/dist/singleton.d.ts +9 -5
  213. package/dist/soft-delete-params-CBryZXMq.js +668 -0
  214. package/dist/soft-delete-params-CBryZXMq.js.map +1 -0
  215. package/dist/sql-export-routes-D8ZaRvoR.js +240 -0
  216. package/dist/sql-export-routes-D8ZaRvoR.js.map +1 -0
  217. package/dist/{src-vkcwKXbT.js → src-D5Y3N-S4.js} +440 -20
  218. package/dist/src-D5Y3N-S4.js.map +1 -0
  219. package/dist/storage/GCSStorageController.d.ts +48 -2
  220. package/dist/storage/LocalStorageController.d.ts +69 -2
  221. package/dist/storage/S3StorageController.d.ts +39 -3
  222. package/dist/storage/image-transform.d.ts +15 -0
  223. package/dist/storage/index.d.ts +2 -2
  224. package/dist/storage/maintenance-routes.d.ts +23 -0
  225. package/dist/storage/maintenance.d.ts +126 -0
  226. package/dist/storage/property-limits.d.ts +41 -6
  227. package/dist/storage/rendition-cache.d.ts +9 -11
  228. package/dist/storage/request-keys.d.ts +15 -0
  229. package/dist/storage/request-principals.d.ts +30 -0
  230. package/dist/storage/requested-object.d.ts +74 -0
  231. package/dist/storage/routes.d.ts +52 -14
  232. package/dist/storage/source-variables.d.ts +10 -0
  233. package/dist/storage/streams.d.ts +42 -0
  234. package/dist/storage/tus-handler.d.ts +76 -7
  235. package/dist/storage/types.d.ts +101 -7
  236. package/dist/storage/upload-validation.d.ts +13 -0
  237. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  238. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  239. package/dist/strings.es-BeZWAodn.js +256 -0
  240. package/dist/strings.es-BeZWAodn.js.map +1 -0
  241. package/dist/upload-validation-CVobI5xO.js +172 -0
  242. package/dist/upload-validation-CVobI5xO.js.map +1 -0
  243. package/dist/utils/byte-size.d.ts +10 -0
  244. package/dist/utils/compression.d.ts +6 -0
  245. package/dist/utils/load-failure.d.ts +14 -0
  246. package/dist/utils/logger.d.ts +21 -0
  247. package/package.json +5 -5
  248. package/dist/GCSStorageController-CjrA4PMo.js +0 -242
  249. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  250. package/dist/S3StorageController-B6pKDNVj.js +0 -264
  251. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  252. package/dist/admin-roles-vYdp_Pil.js +0 -36
  253. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  254. package/dist/admin_block-DxKLmdiv.js +0 -206
  255. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  256. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  257. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  258. package/dist/auth-B-GIMpDG.js.map +0 -1
  259. package/dist/backup-D7YR94N3.js +0 -253
  260. package/dist/backup-D7YR94N3.js.map +0 -1
  261. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  262. package/dist/cron-loader-DfTj2Hbi.js +0 -118
  263. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  264. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  265. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  266. package/dist/cron-store-TcoGz-xS.js +0 -305
  267. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  268. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  269. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  270. package/dist/errors-DWsX4yTd.js.map +0 -1
  271. package/dist/function-loader-xnbDAPfa.js.map +0 -1
  272. package/dist/function-routes-Chet4-lB.js.map +0 -1
  273. package/dist/jobs-CazMYhyy.js.map +0 -1
  274. package/dist/jwt-DnQHNFCl.js.map +0 -1
  275. package/dist/logger-DO2PZc4i.js.map +0 -1
  276. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  277. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  278. package/dist/proxy-Czngl3p9.js.map +0 -1
  279. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  280. package/dist/request-timeout-C_4C2BeR.js.map +0 -1
  281. package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
  282. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  283. package/dist/src-pmvW7BFx.js +0 -6688
  284. package/dist/src-pmvW7BFx.js.map +0 -1
  285. package/dist/src-vkcwKXbT.js.map +0 -1
  286. package/dist/types-BfKcm9do.js +0 -39
  287. package/dist/types-BfKcm9do.js.map +0 -1
@@ -1,5 +1,19 @@
1
1
  import { type CronJobDefinition, type CronResourceOptions } from "@rebasepro/types";
2
2
  import { type ModuleImporter } from "../utils/dynamic-import.js";
3
+ import type { LoadDirectoryOptions } from "../functions/function-loader.js";
4
+ /**
5
+ * Cron jobs as values: the job id, to its definition.
6
+ *
7
+ * `crons: { "nightly-report": nightlyReport }` schedules `nightlyReport` as the
8
+ * job `nightly-report` — the same thing a file named `crons/nightly-report.ts`
9
+ * does. {@link loadCrons} returns exactly this shape, so
10
+ * `crons: await loadCrons(dir)` and `cronsDir: dir` schedule the same jobs.
11
+ *
12
+ * A value is what the author wrote — `defineCron({...})` — with no defaults
13
+ * filled in. The scheduler fills them in when it registers the job.
14
+ */
15
+ export type CronsRecord = Record<string, CronJobDefinition>;
16
+ /** A job as the scheduler registers it: its id, and its definition with defaults filled in. */
3
17
  export interface LoadedCronJob {
4
18
  /** Job ID derived from filename (e.g. "cleanup-sessions"). */
5
19
  id: string;
@@ -13,34 +27,79 @@ export interface LoadedCronJobs {
13
27
  problems: string[];
14
28
  }
15
29
  /**
16
- * Auto-discover cron job files from a directory.
30
+ * Load the cron jobs in a directory, as values.
31
+ *
32
+ * Each file default-exports a `CronJobDefinition` — `defineCron({...})`. The
33
+ * filename without its extension is the record key, which is the job id:
34
+ * `crons/cleanup-sessions.ts` → `{ "cleanup-sessions": definition }`.
35
+ *
36
+ * Loading declares nothing: the definitions come back exactly as written, and
37
+ * whoever uses them decides what they are for — a backend schedules them, the
38
+ * build's derive step records them in the resource graph ({@link declareCrons}).
39
+ *
40
+ * Never throws for a bad file: it is skipped, logged, and passed to
41
+ * `options.onSkip`.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const crons = await loadCrons(path.resolve("crons"));
46
+ * const backend = await createRebaseBackend({ database, crons, provision: true });
47
+ * ```
48
+ */
49
+ export declare function loadCrons(directory: string, options?: LoadDirectoryOptions): Promise<CronsRecord>;
50
+ /**
51
+ * Record each cron in the project's resource graph — what `rebase resources`
52
+ * lists and a host reads to know a schedule exists, without running it.
17
53
  *
18
- * Each file should default-export a `CronJobDefinition`.
19
- * The filename (without extension) becomes the job ID:
20
- * `crons/cleanup-sessions.ts` → id = "cleanup-sessions"
54
+ * Keyed by the same id the scheduler, the routes and the Studio use, and built
55
+ * from the definitions as written: a default the scheduler fills in
56
+ * (`enabled: true`, `timeoutSeconds: 300`) is this process's, not something the
57
+ * author declared.
58
+ */
59
+ export declare function declareCrons(crons: CronsRecord): void;
60
+ /**
61
+ * The jobs a scheduler registers, from cron values: the definitions with this
62
+ * process's defaults filled in.
63
+ *
64
+ * Spread first, then normalise. Rebuilding the object field by field dropped
65
+ * `catchUpWindowSeconds` once — and since this is the path every job takes to
66
+ * the scheduler, catch-up never ran for any job authored the documented way. A
67
+ * field added to `CronJobDefinition` tomorrow would be dropped the same way.
21
68
  *
22
- * Follows the same discovery pattern as `loadFunctionsFromDirectory` — see
23
- * {@link loadCronJobsWithDiagnostics} for what happens to the files it cannot
24
- * load.
69
+ * @throws when a value has no `schedule` or no `handler`. A file like that is
70
+ * skipped by the loader with a reason; a value handed over in code is refused,
71
+ * because the caller is right there to fix it.
72
+ */
73
+ export declare function cronJobsFrom(crons: CronsRecord): LoadedCronJob[];
74
+ /**
75
+ * @deprecated Use `loadCrons` instead, which returns the jobs as a record keyed
76
+ * by id. Will be removed in a future release.
25
77
  */
26
78
  export declare function loadCronJobsFromDirectory(directory: string, importModule?: ModuleImporter): Promise<LoadedCronJob[]>;
27
79
  /**
28
- * {@link loadCronJobsFromDirectory}, plus the list of files that were skipped.
80
+ * @deprecated Use `loadCrons` instead, with `onSkip` to collect the files it
81
+ * skipped — and `declareCrons` where the jobs belong in the resource graph,
82
+ * which this used to do as a side effect of loading. Will be removed in a
83
+ * future release.
84
+ */
85
+ export declare function loadCronJobsWithDiagnostics(directory: string, importModule?: ModuleImporter): Promise<LoadedCronJobs>;
86
+ /**
87
+ * Read a directory of cron files: the definitions that loaded, keyed by id, and
88
+ * one `<file> (<reason>)` per file that will NOT be scheduled.
29
89
  *
30
- * A job that does not load does not run, and until now the only trace was one
90
+ * A job that does not load does not run, and the only trace used to be one
31
91
  * `warn` per file in the boot log — next to nothing at 3am when the report that
32
- * should have been mailed was not. A cron file is written once and then trusted
33
- * for months, so "it silently never ran" is the failure mode this surface has,
34
- * and a log line is not a surface.
92
+ * should have been mailed was not. So the skips are counted, summarised in one
93
+ * place, and answered by `GET /api/admin/cron`.
35
94
  *
36
- * Never throws, for the same reason `loadFunctionsWithDiagnostics` does not: one
95
+ * Never throws, for the same reason the function loader does not: one
37
96
  * malformed file must not take the server down, and the other jobs in the
38
- * directory are still worth scheduling. The difference is that the skips are now
39
- * counted, summarised in one place, and answered by `GET /api/cron` — so the
40
- * question "why did nothing run?" has an answer that does not require log
41
- * access.
97
+ * directory are still worth scheduling.
42
98
  */
43
- export declare function loadCronJobsWithDiagnostics(directory: string, importModule?: ModuleImporter): Promise<LoadedCronJobs>;
99
+ export declare function readCronsDirectory(directory: string, importModule?: ModuleImporter): Promise<{
100
+ crons: CronsRecord;
101
+ problems: string[];
102
+ }>;
44
103
  /**
45
104
  * The declaration a definition makes: every field a host can read without the
46
105
  * handler. Spelled once here so the graph and the scheduler cannot disagree
@@ -7,7 +7,7 @@ import { type CronScheduler } from "./cron-scheduler.js";
7
7
  * Routes:
8
8
  * GET / → list all cron jobs
9
9
  * GET /:id → get a single job's status
10
- * POST /:id/trigger → manually trigger a job
10
+ * POST /:id/trigger → manually trigger a job (`?async=1`: 202 once it has started)
11
11
  * GET /:id/logs → get execution logs for a job
12
12
  * PUT /:id → update job (enable/disable, or `null` to follow the code)
13
13
  *
@@ -1,4 +1,4 @@
1
- import type { CronJobStatus, CronJobLogEntry } from "@rebasepro/types";
1
+ import type { CronJobDefinition, CronJobStatus, CronJobLogEntry, RejectedCronJob } from "@rebasepro/types";
2
2
  import type { RebaseServerClient } from "@rebasepro/types";
3
3
  import type { LoadedCronJob } from "./cron-loader.js";
4
4
  import type { CronStore } from "./cron-store.js";
@@ -30,9 +30,9 @@ export declare function parseCronExpression(expression: string, after: Date, tim
30
30
  * The latest slot matching `expression` within the inclusive window
31
31
  * `[from, to]`, or `undefined` when the expression has no slot in it.
32
32
  *
33
- * Walks backwards a minute at a time from `to`, so the first hit is already
34
- * the answer — in the common case (a job that ran normally moments ago) that
35
- * is a handful of iterations, not a scan of the whole window.
33
+ * Walks backwards from `to`, so the first hit is already the answer — in the
34
+ * common case (a job that ran normally moments ago) that is a handful of
35
+ * steps, not a scan of the whole window.
36
36
  *
37
37
  * `to`'s own minute is included: an instance booting at 06:00:30 has *not* run
38
38
  * the 06:00 slot — `parseCronExpression` already skipped past it to tomorrow —
@@ -43,6 +43,29 @@ export declare function parseCronExpression(expression: string, after: Date, tim
43
43
  * ISO string down either path. The claim key depends on that.
44
44
  */
45
45
  export declare function findMostRecentSlot(expression: string, from: Date, to: Date, timezone?: string): Date | undefined;
46
+ /**
47
+ * What one run keeps of its `ctx.log` lines: up to 500 lines or 64 KB,
48
+ * whichever comes first. The lines past that are counted, and one line at the
49
+ * end says how many there were; every line still reaches the server log.
50
+ *
51
+ * Uncapped, a job logging one line per item kept them all — in the 50-run ring
52
+ * per job, in a `cron_logs` row, and in every history read: a per-minute sync
53
+ * of 1,000 items held 4 MB per job in memory and wrote 115 MB a day.
54
+ */
55
+ export declare const MAX_CAPTURED_LOG_LINES = 500;
56
+ export declare const MAX_CAPTURED_LOG_BYTES: number;
57
+ /**
58
+ * What {@link CronScheduler.startJob} answers: the run began at `startedAt`
59
+ * (its history entry will carry the same timestamp), or it did not start and
60
+ * `log` is the skip that says why.
61
+ */
62
+ export type StartedCronRun = {
63
+ started: true;
64
+ startedAt: string;
65
+ } | {
66
+ started: false;
67
+ log: CronJobLogEntry;
68
+ };
46
69
  /**
47
70
  * Whether a log entry records a run that did not happen because one was
48
71
  * already executing — on this process, or on another that holds the job's run
@@ -50,19 +73,6 @@ export declare function findMostRecentSlot(expression: string, from: Date, to: D
50
73
  * panel reads.
51
74
  */
52
75
  export declare function isAlreadyExecutingSkip(entry: CronJobLogEntry): boolean;
53
- /**
54
- * A job the scheduler refused, and why.
55
- *
56
- * It is not a `CronJobStatus`: it has no state, no next run and no counters,
57
- * because it was never registered. Reporting it as a job with `state: "error"`
58
- * would be a lie in the other direction — nothing is going to run it.
59
- */
60
- export interface RejectedCronJob {
61
- id: string;
62
- name: string;
63
- schedule: string;
64
- reason: string;
65
- }
66
76
  export declare class CronScheduler {
67
77
  private jobs;
68
78
  private rejected;
@@ -82,6 +92,10 @@ export declare class CronScheduler {
82
92
  * ever clear the lease that run took.
83
93
  */
84
94
  private readonly instanceLabel;
95
+ /** The hourly claim- and log-retention sweep, while the scheduler is started with a store. */
96
+ private claimSweepTimer?;
97
+ private sweepingClaims;
98
+ private sweepingLogs;
85
99
  /**
86
100
  * Set the server singleton to make it available to cron job handlers.
87
101
  *
@@ -111,6 +125,14 @@ export declare class CronScheduler {
111
125
  * Start the scheduler — begins ticking all enabled jobs.
112
126
  */
113
127
  start(): void;
128
+ /** Delete slot claims past retention. Failures are logged; the next hour tries again. */
129
+ private sweepClaims;
130
+ /**
131
+ * Delete run history past retention, and count the runs not counted yet.
132
+ * Failures are logged; the next hour tries again, from where this one
133
+ * stopped — each batch it finished is committed.
134
+ */
135
+ private sweepLogs;
114
136
  /**
115
137
  * Stop the scheduler: clear every timer, then wait for the runs executing
116
138
  * right now — for at most `timeoutMs` when given.
@@ -128,6 +150,8 @@ export declare class CronScheduler {
128
150
  * List all registered jobs with their current status.
129
151
  */
130
152
  listJobs(): CronJobStatus[];
153
+ /** Keep a refused job, so the admin surface can say why it never runs. */
154
+ private refuse;
131
155
  /**
132
156
  * Jobs that loaded but whose schedule the scheduler refused.
133
157
  *
@@ -136,6 +160,17 @@ export declare class CronScheduler {
136
160
  * cron will never fire" look identical from the admin panel.
137
161
  */
138
162
  listRejectedJobs(): RejectedCronJob[];
163
+ /**
164
+ * {@link listRejectedJobs}, those whose definition matches — how a surface
165
+ * finds a refused job by what it is, as {@link jobIdsWhere} does for the
166
+ * registered ones.
167
+ */
168
+ rejectedJobsWhere(predicate: (definition: CronJobDefinition) => boolean): RejectedCronJob[];
169
+ /**
170
+ * The ids of the registered jobs whose definition matches — how a surface
171
+ * finds a job by what it is rather than by the file it happens to live in.
172
+ */
173
+ jobIdsWhere(predicate: (definition: CronJobDefinition) => boolean): string[];
139
174
  /**
140
175
  * Get a single job status by ID.
141
176
  */
@@ -144,11 +179,11 @@ export declare class CronScheduler {
144
179
  * {@link listJobs}, as the fleet sees it rather than this process alone.
145
180
  *
146
181
  * Reads the store: every job's enabled state — a pause another replica
147
- * made — and whether another process holds its run lease. On a process
148
- * whose scheduler is not started (the `api` role) it also reads each job's
149
- * run count and last run from `cron_logs`, since that process runs nothing
150
- * and its own counters would say 0 runs for a job the worker has run a
151
- * thousand times.
182
+ * made — whether another process holds its run lease, and each job's run
183
+ * count and last run from `cron_logs`. The counters in this process's
184
+ * memory count only the runs it made: the `api` role runs nothing and
185
+ * would say 0 runs for a job the worker has run a thousand times, and one
186
+ * of two scheduling replicas never sees the slots the other one claimed.
152
187
  *
153
188
  * A store that cannot answer leaves this process's own view, with a
154
189
  * warning: a listing is not worth failing.
@@ -162,7 +197,12 @@ export declare class CronScheduler {
162
197
  getJobLogs(id: string, limit?: number): CronJobLogEntry[];
163
198
  /**
164
199
  * Get log entries for a job from the database (if store is available).
165
- * Falls back to in-memory logs if no store is configured.
200
+ * Falls back to in-memory logs if no store is configured, or if the store
201
+ * has none — a run whose row is still being written.
202
+ *
203
+ * Throws when the store cannot read the history. This process's memory is
204
+ * not a stand-in for it: it is empty on the `api` role and after a
205
+ * restart, and holds one replica's runs at most.
166
206
  */
167
207
  getJobLogsFromDb(id: string, limit?: number): Promise<CronJobLogEntry[]>;
168
208
  /**
@@ -199,6 +239,22 @@ export declare class CronScheduler {
199
239
  * recognises it.
200
240
  */
201
241
  triggerJob(id: string): Promise<CronJobLogEntry | undefined>;
242
+ /**
243
+ * Start a job now and answer as soon as the run has begun, not when it
244
+ * ends — the manual trigger's `?async=1`.
245
+ *
246
+ * The synchronous {@link triggerJob} holds its HTTP request open for the
247
+ * whole run, and a proxy in front of the server (many stop at 60 s) cuts
248
+ * a long job's answer while the run carries on. This resolves once the
249
+ * run lease is held and the handler is about to be called, so a run that
250
+ * cannot start — already executing here or on the lease holder — is still
251
+ * answered as the skip it is. The run itself is tracked like any other:
252
+ * `stop()` waits for it, and its outcome lands in the run history, where
253
+ * the entry carries `manual: true` and the `startedAt` returned here.
254
+ *
255
+ * Returns `undefined` if the job doesn't exist.
256
+ */
257
+ startJob(id: string): Promise<StartedCronRun | undefined>;
202
258
  /**
203
259
  * Record a run that did not happen because the previous one had not
204
260
  * finished.
@@ -262,6 +318,21 @@ export declare class CronScheduler {
262
318
  * rest as a view for {@link toStatus}. Never throws.
263
319
  */
264
320
  private readFleet;
321
+ /**
322
+ * The next slot of `job` after `now`, for a listing on a process whose
323
+ * scheduler is not started — the `api` role, which Studio's Cron panel
324
+ * polls every 15 seconds.
325
+ *
326
+ * Cached on the job. The key is the job's schedule and zone (a re-register
327
+ * replaces the job, and with it the cache); the entry holds while
328
+ * `computedAt <= now < slot`. That is the same answer, not an
329
+ * approximation: the search found no matching minute between
330
+ * `computedAt`'s next minute and `slot`, so from any later instant before
331
+ * `slot` it lands on `slot` again. A clock that moves back, or reaching the
332
+ * slot, recomputes. The cache holds a pure function of the schedule, so
333
+ * there is nothing user- or role-scoped in it.
334
+ */
335
+ private listedNextRun;
265
336
  /**
266
337
  * Warn once at start when the process looks like it is running on a
267
338
  * platform that freezes or evicts instances between requests, where the
@@ -11,12 +11,21 @@ import type { DataDriver } from "@rebasepro/types";
11
11
  * occurs, and a pause or a trigger's "already executing" is per process.
12
12
  */
13
13
  export interface CronStore {
14
- /** Ensure the backing table exists. Called once on startup. */
14
+ /**
15
+ * Ensure the backing tables exist and are locked away from end users. Runs
16
+ * once per process per database, however many stores ask: concurrent
17
+ * callers share the attempt in flight, and an attempt that fell short is
18
+ * made again on the next call.
19
+ */
15
20
  ensureTable(): Promise<void>;
16
21
  /** Persist a single log entry after execution. */
17
22
  insertLog(entry: CronJobLogEntry): Promise<void>;
18
23
  /**
19
24
  * Fetch the most recent logs for a job.
25
+ *
26
+ * Throws when the history cannot be read. An empty array is a claim — the
27
+ * job has no recorded runs — and a caller shows it as "never ran".
28
+ *
20
29
  * @param jobId The job identifier
21
30
  * @param limit Max entries to return (default 50)
22
31
  * @returns Logs sorted newest-first
@@ -24,7 +33,8 @@ export interface CronStore {
24
33
  fetchLogs(jobId: string, limit?: number): Promise<CronJobLogEntry[]>;
25
34
  /**
26
35
  * Fetch aggregate stats for all jobs (totalRuns, totalFailures, lastRunAt).
27
- * Used to seed in-memory counters on startup.
36
+ * Used to seed in-memory counters on startup. The SQL store reads them from
37
+ * the all-time counters on `cron_job_state`, not from the history.
28
38
  */
29
39
  fetchJobStats(): Promise<Map<string, {
30
40
  totalRuns: number;
@@ -82,11 +92,33 @@ export interface CronStore {
82
92
  /** Release a lease taken with {@link tryAcquireRunLease} — only if `holder` still holds it. */
83
93
  releaseRunLease?(jobId: string, holder: string): Promise<void>;
84
94
  /**
85
- * Run count, failure count and last run of each job, read from
86
- * `cron_logs` — for a process whose scheduler is not started (the `api`
87
- * role), which runs nothing and so counts nothing of its own.
95
+ * Run count, failure count and last run of each job — every process's
96
+ * runs, where the scheduler's own counters hold only the ones it made. The
97
+ * SQL store keeps them as all-time counters on `cron_job_state`, so they
98
+ * outlive the history's retention.
88
99
  */
89
100
  fetchRunSummaries?(jobIds: readonly string[]): Promise<Map<string, CronJobRunSummary>>;
101
+ /**
102
+ * Delete slot claims past retention — all but each job's most recent one —
103
+ * in batches. Resolves with how many went. The scheduler runs it on a
104
+ * timer while it is started; a store without it keeps every claim.
105
+ */
106
+ sweepClaims?(): Promise<number>;
107
+ /**
108
+ * Delete run history past retention, and count into the job counters any
109
+ * run that is not counted yet — a history written before the counters
110
+ * existed, or by an older server during a rolling deploy. Both in batches.
111
+ * The scheduler runs it with {@link sweepClaims}; a store without it keeps
112
+ * every run.
113
+ */
114
+ sweepLogs?(): Promise<CronLogSweep>;
115
+ }
116
+ /** What one {@link CronStore.sweepLogs} did. */
117
+ export interface CronLogSweep {
118
+ /** History rows deleted past retention. */
119
+ deleted: number;
120
+ /** Runs counted into the job counters that were not counted before. */
121
+ counted: number;
90
122
  }
91
123
  /** One job's row in `rebase.cron_job_state`, as the scheduler reads it. */
92
124
  export interface CronJobPersistedState {
@@ -1,4 +1,4 @@
1
- import type { CronJobDefinition } from "@rebasepro/types";
1
+ import type { CronJobDefinition, RegisteredDatabase } from "@rebasepro/types";
2
2
  /**
3
3
  * Typed authoring helper for a cron job file. Identity at runtime —
4
4
  * a plain default-exported {@link CronJobDefinition} works identically;
@@ -38,5 +38,9 @@ import type { CronJobDefinition } from "@rebasepro/types";
38
38
  * },
39
39
  * });
40
40
  * ```
41
+ *
42
+ * `ctx.rebase` is typed from the `Database` the project registered on
43
+ * `Register` (see the typed server data guide), and untyped when it registered
44
+ * none. `defineCron<Database>({…})` types one job without the registration.
41
45
  */
42
- export declare function defineCron(definition: CronJobDefinition): CronJobDefinition;
46
+ export declare function defineCron<DB = RegisteredDatabase>(definition: CronJobDefinition<DB>): CronJobDefinition<DB>;
@@ -1,8 +1,8 @@
1
- export { loadCronJobsFromDirectory, loadCronJobsWithDiagnostics } from "./cron-loader.js";
2
- export type { LoadedCronJob, LoadedCronJobs } from "./cron-loader.js";
1
+ export { loadCrons, declareCrons, cronJobsFrom, loadCronJobsFromDirectory, loadCronJobsWithDiagnostics } from "./cron-loader.js";
2
+ export type { CronsRecord, LoadedCronJob, LoadedCronJobs } from "./cron-loader.js";
3
3
  export { defineCron } from "./define-cron.js";
4
4
  export { CronScheduler, validateCronExpression } from "./cron-scheduler.js";
5
5
  export { createCronRoutes } from "./cron-routes.js";
6
6
  export { createCronStore } from "./cron-store.js";
7
7
  export { detectFreezableRuntime, buildScaleToZeroWarning, CRON_ALWAYS_ON_ENV } from "./scale-to-zero.js";
8
- export type { CronStore, CronJobPersistedState, CronRunLease, CronJobRunSummary } from "./cron-store.js";
8
+ export type { CronStore, CronJobPersistedState, CronRunLease, CronJobRunSummary, CronLogSweep } from "./cron-store.js";
@@ -0,0 +1,202 @@
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
5
+ import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
+ import { x as declareCron } from "./src-D5Y3N-S4.js";
7
+ import { r as logger } from "./logger-Bt32sHFw.js";
8
+ import { i as warnDeprecated, n as describeLoadFailure, r as nativeDynamicImport, t as SYNTAX_ERROR_ADVICE } from "./load-failure-L5hvfDoG.js";
9
+ import * as fs$1 from "fs";
10
+ import * as path$1 from "path";
11
+ import { pathToFileURL } from "url";
12
+ //#region src/cron/cron-loader.ts
13
+ var cron_loader_exports = /* @__PURE__ */ __exportAll({
14
+ cronJobsFrom: () => cronJobsFrom,
15
+ cronResourceOptions: () => cronResourceOptions,
16
+ declareCrons: () => declareCrons,
17
+ loadCronJobsFromDirectory: () => loadCronJobsFromDirectory,
18
+ loadCronJobsWithDiagnostics: () => loadCronJobsWithDiagnostics,
19
+ loadCrons: () => loadCrons,
20
+ readCronsDirectory: () => readCronsDirectory
21
+ });
22
+ /**
23
+ * Load the cron jobs in a directory, as values.
24
+ *
25
+ * Each file default-exports a `CronJobDefinition` — `defineCron({...})`. The
26
+ * filename without its extension is the record key, which is the job id:
27
+ * `crons/cleanup-sessions.ts` → `{ "cleanup-sessions": definition }`.
28
+ *
29
+ * Loading declares nothing: the definitions come back exactly as written, and
30
+ * whoever uses them decides what they are for — a backend schedules them, the
31
+ * build's derive step records them in the resource graph ({@link declareCrons}).
32
+ *
33
+ * Never throws for a bad file: it is skipped, logged, and passed to
34
+ * `options.onSkip`.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const crons = await loadCrons(path.resolve("crons"));
39
+ * const backend = await createRebaseBackend({ database, crons, provision: true });
40
+ * ```
41
+ */
42
+ async function loadCrons(directory, options = {}) {
43
+ const { crons, problems } = await readCronsDirectory(directory, options.importModule);
44
+ if (options.onSkip) for (const problem of problems) options.onSkip(problem);
45
+ return crons;
46
+ }
47
+ /**
48
+ * Record each cron in the project's resource graph — what `rebase resources`
49
+ * lists and a host reads to know a schedule exists, without running it.
50
+ *
51
+ * Keyed by the same id the scheduler, the routes and the Studio use, and built
52
+ * from the definitions as written: a default the scheduler fills in
53
+ * (`enabled: true`, `timeoutSeconds: 300`) is this process's, not something the
54
+ * author declared.
55
+ */
56
+ function declareCrons(crons) {
57
+ for (const [id, definition] of Object.entries(crons)) declareCron(id, cronResourceOptions(definition));
58
+ }
59
+ /**
60
+ * The jobs a scheduler registers, from cron values: the definitions with this
61
+ * process's defaults filled in.
62
+ *
63
+ * Spread first, then normalise. Rebuilding the object field by field dropped
64
+ * `catchUpWindowSeconds` once — and since this is the path every job takes to
65
+ * the scheduler, catch-up never ran for any job authored the documented way. A
66
+ * field added to `CronJobDefinition` tomorrow would be dropped the same way.
67
+ *
68
+ * @throws when a value has no `schedule` or no `handler`. A file like that is
69
+ * skipped by the loader with a reason; a value handed over in code is refused,
70
+ * because the caller is right there to fix it.
71
+ */
72
+ function cronJobsFrom(crons) {
73
+ return Object.entries(crons).map(([id, definition]) => {
74
+ const def = definition;
75
+ if (!def || typeof def.schedule !== "string" || typeof def.handler !== "function") throw new Error(`crons["${id}"] is not a cron job: it needs a \`schedule\` string and a \`handler\` function. Author it with \`defineCron({ schedule, handler })\`.`);
76
+ return {
77
+ id,
78
+ definition: {
79
+ ...def,
80
+ schedule: def.schedule,
81
+ name: def.name || id,
82
+ enabled: def.enabled !== false,
83
+ timeoutSeconds: def.timeoutSeconds ?? 300,
84
+ handler: def.handler
85
+ }
86
+ };
87
+ });
88
+ }
89
+ /**
90
+ * @deprecated Use `loadCrons` instead, which returns the jobs as a record keyed
91
+ * by id. Will be removed in a future release.
92
+ */
93
+ async function loadCronJobsFromDirectory(directory, importModule = nativeDynamicImport) {
94
+ warnDeprecated("server.loadCronJobsFromDirectory", "`loadCronJobsFromDirectory` is deprecated; use `loadCrons(dir)`, which returns `{ [id]: definition }`.");
95
+ return (await legacyLoad(directory, importModule)).jobs;
96
+ }
97
+ /**
98
+ * @deprecated Use `loadCrons` instead, with `onSkip` to collect the files it
99
+ * skipped — and `declareCrons` where the jobs belong in the resource graph,
100
+ * which this used to do as a side effect of loading. Will be removed in a
101
+ * future release.
102
+ */
103
+ async function loadCronJobsWithDiagnostics(directory, importModule = nativeDynamicImport) {
104
+ warnDeprecated("server.loadCronJobsWithDiagnostics", "`loadCronJobsWithDiagnostics` is deprecated; use `loadCrons(dir, { onSkip })`, which returns `{ [id]: definition }`, and `declareCrons(crons)` to record them in the resource graph.");
105
+ return legacyLoad(directory, importModule);
106
+ }
107
+ /**
108
+ * What the deprecated loaders did: load, declare every job in the resource
109
+ * graph, and hand back normalised jobs. A job the graph refuses is skipped as a
110
+ * problem, as it was when the declaration ran inside the loader.
111
+ */
112
+ async function legacyLoad(directory, importModule) {
113
+ const { crons, problems } = await readCronsDirectory(directory, importModule);
114
+ const declared = {};
115
+ for (const [id, definition] of Object.entries(crons)) try {
116
+ declareCron(id, cronResourceOptions(definition));
117
+ declared[id] = definition;
118
+ } catch (err) {
119
+ problems.push(describeLoadFailure(id, err));
120
+ }
121
+ return {
122
+ jobs: cronJobsFrom(declared),
123
+ problems
124
+ };
125
+ }
126
+ /**
127
+ * Read a directory of cron files: the definitions that loaded, keyed by id, and
128
+ * one `<file> (<reason>)` per file that will NOT be scheduled.
129
+ *
130
+ * A job that does not load does not run, and the only trace used to be one
131
+ * `warn` per file in the boot log — next to nothing at 3am when the report that
132
+ * should have been mailed was not. So the skips are counted, summarised in one
133
+ * place, and answered by `GET /api/admin/cron`.
134
+ *
135
+ * Never throws, for the same reason the function loader does not: one
136
+ * malformed file must not take the server down, and the other jobs in the
137
+ * directory are still worth scheduling.
138
+ */
139
+ async function readCronsDirectory(directory, importModule = nativeDynamicImport) {
140
+ const crons = {};
141
+ const problems = [];
142
+ if (!fs$1.existsSync(directory)) return {
143
+ crons,
144
+ problems
145
+ };
146
+ const files = fs$1.readdirSync(directory);
147
+ for (const file of files) if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.startsWith(".") && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js") {
148
+ const filePath = path$1.join(directory, file);
149
+ try {
150
+ const fileUrl = pathToFileURL(filePath).href;
151
+ const exported = (await importModule(fileUrl)).default;
152
+ if (!exported || typeof exported !== "object") {
153
+ problems.push(`${file} (no default export)`);
154
+ logger.warn(`[cron] ${file}: no valid default export. Skipping.`);
155
+ continue;
156
+ }
157
+ const def = exported;
158
+ if (typeof def.schedule !== "string" || def.schedule.trim() === "" || typeof def.handler !== "function") {
159
+ problems.push(`${file} (default export has no 'schedule' or no 'handler')`);
160
+ logger.warn(`[cron] ${file}: default export missing required 'schedule' or 'handler'. Skipping.`);
161
+ continue;
162
+ }
163
+ const id = path$1.basename(file, path$1.extname(file));
164
+ const definition = exported;
165
+ crons[id] = definition;
166
+ logger.info(`⏰ Loaded cron job: ${id} (${definition.schedule}${definition.timezone ? ` ${definition.timezone}` : " in the process's local time"})`);
167
+ } catch (err) {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ problems.push(describeLoadFailure(file, err));
170
+ logger.debug(`[cron] Failed to load ${file}: ${message}`);
171
+ }
172
+ }
173
+ if (problems.length > 0) {
174
+ const syntax = problems.some((p) => p.includes("(syntax error"));
175
+ const other = problems.some((p) => !p.includes("(syntax error"));
176
+ logger.warn(`[cron] ${problems.length} cron file(s) were skipped and will NOT be scheduled:\n` + problems.map((p) => ` - ${p}`).join("\n") + "\n" + [...syntax ? [SYNTAX_ERROR_ADVICE] : [], ...other ? [" Fix these or author them with `defineCron(...)` for a typed, compile-checked contract."] : []].join("\n"));
177
+ }
178
+ return {
179
+ crons,
180
+ problems
181
+ };
182
+ }
183
+ /**
184
+ * The declaration a definition makes: every field a host can read without the
185
+ * handler. Spelled once here so the graph and the scheduler cannot disagree
186
+ * about what a cron is.
187
+ */
188
+ function cronResourceOptions(definition) {
189
+ return {
190
+ schedule: definition.schedule,
191
+ ...definition.timezone !== void 0 ? { timezone: definition.timezone } : {},
192
+ ...definition.description !== void 0 ? { description: definition.description } : {},
193
+ ...definition.enabled !== void 0 ? { enabled: definition.enabled } : {},
194
+ ...definition.timeoutSeconds !== void 0 ? { timeoutSeconds: definition.timeoutSeconds } : {},
195
+ ...definition.catchUpWindowSeconds !== void 0 ? { catchUpWindowSeconds: definition.catchUpWindowSeconds } : {},
196
+ ...definition.name ? { label: definition.name } : {}
197
+ };
198
+ }
199
+ //#endregion
200
+ export { loadCrons as a, loadCronJobsWithDiagnostics as i, declareCrons as n, loadCronJobsFromDirectory as r, cron_loader_exports as t };
201
+
202
+ //# sourceMappingURL=cron-loader-NHqRfKF0.js.map