@geekmidas/constructs 10.0.0-alpha.1 → 10.0.0-alpha.100
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.
- package/README.md +223 -158
- package/dist/{Construct-CdhThxDI.mjs → Construct-B1vGBPme.mjs} +45 -33
- package/dist/Construct-B1vGBPme.mjs.map +1 -0
- package/dist/Construct-BQALFCh2.d.cts +230 -0
- package/dist/Construct-BQALFCh2.d.cts.map +1 -0
- package/dist/{Construct-roId57cU.cjs → Construct-DUSXuTHP.cjs} +65 -47
- package/dist/Construct-DUSXuTHP.cjs.map +1 -0
- package/dist/Construct-QXnl3VqT.d.mts +230 -0
- package/dist/Construct-QXnl3VqT.d.mts.map +1 -0
- package/dist/Cron-DI3nKoic.d.cts +45 -0
- package/dist/Cron-DI3nKoic.d.cts.map +1 -0
- package/dist/Cron-uYAHPnp7.d.mts +45 -0
- package/dist/Cron-uYAHPnp7.d.mts.map +1 -0
- package/dist/CronBuilder-C0Ek1-RA.cjs +121 -0
- package/dist/CronBuilder-C0Ek1-RA.cjs.map +1 -0
- package/dist/CronBuilder-DaSUm2Na.d.mts +47 -0
- package/dist/CronBuilder-DaSUm2Na.d.mts.map +1 -0
- package/dist/CronBuilder-Dmix_Pen.mjs +109 -0
- package/dist/CronBuilder-Dmix_Pen.mjs.map +1 -0
- package/dist/CronBuilder-g1lZLWyG.d.cts +47 -0
- package/dist/CronBuilder-g1lZLWyG.d.cts.map +1 -0
- package/dist/{Endpoint-BTj58Gob.mjs → Endpoint-BG4UJ9oA.mjs} +146 -109
- package/dist/Endpoint-BG4UJ9oA.mjs.map +1 -0
- package/dist/{Endpoint-BBkV6l0k.cjs → Endpoint-BoUg8ocM.cjs} +174 -131
- package/dist/Endpoint-BoUg8ocM.cjs.map +1 -0
- package/dist/{Endpoint-BKR5QH6O.d.mts → Endpoint-Du-NwPX2.d.mts} +83 -91
- package/dist/Endpoint-Du-NwPX2.d.mts.map +1 -0
- package/dist/{Endpoint-BQ9O0P1w.d.cts → Endpoint-aCvBMPoH.d.cts} +82 -90
- package/dist/Endpoint-aCvBMPoH.d.cts.map +1 -0
- package/dist/{EndpointFactory-BejSTzbB.mjs → EndpointFactory-BDtsrEvJ.mjs} +60 -77
- package/dist/EndpointFactory-BDtsrEvJ.mjs.map +1 -0
- package/dist/{EndpointFactory-DYoGLaIT.cjs → EndpointFactory-DJ7gXLpR.cjs} +96 -113
- package/dist/EndpointFactory-DJ7gXLpR.cjs.map +1 -0
- package/dist/{index-D5zqAJ2e.d.cts → EndpointFactory-DMYdJCOj.d.mts} +78 -134
- package/dist/EndpointFactory-DMYdJCOj.d.mts.map +1 -0
- package/dist/{index-Cvz551AO.d.mts → EndpointFactory-I7s1co3Y.d.cts} +79 -135
- package/dist/EndpointFactory-I7s1co3Y.d.cts.map +1 -0
- package/dist/Function-DuqGcbOI.d.mts +76 -0
- package/dist/Function-DuqGcbOI.d.mts.map +1 -0
- package/dist/Function-Pu-1kKZz.d.cts +76 -0
- package/dist/Function-Pu-1kKZz.d.cts.map +1 -0
- package/dist/{HonoEndpointAdaptor-7aYNdqJq.mjs → HonoEndpointAdaptor-PtPlbeKY.mjs} +75 -57
- package/dist/HonoEndpointAdaptor-PtPlbeKY.mjs.map +1 -0
- package/dist/{HonoEndpointAdaptor-wMsCyMgt.cjs → HonoEndpointAdaptor-zVzVLJEO.cjs} +83 -62
- package/dist/HonoEndpointAdaptor-zVzVLJEO.cjs.map +1 -0
- package/dist/Queue-A3wfD0it.cjs +109 -0
- package/dist/Queue-A3wfD0it.cjs.map +1 -0
- package/dist/Queue-D6USGS4q.mjs +104 -0
- package/dist/Queue-D6USGS4q.mjs.map +1 -0
- package/dist/{Queue-D6WeziRD.d.cts → Queue-DlsHRzUp.d.mts} +48 -48
- package/dist/Queue-DlsHRzUp.d.mts.map +1 -0
- package/dist/{Queue-DpUeqdC8.d.mts → Queue-HPyi0e1i.d.cts} +49 -49
- package/dist/Queue-HPyi0e1i.d.cts.map +1 -0
- package/dist/QueueBuilder-BjuF0UBl.cjs +135 -0
- package/dist/QueueBuilder-BjuF0UBl.cjs.map +1 -0
- package/dist/QueueBuilder-Cue73M4t.mjs +118 -0
- package/dist/QueueBuilder-Cue73M4t.mjs.map +1 -0
- package/dist/QueueBuilder-OSi6LkV9.d.cts +75 -0
- package/dist/QueueBuilder-OSi6LkV9.d.cts.map +1 -0
- package/dist/QueueBuilder-QHsnVt1P.d.mts +75 -0
- package/dist/QueueBuilder-QHsnVt1P.d.mts.map +1 -0
- package/dist/{Subscriber-4rBJgDKI.d.mts → Subscriber-Bpp02cGr.d.cts} +30 -21
- package/dist/Subscriber-Bpp02cGr.d.cts.map +1 -0
- package/dist/{Subscriber-DVQowdyj.d.cts → Subscriber-CtN9R48-.d.mts} +29 -20
- package/dist/Subscriber-CtN9R48-.d.mts.map +1 -0
- package/dist/SubscriberBuilder-CDy4nHCz.mjs +134 -0
- package/dist/SubscriberBuilder-CDy4nHCz.mjs.map +1 -0
- package/dist/SubscriberBuilder-CuCMFpYW.d.mts +67 -0
- package/dist/SubscriberBuilder-CuCMFpYW.d.mts.map +1 -0
- package/dist/SubscriberBuilder-D_owVoHo.cjs +145 -0
- package/dist/SubscriberBuilder-D_owVoHo.cjs.map +1 -0
- package/dist/SubscriberBuilder-g3fAVWgs.d.cts +67 -0
- package/dist/SubscriberBuilder-g3fAVWgs.d.cts.map +1 -0
- package/dist/Topic-D74b5cgs.mjs +99 -0
- package/dist/Topic-D74b5cgs.mjs.map +1 -0
- package/dist/Topic-GHxcU8AG.cjs +104 -0
- package/dist/Topic-GHxcU8AG.cjs.map +1 -0
- package/dist/adaptors/aws.cjs +332 -188
- package/dist/adaptors/aws.cjs.map +1 -1
- package/dist/adaptors/aws.d.cts +114 -44
- package/dist/adaptors/aws.d.cts.map +1 -1
- package/dist/adaptors/aws.d.mts +114 -44
- package/dist/adaptors/aws.d.mts.map +1 -1
- package/dist/adaptors/aws.mjs +308 -166
- package/dist/adaptors/aws.mjs.map +1 -1
- package/dist/adaptors/hono.cjs +3 -11
- package/dist/adaptors/hono.d.cts +7 -16
- package/dist/adaptors/hono.d.cts.map +1 -1
- package/dist/adaptors/hono.d.mts +8 -17
- package/dist/adaptors/hono.d.mts.map +1 -1
- package/dist/adaptors/hono.mjs +2 -11
- package/dist/adaptors/testing.cjs +743 -90
- package/dist/adaptors/testing.cjs.map +1 -1
- package/dist/adaptors/testing.d.cts +351 -54
- package/dist/adaptors/testing.d.cts.map +1 -1
- package/dist/adaptors/testing.d.mts +351 -54
- package/dist/adaptors/testing.d.mts.map +1 -1
- package/dist/adaptors/testing.mjs +706 -71
- package/dist/adaptors/testing.mjs.map +1 -1
- package/dist/auth-BMCJN30Y.d.cts +268 -0
- package/dist/auth-BMCJN30Y.d.cts.map +1 -0
- package/dist/auth-CcuceLYo.d.mts +268 -0
- package/dist/auth-CcuceLYo.d.mts.map +1 -0
- package/dist/auth.cjs +302 -34
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -108
- package/dist/auth.d.mts +2 -108
- package/dist/auth.mjs +290 -28
- package/dist/auth.mjs.map +1 -1
- package/dist/{cache-CdQ7OLpR.d.cts → cache-CXh6GIgb.d.mts} +4 -7
- package/dist/cache-CXh6GIgb.d.mts.map +1 -0
- package/dist/{cache-DYoJFcfd.d.mts → cache-DnGDGly3.d.cts} +4 -7
- package/dist/cache-DnGDGly3.d.cts.map +1 -0
- package/dist/cache.cjs +68 -2
- package/dist/cache.cjs.map +1 -0
- package/dist/cache.d.cts +1 -2
- package/dist/cache.d.mts +1 -2
- package/dist/cache.mjs +67 -2
- package/dist/cache.mjs.map +1 -0
- package/dist/cipher-C1lEFX63.mjs +225 -0
- package/dist/cipher-C1lEFX63.mjs.map +1 -0
- package/dist/cipher-CVxfwLjk.cjs +332 -0
- package/dist/cipher-CVxfwLjk.cjs.map +1 -0
- package/dist/{clone-DblgEZZa.mjs → clone-EKNovYHu.mjs} +3 -3
- package/dist/clone-EKNovYHu.mjs.map +1 -0
- package/dist/{clone-Dy0cR2SZ.cjs → clone-UGJpvQnL.cjs} +7 -8
- package/dist/clone-UGJpvQnL.cjs.map +1 -0
- package/dist/{construct-interface-BqwRNYA2.d.mts → construct-interface-B1AfqRl4.d.cts} +43 -12
- package/dist/construct-interface-B1AfqRl4.d.cts.map +1 -0
- package/dist/{construct-interface-D2nNkHUN.d.cts → construct-interface-DluK3_da.d.mts} +43 -12
- package/dist/construct-interface-DluK3_da.d.mts.map +1 -0
- package/dist/construct-interface.cjs +113 -7
- package/dist/construct-interface.cjs.map +1 -0
- package/dist/construct-interface.d.cts +2 -2
- package/dist/construct-interface.d.mts +2 -2
- package/dist/construct-interface.mjs +106 -2
- package/dist/construct-interface.mjs.map +1 -0
- package/dist/credential.cjs +77 -17
- package/dist/credential.cjs.map +1 -1
- package/dist/credential.d.cts +34 -9
- package/dist/credential.d.cts.map +1 -1
- package/dist/credential.d.mts +34 -9
- package/dist/credential.d.mts.map +1 -1
- package/dist/credential.mjs +71 -13
- package/dist/credential.mjs.map +1 -1
- package/dist/crons/index.cjs +189 -85
- package/dist/crons/index.cjs.map +1 -1
- package/dist/crons/index.d.cts +70 -48
- package/dist/crons/index.d.cts.map +1 -1
- package/dist/crons/index.d.mts +70 -48
- package/dist/crons/index.d.mts.map +1 -1
- package/dist/crons/index.mjs +183 -81
- package/dist/crons/index.mjs.map +1 -1
- package/dist/database/kysely.cjs +4 -181
- package/dist/database/kysely.d.cts +2 -152
- package/dist/database/kysely.d.mts +2 -152
- package/dist/database/kysely.mjs +2 -180
- package/dist/email.cjs +25 -12
- package/dist/email.cjs.map +1 -1
- package/dist/email.d.cts +3 -7
- package/dist/email.d.cts.map +1 -1
- package/dist/email.d.mts +3 -7
- package/dist/email.d.mts.map +1 -1
- package/dist/email.mjs +16 -3
- package/dist/email.mjs.map +1 -1
- package/dist/encryption.cjs +96 -0
- package/dist/encryption.cjs.map +1 -0
- package/dist/encryption.d.cts +117 -0
- package/dist/encryption.d.cts.map +1 -0
- package/dist/encryption.d.mts +117 -0
- package/dist/encryption.d.mts.map +1 -0
- package/dist/encryption.mjs +83 -0
- package/dist/encryption.mjs.map +1 -0
- package/dist/endpoints/index.cjs +8 -11
- package/dist/endpoints/index.d.cts +51 -7
- package/dist/endpoints/index.d.cts.map +1 -0
- package/dist/endpoints/index.d.mts +51 -7
- package/dist/endpoints/index.d.mts.map +1 -0
- package/dist/endpoints/index.mjs +6 -11
- package/dist/external-api-MJ8id87V.d.mts +105 -0
- package/dist/external-api-MJ8id87V.d.mts.map +1 -0
- package/dist/external-api-O107yzZJ.d.cts +105 -0
- package/dist/external-api-O107yzZJ.d.cts.map +1 -0
- package/dist/external-api.cjs +142 -0
- package/dist/external-api.cjs.map +1 -0
- package/dist/external-api.d.cts +2 -0
- package/dist/external-api.d.mts +2 -0
- package/dist/external-api.mjs +139 -0
- package/dist/external-api.mjs.map +1 -0
- package/dist/file-server.cjs +42 -10
- package/dist/file-server.cjs.map +1 -1
- package/dist/file-server.d.cts +6 -10
- package/dist/file-server.d.cts.map +1 -1
- package/dist/file-server.d.mts +6 -10
- package/dist/file-server.d.mts.map +1 -1
- package/dist/file-server.mjs +34 -2
- package/dist/file-server.mjs.map +1 -1
- package/dist/functions/index.cjs +2 -6
- package/dist/functions/index.d.cts +3 -4
- package/dist/functions/index.d.mts +3 -4
- package/dist/functions/index.mjs +2 -6
- package/dist/{functions-Cpff1uZH.mjs → functions-BFizikJU.mjs} +394 -243
- package/dist/functions-BFizikJU.mjs.map +1 -0
- package/dist/{functions-DukjG8ov.cjs → functions-CfzJG9hK.cjs} +432 -275
- package/dist/functions-CfzJG9hK.cjs.map +1 -0
- package/dist/index-BuzRvKOQ.d.cts +115 -0
- package/dist/index-BuzRvKOQ.d.cts.map +1 -0
- package/dist/index-CTkKOer4.d.mts +115 -0
- package/dist/index-CTkKOer4.d.mts.map +1 -0
- package/dist/index-CeEtxDmK.d.cts +79 -0
- package/dist/index-CeEtxDmK.d.cts.map +1 -0
- package/dist/index-CeEtxDmK.d.mts +79 -0
- package/dist/index-CeEtxDmK.d.mts.map +1 -0
- package/dist/index.cjs +6 -3
- package/dist/index.d.cts +27 -4
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +27 -4
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -3
- package/dist/kms-B-Hm3d5r.cjs +85 -0
- package/dist/kms-B-Hm3d5r.cjs.map +1 -0
- package/dist/kms-BdUlMJEZ.mjs +85 -0
- package/dist/kms-BdUlMJEZ.mjs.map +1 -0
- package/dist/kysely-Bii4l6eP.mjs +370 -0
- package/dist/kysely-Bii4l6eP.mjs.map +1 -0
- package/dist/kysely-CO_Dk43W.cjs +383 -0
- package/dist/kysely-CO_Dk43W.cjs.map +1 -0
- package/dist/kysely-D0bpzgPs.d.cts +175 -0
- package/dist/kysely-D0bpzgPs.d.cts.map +1 -0
- package/dist/kysely-DUqC3Ie0.d.mts +175 -0
- package/dist/kysely-DUqC3Ie0.d.mts.map +1 -0
- package/dist/{telemetry-C3L0iS28.d.cts → lambdaTelemetry-8vnmVZQ5.d.cts} +3 -6
- package/dist/lambdaTelemetry-8vnmVZQ5.d.cts.map +1 -0
- package/dist/{telemetry-D7bVFLGi.d.mts → lambdaTelemetry-8vnmVZQ5.d.mts} +3 -6
- package/dist/lambdaTelemetry-8vnmVZQ5.d.mts.map +1 -0
- package/dist/{lazyAccessors-DM6HcXQk.mjs → lazyAccessors-CFanua0O.mjs} +20 -21
- package/dist/lazyAccessors-CFanua0O.mjs.map +1 -0
- package/dist/{lazyAccessors-DrJj8NpA.cjs → lazyAccessors-hJKGv8Ay.cjs} +60 -62
- package/dist/lazyAccessors-hJKGv8Ay.cjs.map +1 -0
- package/dist/mobile-app.cjs +73 -0
- package/dist/mobile-app.cjs.map +1 -0
- package/dist/mobile-app.d.cts +61 -0
- package/dist/mobile-app.d.cts.map +1 -0
- package/dist/mobile-app.d.mts +61 -0
- package/dist/mobile-app.d.mts.map +1 -0
- package/dist/mobile-app.mjs +72 -0
- package/dist/mobile-app.mjs.map +1 -0
- package/dist/object-storage.cjs +18 -10
- package/dist/object-storage.cjs.map +1 -1
- package/dist/object-storage.d.cts +3 -7
- package/dist/object-storage.d.cts.map +1 -1
- package/dist/object-storage.d.mts +3 -7
- package/dist/object-storage.d.mts.map +1 -1
- package/dist/object-storage.mjs +11 -3
- package/dist/object-storage.mjs.map +1 -1
- package/dist/processAudits-BRRVVHUB.mjs +131 -0
- package/dist/processAudits-BRRVVHUB.mjs.map +1 -0
- package/dist/processAudits-DasOZjPl.cjs +154 -0
- package/dist/processAudits-DasOZjPl.cjs.map +1 -0
- package/dist/queue/index.cjs +6 -189
- package/dist/queue/index.d.cts +3 -60
- package/dist/queue/index.d.mts +3 -60
- package/dist/queue/index.mjs +3 -186
- package/dist/rest-api.cjs +92 -63
- package/dist/rest-api.cjs.map +1 -1
- package/dist/rest-api.d.cts +81 -61
- package/dist/rest-api.d.cts.map +1 -1
- package/dist/rest-api.d.mts +81 -61
- package/dist/rest-api.d.mts.map +1 -1
- package/dist/rest-api.mjs +84 -55
- package/dist/rest-api.mjs.map +1 -1
- package/dist/{chunk-DWy1uDak.cjs → rolldown-runtime-y5ARvAq5.cjs} +14 -18
- package/dist/runSubscriber-BHvJ8POr.mjs +109 -0
- package/dist/runSubscriber-BHvJ8POr.mjs.map +1 -0
- package/dist/runSubscriber-D8nLXIv1.cjs +126 -0
- package/dist/runSubscriber-D8nLXIv1.cjs.map +1 -0
- package/dist/shutdown-BMJmAhSt.cjs +38 -0
- package/dist/shutdown-BMJmAhSt.cjs.map +1 -0
- package/dist/shutdown-C7xa8x9S.mjs +27 -0
- package/dist/shutdown-C7xa8x9S.mjs.map +1 -0
- package/dist/site.cjs +35 -9
- package/dist/site.cjs.map +1 -1
- package/dist/site.d.cts +25 -22
- package/dist/site.d.cts.map +1 -1
- package/dist/site.d.mts +25 -22
- package/dist/site.d.mts.map +1 -1
- package/dist/site.mjs +31 -5
- package/dist/site.mjs.map +1 -1
- package/dist/stages-BSCF19o2.d.cts +56 -0
- package/dist/stages-BSCF19o2.d.cts.map +1 -0
- package/dist/stages-BSCF19o2.d.mts +56 -0
- package/dist/stages-BSCF19o2.d.mts.map +1 -0
- package/dist/subscribers/index.cjs +111 -101
- package/dist/subscribers/index.cjs.map +1 -1
- package/dist/subscribers/index.d.cts +44 -56
- package/dist/subscribers/index.d.cts.map +1 -1
- package/dist/subscribers/index.d.mts +44 -56
- package/dist/subscribers/index.d.mts.map +1 -1
- package/dist/subscribers/index.mjs +108 -98
- package/dist/subscribers/index.mjs.map +1 -1
- package/dist/surfaceEnv-CNimK76p.cjs +56 -0
- package/dist/surfaceEnv-CNimK76p.cjs.map +1 -0
- package/dist/surfaceEnv-wMT5QzgZ.mjs +45 -0
- package/dist/surfaceEnv-wMT5QzgZ.mjs.map +1 -0
- package/dist/telemetry-B48RLjyk.d.cts +40 -0
- package/dist/telemetry-B48RLjyk.d.cts.map +1 -0
- package/dist/telemetry-DRtT1_0A.d.mts +40 -0
- package/dist/telemetry-DRtT1_0A.d.mts.map +1 -0
- package/dist/telemetry.cjs +56 -0
- package/dist/telemetry.cjs.map +1 -0
- package/dist/telemetry.d.cts +2 -0
- package/dist/telemetry.d.mts +2 -0
- package/dist/telemetry.mjs +55 -0
- package/dist/telemetry.mjs.map +1 -0
- package/dist/topic/index.cjs +3 -137
- package/dist/topic/index.d.cts +2 -32
- package/dist/topic/index.d.mts +2 -32
- package/dist/topic/index.mjs +2 -134
- package/dist/tracing-CxaZ98IB.mjs +110 -0
- package/dist/tracing-CxaZ98IB.mjs.map +1 -0
- package/dist/tracing-DPMidF_r.cjs +127 -0
- package/dist/tracing-DPMidF_r.cjs.map +1 -0
- package/dist/{types-BlAkSuGI.d.cts → types-DMuSQeLL.d.cts} +3 -5
- package/dist/types-DMuSQeLL.d.cts.map +1 -0
- package/dist/{types-aKksMEWL.d.mts → types-DMuSQeLL.d.mts} +3 -5
- package/dist/types-DMuSQeLL.d.mts.map +1 -0
- package/dist/types.d.cts +1 -1
- package/dist/types.d.mts +1 -1
- package/dist/types.mjs +1 -0
- package/dist/worker.cjs +191 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +171 -0
- package/dist/worker.d.cts.map +1 -0
- package/dist/worker.d.mts +171 -0
- package/dist/worker.d.mts.map +1 -0
- package/dist/worker.mjs +190 -0
- package/dist/worker.mjs.map +1 -0
- package/package.json +116 -70
- package/dist/AWSScheduledFunction-9bJS4WdJ.d.cts +0 -122
- package/dist/AWSScheduledFunction-9bJS4WdJ.d.cts.map +0 -1
- package/dist/AWSScheduledFunction-BJjj8B5V.mjs +0 -243
- package/dist/AWSScheduledFunction-BJjj8B5V.mjs.map +0 -1
- package/dist/AWSScheduledFunction-BO5iEMSh.d.mts +0 -122
- package/dist/AWSScheduledFunction-BO5iEMSh.d.mts.map +0 -1
- package/dist/AWSScheduledFunction-qu4ieRZR.cjs +0 -255
- package/dist/AWSScheduledFunction-qu4ieRZR.cjs.map +0 -1
- package/dist/Construct-B-xc0SE9.d.mts +0 -104
- package/dist/Construct-B-xc0SE9.d.mts.map +0 -1
- package/dist/Construct-CdhThxDI.mjs.map +0 -1
- package/dist/Construct-WhCXIOPV.d.cts +0 -104
- package/dist/Construct-WhCXIOPV.d.cts.map +0 -1
- package/dist/Construct-roId57cU.cjs.map +0 -1
- package/dist/Endpoint-BBkV6l0k.cjs.map +0 -1
- package/dist/Endpoint-BKR5QH6O.d.mts.map +0 -1
- package/dist/Endpoint-BQ9O0P1w.d.cts.map +0 -1
- package/dist/Endpoint-BTj58Gob.mjs.map +0 -1
- package/dist/EndpointFactory-BejSTzbB.mjs.map +0 -1
- package/dist/EndpointFactory-DYoGLaIT.cjs.map +0 -1
- package/dist/HonoEndpointAdaptor-7aYNdqJq.mjs.map +0 -1
- package/dist/HonoEndpointAdaptor-wMsCyMgt.cjs.map +0 -1
- package/dist/Queue-D6WeziRD.d.cts.map +0 -1
- package/dist/Queue-DpUeqdC8.d.mts.map +0 -1
- package/dist/Subscriber-4rBJgDKI.d.mts.map +0 -1
- package/dist/Subscriber-DVQowdyj.d.cts.map +0 -1
- package/dist/Topic-Ohx9dvWr.d.cts +0 -91
- package/dist/Topic-Ohx9dvWr.d.cts.map +0 -1
- package/dist/Topic-cmy2jOUy.d.mts +0 -91
- package/dist/Topic-cmy2jOUy.d.mts.map +0 -1
- package/dist/auth.d.cts.map +0 -1
- package/dist/auth.d.mts.map +0 -1
- package/dist/cache-0ANvRQnD.mjs +0 -51
- package/dist/cache-0ANvRQnD.mjs.map +0 -1
- package/dist/cache-3WSvAulx.cjs +0 -57
- package/dist/cache-3WSvAulx.cjs.map +0 -1
- package/dist/cache-CdQ7OLpR.d.cts.map +0 -1
- package/dist/cache-DYoJFcfd.d.mts.map +0 -1
- package/dist/clone-DblgEZZa.mjs.map +0 -1
- package/dist/clone-Dy0cR2SZ.cjs.map +0 -1
- package/dist/construct-interface-BfjcOD_E.cjs +0 -117
- package/dist/construct-interface-BfjcOD_E.cjs.map +0 -1
- package/dist/construct-interface-BqwRNYA2.d.mts.map +0 -1
- package/dist/construct-interface-D2nNkHUN.d.cts.map +0 -1
- package/dist/construct-interface-Z4CpS8tK.mjs +0 -81
- package/dist/construct-interface-Z4CpS8tK.mjs.map +0 -1
- package/dist/database/kysely.cjs.map +0 -1
- package/dist/database/kysely.d.cts.map +0 -1
- package/dist/database/kysely.d.mts.map +0 -1
- package/dist/database/kysely.mjs.map +0 -1
- package/dist/functions-Cpff1uZH.mjs.map +0 -1
- package/dist/functions-DukjG8ov.cjs.map +0 -1
- package/dist/index-BTvozlUD.d.cts +0 -163
- package/dist/index-BTvozlUD.d.cts.map +0 -1
- package/dist/index-Cvz551AO.d.mts.map +0 -1
- package/dist/index-D5zqAJ2e.d.cts.map +0 -1
- package/dist/index-L7pz0R0T.d.mts +0 -162
- package/dist/index-L7pz0R0T.d.mts.map +0 -1
- package/dist/lazyAccessors-DM6HcXQk.mjs.map +0 -1
- package/dist/lazyAccessors-DrJj8NpA.cjs.map +0 -1
- package/dist/processAudits-Bc8rz6Ev.cjs +0 -92
- package/dist/processAudits-Bc8rz6Ev.cjs.map +0 -1
- package/dist/processAudits-D89tICM4.mjs +0 -80
- package/dist/processAudits-D89tICM4.mjs.map +0 -1
- package/dist/publisher-CGEo8UKD.mjs +0 -41
- package/dist/publisher-CGEo8UKD.mjs.map +0 -1
- package/dist/publisher-CqGsXB2X.cjs +0 -53
- package/dist/publisher-CqGsXB2X.cjs.map +0 -1
- package/dist/queue/index.cjs.map +0 -1
- package/dist/queue/index.d.cts.map +0 -1
- package/dist/queue/index.d.mts.map +0 -1
- package/dist/queue/index.mjs.map +0 -1
- package/dist/surfaceEnv-BFXEms2f.cjs +0 -39
- package/dist/surfaceEnv-BFXEms2f.cjs.map +0 -1
- package/dist/surfaceEnv-C3Ktahea.mjs +0 -27
- package/dist/surfaceEnv-C3Ktahea.mjs.map +0 -1
- package/dist/telemetry-C3L0iS28.d.cts.map +0 -1
- package/dist/telemetry-D7bVFLGi.d.mts.map +0 -1
- package/dist/topic/index.cjs.map +0 -1
- package/dist/topic/index.d.cts.map +0 -1
- package/dist/topic/index.d.mts.map +0 -1
- package/dist/topic/index.mjs.map +0 -1
- package/dist/types-BlAkSuGI.d.cts.map +0 -1
- package/dist/types-aKksMEWL.d.mts.map +0 -1
package/dist/credential.d.mts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as StandardSchemaV1 } from "./index-CeEtxDmK.mjs";
|
|
2
|
+
import { r as Consumable } from "./construct-interface-DluK3_da.mjs";
|
|
2
3
|
import { ConstructName, Declaration } from "@geekmidas/manifest";
|
|
3
|
-
import { Service } from "@geekmidas/services";
|
|
4
|
-
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
|
-
|
|
4
|
+
import { Service, ServiceRegisterOptions } from "@geekmidas/services";
|
|
6
5
|
//#region src/credential.d.ts
|
|
7
|
-
|
|
8
6
|
/** What a credential's schema yields. */
|
|
9
7
|
type Value<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>;
|
|
10
|
-
interface CredentialOptions<TSchema extends StandardSchemaV1> {
|
|
8
|
+
export interface CredentialOptions<TSchema extends StandardSchemaV1> {
|
|
11
9
|
/**
|
|
12
10
|
* What the credential must look like.
|
|
13
11
|
*
|
|
@@ -27,7 +25,7 @@ interface CredentialOptions<TSchema extends StandardSchemaV1> {
|
|
|
27
25
|
*/
|
|
28
26
|
refresh?: boolean;
|
|
29
27
|
}
|
|
30
|
-
declare class Credential<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> implements Consumable<TName, Value<TSchema>> {
|
|
28
|
+
export declare class Credential<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> implements Consumable<TName, Value<TSchema>> {
|
|
31
29
|
private readonly options;
|
|
32
30
|
readonly id: TName;
|
|
33
31
|
readonly service: Service<Uncapitalize<TName>, Value<TSchema>>;
|
|
@@ -45,17 +43,44 @@ declare class Credential<TName extends string = string, TSchema extends Standard
|
|
|
45
43
|
*/
|
|
46
44
|
private resolved?;
|
|
47
45
|
constructor(id: ConstructName<TName>, options: CredentialOptions<TSchema>);
|
|
46
|
+
/**
|
|
47
|
+
* What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
|
|
48
|
+
* is validated against where it is read. Public so a tool setting the value
|
|
49
|
+
* (`gkm secrets:add`) can check it, and describe its fields, before it is
|
|
50
|
+
* stored rather than at the first request.
|
|
51
|
+
*/
|
|
52
|
+
get credentialsSchema(): TSchema;
|
|
48
53
|
declare(): Declaration[];
|
|
49
54
|
private connect;
|
|
50
55
|
private resolve;
|
|
51
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* A credentials key, read and validated against its schema.
|
|
59
|
+
*
|
|
60
|
+
* Shared with every construct that holds a third party's credentials — an
|
|
61
|
+
* `ExternalApi` reads its own the same way — so the shape a stage stores and
|
|
62
|
+
* the error a bad value raises are the same everywhere.
|
|
63
|
+
*
|
|
64
|
+
* @throws {MalformedCredential} when the value is not what the schema says.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readCredentials<TSchema extends StandardSchemaV1>(id: string, key: string, schema: TSchema, envParser: ServiceRegisterOptions['envParser']): Promise<Value<TSchema>>;
|
|
67
|
+
/**
|
|
68
|
+
* The raw environment value as the thing the schema should see.
|
|
69
|
+
*
|
|
70
|
+
* JSON when it is JSON, and the string itself when it is not — a credential
|
|
71
|
+
* with a single opaque field is ordinary, and requiring `"…"` around it would
|
|
72
|
+
* be ceremony that every operator setting one by hand would get wrong once.
|
|
73
|
+
*
|
|
74
|
+
* Exported so a tool checking a value before it is stored reads it exactly as
|
|
75
|
+
* the process that uses it will.
|
|
76
|
+
*/
|
|
77
|
+
export declare function decodeCredentials(raw: string): unknown;
|
|
52
78
|
/** A credential was set, and is not what its schema says it should be. */
|
|
53
|
-
declare class MalformedCredential extends Error {
|
|
79
|
+
export declare class MalformedCredential extends Error {
|
|
54
80
|
readonly id: string;
|
|
55
81
|
readonly key: string;
|
|
56
82
|
readonly issues: unknown;
|
|
57
83
|
constructor(id: string, key: string, issues: unknown);
|
|
58
84
|
}
|
|
59
85
|
//#endregion
|
|
60
|
-
export { Credential, CredentialOptions, MalformedCredential };
|
|
61
86
|
//# sourceMappingURL=credential.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential.d.mts","names":[],"sources":["../src/credential.ts"],"
|
|
1
|
+
{"version":3,"file":"credential.d.mts","names":[],"sources":["../src/credential.ts"],"mappings":";;;;;;KA6CK,MAAM,gBAAgB,oBAC1B,iBAAiB,YAAY;iBAEb,kBAAkB,gBAAgB;;;;;;;;EAQlD,QAAQ;;;;;;;;;;EAUR;;qBAGY,WACZ,+BACA,gBAAgB,mBAAmB,6BACvB,WAAW,OAAO,MAAM;mBA6BlB;WApBT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ,MAAM;;;;;mBAMpC;;;;;;;;UAST;EAER,YACC,IAAI,cAAc,QACD,SAAS,kBAAkB;;;;;;;MAoBzC,qBAAqB;EAIzB,WAAW;UAUH;UAQA;;;;;;;;;;;wBAmBa,gBAAgB,gBAAgB,kBACrD,YACA,aACA,QAAQ,SACR,WAAW,sCACT,QAAQ,MAAM;;;;;;;;;;;wBAsBD,kBAAkB;;qBAoBrB,4BAA4B;WAE9B;WACA;WACA;EAHV,YACU,YACA,aACA"}
|
package/dist/credential.mjs
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { canonicalId, provideKey, serviceKey } from "@geekmidas/manifest";
|
|
2
2
|
import { parseSchema } from "@geekmidas/schema/parser";
|
|
3
|
-
|
|
4
3
|
//#region src/credential.ts
|
|
4
|
+
/**
|
|
5
|
+
* `Credential` — a third-party credential with a shape.
|
|
6
|
+
*
|
|
7
|
+
* The answer to the design's first open question, "where does `Secret` sit?".
|
|
8
|
+
* It sits beside a secret rather than replacing it, because the two differ by
|
|
9
|
+
* *lifecycle* and that is the only difference worth two kinds for: a secret is
|
|
10
|
+
* generated and rotated by the platform and is one opaque string, while a
|
|
11
|
+
* credential is issued by someone else, arrives with several fields, and is
|
|
12
|
+
* worth validating on the way in.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* export const stripe = new Credential('Stripe', {
|
|
16
|
+
* schema: z.object({ secretKey: z.string(), webhookSecret: z.string() }),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // in a handler — no await, already parsed and validated
|
|
20
|
+
* services.stripe.secretKey
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* **Nothing about it is async at the call site**, which is the part that looks
|
|
24
|
+
* like it should be hard and is not. `Service.register()` may return a promise
|
|
25
|
+
* and `ServiceDiscovery` awaits every one before a handler runs, so fetching a
|
|
26
|
+
* value and validating it — including when a StandardSchema's `~standard.validate`
|
|
27
|
+
* returns a promise of its own — both happen inside that existing seam.
|
|
28
|
+
*
|
|
29
|
+
* **One key holding JSON, not one key per field.** That is the shape a secret
|
|
30
|
+
* manager stores, and it is the only shape that works with an arbitrary
|
|
31
|
+
* StandardSchema: the spec has no introspection API, so enumerating a schema's
|
|
32
|
+
* fields means reaching into one library's internals (`.shape`) and being wrong
|
|
33
|
+
* for every other.
|
|
34
|
+
*/
|
|
5
35
|
var Credential = class {
|
|
36
|
+
options;
|
|
6
37
|
id;
|
|
7
38
|
service;
|
|
8
39
|
/**
|
|
@@ -22,17 +53,26 @@ var Credential = class {
|
|
|
22
53
|
this.options = options;
|
|
23
54
|
const canonical = canonicalId(id);
|
|
24
55
|
this.id = canonical;
|
|
25
|
-
this.keys = {
|
|
56
|
+
this.keys = { credentials: provideKey(canonical, "credentials") };
|
|
26
57
|
this.service = {
|
|
27
58
|
serviceName: serviceKey(canonical),
|
|
28
59
|
register: (registerOptions) => this.connect(registerOptions)
|
|
29
60
|
};
|
|
30
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
|
|
64
|
+
* is validated against where it is read. Public so a tool setting the value
|
|
65
|
+
* (`gkm secrets:add`) can check it, and describe its fields, before it is
|
|
66
|
+
* stored rather than at the first request.
|
|
67
|
+
*/
|
|
68
|
+
get credentialsSchema() {
|
|
69
|
+
return this.options.schema;
|
|
70
|
+
}
|
|
31
71
|
declare() {
|
|
32
72
|
return [{
|
|
33
73
|
kind: "credential",
|
|
34
74
|
id: this.id,
|
|
35
|
-
provides: [this.keys.
|
|
75
|
+
provides: [this.keys.credentials]
|
|
36
76
|
}];
|
|
37
77
|
}
|
|
38
78
|
connect(options) {
|
|
@@ -40,23 +80,38 @@ var Credential = class {
|
|
|
40
80
|
this.resolved ??= this.resolve(options);
|
|
41
81
|
return this.resolved;
|
|
42
82
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
return await parseSchema(this.options.schema, decode(raw));
|
|
47
|
-
} catch (issues) {
|
|
48
|
-
throw new MalformedCredential(this.id, this.keys.credential, issues);
|
|
49
|
-
}
|
|
83
|
+
resolve(options) {
|
|
84
|
+
return readCredentials(this.id, this.keys.credentials, this.options.schema, options.envParser);
|
|
50
85
|
}
|
|
51
86
|
};
|
|
52
87
|
/**
|
|
88
|
+
* A credentials key, read and validated against its schema.
|
|
89
|
+
*
|
|
90
|
+
* Shared with every construct that holds a third party's credentials — an
|
|
91
|
+
* `ExternalApi` reads its own the same way — so the shape a stage stores and
|
|
92
|
+
* the error a bad value raises are the same everywhere.
|
|
93
|
+
*
|
|
94
|
+
* @throws {MalformedCredential} when the value is not what the schema says.
|
|
95
|
+
*/
|
|
96
|
+
async function readCredentials(id, key, schema, envParser) {
|
|
97
|
+
const { raw } = envParser.create((get) => ({ raw: get(key).string() })).parse();
|
|
98
|
+
try {
|
|
99
|
+
return await parseSchema(schema, decodeCredentials(raw));
|
|
100
|
+
} catch (issues) {
|
|
101
|
+
throw new MalformedCredential(id, key, issues);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
53
105
|
* The raw environment value as the thing the schema should see.
|
|
54
106
|
*
|
|
55
107
|
* JSON when it is JSON, and the string itself when it is not — a credential
|
|
56
108
|
* with a single opaque field is ordinary, and requiring `"…"` around it would
|
|
57
109
|
* be ceremony that every operator setting one by hand would get wrong once.
|
|
110
|
+
*
|
|
111
|
+
* Exported so a tool checking a value before it is stored reads it exactly as
|
|
112
|
+
* the process that uses it will.
|
|
58
113
|
*/
|
|
59
|
-
function
|
|
114
|
+
function decodeCredentials(raw) {
|
|
60
115
|
const value = raw.startsWith(JSON_SCHEME) ? raw.slice(JSON_SCHEME.length) : raw;
|
|
61
116
|
try {
|
|
62
117
|
return JSON.parse(value);
|
|
@@ -72,6 +127,9 @@ function decode(raw) {
|
|
|
72
127
|
const JSON_SCHEME = "json:";
|
|
73
128
|
/** A credential was set, and is not what its schema says it should be. */
|
|
74
129
|
var MalformedCredential = class extends Error {
|
|
130
|
+
id;
|
|
131
|
+
key;
|
|
132
|
+
issues;
|
|
75
133
|
constructor(id, key, issues) {
|
|
76
134
|
super(`${key} is not a valid ${id} credential. It is validated where it is read, so this is the value that is set rather than the code that uses it: ${describe(issues)}`);
|
|
77
135
|
this.id = id;
|
|
@@ -88,7 +146,7 @@ function describe(issues) {
|
|
|
88
146
|
return path ? `${path}: ${issue.message}` : issue.message;
|
|
89
147
|
}).join("; ");
|
|
90
148
|
}
|
|
91
|
-
|
|
92
149
|
//#endregion
|
|
93
|
-
export { Credential, MalformedCredential };
|
|
150
|
+
export { Credential, MalformedCredential, decodeCredentials, readCredentials };
|
|
151
|
+
|
|
94
152
|
//# sourceMappingURL=credential.mjs.map
|
package/dist/credential.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential.mjs","names":["id: ConstructName<TName>","options: CredentialOptions<TSchema>","options: ServiceRegisterOptions","raw: string","id: string","key: string","issues: unknown","issue: StandardSchemaV1.Issue"],"sources":["../src/credential.ts"],"sourcesContent":["/**\n * `Credential` — a third-party credential with a shape.\n *\n * The answer to the design's first open question, \"where does `Secret` sit?\".\n * It sits beside a secret rather than replacing it, because the two differ by\n * *lifecycle* and that is the only difference worth two kinds for: a secret is\n * generated and rotated by the platform and is one opaque string, while a\n * credential is issued by someone else, arrives with several fields, and is\n * worth validating on the way in.\n *\n * ```ts\n * export const stripe = new Credential('Stripe', {\n * schema: z.object({ secretKey: z.string(), webhookSecret: z.string() }),\n * });\n *\n * // in a handler — no await, already parsed and validated\n * services.stripe.secretKey\n * ```\n *\n * **Nothing about it is async at the call site**, which is the part that looks\n * like it should be hard and is not. `Service.register()` may return a promise\n * and `ServiceDiscovery` awaits every one before a handler runs, so fetching a\n * value and validating it — including when a StandardSchema's `~standard.validate`\n * returns a promise of its own — both happen inside that existing seam.\n *\n * **One key holding JSON, not one key per field.** That is the shape a secret\n * manager stores, and it is the only shape that works with an arbitrary\n * StandardSchema: the spec has no introspection API, so enumerating a schema's\n * fields means reaching into one library's internals (`.shape`) and being wrong\n * for every other.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport { parseSchema } from '@geekmidas/schema/parser';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Consumable } from './construct-interface';\n\n/** What a credential's schema yields. */\ntype Value<TSchema extends StandardSchemaV1> =\n\tStandardSchemaV1.InferOutput<TSchema>;\n\nexport interface CredentialOptions<TSchema extends StandardSchemaV1> {\n\t/**\n\t * What the credential must look like.\n\t *\n\t * Validated at registration, so a malformed or half-set credential fails\n\t * when the process starts rather than on the first request that needs the\n\t * one field somebody forgot.\n\t */\n\tschema: TSchema;\n\t/**\n\t * Re-resolve on every registration instead of once per process.\n\t *\n\t * Off by default: a credential is resolved once and reused, because the\n\t * alternative is a fetch on every request. The cost of that default is that\n\t * a rotated credential needs a restart, which is the right trade for a value\n\t * that changes a few times a year — and this is the escape hatch for when it\n\t * is not.\n\t */\n\trefresh?: boolean;\n}\n\nexport class Credential<\n\tTName extends string = string,\n\tTSchema extends StandardSchemaV1 = StandardSchemaV1,\n> implements Consumable<TName, Value<TSchema>>\n{\n\t// `Consumable` rather than `Construct`, and not by preference: `Construct`\n\t// types its service as `[TClient] extends [never] ? never : Service<…>` so\n\t// that a `Cron` cannot be depended on, and TypeScript cannot evaluate that\n\t// conditional while `TClient` is still a type parameter. Every construct\n\t// with a concrete client satisfies `Construct`; the first one whose client\n\t// is generic cannot. `Consumable` is the face `.dependsOn()` requires and\n\t// carries no conditional, so it is the honest annotation here.\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Value<TSchema>>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * target publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly keys: { credential: string };\n\n\t/**\n\t * The resolved value, held as the *promise* rather than the value.\n\t *\n\t * Caching the promise is what makes two concurrent registrations share one\n\t * resolution: caching the value would let both start before either finished,\n\t * which is a duplicated fetch every cold start rather than never.\n\t */\n\tprivate resolved?: Promise<Value<TSchema>>;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: CredentialOptions<TSchema>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.keys = { credential: provideKey(canonical, 'credential') };\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'credential',\n\t\t\t\tid: this.id,\n\t\t\t\tprovides: [this.keys.credential],\n\t\t\t},\n\t\t];\n\t}\n\n\tprivate connect(options: ServiceRegisterOptions): Promise<Value<TSchema>> {\n\t\tif (this.options.refresh) return this.resolve(options);\n\n\t\tthis.resolved ??= this.resolve(options);\n\n\t\treturn this.resolved;\n\t}\n\n\tprivate async resolve(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<Value<TSchema>> {\n\t\tconst { raw } = options.envParser\n\t\t\t.create((get) => ({ raw: get(this.keys.credential).string() }))\n\t\t\t.parse();\n\n\t\ttry {\n\t\t\treturn await parseSchema(this.options.schema, decode(raw));\n\t\t} catch (issues) {\n\t\t\tthrow new MalformedCredential(this.id, this.keys.credential, issues);\n\t\t}\n\t}\n}\n\n/**\n * The raw environment value as the thing the schema should see.\n *\n * JSON when it is JSON, and the string itself when it is not — a credential\n * with a single opaque field is ordinary, and requiring `\"…\"` around it would\n * be ceremony that every operator setting one by hand would get wrong once.\n */\nfunction decode(raw: string): unknown {\n\tconst value = raw.startsWith(JSON_SCHEME)\n\t\t? raw.slice(JSON_SCHEME.length)\n\t\t: raw;\n\n\ttry {\n\t\treturn JSON.parse(value);\n\t} catch {\n\t\treturn value;\n\t}\n}\n\n/**\n * The explicit form, for the case a credential legitimately *is* a JSON string\n * and must not be parsed into an object. Rare, and cheaper to support than to\n * explain.\n */\nconst JSON_SCHEME = 'json:';\n\n/** A credential was set, and is not what its schema says it should be. */\nexport class MalformedCredential extends Error {\n\tconstructor(\n\t\treadonly id: string,\n\t\treadonly key: string,\n\t\treadonly issues: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${key} is not a valid ${id} credential. ` +\n\t\t\t\t`It is validated where it is read, so this is the value that is set ` +\n\t\t\t\t`rather than the code that uses it: ${describe(issues)}`,\n\t\t);\n\t\tthis.name = 'MalformedCredential';\n\t}\n}\n\n/** StandardSchema issues as one line. */\nfunction describe(issues: unknown): string {\n\tif (!Array.isArray(issues)) return String(issues);\n\n\treturn issues\n\t\t.map((issue: StandardSchemaV1.Issue) => {\n\t\t\tconst path = issue.path\n\t\t\t\t?.map((segment) =>\n\t\t\t\t\ttypeof segment === 'object' ? String(segment.key) : String(segment),\n\t\t\t\t)\n\t\t\t\t.join('.');\n\n\t\t\treturn path ? `${path}: ${issue.message}` : issue.message;\n\t\t})\n\t\t.join('; ');\n}\n"],"mappings":";;;;AAqEA,IAAa,aAAb,MAIA;CAQC,AAAS;CACT,AAAS;;;;;CAMT,AAAiB;;;;;;;;CASjB,AAAQ;CAER,YACCA,IACiBC,SAChB;EADgB;EAEjB,MAAM,YAAY,YAAY,GAAa;AAE3C,OAAK,KAAK;AACV,OAAK,OAAO,EAAE,YAAY,WAAW,WAAW,aAAa,CAAE;AAG/D,OAAK,UAAU;GACd,aAAa,WAAW,UAAU;GAClC,UAAU,CAAC,oBAAoB,KAAK,QAAQ,gBAAgB;EAC5D;CACD;CAED,UAAyB;AACxB,SAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,UAAU,CAAC,KAAK,KAAK,UAAW;EAChC,CACD;CACD;CAED,AAAQ,QAAQC,SAA0D;AACzE,MAAI,KAAK,QAAQ,QAAS,QAAO,KAAK,QAAQ,QAAQ;AAEtD,OAAK,aAAa,KAAK,QAAQ,QAAQ;AAEvC,SAAO,KAAK;CACZ;CAED,MAAc,QACbA,SAC0B;EAC1B,MAAM,EAAE,KAAK,GAAG,QAAQ,UACtB,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,KAAK,KAAK,WAAW,CAAC,QAAQ,CAAE,GAAE,CAC9D,OAAO;AAET,MAAI;AACH,UAAO,MAAM,YAAY,KAAK,QAAQ,QAAQ,OAAO,IAAI,CAAC;EAC1D,SAAQ,QAAQ;AAChB,SAAM,IAAI,oBAAoB,KAAK,IAAI,KAAK,KAAK,YAAY;EAC7D;CACD;AACD;;;;;;;;AASD,SAAS,OAAOC,KAAsB;CACrC,MAAM,QAAQ,IAAI,WAAW,YAAY,GACtC,IAAI,MAAM,YAAY,OAAO,GAC7B;AAEH,KAAI;AACH,SAAO,KAAK,MAAM,MAAM;CACxB,QAAO;AACP,SAAO;CACP;AACD;;;;;;AAOD,MAAM,cAAc;;AAGpB,IAAa,sBAAb,cAAyC,MAAM;CAC9C,YACUC,IACAC,KACAC,QACR;AACD,SACE,EAAE,IAAI,kBAAkB,GAAG,qHAEW,SAAS,OAAO,CAAC,EACxD;EARQ;EACA;EACA;AAOT,OAAK,OAAO;CACZ;AACD;;AAGD,SAAS,SAASA,QAAyB;AAC1C,MAAK,MAAM,QAAQ,OAAO,CAAE,QAAO,OAAO,OAAO;AAEjD,QAAO,OACL,IAAI,CAACC,UAAkC;EACvC,MAAM,OAAO,MAAM,MAChB,IAAI,CAAC,mBACC,YAAY,WAAW,OAAO,QAAQ,IAAI,GAAG,OAAO,QAAQ,CACnE,CACA,KAAK,IAAI;AAEX,SAAO,QAAQ,EAAE,KAAK,IAAI,MAAM,QAAQ,IAAI,MAAM;CAClD,EAAC,CACD,KAAK,KAAK;AACZ"}
|
|
1
|
+
{"version":3,"file":"credential.mjs","names":[],"sources":["../src/credential.ts"],"sourcesContent":["/**\n * `Credential` — a third-party credential with a shape.\n *\n * The answer to the design's first open question, \"where does `Secret` sit?\".\n * It sits beside a secret rather than replacing it, because the two differ by\n * *lifecycle* and that is the only difference worth two kinds for: a secret is\n * generated and rotated by the platform and is one opaque string, while a\n * credential is issued by someone else, arrives with several fields, and is\n * worth validating on the way in.\n *\n * ```ts\n * export const stripe = new Credential('Stripe', {\n * schema: z.object({ secretKey: z.string(), webhookSecret: z.string() }),\n * });\n *\n * // in a handler — no await, already parsed and validated\n * services.stripe.secretKey\n * ```\n *\n * **Nothing about it is async at the call site**, which is the part that looks\n * like it should be hard and is not. `Service.register()` may return a promise\n * and `ServiceDiscovery` awaits every one before a handler runs, so fetching a\n * value and validating it — including when a StandardSchema's `~standard.validate`\n * returns a promise of its own — both happen inside that existing seam.\n *\n * **One key holding JSON, not one key per field.** That is the shape a secret\n * manager stores, and it is the only shape that works with an arbitrary\n * StandardSchema: the spec has no introspection API, so enumerating a schema's\n * fields means reaching into one library's internals (`.shape`) and being wrong\n * for every other.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport { parseSchema } from '@geekmidas/schema/parser';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Consumable } from './construct-interface';\n\n/** What a credential's schema yields. */\ntype Value<TSchema extends StandardSchemaV1> =\n\tStandardSchemaV1.InferOutput<TSchema>;\n\nexport interface CredentialOptions<TSchema extends StandardSchemaV1> {\n\t/**\n\t * What the credential must look like.\n\t *\n\t * Validated at registration, so a malformed or half-set credential fails\n\t * when the process starts rather than on the first request that needs the\n\t * one field somebody forgot.\n\t */\n\tschema: TSchema;\n\t/**\n\t * Re-resolve on every registration instead of once per process.\n\t *\n\t * Off by default: a credential is resolved once and reused, because the\n\t * alternative is a fetch on every request. The cost of that default is that\n\t * a rotated credential needs a restart, which is the right trade for a value\n\t * that changes a few times a year — and this is the escape hatch for when it\n\t * is not.\n\t */\n\trefresh?: boolean;\n}\n\nexport class Credential<\n\tTName extends string = string,\n\tTSchema extends StandardSchemaV1 = StandardSchemaV1,\n> implements Consumable<TName, Value<TSchema>>\n{\n\t// `Consumable` rather than `Construct`, and not by preference: `Construct`\n\t// types its service as `[TClient] extends [never] ? never : Service<…>` so\n\t// that a `Cron` cannot be depended on, and TypeScript cannot evaluate that\n\t// conditional while `TClient` is still a type parameter. Every construct\n\t// with a concrete client satisfies `Construct`; the first one whose client\n\t// is generic cannot. `Consumable` is the face `.dependsOn()` requires and\n\t// carries no conditional, so it is the honest annotation here.\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, Value<TSchema>>;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the key the\n\t * target publishes and the key the client reads cannot drift.\n\t */\n\tprivate readonly keys: { credentials: string };\n\n\t/**\n\t * The resolved value, held as the *promise* rather than the value.\n\t *\n\t * Caching the promise is what makes two concurrent registrations share one\n\t * resolution: caching the value would let both start before either finished,\n\t * which is a duplicated fetch every cold start rather than never.\n\t */\n\tprivate resolved?: Promise<Value<TSchema>>;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: CredentialOptions<TSchema>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.keys = { credentials: provideKey(canonical, 'credentials') };\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\t}\n\n\t/**\n\t * What the stage's `<ID>_CREDENTIALS` must look like — the schema the value\n\t * is validated against where it is read. Public so a tool setting the value\n\t * (`gkm secrets:add`) can check it, and describe its fields, before it is\n\t * stored rather than at the first request.\n\t */\n\tget credentialsSchema(): TSchema {\n\t\treturn this.options.schema;\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'credential',\n\t\t\t\tid: this.id,\n\t\t\t\tprovides: [this.keys.credentials],\n\t\t\t},\n\t\t];\n\t}\n\n\tprivate connect(options: ServiceRegisterOptions): Promise<Value<TSchema>> {\n\t\tif (this.options.refresh) return this.resolve(options);\n\n\t\tthis.resolved ??= this.resolve(options);\n\n\t\treturn this.resolved;\n\t}\n\n\tprivate resolve(options: ServiceRegisterOptions): Promise<Value<TSchema>> {\n\t\treturn readCredentials(\n\t\t\tthis.id,\n\t\t\tthis.keys.credentials,\n\t\t\tthis.options.schema,\n\t\t\toptions.envParser,\n\t\t);\n\t}\n}\n\n/**\n * A credentials key, read and validated against its schema.\n *\n * Shared with every construct that holds a third party's credentials — an\n * `ExternalApi` reads its own the same way — so the shape a stage stores and\n * the error a bad value raises are the same everywhere.\n *\n * @throws {MalformedCredential} when the value is not what the schema says.\n */\nexport async function readCredentials<TSchema extends StandardSchemaV1>(\n\tid: string,\n\tkey: string,\n\tschema: TSchema,\n\tenvParser: ServiceRegisterOptions['envParser'],\n): Promise<Value<TSchema>> {\n\tconst { raw } = envParser\n\t\t.create((get) => ({ raw: get(key).string() }))\n\t\t.parse();\n\n\ttry {\n\t\treturn await parseSchema(schema, decodeCredentials(raw));\n\t} catch (issues) {\n\t\tthrow new MalformedCredential(id, key, issues);\n\t}\n}\n\n/**\n * The raw environment value as the thing the schema should see.\n *\n * JSON when it is JSON, and the string itself when it is not — a credential\n * with a single opaque field is ordinary, and requiring `\"…\"` around it would\n * be ceremony that every operator setting one by hand would get wrong once.\n *\n * Exported so a tool checking a value before it is stored reads it exactly as\n * the process that uses it will.\n */\nexport function decodeCredentials(raw: string): unknown {\n\tconst value = raw.startsWith(JSON_SCHEME)\n\t\t? raw.slice(JSON_SCHEME.length)\n\t\t: raw;\n\n\ttry {\n\t\treturn JSON.parse(value);\n\t} catch {\n\t\treturn value;\n\t}\n}\n\n/**\n * The explicit form, for the case a credential legitimately *is* a JSON string\n * and must not be parsed into an object. Rare, and cheaper to support than to\n * explain.\n */\nconst JSON_SCHEME = 'json:';\n\n/** A credential was set, and is not what its schema says it should be. */\nexport class MalformedCredential extends Error {\n\tconstructor(\n\t\treadonly id: string,\n\t\treadonly key: string,\n\t\treadonly issues: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${key} is not a valid ${id} credential. ` +\n\t\t\t\t`It is validated where it is read, so this is the value that is set ` +\n\t\t\t\t`rather than the code that uses it: ${describe(issues)}`,\n\t\t);\n\t\tthis.name = 'MalformedCredential';\n\t}\n}\n\n/** StandardSchema issues as one line. */\nfunction describe(issues: unknown): string {\n\tif (!Array.isArray(issues)) return String(issues);\n\n\treturn issues\n\t\t.map((issue: StandardSchemaV1.Issue) => {\n\t\t\tconst path = issue.path\n\t\t\t\t?.map((segment) =>\n\t\t\t\t\ttypeof segment === 'object' ? String(segment.key) : String(segment),\n\t\t\t\t)\n\t\t\t\t.join('.');\n\n\t\t\treturn path ? `${path}: ${issue.message}` : issue.message;\n\t\t})\n\t\t.join('; ');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEA,IAAa,aAAb,MAIA;CA4BmB;CApBlB;CACA;;;;;CAMA;;;;;;;;CASA;CAEA,YACC,IACA,SACC;EADgB,KAAA,UAAA;EAEjB,MAAM,YAAY,YAAY,EAAY;EAE1C,KAAK,KAAK;EACV,KAAK,OAAO,EAAE,aAAa,WAAW,WAAW,aAAa,EAAE;EAGhE,KAAK,UAAU;GACd,aAAa,WAAW,SAAS;GACjC,WAAW,oBAAoB,KAAK,QAAQ,eAAe;EAC5D;CACD;;;;;;;CAQA,IAAI,oBAA6B;EAChC,OAAO,KAAK,QAAQ;CACrB;CAEA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,UAAU,CAAC,KAAK,KAAK,WAAW;EACjC,CACD;CACD;CAEA,QAAgB,SAA0D;EACzE,IAAI,KAAK,QAAQ,SAAS,OAAO,KAAK,QAAQ,OAAO;EAErD,KAAK,aAAa,KAAK,QAAQ,OAAO;EAEtC,OAAO,KAAK;CACb;CAEA,QAAgB,SAA0D;EACzE,OAAO,gBACN,KAAK,IACL,KAAK,KAAK,aACV,KAAK,QAAQ,QACb,QAAQ,SACT;CACD;AACD;;;;;;;;;;AAWA,eAAsB,gBACrB,IACA,KACA,QACA,WAC0B;CAC1B,MAAM,EAAE,QAAQ,UACd,QAAQ,SAAS,EAAE,KAAK,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAC7C,MAAM;CAER,IAAI;EACH,OAAO,MAAM,YAAY,QAAQ,kBAAkB,GAAG,CAAC;CACxD,SAAS,QAAQ;EAChB,MAAM,IAAI,oBAAoB,IAAI,KAAK,MAAM;CAC9C;AACD;;;;;;;;;;;AAYA,SAAgB,kBAAkB,KAAsB;CACvD,MAAM,QAAQ,IAAI,WAAW,WAAW,IACrC,IAAI,MAAM,YAAY,MAAM,IAC5B;CAEH,IAAI;EACH,OAAO,KAAK,MAAM,KAAK;CACxB,QAAQ;EACP,OAAO;CACR;AACD;;;;;;AAOA,MAAM,cAAc;;AAGpB,IAAa,sBAAb,cAAyC,MAAM;CAEpC;CACA;CACA;CAHV,YACC,IACA,KACA,QACC;EACD,MACC,GAAG,IAAI,kBAAkB,GAAG,qHAEW,SAAS,MAAM,GACvD;EARS,KAAA,KAAA;EACA,KAAA,MAAA;EACA,KAAA,SAAA;EAOT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,SAAS,QAAyB;CAC1C,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,OAAO,MAAM;CAEhD,OAAO,OACL,KAAK,UAAkC;EACvC,MAAM,OAAO,MAAM,MAChB,KAAK,YACN,OAAO,YAAY,WAAW,OAAO,QAAQ,GAAG,IAAI,OAAO,OAAO,CACnE,CAAC,CACA,KAAK,GAAG;EAEV,OAAO,OAAO,GAAG,KAAK,IAAI,MAAM,YAAY,MAAM;CACnD,CAAC,CAAC,CACD,KAAK,IAAI;AACZ"}
|
package/dist/crons/index.cjs
CHANGED
|
@@ -1,93 +1,197 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_functions = require("../functions-CfzJG9hK.cjs");
|
|
3
|
+
const require_CronBuilder = require("../CronBuilder-C0Ek1-RA.cjs");
|
|
4
|
+
let _opentelemetry_api = require("@opentelemetry/api");
|
|
5
|
+
let _geekmidas_services = require("@geekmidas/services");
|
|
6
|
+
let cron_parser = require("cron-parser");
|
|
7
|
+
//#region src/crons/inProcess.ts
|
|
8
|
+
/**
|
|
9
|
+
* The longest delay a timer takes. Node clamps anything longer to 1ms, so a
|
|
10
|
+
* monthly cron would fire at once and then every millisecond.
|
|
11
|
+
*/
|
|
12
|
+
const MAX_DELAY = 2 ** 31 - 1;
|
|
13
|
+
/**
|
|
14
|
+
* Run `fire` on a cron expression, in this process, by timer.
|
|
15
|
+
*
|
|
16
|
+
* For the one process that has no broker to keep a schedule: `gkm dev` on a
|
|
17
|
+
* target whose crons are infrastructure once deployed — an EventBridge rule on
|
|
18
|
+
* AWS. Deployed, a timer is the wrong tool: each replica would fire every job
|
|
19
|
+
* once, and nothing would say so. That is why a server schedules through
|
|
20
|
+
* pg-boss, and why this is never what a deployed process does.
|
|
21
|
+
*
|
|
22
|
+
* In UTC, as the deployed schedule is. The next firing is worked out from the
|
|
23
|
+
* clock after each run rather than read off a sequence, so a run that outlasts
|
|
24
|
+
* its interval skips what it overran instead of firing them back to back.
|
|
25
|
+
*/
|
|
26
|
+
function scheduleInProcess(expression, fire) {
|
|
27
|
+
cron_parser.CronExpressionParser.parse(expression, { tz: "UTC" });
|
|
28
|
+
let timer;
|
|
29
|
+
let stopped = false;
|
|
30
|
+
const arm = (at) => {
|
|
31
|
+
if (stopped) return;
|
|
32
|
+
const delay = at - Date.now();
|
|
33
|
+
timer = setTimeout(async () => {
|
|
34
|
+
if (Date.now() < at) return arm(at);
|
|
35
|
+
await fire();
|
|
36
|
+
arm(nextAfter(expression, /* @__PURE__ */ new Date()));
|
|
37
|
+
}, Math.min(Math.max(delay, 0), MAX_DELAY));
|
|
38
|
+
timer.unref?.();
|
|
39
|
+
};
|
|
40
|
+
arm(nextAfter(expression, /* @__PURE__ */ new Date()));
|
|
41
|
+
return { stop() {
|
|
42
|
+
stopped = true;
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
} };
|
|
45
|
+
}
|
|
46
|
+
function nextAfter(expression, from) {
|
|
47
|
+
return cron_parser.CronExpressionParser.parse(expression, {
|
|
48
|
+
tz: "UTC",
|
|
49
|
+
currentDate: from
|
|
50
|
+
}).next().getTime();
|
|
51
|
+
}
|
|
23
52
|
//#endregion
|
|
24
|
-
//#region src/crons/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
//#region src/crons/runCron.ts
|
|
54
|
+
/**
|
|
55
|
+
* Run a cron once, in this process: what an EventBridge rule's invocation does
|
|
56
|
+
* on Lambda, for a server that schedules its own.
|
|
57
|
+
*
|
|
58
|
+
* The same steps as the Lambda adaptor — its services, its worker's database
|
|
59
|
+
* as `db`, an auditor when it declared one, its output parsed, its events
|
|
60
|
+
* published — because a cron that ran differently on a server than deployed
|
|
61
|
+
* would pass locally and fail where it matters. Services come through the
|
|
62
|
+
* discovery, which keeps one instance of each, so running every minute does not
|
|
63
|
+
* reconnect a database every minute.
|
|
64
|
+
*
|
|
65
|
+
* Each run is a trace of its own: a root span, `cron <name>`, whatever was
|
|
66
|
+
* active when the timer or the broker fired it.
|
|
67
|
+
*/
|
|
68
|
+
async function runCron(cron, serviceDiscovery, name) {
|
|
69
|
+
const span = _opentelemetry_api.trace.getTracer("@geekmidas/constructs").startSpan(`cron ${name ?? cron.schedule ?? "manual"}`, {
|
|
70
|
+
kind: _opentelemetry_api.SpanKind.INTERNAL,
|
|
71
|
+
attributes: {
|
|
72
|
+
...name && { "gkm.cron.name": name },
|
|
73
|
+
...cron.schedule && { "gkm.cron.schedule": cron.schedule }
|
|
74
|
+
}
|
|
75
|
+
}, _opentelemetry_api.ROOT_CONTEXT);
|
|
76
|
+
return _opentelemetry_api.context.with(_opentelemetry_api.trace.setSpan(_opentelemetry_api.ROOT_CONTEXT, span), async () => {
|
|
77
|
+
try {
|
|
78
|
+
return await run(cron, serviceDiscovery);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof Error) span.recordException(error);
|
|
81
|
+
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
82
|
+
throw error;
|
|
83
|
+
} finally {
|
|
84
|
+
span.end();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function run(cron, serviceDiscovery) {
|
|
89
|
+
const services = cron.services.length > 0 ? await serviceDiscovery.register(cron.services) : {};
|
|
90
|
+
const db = cron.databaseService ? (await serviceDiscovery.register([cron.databaseService]))[cron.databaseService.serviceName] : void 0;
|
|
91
|
+
let auditor;
|
|
92
|
+
if (cron.auditorStorageService) {
|
|
93
|
+
const { DefaultAuditor } = await import("@geekmidas/audit");
|
|
94
|
+
const storage = (await serviceDiscovery.register([cron.auditorStorageService]))[cron.auditorStorageService.serviceName];
|
|
95
|
+
auditor = new DefaultAuditor({
|
|
96
|
+
actor: {
|
|
97
|
+
id: "system",
|
|
98
|
+
type: "system"
|
|
99
|
+
},
|
|
100
|
+
storage,
|
|
101
|
+
metadata: { function: cron.type }
|
|
59
102
|
});
|
|
60
103
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
104
|
+
const logger = cron.logger;
|
|
105
|
+
const requestId = `cron-${Date.now()}`;
|
|
106
|
+
return (0, _geekmidas_services.runWithRequestContext)({
|
|
107
|
+
logger,
|
|
108
|
+
requestId,
|
|
109
|
+
startTime: Date.now(),
|
|
110
|
+
operation: `cron ${cron.schedule ?? "manual"}`
|
|
111
|
+
}, async () => {
|
|
112
|
+
const response = await cron.fn({
|
|
113
|
+
input: void 0,
|
|
114
|
+
services,
|
|
115
|
+
logger,
|
|
116
|
+
db,
|
|
117
|
+
auditor
|
|
118
|
+
});
|
|
119
|
+
const output = await cron.parseOutput(response);
|
|
120
|
+
await auditor?.flush();
|
|
121
|
+
await require_functions.publishEvents(logger, serviceDiscovery, cron.events, output);
|
|
122
|
+
return output;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/crons/schedule.ts
|
|
127
|
+
/**
|
|
128
|
+
* A schedule a cron runner can take, as standard five-field cron.
|
|
129
|
+
*
|
|
130
|
+
* `ScheduleExpression` has two spellings. `cron(m h dom mon dow)` is already
|
|
131
|
+
* five-field cron and unwraps; `rate(n unit)` is an interval, and an interval
|
|
132
|
+
* is not a schedule — it says how often, where cron says when.
|
|
133
|
+
*
|
|
134
|
+
* Most intervals still have an exact cron equivalent, because they divide their
|
|
135
|
+
* unit evenly: `rate(5 minutes)` is every fifth minute of every hour, and that
|
|
136
|
+
* is `*/5 * * * *` precisely. `rate(7 hours)` has none. There is no cron field
|
|
137
|
+
* that means "every seven hours" — `*/7` in the hour field fires at 0, 7, 14
|
|
138
|
+
* and 21 and then starts over at midnight, so the gap between the last firing
|
|
139
|
+
* of one day and the first of the next is three hours rather than seven.
|
|
140
|
+
*
|
|
141
|
+
* Those are refused rather than rounded. A job that runs at the wrong hour is
|
|
142
|
+
* far harder to notice than one that refused to build, and a schedule quietly
|
|
143
|
+
* adjusted by the framework is a bug nobody goes looking for.
|
|
144
|
+
*/
|
|
145
|
+
var UnrepresentableSchedule = class extends Error {
|
|
146
|
+
schedule;
|
|
147
|
+
constructor(schedule, reason) {
|
|
148
|
+
super(`\`${schedule}\` has no exact cron equivalent: ${reason}.\n\nCron says *when*, not *how often*, so an interval is only exact when it divides its unit evenly.
|
|
149
|
+
Write the schedule you mean instead — \`cron(0 */6 * * *)\` for every sixth hour, say — or pick an interval that divides: minutes into 60, hours into 24.`);
|
|
150
|
+
this.schedule = schedule;
|
|
151
|
+
this.name = "UnrepresentableSchedule";
|
|
81
152
|
}
|
|
82
153
|
};
|
|
83
|
-
|
|
154
|
+
/** Units `rate(…)` accepts, singular or plural. */
|
|
155
|
+
const UNITS = [
|
|
156
|
+
"minute",
|
|
157
|
+
"hour",
|
|
158
|
+
"day"
|
|
159
|
+
];
|
|
160
|
+
/**
|
|
161
|
+
* Standard five-field cron for a schedule expression.
|
|
162
|
+
*
|
|
163
|
+
* @throws {UnrepresentableSchedule} when a `rate(…)` has no exact equivalent.
|
|
164
|
+
*/
|
|
165
|
+
function toCronExpression(schedule) {
|
|
166
|
+
const cron = /^cron\((.+)\)$/.exec(schedule);
|
|
167
|
+
if (cron?.[1]) return cron[1].trim();
|
|
168
|
+
const rate = /^rate\(\s*(\d+)\s+(\w+?)s?\s*\)$/.exec(schedule);
|
|
169
|
+
if (!rate) throw new UnrepresentableSchedule(schedule, "it is neither `cron(…)` nor `rate(n unit)`");
|
|
170
|
+
const every = Number(rate[1]);
|
|
171
|
+
const unit = rate[2];
|
|
172
|
+
/* c8 ignore next */ if (!unit) throw new UnrepresentableSchedule(schedule, "no unit");
|
|
173
|
+
if (!UNITS.includes(unit)) throw new UnrepresentableSchedule(schedule, `\`${unit}\` is not one of ${UNITS.join(", ")}`);
|
|
174
|
+
if (every < 1) throw new UnrepresentableSchedule(schedule, "an interval must be at least 1");
|
|
175
|
+
switch (unit) {
|
|
176
|
+
case "minute":
|
|
177
|
+
if (every === 1) return "* * * * *";
|
|
178
|
+
if (60 % every !== 0) throw new UnrepresentableSchedule(schedule, `${every} does not divide 60, so the interval would reset at the top of each hour`);
|
|
179
|
+
return `*/${every} * * * *`;
|
|
180
|
+
case "hour":
|
|
181
|
+
if (every === 1) return "0 * * * *";
|
|
182
|
+
if (24 % every !== 0) throw new UnrepresentableSchedule(schedule, `${every} does not divide 24, so the interval would reset at midnight`);
|
|
183
|
+
return `0 */${every} * * *`;
|
|
184
|
+
case "day":
|
|
185
|
+
if (every === 1) return "0 0 * * *";
|
|
186
|
+
throw new UnrepresentableSchedule(schedule, "day-of-month restarts every month, so no multi-day interval is even");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
84
189
|
//#endregion
|
|
85
|
-
|
|
86
|
-
|
|
190
|
+
exports.Cron = require_CronBuilder.Cron;
|
|
191
|
+
exports.CronBuilder = require_CronBuilder.CronBuilder;
|
|
192
|
+
exports.UnrepresentableSchedule = UnrepresentableSchedule;
|
|
193
|
+
exports.runCron = runCron;
|
|
194
|
+
exports.scheduleInProcess = scheduleInProcess;
|
|
195
|
+
exports.toCronExpression = toCronExpression;
|
|
87
196
|
|
|
88
|
-
//#endregion
|
|
89
|
-
exports.AWSScheduledFunction = require_AWSScheduledFunction.AWSScheduledFunction;
|
|
90
|
-
exports.Cron = Cron;
|
|
91
|
-
exports.CronBuilder = CronBuilder;
|
|
92
|
-
exports.c = c;
|
|
93
197
|
//# sourceMappingURL=index.cjs.map
|
package/dist/crons/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["Function","obj: any","ConstructType","fn: FunctionHandler<TInput, TServices, TLogger, OutSchema, TDatabase>","timeout?: number","_schedule?: ScheduleExpression","input?: TInput","outputSchema?: OutSchema","services: TServices","logger?: TLogger","publisherService?: Service<TEventPublisherServiceName, TEventPublisher>","events: any[]","memorySize?: number","databaseService?: Service<TDatabaseServiceName, TDatabase>","constructs: string[]","FunctionBuilder","ConstructType","memorySize: number","_expression: ScheduleExpression","schema: T","constructs: T","s: Service","services: T","logger: T","publisher: Service<TName, T>","source: Consumable<TName, T> | Service<TName, T>","fn: FunctionHandler<TInput, TServices, TLogger, OutSchema, TDatabase>"],"sources":["../../src/crons/Cron.ts","../../src/crons/CronBuilder.ts","../../src/crons/index.ts"],"sourcesContent":["import type { AuditableAction } from '@geekmidas/audit';\nimport type { EventPublisher } from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ComposableStandardSchema } from '@geekmidas/schema';\nimport type { Service } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nimport { ConstructType } from '../Construct';\nimport { Function, type FunctionHandler } from '../functions';\n\nexport class Cron<\n\tTInput extends ComposableStandardSchema | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n> extends Function<\n\tTInput,\n\tTServices,\n\tTLogger,\n\tOutSchema,\n\tTEventPublisher,\n\tTEventPublisherServiceName,\n\tundefined,\n\tstring,\n\tTDatabase,\n\tTDatabaseServiceName,\n\tAuditableAction<string, unknown>,\n\tFunctionHandler<TInput, TServices, TLogger, OutSchema, TDatabase>\n> {\n\tstatic isCron(obj: any): obj is Cron<any, any, any, any> {\n\t\treturn Boolean(\n\t\t\tobj &&\n\t\t\t\t(obj as Function).__IS_FUNCTION__ === true &&\n\t\t\t\tobj.type === ConstructType.Cron,\n\t\t);\n\t}\n\n\tconstructor(\n\t\tfn: FunctionHandler<TInput, TServices, TLogger, OutSchema, TDatabase>,\n\t\ttimeout?: number,\n\t\tprotected _schedule?: ScheduleExpression,\n\t\tinput?: TInput,\n\t\toutputSchema?: OutSchema,\n\t\tservices: TServices = [] as unknown as TServices,\n\t\tlogger?: TLogger,\n\t\tpublisherService?: Service<TEventPublisherServiceName, TEventPublisher>,\n\t\tevents: any[] = [],\n\t\tmemorySize?: number,\n\t\tdatabaseService?: Service<TDatabaseServiceName, TDatabase>,\n\t\t/**\n\t\t * The construct ids `.dependsOn()` named — last, so no existing positional\n\t\t * argument moves.\n\t\t */\n\t\tconstructs: string[] = [],\n\t) {\n\t\tsuper(\n\t\t\tfn,\n\t\t\ttimeout,\n\t\t\tConstructType.Cron,\n\t\t\tinput,\n\t\t\toutputSchema,\n\t\t\tservices,\n\t\t\tlogger,\n\t\t\tpublisherService,\n\t\t\tevents,\n\t\t\tmemorySize,\n\t\t\tundefined, // auditorStorageService\n\t\t\tdatabaseService,\n\t\t\tconstructs,\n\t\t);\n\t}\n\n\tget schedule(): ScheduleExpression | undefined {\n\t\treturn this._schedule;\n\t}\n}\n\nexport type RateExpression = `rate(${string})`;\n\ntype CronMinute =\n\t| '*'\n\t| number\n\t| `${number}`\n\t| `${number}-${number}`\n\t| `${number}/${number}`\n\t| `*/${number}`\n\t| `${number},${number}`\n\t| string; // Allow more complex patterns\n\ntype CronHour =\n\t| '*'\n\t| number\n\t| `${number}`\n\t| `${number}-${number}`\n\t| `${number}/${number}`\n\t| `*/${number}`\n\t| `${number},${number}`\n\t| string; // Allow more complex patterns\n\ntype CronDay =\n\t| '*'\n\t| number\n\t| `${number}`\n\t| `${number}-${number}`\n\t| `${number}/${number}`\n\t| `*/${number}`\n\t| `${number},${number}`\n\t| string; // Allow more complex patterns\n\ntype CronMonth =\n\t| '*'\n\t| number\n\t| `${number}`\n\t| `${number}-${number}`\n\t| `${number}/${number}`\n\t| `*/${number}`\n\t| 'JAN'\n\t| 'FEB'\n\t| 'MAR'\n\t| 'APR'\n\t| 'MAY'\n\t| 'JUN'\n\t| 'JUL'\n\t| 'AUG'\n\t| 'SEP'\n\t| 'OCT'\n\t| 'NOV'\n\t| 'DEC'\n\t| string; // Allow more complex patterns\n\ntype CronWeekday =\n\t| '*'\n\t| number\n\t| `${number}`\n\t| `${number}-${number}`\n\t| `${number}/${number}`\n\t| `*/${number}`\n\t| 'SUN'\n\t| 'MON'\n\t| 'TUE'\n\t| 'WED'\n\t| 'THU'\n\t| 'FRI'\n\t| 'SAT'\n\t| `${string}-${string}` // Allow patterns like MON-FRI\n\t| string; // Allow more complex patterns\n\nexport type CronExpression =\n\t`cron(${CronMinute} ${CronHour} ${CronDay} ${CronMonth} ${CronWeekday})`;\n\nexport type ScheduleExpression = RateExpression | CronExpression;\n","import type { EventPublisher } from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ComposableStandardSchema } from '@geekmidas/schema';\nimport type { Service } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport uniqBy from 'lodash.uniqby';\nimport { ConstructType } from '../Construct';\nimport { cloneWith } from '../clone';\nimport {\n\ttype Consumable,\n\tidsOf,\n\ttype ServicesOf,\n\tserviceOf,\n\tservicesOf,\n} from '../construct-interface';\nimport { FunctionBuilder, type FunctionHandler } from '../functions';\nimport { Cron, type ScheduleExpression } from './Cron';\n\nexport class CronBuilder<\n\tTInput extends ComposableStandardSchema,\n\tTServices extends Service[],\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n> extends FunctionBuilder<\n\tTInput,\n\tOutSchema,\n\tTServices,\n\tTLogger,\n\tTEventPublisher,\n\tTEventPublisherServiceName,\n\tundefined,\n\tstring,\n\tTDatabase,\n\tTDatabaseServiceName\n> {\n\tprivate _schedule?: ScheduleExpression;\n\n\tconstructor() {\n\t\tsuper(ConstructType.Cron);\n\t}\n\n\toverride memorySize(memorySize: number): this {\n\t\treturn cloneWith(this, { _memorySize: memorySize });\n\t}\n\n\tschedule(\n\t\t_expression: ScheduleExpression,\n\t): CronBuilder<\n\t\tTInput,\n\t\tTServices,\n\t\tTLogger,\n\t\tOutSchema,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\treturn cloneWith(this, { _schedule: _expression });\n\t}\n\n\toverride input<T extends ComposableStandardSchema>(\n\t\tschema: T,\n\t): CronBuilder<\n\t\tT,\n\t\tTServices,\n\t\tTLogger,\n\t\tOutSchema,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\treturn cloneWith(this, {\n\t\t\tinputSchema: schema as unknown as TInput,\n\t\t}) as unknown as CronBuilder<\n\t\t\tT,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>;\n\t}\n\n\toverride output<T extends StandardSchemaV1>(\n\t\tschema: T,\n\t): CronBuilder<\n\t\tTInput,\n\t\tTServices,\n\t\tTLogger,\n\t\tT,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\treturn cloneWith(this, {\n\t\t\toutputSchema: schema as unknown as OutSchema,\n\t\t}) as unknown as CronBuilder<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tT,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>;\n\t}\n\n\t/**\n\t * Depend on constructs — a database, a bucket, a mail sender, a queue, a\n\t * topic.\n\t *\n\t * It records the edge and dissolves each construct's client into the\n\t * handler's service record under the construct's own id, so\n\t * `.dependsOn([uploads])` is what makes `services.uploads` exist and type.\n\t *\n\t * Constructs only. A `Service` does not match the shape, which is what keeps\n\t * env sniffing confined to `.services()` and the explicit lift.\n\t */\n\toverride dependsOn<const T extends readonly Consumable[]>(\n\t\tconstructs: T,\n\t): CronBuilder<\n\t\tTInput,\n\t\t[...TServices, ...ServicesOf<T>],\n\t\tTLogger,\n\t\tOutSchema,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\t// Both halves of the edge, from one call and one clone: the services the\n\t\t// handler runs with, and the ids the manifest records. Recording them\n\t\t// separately is what let them drift apart.\n\t\t//\n\t\t// `servicesOf` is the half that validates, so it runs first — recording\n\t\t// ids ahead of it left a caught `NotAConstruct` with `undefined` already\n\t\t// on a `string[]`.\n\t\tconst services = servicesOf(constructs) as unknown as Service[];\n\n\t\treturn cloneWith(this, {\n\t\t\t_services: uniqBy(\n\t\t\t\t[...this._services, ...services],\n\t\t\t\t(s: Service) => s.serviceName,\n\t\t\t),\n\t\t\t_constructs: idsOf(constructs, this._constructs),\n\t\t}) as unknown as CronBuilder<\n\t\t\tTInput,\n\t\t\t[...TServices, ...ServicesOf<T>],\n\t\t\tTLogger,\n\t\t\tOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>;\n\t}\n\n\toverride services<T extends Service[]>(\n\t\tservices: T,\n\t): CronBuilder<\n\t\tTInput,\n\t\t[...TServices, ...T],\n\t\tTLogger,\n\t\tOutSchema,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\treturn cloneWith(this, {\n\t\t\t_services: uniqBy(\n\t\t\t\t[...this._services, ...services],\n\t\t\t\t(s) => s.serviceName,\n\t\t\t) as TServices,\n\t\t}) as unknown as CronBuilder<\n\t\t\tTInput,\n\t\t\t[...TServices, ...T],\n\t\t\tTLogger,\n\t\t\tOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>;\n\t}\n\n\toverride logger<T extends Logger>(\n\t\tlogger: T,\n\t): CronBuilder<\n\t\tTInput,\n\t\tTServices,\n\t\tT,\n\t\tOutSchema,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\treturn cloneWith(this, {\n\t\t\t_logger: logger as unknown as TLogger,\n\t\t}) as unknown as CronBuilder<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tT,\n\t\t\tOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>;\n\t}\n\n\toverride publisher<T extends EventPublisher<any>, TName extends string>(\n\t\tpublisher: Service<TName, T>,\n\t): CronBuilder<\n\t\tTInput,\n\t\tTServices,\n\t\tTLogger,\n\t\tOutSchema,\n\t\tT,\n\t\tTName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\treturn cloneWith(this, {\n\t\t\t_publisher: publisher as unknown as Service<\n\t\t\t\tTEventPublisherServiceName,\n\t\t\t\tTEventPublisher\n\t\t\t>,\n\t\t}) as unknown as CronBuilder<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tOutSchema,\n\t\t\tT,\n\t\t\tTName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>;\n\t}\n\n\t/**\n\t * Set the database service for this cron job.\n\t * The database will be available in the handler context as `db`.\n\t */\n\toverride database<T, TName extends string>(\n\t\tsource: Consumable<TName, T> | Service<TName, T>,\n\t): CronBuilder<\n\t\tTInput,\n\t\tTServices,\n\t\tTLogger,\n\t\tOutSchema,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tT,\n\t\tTName\n\t> {\n\t\tconst service = serviceOf(source);\n\n\t\treturn cloneWith(this, {\n\t\t\t_databaseService: service as unknown as Service<\n\t\t\t\tTDatabaseServiceName,\n\t\t\t\tTDatabase\n\t\t\t>,\n\t\t}) as unknown as CronBuilder<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tT,\n\t\t\tTName\n\t\t>;\n\t}\n\n\toverride handle(\n\t\tfn: FunctionHandler<TInput, TServices, TLogger, OutSchema, TDatabase>,\n\t): Cron<\n\t\tTInput,\n\t\tTServices,\n\t\tTLogger,\n\t\tOutSchema,\n\t\tTEventPublisher,\n\t\tTEventPublisherServiceName,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t> {\n\t\tconst cron = new Cron(\n\t\t\tfn,\n\t\t\tthis._timeout,\n\t\t\tthis._schedule,\n\t\t\tthis.inputSchema,\n\t\t\tthis.outputSchema,\n\t\t\tthis._services,\n\t\t\tthis._logger,\n\t\t\tthis._publisher,\n\t\t\tthis._events,\n\t\t\tthis._memorySize,\n\t\t\tthis._databaseService,\n\t\t\tthis._constructs,\n\t\t);\n\n\t\t// No reset. `.handle()` reads this builder and leaves it alone, so a\n\t\t// configured base — `const fn = f.logger(log).timeout(60_000)` — keeps\n\t\t// its configuration for every construct built from it. The reset that\n\t\t// used to be here wiped the base on first use, which made a base usable\n\t\t// exactly once, and quietly reverted the logger to the default.\n\n\t\treturn cron;\n\t}\n}\n","export { AWSScheduledFunction } from './AWSScheduledFunction';\nexport {\n\tCron,\n\ttype CronExpression,\n\ttype RateExpression,\n\ttype ScheduleExpression,\n} from './Cron';\nexport { CronBuilder } from './CronBuilder';\n\nimport { CronBuilder } from './CronBuilder';\n\nexport const c = new CronBuilder();\n"],"mappings":";;;;;;;;;AAUA,IAAa,OAAb,cASUA,2BAaR;CACD,OAAO,OAAOC,KAA2C;AACxD,SAAO,QACN,OACE,IAAiB,oBAAoB,QACtC,IAAI,SAASC,gCAAc,KAC5B;CACD;CAED,YACCC,IACAC,SACUC,WACVC,OACAC,cACAC,WAAsB,CAAE,GACxBC,QACAC,kBACAC,SAAgB,CAAE,GAClBC,YACAC,iBAKAC,aAAuB,CAAE,GACxB;AACD,QACC,IACA,SACAZ,gCAAc,MACd,OACA,cACA,UACA,QACA,kBACA,QACA,oBAEA,iBACA,WACA;EA7BS;CA8BV;CAED,IAAI,WAA2C;AAC9C,SAAO,KAAK;CACZ;AACD;;;;;AC7DD,IAAa,cAAb,cASUa,kCAWR;CACD,AAAQ;CAER,cAAc;AACb,QAAMC,gCAAc,KAAK;CACzB;CAED,AAAS,WAAWC,YAA0B;AAC7C,SAAO,wBAAU,MAAM,EAAE,aAAa,WAAY,EAAC;CACnD;CAED,SACCC,aAUC;AACD,SAAO,wBAAU,MAAM,EAAE,WAAW,YAAa,EAAC;CAClD;CAED,AAAS,MACRC,QAUC;AACD,SAAO,wBAAU,MAAM,EACtB,aAAa,OACb,EAAC;CAUF;CAED,AAAS,OACRA,QAUC;AACD,SAAO,wBAAU,MAAM,EACtB,cAAc,OACd,EAAC;CAUF;;;;;;;;;;;;CAaD,AAAS,UACRC,YAUC;EAQD,MAAM,WAAW,uCAAW,WAAW;AAEvC,SAAO,wBAAU,MAAM;GACtB,WAAW,2BACV,CAAC,GAAG,KAAK,WAAW,GAAG,QAAS,GAChC,CAACC,MAAe,EAAE,YAClB;GACD,aAAa,kCAAM,YAAY,KAAK,YAAY;EAChD,EAAC;CAUF;CAED,AAAS,SACRC,UAUC;AACD,SAAO,wBAAU,MAAM,EACtB,WAAW,2BACV,CAAC,GAAG,KAAK,WAAW,GAAG,QAAS,GAChC,CAAC,MAAM,EAAE,YACT,CACD,EAAC;CAUF;CAED,AAAS,OACRC,QAUC;AACD,SAAO,wBAAU,MAAM,EACtB,SAAS,OACT,EAAC;CAUF;CAED,AAAS,UACRC,WAUC;AACD,SAAO,wBAAU,MAAM,EACtB,YAAY,UAIZ,EAAC;CAUF;;;;;CAMD,AAAS,SACRC,QAUC;EACD,MAAM,UAAU,sCAAU,OAAO;AAEjC,SAAO,wBAAU,MAAM,EACtB,kBAAkB,QAIlB,EAAC;CAUF;CAED,AAAS,OACRC,IAUC;EACD,MAAM,OAAO,IAAI,KAChB,IACA,KAAK,UACL,KAAK,WACL,KAAK,aACL,KAAK,cACL,KAAK,WACL,KAAK,SACL,KAAK,YACL,KAAK,SACL,KAAK,aACL,KAAK,kBACL,KAAK;AASN,SAAO;CACP;AACD;;;;ACrTD,MAAa,IAAI,IAAI"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["CronExpressionParser","trace","SpanKind","ROOT_CONTEXT","context","SpanStatusCode","runWithRequestContext","publishEvents"],"sources":["../../src/crons/inProcess.ts","../../src/crons/runCron.ts","../../src/crons/schedule.ts"],"sourcesContent":["import { CronExpressionParser } from 'cron-parser';\n\n/**\n * The longest delay a timer takes. Node clamps anything longer to 1ms, so a\n * monthly cron would fire at once and then every millisecond.\n */\nconst MAX_DELAY = 2 ** 31 - 1;\n\n/** A schedule that is running, and the way to stop it. */\nexport interface InProcessSchedule {\n\tstop(): void;\n}\n\n/**\n * Run `fire` on a cron expression, in this process, by timer.\n *\n * For the one process that has no broker to keep a schedule: `gkm dev` on a\n * target whose crons are infrastructure once deployed — an EventBridge rule on\n * AWS. Deployed, a timer is the wrong tool: each replica would fire every job\n * once, and nothing would say so. That is why a server schedules through\n * pg-boss, and why this is never what a deployed process does.\n *\n * In UTC, as the deployed schedule is. The next firing is worked out from the\n * clock after each run rather than read off a sequence, so a run that outlasts\n * its interval skips what it overran instead of firing them back to back.\n */\nexport function scheduleInProcess(\n\texpression: string,\n\tfire: () => Promise<unknown> | unknown,\n): InProcessSchedule {\n\t// Parsed once up front, so a bad expression throws here — to the caller\n\t// scheduling it — rather than inside a timer.\n\tCronExpressionParser.parse(expression, { tz: 'UTC' });\n\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet stopped = false;\n\n\tconst arm = (at: number) => {\n\t\tif (stopped) return;\n\t\tconst delay = at - Date.now();\n\t\ttimer = setTimeout(\n\t\t\tasync () => {\n\t\t\t\tif (Date.now() < at) return arm(at);\n\t\t\t\tawait fire();\n\t\t\t\tarm(nextAfter(expression, new Date()));\n\t\t\t},\n\t\t\tMath.min(Math.max(delay, 0), MAX_DELAY),\n\t\t);\n\t\t// The server keeps the process alive; a schedule alone should not.\n\t\ttimer.unref?.();\n\t};\n\n\tarm(nextAfter(expression, new Date()));\n\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tclearTimeout(timer);\n\t\t},\n\t};\n}\n\nfunction nextAfter(expression: string, from: Date): number {\n\treturn CronExpressionParser.parse(expression, {\n\t\ttz: 'UTC',\n\t\tcurrentDate: from,\n\t})\n\t\t.next()\n\t\t.getTime();\n}\n","import type { AuditStorage } from '@geekmidas/audit';\nimport {\n\trunWithRequestContext,\n\ttype ServiceDiscovery,\n} from '@geekmidas/services';\nimport {\n\tcontext,\n\tROOT_CONTEXT,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport { publishEvents } from '../publisher';\nimport type { Cron } from './Cron';\n\n/**\n * Run a cron once, in this process: what an EventBridge rule's invocation does\n * on Lambda, for a server that schedules its own.\n *\n * The same steps as the Lambda adaptor — its services, its worker's database\n * as `db`, an auditor when it declared one, its output parsed, its events\n * published — because a cron that ran differently on a server than deployed\n * would pass locally and fail where it matters. Services come through the\n * discovery, which keeps one instance of each, so running every minute does not\n * reconnect a database every minute.\n *\n * Each run is a trace of its own: a root span, `cron <name>`, whatever was\n * active when the timer or the broker fired it.\n */\nexport async function runCron(\n\tcron: Cron<any, any, any, any, any, any>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n\t/** The cron's export name, for its span. */\n\tname?: string,\n): Promise<unknown> {\n\tconst span = trace.getTracer('@geekmidas/constructs').startSpan(\n\t\t`cron ${name ?? cron.schedule ?? 'manual'}`,\n\t\t{\n\t\t\tkind: SpanKind.INTERNAL,\n\t\t\tattributes: {\n\t\t\t\t...(name && { 'gkm.cron.name': name }),\n\t\t\t\t...(cron.schedule && { 'gkm.cron.schedule': cron.schedule }),\n\t\t\t},\n\t\t},\n\t\tROOT_CONTEXT,\n\t);\n\n\treturn context.with(trace.setSpan(ROOT_CONTEXT, span), async () => {\n\t\ttry {\n\t\t\treturn await run(cron, serviceDiscovery);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error) span.recordException(error);\n\t\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tspan.end();\n\t\t}\n\t});\n}\n\nasync function run(\n\tcron: Cron<any, any, any, any, any, any>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n): Promise<unknown> {\n\tconst services =\n\t\tcron.services.length > 0\n\t\t\t? await serviceDiscovery.register(cron.services)\n\t\t\t: {};\n\n\tconst db = cron.databaseService\n\t\t? (await serviceDiscovery.register([cron.databaseService]))[\n\t\t\t\tcron.databaseService.serviceName\n\t\t\t]\n\t\t: undefined;\n\n\t// Only imported for a cron that audits: `@geekmidas/audit` is optional.\n\tlet auditor: { flush(): Promise<void> } | undefined;\n\tif (cron.auditorStorageService) {\n\t\tconst { DefaultAuditor } = await import('@geekmidas/audit');\n\t\tconst registered: Record<string, unknown> = await serviceDiscovery.register(\n\t\t\t[cron.auditorStorageService],\n\t\t);\n\t\tconst storage = registered[\n\t\t\tcron.auditorStorageService.serviceName\n\t\t] as AuditStorage;\n\t\tauditor = new DefaultAuditor({\n\t\t\tactor: { id: 'system', type: 'system' },\n\t\t\tstorage,\n\t\t\tmetadata: { function: cron.type },\n\t\t});\n\t}\n\n\tconst logger = cron.logger;\n\tconst requestId = `cron-${Date.now()}`;\n\n\treturn runWithRequestContext(\n\t\t{\n\t\t\tlogger,\n\t\t\trequestId,\n\t\t\tstartTime: Date.now(),\n\t\t\toperation: `cron ${cron.schedule ?? 'manual'}`,\n\t\t},\n\t\tasync () => {\n\t\t\tconst response = await cron.fn({\n\t\t\t\tinput: undefined,\n\t\t\t\tservices,\n\t\t\t\tlogger,\n\t\t\t\tdb,\n\t\t\t\tauditor,\n\t\t\t} as any);\n\t\t\tconst output = await cron.parseOutput(response);\n\n\t\t\tawait auditor?.flush();\n\t\t\t// The cron's output schema is erased by the `any` it is typed with here.\n\t\t\tawait publishEvents(\n\t\t\t\tlogger,\n\t\t\t\tserviceDiscovery,\n\t\t\t\tcron.events,\n\t\t\t\toutput as never,\n\t\t\t);\n\n\t\t\treturn output;\n\t\t},\n\t);\n}\n","import type { ScheduleExpression } from './Cron';\n\n/**\n * A schedule a cron runner can take, as standard five-field cron.\n *\n * `ScheduleExpression` has two spellings. `cron(m h dom mon dow)` is already\n * five-field cron and unwraps; `rate(n unit)` is an interval, and an interval\n * is not a schedule — it says how often, where cron says when.\n *\n * Most intervals still have an exact cron equivalent, because they divide their\n * unit evenly: `rate(5 minutes)` is every fifth minute of every hour, and that\n * is `*/5 * * * *` precisely. `rate(7 hours)` has none. There is no cron field\n * that means \"every seven hours\" — `*/7` in the hour field fires at 0, 7, 14\n * and 21 and then starts over at midnight, so the gap between the last firing\n * of one day and the first of the next is three hours rather than seven.\n *\n * Those are refused rather than rounded. A job that runs at the wrong hour is\n * far harder to notice than one that refused to build, and a schedule quietly\n * adjusted by the framework is a bug nobody goes looking for.\n */\nexport class UnrepresentableSchedule extends Error {\n\tconstructor(\n\t\treadonly schedule: string,\n\t\treason: string,\n\t) {\n\t\tsuper(\n\t\t\t`\\`${schedule}\\` has no exact cron equivalent: ${reason}.\\n\\n` +\n\t\t\t\t'Cron says *when*, not *how often*, so an interval is only exact when ' +\n\t\t\t\t'it divides its unit evenly.\\n' +\n\t\t\t\t'Write the schedule you mean instead — `cron(0 */6 * * *)` for every ' +\n\t\t\t\t'sixth hour, say — or pick an interval that divides: minutes into 60, ' +\n\t\t\t\t'hours into 24.',\n\t\t);\n\t\tthis.name = 'UnrepresentableSchedule';\n\t}\n}\n\n/** Units `rate(…)` accepts, singular or plural. */\nconst UNITS = ['minute', 'hour', 'day'] as const;\ntype Unit = (typeof UNITS)[number];\n\n/**\n * Standard five-field cron for a schedule expression.\n *\n * @throws {UnrepresentableSchedule} when a `rate(…)` has no exact equivalent.\n */\nexport function toCronExpression(schedule: ScheduleExpression): string {\n\tconst cron = /^cron\\((.+)\\)$/.exec(schedule);\n\tif (cron?.[1]) return cron[1].trim();\n\n\tconst rate = /^rate\\(\\s*(\\d+)\\s+(\\w+?)s?\\s*\\)$/.exec(schedule);\n\tif (!rate) {\n\t\tthrow new UnrepresentableSchedule(\n\t\t\tschedule,\n\t\t\t'it is neither `cron(…)` nor `rate(n unit)`',\n\t\t);\n\t}\n\n\tconst every = Number(rate[1]);\n\tconst unit = rate[2] as Unit;\n\t/* c8 ignore next */ if (!unit)\n\t\tthrow new UnrepresentableSchedule(schedule, 'no unit');\n\n\tif (!UNITS.includes(unit)) {\n\t\tthrow new UnrepresentableSchedule(\n\t\t\tschedule,\n\t\t\t`\\`${unit}\\` is not one of ${UNITS.join(', ')}`,\n\t\t);\n\t}\n\n\tif (every < 1) {\n\t\tthrow new UnrepresentableSchedule(\n\t\t\tschedule,\n\t\t\t'an interval must be at least 1',\n\t\t);\n\t}\n\n\tswitch (unit) {\n\t\tcase 'minute':\n\t\t\tif (every === 1) return '* * * * *';\n\t\t\tif (60 % every !== 0) {\n\t\t\t\tthrow new UnrepresentableSchedule(\n\t\t\t\t\tschedule,\n\t\t\t\t\t`${every} does not divide 60, so the interval would reset at the top of each hour`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn `*/${every} * * * *`;\n\n\t\tcase 'hour':\n\t\t\tif (every === 1) return '0 * * * *';\n\t\t\tif (24 % every !== 0) {\n\t\t\t\tthrow new UnrepresentableSchedule(\n\t\t\t\t\tschedule,\n\t\t\t\t\t`${every} does not divide 24, so the interval would reset at midnight`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn `0 */${every} * * *`;\n\n\t\tcase 'day':\n\t\t\tif (every === 1) return '0 0 * * *';\n\t\t\t// `*/n` in day-of-month restarts on the 1st, so a 2-day interval fires\n\t\t\t// on the 1st and 3rd of a 31-day month and then again on the 1st — one\n\t\t\t// day later, not two. No month length makes that come out even.\n\t\t\tthrow new UnrepresentableSchedule(\n\t\t\t\tschedule,\n\t\t\t\t'day-of-month restarts every month, so no multi-day interval is even',\n\t\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAMA,MAAM,YAAY,KAAK,KAAK;;;;;;;;;;;;;;AAoB5B,SAAgB,kBACf,YACA,MACoB;CAGpB,YAAA,qBAAqB,MAAM,YAAY,EAAE,IAAI,MAAM,CAAC;CAEpD,IAAI;CACJ,IAAI,UAAU;CAEd,MAAM,OAAO,OAAe;EAC3B,IAAI,SAAS;EACb,MAAM,QAAQ,KAAK,KAAK,IAAI;EAC5B,QAAQ,WACP,YAAY;GACX,IAAI,KAAK,IAAI,IAAI,IAAI,OAAO,IAAI,EAAE;GAClC,MAAM,KAAK;GACX,IAAI,UAAU,4BAAY,IAAI,KAAK,CAAC,CAAC;EACtC,GACA,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,SAAS,CACvC;EAEA,MAAM,QAAQ;CACf;CAEA,IAAI,UAAU,4BAAY,IAAI,KAAK,CAAC,CAAC;CAErC,OAAO,EACN,OAAO;EACN,UAAU;EACV,aAAa,KAAK;CACnB,EACD;AACD;AAEA,SAAS,UAAU,YAAoB,MAAoB;CAC1D,OAAOA,YAAAA,qBAAqB,MAAM,YAAY;EAC7C,IAAI;EACJ,aAAa;CACd,CAAC,CAAC,CACA,KAAK,CAAC,CACN,QAAQ;AACX;;;;;;;;;;;;;;;;;ACxCA,eAAsB,QACrB,MACA,kBAEA,MACmB;CACnB,MAAM,OAAOC,mBAAAA,MAAM,UAAU,uBAAuB,CAAC,CAAC,UACrD,QAAQ,QAAQ,KAAK,YAAY,YACjC;EACC,MAAMC,mBAAAA,SAAS;EACf,YAAY;GACX,GAAI,QAAQ,EAAE,iBAAiB,KAAK;GACpC,GAAI,KAAK,YAAY,EAAE,qBAAqB,KAAK,SAAS;EAC3D;CACD,GACAC,mBAAAA,YACD;CAEA,OAAOC,mBAAAA,QAAQ,KAAKH,mBAAAA,MAAM,QAAQE,mBAAAA,cAAc,IAAI,GAAG,YAAY;EAClE,IAAI;GACH,OAAO,MAAM,IAAI,MAAM,gBAAgB;EACxC,SAAS,OAAO;GACf,IAAI,iBAAiB,OAAO,KAAK,gBAAgB,KAAK;GACtD,KAAK,UAAU,EAAE,MAAME,mBAAAA,eAAe,MAAM,CAAC;GAC7C,MAAM;EACP,UAAU;GACT,KAAK,IAAI;EACV;CACD,CAAC;AACF;AAEA,eAAe,IACd,MACA,kBACmB;CACnB,MAAM,WACL,KAAK,SAAS,SAAS,IACpB,MAAM,iBAAiB,SAAS,KAAK,QAAQ,IAC7C,CAAC;CAEL,MAAM,KAAK,KAAK,mBACZ,MAAM,iBAAiB,SAAS,CAAC,KAAK,eAAe,CAAC,EAAA,CACvD,KAAK,gBAAgB,eAErB,KAAA;CAGH,IAAI;CACJ,IAAI,KAAK,uBAAuB;EAC/B,MAAM,EAAE,mBAAmB,MAAM,OAAO;EAIxC,MAAM,WAAU,MAHkC,iBAAiB,SAClE,CAAC,KAAK,qBAAqB,CAC5B,EAAA,CAEC,KAAK,sBAAsB;EAE5B,UAAU,IAAI,eAAe;GAC5B,OAAO;IAAE,IAAI;IAAU,MAAM;GAAS;GACtC;GACA,UAAU,EAAE,UAAU,KAAK,KAAK;EACjC,CAAC;CACF;CAEA,MAAM,SAAS,KAAK;CACpB,MAAM,YAAY,QAAQ,KAAK,IAAI;CAEnC,QAAA,GAAOC,oBAAAA,sBAAAA,CACN;EACC;EACA;EACA,WAAW,KAAK,IAAI;EACpB,WAAW,QAAQ,KAAK,YAAY;CACrC,GACA,YAAY;EACX,MAAM,WAAW,MAAM,KAAK,GAAG;GAC9B,OAAO,KAAA;GACP;GACA;GACA;GACA;EACD,CAAQ;EACR,MAAM,SAAS,MAAM,KAAK,YAAY,QAAQ;EAE9C,MAAM,SAAS,MAAM;EAErB,MAAMC,kBAAAA,cACL,QACA,kBACA,KAAK,QACL,MACD;EAEA,OAAO;CACR,CACD;AACD;;;;;;;;;;;;;;;;;;;;;ACxGA,IAAa,0BAAb,cAA6C,MAAM;CAExC;CADV,YACC,UACA,QACC;EACD,MACC,KAAK,SAAS,mCAAmC,OAAO;0JAMzD;EAVS,KAAA,WAAA;EAWT,KAAK,OAAO;CACb;AACD;;AAGA,MAAM,QAAQ;CAAC;CAAU;CAAQ;AAAK;;;;;;AAQtC,SAAgB,iBAAiB,UAAsC;CACtE,MAAM,OAAO,iBAAiB,KAAK,QAAQ;CAC3C,IAAI,OAAO,IAAI,OAAO,KAAK,EAAE,CAAC,KAAK;CAEnC,MAAM,OAAO,mCAAmC,KAAK,QAAQ;CAC7D,IAAI,CAAC,MACJ,MAAM,IAAI,wBACT,UACA,4CACD;CAGD,MAAM,QAAQ,OAAO,KAAK,EAAE;CAC5B,MAAM,OAAO,KAAK;sBACG,IAAI,CAAC,MACzB,MAAM,IAAI,wBAAwB,UAAU,SAAS;CAEtD,IAAI,CAAC,MAAM,SAAS,IAAI,GACvB,MAAM,IAAI,wBACT,UACA,KAAK,KAAK,mBAAmB,MAAM,KAAK,IAAI,GAC7C;CAGD,IAAI,QAAQ,GACX,MAAM,IAAI,wBACT,UACA,gCACD;CAGD,QAAQ,MAAR;EACC,KAAK;GACJ,IAAI,UAAU,GAAG,OAAO;GACxB,IAAI,KAAK,UAAU,GAClB,MAAM,IAAI,wBACT,UACA,GAAG,MAAM,yEACV;GAED,OAAO,KAAK,MAAM;EAEnB,KAAK;GACJ,IAAI,UAAU,GAAG,OAAO;GACxB,IAAI,KAAK,UAAU,GAClB,MAAM,IAAI,wBACT,UACA,GAAG,MAAM,6DACV;GAED,OAAO,OAAO,MAAM;EAErB,KAAK;GACJ,IAAI,UAAU,GAAG,OAAO;GAIxB,MAAM,IAAI,wBACT,UACA,qEACD;CACF;AACD"}
|