theokit 0.4.0-beta.0 → 0.5.0
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/dist/{actions-virtual-module-HWNTIEPI.js → actions-virtual-module-IY46EEEX.js} +2 -2
- package/dist/{actions-virtual-module-SEIPACG5.js → actions-virtual-module-XNHDNCZ6.js} +2 -2
- package/dist/add-2ZFFGGE4.js +0 -0
- package/dist/{app-typed-client-ITIYTBXO.js → app-typed-client-OUJO3V5J.js} +10 -5
- package/dist/{app-typed-client-62FYBLVJ.js.map → app-typed-client-OUJO3V5J.js.map} +1 -1
- package/dist/{app-typed-client-62FYBLVJ.js → app-typed-client-YOMWSA3F.js} +11 -6
- package/dist/{app-typed-client-ITIYTBXO.js.map → app-typed-client-YOMWSA3F.js.map} +1 -1
- package/dist/audit-log-BQWM5YLG.d.ts +60 -0
- package/dist/{aws-lambda-XYCGZH3I.js → aws-lambda-GKSTX6HD.js} +3 -3
- package/dist/body-parser-web-MUWBKZ3F.js +70 -0
- package/dist/body-parser-web-MUWBKZ3F.js.map +1 -0
- package/dist/broadcast-QOQGUNNK.js +0 -0
- package/dist/{build-HIGHJQQV.js → build-D4J7XECU.js} +31 -22
- package/dist/build-D4J7XECU.js.map +1 -0
- package/dist/build-request-body-preview-II2235E6.js +0 -0
- package/dist/{bun-K73TQEWC.js → bun-ISHSNFIO.js} +3 -3
- package/dist/{check-PZR67OY5.js → check-NXYPLM6M.js} +2 -2
- package/dist/{chunk-WZ7CPVQB.js → chunk-27LWMYNK.js} +28 -24
- package/dist/chunk-27LWMYNK.js.map +1 -0
- package/dist/{chunk-BIGBFZSU.js → chunk-2F4FROEQ.js} +1689 -1521
- package/dist/chunk-2F4FROEQ.js.map +1 -0
- package/dist/chunk-4HBI7DHP.js +20 -0
- package/dist/chunk-4HBI7DHP.js.map +1 -0
- package/dist/{chunk-X4JAX2U3.js → chunk-4S2UCILN.js} +180 -125
- package/dist/chunk-4S2UCILN.js.map +1 -0
- package/dist/{chunk-C52GSJPF.js → chunk-4S6YHNG2.js} +11 -8
- package/dist/chunk-4S6YHNG2.js.map +1 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/{chunk-KJVEJILQ.js → chunk-5PU65T5W.js} +9 -3
- package/dist/chunk-5PU65T5W.js.map +1 -0
- package/dist/chunk-5QW7IQQU.js +36 -0
- package/dist/chunk-5QW7IQQU.js.map +1 -0
- package/dist/chunk-5Z2727GV.js +1324 -0
- package/dist/chunk-5Z2727GV.js.map +1 -0
- package/dist/{chunk-YLBSSEHX.js → chunk-6NEXVBPY.js} +5 -15
- package/dist/chunk-6NEXVBPY.js.map +1 -0
- package/dist/chunk-7MQOHNHE.js +36 -0
- package/dist/chunk-7MQOHNHE.js.map +1 -0
- package/dist/{chunk-TPCT3MXV.js → chunk-ABVITXFH.js} +405 -272
- package/dist/chunk-ABVITXFH.js.map +1 -0
- package/dist/chunk-ASDXVRJD.js +185 -0
- package/dist/chunk-ASDXVRJD.js.map +1 -0
- package/dist/chunk-B3L3TJVF.js +406 -0
- package/dist/chunk-B3L3TJVF.js.map +1 -0
- package/dist/{chunk-PB4GR7EP.js → chunk-C3ZZ56YZ.js} +1 -18
- package/dist/chunk-C3ZZ56YZ.js.map +1 -0
- package/dist/{chunk-O7335ZGH.js → chunk-CG563V5M.js} +5 -3
- package/dist/chunk-CG563V5M.js.map +1 -0
- package/dist/{chunk-5OFPQK6N.js → chunk-COXLEZCN.js} +2 -1
- package/dist/chunk-COXLEZCN.js.map +1 -0
- package/dist/{chunk-O4BLVPML.js → chunk-DNMSV3R3.js} +22 -28
- package/dist/chunk-DNMSV3R3.js.map +1 -0
- package/dist/chunk-E3JH6YUM.js +1 -0
- package/dist/chunk-ESC54TAK.js +220 -0
- package/dist/chunk-ESC54TAK.js.map +1 -0
- package/dist/chunk-FI7MPTJW.js +77 -0
- package/dist/chunk-FI7MPTJW.js.map +1 -0
- package/dist/chunk-H4J2LECY.js +201 -0
- package/dist/chunk-H4J2LECY.js.map +1 -0
- package/dist/chunk-IAJ2JVEH.js +256 -0
- package/dist/chunk-IAJ2JVEH.js.map +1 -0
- package/dist/{chunk-F3TG5FJV.js → chunk-IEZCAT2I.js} +7 -1
- package/dist/{chunk-F3TG5FJV.js.map → chunk-IEZCAT2I.js.map} +1 -1
- package/dist/chunk-JHEAWR3L.js +1 -0
- package/dist/chunk-JQSKBMXP.js +17 -0
- package/dist/chunk-JQSKBMXP.js.map +1 -0
- package/dist/{chunk-4QTKSLTO.js → chunk-K4M64WD6.js} +9 -5
- package/dist/{chunk-4QTKSLTO.js.map → chunk-K4M64WD6.js.map} +1 -1
- package/dist/chunk-KI7OWQUX.js +293 -0
- package/dist/chunk-KI7OWQUX.js.map +1 -0
- package/dist/chunk-KT3MWNZG.js +0 -0
- package/dist/{chunk-ZJW5FFYJ.js → chunk-LC5PYNJP.js} +2 -2
- package/dist/{chunk-JAAHOGKI.js → chunk-M2EY7KDR.js} +1223 -1124
- package/dist/chunk-M2EY7KDR.js.map +1 -0
- package/dist/{chunk-2VQKDRVF.js → chunk-MR7OLIC6.js} +3 -3
- package/dist/chunk-NM4WF3XD.js +63 -0
- package/dist/chunk-NM4WF3XD.js.map +1 -0
- package/dist/chunk-NPMCKOX4.js +1 -0
- package/dist/{chunk-VRHXOKRP.js → chunk-NZXH2LQQ.js} +86 -83
- package/dist/chunk-NZXH2LQQ.js.map +1 -0
- package/dist/{chunk-B6RP57M3.js → chunk-OLPB36O2.js} +4 -4
- package/dist/chunk-PIVX3DYW.js +142 -0
- package/dist/chunk-PIVX3DYW.js.map +1 -0
- package/dist/{chunk-7TOMTMHT.js → chunk-R7OC6UDK.js} +2 -1
- package/dist/chunk-R7OC6UDK.js.map +1 -0
- package/dist/chunk-RESN62GB.js +269 -0
- package/dist/chunk-RESN62GB.js.map +1 -0
- package/dist/{chunk-64JI5LTO.js → chunk-RMU7EBRO.js} +2 -2
- package/dist/chunk-RMU7EBRO.js.map +1 -0
- package/dist/chunk-TQYSPYKU.js +131 -0
- package/dist/chunk-TQYSPYKU.js.map +1 -0
- package/dist/chunk-TSLSIRBR.js +107 -0
- package/dist/chunk-TSLSIRBR.js.map +1 -0
- package/dist/{chunk-XP7YXRHO.js → chunk-U6TPSW4L.js} +37 -5
- package/dist/chunk-U6TPSW4L.js.map +1 -0
- package/dist/chunk-UD3LFGDL.js +45 -0
- package/dist/chunk-UD3LFGDL.js.map +1 -0
- package/dist/chunk-UUFYP2UM.js +161 -0
- package/dist/chunk-UUFYP2UM.js.map +1 -0
- package/dist/{chunk-OXIQI2XZ.js → chunk-VBZCVFSA.js} +2 -2
- package/dist/chunk-VMEWD57H.js +137 -0
- package/dist/chunk-VMEWD57H.js.map +1 -0
- package/dist/{chunk-TRH2L3FI.js → chunk-WEGALZEU.js} +3 -3
- package/dist/{chunk-ZOXNJV2O.js → chunk-WGHJD6I7.js} +35 -138
- package/dist/chunk-WGHJD6I7.js.map +1 -0
- package/dist/chunk-XMS5VRIK.js +0 -0
- package/dist/chunk-Y4H3JNVK.js +18 -0
- package/dist/chunk-Y4H3JNVK.js.map +1 -0
- package/dist/chunk-Z5IGLBWE.js +329 -0
- package/dist/chunk-Z5IGLBWE.js.map +1 -0
- package/dist/chunk-ZEGYW52B.js +26 -0
- package/dist/chunk-ZEGYW52B.js.map +1 -0
- package/dist/chunk-ZJQ4O76G.js +87 -0
- package/dist/chunk-ZJQ4O76G.js.map +1 -0
- package/dist/cli/index.js +32 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +107 -1
- package/dist/client/index.js +152 -6
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-LCAOTRYZ.js → cloudflare-OJGJ7R2D.js} +3 -3
- package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
- package/dist/cookies-MJKMGJ7N.js +22 -0
- package/dist/csrf-BBrEZSBW.d.ts +107 -0
- package/dist/csrf-readiness-store-CjIoub3U.d.ts +43 -0
- package/dist/define-websocket-CdK94O-D.d.ts +64 -0
- package/dist/{deno-deploy-ZN4RE5HF.js → deno-deploy-BHWKFTOW.js} +3 -3
- package/dist/{dev-266MVCVA.js → dev-MJVSRZGF.js} +11 -11
- package/dist/{dev-emit-RBSFZZNO.js → dev-emit-5VPRCHOD.js} +39 -142
- package/dist/dev-emit-5VPRCHOD.js.map +1 -0
- package/dist/{dev-emit-NO6OZJOQ.js → dev-emit-HHP2XJXE.js} +5 -5
- package/dist/devtools/entry.js +185 -131
- package/dist/devtools/entry.js.map +1 -1
- package/dist/{dispatcher-AQJGI3HU.js → dispatcher-63LBXYLE.js} +2 -2
- package/dist/{dispatcher-E6QV32BO.js → dispatcher-EJME6C6M.js} +5 -2
- package/dist/dispatcher-EJME6C6M.js.map +1 -0
- package/dist/{dist-5V77Z223.js → dist-KRW6OVD4.js} +7 -7
- package/dist/dist-KRW6OVD4.js.map +1 -0
- package/dist/docker-EZDA5FT7.js +0 -0
- package/dist/error-envelope-BsNzzAV5.d.ts +62 -0
- package/dist/{generate-DT4IIAHF.js → generate-AZ2K6Z2Z.js} +75 -2
- package/dist/generate-AZ2K6Z2Z.js.map +1 -0
- package/dist/index-DWWEdFh5.d.ts +399 -0
- package/dist/index.d.ts +161 -1391
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/dist/{info-FJ4NXKTB.js → info-47VB62MV.js} +5 -5
- package/dist/job-backend-CgC8Xf33.d.ts +68 -0
- package/dist/{lib-NL5JXGEB.js → lib-NST3PNZX.js} +31 -31
- package/dist/lib-NST3PNZX.js.map +1 -0
- package/dist/module-loader-Cfz7dgCP.d.ts +26 -0
- package/dist/{netlify-5VQ22Z2P.js → netlify-GSNSJGMN.js} +3 -3
- package/dist/{node-3APTLGWB.js → node-6I5B6RL3.js} +3 -3
- package/dist/node-6I5B6RL3.js.map +1 -0
- package/dist/{openapi-FNVSWZOL.js → openapi-NFLSNNVQ.js} +10 -10
- package/dist/plugin-runner-BGBkzgi0.d.ts +95 -0
- package/dist/plugin-types-DNJGxr4Z.d.ts +79 -0
- package/dist/{proper-lockfile-4Y3DP3RP.js → proper-lockfile-MVKMQGFK.js} +27 -27
- package/dist/proper-lockfile-MVKMQGFK.js.map +1 -0
- package/dist/rate-limit-BdNDZ3vt.d.ts +58 -0
- package/dist/registry-QHEZ3BWB.js +25 -0
- package/dist/router-RGZFX75G.js +0 -0
- package/dist/{routes-H4SPLFHJ.js → routes-3A4XGIEJ.js} +6 -6
- package/dist/{scan-C2BOKLSY.js → scan-NQFI5S7P.js} +2 -2
- package/dist/scan-NQFI5S7P.js.map +1 -0
- package/dist/schema-D3v8VB7o.d.ts +76 -0
- package/dist/{schema-VR6YNVLQ.js → schema-KZVOV333.js} +5 -3
- package/dist/schema-KZVOV333.js.map +1 -0
- package/dist/server/agent/index.d.ts +229 -0
- package/dist/server/agent/index.js +16 -0
- package/dist/server/agent/index.js.map +1 -0
- package/dist/server/auth/index.d.ts +46 -1
- package/dist/server/auth/index.js +10 -3
- package/dist/server/cost/index.d.ts +1 -3
- package/dist/server/cost/index.js +1 -1
- package/dist/server/cron/index.js +4 -2
- package/dist/server/define/index.d.ts +281 -0
- package/dist/server/define/index.js +26 -0
- package/dist/server/define/index.js.map +1 -0
- package/dist/server/http/index.d.ts +10 -0
- package/dist/server/http/index.js +74 -0
- package/dist/server/http/index.js.map +1 -0
- package/dist/server/index.d.ts +151 -1677
- package/dist/server/index.js +558 -1102
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +348 -2
- package/dist/server/jobs/index.js +5 -3
- package/dist/server/observability/index.d.ts +324 -0
- package/dist/server/observability/index.js +48 -0
- package/dist/server/observability/index.js.map +1 -0
- package/dist/server/plugins/index.d.ts +17 -0
- package/dist/server/plugins/index.js +17 -0
- package/dist/server/plugins/index.js.map +1 -0
- package/dist/server/rate-limit/index.d.ts +105 -0
- package/dist/server/rate-limit/index.js +25 -0
- package/dist/server/rate-limit/index.js.map +1 -0
- package/dist/server/realtime/index.d.ts +15 -0
- package/dist/server/realtime/index.js +8 -0
- package/dist/server/realtime/index.js.map +1 -0
- package/dist/server/scan/index.d.ts +106 -0
- package/dist/server/scan/index.js +43 -0
- package/dist/server/scan/index.js.map +1 -0
- package/dist/server/security/index.d.ts +193 -0
- package/dist/server/security/index.js +50 -0
- package/dist/server/security/index.js.map +1 -0
- package/dist/server/storage/index.d.ts +22 -0
- package/dist/server/storage/index.js +14 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/webhook/index.d.ts +148 -0
- package/dist/server/webhook/index.js +19 -0
- package/dist/server/webhook/index.js.map +1 -0
- package/dist/server-error-to-envelope-NO4CCAFQ.js +8 -0
- package/dist/server-error-to-envelope-NO4CCAFQ.js.map +1 -0
- package/dist/server-error-to-envelope-UJK6OWDJ.js +134 -0
- package/dist/server-error-to-envelope-UJK6OWDJ.js.map +1 -0
- package/dist/server-routes-hmr-GZJGPWMS.js +0 -0
- package/dist/services-json-Z2QNGVPW.js +142 -0
- package/dist/services-json-Z2QNGVPW.js.map +1 -0
- package/dist/{services-typed-client-XWYYBWGW.js → services-typed-client-KK6RHRDG.js} +2 -2
- package/dist/{services-typed-client-ZX5JUHH3.js → services-typed-client-T7M5VPBP.js} +2 -2
- package/dist/{sqlite-vec-ARPNUBWT.js → sqlite-vec-54KB4RD3.js} +2 -2
- package/dist/sqlite-vec-54KB4RD3.js.map +1 -0
- package/dist/{start-HX3QUSOS.js → start-CGSZPBAG.js} +23 -23
- package/dist/start-CGSZPBAG.js.map +1 -0
- package/dist/{static-TZBVBDTB.js → static-QI5NQT2X.js} +6 -6
- package/dist/storage-manager-C4jsO0Tp.d.ts +89 -0
- package/dist/storage-manager-D5KBXXKB.js +0 -0
- package/dist/{storage-types-DtIVejC1.d.ts → storage-types-DsDTCPbp.d.ts} +1 -1
- package/dist/{theo-cloud-3K4DGB3S.js → theo-cloud-RSQCBNXL.js} +4 -4
- package/dist/theo-cloud-RSQCBNXL.js.map +1 -0
- package/dist/upgrade-readiness-GUJBCJIS.js +0 -0
- package/dist/{vercel-MWW24ABC.js → vercel-TKPZK62A.js} +3 -3
- package/dist/vite-plugin/index.d.ts +3 -1
- package/dist/vite-plugin/index.js +20 -8
- package/dist/{vite-plugin-IK3NOWXS.js → vite-plugin-CR4JDFUQ.js} +9 -9
- package/dist/vite-plugin-CR4JDFUQ.js.map +1 -0
- package/package.json +61 -9
- package/LICENSE +0 -201
- package/dist/build-HIGHJQQV.js.map +0 -1
- package/dist/chunk-5OFPQK6N.js.map +0 -1
- package/dist/chunk-64JI5LTO.js.map +0 -1
- package/dist/chunk-7TOMTMHT.js.map +0 -1
- package/dist/chunk-BIGBFZSU.js.map +0 -1
- package/dist/chunk-C52GSJPF.js.map +0 -1
- package/dist/chunk-CZM6WWWS.js +0 -2450
- package/dist/chunk-CZM6WWWS.js.map +0 -1
- package/dist/chunk-JAAHOGKI.js.map +0 -1
- package/dist/chunk-KJVEJILQ.js.map +0 -1
- package/dist/chunk-O4BLVPML.js.map +0 -1
- package/dist/chunk-O7335ZGH.js.map +0 -1
- package/dist/chunk-PB4GR7EP.js.map +0 -1
- package/dist/chunk-TPCT3MXV.js.map +0 -1
- package/dist/chunk-VRHXOKRP.js.map +0 -1
- package/dist/chunk-WZ7CPVQB.js.map +0 -1
- package/dist/chunk-X4JAX2U3.js.map +0 -1
- package/dist/chunk-XP7YXRHO.js.map +0 -1
- package/dist/chunk-YLBSSEHX.js.map +0 -1
- package/dist/chunk-ZOXNJV2O.js.map +0 -1
- package/dist/dev-emit-RBSFZZNO.js.map +0 -1
- package/dist/dispatcher-E6QV32BO.js.map +0 -1
- package/dist/dist-5V77Z223.js.map +0 -1
- package/dist/generate-DT4IIAHF.js.map +0 -1
- package/dist/index-li83Swxa.d.ts +0 -506
- package/dist/lib-NL5JXGEB.js.map +0 -1
- package/dist/proper-lockfile-4Y3DP3RP.js.map +0 -1
- package/dist/registry-4MZ26TZD.js +0 -25
- package/dist/schema-CjVly9c_.d.ts +0 -274
- package/dist/sqlite-vec-ARPNUBWT.js.map +0 -1
- package/dist/start-HX3QUSOS.js.map +0 -1
- package/dist/theo-cloud-3K4DGB3S.js.map +0 -1
- /package/dist/{actions-virtual-module-HWNTIEPI.js.map → actions-virtual-module-IY46EEEX.js.map} +0 -0
- /package/dist/{actions-virtual-module-SEIPACG5.js.map → actions-virtual-module-XNHDNCZ6.js.map} +0 -0
- /package/dist/{aws-lambda-XYCGZH3I.js.map → aws-lambda-GKSTX6HD.js.map} +0 -0
- /package/dist/{bun-K73TQEWC.js.map → bun-ISHSNFIO.js.map} +0 -0
- /package/dist/{check-PZR67OY5.js.map → check-NXYPLM6M.js.map} +0 -0
- /package/dist/{dispatcher-AQJGI3HU.js.map → chunk-E3JH6YUM.js.map} +0 -0
- /package/dist/{node-3APTLGWB.js.map → chunk-JHEAWR3L.js.map} +0 -0
- /package/dist/{chunk-ZJW5FFYJ.js.map → chunk-LC5PYNJP.js.map} +0 -0
- /package/dist/{chunk-2VQKDRVF.js.map → chunk-MR7OLIC6.js.map} +0 -0
- /package/dist/{scan-C2BOKLSY.js.map → chunk-NPMCKOX4.js.map} +0 -0
- /package/dist/{chunk-B6RP57M3.js.map → chunk-OLPB36O2.js.map} +0 -0
- /package/dist/{chunk-OXIQI2XZ.js.map → chunk-VBZCVFSA.js.map} +0 -0
- /package/dist/{chunk-TRH2L3FI.js.map → chunk-WEGALZEU.js.map} +0 -0
- /package/dist/{cloudflare-LCAOTRYZ.js.map → cloudflare-OJGJ7R2D.js.map} +0 -0
- /package/dist/{schema-VR6YNVLQ.js.map → cookies-MJKMGJ7N.js.map} +0 -0
- /package/dist/{deno-deploy-ZN4RE5HF.js.map → deno-deploy-BHWKFTOW.js.map} +0 -0
- /package/dist/{dev-266MVCVA.js.map → dev-MJVSRZGF.js.map} +0 -0
- /package/dist/{dev-emit-NO6OZJOQ.js.map → dev-emit-HHP2XJXE.js.map} +0 -0
- /package/dist/{vite-plugin-IK3NOWXS.js.map → dispatcher-63LBXYLE.js.map} +0 -0
- /package/dist/{info-FJ4NXKTB.js.map → info-47VB62MV.js.map} +0 -0
- /package/dist/{netlify-5VQ22Z2P.js.map → netlify-GSNSJGMN.js.map} +0 -0
- /package/dist/{openapi-FNVSWZOL.js.map → openapi-NFLSNNVQ.js.map} +0 -0
- /package/dist/{registry-4MZ26TZD.js.map → registry-QHEZ3BWB.js.map} +0 -0
- /package/dist/{routes-H4SPLFHJ.js.map → routes-3A4XGIEJ.js.map} +0 -0
- /package/dist/{services-typed-client-XWYYBWGW.js.map → services-typed-client-KK6RHRDG.js.map} +0 -0
- /package/dist/{services-typed-client-ZX5JUHH3.js.map → services-typed-client-T7M5VPBP.js.map} +0 -0
- /package/dist/{static-TZBVBDTB.js.map → static-QI5NQT2X.js.map} +0 -0
- /package/dist/{vercel-MWW24ABC.js.map → vercel-TKPZK62A.js.map} +0 -0
|
@@ -0,0 +1,1324 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DuplicateContextKeyError,
|
|
3
|
+
createOutbox,
|
|
4
|
+
createOutboxDispatcher,
|
|
5
|
+
createQueueClient
|
|
6
|
+
} from "./chunk-GY5Q27BJ.js";
|
|
7
|
+
import {
|
|
8
|
+
parseRequestBody,
|
|
9
|
+
warnOnce
|
|
10
|
+
} from "./chunk-Z5IGLBWE.js";
|
|
11
|
+
import {
|
|
12
|
+
scanMiddlewares
|
|
13
|
+
} from "./chunk-ZEGYW52B.js";
|
|
14
|
+
import {
|
|
15
|
+
enforceCsrf
|
|
16
|
+
} from "./chunk-TSLSIRBR.js";
|
|
17
|
+
import {
|
|
18
|
+
AuthRequiredError
|
|
19
|
+
} from "./chunk-4HBI7DHP.js";
|
|
20
|
+
|
|
21
|
+
// src/core/contracts/action-protocol.ts
|
|
22
|
+
var CODE_TO_STATUS = {
|
|
23
|
+
VALIDATION_ERROR: 422,
|
|
24
|
+
BAD_REQUEST: 400,
|
|
25
|
+
UNAUTHORIZED: 401,
|
|
26
|
+
FORBIDDEN: 403,
|
|
27
|
+
NOT_FOUND: 404,
|
|
28
|
+
METHOD_NOT_ALLOWED: 405,
|
|
29
|
+
CONFLICT: 409,
|
|
30
|
+
CONTENT_TOO_LARGE: 413,
|
|
31
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
32
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
33
|
+
TOO_MANY_REQUESTS: 429,
|
|
34
|
+
INTERNAL_SERVER_ERROR: 500
|
|
35
|
+
};
|
|
36
|
+
var STATUS_TO_CODE = {
|
|
37
|
+
400: "BAD_REQUEST",
|
|
38
|
+
401: "UNAUTHORIZED",
|
|
39
|
+
403: "FORBIDDEN",
|
|
40
|
+
404: "NOT_FOUND",
|
|
41
|
+
405: "METHOD_NOT_ALLOWED",
|
|
42
|
+
409: "CONFLICT",
|
|
43
|
+
413: "PAYLOAD_TOO_LARGE",
|
|
44
|
+
415: "UNSUPPORTED_MEDIA_TYPE",
|
|
45
|
+
422: "VALIDATION_ERROR",
|
|
46
|
+
429: "TOO_MANY_REQUESTS",
|
|
47
|
+
500: "INTERNAL_SERVER_ERROR"
|
|
48
|
+
};
|
|
49
|
+
var ActionError = class _ActionError extends Error {
|
|
50
|
+
// Discriminator widened to the full union so subclasses can narrow to their
|
|
51
|
+
// specific literal (TypeScript would otherwise reject the override). Concrete
|
|
52
|
+
// values are still always exact literals at runtime.
|
|
53
|
+
type = "TheoActionError";
|
|
54
|
+
code;
|
|
55
|
+
status;
|
|
56
|
+
constructor(params) {
|
|
57
|
+
super(params.message ?? params.code);
|
|
58
|
+
this.code = params.code;
|
|
59
|
+
this.status = _ActionError.codeToStatus(params.code);
|
|
60
|
+
if (params.stack) {
|
|
61
|
+
this.stack = params.stack;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* G5 T2.4 — canonical envelope view of the action error. Maps the G3
|
|
66
|
+
* ActionErrorCode to a canonical TheoErrorCode (VALIDATION_ERROR ↔
|
|
67
|
+
* UNPROCESSABLE_ENTITY, CONTENT_TOO_LARGE ↔ PAYLOAD_TOO_LARGE) so consumer
|
|
68
|
+
* UI / SDK code can switch on the unified envelope.
|
|
69
|
+
*
|
|
70
|
+
* Subclasses override to populate `ext` (see `ActionInputError.envelope`).
|
|
71
|
+
*/
|
|
72
|
+
get envelope() {
|
|
73
|
+
return {
|
|
74
|
+
code: _ActionError.toTheoErrorCode(this.code),
|
|
75
|
+
message: this.message
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Translate G3 ActionErrorCode → canonical TheoErrorCode (blueprint
|
|
80
|
+
* Recommendations § "G3 ActionError becomes inaugural envelope user").
|
|
81
|
+
*/
|
|
82
|
+
static toTheoErrorCode(code) {
|
|
83
|
+
if (code === "VALIDATION_ERROR") return "UNPROCESSABLE_ENTITY";
|
|
84
|
+
if (code === "CONTENT_TOO_LARGE") return "PAYLOAD_TOO_LARGE";
|
|
85
|
+
return code;
|
|
86
|
+
}
|
|
87
|
+
static codeToStatus(code) {
|
|
88
|
+
return CODE_TO_STATUS[code];
|
|
89
|
+
}
|
|
90
|
+
static statusToCode(status) {
|
|
91
|
+
return STATUS_TO_CODE[status] ?? "INTERNAL_SERVER_ERROR";
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse a serialized error JSON back into the typed class hierarchy.
|
|
95
|
+
* Distinguishes `TheoActionInputError` (with `issues` array) from
|
|
96
|
+
* `TheoActionError` via the `type` discriminator. Falls back to
|
|
97
|
+
* `INTERNAL_SERVER_ERROR` for malformed bodies (non-object, missing
|
|
98
|
+
* `type`, unknown `code`).
|
|
99
|
+
*/
|
|
100
|
+
static fromJson(body) {
|
|
101
|
+
if (typeof body !== "object" || body === null) {
|
|
102
|
+
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
103
|
+
}
|
|
104
|
+
const obj = body;
|
|
105
|
+
if (obj.type === "TheoActionInputError" && Array.isArray(obj.issues)) {
|
|
106
|
+
return new ActionInputError(obj.issues);
|
|
107
|
+
}
|
|
108
|
+
if (obj.type === "TheoActionError" && typeof obj.code === "string" && obj.code in CODE_TO_STATUS) {
|
|
109
|
+
return new _ActionError({
|
|
110
|
+
code: obj.code,
|
|
111
|
+
message: typeof obj.message === "string" ? obj.message : void 0
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var ActionInputError = class extends ActionError {
|
|
118
|
+
type = "TheoActionInputError";
|
|
119
|
+
issues;
|
|
120
|
+
fields;
|
|
121
|
+
constructor(rawIssues) {
|
|
122
|
+
super({ code: "VALIDATION_ERROR", message: "Validation failed" });
|
|
123
|
+
this.issues = extractUniversalIssues(rawIssues);
|
|
124
|
+
this.fields = buildFieldsMap(this.issues);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* G5 T2.4 — envelope view with ValidationFieldsExt populated from .fields.
|
|
128
|
+
* UI consumers can `switch (env.code)` on `UNPROCESSABLE_ENTITY` and read
|
|
129
|
+
* `(env.ext as ValidationFieldsExt).fields` for field-level rendering.
|
|
130
|
+
*/
|
|
131
|
+
get envelope() {
|
|
132
|
+
return {
|
|
133
|
+
code: "UNPROCESSABLE_ENTITY",
|
|
134
|
+
message: this.message,
|
|
135
|
+
ext: { fields: this.fields }
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function buildFieldsMap(issues) {
|
|
140
|
+
const fields = {};
|
|
141
|
+
const seen = /* @__PURE__ */ new Set();
|
|
142
|
+
for (const issue of issues) {
|
|
143
|
+
const key = issue.path.length === 0 ? "" : issue.path.join(".");
|
|
144
|
+
const dedupeKey = `${key}\0${issue.message}`;
|
|
145
|
+
if (seen.has(dedupeKey)) continue;
|
|
146
|
+
seen.add(dedupeKey);
|
|
147
|
+
const bucket = fields[key] ?? [];
|
|
148
|
+
bucket.push(issue.message);
|
|
149
|
+
fields[key] = bucket;
|
|
150
|
+
}
|
|
151
|
+
return fields;
|
|
152
|
+
}
|
|
153
|
+
function extractUniversalIssues(raw) {
|
|
154
|
+
if (!Array.isArray(raw)) return [];
|
|
155
|
+
const out = [];
|
|
156
|
+
for (const entry of raw) {
|
|
157
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
158
|
+
const obj = entry;
|
|
159
|
+
if (!Array.isArray(obj.path)) continue;
|
|
160
|
+
if (typeof obj.message !== "string") continue;
|
|
161
|
+
const path = [];
|
|
162
|
+
let pathValid = true;
|
|
163
|
+
for (const seg of obj.path) {
|
|
164
|
+
if (typeof seg === "string" || typeof seg === "number") {
|
|
165
|
+
path.push(seg);
|
|
166
|
+
} else {
|
|
167
|
+
pathValid = false;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!pathValid) continue;
|
|
172
|
+
out.push({
|
|
173
|
+
path,
|
|
174
|
+
message: obj.message,
|
|
175
|
+
code: typeof obj.code === "string" ? obj.code : void 0
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
function isActionError(value) {
|
|
181
|
+
return value instanceof ActionError;
|
|
182
|
+
}
|
|
183
|
+
function isInputError(value) {
|
|
184
|
+
return value instanceof ActionInputError;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/server/http/send-response.ts
|
|
188
|
+
function sendJson(res, data, status = 200, transformer) {
|
|
189
|
+
const body = transformer ? transformer.serialize(data) : JSON.stringify(data);
|
|
190
|
+
res.writeHead(status, {
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
"Content-Length": Buffer.byteLength(body)
|
|
193
|
+
});
|
|
194
|
+
res.end(body);
|
|
195
|
+
}
|
|
196
|
+
function sendError(res, codeOrInput, message, status, issues, requestId, options) {
|
|
197
|
+
let code;
|
|
198
|
+
if (typeof codeOrInput === "string") {
|
|
199
|
+
code = codeOrInput;
|
|
200
|
+
message = message ?? "";
|
|
201
|
+
status = status ?? 500;
|
|
202
|
+
} else {
|
|
203
|
+
code = codeOrInput.code;
|
|
204
|
+
message = codeOrInput.message;
|
|
205
|
+
status = codeOrInput.status;
|
|
206
|
+
issues = codeOrInput.issues;
|
|
207
|
+
requestId = codeOrInput.requestId;
|
|
208
|
+
options = codeOrInput.options;
|
|
209
|
+
}
|
|
210
|
+
const errorMessage = code === "INTERNAL_ERROR" && process.env.NODE_ENV === "production" ? "Internal server error" : message;
|
|
211
|
+
if (code === "INTERNAL_ERROR") {
|
|
212
|
+
console.error(`[${requestId ?? "no-id"}] ${message}`);
|
|
213
|
+
}
|
|
214
|
+
if (status === 404 && options?.custom404Html) {
|
|
215
|
+
const body = options.custom404Html;
|
|
216
|
+
res.writeHead(404, {
|
|
217
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
218
|
+
"Content-Length": Buffer.byteLength(body)
|
|
219
|
+
});
|
|
220
|
+
res.end(body);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (status === 500 && options?.custom500Html) {
|
|
224
|
+
const body = options.custom500Html;
|
|
225
|
+
res.writeHead(500, {
|
|
226
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
227
|
+
"Content-Length": Buffer.byteLength(body)
|
|
228
|
+
});
|
|
229
|
+
res.end(body);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
sendJson(
|
|
233
|
+
res,
|
|
234
|
+
{
|
|
235
|
+
error: {
|
|
236
|
+
code,
|
|
237
|
+
message: errorMessage,
|
|
238
|
+
...requestId ? { requestId } : {},
|
|
239
|
+
...issues ? { issues } : {}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
status
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/server/http/middleware-runner.ts
|
|
247
|
+
import { existsSync } from "fs";
|
|
248
|
+
import { join } from "path";
|
|
249
|
+
var middlewareCache = /* @__PURE__ */ new Map();
|
|
250
|
+
function _resetMiddlewareCacheForTests() {
|
|
251
|
+
middlewareCache.clear();
|
|
252
|
+
}
|
|
253
|
+
function getCachedScan(serverDir) {
|
|
254
|
+
let cached = middlewareCache.get(serverDir);
|
|
255
|
+
if (!cached) {
|
|
256
|
+
const singleFilePath = join(serverDir, "middleware.ts");
|
|
257
|
+
cached = {
|
|
258
|
+
singleFilePath,
|
|
259
|
+
singleFileExists: existsSync(singleFilePath),
|
|
260
|
+
dirMiddlewares: scanMiddlewares(serverDir)
|
|
261
|
+
};
|
|
262
|
+
middlewareCache.set(serverDir, cached);
|
|
263
|
+
}
|
|
264
|
+
return cached;
|
|
265
|
+
}
|
|
266
|
+
async function runOneMiddleware(mw, req, res) {
|
|
267
|
+
const state = { nextCalled: false };
|
|
268
|
+
await mw(req, res, () => {
|
|
269
|
+
state.nextCalled = true;
|
|
270
|
+
});
|
|
271
|
+
return state;
|
|
272
|
+
}
|
|
273
|
+
async function runMiddlewareAndContext(req, res, loadModule, serverDir) {
|
|
274
|
+
const { singleFilePath, singleFileExists, dirMiddlewares } = getCachedScan(serverDir);
|
|
275
|
+
const dirExists = dirMiddlewares.length > 0;
|
|
276
|
+
if (singleFileExists && dirExists) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
"Ambiguous middleware configuration: found both server/middleware.ts and server/middleware/ directory. Use one or the other, not both."
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (dirExists) {
|
|
282
|
+
for (const mwPath of dirMiddlewares) {
|
|
283
|
+
const mod = await loadModule(mwPath);
|
|
284
|
+
const mw = mod.default;
|
|
285
|
+
if (typeof mw !== "function") continue;
|
|
286
|
+
const { nextCalled } = await runOneMiddleware(mw, req, res);
|
|
287
|
+
if (!nextCalled || res.writableEnded) {
|
|
288
|
+
return { ctx: {}, aborted: true };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (singleFileExists) {
|
|
293
|
+
const mod = await loadModule(singleFilePath);
|
|
294
|
+
const mw = mod.default;
|
|
295
|
+
if (typeof mw === "function") {
|
|
296
|
+
const { nextCalled } = await runOneMiddleware(mw, req, res);
|
|
297
|
+
if (!nextCalled || res.writableEnded) {
|
|
298
|
+
return { ctx: {}, aborted: true };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
let ctx = {};
|
|
303
|
+
const contextPath = join(serverDir, "context.ts");
|
|
304
|
+
if (existsSync(contextPath)) {
|
|
305
|
+
const mod = await loadModule(contextPath);
|
|
306
|
+
const createContext = mod.createContext;
|
|
307
|
+
if (typeof createContext === "function") {
|
|
308
|
+
ctx = await createContext({ request: req, response: res });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { ctx, aborted: false };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/server/security/csrf-warn-dispatch.ts
|
|
315
|
+
function dispatchCsrfWarn(payload) {
|
|
316
|
+
const key = `${payload.event}:${payload.method}:${payload.path ?? ""}`;
|
|
317
|
+
warnOnce(key, payload);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/server/http/execute-stages.ts
|
|
321
|
+
async function parseQueryAndBody(req, res, requestId) {
|
|
322
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
323
|
+
const query = Object.fromEntries(url.searchParams);
|
|
324
|
+
let body;
|
|
325
|
+
try {
|
|
326
|
+
const parsed = await parseRequestBody(req);
|
|
327
|
+
if (parsed.json !== void 0) {
|
|
328
|
+
body = parsed.json;
|
|
329
|
+
} else if (parsed.files.length > 0 || Object.keys(parsed.fields).length > 0) {
|
|
330
|
+
body = { ...parsed.fields, _files: parsed.files };
|
|
331
|
+
} else {
|
|
332
|
+
body = void 0;
|
|
333
|
+
}
|
|
334
|
+
} catch (err) {
|
|
335
|
+
const message = err.message;
|
|
336
|
+
const status = message.includes("Unsupported Content-Type") ? 415 : 400;
|
|
337
|
+
sendError(res, "VALIDATION_ERROR", message, status, void 0, requestId);
|
|
338
|
+
return { ok: false };
|
|
339
|
+
}
|
|
340
|
+
return { ok: true, data: { query, body } };
|
|
341
|
+
}
|
|
342
|
+
var isZodLike = (value) => typeof value === "object" && value !== null && typeof value.safeParse === "function";
|
|
343
|
+
function runZodValidation(routeConfig, res, requestId, input) {
|
|
344
|
+
const { query, params } = input;
|
|
345
|
+
let { body } = input;
|
|
346
|
+
if (isZodLike(routeConfig.query)) {
|
|
347
|
+
const result = routeConfig.query.safeParse(query);
|
|
348
|
+
if (!result.success) {
|
|
349
|
+
sendError(
|
|
350
|
+
res,
|
|
351
|
+
"VALIDATION_ERROR",
|
|
352
|
+
"Invalid query parameters",
|
|
353
|
+
400,
|
|
354
|
+
result.error?.issues,
|
|
355
|
+
requestId
|
|
356
|
+
);
|
|
357
|
+
return { ok: false };
|
|
358
|
+
}
|
|
359
|
+
Object.assign(query, result.data);
|
|
360
|
+
}
|
|
361
|
+
if (isZodLike(routeConfig.body)) {
|
|
362
|
+
const result = routeConfig.body.safeParse(body);
|
|
363
|
+
if (!result.success) {
|
|
364
|
+
sendError(
|
|
365
|
+
res,
|
|
366
|
+
"VALIDATION_ERROR",
|
|
367
|
+
"Invalid request body",
|
|
368
|
+
400,
|
|
369
|
+
result.error?.issues,
|
|
370
|
+
requestId
|
|
371
|
+
);
|
|
372
|
+
return { ok: false };
|
|
373
|
+
}
|
|
374
|
+
body = result.data;
|
|
375
|
+
}
|
|
376
|
+
if (isZodLike(routeConfig.params)) {
|
|
377
|
+
const result = routeConfig.params.safeParse(params);
|
|
378
|
+
if (!result.success) {
|
|
379
|
+
sendError(
|
|
380
|
+
res,
|
|
381
|
+
"VALIDATION_ERROR",
|
|
382
|
+
"Invalid route parameters",
|
|
383
|
+
400,
|
|
384
|
+
result.error?.issues,
|
|
385
|
+
requestId
|
|
386
|
+
);
|
|
387
|
+
return { ok: false };
|
|
388
|
+
}
|
|
389
|
+
Object.assign(params, result.data);
|
|
390
|
+
}
|
|
391
|
+
return { ok: true, data: { query, body, params } };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/server/http/execute.ts
|
|
395
|
+
var CSRF_PROTECTED_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
396
|
+
async function pipeWebStreamToResponse(body, res, ctx) {
|
|
397
|
+
const reader = body.getReader();
|
|
398
|
+
try {
|
|
399
|
+
let done = false;
|
|
400
|
+
while (!done) {
|
|
401
|
+
const chunk = await reader.read();
|
|
402
|
+
done = chunk.done;
|
|
403
|
+
if (!done) res.write(chunk.value);
|
|
404
|
+
}
|
|
405
|
+
} catch (streamErr) {
|
|
406
|
+
warnOnce(`stream-error:${ctx.routePath}:${ctx.method}`, {
|
|
407
|
+
event: "stream.error",
|
|
408
|
+
requestId: ctx.requestId ?? "no-id",
|
|
409
|
+
route: ctx.routePath,
|
|
410
|
+
method: ctx.method,
|
|
411
|
+
message: streamErr instanceof Error ? streamErr.message : String(streamErr)
|
|
412
|
+
});
|
|
413
|
+
if (ctx.pluginRunner) {
|
|
414
|
+
try {
|
|
415
|
+
await ctx.pluginRunner.runOnError(ctx.buildPluginCtx(ctx.ctx), streamErr);
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} finally {
|
|
420
|
+
try {
|
|
421
|
+
reader.releaseLock();
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async function executeRoute(ctx) {
|
|
427
|
+
const {
|
|
428
|
+
route,
|
|
429
|
+
method,
|
|
430
|
+
params,
|
|
431
|
+
req,
|
|
432
|
+
res,
|
|
433
|
+
loadModule,
|
|
434
|
+
serverDir,
|
|
435
|
+
requestId,
|
|
436
|
+
pluginRunner,
|
|
437
|
+
transformer,
|
|
438
|
+
csrfMode = "strict",
|
|
439
|
+
disallowed,
|
|
440
|
+
jobBackend
|
|
441
|
+
} = ctx;
|
|
442
|
+
const buildPluginCtx = (ctxObj) => ({
|
|
443
|
+
request: req,
|
|
444
|
+
response: res,
|
|
445
|
+
ctx: ctxObj,
|
|
446
|
+
requestId: requestId ?? "no-id"
|
|
447
|
+
});
|
|
448
|
+
if (transformer && transformer.name !== "json") {
|
|
449
|
+
res.setHeader("x-theo-transformer", transformer.name);
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
let ctx2 = {};
|
|
453
|
+
if (pluginRunner) {
|
|
454
|
+
pluginRunner.applyDecorations(ctx2);
|
|
455
|
+
const onReqResult = await pluginRunner.runOnRequest(buildPluginCtx(ctx2));
|
|
456
|
+
if (onReqResult.shortCircuited) return;
|
|
457
|
+
}
|
|
458
|
+
if (serverDir) {
|
|
459
|
+
const result = await runMiddlewareAndContext(req, res, loadModule, serverDir);
|
|
460
|
+
if (result.aborted) return;
|
|
461
|
+
ctx2 = result.ctx ?? {};
|
|
462
|
+
if (pluginRunner) pluginRunner.applyDecorations(ctx2);
|
|
463
|
+
}
|
|
464
|
+
if (jobBackend) {
|
|
465
|
+
if (ctx2.queue !== void 0) {
|
|
466
|
+
throw new DuplicateContextKeyError("queue", {
|
|
467
|
+
reason: "A plugin or middleware already decorated ctx.queue; choose a different key OR remove jobs.backend from theo.config.ts."
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
const outbox = createOutbox();
|
|
471
|
+
const queueClient = createQueueClient(jobBackend, outbox);
|
|
472
|
+
ctx2.queue = queueClient;
|
|
473
|
+
res.on("close", () => {
|
|
474
|
+
if (!res.writableFinished) outbox.discard();
|
|
475
|
+
});
|
|
476
|
+
res.on("finish", () => {
|
|
477
|
+
if (res.statusCode >= 400) {
|
|
478
|
+
outbox.discard();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
void outbox.flush(createOutboxDispatcher(jobBackend));
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
const mod = await loadModule(route.filePath);
|
|
485
|
+
const routeConfig = mod[method];
|
|
486
|
+
if (!routeConfig) {
|
|
487
|
+
sendError(
|
|
488
|
+
res,
|
|
489
|
+
"METHOD_NOT_ALLOWED",
|
|
490
|
+
`Method ${method} not allowed`,
|
|
491
|
+
405,
|
|
492
|
+
void 0,
|
|
493
|
+
requestId
|
|
494
|
+
);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const handler = typeof routeConfig === "function" ? routeConfig : routeConfig.handler;
|
|
498
|
+
if (typeof handler !== "function") {
|
|
499
|
+
sendError(res, "INTERNAL_ERROR", "Route handler is not a function", 500, void 0, requestId);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const routeOptOut = typeof routeConfig === "object" && routeConfig.csrf === false;
|
|
503
|
+
if (CSRF_PROTECTED_METHODS.has(method) && !routeOptOut) {
|
|
504
|
+
const decision = enforceCsrf(
|
|
505
|
+
req,
|
|
506
|
+
csrfMode,
|
|
507
|
+
{
|
|
508
|
+
// T3.3 DRY — see security/csrf-warn-dispatch.ts
|
|
509
|
+
warn: dispatchCsrfWarn,
|
|
510
|
+
path: req.url
|
|
511
|
+
},
|
|
512
|
+
disallowed
|
|
513
|
+
);
|
|
514
|
+
if (!decision.allow) {
|
|
515
|
+
sendError(
|
|
516
|
+
res,
|
|
517
|
+
"CSRF_INVALID",
|
|
518
|
+
decision.reason ?? "CSRF check failed",
|
|
519
|
+
403,
|
|
520
|
+
void 0,
|
|
521
|
+
requestId
|
|
522
|
+
);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const rc = routeConfig;
|
|
527
|
+
const parseResult = await parseQueryAndBody(req, res, requestId);
|
|
528
|
+
if (!parseResult.ok) return;
|
|
529
|
+
const { query } = parseResult.data;
|
|
530
|
+
let { body } = parseResult.data;
|
|
531
|
+
const validationResult = runZodValidation(rc, res, requestId, { query, body, params });
|
|
532
|
+
if (!validationResult.ok) return;
|
|
533
|
+
body = validationResult.data.body;
|
|
534
|
+
if (pluginRunner) {
|
|
535
|
+
const preResult = await pluginRunner.runPreHandler(buildPluginCtx(ctx2));
|
|
536
|
+
if (preResult.shortCircuited) return;
|
|
537
|
+
}
|
|
538
|
+
const callableHandler = handler;
|
|
539
|
+
const handlerResult = await callableHandler({ query, body, params, request: req, ctx: ctx2 });
|
|
540
|
+
if (handlerResult === void 0 || handlerResult === null) {
|
|
541
|
+
sendJson(res, null, rc.status ?? 204, transformer);
|
|
542
|
+
if (pluginRunner) await pluginRunner.runOnResponse(buildPluginCtx(ctx2));
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (handlerResult instanceof Response) {
|
|
546
|
+
const headersBag = {};
|
|
547
|
+
for (const [k, v] of handlerResult.headers) {
|
|
548
|
+
if (k.toLowerCase() !== "set-cookie") headersBag[k] = v;
|
|
549
|
+
}
|
|
550
|
+
const setCookies = handlerResult.headers.getSetCookie();
|
|
551
|
+
if (setCookies.length > 0) {
|
|
552
|
+
res.setHeader("Set-Cookie", setCookies);
|
|
553
|
+
}
|
|
554
|
+
res.writeHead(handlerResult.status, headersBag);
|
|
555
|
+
if (handlerResult.body) {
|
|
556
|
+
await pipeWebStreamToResponse(handlerResult.body, res, {
|
|
557
|
+
buildPluginCtx,
|
|
558
|
+
ctx: ctx2,
|
|
559
|
+
method,
|
|
560
|
+
pluginRunner,
|
|
561
|
+
requestId,
|
|
562
|
+
routePath: route.routePath
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
res.end();
|
|
566
|
+
if (pluginRunner) await pluginRunner.runOnResponse(buildPluginCtx(ctx2));
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
sendJson(res, handlerResult, rc.status ?? 200, transformer);
|
|
570
|
+
if (pluginRunner) await pluginRunner.runOnResponse(buildPluginCtx(ctx2));
|
|
571
|
+
} catch (err) {
|
|
572
|
+
if (pluginRunner) {
|
|
573
|
+
const errCtxObj = {};
|
|
574
|
+
pluginRunner.applyDecorations(errCtxObj);
|
|
575
|
+
await pluginRunner.runOnError(buildPluginCtx(errCtxObj), err);
|
|
576
|
+
if (res.writableEnded) {
|
|
577
|
+
await pluginRunner.runOnResponse(buildPluginCtx(errCtxObj), { inErrorPath: true });
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const isAuthError = err instanceof AuthRequiredError || err !== null && typeof err === "object" && err.code === "AUTH_REQUIRED" && err.status === 401;
|
|
582
|
+
if (isAuthError) {
|
|
583
|
+
const authErr = err;
|
|
584
|
+
sendError(res, authErr.code, authErr.message, authErr.status, void 0, requestId);
|
|
585
|
+
if (pluginRunner) {
|
|
586
|
+
const errCtxObj = {};
|
|
587
|
+
pluginRunner.applyDecorations(errCtxObj);
|
|
588
|
+
await pluginRunner.runOnResponse(buildPluginCtx(errCtxObj), { inErrorPath: true });
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
sendError(
|
|
593
|
+
res,
|
|
594
|
+
"INTERNAL_ERROR",
|
|
595
|
+
err instanceof Error && err.message ? err.message : "Internal server error",
|
|
596
|
+
500,
|
|
597
|
+
void 0,
|
|
598
|
+
requestId
|
|
599
|
+
);
|
|
600
|
+
if (pluginRunner) {
|
|
601
|
+
const errCtxObj = {};
|
|
602
|
+
pluginRunner.applyDecorations(errCtxObj);
|
|
603
|
+
await pluginRunner.runOnResponse(buildPluginCtx(errCtxObj), { inErrorPath: true });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/server/http/form-data-to-object.ts
|
|
609
|
+
import { z } from "zod";
|
|
610
|
+
function formDataToObject(formData, schema, prefix = "") {
|
|
611
|
+
const shape = schema._def.shape();
|
|
612
|
+
const out = {};
|
|
613
|
+
for (const [key, rawValidator] of Object.entries(shape)) {
|
|
614
|
+
const fullKey = prefix + key;
|
|
615
|
+
const validator = unwrapWrappers(rawValidator);
|
|
616
|
+
if (validator instanceof z.ZodObject) {
|
|
617
|
+
const nestedPrefix = `${fullKey}.`;
|
|
618
|
+
const hasNestedKeys = [...formData.keys()].some((k) => k.startsWith(nestedPrefix));
|
|
619
|
+
if (hasNestedKeys) {
|
|
620
|
+
out[key] = formDataToObject(formData, validator, nestedPrefix);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
out[key] = unwrapMissingDefault(rawValidator);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (validator instanceof z.ZodArray) {
|
|
627
|
+
const values = formData.getAll(fullKey);
|
|
628
|
+
out[key] = coerceArrayElements(values, validator);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (validator instanceof z.ZodBoolean) {
|
|
632
|
+
out[key] = coerceBoolean(formData, fullKey);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (formData.has(fullKey)) {
|
|
636
|
+
const raw = formData.get(fullKey);
|
|
637
|
+
out[key] = coerceScalar(raw, validator);
|
|
638
|
+
} else {
|
|
639
|
+
out[key] = unwrapMissingDefault(rawValidator);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
644
|
+
function unwrapWrappers(validator) {
|
|
645
|
+
let inner = validator;
|
|
646
|
+
while (inner instanceof z.ZodOptional || inner instanceof z.ZodNullable || inner instanceof z.ZodDefault) {
|
|
647
|
+
inner = inner._def.innerType;
|
|
648
|
+
}
|
|
649
|
+
return inner;
|
|
650
|
+
}
|
|
651
|
+
function unwrapMissingDefault(validator) {
|
|
652
|
+
let cursor = validator;
|
|
653
|
+
while (cursor instanceof z.ZodOptional || cursor instanceof z.ZodNullable || cursor instanceof z.ZodDefault) {
|
|
654
|
+
if (cursor instanceof z.ZodDefault) {
|
|
655
|
+
const def = cursor._def.defaultValue;
|
|
656
|
+
return typeof def === "function" ? def() : def;
|
|
657
|
+
}
|
|
658
|
+
if (cursor instanceof z.ZodNullable) return null;
|
|
659
|
+
cursor = cursor._def.innerType;
|
|
660
|
+
}
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
663
|
+
function coerceScalar(raw, validator) {
|
|
664
|
+
if (raw === null) return void 0;
|
|
665
|
+
if (validator instanceof z.ZodNumber) {
|
|
666
|
+
return typeof raw === "string" ? Number(raw) : raw;
|
|
667
|
+
}
|
|
668
|
+
return raw;
|
|
669
|
+
}
|
|
670
|
+
function coerceArrayElements(values, arrayValidator) {
|
|
671
|
+
const elementType = unwrapWrappers(arrayValidator._def.type);
|
|
672
|
+
if (elementType instanceof z.ZodNumber) {
|
|
673
|
+
return values.map((v) => typeof v === "string" ? Number(v) : v);
|
|
674
|
+
}
|
|
675
|
+
if (elementType instanceof z.ZodBoolean) {
|
|
676
|
+
return values.map((v) => {
|
|
677
|
+
if (v === "true") return true;
|
|
678
|
+
if (v === "false") return false;
|
|
679
|
+
return Boolean(v);
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return values;
|
|
683
|
+
}
|
|
684
|
+
function coerceBoolean(formData, key) {
|
|
685
|
+
if (!formData.has(key)) return void 0;
|
|
686
|
+
const val = formData.get(key);
|
|
687
|
+
if (val === "true") return true;
|
|
688
|
+
if (val === "false") return false;
|
|
689
|
+
return Boolean(val);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/server/http/handle-request-error.ts
|
|
693
|
+
async function handleRequestError(err, c) {
|
|
694
|
+
if (c.pluginRunner) {
|
|
695
|
+
const errCtxObj = {};
|
|
696
|
+
c.pluginRunner.applyDecorations(errCtxObj);
|
|
697
|
+
try {
|
|
698
|
+
await c.pluginRunner.runOnError(c.buildPluginCtx(errCtxObj), err);
|
|
699
|
+
} catch {
|
|
700
|
+
}
|
|
701
|
+
if (c.res.writableEnded) {
|
|
702
|
+
try {
|
|
703
|
+
await c.pluginRunner.runOnResponse(c.buildPluginCtx(errCtxObj), {
|
|
704
|
+
inErrorPath: true
|
|
705
|
+
});
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const isAuthError = err instanceof AuthRequiredError || err !== null && typeof err === "object" && err.code === "AUTH_REQUIRED" && err.status === 401;
|
|
712
|
+
if (isAuthError) {
|
|
713
|
+
const authErr = err;
|
|
714
|
+
sendError(c.res, authErr.code, authErr.message, authErr.status, void 0, c.requestId);
|
|
715
|
+
} else {
|
|
716
|
+
sendError(
|
|
717
|
+
c.res,
|
|
718
|
+
"INTERNAL_ERROR",
|
|
719
|
+
err instanceof Error ? err.message : "Internal server error",
|
|
720
|
+
500,
|
|
721
|
+
void 0,
|
|
722
|
+
c.requestId
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
if (c.pluginRunner) {
|
|
726
|
+
const errCtxObj = {};
|
|
727
|
+
c.pluginRunner.applyDecorations(errCtxObj);
|
|
728
|
+
try {
|
|
729
|
+
await c.pluginRunner.runOnResponse(c.buildPluginCtx(errCtxObj), {
|
|
730
|
+
inErrorPath: true
|
|
731
|
+
});
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/server/http/serialize-action-result.ts
|
|
738
|
+
import { stringify as devalueStringify } from "devalue";
|
|
739
|
+
var DEFAULT_RESPONSE_BODY_SIZE_LIMIT = 5 * 1024 * 1024;
|
|
740
|
+
function serializeActionResult(result, options = {}) {
|
|
741
|
+
const limit = options.responseBodySizeLimit ?? DEFAULT_RESPONSE_BODY_SIZE_LIMIT;
|
|
742
|
+
if (result.error !== void 0) {
|
|
743
|
+
const body2 = result.error instanceof ActionInputError ? JSON.stringify({
|
|
744
|
+
type: result.error.type,
|
|
745
|
+
code: result.error.code,
|
|
746
|
+
message: result.error.message,
|
|
747
|
+
issues: result.error.issues,
|
|
748
|
+
fields: result.error.fields
|
|
749
|
+
}) : JSON.stringify({
|
|
750
|
+
type: result.error.type,
|
|
751
|
+
code: result.error.code,
|
|
752
|
+
message: result.error.message
|
|
753
|
+
});
|
|
754
|
+
if (Buffer.byteLength(body2, "utf8") > limit) {
|
|
755
|
+
throw new ActionError({
|
|
756
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
757
|
+
message: `Serialized error body exceeds limit (${limit} bytes)`
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
type: "error",
|
|
762
|
+
status: result.error.status,
|
|
763
|
+
contentType: "application/json",
|
|
764
|
+
body: body2
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
if (result.data === void 0) {
|
|
768
|
+
return { type: "empty", status: 204 };
|
|
769
|
+
}
|
|
770
|
+
if (result.data instanceof Response) {
|
|
771
|
+
throw new ActionError({
|
|
772
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
773
|
+
message: "Action handler cannot serialize Response objects \u2014 return plain data instead"
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
let body;
|
|
777
|
+
try {
|
|
778
|
+
body = devalueStringify(result.data, {
|
|
779
|
+
URL: (value) => value instanceof URL && value.href
|
|
780
|
+
});
|
|
781
|
+
} catch (e) {
|
|
782
|
+
throw new ActionError({
|
|
783
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
784
|
+
message: `Action data not serializable: ${e instanceof Error ? e.message : String(e)}`
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (Buffer.byteLength(body, "utf8") > limit) {
|
|
788
|
+
throw new ActionError({
|
|
789
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
790
|
+
message: `Serialized response body exceeds limit (${limit} bytes)`
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
type: "data",
|
|
795
|
+
status: 200,
|
|
796
|
+
contentType: "application/json+devalue",
|
|
797
|
+
body
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/server/http/action-execute.ts
|
|
802
|
+
var __IS_DEV = (() => {
|
|
803
|
+
try {
|
|
804
|
+
return import.meta.env?.DEV === true;
|
|
805
|
+
} catch {
|
|
806
|
+
return process.env.NODE_ENV !== "production";
|
|
807
|
+
}
|
|
808
|
+
})();
|
|
809
|
+
function isActionConfig(value) {
|
|
810
|
+
if (typeof value !== "object" || value === null) return false;
|
|
811
|
+
const candidate = value;
|
|
812
|
+
if (typeof candidate.handler !== "function") return false;
|
|
813
|
+
const input = candidate.input;
|
|
814
|
+
return typeof input?.safeParse === "function";
|
|
815
|
+
}
|
|
816
|
+
async function executeAction(filePath, exportName, req, res, loadModule, serverDir, requestId, pluginRunner, csrfMode = "strict", disallowed) {
|
|
817
|
+
return executeActionWithOptions({
|
|
818
|
+
filePath,
|
|
819
|
+
exportName,
|
|
820
|
+
req,
|
|
821
|
+
res,
|
|
822
|
+
loadModule,
|
|
823
|
+
serverDir,
|
|
824
|
+
requestId,
|
|
825
|
+
pluginRunner,
|
|
826
|
+
csrfMode,
|
|
827
|
+
disallowed
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
async function loadActionConfig(loadModule, filePath, exportName, res, requestId) {
|
|
831
|
+
const mod = await loadModule(filePath);
|
|
832
|
+
const exportedValue = mod[exportName];
|
|
833
|
+
if (!isActionConfig(exportedValue)) {
|
|
834
|
+
sendError(res, "NOT_FOUND", `Action "${exportName}" not found`, 404, void 0, requestId);
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
return exportedValue;
|
|
838
|
+
}
|
|
839
|
+
function enforceCsrfForAction(req, res, ctx) {
|
|
840
|
+
if (ctx.actionConfig.csrf === false) return true;
|
|
841
|
+
const decision = enforceCsrf(
|
|
842
|
+
req,
|
|
843
|
+
ctx.csrfMode,
|
|
844
|
+
{
|
|
845
|
+
// T3.3 DRY — canonical dispatcher
|
|
846
|
+
warn: dispatchCsrfWarn,
|
|
847
|
+
path: req.url
|
|
848
|
+
},
|
|
849
|
+
ctx.disallowed
|
|
850
|
+
);
|
|
851
|
+
if (decision.allow) return true;
|
|
852
|
+
sendError(
|
|
853
|
+
res,
|
|
854
|
+
"CSRF_INVALID",
|
|
855
|
+
decision.reason ?? "CSRF check failed",
|
|
856
|
+
403,
|
|
857
|
+
void 0,
|
|
858
|
+
ctx.requestId
|
|
859
|
+
);
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
async function runPreHandlerPipeline(p) {
|
|
863
|
+
if (p.serverDir) {
|
|
864
|
+
const result = await runMiddlewareAndContext(p.req, p.res, p.loadModule, p.serverDir);
|
|
865
|
+
if (result.aborted) return false;
|
|
866
|
+
Object.assign(p.ctx, result.ctx ?? {});
|
|
867
|
+
p.pluginRunner?.applyDecorations(p.ctx);
|
|
868
|
+
}
|
|
869
|
+
if (p.pluginRunner) {
|
|
870
|
+
const preResult = await p.pluginRunner.runPreHandler(p.buildPluginCtx(p.ctx));
|
|
871
|
+
if (preResult.shortCircuited) return false;
|
|
872
|
+
}
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
async function readActionBody(req, res, requestId, actionConfig) {
|
|
876
|
+
try {
|
|
877
|
+
const parsed = await parseRequestBody(req);
|
|
878
|
+
const accept = actionConfig.accept ?? "json";
|
|
879
|
+
let body;
|
|
880
|
+
if (accept === "form") {
|
|
881
|
+
body = formDataToObject(
|
|
882
|
+
synthesizeFormData(parsed),
|
|
883
|
+
actionConfig.input
|
|
884
|
+
);
|
|
885
|
+
} else if (parsed.json !== void 0) {
|
|
886
|
+
body = parsed.json;
|
|
887
|
+
} else {
|
|
888
|
+
body = parsed.fields;
|
|
889
|
+
}
|
|
890
|
+
return { ok: true, body };
|
|
891
|
+
} catch (err) {
|
|
892
|
+
sendError(res, "VALIDATION_ERROR", err.message, 400, void 0, requestId);
|
|
893
|
+
return { ok: false };
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function synthesizeFormData(parsed) {
|
|
897
|
+
const fd = new FormData();
|
|
898
|
+
for (const [name, value] of Object.entries(parsed.fields)) {
|
|
899
|
+
fd.append(name, value);
|
|
900
|
+
}
|
|
901
|
+
for (const file of parsed.files) {
|
|
902
|
+
const blob = new Blob([file.buffer], {
|
|
903
|
+
type: file.mimeType
|
|
904
|
+
});
|
|
905
|
+
fd.append(file.fieldname, blob, file.filename);
|
|
906
|
+
}
|
|
907
|
+
return fd;
|
|
908
|
+
}
|
|
909
|
+
function writeSerialized(res, serialized) {
|
|
910
|
+
if (serialized.type === "empty") {
|
|
911
|
+
res.statusCode = serialized.status;
|
|
912
|
+
res.end();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
res.statusCode = serialized.status;
|
|
916
|
+
res.setHeader("Content-Type", serialized.contentType);
|
|
917
|
+
res.end(serialized.body);
|
|
918
|
+
}
|
|
919
|
+
async function emitActionCallTelemetry(name, startedAt, input, outcome) {
|
|
920
|
+
if (!__IS_DEV) return;
|
|
921
|
+
try {
|
|
922
|
+
const mod = await import("./dispatcher-63LBXYLE.js");
|
|
923
|
+
mod.dispatcher.onActionCall({
|
|
924
|
+
// eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id
|
|
925
|
+
id: `act-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`,
|
|
926
|
+
timestamp: startedAt,
|
|
927
|
+
name,
|
|
928
|
+
input,
|
|
929
|
+
output: outcome.status === "success" ? outcome.output : void 0,
|
|
930
|
+
error: outcome.status === "error" ? {
|
|
931
|
+
code: outcome.error.code,
|
|
932
|
+
message: outcome.error.message,
|
|
933
|
+
fields: outcome.error instanceof ActionInputError ? outcome.error.fields : void 0
|
|
934
|
+
} : void 0,
|
|
935
|
+
durationMs: Date.now() - startedAt,
|
|
936
|
+
status: outcome.status
|
|
937
|
+
});
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async function executeActionWithOptions(opts) {
|
|
942
|
+
const {
|
|
943
|
+
filePath,
|
|
944
|
+
exportName,
|
|
945
|
+
req,
|
|
946
|
+
res,
|
|
947
|
+
loadModule,
|
|
948
|
+
serverDir,
|
|
949
|
+
requestId,
|
|
950
|
+
pluginRunner,
|
|
951
|
+
csrfMode = "strict",
|
|
952
|
+
disallowed
|
|
953
|
+
} = opts;
|
|
954
|
+
const buildPluginCtx = (ctxObj) => ({
|
|
955
|
+
request: req,
|
|
956
|
+
response: res,
|
|
957
|
+
ctx: ctxObj,
|
|
958
|
+
requestId: requestId ?? "no-id"
|
|
959
|
+
});
|
|
960
|
+
let ctx = {};
|
|
961
|
+
try {
|
|
962
|
+
if ((req.method ?? "GET").toUpperCase() !== "POST") {
|
|
963
|
+
sendError(res, "METHOD_NOT_ALLOWED", "Actions only accept POST", 405, void 0, requestId);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
if (pluginRunner) {
|
|
967
|
+
pluginRunner.applyDecorations(ctx);
|
|
968
|
+
const onReqResult = await pluginRunner.runOnRequest(buildPluginCtx(ctx));
|
|
969
|
+
if (onReqResult.shortCircuited) return;
|
|
970
|
+
}
|
|
971
|
+
const actionConfig = await loadActionConfig(loadModule, filePath, exportName, res, requestId);
|
|
972
|
+
if (!actionConfig) return;
|
|
973
|
+
if (!enforceCsrfForAction(req, res, { actionConfig, csrfMode, disallowed, requestId })) {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const pipeline = {
|
|
977
|
+
ctx,
|
|
978
|
+
buildPluginCtx,
|
|
979
|
+
pluginRunner,
|
|
980
|
+
serverDir,
|
|
981
|
+
loadModule,
|
|
982
|
+
req,
|
|
983
|
+
res
|
|
984
|
+
};
|
|
985
|
+
if (!await runPreHandlerPipeline(pipeline)) return;
|
|
986
|
+
ctx = pipeline.ctx;
|
|
987
|
+
const bodyOutcome = await readActionBody(req, res, requestId, actionConfig);
|
|
988
|
+
if (!bodyOutcome.ok) return;
|
|
989
|
+
const actionName = exportName === "default" ? deriveActionNameFromPath(filePath) : exportName;
|
|
990
|
+
const startedAt = Date.now();
|
|
991
|
+
const result = actionConfig.input.safeParse(bodyOutcome.body);
|
|
992
|
+
if (!result.success) {
|
|
993
|
+
const inputErr = new ActionInputError(result.error?.issues ?? []);
|
|
994
|
+
writeSerialized(res, serializeActionResult({ data: void 0, error: inputErr }));
|
|
995
|
+
await emitActionCallTelemetry(actionName, startedAt, bodyOutcome.body, {
|
|
996
|
+
status: "error",
|
|
997
|
+
error: inputErr
|
|
998
|
+
});
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
await runActionHandler({
|
|
1002
|
+
actionConfig,
|
|
1003
|
+
actionName,
|
|
1004
|
+
startedAt,
|
|
1005
|
+
input: result.data,
|
|
1006
|
+
ctx,
|
|
1007
|
+
res,
|
|
1008
|
+
pluginRunner,
|
|
1009
|
+
buildPluginCtx
|
|
1010
|
+
});
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
await handleActionError(err, { req, res, ctx, requestId, pluginRunner, buildPluginCtx });
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function deriveActionNameFromPath(filePath) {
|
|
1016
|
+
const base = filePath.split(/[\\/]/).pop() ?? "unknown";
|
|
1017
|
+
return base.replace(/\.[jt]sx?$/, "");
|
|
1018
|
+
}
|
|
1019
|
+
function isActionErrorLike(err) {
|
|
1020
|
+
if (err === null || typeof err !== "object") return false;
|
|
1021
|
+
const obj = err;
|
|
1022
|
+
return (obj.type === "TheoActionError" || obj.type === "TheoActionInputError") && typeof obj.code === "string" && typeof obj.status === "number";
|
|
1023
|
+
}
|
|
1024
|
+
async function runActionHandler(args) {
|
|
1025
|
+
try {
|
|
1026
|
+
const handlerResult = await args.actionConfig.handler({ input: args.input, ctx: args.ctx });
|
|
1027
|
+
writeSerialized(args.res, serializeActionResult({ data: handlerResult, error: void 0 }));
|
|
1028
|
+
if (args.pluginRunner) {
|
|
1029
|
+
await args.pluginRunner.runOnResponse(args.buildPluginCtx(args.ctx));
|
|
1030
|
+
}
|
|
1031
|
+
await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
|
|
1032
|
+
status: "success",
|
|
1033
|
+
output: handlerResult
|
|
1034
|
+
});
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
if (isActionErrorLike(err)) {
|
|
1037
|
+
writeSerialized(args.res, serializeActionResult({ data: void 0, error: err }));
|
|
1038
|
+
await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
|
|
1039
|
+
status: "error",
|
|
1040
|
+
error: err
|
|
1041
|
+
});
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const wrapped = new ActionError({
|
|
1045
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
1046
|
+
message: err instanceof Error ? err.message : "Action handler threw"
|
|
1047
|
+
});
|
|
1048
|
+
await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
|
|
1049
|
+
status: "error",
|
|
1050
|
+
error: wrapped
|
|
1051
|
+
});
|
|
1052
|
+
throw err;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
async function handleActionError(err, c) {
|
|
1056
|
+
return handleRequestError(err, {
|
|
1057
|
+
req: c.req,
|
|
1058
|
+
res: c.res,
|
|
1059
|
+
requestId: c.requestId,
|
|
1060
|
+
pluginRunner: c.pluginRunner,
|
|
1061
|
+
buildPluginCtx: c.buildPluginCtx
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// src/server/http/batch-handler.ts
|
|
1066
|
+
import { z as z2 } from "zod";
|
|
1067
|
+
var STRIPPED_HEADERS = [
|
|
1068
|
+
"authorization",
|
|
1069
|
+
"cookie",
|
|
1070
|
+
"x-forwarded-for",
|
|
1071
|
+
"x-forwarded-host",
|
|
1072
|
+
"x-forwarded-proto",
|
|
1073
|
+
"x-real-ip",
|
|
1074
|
+
"host"
|
|
1075
|
+
];
|
|
1076
|
+
var BATCH_PATH = "/api/__theo_batch__";
|
|
1077
|
+
var DEFAULT_MAX_BATCH = 32;
|
|
1078
|
+
var BatchPathConflictError = class extends Error {
|
|
1079
|
+
constructor(path) {
|
|
1080
|
+
super(
|
|
1081
|
+
`Server route conflicts with reserved batch path ${path}. Rename the route or disable batching in theo.config.ts.`
|
|
1082
|
+
);
|
|
1083
|
+
this.name = "BatchPathConflictError";
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
var batchRequestSchema = z2.object({
|
|
1087
|
+
path: z2.string().min(1),
|
|
1088
|
+
method: z2.string().min(1),
|
|
1089
|
+
query: z2.record(z2.string(), z2.unknown()).optional(),
|
|
1090
|
+
body: z2.unknown().optional(),
|
|
1091
|
+
headers: z2.record(z2.string(), z2.string()).optional()
|
|
1092
|
+
});
|
|
1093
|
+
var batchPayloadSchema = z2.object({
|
|
1094
|
+
requests: z2.array(batchRequestSchema).min(1)
|
|
1095
|
+
});
|
|
1096
|
+
function sanitizeItemHeaders(itemHeaders, outerHeaders) {
|
|
1097
|
+
const out = {};
|
|
1098
|
+
if (itemHeaders) {
|
|
1099
|
+
for (const [k, v] of Object.entries(itemHeaders)) {
|
|
1100
|
+
const lower = k.toLowerCase();
|
|
1101
|
+
if (!STRIPPED_HEADERS.includes(lower)) {
|
|
1102
|
+
out[lower] = v;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (outerHeaders) {
|
|
1107
|
+
for (const stripped of STRIPPED_HEADERS) {
|
|
1108
|
+
if (Object.hasOwn(outerHeaders, stripped)) {
|
|
1109
|
+
out[stripped] = outerHeaders[stripped];
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return out;
|
|
1114
|
+
}
|
|
1115
|
+
async function handleBatchRequest(payload, options) {
|
|
1116
|
+
const parsed = batchPayloadSchema.parse(payload);
|
|
1117
|
+
const max = options.max ?? DEFAULT_MAX_BATCH;
|
|
1118
|
+
if (parsed.requests.length > max) {
|
|
1119
|
+
throw new Error(`Batch size ${parsed.requests.length} exceeds max ${max}`);
|
|
1120
|
+
}
|
|
1121
|
+
const results = [];
|
|
1122
|
+
for (const item of parsed.requests) {
|
|
1123
|
+
const sanitized = {
|
|
1124
|
+
...item,
|
|
1125
|
+
headers: sanitizeItemHeaders(item.headers, options.outerHeaders)
|
|
1126
|
+
};
|
|
1127
|
+
try {
|
|
1128
|
+
const r = await options.execute(sanitized);
|
|
1129
|
+
results.push(r);
|
|
1130
|
+
} catch (err) {
|
|
1131
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1132
|
+
results.push({ error: { message } });
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return { results };
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/server/http/cors.ts
|
|
1139
|
+
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
|
|
1140
|
+
var DEFAULT_ALLOWED_HEADERS = ["Content-Type", "X-Theo-Action", "Authorization"];
|
|
1141
|
+
var DEFAULT_MAX_AGE = 600;
|
|
1142
|
+
function readOrigin(req) {
|
|
1143
|
+
const raw = req.headers.origin;
|
|
1144
|
+
if (raw === void 0) return void 0;
|
|
1145
|
+
if (Array.isArray(raw)) {
|
|
1146
|
+
return raw.find((v) => typeof v === "string" && v.length > 0);
|
|
1147
|
+
}
|
|
1148
|
+
return raw;
|
|
1149
|
+
}
|
|
1150
|
+
function matchesOrigin(origin, allowed) {
|
|
1151
|
+
if (allowed === "*") return true;
|
|
1152
|
+
if (typeof allowed === "string") return origin === allowed;
|
|
1153
|
+
if (allowed instanceof RegExp) {
|
|
1154
|
+
allowed.lastIndex = 0;
|
|
1155
|
+
return allowed.test(origin);
|
|
1156
|
+
}
|
|
1157
|
+
if (Array.isArray(allowed)) {
|
|
1158
|
+
for (const entry of allowed) {
|
|
1159
|
+
if (typeof entry === "string" && origin === entry) return true;
|
|
1160
|
+
if (entry instanceof RegExp) {
|
|
1161
|
+
entry.lastIndex = 0;
|
|
1162
|
+
if (entry.test(origin)) return true;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
if (typeof allowed === "function") {
|
|
1168
|
+
try {
|
|
1169
|
+
return allowed(origin);
|
|
1170
|
+
} catch {
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
function createCorsHandler(config) {
|
|
1177
|
+
const methods = (config.methods ?? DEFAULT_METHODS).slice();
|
|
1178
|
+
const allowedHeaders = (config.allowedHeaders ?? DEFAULT_ALLOWED_HEADERS).slice();
|
|
1179
|
+
const maxAge = String(config.maxAge ?? DEFAULT_MAX_AGE);
|
|
1180
|
+
const credentials = config.credentials === true;
|
|
1181
|
+
return {
|
|
1182
|
+
handlePreflight(req, res) {
|
|
1183
|
+
if (req.method !== "OPTIONS") return false;
|
|
1184
|
+
const acMethod = req.headers["access-control-request-method"];
|
|
1185
|
+
if (!acMethod) return false;
|
|
1186
|
+
const origin = readOrigin(req);
|
|
1187
|
+
if (!origin) return false;
|
|
1188
|
+
if (!matchesOrigin(origin, config.origins)) {
|
|
1189
|
+
res.statusCode = 403;
|
|
1190
|
+
res.end();
|
|
1191
|
+
return true;
|
|
1192
|
+
}
|
|
1193
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1194
|
+
res.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
1195
|
+
res.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
1196
|
+
res.setHeader("Access-Control-Max-Age", maxAge);
|
|
1197
|
+
res.setHeader("Vary", "Origin");
|
|
1198
|
+
if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1199
|
+
res.statusCode = 204;
|
|
1200
|
+
res.end();
|
|
1201
|
+
return true;
|
|
1202
|
+
},
|
|
1203
|
+
applyHeaders(req, res) {
|
|
1204
|
+
const origin = readOrigin(req);
|
|
1205
|
+
if (!origin) return;
|
|
1206
|
+
if (!matchesOrigin(origin, config.origins)) return;
|
|
1207
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1208
|
+
res.setHeader("Vary", "Origin");
|
|
1209
|
+
if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1210
|
+
if (config.exposedHeaders && config.exposedHeaders.length > 0) {
|
|
1211
|
+
res.setHeader("Access-Control-Expose-Headers", config.exposedHeaders.join(", "));
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
function createCorsWebHandler(config) {
|
|
1217
|
+
const methods = (config.methods ?? DEFAULT_METHODS).slice();
|
|
1218
|
+
const allowedHeaders = (config.allowedHeaders ?? DEFAULT_ALLOWED_HEADERS).slice();
|
|
1219
|
+
const maxAge = String(config.maxAge ?? DEFAULT_MAX_AGE);
|
|
1220
|
+
const credentials = config.credentials === true;
|
|
1221
|
+
return {
|
|
1222
|
+
handlePreflightRequest(request) {
|
|
1223
|
+
if (request.method !== "OPTIONS") return null;
|
|
1224
|
+
const acMethod = request.headers.get("access-control-request-method");
|
|
1225
|
+
if (acMethod === null || acMethod.length === 0) return null;
|
|
1226
|
+
const origin = request.headers.get("origin");
|
|
1227
|
+
if (origin === null || origin.length === 0) return null;
|
|
1228
|
+
if (!matchesOrigin(origin, config.origins)) {
|
|
1229
|
+
return new Response(null, { status: 403 });
|
|
1230
|
+
}
|
|
1231
|
+
const headers = new Headers({
|
|
1232
|
+
"Access-Control-Allow-Origin": origin,
|
|
1233
|
+
"Access-Control-Allow-Methods": methods.join(", "),
|
|
1234
|
+
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
1235
|
+
"Access-Control-Max-Age": maxAge,
|
|
1236
|
+
Vary: "Origin"
|
|
1237
|
+
});
|
|
1238
|
+
if (credentials) headers.set("Access-Control-Allow-Credentials", "true");
|
|
1239
|
+
return new Response(null, { status: 204, headers });
|
|
1240
|
+
},
|
|
1241
|
+
applyCorsHeaders(request, target) {
|
|
1242
|
+
const origin = request.headers.get("origin");
|
|
1243
|
+
if (origin === null || origin.length === 0) return;
|
|
1244
|
+
if (!matchesOrigin(origin, config.origins)) return;
|
|
1245
|
+
target.set("Access-Control-Allow-Origin", origin);
|
|
1246
|
+
target.set("Vary", "Origin");
|
|
1247
|
+
if (credentials) target.set("Access-Control-Allow-Credentials", "true");
|
|
1248
|
+
if (config.exposedHeaders !== void 0 && config.exposedHeaders.length > 0) {
|
|
1249
|
+
target.set("Access-Control-Expose-Headers", config.exposedHeaders.join(", "));
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/server/http/trace-context.ts
|
|
1256
|
+
var TRACE_HEADER = "x-trace-id";
|
|
1257
|
+
var TRACE_PARENT_HEADER = "traceparent";
|
|
1258
|
+
var REQUEST_ID_HEADER = "x-request-id";
|
|
1259
|
+
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
|
1260
|
+
function parseTraceparent(value) {
|
|
1261
|
+
if (!value) return null;
|
|
1262
|
+
const m = TRACEPARENT_RE.exec(value);
|
|
1263
|
+
if (!m) return null;
|
|
1264
|
+
const traceId = m[1];
|
|
1265
|
+
if (/^0+$/.test(traceId)) return null;
|
|
1266
|
+
return traceId;
|
|
1267
|
+
}
|
|
1268
|
+
function pickHeader(value) {
|
|
1269
|
+
if (Array.isArray(value)) {
|
|
1270
|
+
for (const v of value) {
|
|
1271
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
1272
|
+
}
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
function resolveTraceIdFromHeaders(traceparent, requestId) {
|
|
1279
|
+
if (traceparent !== null) {
|
|
1280
|
+
const parsed = parseTraceparent(traceparent);
|
|
1281
|
+
if (parsed !== null) return parsed;
|
|
1282
|
+
}
|
|
1283
|
+
if (requestId !== null) return requestId;
|
|
1284
|
+
return globalThis.crypto.randomUUID();
|
|
1285
|
+
}
|
|
1286
|
+
function extractTraceId(req) {
|
|
1287
|
+
return resolveTraceIdFromHeaders(
|
|
1288
|
+
pickHeader(req.headers[TRACE_PARENT_HEADER]),
|
|
1289
|
+
pickHeader(req.headers[REQUEST_ID_HEADER])
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
function extractTraceIdFromRequest(request) {
|
|
1293
|
+
return resolveTraceIdFromHeaders(
|
|
1294
|
+
request.headers.get(TRACE_PARENT_HEADER),
|
|
1295
|
+
request.headers.get(REQUEST_ID_HEADER)
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
export {
|
|
1300
|
+
ActionError,
|
|
1301
|
+
ActionInputError,
|
|
1302
|
+
extractUniversalIssues,
|
|
1303
|
+
isActionError,
|
|
1304
|
+
isInputError,
|
|
1305
|
+
sendJson,
|
|
1306
|
+
sendError,
|
|
1307
|
+
_resetMiddlewareCacheForTests,
|
|
1308
|
+
runMiddlewareAndContext,
|
|
1309
|
+
executeRoute,
|
|
1310
|
+
executeAction,
|
|
1311
|
+
STRIPPED_HEADERS,
|
|
1312
|
+
BATCH_PATH,
|
|
1313
|
+
BatchPathConflictError,
|
|
1314
|
+
handleBatchRequest,
|
|
1315
|
+
matchesOrigin,
|
|
1316
|
+
createCorsHandler,
|
|
1317
|
+
createCorsWebHandler,
|
|
1318
|
+
TRACE_HEADER,
|
|
1319
|
+
TRACE_PARENT_HEADER,
|
|
1320
|
+
parseTraceparent,
|
|
1321
|
+
extractTraceId,
|
|
1322
|
+
extractTraceIdFromRequest
|
|
1323
|
+
};
|
|
1324
|
+
//# sourceMappingURL=chunk-5Z2727GV.js.map
|