@rebasepro/server 0.23.0 → 0.24.1-canary.g56af4a4

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 (261) hide show
  1. package/README.md +1 -1
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/GCSStorageController-DivE9bAx.js +402 -0
  4. package/dist/GCSStorageController-DivE9bAx.js.map +1 -0
  5. package/dist/S3StorageController-D_vKs4kP.js +480 -0
  6. package/dist/S3StorageController-D_vKs4kP.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +9 -0
  9. package/dist/api/live-schema-routes.d.ts +38 -8
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/openapi-generator.d.ts +17 -0
  12. package/dist/api/rest/api-generator.d.ts +97 -11
  13. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  14. package/dist/api/rest/field-access-query.d.ts +28 -8
  15. package/dist/api/rest/field-ops.d.ts +19 -7
  16. package/dist/api/rest/query-body.d.ts +10 -0
  17. package/dist/api/rest/write-validation.d.ts +9 -7
  18. package/dist/api/schema-editor-routes.d.ts +10 -1
  19. package/dist/api/sql-export-routes.d.ts +8 -0
  20. package/dist/api/types.d.ts +28 -1
  21. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-CSdL0iCk.js} +310 -12
  22. package/dist/ast-schema-editor-CSdL0iCk.js.map +1 -0
  23. package/dist/auth/access.d.ts +121 -0
  24. package/dist/auth/adapter-middleware.d.ts +2 -1
  25. package/dist/auth/address-ownership.d.ts +16 -1
  26. package/dist/auth/admin-roles-route.d.ts +4 -2
  27. package/dist/auth/admin-roles.d.ts +17 -20
  28. package/dist/auth/admin-user-ops.d.ts +24 -2
  29. package/dist/auth/admin-users-route.d.ts +2 -0
  30. package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
  31. package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
  32. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  33. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  34. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  35. package/dist/auth/api-keys/index.d.ts +11 -11
  36. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  37. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  38. package/dist/auth/auth-hooks.d.ts +46 -7
  39. package/dist/auth/builtin-auth-adapter.d.ts +8 -0
  40. package/dist/auth/cookie-utils.d.ts +7 -0
  41. package/dist/auth/deliverable-address.d.ts +6 -0
  42. package/dist/auth/email-change-routes.d.ts +41 -0
  43. package/dist/auth/expired-token-sweep.d.ts +71 -0
  44. package/dist/auth/impersonation.d.ts +110 -0
  45. package/dist/auth/index.d.ts +4 -2
  46. package/dist/auth/interfaces.d.ts +120 -59
  47. package/dist/auth/jwt-crypto.d.ts +7 -0
  48. package/dist/auth/jwt-keys.d.ts +4 -0
  49. package/dist/auth/jwt.d.ts +54 -3
  50. package/dist/auth/magic-link-routes.d.ts +2 -6
  51. package/dist/auth/mfa-routes.d.ts +2 -9
  52. package/dist/auth/middleware.d.ts +17 -5
  53. package/dist/auth/otp-routes.d.ts +2 -6
  54. package/dist/auth/passwordless-signup.d.ts +27 -0
  55. package/dist/auth/platform-token.d.ts +122 -0
  56. package/dist/auth/rate-limiter.d.ts +54 -1
  57. package/dist/auth/registration-policy.d.ts +17 -0
  58. package/dist/auth/routes.d.ts +45 -0
  59. package/dist/auth/scope-routes.d.ts +22 -0
  60. package/dist/auth/session-routes.d.ts +11 -6
  61. package/dist/auth/token-revocation.d.ts +50 -1
  62. package/dist/auth/user-picking.d.ts +35 -0
  63. package/dist/auth/verify-credential.d.ts +28 -0
  64. package/dist/{auth-B-GIMpDG.js → auth-OPq_FeMs.js} +3115 -638
  65. package/dist/auth-OPq_FeMs.js.map +1 -0
  66. package/dist/auth_adapter-CIItCTc8.js +21 -0
  67. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  68. package/dist/backend-DTAOsLQc.js.map +1 -1
  69. package/dist/backup/backup-common.d.ts +24 -3
  70. package/dist/backup/backup-routes.d.ts +46 -5
  71. package/dist/backup/backup-schedule.d.ts +33 -0
  72. package/dist/backup/backup-storage.d.ts +16 -0
  73. package/dist/backup/index.d.ts +5 -1
  74. package/dist/backup-5d7XhLX3.js +581 -0
  75. package/dist/backup-5d7XhLX3.js.map +1 -0
  76. package/dist/boot/bundle.d.ts +19 -0
  77. package/dist/boot/driver.d.ts +3 -0
  78. package/dist/boot/env.d.ts +56 -4
  79. package/dist/boot/http-server.d.ts +22 -0
  80. package/dist/boot/options.d.ts +16 -1
  81. package/dist/boot/runtime-cors.d.ts +17 -0
  82. package/dist/boot/security-headers.d.ts +26 -0
  83. package/dist/boot/static-routing.d.ts +65 -0
  84. package/dist/collection_patch-BRu-BvDv.js +472 -0
  85. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  86. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-02lASyJI.js} +5 -8
  87. package/dist/contract-routes-02lASyJI.js.map +1 -0
  88. package/dist/cron/cron-routes.d.ts +1 -1
  89. package/dist/cron/cron-scheduler.d.ts +94 -23
  90. package/dist/cron/cron-store.d.ts +31 -4
  91. package/dist/cron/define-cron.d.ts +6 -2
  92. package/dist/cron/index.d.ts +1 -1
  93. package/dist/{cron-loader-DfTj2Hbi.js → cron-loader-D7OmleAb.js} +11 -7
  94. package/dist/cron-loader-D7OmleAb.js.map +1 -0
  95. package/dist/{cron-routes-eE8nif_b.js → cron-routes-CBfiaqEL.js} +34 -8
  96. package/dist/cron-routes-CBfiaqEL.js.map +1 -0
  97. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-C2vbETMo.js} +349 -65
  98. package/dist/cron-scheduler-C2vbETMo.js.map +1 -0
  99. package/dist/cron-store-B2Ih0gCN.js +540 -0
  100. package/dist/cron-store-B2Ih0gCN.js.map +1 -0
  101. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BaqMSa4Y.js} +2 -2
  102. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BaqMSa4Y.js.map} +1 -1
  103. package/dist/email/index.d.ts +2 -2
  104. package/dist/email/smtp-email-service.d.ts +23 -0
  105. package/dist/email/templates.d.ts +22 -0
  106. package/dist/email/types.d.ts +44 -0
  107. package/dist/env.d.ts +1 -39
  108. package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
  109. package/dist/errors-D6_y86c5.js.map +1 -0
  110. package/dist/filter-dialect-REl-TjZr.js +1628 -0
  111. package/dist/filter-dialect-REl-TjZr.js.map +1 -0
  112. package/dist/{function-loader-xnbDAPfa.js → function-loader-BDKKiBmy.js} +5 -4
  113. package/dist/function-loader-BDKKiBmy.js.map +1 -0
  114. package/dist/{function-routes-Chet4-lB.js → function-routes-DWB7BBsi.js} +25 -13
  115. package/dist/function-routes-DWB7BBsi.js.map +1 -0
  116. package/dist/functions/context.d.ts +26 -6
  117. package/dist/functions/define-function.d.ts +7 -4
  118. package/dist/functions/guards.d.ts +22 -5
  119. package/dist/functions/index.d.ts +2 -2
  120. package/dist/functions/index.js +114 -37
  121. package/dist/functions/index.js.map +1 -1
  122. package/dist/functions/request-timeout.d.ts +7 -5
  123. package/dist/history/history-routes.d.ts +9 -0
  124. package/dist/history/retention-sweep.d.ts +81 -0
  125. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-DHenr3lm.js} +4 -4
  126. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-DHenr3lm.js.map} +1 -1
  127. package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
  128. package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
  129. package/dist/index.d.ts +14 -7
  130. package/dist/index.es.js +23363 -17552
  131. package/dist/index.es.js.map +1 -1
  132. package/dist/init/health.d.ts +17 -2
  133. package/dist/init/shutdown.d.ts +20 -0
  134. package/dist/init.d.ts +70 -6
  135. package/dist/internal-tables-L2t39RdT.js +136 -0
  136. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  137. package/dist/jobs/job-queue.d.ts +8 -0
  138. package/dist/jobs/job-store.d.ts +24 -0
  139. package/dist/jobs/types.d.ts +25 -4
  140. package/dist/{jobs-CazMYhyy.js → jobs-BpD4Q0qR.js} +235 -25
  141. package/dist/jobs-BpD4Q0qR.js.map +1 -0
  142. package/dist/{jwt-DnQHNFCl.js → jwt-Bqcu7cGt.js} +207 -77
  143. package/dist/jwt-Bqcu7cGt.js.map +1 -0
  144. package/dist/{keys-CogCQpxG.js → keys-GAVZqbqx.js} +3 -17
  145. package/dist/{keys-CogCQpxG.js.map → keys-GAVZqbqx.js.map} +1 -1
  146. package/dist/load-failure-UvzZgGXT.js +48 -0
  147. package/dist/load-failure-UvzZgGXT.js.map +1 -0
  148. package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
  149. package/dist/logger-D-S-hO5e.js.map +1 -0
  150. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DAdv37GI.js} +48 -8
  151. package/dist/logs-routes-DAdv37GI.js.map +1 -0
  152. package/dist/mcp/consent-page.d.ts +1 -1
  153. package/dist/mcp/mcp-routes.d.ts +7 -0
  154. package/dist/mcp/mcp-tools.d.ts +23 -9
  155. package/dist/mcp/oauth-metadata.d.ts +21 -16
  156. package/dist/mcp/oauth-routes.d.ts +7 -1
  157. package/dist/metrics/index.d.ts +18 -0
  158. package/dist/metrics/pool-wait.d.ts +50 -0
  159. package/dist/offline-8Ks3yp9n-CadBKm2D.js +3305 -0
  160. package/dist/offline-8Ks3yp9n-CadBKm2D.js.map +1 -0
  161. package/dist/offline-store-D9ytNNaa-BCr2vsFB.js +1903 -0
  162. package/dist/offline-store-D9ytNNaa-BCr2vsFB.js.map +1 -0
  163. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-BqoUh4Ij.js} +455 -80
  164. package/dist/openapi-generator-BqoUh4Ij.js.map +1 -0
  165. package/dist/policy-PdzX-Opd.js +121 -0
  166. package/dist/policy-PdzX-Opd.js.map +1 -0
  167. package/dist/project_manifest-R6y_KHms.js +59 -0
  168. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  169. package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
  170. package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
  171. package/dist/{query-parser-DGRVFNM3.js → query-parser-DK9eMkb9.js} +497 -52
  172. package/dist/query-parser-DK9eMkb9.js.map +1 -0
  173. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-Dnl2_OBc.js} +17 -10
  174. package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
  175. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  176. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  177. package/dist/schema-edit/apply-schema-change.d.ts +73 -3
  178. package/dist/schema-edit/local-source.d.ts +29 -0
  179. package/dist/schema-edit/project-root.d.ts +3 -2
  180. package/dist/schema-edit/remote-source.d.ts +20 -5
  181. package/dist/schema-edit/source-editor.d.ts +28 -0
  182. package/dist/schema-editor-routes-BFf3i8jm.js +136 -0
  183. package/dist/schema-editor-routes-BFf3i8jm.js.map +1 -0
  184. package/dist/serve-spa.d.ts +58 -0
  185. package/dist/services/outbound-post.d.ts +50 -0
  186. package/dist/services/outbound-url-guard.d.ts +34 -5
  187. package/dist/services/routed-realtime-service.d.ts +16 -0
  188. package/dist/services/webhook-service.d.ts +66 -14
  189. package/dist/soft-delete-params-47yBGs3_.js +668 -0
  190. package/dist/soft-delete-params-47yBGs3_.js.map +1 -0
  191. package/dist/sql-export-routes-BNNuGGLP.js +240 -0
  192. package/dist/sql-export-routes-BNNuGGLP.js.map +1 -0
  193. package/dist/{src-vkcwKXbT.js → src-CatHFUym.js} +439 -20
  194. package/dist/src-CatHFUym.js.map +1 -0
  195. package/dist/storage/GCSStorageController.d.ts +48 -2
  196. package/dist/storage/LocalStorageController.d.ts +69 -2
  197. package/dist/storage/S3StorageController.d.ts +39 -3
  198. package/dist/storage/image-transform.d.ts +5 -0
  199. package/dist/storage/index.d.ts +2 -2
  200. package/dist/storage/maintenance-routes.d.ts +23 -0
  201. package/dist/storage/maintenance.d.ts +126 -0
  202. package/dist/storage/property-limits.d.ts +41 -6
  203. package/dist/storage/rendition-cache.d.ts +9 -11
  204. package/dist/storage/request-keys.d.ts +15 -0
  205. package/dist/storage/request-principals.d.ts +30 -0
  206. package/dist/storage/requested-object.d.ts +74 -0
  207. package/dist/storage/routes.d.ts +52 -14
  208. package/dist/storage/source-variables.d.ts +10 -0
  209. package/dist/storage/streams.d.ts +42 -0
  210. package/dist/storage/tus-handler.d.ts +76 -7
  211. package/dist/storage/types.d.ts +101 -7
  212. package/dist/storage/upload-validation.d.ts +13 -0
  213. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  214. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  215. package/dist/strings.es-BeZWAodn.js +256 -0
  216. package/dist/strings.es-BeZWAodn.js.map +1 -0
  217. package/dist/upload-validation-Dd-fnsbA.js +172 -0
  218. package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
  219. package/dist/utils/byte-size.d.ts +10 -0
  220. package/dist/utils/compression.d.ts +6 -0
  221. package/dist/utils/load-failure.d.ts +14 -0
  222. package/dist/utils/logger.d.ts +12 -0
  223. package/package.json +5 -5
  224. package/dist/GCSStorageController-CjrA4PMo.js +0 -242
  225. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  226. package/dist/S3StorageController-B6pKDNVj.js +0 -264
  227. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  228. package/dist/admin-roles-vYdp_Pil.js +0 -36
  229. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  230. package/dist/admin_block-DxKLmdiv.js +0 -206
  231. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  232. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  233. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  234. package/dist/auth-B-GIMpDG.js.map +0 -1
  235. package/dist/backup-D7YR94N3.js +0 -253
  236. package/dist/backup-D7YR94N3.js.map +0 -1
  237. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  238. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  239. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  240. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  241. package/dist/cron-store-TcoGz-xS.js +0 -305
  242. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  243. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  244. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  245. package/dist/errors-DWsX4yTd.js.map +0 -1
  246. package/dist/function-loader-xnbDAPfa.js.map +0 -1
  247. package/dist/function-routes-Chet4-lB.js.map +0 -1
  248. package/dist/jobs-CazMYhyy.js.map +0 -1
  249. package/dist/jwt-DnQHNFCl.js.map +0 -1
  250. package/dist/logger-DO2PZc4i.js.map +0 -1
  251. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  252. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  253. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  254. package/dist/request-timeout-C_4C2BeR.js.map +0 -1
  255. package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
  256. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  257. package/dist/src-pmvW7BFx.js +0 -6688
  258. package/dist/src-pmvW7BFx.js.map +0 -1
  259. package/dist/src-vkcwKXbT.js.map +0 -1
  260. package/dist/types-BfKcm9do.js +0 -39
  261. package/dist/types-BfKcm9do.js.map +0 -1
