@geekmidas/constructs 10.0.0-alpha.12 → 10.0.0-alpha.121
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 +222 -158
- package/dist/Construct-B7AXn0xH.d.mts +230 -0
- package/dist/Construct-B7AXn0xH.d.mts.map +1 -0
- package/dist/Construct-CFW0zjdD.d.cts +230 -0
- package/dist/Construct-CFW0zjdD.d.cts.map +1 -0
- package/dist/{Construct-BD5neF7w.mjs → Construct-CKUA-CTA.mjs} +35 -35
- package/dist/Construct-CKUA-CTA.mjs.map +1 -0
- package/dist/{Construct-CyS3RpTL.cjs → Construct-DEOVauyA.cjs} +55 -49
- package/dist/Construct-DEOVauyA.cjs.map +1 -0
- package/dist/{Cron-DmQ8vQ4b.d.cts → Cron-bdGRSROY.d.cts} +15 -9
- package/dist/Cron-bdGRSROY.d.cts.map +1 -0
- package/dist/{Cron-dh4rUgib.d.mts → Cron-i4Uetezh.d.mts} +15 -9
- package/dist/Cron-i4Uetezh.d.mts.map +1 -0
- package/dist/CronBuilder-9H4BnD6W.d.mts +47 -0
- package/dist/CronBuilder-9H4BnD6W.d.mts.map +1 -0
- package/dist/{CronBuilder-Da0C0edR.cjs → CronBuilder-BXTG10u5.cjs} +47 -31
- package/dist/CronBuilder-BXTG10u5.cjs.map +1 -0
- package/dist/CronBuilder-B_24LZpn.d.cts +47 -0
- package/dist/CronBuilder-B_24LZpn.d.cts.map +1 -0
- package/dist/{CronBuilder-CYKT5RiO.mjs → CronBuilder-D3sF9tS_.mjs} +37 -21
- package/dist/CronBuilder-D3sF9tS_.mjs.map +1 -0
- package/dist/{Endpoint-BjWKinFA.d.mts → Endpoint-CeFkSwh5.d.mts} +129 -99
- package/dist/Endpoint-CeFkSwh5.d.mts.map +1 -0
- package/dist/{Endpoint-dav89W6-.d.cts → Endpoint-DgrpG15P.d.cts} +127 -97
- package/dist/Endpoint-DgrpG15P.d.cts.map +1 -0
- package/dist/{Endpoint-DCQPMdUX.mjs → Endpoint-gPXhHKhJ.mjs} +208 -100
- package/dist/Endpoint-gPXhHKhJ.mjs.map +1 -0
- package/dist/{Endpoint-DcWXE-SZ.cjs → Endpoint-yhR2T2Wt.cjs} +248 -122
- package/dist/Endpoint-yhR2T2Wt.cjs.map +1 -0
- package/dist/{EndpointFactory-CdWB7wPw.cjs → EndpointFactory-CSrAyazj.cjs} +361 -112
- package/dist/EndpointFactory-CSrAyazj.cjs.map +1 -0
- package/dist/{index-lAO9egTb.d.mts → EndpointFactory-Cc863XJS.d.cts} +190 -136
- package/dist/EndpointFactory-Cc863XJS.d.cts.map +1 -0
- package/dist/{index-Bd5FqCoe.d.cts → EndpointFactory-Dbw9ugkX.d.mts} +189 -135
- package/dist/EndpointFactory-Dbw9ugkX.d.mts.map +1 -0
- package/dist/{EndpointFactory-CjV8F_lJ.mjs → EndpointFactory-c2ZugGig.mjs} +283 -76
- package/dist/EndpointFactory-c2ZugGig.mjs.map +1 -0
- package/dist/Function-B0uZhZnU.d.cts +76 -0
- package/dist/Function-B0uZhZnU.d.cts.map +1 -0
- package/dist/Function-B_9R7Zy1.d.mts +76 -0
- package/dist/Function-B_9R7Zy1.d.mts.map +1 -0
- package/dist/{HonoEndpointAdaptor-DtL9j19R.cjs → HonoEndpointAdaptor-CN1jjUBJ.cjs} +115 -71
- package/dist/HonoEndpointAdaptor-CN1jjUBJ.cjs.map +1 -0
- package/dist/{HonoEndpointAdaptor-P1ahhZHu.mjs → HonoEndpointAdaptor-euH1oSdY.mjs} +105 -64
- package/dist/HonoEndpointAdaptor-euH1oSdY.mjs.map +1 -0
- package/dist/{Queue-D7ZuwBhy.d.cts → Queue-B-dDXyjF.d.mts} +48 -48
- package/dist/Queue-B-dDXyjF.d.mts.map +1 -0
- package/dist/Queue-BhuofM3k.cjs +109 -0
- package/dist/Queue-BhuofM3k.cjs.map +1 -0
- package/dist/{Queue-gVGfi2Yk.d.mts → Queue-JbIjVtTf.d.cts} +49 -49
- package/dist/Queue-JbIjVtTf.d.cts.map +1 -0
- package/dist/Queue-cmhqEBOB.mjs +104 -0
- package/dist/Queue-cmhqEBOB.mjs.map +1 -0
- package/dist/QueueBuilder-48W-q8In.d.mts +75 -0
- package/dist/QueueBuilder-48W-q8In.d.mts.map +1 -0
- package/dist/QueueBuilder-DXu1qxT1.mjs +118 -0
- package/dist/QueueBuilder-DXu1qxT1.mjs.map +1 -0
- package/dist/QueueBuilder-DdU2klPu.cjs +135 -0
- package/dist/QueueBuilder-DdU2klPu.cjs.map +1 -0
- package/dist/QueueBuilder-MfpkJ3R9.d.cts +75 -0
- package/dist/QueueBuilder-MfpkJ3R9.d.cts.map +1 -0
- package/dist/{Subscriber-2mEi0ImH.d.cts → Subscriber-CEt7kHkc.d.cts} +29 -20
- package/dist/Subscriber-CEt7kHkc.d.cts.map +1 -0
- package/dist/{Subscriber-xTJ9ayPN.d.mts → Subscriber-dsxc5LKa.d.mts} +29 -20
- package/dist/Subscriber-dsxc5LKa.d.mts.map +1 -0
- package/dist/SubscriberBuilder-BRa_nrmH.d.cts +67 -0
- package/dist/SubscriberBuilder-BRa_nrmH.d.cts.map +1 -0
- package/dist/SubscriberBuilder-D9bkrYzH.d.mts +67 -0
- package/dist/SubscriberBuilder-D9bkrYzH.d.mts.map +1 -0
- package/dist/{SubscriberBuilder-BjkbeT9l.mjs → SubscriberBuilder-DRXfxWn_.mjs} +50 -24
- package/dist/SubscriberBuilder-DRXfxWn_.mjs.map +1 -0
- package/dist/{SubscriberBuilder-4OPehf2R.cjs → SubscriberBuilder-M2EAgQhC.cjs} +61 -36
- package/dist/SubscriberBuilder-M2EAgQhC.cjs.map +1 -0
- package/dist/Topic-D2R0r24C.mjs +99 -0
- package/dist/Topic-D2R0r24C.mjs.map +1 -0
- package/dist/Topic-DwS3QO0m.cjs +104 -0
- package/dist/Topic-DwS3QO0m.cjs.map +1 -0
- package/dist/adaptors/aws.cjs +200 -246
- package/dist/adaptors/aws.cjs.map +1 -1
- package/dist/adaptors/aws.d.cts +58 -62
- package/dist/adaptors/aws.d.cts.map +1 -1
- package/dist/adaptors/aws.d.mts +59 -63
- package/dist/adaptors/aws.d.mts.map +1 -1
- package/dist/adaptors/aws.mjs +171 -219
- package/dist/adaptors/aws.mjs.map +1 -1
- package/dist/adaptors/hono.cjs +3 -10
- package/dist/adaptors/hono.d.cts +7 -16
- package/dist/adaptors/hono.d.cts.map +1 -1
- package/dist/adaptors/hono.d.mts +9 -18
- package/dist/adaptors/hono.d.mts.map +1 -1
- package/dist/adaptors/hono.mjs +2 -10
- package/dist/adaptors/testing.cjs +765 -99
- 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 +726 -78
- 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-D_0788uW.d.mts +268 -0
- package/dist/auth-D_0788uW.d.mts.map +1 -0
- package/dist/auth.cjs +301 -33
- 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 +289 -27
- package/dist/auth.mjs.map +1 -1
- package/dist/{cache-BrRz3fEB.d.mts → cache-5QZzh5Er.d.mts} +3 -6
- package/dist/cache-5QZzh5Er.d.mts.map +1 -0
- package/dist/{cache-BtZY_tHe.d.cts → cache-DnGDGly3.d.cts} +3 -6
- 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/cipher-DSDxPRIp.d.cts +106 -0
- package/dist/cipher-DSDxPRIp.d.cts.map +1 -0
- package/dist/cipher-DSDxPRIp.d.mts +106 -0
- package/dist/cipher-DSDxPRIp.d.mts.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-D9d4Gm4p.d.cts → construct-interface-B1AfqRl4.d.cts} +42 -11
- package/dist/construct-interface-B1AfqRl4.d.cts.map +1 -0
- package/dist/{construct-interface-g5GM5pwR.d.mts → construct-interface-ZvYQbivO.d.mts} +42 -11
- package/dist/construct-interface-ZvYQbivO.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-DYymbp_7.cjs +257 -0
- package/dist/credential-DYymbp_7.cjs.map +1 -0
- package/dist/credential-DfSAOOdY.mjs +222 -0
- package/dist/credential-DfSAOOdY.mjs.map +1 -0
- package/dist/credential.cjs +8 -96
- package/dist/credential.d.cts +2 -61
- package/dist/credential.d.mts +2 -61
- package/dist/credential.mjs +2 -94
- package/dist/crons/index.cjs +129 -7
- package/dist/crons/index.cjs.map +1 -1
- package/dist/crons/index.d.cts +45 -11
- package/dist/crons/index.d.cts.map +1 -1
- package/dist/crons/index.d.mts +45 -11
- package/dist/crons/index.d.mts.map +1 -1
- package/dist/crons/index.mjs +127 -8
- 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 +4 -8
- package/dist/email.d.mts.map +1 -1
- package/dist/email.mjs +16 -3
- package/dist/email.mjs.map +1 -1
- package/dist/encryption/kms.cjs +95 -0
- package/dist/encryption/kms.cjs.map +1 -0
- package/dist/encryption/kms.d.cts +7 -0
- package/dist/encryption/kms.d.cts.map +1 -0
- package/dist/encryption/kms.d.mts +7 -0
- package/dist/encryption/kms.d.mts.map +1 -0
- package/dist/encryption/kms.mjs +93 -0
- package/dist/encryption/kms.mjs.map +1 -0
- package/dist/encryption.cjs +116 -0
- package/dist/encryption.cjs.map +1 -0
- package/dist/encryption.d.cts +37 -0
- package/dist/encryption.d.cts.map +1 -0
- package/dist/encryption.d.mts +37 -0
- package/dist/encryption.d.mts.map +1 -0
- package/dist/encryption.mjs +101 -0
- package/dist/encryption.mjs.map +1 -0
- package/dist/endpoints/index.cjs +27 -16
- package/dist/endpoints/index.d.cts +105 -7
- package/dist/endpoints/index.d.cts.map +1 -0
- package/dist/endpoints/index.d.mts +105 -7
- package/dist/endpoints/index.d.mts.map +1 -0
- package/dist/endpoints/index.mjs +6 -10
- package/dist/expire-C586zznE.d.cts +38 -0
- package/dist/expire-C586zznE.d.cts.map +1 -0
- package/dist/expire-C586zznE.d.mts +38 -0
- package/dist/expire-C586zznE.d.mts.map +1 -0
- package/dist/expire-CieftS01.cjs +67 -0
- package/dist/expire-CieftS01.cjs.map +1 -0
- package/dist/expire-CpzUAfr9.mjs +50 -0
- package/dist/expire-CpzUAfr9.mjs.map +1 -0
- package/dist/external-api.cjs +106 -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 +103 -0
- package/dist/external-api.mjs.map +1 -0
- package/dist/fake-B7rvi4Qp.d.cts +201 -0
- package/dist/fake-B7rvi4Qp.d.cts.map +1 -0
- package/dist/fake-JXbkdao8.d.mts +201 -0
- package/dist/fake-JXbkdao8.d.mts.map +1 -0
- package/dist/file-server.cjs +66 -11
- package/dist/file-server.cjs.map +1 -1
- package/dist/file-server.d.cts +25 -10
- package/dist/file-server.d.cts.map +1 -1
- package/dist/file-server.d.mts +25 -10
- package/dist/file-server.d.mts.map +1 -1
- package/dist/file-server.mjs +58 -4
- package/dist/file-server.mjs.map +1 -1
- package/dist/functions/index.cjs +3 -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-14lpEFL4.mjs → functions-CTI-hDem.mjs} +376 -238
- package/dist/{functions-14lpEFL4.mjs.map → functions-CTI-hDem.mjs.map} +1 -1
- package/dist/{functions-i_la-N5r.cjs → functions-D7O5SGVc.cjs} +414 -264
- package/dist/functions-D7O5SGVc.cjs.map +1 -0
- package/dist/index-Cd2w-DRL.d.cts +115 -0
- package/dist/index-Cd2w-DRL.d.cts.map +1 -0
- package/dist/index-DdRjYhiJ.d.cts +119 -0
- package/dist/index-DdRjYhiJ.d.cts.map +1 -0
- package/dist/index-DdRjYhiJ.d.mts +119 -0
- package/dist/index-DdRjYhiJ.d.mts.map +1 -0
- package/dist/index-aBIcAiH5.d.mts +115 -0
- package/dist/index-aBIcAiH5.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/kysely-BHYV-WC-.d.cts +216 -0
- package/dist/kysely-BHYV-WC-.d.cts.map +1 -0
- package/dist/kysely-CAW3QSqp.d.mts +216 -0
- package/dist/kysely-CAW3QSqp.d.mts.map +1 -0
- package/dist/kysely-Cs4uPO8k.mjs +371 -0
- package/dist/kysely-Cs4uPO8k.mjs.map +1 -0
- package/dist/kysely-CukArHJ0.cjs +384 -0
- package/dist/kysely-CukArHJ0.cjs.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/mobile-app.cjs +74 -0
- package/dist/mobile-app.cjs.map +1 -0
- package/dist/mobile-app.d.cts +73 -0
- package/dist/mobile-app.d.cts.map +1 -0
- package/dist/mobile-app.d.mts +73 -0
- package/dist/mobile-app.d.mts.map +1 -0
- package/dist/mobile-app.mjs +73 -0
- package/dist/mobile-app.mjs.map +1 -0
- package/dist/object-storage.cjs +26 -11
- package/dist/object-storage.cjs.map +1 -1
- package/dist/object-storage.d.cts +12 -7
- package/dist/object-storage.d.cts.map +1 -1
- package/dist/object-storage.d.mts +12 -7
- package/dist/object-storage.d.mts.map +1 -1
- package/dist/object-storage.mjs +18 -5
- package/dist/object-storage.mjs.map +1 -1
- 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/random-password.cjs +86 -0
- package/dist/random-password.cjs.map +1 -0
- package/dist/random-password.d.cts +30 -0
- package/dist/random-password.d.cts.map +1 -0
- package/dist/random-password.d.mts +30 -0
- package/dist/random-password.d.mts.map +1 -0
- package/dist/random-password.mjs +84 -0
- package/dist/random-password.mjs.map +1 -0
- package/dist/{lazyAccessors-BoJN3SMd.mjs → readRequestBody-BTIl3k-D.mjs} +117 -60
- package/dist/readRequestBody-BTIl3k-D.mjs.map +1 -0
- package/dist/readRequestBody-Dej_L3c4.cjs +342 -0
- package/dist/readRequestBody-Dej_L3c4.cjs.map +1 -0
- package/dist/requestLogger-BZcXwdBs.cjs +207 -0
- package/dist/requestLogger-BZcXwdBs.cjs.map +1 -0
- package/dist/requestLogger-pA4gi3E1.mjs +178 -0
- package/dist/requestLogger-pA4gi3E1.mjs.map +1 -0
- package/dist/rest-api.cjs +90 -59
- package/dist/rest-api.cjs.map +1 -1
- package/dist/rest-api.d.cts +70 -42
- package/dist/rest-api.d.cts.map +1 -1
- package/dist/rest-api.d.mts +70 -42
- package/dist/rest-api.d.mts.map +1 -1
- package/dist/rest-api.mjs +82 -51
- 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 +33 -7
- package/dist/site.cjs.map +1 -1
- package/dist/site.d.cts +17 -7
- package/dist/site.d.cts.map +1 -1
- package/dist/site.d.mts +17 -7
- package/dist/site.d.mts.map +1 -1
- package/dist/site.mjs +29 -3
- 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 +118 -6
- package/dist/subscribers/index.cjs.map +1 -0
- package/dist/subscribers/index.d.cts +51 -6
- package/dist/subscribers/index.d.cts.map +1 -0
- package/dist/subscribers/index.d.mts +51 -6
- package/dist/subscribers/index.d.mts.map +1 -0
- package/dist/subscribers/index.mjs +115 -5
- package/dist/subscribers/index.mjs.map +1 -0
- 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-BEoBn6GO.d.mts +40 -0
- package/dist/telemetry-BEoBn6GO.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 -4
- package/dist/topic/index.d.mts +2 -4
- package/dist/topic/index.mjs +2 -134
- package/dist/tracing-Bbj1uoFJ.mjs +127 -0
- package/dist/tracing-Bbj1uoFJ.mjs.map +1 -0
- package/dist/tracing-C0ooU0V2.cjs +150 -0
- package/dist/tracing-C0ooU0V2.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 +59 -34
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +70 -45
- package/dist/worker.d.cts.map +1 -1
- package/dist/worker.d.mts +71 -45
- package/dist/worker.d.mts.map +1 -1
- package/dist/worker.mjs +54 -29
- package/dist/worker.mjs.map +1 -1
- package/package.json +115 -41
- package/dist/Construct-BD5neF7w.mjs.map +0 -1
- package/dist/Construct-C0ijr2qp.d.cts +0 -117
- package/dist/Construct-C0ijr2qp.d.cts.map +0 -1
- package/dist/Construct-Ct8nalEi.d.mts +0 -117
- package/dist/Construct-Ct8nalEi.d.mts.map +0 -1
- package/dist/Construct-CyS3RpTL.cjs.map +0 -1
- package/dist/Cron-DmQ8vQ4b.d.cts.map +0 -1
- package/dist/Cron-dh4rUgib.d.mts.map +0 -1
- package/dist/CronBuilder-BquXDbDI.d.cts +0 -44
- package/dist/CronBuilder-BquXDbDI.d.cts.map +0 -1
- package/dist/CronBuilder-CGgY2080.d.mts +0 -44
- package/dist/CronBuilder-CGgY2080.d.mts.map +0 -1
- package/dist/CronBuilder-CYKT5RiO.mjs.map +0 -1
- package/dist/CronBuilder-Da0C0edR.cjs.map +0 -1
- package/dist/Endpoint-BjWKinFA.d.mts.map +0 -1
- package/dist/Endpoint-DCQPMdUX.mjs.map +0 -1
- package/dist/Endpoint-DcWXE-SZ.cjs.map +0 -1
- package/dist/Endpoint-dav89W6-.d.cts.map +0 -1
- package/dist/EndpointFactory-CdWB7wPw.cjs.map +0 -1
- package/dist/EndpointFactory-CjV8F_lJ.mjs.map +0 -1
- package/dist/HonoEndpointAdaptor-DtL9j19R.cjs.map +0 -1
- package/dist/HonoEndpointAdaptor-P1ahhZHu.mjs.map +0 -1
- package/dist/Queue-D7ZuwBhy.d.cts.map +0 -1
- package/dist/Queue-gVGfi2Yk.d.mts.map +0 -1
- package/dist/Subscriber-2mEi0ImH.d.cts.map +0 -1
- package/dist/Subscriber-xTJ9ayPN.d.mts.map +0 -1
- package/dist/SubscriberBuilder-4OPehf2R.cjs.map +0 -1
- package/dist/SubscriberBuilder-BjkbeT9l.mjs.map +0 -1
- package/dist/SubscriberBuilder-Dptmo-1A.d.mts +0 -62
- package/dist/SubscriberBuilder-Dptmo-1A.d.mts.map +0 -1
- package/dist/SubscriberBuilder-waNhzjSM.d.cts +0 -62
- package/dist/SubscriberBuilder-waNhzjSM.d.cts.map +0 -1
- package/dist/Topic-19QeHBIR.d.cts +0 -91
- package/dist/Topic-19QeHBIR.d.cts.map +0 -1
- package/dist/Topic-DuFeqkBh.d.mts +0 -91
- package/dist/Topic-DuFeqkBh.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-BrRz3fEB.d.mts.map +0 -1
- package/dist/cache-BtZY_tHe.d.cts.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-D9d4Gm4p.d.cts.map +0 -1
- package/dist/construct-interface-Z4CpS8tK.mjs +0 -81
- package/dist/construct-interface-Z4CpS8tK.mjs.map +0 -1
- package/dist/construct-interface-g5GM5pwR.d.mts.map +0 -1
- package/dist/credential.cjs.map +0 -1
- package/dist/credential.d.cts.map +0 -1
- package/dist/credential.d.mts.map +0 -1
- package/dist/credential.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-i_la-N5r.cjs.map +0 -1
- package/dist/index-Bd5FqCoe.d.cts.map +0 -1
- package/dist/index-BknhM6Ux.d.mts +0 -31
- package/dist/index-BknhM6Ux.d.mts.map +0 -1
- package/dist/index-CBSfrowv.d.cts +0 -31
- package/dist/index-CBSfrowv.d.cts.map +0 -1
- package/dist/index-D24Xm79M.d.cts +0 -171
- package/dist/index-D24Xm79M.d.cts.map +0 -1
- package/dist/index-_ajboYxK.d.mts +0 -171
- package/dist/index-_ajboYxK.d.mts.map +0 -1
- package/dist/index-lAO9egTb.d.mts.map +0 -1
- package/dist/lazyAccessors-BoJN3SMd.mjs.map +0 -1
- package/dist/lazyAccessors-e3j2oE2m.cjs +0 -274
- package/dist/lazyAccessors-e3j2oE2m.cjs.map +0 -1
- package/dist/processAudits-CGqttuXm.cjs +0 -92
- package/dist/processAudits-CGqttuXm.cjs.map +0 -1
- package/dist/processAudits-Dmts9H_b.mjs +0 -80
- package/dist/processAudits-Dmts9H_b.mjs.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.mjs.map +0 -1
- package/dist/types-BlAkSuGI.d.cts.map +0 -1
- package/dist/types-aKksMEWL.d.mts.map +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
//#region src/endpoints/optionalPeers.ts
|
|
2
|
+
/**
|
|
3
|
+
* The optional peers an endpoint needs only for a feature it opted into.
|
|
4
|
+
*
|
|
5
|
+
* `@geekmidas/audit`, `@geekmidas/rate-limit` and `@geekmidas/db` are optional
|
|
6
|
+
* peers of this package: an app that audits nothing, limits nothing and sets
|
|
7
|
+
* no row-level security need not install them. The adaptor every endpoint is
|
|
8
|
+
* served by used to import them at the top, so serving any endpoint needed
|
|
9
|
+
* them all — and a production bundle of an app without them failed to build.
|
|
10
|
+
* They are loaded here, when an endpoint that uses one is served, which is
|
|
11
|
+
* also when the app declaring that endpoint has installed it: an auditor's
|
|
12
|
+
* storage, a rate limit's store and an RLS policy's database come from these
|
|
13
|
+
* packages.
|
|
14
|
+
*
|
|
15
|
+
* The `try` is load-bearing beyond the error it names: a bundler leaves an
|
|
16
|
+
* import it cannot resolve to run time when the import is guarded, and fails
|
|
17
|
+
* the build when it is not.
|
|
18
|
+
*/
|
|
19
|
+
/** An endpoint uses a feature whose optional peer is not installed. */
|
|
20
|
+
var OptionalPeerMissing = class extends Error {
|
|
21
|
+
packageName;
|
|
22
|
+
feature;
|
|
23
|
+
constructor(packageName, feature, cause) {
|
|
24
|
+
super(`${feature} needs '${packageName}', which could not be loaded. Add it to the dependencies of the app that serves this endpoint.`, { cause });
|
|
25
|
+
this.packageName = packageName;
|
|
26
|
+
this.feature = feature;
|
|
27
|
+
this.name = "OptionalPeerMissing";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/** `@geekmidas/audit`, for an endpoint with an auditor. */
|
|
31
|
+
async function loadAudit() {
|
|
32
|
+
try {
|
|
33
|
+
return await import("@geekmidas/audit");
|
|
34
|
+
} catch (cause) {
|
|
35
|
+
throw new OptionalPeerMissing("@geekmidas/audit", "An endpoint with an auditor", cause);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** `@geekmidas/rate-limit`, for an endpoint with a rate limit. */
|
|
39
|
+
async function loadRateLimit() {
|
|
40
|
+
try {
|
|
41
|
+
return await import("@geekmidas/rate-limit");
|
|
42
|
+
} catch (cause) {
|
|
43
|
+
throw new OptionalPeerMissing("@geekmidas/rate-limit", "An endpoint with a rate limit", cause);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** `@geekmidas/db/rls`, for an endpoint with row-level security. */
|
|
47
|
+
async function loadRls() {
|
|
48
|
+
try {
|
|
49
|
+
return await import("@geekmidas/db/rls");
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
throw new OptionalPeerMissing("@geekmidas/db", "An endpoint with row-level security", cause);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/endpoints/processAudits.ts
|
|
56
|
+
/**
|
|
57
|
+
* Create audit context for handler execution.
|
|
58
|
+
* Returns the auditor and storage for use in the handler.
|
|
59
|
+
*
|
|
60
|
+
* @param endpoint - The endpoint with audit configuration
|
|
61
|
+
* @param serviceDiscovery - Service discovery for getting audit storage
|
|
62
|
+
* @param logger - Logger for debug/error messages
|
|
63
|
+
* @param ctx - Request context for actor extraction
|
|
64
|
+
* @returns Audit context with auditor and storage, or undefined if not configured
|
|
65
|
+
*/
|
|
66
|
+
async function createAuditContext(endpoint, serviceDiscovery, logger, ctx) {
|
|
67
|
+
if (!endpoint.auditorStorageService) return;
|
|
68
|
+
const storage = (await serviceDiscovery.register([endpoint.auditorStorageService]))[endpoint.auditorStorageService.serviceName];
|
|
69
|
+
let actor = {
|
|
70
|
+
id: "system",
|
|
71
|
+
type: "system"
|
|
72
|
+
};
|
|
73
|
+
if (endpoint.actorExtractor) try {
|
|
74
|
+
actor = await endpoint.actorExtractor({
|
|
75
|
+
services: ctx.services,
|
|
76
|
+
session: ctx.session,
|
|
77
|
+
header: ctx.header,
|
|
78
|
+
cookie: ctx.cookie,
|
|
79
|
+
logger
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.error(error, "Failed to extract actor for audits");
|
|
83
|
+
}
|
|
84
|
+
const { DefaultAuditor } = await loadAudit();
|
|
85
|
+
return {
|
|
86
|
+
auditor: new DefaultAuditor({
|
|
87
|
+
actor,
|
|
88
|
+
storage,
|
|
89
|
+
metadata: {
|
|
90
|
+
endpoint: endpoint.route,
|
|
91
|
+
method: endpoint.method
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
storage
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Execute a handler with automatic audit transaction support.
|
|
99
|
+
* If the audit storage provides a withTransaction method, wraps execution
|
|
100
|
+
* in a transaction so audits are atomic with handler's database operations.
|
|
101
|
+
*
|
|
102
|
+
* This is database-agnostic - each storage implementation provides its own
|
|
103
|
+
* transaction handling based on the underlying database (Kysely, Drizzle, etc.).
|
|
104
|
+
*
|
|
105
|
+
* If the db parameter is provided and is already a transaction, the storage
|
|
106
|
+
* will reuse it instead of creating a nested transaction (similar to
|
|
107
|
+
* packages/db/src/kysely.ts#withTransaction).
|
|
108
|
+
*
|
|
109
|
+
* @param auditContext - The audit context from createAuditContext
|
|
110
|
+
* @param handler - The handler function to execute (receives auditor)
|
|
111
|
+
* @param onComplete - Called after handler with response, to process declarative audits
|
|
112
|
+
* @param options - Optional configuration including database connection
|
|
113
|
+
* @returns The handler result
|
|
114
|
+
*/
|
|
115
|
+
async function executeWithAuditTransaction(auditContext, handler, onComplete, options) {
|
|
116
|
+
if (!auditContext) return handler(void 0);
|
|
117
|
+
const { auditor, storage } = auditContext;
|
|
118
|
+
if (storage.withTransaction) return storage.withTransaction(auditor, async () => {
|
|
119
|
+
const response = await handler(auditor);
|
|
120
|
+
if (onComplete) await onComplete(response, auditor);
|
|
121
|
+
return response;
|
|
122
|
+
}, options?.db);
|
|
123
|
+
const response = await handler(auditor);
|
|
124
|
+
if (onComplete) await onComplete(response, auditor);
|
|
125
|
+
await auditor.flush();
|
|
126
|
+
return response;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/endpoints/rateLimit.ts
|
|
130
|
+
/**
|
|
131
|
+
* An endpoint's `.rateLimit()`, checked the one way every adaptor checks it —
|
|
132
|
+
* Hono, Lambda and the test adaptor — after the request is authorized.
|
|
133
|
+
*
|
|
134
|
+
* Under the limit, it answers the headers the response carries:
|
|
135
|
+
* `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (and
|
|
136
|
+
* `Retry-After` on the last request the window allows).
|
|
137
|
+
*
|
|
138
|
+
* @throws {TooManyRequestsError} over the limit — `@geekmidas/errors`' 429,
|
|
139
|
+
* whose `headers` hold `Retry-After` (seconds until the window resets) and the
|
|
140
|
+
* same `X-RateLimit-*`, which every adaptor sets on the error response.
|
|
141
|
+
*/
|
|
142
|
+
async function enforceRateLimit(config, ctx) {
|
|
143
|
+
const { checkRateLimit, getRateLimitHeaders } = await loadRateLimit();
|
|
144
|
+
const info = await checkRateLimit(config, ctx);
|
|
145
|
+
const headers = {};
|
|
146
|
+
for (const [key, value] of Object.entries(getRateLimitHeaders(info, config))) if (value !== void 0) headers[key] = value;
|
|
147
|
+
return headers;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/endpoints/requestLogger.ts
|
|
151
|
+
/**
|
|
152
|
+
* The logger for one request to `endpoint`: the surface's, bound with which
|
|
153
|
+
* request it is and what it was for — the request id, the endpoint, its route
|
|
154
|
+
* and method — plus whatever the adaptor knows of the request itself (host,
|
|
155
|
+
* path, the Lambda and API Gateway context).
|
|
156
|
+
*
|
|
157
|
+
* One per request, made once, and the one the request context carries: the
|
|
158
|
+
* handler logs through it, a service reaches it through the context, and
|
|
159
|
+
* identifying the request (`identifyRequest`) derives the signed-in user's
|
|
160
|
+
* logger from it — so a line says whose request it was without losing which
|
|
161
|
+
* route it came from. A field the request does not have is left out rather
|
|
162
|
+
* than logged as `undefined`.
|
|
163
|
+
*/
|
|
164
|
+
function requestLogger(endpoint, fields) {
|
|
165
|
+
const { requestId, ...request } = fields;
|
|
166
|
+
const bindings = {
|
|
167
|
+
requestId,
|
|
168
|
+
endpoint: endpoint.fullPath,
|
|
169
|
+
route: endpoint.route,
|
|
170
|
+
method: endpoint.method
|
|
171
|
+
};
|
|
172
|
+
for (const [key, value] of Object.entries(request)) if (value !== void 0) bindings[key] = value;
|
|
173
|
+
return endpoint.logger.child(bindings);
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
export { loadRls as a, executeWithAuditTransaction as i, enforceRateLimit as n, createAuditContext as r, requestLogger as t };
|
|
177
|
+
|
|
178
|
+
//# sourceMappingURL=requestLogger-pA4gi3E1.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestLogger-pA4gi3E1.mjs","names":[],"sources":["../src/endpoints/optionalPeers.ts","../src/endpoints/processAudits.ts","../src/endpoints/rateLimit.ts","../src/endpoints/requestLogger.ts"],"sourcesContent":["/**\n * The optional peers an endpoint needs only for a feature it opted into.\n *\n * `@geekmidas/audit`, `@geekmidas/rate-limit` and `@geekmidas/db` are optional\n * peers of this package: an app that audits nothing, limits nothing and sets\n * no row-level security need not install them. The adaptor every endpoint is\n * served by used to import them at the top, so serving any endpoint needed\n * them all — and a production bundle of an app without them failed to build.\n * They are loaded here, when an endpoint that uses one is served, which is\n * also when the app declaring that endpoint has installed it: an auditor's\n * storage, a rate limit's store and an RLS policy's database come from these\n * packages.\n *\n * The `try` is load-bearing beyond the error it names: a bundler leaves an\n * import it cannot resolve to run time when the import is guarded, and fails\n * the build when it is not.\n */\n\n/** An endpoint uses a feature whose optional peer is not installed. */\nexport class OptionalPeerMissing extends Error {\n\tconstructor(\n\t\treadonly packageName: string,\n\t\treadonly feature: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${feature} needs '${packageName}', which could not be loaded. ` +\n\t\t\t\t`Add it to the dependencies of the app that serves this endpoint.`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'OptionalPeerMissing';\n\t}\n}\n\n/** `@geekmidas/audit`, for an endpoint with an auditor. */\nexport async function loadAudit(): Promise<typeof import('@geekmidas/audit')> {\n\ttry {\n\t\treturn await import('@geekmidas/audit');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/audit',\n\t\t\t'An endpoint with an auditor',\n\t\t\tcause,\n\t\t);\n\t}\n}\n\n/** `@geekmidas/rate-limit`, for an endpoint with a rate limit. */\nexport async function loadRateLimit(): Promise<\n\ttypeof import('@geekmidas/rate-limit')\n> {\n\ttry {\n\t\treturn await import('@geekmidas/rate-limit');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/rate-limit',\n\t\t\t'An endpoint with a rate limit',\n\t\t\tcause,\n\t\t);\n\t}\n}\n\n/** `@geekmidas/db/rls`, for an endpoint with row-level security. */\nexport async function loadRls(): Promise<typeof import('@geekmidas/db/rls')> {\n\ttry {\n\t\treturn await import('@geekmidas/db/rls');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/db',\n\t\t\t'An endpoint with row-level security',\n\t\t\tcause,\n\t\t);\n\t}\n}\n","import type {\n\tAuditActor,\n\tAuditableAction,\n\tAuditor,\n\tAuditStorage,\n} from '@geekmidas/audit';\nimport type { Logger } from '@geekmidas/logger';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceDiscovery } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { ActorExtractor, MappedAudit } from './audit';\nimport type { CookieFn, Endpoint, HeaderFn } from './Endpoint';\nimport { loadAudit } from './optionalPeers';\n\n/**\n * Process declarative audit definitions after successful endpoint execution.\n * Similar to publishConstructEvents for events.\n *\n * @param endpoint - The endpoint with audit configuration\n * @param response - The handler response to generate audit payloads from\n * @param serviceDiscovery - Service discovery for registering audit storage\n * @param logger - Logger for debug/error messages\n * @param ctx - Request context (session, headers, cookies, services)\n * @param existingAuditor - Optional existing auditor instance (e.g., from handler context).\n * If provided, uses this auditor (with its stored transaction).\n * If not provided, creates a new auditor.\n */\nexport async function processEndpointAudits<\n\tTServices extends Service[] = [],\n\tTSession = unknown,\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n>(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tOutSchema,\n\t\tTServices,\n\t\tTLogger,\n\t\tTSession,\n\t\tTAuditStorage,\n\t\tTAuditStorageServiceName,\n\t\tTAuditAction\n\t>,\n\tresponse: InferStandardSchema<OutSchema>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n\tlogger: TLogger,\n\tctx: {\n\t\tsession: TSession;\n\t\theader: HeaderFn;\n\t\tcookie: CookieFn;\n\t\tservices: Record<string, unknown>;\n\t},\n\texistingAuditor?: Auditor<TAuditAction>,\n): Promise<void> {\n\ttry {\n\t\tconst audits = endpoint.audits as MappedAudit<TAuditAction, OutSchema>[];\n\n\t\t// If we have an existing auditor (from handler context), we need to flush\n\t\t// any manual audits it collected, even if there are no declarative audits\n\t\tconst hasExistingRecords =\n\t\t\texistingAuditor && existingAuditor.getRecords().length > 0;\n\n\t\t// Skip if no declarative audits and no existing records to flush\n\t\tif (!audits?.length && !hasExistingRecords) {\n\t\t\tlogger.debug('No audits to process');\n\t\t\treturn;\n\t\t}\n\n\t\t// If no auditor storage service and we have things to process, warn\n\t\tif (!endpoint.auditorStorageService) {\n\t\t\tif (hasExistingRecords || audits?.length) {\n\t\t\t\tlogger.warn('No auditor storage service available');\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Get or create auditor\n\t\tlet auditor: Auditor<TAuditAction>;\n\n\t\tif (existingAuditor) {\n\t\t\t// Use existing auditor (preserves stored transaction and manual audits)\n\t\t\tauditor = existingAuditor;\n\t\t\tlogger.debug('Using existing auditor from handler context');\n\t\t} else {\n\t\t\t// Create new auditor (backward compatibility)\n\t\t\tconst services = await serviceDiscovery.register([\n\t\t\t\tendpoint.auditorStorageService,\n\t\t\t]);\n\t\t\tconst storage = services[\n\t\t\t\tendpoint.auditorStorageService.serviceName\n\t\t\t] as AuditStorage;\n\n\t\t\t// Extract actor if configured\n\t\t\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\t\t\tif (endpoint.actorExtractor) {\n\t\t\t\ttry {\n\t\t\t\t\tactor = await (\n\t\t\t\t\t\tendpoint.actorExtractor as ActorExtractor<\n\t\t\t\t\t\t\tTServices,\n\t\t\t\t\t\t\tTSession,\n\t\t\t\t\t\t\tTLogger\n\t\t\t\t\t\t>\n\t\t\t\t\t)({\n\t\t\t\t\t\tservices: ctx.services as any,\n\t\t\t\t\t\tsession: ctx.session,\n\t\t\t\t\t\theader: ctx.header,\n\t\t\t\t\t\tcookie: ctx.cookie,\n\t\t\t\t\t\tlogger,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t\t\t\t// Continue with system actor\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst { DefaultAuditor } = await loadAudit();\n\t\t\tauditor = new DefaultAuditor<TAuditAction>({\n\t\t\t\tactor,\n\t\t\t\tstorage,\n\t\t\t\tmetadata: {\n\t\t\t\t\tendpoint: endpoint.route,\n\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// Process each declarative audit\n\t\tif (audits?.length) {\n\t\t\tfor (const audit of audits) {\n\t\t\t\tlogger.debug({ audit: audit.type }, 'Processing declarative audit');\n\n\t\t\t\t// Check when condition\n\t\t\t\tif (audit.when && !audit.when(response as any)) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t{ audit: audit.type },\n\t\t\t\t\t\t'Audit skipped due to when condition',\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Extract payload\n\t\t\t\tconst payload = audit.payload(response as any);\n\n\t\t\t\t// Extract entityId if configured\n\t\t\t\tconst entityId = audit.entityId?.(response as any);\n\n\t\t\t\t// Record the audit\n\t\t\t\tauditor.audit(audit.type as any, payload as any, {\n\t\t\t\t\ttable: audit.table,\n\t\t\t\t\tentityId,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Flush audits to storage\n\t\t// Note: If existingAuditor has a stored transaction (via setTransaction),\n\t\t// flush() will use it automatically\n\t\tconst recordCount = auditor.getRecords().length;\n\t\tif (recordCount > 0) {\n\t\t\t// Check if auditor has a stored transaction (for logging purposes)\n\t\t\tconst trx =\n\t\t\t\t'getTransaction' in auditor\n\t\t\t\t\t? (auditor as { getTransaction(): unknown }).getTransaction()\n\t\t\t\t\t: undefined;\n\t\t\tlogger.debug(\n\t\t\t\t{ auditCount: recordCount, hasTransaction: !!trx },\n\t\t\t\t'Flushing audits',\n\t\t\t);\n\t\t\tawait auditor.flush();\n\t\t}\n\t} catch (error) {\n\t\tlogger.error(error as Error, 'Failed to process audits');\n\t\t// Don't rethrow - audit failures shouldn't fail the request\n\t}\n}\n\n/**\n * Context for audit-aware handler execution.\n */\nexport interface AuditExecutionContext<\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> {\n\t/** The auditor instance for recording audits */\n\tauditor: Auditor<TAuditAction>;\n\t/** The audit storage instance */\n\tstorage: AuditStorage;\n}\n\n/**\n * Create audit context for handler execution.\n * Returns the auditor and storage for use in the handler.\n *\n * @param endpoint - The endpoint with audit configuration\n * @param serviceDiscovery - Service discovery for getting audit storage\n * @param logger - Logger for debug/error messages\n * @param ctx - Request context for actor extraction\n * @returns Audit context with auditor and storage, or undefined if not configured\n */\nexport async function createAuditContext<\n\tTServices extends Service[] = [],\n\tTSession = unknown,\n\tTLogger extends Logger = Logger,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n>(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tTServices,\n\t\tTLogger,\n\t\tTSession,\n\t\tTAuditStorage,\n\t\tTAuditStorageServiceName,\n\t\tTAuditAction,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n\tlogger: TLogger,\n\tctx: {\n\t\tsession: TSession;\n\t\theader: HeaderFn;\n\t\tcookie: CookieFn;\n\t\tservices: Record<string, unknown>;\n\t},\n): Promise<AuditExecutionContext<TAuditAction> | undefined> {\n\tif (!endpoint.auditorStorageService) {\n\t\treturn undefined;\n\t}\n\n\tconst services = await serviceDiscovery.register([\n\t\tendpoint.auditorStorageService,\n\t]);\n\tconst storage = services[\n\t\tendpoint.auditorStorageService.serviceName\n\t] as AuditStorage;\n\n\t// Extract actor if configured\n\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\tif (endpoint.actorExtractor) {\n\t\ttry {\n\t\t\tactor = await (\n\t\t\t\tendpoint.actorExtractor as ActorExtractor<TServices, TSession, TLogger>\n\t\t\t)({\n\t\t\t\tservices: ctx.services as any,\n\t\t\t\tsession: ctx.session,\n\t\t\t\theader: ctx.header,\n\t\t\t\tcookie: ctx.cookie,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t}\n\t}\n\n\tconst { DefaultAuditor } = await loadAudit();\n\tconst auditor = new DefaultAuditor<TAuditAction>({\n\t\tactor,\n\t\tstorage,\n\t\tmetadata: {\n\t\t\tendpoint: endpoint.route,\n\t\t\tmethod: endpoint.method,\n\t\t},\n\t});\n\n\treturn { auditor, storage };\n}\n\n/**\n * Options for executeWithAuditTransaction.\n */\nexport interface ExecuteWithAuditTransactionOptions {\n\t/**\n\t * Database connection to use for the transaction.\n\t * If this is already a transaction, it will be reused instead of creating a nested one.\n\t * If not provided, the storage's internal database is used.\n\t */\n\tdb?: unknown;\n}\n\n/**\n * Execute a handler with automatic audit transaction support.\n * If the audit storage provides a withTransaction method, wraps execution\n * in a transaction so audits are atomic with handler's database operations.\n *\n * This is database-agnostic - each storage implementation provides its own\n * transaction handling based on the underlying database (Kysely, Drizzle, etc.).\n *\n * If the db parameter is provided and is already a transaction, the storage\n * will reuse it instead of creating a nested transaction (similar to\n * packages/db/src/kysely.ts#withTransaction).\n *\n * @param auditContext - The audit context from createAuditContext\n * @param handler - The handler function to execute (receives auditor)\n * @param onComplete - Called after handler with response, to process declarative audits\n * @param options - Optional configuration including database connection\n * @returns The handler result\n */\nexport async function executeWithAuditTransaction<\n\tT,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n>(\n\tauditContext: AuditExecutionContext<TAuditAction> | undefined,\n\thandler: (auditor?: Auditor<TAuditAction>) => Promise<T>,\n\tonComplete?: (response: T, auditor: Auditor<TAuditAction>) => Promise<void>,\n\toptions?: ExecuteWithAuditTransactionOptions,\n): Promise<T> {\n\t// No audit context - just run handler\n\tif (!auditContext) {\n\t\treturn handler(undefined);\n\t}\n\n\tconst { auditor, storage } = auditContext;\n\n\t// Check if storage provides a transaction wrapper\n\tif (storage.withTransaction) {\n\t\t// Wrap in transaction - audits are atomic with handler operations\n\t\t// The storage's withTransaction handles setTransaction and flush\n\t\t// Pass db so existing transactions are reused\n\t\treturn storage.withTransaction(\n\t\t\tauditor,\n\t\t\tasync () => {\n\t\t\t\tconst response = await handler(auditor);\n\n\t\t\t\t// Process declarative audits within the transaction\n\t\t\t\tif (onComplete) {\n\t\t\t\t\tawait onComplete(response, auditor);\n\t\t\t\t}\n\n\t\t\t\treturn response;\n\t\t\t},\n\t\t\toptions?.db,\n\t\t);\n\t}\n\n\t// No transaction support - run handler and flush audits after\n\tconst response = await handler(auditor);\n\n\tif (onComplete) {\n\t\tawait onComplete(response, auditor);\n\t}\n\n\t// Flush audits (no transaction)\n\tawait auditor.flush();\n\n\treturn response;\n}\n","import type { RateLimitConfig, RateLimitContext } from '@geekmidas/rate-limit';\nimport { loadRateLimit } from './optionalPeers';\n\n/**\n * An endpoint's `.rateLimit()`, checked the one way every adaptor checks it —\n * Hono, Lambda and the test adaptor — after the request is authorized.\n *\n * Under the limit, it answers the headers the response carries:\n * `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (and\n * `Retry-After` on the last request the window allows).\n *\n * @throws {TooManyRequestsError} over the limit — `@geekmidas/errors`' 429,\n * whose `headers` hold `Retry-After` (seconds until the window resets) and the\n * same `X-RateLimit-*`, which every adaptor sets on the error response.\n */\nexport async function enforceRateLimit(\n\tconfig: RateLimitConfig,\n\tctx: RateLimitContext<any, any, any>,\n): Promise<Record<string, string>> {\n\tconst { checkRateLimit, getRateLimitHeaders } = await loadRateLimit();\n\tconst info = await checkRateLimit(config, ctx);\n\n\tconst headers: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(\n\t\tgetRateLimitHeaders(info, config),\n\t)) {\n\t\tif (value !== undefined) headers[key] = value;\n\t}\n\treturn headers;\n}\n","import type { Logger } from '@geekmidas/logger';\n\n/** What an endpoint contributes to every line logged for one of its requests. */\ninterface LoggingEndpoint {\n\tlogger: Logger;\n\tfullPath: string;\n\troute: string;\n\tmethod: string;\n}\n\n/**\n * The logger for one request to `endpoint`: the surface's, bound with which\n * request it is and what it was for — the request id, the endpoint, its route\n * and method — plus whatever the adaptor knows of the request itself (host,\n * path, the Lambda and API Gateway context).\n *\n * One per request, made once, and the one the request context carries: the\n * handler logs through it, a service reaches it through the context, and\n * identifying the request (`identifyRequest`) derives the signed-in user's\n * logger from it — so a line says whose request it was without losing which\n * route it came from. A field the request does not have is left out rather\n * than logged as `undefined`.\n */\nexport function requestLogger<TLogger extends Logger>(\n\tendpoint: LoggingEndpoint,\n\tfields: { requestId: string } & Record<string, unknown>,\n): TLogger {\n\tconst { requestId, ...request } = fields;\n\tconst bindings: Record<string, unknown> = {\n\t\trequestId,\n\t\tendpoint: endpoint.fullPath,\n\t\troute: endpoint.route,\n\t\tmethod: endpoint.method,\n\t};\n\tfor (const [key, value] of Object.entries(request)) {\n\t\tif (value !== undefined) bindings[key] = value;\n\t}\n\treturn endpoint.logger.child(bindings) as TLogger;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAb,cAAyC,MAAM;CAEpC;CACA;CAFV,YACC,aACA,SACA,OACC;EACD,MACC,GAAG,QAAQ,UAAU,YAAY,iGAEjC,EAAE,MAAM,CACT;EARS,KAAA,cAAA;EACA,KAAA,UAAA;EAQT,KAAK,OAAO;CACb;AACD;;AAGA,eAAsB,YAAwD;CAC7E,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,oBACA,+BACA,KACD;CACD;AACD;;AAGA,eAAsB,gBAEpB;CACD,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,yBACA,iCACA,KACD;CACD;AACD;;AAGA,eAAsB,UAAuD;CAC5E,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,iBACA,uCACA,KACD;CACD;AACD;;;;;;;;;;;;;ACwIA,eAAsB,mBAarB,UAcA,kBACA,QACA,KAM2D;CAC3D,IAAI,CAAC,SAAS,uBACb;CAMD,MAAM,WAAU,MAHO,iBAAiB,SAAS,CAChD,SAAS,qBACV,CAAC,EAAA,CAEA,SAAS,sBAAsB;CAIhC,IAAI,QAAoB;EAAE,IAAI;EAAU,MAAM;CAAS;CACvD,IAAI,SAAS,gBACZ,IAAI;EACH,QAAQ,MACP,SAAS,eACR;GACD,UAAU,IAAI;GACd,SAAS,IAAI;GACb,QAAQ,IAAI;GACZ,QAAQ,IAAI;GACZ;EACD,CAAC;CACF,SAAS,OAAO;EACf,OAAO,MAAM,OAAgB,oCAAoC;CAClE;CAGD,MAAM,EAAE,mBAAmB,MAAM,UAAU;CAU3C,OAAO;EAAE,SAAA,IATW,eAA6B;GAChD;GACA;GACA,UAAU;IACT,UAAU,SAAS;IACnB,QAAQ,SAAS;GAClB;EACD,CAEe;EAAG;CAAQ;AAC3B;;;;;;;;;;;;;;;;;;;AAgCA,eAAsB,4BAOrB,cACA,SACA,YACA,SACa;CAEb,IAAI,CAAC,cACJ,OAAO,QAAQ,KAAA,CAAS;CAGzB,MAAM,EAAE,SAAS,YAAY;CAG7B,IAAI,QAAQ,iBAIX,OAAO,QAAQ,gBACd,SACA,YAAY;EACX,MAAM,WAAW,MAAM,QAAQ,OAAO;EAGtC,IAAI,YACH,MAAM,WAAW,UAAU,OAAO;EAGnC,OAAO;CACR,GACA,SAAS,EACV;CAID,MAAM,WAAW,MAAM,QAAQ,OAAO;CAEtC,IAAI,YACH,MAAM,WAAW,UAAU,OAAO;CAInC,MAAM,QAAQ,MAAM;CAEpB,OAAO;AACR;;;;;;;;;;;;;;;ACjWA,eAAsB,iBACrB,QACA,KACkC;CAClC,MAAM,EAAE,gBAAgB,wBAAwB,MAAM,cAAc;CACpE,MAAM,OAAO,MAAM,eAAe,QAAQ,GAAG;CAE7C,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QACjC,oBAAoB,MAAM,MAAM,CACjC,GACC,IAAI,UAAU,KAAA,GAAW,QAAQ,OAAO;CAEzC,OAAO;AACR;;;;;;;;;;;;;;;;ACNA,SAAgB,cACf,UACA,QACU;CACV,MAAM,EAAE,WAAW,GAAG,YAAY;CAClC,MAAM,WAAoC;EACzC;EACA,UAAU,SAAS;EACnB,OAAO,SAAS;EAChB,QAAQ,SAAS;CAClB;CACA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAChD,IAAI,UAAU,KAAA,GAAW,SAAS,OAAO;CAE1C,OAAO,SAAS,OAAO,MAAM,QAAQ;AACtC"}
|
package/dist/rest-api.cjs
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
const require_construct_interface = require(
|
|
3
|
-
require(
|
|
4
|
-
require(
|
|
5
|
-
|
|
6
|
-
require(
|
|
7
|
-
require('./Endpoint-DcWXE-SZ.cjs');
|
|
8
|
-
const require_surfaceEnv = require('./surfaceEnv-BFXEms2f.cjs');
|
|
9
|
-
const __geekmidas_manifest = require_chunk.__toESM(require("@geekmidas/manifest"));
|
|
10
|
-
const __geekmidas_logger_console = require_chunk.__toESM(require("@geekmidas/logger/console"));
|
|
11
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_construct_interface = require("./construct-interface.cjs");
|
|
3
|
+
const require_EndpointFactory = require("./EndpointFactory-CSrAyazj.cjs");
|
|
4
|
+
const require_surfaceEnv = require("./surfaceEnv-CNimK76p.cjs");
|
|
5
|
+
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
6
|
+
let _geekmidas_logger_console = require("@geekmidas/logger/console");
|
|
12
7
|
//#region src/rest-api.ts
|
|
13
8
|
/**
|
|
14
9
|
* What is *not* here, and why.
|
|
15
10
|
*
|
|
16
|
-
* `dependsOn`, `database`, `auditor` and `
|
|
11
|
+
* `dependsOn`, `database`, `auditor` and `.event(topic, …)` all inject a capability
|
|
17
12
|
* into a handler. Putting one on the surface hands it to every route the
|
|
18
13
|
* surface serves — a health check gets the database because a report needed it
|
|
19
14
|
* — which is the thing least privilege forbids and the reason `.calls()` is not
|
|
20
|
-
* spelled `dependsOn`. They belong to the endpoint, or to a
|
|
21
|
-
*
|
|
15
|
+
* spelled `dependsOn`. They belong to the endpoint, or to a branch —
|
|
16
|
+
* `api.database(db)` — for a group of endpoints that genuinely share them. A
|
|
17
|
+
* branch is a new factory; the surface config stays free of grants.
|
|
22
18
|
*
|
|
23
19
|
* `auth` is not here either: `.auth(construct)` already declares it, and a
|
|
24
20
|
* second way to say the same thing is the duplication this whole model removes.
|
|
@@ -26,6 +22,9 @@ const __geekmidas_logger_console = require_chunk.__toESM(require("@geekmidas/log
|
|
|
26
22
|
* What is left is what the *process* is rather than what a route may reach.
|
|
27
23
|
*/
|
|
28
24
|
var RestApi = class RestApi {
|
|
25
|
+
config;
|
|
26
|
+
dependencies;
|
|
27
|
+
authenticator;
|
|
29
28
|
id;
|
|
30
29
|
/**
|
|
31
30
|
* Declared once and read by both `declare()` and anything that consumes this
|
|
@@ -52,70 +51,99 @@ var RestApi = class RestApi {
|
|
|
52
51
|
envParser;
|
|
53
52
|
/** The Telescope instance, when this surface was given one. */
|
|
54
53
|
telescope;
|
|
54
|
+
/** What this surface's process emits, when it was given a `Telemetry`. */
|
|
55
|
+
telemetry;
|
|
55
56
|
constructor(id, config, dependencies = [], authenticator) {
|
|
56
57
|
this.config = config;
|
|
57
58
|
this.dependencies = dependencies;
|
|
58
59
|
this.authenticator = authenticator;
|
|
59
|
-
const canonical = (0,
|
|
60
|
+
const canonical = (0, _geekmidas_manifest.canonicalId)(id);
|
|
60
61
|
this.id = canonical;
|
|
61
62
|
this.keys = {
|
|
62
|
-
url: (0,
|
|
63
|
-
trustedOrigins: (0,
|
|
64
|
-
cookieDomain: (0,
|
|
63
|
+
url: (0, _geekmidas_manifest.provideKey)(canonical, "url"),
|
|
64
|
+
trustedOrigins: (0, _geekmidas_manifest.provideKey)(canonical, "trustedOrigins"),
|
|
65
|
+
cookieDomain: (0, _geekmidas_manifest.provideKey)(canonical, "cookieDomain")
|
|
65
66
|
};
|
|
66
|
-
this.logger = config.logger ??
|
|
67
|
+
this.logger = config.logger ?? _geekmidas_logger_console.DEFAULT_LOGGER;
|
|
67
68
|
this.telescope = config.telescope;
|
|
69
|
+
this.telemetry = config.telemetry;
|
|
68
70
|
this.envParser = require_surfaceEnv.envParserFor(config.envParser ? {
|
|
69
71
|
id: canonical,
|
|
70
72
|
envParser: config.envParser
|
|
71
73
|
} : void 0);
|
|
72
|
-
this
|
|
74
|
+
this.#endpoints = new require_EndpointFactory.EndpointFactory({
|
|
73
75
|
defaultLogger: this.logger,
|
|
74
|
-
...config.authorizers ? { availableAuthorizers: config.authorizers.map((name) =>
|
|
76
|
+
...config.authorizers ? { availableAuthorizers: config.authorizers.map((name) => {
|
|
77
|
+
const securityScheme = require_EndpointFactory.getSecurityScheme(name);
|
|
78
|
+
return securityScheme ? {
|
|
79
|
+
name,
|
|
80
|
+
securityScheme
|
|
81
|
+
} : { name };
|
|
82
|
+
}) } : {},
|
|
75
83
|
...config.defaultAuthorizer && config.defaultAuthorizer !== "none" ? { defaultAuthorizerName: config.defaultAuthorizer } : {},
|
|
76
84
|
surface: {
|
|
77
85
|
id: this.id,
|
|
78
|
-
envParser: this.envParser
|
|
86
|
+
envParser: this.envParser,
|
|
87
|
+
...authenticator ? { auth: authenticator } : {}
|
|
79
88
|
}
|
|
80
89
|
});
|
|
90
|
+
const endpoints = this.#endpoints;
|
|
91
|
+
this.get = endpoints.get.bind(endpoints);
|
|
92
|
+
this.post = endpoints.post.bind(endpoints);
|
|
93
|
+
this.put = endpoints.put.bind(endpoints);
|
|
94
|
+
this.patch = endpoints.patch.bind(endpoints);
|
|
95
|
+
this.delete = endpoints.delete.bind(endpoints);
|
|
96
|
+
this.options = endpoints.options.bind(endpoints);
|
|
97
|
+
this.database = endpoints.database.bind(endpoints);
|
|
98
|
+
this.session = endpoints.session.bind(endpoints);
|
|
99
|
+
this.auditor = endpoints.auditor.bind(endpoints);
|
|
100
|
+
this.actor = endpoints.actor.bind(endpoints);
|
|
101
|
+
this.authorizer = endpoints.authorizer.bind(endpoints);
|
|
102
|
+
this.authorize = endpoints.authorize.bind(endpoints);
|
|
103
|
+
this.rls = endpoints.rls.bind(endpoints);
|
|
104
|
+
this.route = endpoints.route.bind(endpoints);
|
|
81
105
|
}
|
|
82
106
|
/**
|
|
83
|
-
* This surface's endpoint factory.
|
|
107
|
+
* This surface's endpoint factory, which every method below starts from.
|
|
84
108
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
109
|
+
* Private: a surface *is* where endpoints are built, so there is no second
|
|
110
|
+
* object to reach through first. `api.database(db)` said the same
|
|
111
|
+
* thing as `api.database(db)` with one more word to learn.
|
|
112
|
+
*/
|
|
113
|
+
#endpoints;
|
|
114
|
+
/** `api.get('/users')` — one route on this surface. */
|
|
115
|
+
get;
|
|
116
|
+
post;
|
|
117
|
+
put;
|
|
118
|
+
patch;
|
|
119
|
+
delete;
|
|
120
|
+
options;
|
|
121
|
+
/**
|
|
122
|
+
* A branch for a *group* of endpoints that shares what a single route would
|
|
123
|
+
* otherwise repeat — a database, an auditor, a publisher, a session:
|
|
88
124
|
*
|
|
89
125
|
* ```ts
|
|
90
|
-
* const
|
|
91
|
-
* export const createUser =
|
|
126
|
+
* export const router = api.database(database);
|
|
127
|
+
* export const createUser = router.post('/users').handle(({ db }) => …);
|
|
92
128
|
* ```
|
|
93
129
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
130
|
+
* Each returns a new factory and leaves the surface as it was. That is the
|
|
131
|
+
* whole difference from putting the same thing in the surface's config: a
|
|
132
|
+
* group opts in, where the surface would grant it to every route it serves —
|
|
133
|
+
* a health check handed the database because a profile endpoint needed it.
|
|
134
|
+
*
|
|
135
|
+
* `dependsOn` is deliberately not here. It is per endpoint, and on the
|
|
136
|
+
* surface it would read as the API depending on something, which is what
|
|
137
|
+
* `.calls()` and `.auth()` say instead.
|
|
98
138
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
put(path) {
|
|
108
|
-
return this.endpoints.put(path);
|
|
109
|
-
}
|
|
110
|
-
patch(path) {
|
|
111
|
-
return this.endpoints.patch(path);
|
|
112
|
-
}
|
|
113
|
-
delete(path) {
|
|
114
|
-
return this.endpoints.delete(path);
|
|
115
|
-
}
|
|
116
|
-
options(path) {
|
|
117
|
-
return this.endpoints.options(path);
|
|
118
|
-
}
|
|
139
|
+
database;
|
|
140
|
+
session;
|
|
141
|
+
auditor;
|
|
142
|
+
actor;
|
|
143
|
+
authorizer;
|
|
144
|
+
authorize;
|
|
145
|
+
rls;
|
|
146
|
+
route;
|
|
119
147
|
/**
|
|
120
148
|
* What authenticates this surface.
|
|
121
149
|
*
|
|
@@ -125,13 +153,14 @@ var RestApi = class RestApi {
|
|
|
125
153
|
* what a request is allowed to be. A surface with two of the first is a
|
|
126
154
|
* mistake; two of the second is Tuesday.
|
|
127
155
|
*
|
|
128
|
-
* It
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
156
|
+
* It declares the id, and keeps the construct: a session callback reads from
|
|
157
|
+
* it as `auth` — `session(async ({ auth }) => auth.getSession())` — which
|
|
158
|
+
* calls the construct's `verify(headers)`. That is the one thing every
|
|
159
|
+
* provider shares, whether the server is mounted in this process, deployed
|
|
160
|
+
* beside it, or an OIDC issuer somebody else runs.
|
|
132
161
|
*/
|
|
133
162
|
auth(construct) {
|
|
134
|
-
return new RestApi(this.id, this.config, [...this.dependencies, require_construct_interface.edgeTo(construct)], construct
|
|
163
|
+
return new RestApi(this.id, this.config, [...this.dependencies, require_construct_interface.edgeTo(construct)], construct);
|
|
135
164
|
}
|
|
136
165
|
/**
|
|
137
166
|
* Other surfaces this one calls.
|
|
@@ -164,11 +193,13 @@ var RestApi = class RestApi {
|
|
|
164
193
|
kind: "rest-api",
|
|
165
194
|
id: this.id,
|
|
166
195
|
path: this.config.path,
|
|
196
|
+
...this.config.subdomain ? { subdomain: this.config.subdomain } : {},
|
|
167
197
|
...this.config.telescope ? { telescope: true } : {},
|
|
168
198
|
...this.config.cors ? { cors: this.config.cors } : {},
|
|
169
|
-
...this.authenticator ? { auth: this.authenticator } : {},
|
|
199
|
+
...this.authenticator ? { auth: this.authenticator.id } : {},
|
|
170
200
|
endpoints: [],
|
|
171
201
|
...this.dependencies.length ? { calls: this.dependencies } : {},
|
|
202
|
+
...this.config.telemetry ? { telemetry: this.config.telemetry.id } : {},
|
|
172
203
|
...this.config.authorizers?.length ? { authorizers: this.config.authorizers } : {},
|
|
173
204
|
defaultAuthorizer: this.config.defaultAuthorizer,
|
|
174
205
|
provides: [
|
|
@@ -179,7 +210,7 @@ var RestApi = class RestApi {
|
|
|
179
210
|
}];
|
|
180
211
|
}
|
|
181
212
|
};
|
|
182
|
-
|
|
183
213
|
//#endregion
|
|
184
214
|
exports.RestApi = RestApi;
|
|
215
|
+
|
|
185
216
|
//# sourceMappingURL=rest-api.cjs.map
|
package/dist/rest-api.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.cjs","names":["id: ConstructName<TName>","config: RestApiConfig<TAuthorizers>","dependencies: readonly Dependency[]","authenticator?: string","DEFAULT_LOGGER","EndpointFactory","path: TPath","construct: Declarable","constructs: readonly Declarable[]","edgeTo"],"sources":["../src/rest-api.ts"],"sourcesContent":["/**\n * `RestApi` — the application's own HTTP surface, declared.\n *\n * Everything an app serves already existed; what did not exist was a *node* for\n * it. Without one, three things that are properties of the graph had to be\n * written down somewhere else: which origins the API accepts, which origins the\n * auth server trusts, and which URL a frontend is built against. All three were\n * being derived by walking the workspace config for ports — a second mechanism,\n * running beside the graph and answering the same question worse, since it can\n * only ever describe what this repo happens to run.\n *\n * With the surface declared, all three become one edge read backwards: a\n * consumer depends on the API, and *that* is the CORS entry, the trusted origin,\n * and the build-time URL. Nothing enumerates its own callers.\n *\n * ```ts\n * export const api = new RestApi('Api', {\n * authorizers: ['session'],\n * defaultAuthorizer: 'session',\n * });\n * ```\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Telescope } from '@geekmidas/telescope';\nimport { type Declarable, edgeTo } from './construct-interface';\nimport type { BuiltInSecuritySchemeId } from './endpoints/Authorizer';\nimport { EndpointFactory } from './endpoints/EndpointFactory';\nimport { envParserFor } from './endpoints/surfaceEnv';\n\nexport interface RestApiConfig<\n\t// `readonly []` rather than `readonly string[]`: a surface that declares no\n\t// authorizers of its own should narrow `defaultAuthorizer` to the built-ins\n\t// and `'none'`, not widen it back to any string.\n\tTAuthorizers extends readonly string[] = readonly [],\n> {\n\t/**\n\t * The app that serves this surface, relative to the workspace root —\n\t * `'apps/api'`, or `'.'` in a single-app project.\n\t *\n\t * Required. It was inferred from the id (`apps/<kebab-id>`, or the root\n\t * when no such directory existed), which meant a surface's home was\n\t * whatever happened to be on disk. It is where the app's `package.json`\n\t * and base `tsconfig.json` are, and which build serves this surface. It\n\t * does not change discovery: constructs still load from the workspace's\n\t * `constructs` glob.\n\t */\n\tpath: string;\n\t/**\n\t * CORS tunables. The *origins* are never here — they are read off the\n\t * constructs that declared an edge to this surface, which is the whole point\n\t * of declaring one. These are the parts a graph cannot answer.\n\t */\n\tcors?: {\n\t\tmaxAge?: number;\n\t\tcredentials?: boolean;\n\t\tallowHeaders?: readonly string[];\n\t\texposeHeaders?: readonly string[];\n\t};\n\t/**\n\t * The authorizer names this surface exposes. Names only — what verifies a\n\t * request legitimately differs between local and deployed, so the mechanism\n\t * belongs to the target and never to portable code.\n\t */\n\tauthorizers?: TAuthorizers;\n\t/**\n\t * The authorizer applied to an endpoint that names none.\n\t *\n\t * Required, and `'none'` is a valid answer that has to be typed out. The\n\t * alternative is an API that ships open because a field was left off, which\n\t * is the one default worth refusing to have.\n\t *\n\t * It was called `default`, which said what it was to the type and nothing\n\t * to the reader: next to `authorizers: ['iam']`, a bare `default: 'none'`\n\t * could be defaulting the runtime or the target. The manifest has always\n\t * called it `defaultAuthorizer` and the factory `defaultAuthorizerName`;\n\t * this is the same fact under the same name.\n\t *\n\t * Typed from `authorizers`, so it can only name one this surface exposes —\n\t * plus a built-in scheme, and `'none'`. It was a bare `string`, which let\n\t * `defaultAuthorizer: 'jwtt'` compile and every endpoint on the surface\n\t * default to an authorizer that does not exist.\n\t */\n\tdefaultAuthorizer: TAuthorizers[number] | BuiltInSecuritySchemeId | 'none';\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * The actual logger, not a path to one. It used to be named in config as\n\t * `logger: './config/logger'`, because the build wrote an import into each\n\t * generated handler and a generator can print a specifier but not an object.\n\t * Endpoints built from the surface carry it, so there is nothing to print\n\t * and nothing to keep in step with a moved file.\n\t */\n\tlogger?: Logger;\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Same reason as `logger`, and the same field it replaces —\n\t * `envParser: './config/env#envParser'`, a module path with an export name\n\t * appended, parsed by us, checked by nothing.\n\t */\n\tenvParser?: EnvironmentParser<{}>;\n\t/**\n\t * The Telescope instance this surface's entry mounts.\n\t *\n\t * The actual instance, for the reason `logger` is: it used to be named as\n\t * `telescope: './config/telescope#telescope'`, a module path the build\n\t * printed an import for. The entry already imports the surface.\n\t */\n\ttelescope?: Telescope;\n}\n\n/**\n * What is *not* here, and why.\n *\n * `dependsOn`, `database`, `auditor` and `publisher` all inject a capability\n * into a handler. Putting one on the surface hands it to every route the\n * surface serves — a health check gets the database because a report needed it\n * — which is the thing least privilege forbids and the reason `.calls()` is not\n * spelled `dependsOn`. They belong to the endpoint, or to a factory branched\n * from `api.endpoints` for a group of endpoints that genuinely share them.\n *\n * `auth` is not here either: `.auth(construct)` already declares it, and a\n * second way to say the same thing is the duplication this whole model removes.\n *\n * What is left is what the *process* is rather than what a route may reach.\n */\n\nexport class RestApi<\n\tTName extends string = string,\n\tconst TAuthorizers extends readonly string[] = readonly [],\n> implements Declarable<TName>\n{\n\treadonly id: TName;\n\n\t/**\n\t * Declared once and read by both `declare()` and anything that consumes this\n\t * surface, so what the target publishes and what a consumer reads cannot\n\t * drift.\n\t */\n\treadonly keys: {\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * Always defined — the surface's own if it was given one, the console\n\t * logger otherwise — so nothing downstream has to decide what to do about a\n\t * missing one. The generated entry reads it from here.\n\t */\n\treadonly logger: Logger;\n\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Always defined, and normally the default: `process.env` merged with the\n\t * credentials `gkm dev`/`gkm exec` injected. That is what every\n\t * application's own `config/env.ts` was — boilerplate identical in every\n\t * project, and mandatory, which is a poor combination.\n\t */\n\treadonly envParser: EnvironmentParser<{}>;\n\n\t/** The Telescope instance, when this surface was given one. */\n\treadonly telescope?: Telescope;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: RestApiConfig<TAuthorizers>,\n\t\t/** Internal: how `.calls()` carries edges into the copy it returns. */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t\t/** Internal: how `.auth()` carries the authenticator into its copy. */\n\t\tprivate readonly authenticator?: string,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\tthis.logger = config.logger ?? DEFAULT_LOGGER;\n\t\tthis.telescope = config.telescope;\n\t\tthis.envParser = envParserFor(\n\t\t\tconfig.envParser\n\t\t\t\t? { id: canonical, envParser: config.envParser }\n\t\t\t\t: undefined,\n\t\t);\n\n\t\tthis.endpoints = new EndpointFactory({\n\t\t\tdefaultLogger: this.logger,\n\t\t\t...(config.authorizers\n\t\t\t\t? {\n\t\t\t\t\t\tavailableAuthorizers: config.authorizers.map((name) => ({ name })),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// `'none'` reaches the factory as no default at all, which is what\n\t\t\t// public means to an endpoint that names no authorizer.\n\t\t\t...(config.defaultAuthorizer && config.defaultAuthorizer !== 'none'\n\t\t\t\t? { defaultAuthorizerName: config.defaultAuthorizer }\n\t\t\t\t: {}),\n\t\t\tsurface: { id: this.id, envParser: this.envParser },\n\t\t});\n\t}\n\n\t/**\n\t * This surface's endpoint factory.\n\t *\n\t * `api.get('/users')` is the short form and covers most routes. Reach for\n\t * this one when a *group* of endpoints shares something a single route would\n\t * otherwise repeat — a database, an auditor, a publisher:\n\t *\n\t * ```ts\n\t * const audited = api.endpoints.database(db).auditor(AuditStore);\n\t * export const createUser = audited.post('/users').handle(...);\n\t * ```\n\t *\n\t * A factory branched from here keeps the surface, so its endpoints still\n\t * know which API serves them. That is deliberately narrower than putting the\n\t * same thing in the surface's own config: a group opts in, where a surface\n\t * would grant it to every route it serves.\n\t */\n\treadonly endpoints: EndpointFactory<[], '', Logger>;\n\n\t/** `api.get('/users')` — sugar for `api.endpoints.get('/users')`. */\n\tget<TPath extends string>(path: TPath) {\n\t\treturn this.endpoints.get(path);\n\t}\n\n\tpost<TPath extends string>(path: TPath) {\n\t\treturn this.endpoints.post(path);\n\t}\n\n\tput<TPath extends string>(path: TPath) {\n\t\treturn this.endpoints.put(path);\n\t}\n\n\tpatch<TPath extends string>(path: TPath) {\n\t\treturn this.endpoints.patch(path);\n\t}\n\n\tdelete<TPath extends string>(path: TPath) {\n\t\treturn this.endpoints.delete(path);\n\t}\n\n\toptions<TPath extends string>(path: TPath) {\n\t\treturn this.endpoints.options(path);\n\t}\n\n\t/**\n\t * What authenticates this surface.\n\t *\n\t * An edge like `.calls()` — the auth server learns this origin, and the two\n\t * share a cookie domain — but a named one, because *who authenticates me* is\n\t * a different fact from *who I happen to call*, and only one of them decides\n\t * what a request is allowed to be. A surface with two of the first is a\n\t * mistake; two of the second is Tuesday.\n\t *\n\t * It records the id, not a client. Every endpoint consumes the same thing —\n\t * `verify(request) → Session | null` — and that is what every provider\n\t * shares, whether the server is mounted in this process, deployed beside it,\n\t * or an OIDC issuer somebody else runs.\n\t */\n\tauth(construct: Declarable): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, edgeTo(construct)],\n\t\t\tconstruct.id,\n\t\t);\n\t}\n\n\t/**\n\t * Other surfaces this one calls.\n\t *\n\t * **Not `.dependsOn()`, and the difference matters.** An endpoint's\n\t * `.dependsOn()` injects a client into that handler and grants it exactly\n\t * what it named. This grants nothing and injects nothing: it records that\n\t * this API calls another surface, which is what puts this API's origin on\n\t * that surface's trusted-origin list.\n\t *\n\t * Spelling it `dependsOn` would invite the thing least privilege forbids —\n\t * every route on the surface receiving whatever the surface named.\n\t *\n\t * Immutable, like every other builder here.\n\t */\n\tcalls(constructs: readonly Declarable[]): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, ...constructs.map(edgeTo)],\n\t\t\tthis.authenticator,\n\t\t);\n\t}\n\n\t/**\n\t * One surface node, with its endpoints left to the build.\n\t *\n\t * The three provided keys are one fact about this surface and two about its\n\t * callers. Declaring the caller-derived pair here rather than having the\n\t * target invent them is what makes the contract checkable: a target that\n\t * resolves no origins is caught against this list instead of being noticed\n\t * when a browser is refused in production.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\t...(this.config.telescope ? { telescope: true } : {}),\n\t\t\t\t...(this.config.cors ? { cors: this.config.cors } : {}),\n\t\t\t\t...(this.authenticator ? { auth: this.authenticator } : {}),\n\t\t\t\t// Filled by the build, which already generates one handler per\n\t\t\t\t// endpoint and knows the path it wrote it to. A surface that\n\t\t\t\t// enumerates its own routes statically — an auth server's single\n\t\t\t\t// wildcard — puts them here instead.\n\t\t\t\tendpoints: [],\n\t\t\t\t...(this.dependencies.length ? { calls: this.dependencies } : {}),\n\t\t\t\t...(this.config.authorizers?.length\n\t\t\t\t\t? { authorizers: this.config.authorizers }\n\t\t\t\t\t: {}),\n\t\t\t\tdefaultAuthorizer: this.config.defaultAuthorizer,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyIA,IAAa,UAAb,MAAa,QAIb;CACC,AAAS;;;;;;CAOT,AAAS;;;;;;;;CAaT,AAAS;;;;;;;;;CAUT,AAAS;;CAGT,AAAS;CAET,YACCA,IACiBC,QAEAC,eAAsC,CAAE,GAExCC,eAChB;EALgB;EAEA;EAEA;EAEjB,MAAM,YAAY,sCAAY,GAAa;AAE3C,OAAK,KAAK;AAEV,OAAK,OAAO;GACX,KAAK,qCAAW,WAAW,MAAM;GACjC,gBAAgB,qCAAW,WAAW,iBAAiB;GACvD,cAAc,qCAAW,WAAW,eAAe;EACnD;AAED,OAAK,SAAS,OAAO,UAAUC;AAC/B,OAAK,YAAY,OAAO;AACxB,OAAK,YAAY,gCAChB,OAAO,YACJ;GAAE,IAAI;GAAW,WAAW,OAAO;EAAW,WAEjD;AAED,OAAK,YAAY,IAAIC,wCAAgB;GACpC,eAAe,KAAK;GACpB,GAAI,OAAO,cACR,EACA,sBAAsB,OAAO,YAAY,IAAI,CAAC,UAAU,EAAE,KAAM,GAAE,CAClE,IACA,CAAE;GAGL,GAAI,OAAO,qBAAqB,OAAO,sBAAsB,SAC1D,EAAE,uBAAuB,OAAO,kBAAmB,IACnD,CAAE;GACL,SAAS;IAAE,IAAI,KAAK;IAAI,WAAW,KAAK;GAAW;EACnD;CACD;;;;;;;;;;;;;;;;;;CAmBD,AAAS;;CAGT,IAA0BC,MAAa;AACtC,SAAO,KAAK,UAAU,IAAI,KAAK;CAC/B;CAED,KAA2BA,MAAa;AACvC,SAAO,KAAK,UAAU,KAAK,KAAK;CAChC;CAED,IAA0BA,MAAa;AACtC,SAAO,KAAK,UAAU,IAAI,KAAK;CAC/B;CAED,MAA4BA,MAAa;AACxC,SAAO,KAAK,UAAU,MAAM,KAAK;CACjC;CAED,OAA6BA,MAAa;AACzC,SAAO,KAAK,UAAU,OAAO,KAAK;CAClC;CAED,QAA8BA,MAAa;AAC1C,SAAO,KAAK,UAAU,QAAQ,KAAK;CACnC;;;;;;;;;;;;;;;CAgBD,KAAKC,WAAqD;AACzD,SAAO,IAAI,QACV,KAAK,IACL,KAAK,QACL,CAAC,GAAG,KAAK,cAAc,mCAAO,UAAU,AAAC,GACzC,UAAU;CAEX;;;;;;;;;;;;;;;CAgBD,MAAMC,YAAiE;AACtE,SAAO,IAAI,QACV,KAAK,IACL,KAAK,QACL,CAAC,GAAG,KAAK,cAAc,GAAG,WAAW,IAAIC,mCAAO,AAAC,GACjD,KAAK;CAEN;;;;;;;;;;CAWD,UAAyB;AACxB,SAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,MAAM,KAAK,OAAO;GAClB,GAAI,KAAK,OAAO,YAAY,EAAE,WAAW,KAAM,IAAG,CAAE;GACpD,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,OAAO,KAAM,IAAG,CAAE;GACtD,GAAI,KAAK,gBAAgB,EAAE,MAAM,KAAK,cAAe,IAAG,CAAE;GAK1D,WAAW,CAAE;GACb,GAAI,KAAK,aAAa,SAAS,EAAE,OAAO,KAAK,aAAc,IAAG,CAAE;GAChE,GAAI,KAAK,OAAO,aAAa,SAC1B,EAAE,aAAa,KAAK,OAAO,YAAa,IACxC,CAAE;GACL,mBAAmB,KAAK,OAAO;GAC/B,UAAU;IACT,KAAK,KAAK;IACV,KAAK,KAAK;IACV,KAAK,KAAK;GACV;EACD,CACD;CACD;AACD"}
|
|
1
|
+
{"version":3,"file":"rest-api.cjs","names":["canonicalId","provideKey","DEFAULT_LOGGER","envParserFor","EndpointFactory","getSecurityScheme","edgeTo"],"sources":["../src/rest-api.ts"],"sourcesContent":["/**\n * `RestApi` — the application's own HTTP surface, declared.\n *\n * Everything an app serves already existed; what did not exist was a *node* for\n * it. Without one, three things that are properties of the graph had to be\n * written down somewhere else: which origins the API accepts, which origins the\n * auth server trusts, and which URL a frontend is built against. All three were\n * being derived by walking the workspace config for ports — a second mechanism,\n * running beside the graph and answering the same question worse, since it can\n * only ever describe what this repo happens to run.\n *\n * With the surface declared, all three become one edge read backwards: a\n * consumer depends on the API, and *that* is the CORS entry, the trusted origin,\n * and the build-time URL. Nothing enumerates its own callers.\n *\n * ```ts\n * export const api = new RestApi('Api', {\n * authorizers: ['session'],\n * defaultAuthorizer: 'session',\n * });\n * ```\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Telescope } from '@geekmidas/telescope';\nimport {\n\ttype Authenticator,\n\ttype Declarable,\n\tedgeTo,\n} from './construct-interface';\n\nexport type { Authenticator } from './construct-interface';\n\nimport {\n\ttype BuiltInSecuritySchemeId,\n\tgetSecurityScheme,\n} from './endpoints/Authorizer';\nimport { EndpointFactory } from './endpoints/EndpointFactory';\nimport { envParserFor } from './endpoints/surfaceEnv';\nimport type { Telemetry } from './telemetry';\n\n/** The factory a surface builds its endpoints from. */\ntype Endpoints = EndpointFactory<[], '', Logger>;\n\nexport interface RestApiConfig<\n\t// `readonly []` rather than `readonly string[]`: a surface that declares no\n\t// authorizers of its own should narrow `defaultAuthorizer` to the built-ins\n\t// and `'none'`, not widen it back to any string.\n\tTAuthorizers extends readonly string[] = readonly [],\n> {\n\t/**\n\t * The app that serves this surface, relative to the workspace root —\n\t * `'apps/api'`, or `'.'` in a single-app project.\n\t *\n\t * Required. It was inferred from the id (`apps/<kebab-id>`, or the root\n\t * when no such directory existed), which meant a surface's home was\n\t * whatever happened to be on disk. It is where the app's `package.json`\n\t * and base `tsconfig.json` are, and which build serves this surface. It\n\t * does not change discovery: constructs still load from the workspace's\n\t * `constructs` glob.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * CORS tunables. The *origins* are never here — they are read off the\n\t * constructs that declared an edge to this surface, which is the whole point\n\t * of declaring one. These are the parts a graph cannot answer.\n\t */\n\tcors?: {\n\t\tmaxAge?: number;\n\t\tcredentials?: boolean;\n\t\tallowHeaders?: readonly string[];\n\t\texposeHeaders?: readonly string[];\n\t};\n\t/**\n\t * The authorizer names this surface exposes. Names only — what verifies a\n\t * request legitimately differs between local and deployed, so the mechanism\n\t * belongs to the target and never to portable code.\n\t */\n\tauthorizers?: TAuthorizers;\n\t/**\n\t * The authorizer applied to an endpoint that names none.\n\t *\n\t * Required, and `'none'` is a valid answer that has to be typed out. The\n\t * alternative is an API that ships open because a field was left off, which\n\t * is the one default worth refusing to have.\n\t *\n\t * It was called `default`, which said what it was to the type and nothing\n\t * to the reader: next to `authorizers: ['iam']`, a bare `default: 'none'`\n\t * could be defaulting the runtime or the target. The manifest has always\n\t * called it `defaultAuthorizer` and the factory `defaultAuthorizerName`;\n\t * this is the same fact under the same name.\n\t *\n\t * Typed from `authorizers`, so it can only name one this surface exposes —\n\t * plus a built-in scheme, and `'none'`. It was a bare `string`, which let\n\t * `defaultAuthorizer: 'jwtt'` compile and every endpoint on the surface\n\t * default to an authorizer that does not exist.\n\t */\n\tdefaultAuthorizer: TAuthorizers[number] | BuiltInSecuritySchemeId | 'none';\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * The actual logger, not a path to one. It used to be named in config as\n\t * `logger: './config/logger'`, because the build wrote an import into each\n\t * generated handler and a generator can print a specifier but not an object.\n\t * Endpoints built from the surface carry it, so there is nothing to print\n\t * and nothing to keep in step with a moved file.\n\t */\n\tlogger?: Logger;\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Same reason as `logger`, and the same field it replaces —\n\t * `envParser: './config/env#envParser'`, a module path with an export name\n\t * appended, parsed by us, checked by nothing.\n\t */\n\tenvParser?: EnvironmentParser<{}>;\n\t/**\n\t * The Telescope instance this surface's entry mounts.\n\t *\n\t * The actual instance, for the reason `logger` is: it used to be named as\n\t * `telescope: './config/telescope#telescope'`, a module path the build\n\t * printed an import for. The entry already imports the surface.\n\t */\n\ttelescope?: Telescope;\n\t/**\n\t * What this surface's process emits — the `Telemetry` construct, passed\n\t * like the logger: a fact about the process, not a grant to a route.\n\t *\n\t * An edge to the node, so the deploy hands this process the `OTEL_*` keys\n\t * its stage resolves, and the build fails without the OpenTelemetry\n\t * packages. Where the telemetry goes is never here: it is the deploy's,\n\t * per stage.\n\t */\n\ttelemetry?: Telemetry;\n}\n\n/**\n * What is *not* here, and why.\n *\n * `dependsOn`, `database`, `auditor` and `.event(topic, …)` all inject a capability\n * into a handler. Putting one on the surface hands it to every route the\n * surface serves — a health check gets the database because a report needed it\n * — which is the thing least privilege forbids and the reason `.calls()` is not\n * spelled `dependsOn`. They belong to the endpoint, or to a branch —\n * `api.database(db)` — for a group of endpoints that genuinely share them. A\n * branch is a new factory; the surface config stays free of grants.\n *\n * `auth` is not here either: `.auth(construct)` already declares it, and a\n * second way to say the same thing is the duplication this whole model removes.\n *\n * What is left is what the *process* is rather than what a route may reach.\n */\n\nexport class RestApi<\n\tTName extends string = string,\n\tconst TAuthorizers extends readonly string[] = readonly [],\n> implements Declarable<TName>\n{\n\treadonly id: TName;\n\n\t/**\n\t * Declared once and read by both `declare()` and anything that consumes this\n\t * surface, so what the target publishes and what a consumer reads cannot\n\t * drift.\n\t */\n\treadonly keys: {\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * Always defined — the surface's own if it was given one, the console\n\t * logger otherwise — so nothing downstream has to decide what to do about a\n\t * missing one. The generated entry reads it from here.\n\t */\n\treadonly logger: Logger;\n\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Always defined, and normally the default: `process.env` merged with the\n\t * credentials `gkm dev`/`gkm exec` injected. That is what every\n\t * application's own `config/env.ts` was — boilerplate identical in every\n\t * project, and mandatory, which is a poor combination.\n\t */\n\treadonly envParser: EnvironmentParser<{}>;\n\n\t/** The Telescope instance, when this surface was given one. */\n\treadonly telescope?: Telescope;\n\n\t/** What this surface's process emits, when it was given a `Telemetry`. */\n\treadonly telemetry?: Telemetry;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: RestApiConfig<TAuthorizers>,\n\t\t/** Internal: how `.calls()` carries edges into the copy it returns. */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t\t/** Internal: how `.auth()` carries the authenticator into its copy. */\n\t\tprivate readonly authenticator?: Authenticator,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\tthis.logger = config.logger ?? DEFAULT_LOGGER;\n\t\tthis.telescope = config.telescope;\n\t\tthis.telemetry = config.telemetry;\n\t\tthis.envParser = envParserFor(\n\t\t\tconfig.envParser\n\t\t\t\t? { id: canonical, envParser: config.envParser }\n\t\t\t\t: undefined,\n\t\t);\n\n\t\tthis.#endpoints = new EndpointFactory({\n\t\t\tdefaultLogger: this.logger,\n\t\t\t...(config.authorizers\n\t\t\t\t? {\n\t\t\t\t\t\t// With the scheme a built-in name stands for, as the factory's own\n\t\t\t\t\t\t// `.authorizers()` resolves it. Bare names reached the OpenAPI\n\t\t\t\t\t\t// document with no scheme, so a guarded endpoint read as public.\n\t\t\t\t\t\tavailableAuthorizers: config.authorizers.map((name) => {\n\t\t\t\t\t\t\tconst securityScheme = getSecurityScheme(name);\n\t\t\t\t\t\t\treturn securityScheme ? { name, securityScheme } : { name };\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// `'none'` reaches the factory as no default at all, which is what\n\t\t\t// public means to an endpoint that names no authorizer.\n\t\t\t...(config.defaultAuthorizer && config.defaultAuthorizer !== 'none'\n\t\t\t\t? { defaultAuthorizerName: config.defaultAuthorizer }\n\t\t\t\t: {}),\n\t\t\tsurface: {\n\t\t\t\tid: this.id,\n\t\t\t\tenvParser: this.envParser,\n\t\t\t\t...(authenticator ? { auth: authenticator } : {}),\n\t\t\t},\n\t\t});\n\n\t\tconst endpoints = this.#endpoints;\n\t\tthis.get = endpoints.get.bind(endpoints);\n\t\tthis.post = endpoints.post.bind(endpoints);\n\t\tthis.put = endpoints.put.bind(endpoints);\n\t\tthis.patch = endpoints.patch.bind(endpoints);\n\t\tthis.delete = endpoints.delete.bind(endpoints);\n\t\tthis.options = endpoints.options.bind(endpoints);\n\t\tthis.database = endpoints.database.bind(endpoints);\n\t\tthis.session = endpoints.session.bind(endpoints);\n\t\tthis.auditor = endpoints.auditor.bind(endpoints);\n\t\tthis.actor = endpoints.actor.bind(endpoints);\n\t\tthis.authorizer = endpoints.authorizer.bind(endpoints);\n\t\tthis.authorize = endpoints.authorize.bind(endpoints);\n\t\tthis.rls = endpoints.rls.bind(endpoints);\n\t\tthis.route = endpoints.route.bind(endpoints);\n\t}\n\n\t/**\n\t * This surface's endpoint factory, which every method below starts from.\n\t *\n\t * Private: a surface *is* where endpoints are built, so there is no second\n\t * object to reach through first. `api.database(db)` said the same\n\t * thing as `api.database(db)` with one more word to learn.\n\t */\n\treadonly #endpoints: Endpoints;\n\n\t/** `api.get('/users')` — one route on this surface. */\n\treadonly get: Endpoints['get'];\n\treadonly post: Endpoints['post'];\n\treadonly put: Endpoints['put'];\n\treadonly patch: Endpoints['patch'];\n\treadonly delete: Endpoints['delete'];\n\treadonly options: Endpoints['options'];\n\n\t/**\n\t * A branch for a *group* of endpoints that shares what a single route would\n\t * otherwise repeat — a database, an auditor, a publisher, a session:\n\t *\n\t * ```ts\n\t * export const router = api.database(database);\n\t * export const createUser = router.post('/users').handle(({ db }) => …);\n\t * ```\n\t *\n\t * Each returns a new factory and leaves the surface as it was. That is the\n\t * whole difference from putting the same thing in the surface's config: a\n\t * group opts in, where the surface would grant it to every route it serves —\n\t * a health check handed the database because a profile endpoint needed it.\n\t *\n\t * `dependsOn` is deliberately not here. It is per endpoint, and on the\n\t * surface it would read as the API depending on something, which is what\n\t * `.calls()` and `.auth()` say instead.\n\t */\n\treadonly database: Endpoints['database'];\n\treadonly session: Endpoints['session'];\n\treadonly auditor: Endpoints['auditor'];\n\treadonly actor: Endpoints['actor'];\n\treadonly authorizer: Endpoints['authorizer'];\n\treadonly authorize: Endpoints['authorize'];\n\treadonly rls: Endpoints['rls'];\n\treadonly route: Endpoints['route'];\n\n\t/**\n\t * What authenticates this surface.\n\t *\n\t * An edge like `.calls()` — the auth server learns this origin, and the two\n\t * share a cookie domain — but a named one, because *who authenticates me* is\n\t * a different fact from *who I happen to call*, and only one of them decides\n\t * what a request is allowed to be. A surface with two of the first is a\n\t * mistake; two of the second is Tuesday.\n\t *\n\t * It declares the id, and keeps the construct: a session callback reads from\n\t * it as `auth` — `session(async ({ auth }) => auth.getSession())` — which\n\t * calls the construct's `verify(headers)`. That is the one thing every\n\t * provider shares, whether the server is mounted in this process, deployed\n\t * beside it, or an OIDC issuer somebody else runs.\n\t */\n\tauth(construct: Authenticator): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, edgeTo(construct)],\n\t\t\tconstruct,\n\t\t);\n\t}\n\n\t/**\n\t * Other surfaces this one calls.\n\t *\n\t * **Not `.dependsOn()`, and the difference matters.** An endpoint's\n\t * `.dependsOn()` injects a client into that handler and grants it exactly\n\t * what it named. This grants nothing and injects nothing: it records that\n\t * this API calls another surface, which is what puts this API's origin on\n\t * that surface's trusted-origin list.\n\t *\n\t * Spelling it `dependsOn` would invite the thing least privilege forbids —\n\t * every route on the surface receiving whatever the surface named.\n\t *\n\t * Immutable, like every other builder here.\n\t */\n\tcalls(constructs: readonly Declarable[]): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, ...constructs.map(edgeTo)],\n\t\t\tthis.authenticator,\n\t\t);\n\t}\n\n\t/**\n\t * One surface node, with its endpoints left to the build.\n\t *\n\t * The three provided keys are one fact about this surface and two about its\n\t * callers. Declaring the caller-derived pair here rather than having the\n\t * target invent them is what makes the contract checkable: a target that\n\t * resolves no origins is caught against this list instead of being noticed\n\t * when a browser is refused in production.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\t...(this.config.telescope ? { telescope: true } : {}),\n\t\t\t\t...(this.config.cors ? { cors: this.config.cors } : {}),\n\t\t\t\t...(this.authenticator ? { auth: this.authenticator.id } : {}),\n\t\t\t\t// Filled by the build, which already generates one handler per\n\t\t\t\t// endpoint and knows the path it wrote it to. A surface that\n\t\t\t\t// enumerates its own routes statically — an auth server's single\n\t\t\t\t// wildcard — puts them here instead.\n\t\t\t\tendpoints: [],\n\t\t\t\t...(this.dependencies.length ? { calls: this.dependencies } : {}),\n\t\t\t\t...(this.config.telemetry\n\t\t\t\t\t? { telemetry: this.config.telemetry.id }\n\t\t\t\t\t: {}),\n\t\t\t\t...(this.config.authorizers?.length\n\t\t\t\t\t? { authorizers: this.config.authorizers }\n\t\t\t\t\t: {}),\n\t\t\t\tdefaultAuthorizer: this.config.defaultAuthorizer,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwKA,IAAa,UAAb,MAAa,QAIb;CAyCmB;CAEA;CAEA;CA5ClB;;;;;;CAOA;;;;;;;;CAaA;;;;;;;;;CAUA;;CAGA;;CAGA;CAEA,YACC,IACA,QAEA,eAAuD,CAAC,GAExD,eACC;EALgB,KAAA,SAAA;EAEA,KAAA,eAAA;EAEA,KAAA,gBAAA;EAEjB,MAAM,aAAA,GAAYA,oBAAAA,YAAAA,CAAY,EAAY;EAE1C,KAAK,KAAK;EAEV,KAAK,OAAO;GACX,MAAA,GAAKC,oBAAAA,WAAAA,CAAW,WAAW,KAAK;GAChC,iBAAA,GAAgBA,oBAAAA,WAAAA,CAAW,WAAW,gBAAgB;GACtD,eAAA,GAAcA,oBAAAA,WAAAA,CAAW,WAAW,cAAc;EACnD;EAEA,KAAK,SAAS,OAAO,UAAUC,0BAAAA;EAC/B,KAAK,YAAY,OAAO;EACxB,KAAK,YAAY,OAAO;EACxB,KAAK,YAAYC,mBAAAA,aAChB,OAAO,YACJ;GAAE,IAAI;GAAW,WAAW,OAAO;EAAU,IAC7C,KAAA,CACJ;EAEA,KAAK,aAAa,IAAIC,wBAAAA,gBAAgB;GACrC,eAAe,KAAK;GACpB,GAAI,OAAO,cACR,EAIA,sBAAsB,OAAO,YAAY,KAAK,SAAS;IACtD,MAAM,iBAAiBC,wBAAAA,kBAAkB,IAAI;IAC7C,OAAO,iBAAiB;KAAE;KAAM;IAAe,IAAI,EAAE,KAAK;GAC3D,CAAC,EACF,IACC,CAAC;GAGJ,GAAI,OAAO,qBAAqB,OAAO,sBAAsB,SAC1D,EAAE,uBAAuB,OAAO,kBAAkB,IAClD,CAAC;GACJ,SAAS;IACR,IAAI,KAAK;IACT,WAAW,KAAK;IAChB,GAAI,gBAAgB,EAAE,MAAM,cAAc,IAAI,CAAC;GAChD;EACD,CAAC;EAED,MAAM,YAAY,KAAK;EACvB,KAAK,MAAM,UAAU,IAAI,KAAK,SAAS;EACvC,KAAK,OAAO,UAAU,KAAK,KAAK,SAAS;EACzC,KAAK,MAAM,UAAU,IAAI,KAAK,SAAS;EACvC,KAAK,QAAQ,UAAU,MAAM,KAAK,SAAS;EAC3C,KAAK,SAAS,UAAU,OAAO,KAAK,SAAS;EAC7C,KAAK,UAAU,UAAU,QAAQ,KAAK,SAAS;EAC/C,KAAK,WAAW,UAAU,SAAS,KAAK,SAAS;EACjD,KAAK,UAAU,UAAU,QAAQ,KAAK,SAAS;EAC/C,KAAK,UAAU,UAAU,QAAQ,KAAK,SAAS;EAC/C,KAAK,QAAQ,UAAU,MAAM,KAAK,SAAS;EAC3C,KAAK,aAAa,UAAU,WAAW,KAAK,SAAS;EACrD,KAAK,YAAY,UAAU,UAAU,KAAK,SAAS;EACnD,KAAK,MAAM,UAAU,IAAI,KAAK,SAAS;EACvC,KAAK,QAAQ,UAAU,MAAM,KAAK,SAAS;CAC5C;;;;;;;;CASA;;CAGA;CACA;CACA;CACA;CACA;CACA;;;;;;;;;;;;;;;;;;;CAoBA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;;;;;;;;;;;;;;;CAiBA,KAAK,WAAwD;EAC5D,OAAO,IAAI,QACV,KAAK,IACL,KAAK,QACL,CAAC,GAAG,KAAK,cAAcC,4BAAAA,OAAO,SAAS,CAAC,GACxC,SACD;CACD;;;;;;;;;;;;;;;CAgBA,MAAM,YAAiE;EACtE,OAAO,IAAI,QACV,KAAK,IACL,KAAK,QACL,CAAC,GAAG,KAAK,cAAc,GAAG,WAAW,IAAIA,4BAAAA,MAAM,CAAC,GAChD,KAAK,aACN;CACD;;;;;;;;;;CAWA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,MAAM,KAAK,OAAO;GAClB,GAAI,KAAK,OAAO,YAAY,EAAE,WAAW,KAAK,OAAO,UAAU,IAAI,CAAC;GACpE,GAAI,KAAK,OAAO,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;GACnD,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,OAAO,KAAK,IAAI,CAAC;GACrD,GAAI,KAAK,gBAAgB,EAAE,MAAM,KAAK,cAAc,GAAG,IAAI,CAAC;GAK5D,WAAW,CAAC;GACZ,GAAI,KAAK,aAAa,SAAS,EAAE,OAAO,KAAK,aAAa,IAAI,CAAC;GAC/D,GAAI,KAAK,OAAO,YACb,EAAE,WAAW,KAAK,OAAO,UAAU,GAAG,IACtC,CAAC;GACJ,GAAI,KAAK,OAAO,aAAa,SAC1B,EAAE,aAAa,KAAK,OAAO,YAAY,IACvC,CAAC;GACJ,mBAAmB,KAAK,OAAO;GAC/B,UAAU;IACT,KAAK,KAAK;IACV,KAAK,KAAK;IACV,KAAK,KAAK;GACX;EACD,CACD;CACD;AACD"}
|