@@ -3,8 +3,8 @@ import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
5
  import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
- import "./src-vkcwKXbT.js";
7
- import { a as redactSensitiveText, r as logger } from "./logger-DO2PZc4i.js";
6
+ import "./src-CatHFUym.js";
7
+ import { a as redactSensitiveText, r as logger, s as stringifyForLog } from "./logger-D-S-hO5e.js";
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { hostname } from "node:os";
10
10
  //#region ../types/src/types/env_boolean.ts
@@ -149,6 +149,8 @@ function buildScaleToZeroWarning(jobs, env = process.env) {
149
149
  //#region src/cron/cron-scheduler.ts
150
150
  var cron_scheduler_exports = /* @__PURE__ */ __exportAll({
151
151
  CronScheduler: () => CronScheduler,
152
+ MAX_CAPTURED_LOG_BYTES: () => MAX_CAPTURED_LOG_BYTES,
153
+ MAX_CAPTURED_LOG_LINES: () => 500,
152
154
  findMostRecentSlot: () => findMostRecentSlot,
153
155
  isAlreadyExecutingSkip: () => isAlreadyExecutingSkip,
154
156
  isValidTimeZone: () => isValidTimeZone,
@@ -295,6 +297,7 @@ function wallClockIn(candidate, zone) {
295
297
  timeZone: zone,
296
298
  hourCycle: "h23",
297
299
  weekday: "short",
300
+ year: "numeric",
298
301
  month: "numeric",
299
302
  day: "numeric",
300
303
  hour: "numeric",
@@ -305,6 +308,7 @@ function wallClockIn(candidate, zone) {
305
308
  const parts = {};
306
309
  for (const part of formatter.formatToParts(candidate)) parts[part.type] = part.value;
307
310
  return {
311
+ year: Number(parts.year),
308
312
  minute: Number(parts.minute),
309
313
  hour: Number(parts.hour),
310
314
  dom: Number(parts.day),
@@ -313,21 +317,69 @@ function wallClockIn(candidate, zone) {
313
317
  };
314
318
  }
315
319
  /**
316
- * Whether a minute-precision instant matches every field of the expression.
317
- *
318
- * Read in `zone` when one is named, else in the process's own zone — which is
319
- * whatever the host is set to, UTC in nearly every container. A schedule that
320
- * names its zone means the same wall-clock hour on every host it runs on.
320
+ * The wall clock an instant reads in `zone` when one is named, else in the
321
+ * process's own zone — which is whatever the host is set to, UTC in nearly
322
+ * every container. A schedule that names its zone means the same wall-clock
323
+ * hour on every host it runs on.
321
324
  */
322
- function matchesCronFields(candidate, fields, zone) {
323
- const wall = zone ? wallClockIn(candidate, zone) : {
325
+ function wallClockOf(instantMs, zone) {
326
+ const candidate = new Date(instantMs);
327
+ if (zone) return wallClockIn(candidate, zone);
328
+ return {
329
+ year: candidate.getFullYear(),
324
330
  minute: candidate.getMinutes(),
325
331
  hour: candidate.getHours(),
326
332
  dom: candidate.getDate(),
327
333
  month: candidate.getMonth() + 1,
328
334
  dow: candidate.getDay()
329
335
  };
330
- return fields.months.includes(wall.month) && fields.doms.includes(wall.dom) && fields.dows.includes(wall.dow) && fields.hours.includes(wall.hour) && fields.minutes.includes(wall.minute);
336
+ }
337
+ /**
338
+ * The zone's offset from UTC at an instant: its wall clock read back as UTC,
339
+ * minus the instant. Two instants with the same offset have wall clocks
340
+ * exactly as far apart as the instants themselves.
341
+ */
342
+ function offsetAt(instantMs, wall) {
343
+ return Date.UTC(wall.year, wall.month - 1, wall.dom, wall.hour, wall.minute) - instantMs;
344
+ }
345
+ /** Whether a wall-clock day — month, day of month, weekday — can hold a slot. */
346
+ function dayMatches(wall, fields) {
347
+ return fields.months.includes(wall.month) && fields.doms.includes(wall.dom) && fields.dows.includes(wall.dow);
348
+ }
349
+ /** Whether a wall-clock minute matches every field of the expression. */
350
+ function wallMatches(wall, fields) {
351
+ return dayMatches(wall, fields) && fields.hours.includes(wall.hour) && fields.minutes.includes(wall.minute);
352
+ }
353
+ var MINUTES_PER_DAY = 1440;
354
+ /**
355
+ * How many wall-clock minutes from a non-matching `wall` to the first later
356
+ * wall minute that could match. Never past the next wall midnight, so the
357
+ * minutes in between share `wall`'s day and every one of them is a miss.
358
+ */
359
+ function wallMinutesToNextCandidate(wall, fields) {
360
+ const minuteOfDay = wall.hour * 60 + wall.minute;
361
+ if (!dayMatches(wall, fields)) return MINUTES_PER_DAY - minuteOfDay;
362
+ if (!fields.hours.includes(wall.hour)) {
363
+ const nextHour = fields.hours.find((hour) => hour > wall.hour);
364
+ return nextHour === void 0 ? MINUTES_PER_DAY - minuteOfDay : nextHour * 60 - minuteOfDay;
365
+ }
366
+ const nextMinute = fields.minutes.find((minute) => minute > wall.minute);
367
+ return nextMinute === void 0 ? 60 - wall.minute : nextMinute - wall.minute;
368
+ }
369
+ /**
370
+ * The mirror of {@link wallMinutesToNextCandidate}: how many wall-clock
371
+ * minutes back from a non-matching `wall` to the latest earlier wall minute
372
+ * that could match. Never before 23:59 of the previous wall day.
373
+ */
374
+ function wallMinutesToPreviousCandidate(wall, fields) {
375
+ const minuteOfDay = wall.hour * 60 + wall.minute;
376
+ if (!dayMatches(wall, fields)) return minuteOfDay + 1;
377
+ if (!fields.hours.includes(wall.hour)) {
378
+ const previousHour = [...fields.hours].reverse().find((hour) => hour < wall.hour);
379
+ return previousHour === void 0 ? minuteOfDay + 1 : minuteOfDay - (previousHour * 60 + 59);
380
+ }
381
+ const previousMinute = [...fields.minutes].reverse().find((minute) => minute < wall.minute);
382
+ return previousMinute === void 0 ? wall.minute + 1 : wall.minute - previousMinute;
331
383
  }
332
384
  /**
333
385
  * How far forward to look for the next matching slot.
@@ -339,27 +391,86 @@ function matchesCronFields(candidate, fields, zone) {
339
391
  var MAX_SLOT_SEARCH_MINUTES = 2105280;
340
392
  var MINUTE_MS = 6e4;
341
393
  /**
394
+ * The first real minute in `[startMs, endMs)`, stepping in `direction`, whose
395
+ * wall clock matches `fields` — the same answer as testing every minute in
396
+ * turn, without testing every minute.
397
+ *
398
+ * The search moves through *real* minutes and only reads the wall clock, never
399
+ * steps it: across a daylight-saving change a wall-clock minute is not a real
400
+ * one. Falling back, a wall-clock step from 02:59 summer time lands on 03:00
401
+ * winter time and skips the repeated hour; inside it, the "next" minute can be
402
+ * an hour in the past. That is why this used to test one real minute at a
403
+ * time — and a yearly job named in a zone cost half a million `Intl` calls,
404
+ * blocking the event loop for a third of a second on every re-arm, and two
405
+ * seconds for 29 February.
406
+ *
407
+ * Now it hops. From a miss, {@link wallMinutesToNextCandidate} says how many
408
+ * wall minutes cannot match (the rest of a day that is the wrong date, the
409
+ * rest of an hour that is the wrong hour, the minutes before the next listed
410
+ * one). While the zone's offset holds, that many wall minutes are exactly
411
+ * that many real minutes, so the hop is taken when the offset at both ends is
412
+ * the same. When it differs, a transition lies inside the hop: the search
413
+ * bisects for the first minute on the new offset and resumes there — every
414
+ * minute before it is on the old offset, so its wall clock is inside the
415
+ * stretch already known to miss. A hop is at most a day, and the one thing
416
+ * this assumes is that no zone changes its offset and changes it back within
417
+ * a single day, which none in the tz database does. Each day costs one
418
+ * wall-clock read, plus about a dozen for each transition crossed.
419
+ *
420
+ * `direction: -1` walks back from `startMs` to `endMs` (inclusive) with the
421
+ * mirror of the same hop, for {@link findMostRecentSlot}.
422
+ */
423
+ function searchSlot(fields, startMs, endMs, direction, zone) {
424
+ const inWindow = (instantMs) => direction === 1 ? instantMs < endMs : instantMs >= endMs;
425
+ let current = startMs;
426
+ let wall = wallClockOf(current, zone);
427
+ while (inWindow(current)) {
428
+ if (wallMatches(wall, fields)) return current;
429
+ const skip = direction === 1 ? wallMinutesToNextCandidate(wall, fields) : wallMinutesToPreviousCandidate(wall, fields);
430
+ const target = current + direction * skip * MINUTE_MS;
431
+ const targetWall = wallClockOf(target, zone);
432
+ const offset = offsetAt(current, wall);
433
+ if (skip === 1 || offsetAt(target, targetWall) === offset) {
434
+ current = target;
435
+ wall = targetWall;
436
+ continue;
437
+ }
438
+ let held = current;
439
+ let changed = target;
440
+ let changedWall = targetWall;
441
+ while (Math.abs(changed - held) > MINUTE_MS) {
442
+ const middle = held + direction * Math.floor(Math.abs(changed - held) / MINUTE_MS / 2) * MINUTE_MS;
443
+ const middleWall = wallClockOf(middle, zone);
444
+ if (offsetAt(middle, middleWall) === offset) held = middle;
445
+ else {
446
+ changed = middle;
447
+ changedWall = middleWall;
448
+ }
449
+ }
450
+ current = changed;
451
+ wall = changedWall;
452
+ }
453
+ }
454
+ /**
342
455
  * Calculate the next Date after `after` that matches the cron expression.
343
456
  * Throws on invalid expressions.
344
457
  */
345
458
  function parseCronExpression(expression, after, timezone) {
346
459
  const fields = parseCronFields(expression);
347
- const candidate = new Date(after);
348
- candidate.setUTCSeconds(0, 0);
349
- candidate.setTime(candidate.getTime() + MINUTE_MS);
350
- for (let i = 0; i < MAX_SLOT_SEARCH_MINUTES; i++) {
351
- if (matchesCronFields(candidate, fields, timezone)) return candidate;
352
- candidate.setTime(candidate.getTime() + MINUTE_MS);
353
- }
460
+ const start = new Date(after);
461
+ start.setUTCSeconds(0, 0);
462
+ const startMs = start.getTime() + MINUTE_MS;
463
+ const slot = searchSlot(fields, startMs, startMs + MAX_SLOT_SEARCH_MINUTES * MINUTE_MS, 1, timezone);
464
+ if (slot !== void 0) return new Date(slot);
354
465
  throw new Error(`Cron expression "${expression}" has no matching time within ${Math.round(MAX_SLOT_SEARCH_MINUTES / 525960)} years of ${after.toISOString()}. Check the day-of-month and month fields — a date such as 31 February never occurs.`);
355
466
  }
356
467
  /**
357
468
  * The latest slot matching `expression` within the inclusive window
358
469
  * `[from, to]`, or `undefined` when the expression has no slot in it.
359
470
  *
360
- * Walks backwards a minute at a time from `to`, so the first hit is already
361
- * the answer — in the common case (a job that ran normally moments ago) that
362
- * is a handful of iterations, not a scan of the whole window.
471
+ * Walks backwards from `to`, so the first hit is already the answer — in the
472
+ * common case (a job that ran normally moments ago) that is a handful of
473
+ * steps, not a scan of the whole window.
363
474
  *
364
475
  * `to`'s own minute is included: an instance booting at 06:00:30 has *not* run
365
476
  * the 06:00 slot — `parseCronExpression` already skipped past it to tomorrow —
@@ -371,14 +482,46 @@ function parseCronExpression(expression, after, timezone) {
371
482
  */
372
483
  function findMostRecentSlot(expression, from, to, timezone) {
373
484
  const fields = parseCronFields(expression);
374
- const candidate = new Date(to);
375
- candidate.setUTCSeconds(0, 0);
376
- for (let i = 0; i < MAX_SLOT_SEARCH_MINUTES && candidate.getTime() >= from.getTime(); i++) {
377
- if (matchesCronFields(candidate, fields, timezone)) return candidate;
378
- candidate.setTime(candidate.getTime() - MINUTE_MS);
379
- }
485
+ const start = new Date(to);
486
+ start.setUTCSeconds(0, 0);
487
+ const startMs = start.getTime();
488
+ const slot = searchSlot(fields, startMs, Math.max(from.getTime(), startMs - (MAX_SLOT_SEARCH_MINUTES - 1) * MINUTE_MS), -1, timezone);
489
+ return slot === void 0 ? void 0 : new Date(slot);
380
490
  }
381
491
  var MAX_LOGS_PER_JOB = 50;
492
+ var MAX_CAPTURED_LOG_BYTES = 64 * 1024;
493
+ /**
494
+ * A run's `ctx.log` capture, capped at {@link MAX_CAPTURED_LOG_LINES} and
495
+ * {@link MAX_CAPTURED_LOG_BYTES}. The kept lines are the first ones: once one
496
+ * line is dropped, every later one is too, so the history reads as a prefix of
497
+ * the run's output and never as lines with gaps between them.
498
+ */
499
+ function createLogCapture(firstLine) {
500
+ const lines = [];
501
+ let bytes = 0;
502
+ let dropped = 0;
503
+ let closed = false;
504
+ const capture = {
505
+ add(line) {
506
+ if (closed) return;
507
+ const size = Buffer.byteLength(line, "utf8");
508
+ if (dropped === 0 && lines.length < 500 && bytes + size <= 65536) {
509
+ lines.push(line);
510
+ bytes += size;
511
+ return;
512
+ }
513
+ dropped++;
514
+ },
515
+ /** The lines kept, and the line that says what was not. Later `add`s are ignored. */
516
+ close() {
517
+ if (!closed && dropped > 0) lines.push(`… ${dropped} more line${dropped === 1 ? "" : "s"} not kept — see the server log`);
518
+ closed = true;
519
+ return lines;
520
+ }
521
+ };
522
+ if (firstLine !== void 0) capture.add(firstLine);
523
+ return capture;
524
+ }
382
525
  /**
383
526
  * Minimum milliseconds between scheduled executions of the same job.
384
527
  * Prevents tight re-execution loops caused by jitter or clock drift.
@@ -410,6 +553,12 @@ function setLongTimeout(callback, delayMs) {
410
553
  /** A run's timeout when its definition names none. */
411
554
  var DEFAULT_TIMEOUT_SECONDS = 300;
412
555
  /**
556
+ * How often a started scheduler deletes slot claims and run history past
557
+ * retention. Both accrue one row per job per fire, so a scheduler that lives
558
+ * for months needs to prune while it runs, not only when it next boots.
559
+ */
560
+ var CLAIM_SWEEP_INTERVAL_MS = 60 * 6e4;
561
+ /**
413
562
  * How long a run lease outlasts the run's own timeout.
414
563
  *
415
564
  * The lease is timed on the database's clock from the moment it is taken, the
@@ -469,6 +618,10 @@ var CronScheduler = class {
469
618
  * ever clear the lease that run took.
470
619
  */
471
620
  instanceLabel = `${hostname()}:${process.pid}`;
621
+ /** The hourly claim- and log-retention sweep, while the scheduler is started with a store. */
622
+ claimSweepTimer;
623
+ sweepingClaims = false;
624
+ sweepingLogs = false;
472
625
  /**
473
626
  * Set the server singleton to make it available to cron job handlers.
474
627
  *
@@ -502,34 +655,19 @@ var CronScheduler = class {
502
655
  const validation = validateCronExpression(loaded.definition.schedule);
503
656
  if (!validation.valid) {
504
657
  logger.error(`[cron] Rejecting job "${loaded.id}": invalid schedule "${loaded.definition.schedule}" — ${validation.reason}`);
505
- this.rejected.set(loaded.id, {
506
- id: loaded.id,
507
- name: loaded.definition.name ?? loaded.id,
508
- schedule: loaded.definition.schedule,
509
- reason: validation.reason
510
- });
658
+ this.refuse(loaded, validation.reason);
511
659
  continue;
512
660
  }
513
661
  if (loaded.definition.timezone !== void 0 && !isValidTimeZone(loaded.definition.timezone)) {
514
662
  const reason = `unknown timezone "${loaded.definition.timezone}" — use an IANA name such as "Europe/Madrid" or "UTC"`;
515
663
  logger.error(`[cron] Rejecting job "${loaded.id}": ${reason}.`);
516
- this.rejected.set(loaded.id, {
517
- id: loaded.id,
518
- name: loaded.definition.name ?? loaded.id,
519
- schedule: loaded.definition.schedule,
520
- reason
521
- });
664
+ this.refuse(loaded, reason);
522
665
  continue;
523
666
  }
524
667
  const timeoutProblem = invalidTimeoutReason(loaded.definition.timeoutSeconds);
525
668
  if (timeoutProblem) {
526
669
  logger.error(`[cron] Rejecting job "${loaded.id}": ${timeoutProblem}.`);
527
- this.rejected.set(loaded.id, {
528
- id: loaded.id,
529
- name: loaded.definition.name ?? loaded.id,
530
- schedule: loaded.definition.schedule,
531
- reason: timeoutProblem
532
- });
670
+ this.refuse(loaded, timeoutProblem);
533
671
  continue;
534
672
  }
535
673
  this.rejected.delete(loaded.id);
@@ -574,8 +712,48 @@ var CronScheduler = class {
574
712
  if (!this.store) logger.warn("[cron] No cron store attached — runs are uncoordinated; with multiple app instances every instance will execute every job");
575
713
  this.warnIfScaleToZero();
576
714
  this.catchUpMissedSlots();
715
+ if (this.store?.sweepClaims || this.store?.sweepLogs) {
716
+ const sweep = () => {
717
+ this.sweepClaims();
718
+ this.sweepLogs();
719
+ };
720
+ sweep();
721
+ this.claimSweepTimer = setInterval(sweep, CLAIM_SWEEP_INTERVAL_MS);
722
+ this.claimSweepTimer.unref?.();
723
+ }
577
724
  logger.info(`⏰ Cron scheduler started with ${this.jobs.size} job(s)`);
578
725
  }
726
+ /** Delete slot claims past retention. Failures are logged; the next hour tries again. */
727
+ async sweepClaims() {
728
+ const store = this.store;
729
+ if (!store?.sweepClaims || this.sweepingClaims) return;
730
+ this.sweepingClaims = true;
731
+ try {
732
+ await store.sweepClaims();
733
+ } catch (err) {
734
+ logger.warn("[cron] The claim retention sweep failed; it runs again within the hour", { error: err });
735
+ } finally {
736
+ this.sweepingClaims = false;
737
+ }
738
+ }
739
+ /**
740
+ * Delete run history past retention, and count the runs not counted yet.
741
+ * Failures are logged; the next hour tries again, from where this one
742
+ * stopped — each batch it finished is committed.
743
+ */
744
+ async sweepLogs() {
745
+ const store = this.store;
746
+ if (!store?.sweepLogs || this.sweepingLogs) return;
747
+ this.sweepingLogs = true;
748
+ try {
749
+ const { counted } = await store.sweepLogs();
750
+ if (counted > 0) logger.info(`[cron] Counted ${counted} earlier run(s) into the jobs' run totals`);
751
+ } catch (err) {
752
+ logger.warn("[cron] The run history sweep failed; it runs again within the hour", { error: err });
753
+ } finally {
754
+ this.sweepingLogs = false;
755
+ }
756
+ }
579
757
  /**
580
758
  * Stop the scheduler: clear every timer, then wait for the runs executing
581
759
  * right now — for at most `timeoutMs` when given.
@@ -591,6 +769,10 @@ var CronScheduler = class {
591
769
  async stop(timeoutMs) {
592
770
  this.started = false;
593
771
  for (const [id] of this.jobs) this.stopJob(id);
772
+ if (this.claimSweepTimer) {
773
+ clearInterval(this.claimSweepTimer);
774
+ this.claimSweepTimer = void 0;
775
+ }
594
776
  if (this.inFlight.size === 0) return;
595
777
  const settled = Promise.allSettled([...this.inFlight]).then(() => true);
596
778
  if (timeoutMs === void 0) {
@@ -613,6 +795,18 @@ var CronScheduler = class {
613
795
  listJobs() {
614
796
  return [...this.jobs.values()].map((job) => this.toStatus(job));
615
797
  }
798
+ /** Keep a refused job, so the admin surface can say why it never runs. */
799
+ refuse(loaded, reason) {
800
+ this.rejected.set(loaded.id, {
801
+ job: {
802
+ id: loaded.id,
803
+ name: loaded.definition.name ?? loaded.id,
804
+ schedule: loaded.definition.schedule,
805
+ reason
806
+ },
807
+ definition: loaded.definition
808
+ });
809
+ }
616
810
  /**
617
811
  * Jobs that loaded but whose schedule the scheduler refused.
618
812
  *
@@ -621,7 +815,22 @@ var CronScheduler = class {
621
815
  * cron will never fire" look identical from the admin panel.
622
816
  */
623
817
  listRejectedJobs() {
624
- return [...this.rejected.values()];
818
+ return [...this.rejected.values()].map((refused) => refused.job);
819
+ }
820
+ /**
821
+ * {@link listRejectedJobs}, those whose definition matches — how a surface
822
+ * finds a refused job by what it is, as {@link jobIdsWhere} does for the
823
+ * registered ones.
824
+ */
825
+ rejectedJobsWhere(predicate) {
826
+ return [...this.rejected.values()].filter((refused) => predicate(refused.definition)).map((refused) => refused.job);
827
+ }
828
+ /**
829
+ * The ids of the registered jobs whose definition matches — how a surface
830
+ * finds a job by what it is rather than by the file it happens to live in.
831
+ */
832
+ jobIdsWhere(predicate) {
833
+ return [...this.jobs.values()].filter((job) => predicate(job.definition)).map((job) => job.id);
625
834
  }
626
835
  /**
627
836
  * Get a single job status by ID.
@@ -634,11 +843,11 @@ var CronScheduler = class {
634
843
  * {@link listJobs}, as the fleet sees it rather than this process alone.
635
844
  *
636
845
  * Reads the store: every job's enabled state — a pause another replica
637
- * made — and whether another process holds its run lease. On a process
638
- * whose scheduler is not started (the `api` role) it also reads each job's
639
- * run count and last run from `cron_logs`, since that process runs nothing
640
- * and its own counters would say 0 runs for a job the worker has run a
641
- * thousand times.
846
+ * made — whether another process holds its run lease, and each job's run
847
+ * count and last run from `cron_logs`. The counters in this process's
848
+ * memory count only the runs it made: the `api` role runs nothing and
849
+ * would say 0 runs for a job the worker has run a thousand times, and one
850
+ * of two scheduling replicas never sees the slots the other one claimed.
642
851
  *
643
852
  * A store that cannot answer leaves this process's own view, with a
644
853
  * warning: a listing is not worth failing.
@@ -666,7 +875,12 @@ var CronScheduler = class {
666
875
  }
667
876
  /**
668
877
  * Get log entries for a job from the database (if store is available).
669
- * Falls back to in-memory logs if no store is configured.
878
+ * Falls back to in-memory logs if no store is configured, or if the store
879
+ * has none — a run whose row is still being written.
880
+ *
881
+ * Throws when the store cannot read the history. This process's memory is
882
+ * not a stand-in for it: it is empty on the `api` role and after a
883
+ * restart, and holds one replica's runs at most.
670
884
  */
671
885
  async getJobLogsFromDb(id, limit) {
672
886
  if (this.store) {
@@ -730,6 +944,46 @@ var CronScheduler = class {
730
944
  return this.executeJob(job, true);
731
945
  }
732
946
  /**
947
+ * Start a job now and answer as soon as the run has begun, not when it
948
+ * ends — the manual trigger's `?async=1`.
949
+ *
950
+ * The synchronous {@link triggerJob} holds its HTTP request open for the
951
+ * whole run, and a proxy in front of the server (many stop at 60 s) cuts
952
+ * a long job's answer while the run carries on. This resolves once the
953
+ * run lease is held and the handler is about to be called, so a run that
954
+ * cannot start — already executing here or on the lease holder — is still
955
+ * answered as the skip it is. The run itself is tracked like any other:
956
+ * `stop()` waits for it, and its outcome lands in the run history, where
957
+ * the entry carries `manual: true` and the `startedAt` returned here.
958
+ *
959
+ * Returns `undefined` if the job doesn't exist.
960
+ */
961
+ async startJob(id) {
962
+ const job = this.jobs.get(id);
963
+ if (!job) return void 0;
964
+ if (job.executing) {
965
+ logger.warn(`[cron] Skipping manual trigger of "${id}" — already executing`);
966
+ return {
967
+ started: false,
968
+ log: this.recordSkip(job, "already_executing", true)
969
+ };
970
+ }
971
+ let onStart;
972
+ const begun = new Promise((resolve) => {
973
+ onStart = resolve;
974
+ });
975
+ const run = this.executeJob(job, true, void 0, onStart);
976
+ const first = await Promise.race([begun.then((startedAt) => ({ startedAt })), run.then((log) => ({ log }))]);
977
+ if ("startedAt" in first) return {
978
+ started: true,
979
+ startedAt: first.startedAt
980
+ };
981
+ return {
982
+ started: false,
983
+ log: first.log
984
+ };
985
+ }
986
+ /**
733
987
  * Record a run that did not happen because the previous one had not
734
988
  * finished.
735
989
  *
@@ -882,18 +1136,46 @@ var CronScheduler = class {
882
1136
  if (!this.started) {
883
1137
  const now = /* @__PURE__ */ new Date();
884
1138
  for (const job of jobs) try {
885
- viewOf(job.id).nextRunAt = parseCronExpression(job.definition.schedule, now, job.definition.timezone);
1139
+ viewOf(job.id).nextRunAt = this.listedNextRun(job, now);
886
1140
  } catch {}
887
- if (store?.fetchRunSummaries && ids.length > 0) try {
888
- const summaries = await store.fetchRunSummaries(ids);
889
- for (const [id, summary] of summaries) viewOf(id).summary = summary;
890
- } catch (err) {
891
- logger.warn("[cron] Could not read the jobs' run history — listing what this process knows", { error: err });
892
- }
1141
+ }
1142
+ if (store?.fetchRunSummaries && ids.length > 0) try {
1143
+ const summaries = await store.fetchRunSummaries(ids);
1144
+ for (const [id, summary] of summaries) viewOf(id).summary = summary;
1145
+ } catch (err) {
1146
+ logger.warn("[cron] Could not read the jobs' run history — listing what this process knows", { error: err });
893
1147
  }
894
1148
  return views;
895
1149
  }
896
1150
  /**
1151
+ * The next slot of `job` after `now`, for a listing on a process whose
1152
+ * scheduler is not started — the `api` role, which Studio's Cron panel
1153
+ * polls every 15 seconds.
1154
+ *
1155
+ * Cached on the job. The key is the job's schedule and zone (a re-register
1156
+ * replaces the job, and with it the cache); the entry holds while
1157
+ * `computedAt <= now < slot`. That is the same answer, not an
1158
+ * approximation: the search found no matching minute between
1159
+ * `computedAt`'s next minute and `slot`, so from any later instant before
1160
+ * `slot` it lands on `slot` again. A clock that moves back, or reaching the
1161
+ * slot, recomputes. The cache holds a pure function of the schedule, so
1162
+ * there is nothing user- or role-scoped in it.
1163
+ */
1164
+ listedNextRun(job, now) {
1165
+ const { schedule, timezone } = job.definition;
1166
+ const nowMs = now.getTime();
1167
+ const cached = job.listedSlot;
1168
+ if (cached && cached.schedule === schedule && cached.timezone === timezone && cached.computedAt <= nowMs && nowMs < cached.slot.getTime()) return cached.slot;
1169
+ const slot = parseCronExpression(schedule, now, timezone);
1170
+ job.listedSlot = {
1171
+ schedule,
1172
+ timezone,
1173
+ computedAt: nowMs,
1174
+ slot
1175
+ };
1176
+ return slot;
1177
+ }
1178
+ /**
897
1179
  * Warn once at start when the process looks like it is running on a
898
1180
  * platform that freezes or evicts instances between requests, where the
899
1181
  * in-process timers this scheduler relies on never fire.
@@ -1047,10 +1329,10 @@ var CronScheduler = class {
1047
1329
  * - Persists to store (non-blocking) if available
1048
1330
  * - Always restores state even on catastrophic errors
1049
1331
  */
1050
- executeJob(job, manual, seedLog) {
1332
+ executeJob(job, manual, seedLog, onStart) {
1051
1333
  const abort = new AbortController();
1052
1334
  this.runControllers.add(abort);
1053
- const run = this.runJob(job, manual, abort, seedLog);
1335
+ const run = this.runJob(job, manual, abort, seedLog, onStart);
1054
1336
  this.track(run);
1055
1337
  return run.finally(() => {
1056
1338
  this.runControllers.delete(abort);
@@ -1064,7 +1346,7 @@ var CronScheduler = class {
1064
1346
  };
1065
1347
  work.then(release, release);
1066
1348
  }
1067
- async runJob(job, manual, abort, seedLog) {
1349
+ async runJob(job, manual, abort, seedLog, onStart) {
1068
1350
  if (job.executing) return this.recordSkip(job, "already_executing", manual);
1069
1351
  job.executing = true;
1070
1352
  const lease = await this.acquireLease(job);
@@ -1074,20 +1356,22 @@ var CronScheduler = class {
1074
1356
  return this.recordSkip(job, "already_executing", manual, lease.holder ?? "another process");
1075
1357
  }
1076
1358
  const startedAt = /* @__PURE__ */ new Date();
1077
- const capturedLogs = seedLog ? [seedLog] : [];
1359
+ const capturedLogs = createLogCapture(seedLog || void 0);
1078
1360
  const ctx = {
1079
1361
  jobId: job.id,
1080
1362
  scheduledAt: startedAt,
1081
1363
  signal: abort.signal,
1082
1364
  log: (...args) => {
1083
1365
  const line = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
1084
- capturedLogs.push(line);
1366
+ capturedLogs.add(line);
1367
+ logger.info(args.map((a) => typeof a === "string" ? a : stringifyForLog(a)).join(" "), { cron: job.id });
1085
1368
  },
1086
1369
  rebase: this.client
1087
1370
  };
1088
1371
  job.state = "running";
1089
1372
  job.lastRunAt = startedAt;
1090
1373
  job.totalRuns++;
1374
+ onStart?.(startedAt.toISOString());
1091
1375
  let success = true;
1092
1376
  let error;
1093
1377
  let result;
@@ -1126,7 +1410,7 @@ var CronScheduler = class {
1126
1410
  success,
1127
1411
  error,
1128
1412
  result: result !== void 0 ? result : void 0,
1129
- logs: capturedLogs,
1413
+ logs: capturedLogs.close(),
1130
1414
  manual
1131
1415
  };
1132
1416
  job.logs.push(logEntry);
@@ -1170,4 +1454,4 @@ var CronScheduler = class {
1170
1454
  //#endregion
1171
1455
  export { parseEnvBoolean as a, validateCronExpression as i, cron_scheduler_exports as n, isAlreadyExecutingSkip as r, CronScheduler as t };
1172
1456
 
1173
- //# sourceMappingURL=cron-scheduler-B0pLfAix.js.map
1457
+ //# sourceMappingURL=cron-scheduler-C2vbETMo.js.map