@spfn/core 0.3.0-beta.32 → 0.3.0-beta.33
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/event/index.d.ts +2 -2
- package/dist/event/index.js +32 -4
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +2 -2
- package/dist/event/sse/client.js +12 -4
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +40 -5
- package/dist/event/sse/index.js +437 -28
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +2 -2
- package/dist/event/ws/index.d.ts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +223 -37
- package/dist/server/index.js.map +1 -1
- package/dist/state/client.d.ts +8 -1
- package/dist/state/client.js +15 -5
- package/dist/state/client.js.map +1 -1
- package/dist/state/react.d.ts +3 -1
- package/dist/state/react.js.map +1 -1
- package/dist/{token-manager-Ckjt2-qq.d.ts → token-manager-CvOsAbnK.d.ts} +47 -3
- package/dist/{types-CurMab2Q.d.ts → types-BrO7j6mR.d.ts} +17 -3
- package/dist/{types-DncqXfWN.d.ts → types-DH6d2eOb.d.ts} +1 -1
- package/package.json +1 -1
- package/src/event/README.md +66 -6
- package/src/state/README.md +9 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/event/sse/bounded-writer.ts","../../../src/event/sse/connection-registry.ts","../../../src/event/sse/handler.ts","../../../src/event/sse/token-manager.ts"],"names":["logger"],"mappings":";;;;;;;AA0DO,SAAS,mBAAA,CACZ,MAAA,EACA,QAAA,EACA,OAAA,EAEJ;AACI,EAAA,MAAM,UAAsB,EAAC;AAC7B,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,MAAA,GAAS,KAAA;AAKb,EAAA,MAAM,YAAY,MAClB;AACI,IAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,QAAA,IAAY,QAAA,EACjC;AACI,MAAA,OAAO,KAAA;AAAA,IACX;AAEA,IAAA,OAAA,CAAQ,yCAAyC,CAAA;AAEjD,IAAA,OAAO,IAAA;AAAA,EACX,CAAA;AAEA,EAAA,MAAM,QAAQ,YACd;AACI,IAAA,IAAI,YAAY,MAAA,EAChB;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,QAAA,GAAW,IAAA;AAEX,IAAA,OAAO,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,CAAC,MAAA,EAC9B;AACI,MAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,EAAM;AAC5B,MAAA,IACA;AACI,QAAA,MAAM,MAAA,CAAO,SAAS,KAAK,CAAA;AAAA,MAC/B,SACO,GAAA,EACP;AACI,QAAA,QAAA,GAAW,KAAA;AACX,QAAA,OAAA,CAAQ,eAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AAExD,QAAA;AAAA,MACJ;AAAA,IACJ;AAEA,IAAA,QAAA,GAAW,KAAA;AAAA,EACf,CAAA;AAEA,EAAA,OAAO;AAAA,IACH,QAAQ,KAAA,EACR;AACI,MAAA,IAAI,MAAA,EACJ;AACI,QAAA;AAAA,MACJ;AAEA,MAAA,OAAA,CAAQ,KAAK,KAAK,CAAA;AAElB,MAAA,IAAI,CAAC,WAAU,EACf;AACI,QAAA,KAAK,KAAA,EAAM;AAAA,MACf;AAAA,IACJ,CAAA;AAAA,IAEA,OAAA,GACA;AACI,MAAA,IAAI,MAAA,EACJ;AACI,QAAA,OAAO,KAAA;AAAA,MACX;AAEA,MAAA,QAAA,EAAA;AAEA,MAAA,OAAO,CAAC,SAAA,EAAU;AAAA,IACtB,CAAA;AAAA,IAEA,aAAA,GACA;AACI,MAAA,QAAA,GAAW,CAAA;AAAA,IACf,CAAA;AAAA,IAEA,KAAA,GACA;AACI,MAAA,MAAA,GAAS,IAAA;AACT,MAAA,OAAA,CAAQ,MAAA,GAAS,CAAA;AAAA,IACrB,CAAA;AAAA,IAEA,IAAI,MAAA,GACJ;AACI,MAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,IACnB;AAAA,GACJ;AACJ;ACvIA,IAAM,cAAA,GAAiB,MAAA,CAAO,KAAA,CAAM,gBAAgB,CAAA;AAG7C,IAAM,oBAAA,GAAuB,sBAAA;AAG7B,IAAM,oBAAA,GAAuB,sBAAA;AA8D7B,SAAS,wBAAA,GAChB;AACI,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAgC;AACtD,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAkC;AAM/D,EAAA,MAAM,cAAA,GAAiB,CAAC,OAAA,EAAiB,MAAA,KACzC;AACI,IAAA,MAAM,UAA2B,EAAC;AAClC,IAAA,QAAA,CAAS,gBAAA,EAAkB,SAAS,OAAO,CAAA;AAC3C,IAAA,MAAM,GAAA,GAAM,QAAQ,MAAA,EAAQ,MAAM,cAAc,gBAAA,EAAkB,OAAA,EAAS,OAAO,CAAC,CAAA;AAEnF,IAAA,OAAO;AAAA,MACH,MAAA,EAAQ,GAAA;AAAA,MACR,IAAA,EAAM,CAAC,UAAA,KACP;AACI,QAAA,IAAI,CAAC,KAAI,EACT;AACI,UAAA,OAAO,KAAA;AAAA,QACX;AAEA,QAAA,aAAA,CAAc,YAAY,OAAO,CAAA;AACjC,QAAA,QAAA,CAAS,SAAA,EAAW,SAAS,UAAU,CAAA;AAEvC,QAAA,OAAO,IAAA;AAAA,MACX;AAAA,KACJ;AAAA,EACJ,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,IAAA,EAA0B,OAAA,EAAiB,WAAmB,KAAA,KAC9E;AACI,IAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,GAAA,CAAI,OAAO,CAAA;AAC5C,IAAA,MAAM,WAAA,GAAc,SAAA,CAAU,GAAA,CAAI,OAAO,CAAA;AACzC,IAAA,OAAA,EAAS,OAAA,CAAQ,aAAW,OAAA,CAAQ,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,CAAC,CAAA;AACpE,IAAA,WAAA,EAAa,QAAQ,CAAA,UAAA,KACrB;AACI,MAAA,IAAI,cAAA,CAAe,KAAA,EAAO,UAAA,CAAW,OAAO,CAAA,EAC5C;AACI,QAAA,CAAC,IAAA,KAAS,OAAA,GAAU,OAAA,GAAU,QAAA,EAAU,YAAY,SAAS,CAAA;AAAA,MACjE;AAAA,IACJ,CAAC,CAAA;AAED,IAAA,IAAI,CAAC,OAAA,IAAW,CAAC,WAAA,EACjB;AACI,MAAA,cAAA,CAAe,MAAM,uEAAA,EAAyE;AAAA,QAC1F,KAAA,EAAO,SAAA;AAAA,QACP;AAAA,OACH,CAAA;AAAA,IACL;AAAA,EACJ,CAAA;AAEA,EAAA,OAAO;AAAA,IACH,cAAA;AAAA,IAEA,MAAA,EAAQ,CAAC,UAAA,KACT;AACI,MAAA,IAAI,WAAW,OAAA,EACf;AACI,QAAA,aAAA,CAAc,SAAA,EAAW,UAAA,CAAW,OAAA,EAAS,UAAU,CAAA;AAAA,MAC3D;AAAA,IACJ,CAAA;AAAA;AAAA,IAGA,OAAO,CAAC,OAAA,KAA4B,UAAU,GAAA,CAAI,OAAO,GAAG,IAAA,IAAQ,CAAA;AAAA,IAEpE,cAAc,CAAC,OAAA,KAA4B,iBAAiB,GAAA,CAAI,OAAO,GAAG,IAAA,IAAQ,CAAA;AAAA,IAElF,KAAA,EAAO,CAAC,OAAA,EAAiB,SAAA,EAAmB,UACxC,MAAA,CAAO,OAAA,EAAS,OAAA,EAAS,SAAA,EAAW,KAAK,CAAA;AAAA,IAE7C,MAAA,EAAQ,CAAC,OAAA,EAAiB,SAAA,EAAmB,UACzC,MAAA,CAAO,QAAA,EAAU,OAAA,EAAS,SAAA,EAAW,KAAK;AAAA,GAClD;AACJ;AAQA,SAAS,OAAA,CAAQ,QAAqB,MAAA,EACtC;AACI,EAAA,IAAI,KAAA,GAAQ,KAAA;AACZ,EAAA,MAAM,MAAM,MACZ;AACI,IAAA,IAAI,KAAA,EACJ;AACI,MAAA,OAAO,KAAA;AAAA,IACX;AAEA,IAAA,KAAA,GAAQ,IAAA;AACR,IAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,GAAG,CAAA;AACvC,IAAA,MAAA,EAAO;AAEP,IAAA,OAAO,IAAA;AAAA,EACX,CAAA;AAEA,EAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,GAAA,EAAK,EAAE,IAAA,EAAM,MAAM,CAAA;AACpD,EAAA,IAAI,OAAO,OAAA,EACX;AACI,IAAA,GAAA,EAAI;AAAA,EACR;AAEA,EAAA,OAAO,GAAA;AACX;AAEA,SAAS,QAAA,CAAY,GAAA,EAA0B,GAAA,EAAa,KAAA,EAC5D;AACI,EAAA,MAAM,MAAM,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA,wBAAS,GAAA,EAAO;AACvC,EAAA,GAAA,CAAI,IAAI,KAAK,CAAA;AACb,EAAA,GAAA,CAAI,GAAA,CAAI,KAAK,GAAG,CAAA;AACpB;AAGA,SAAS,aAAA,CAAiB,GAAA,EAA0B,GAAA,EAAa,KAAA,EACjE;AACI,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AACvB,EAAA,GAAA,EAAK,OAAO,KAAK,CAAA;AAEjB,EAAA,IAAI,GAAA,EAAK,SAAS,CAAA,EAClB;AACI,IAAA,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,EAClB;AACJ;AAOO,SAAS,cAAA,CAAe,OAAsC,OAAA,EACrE;AACI,EAAA,OAAO,KAAA,KAAU,UAAa,MAAA,CAAO,OAAA,CAAQ,MAAM,KAAK,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,KAAA,EAAO,KAAK,CAAA,KAC1E,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,KAAK,KAAK,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,KAAM,KAAK,CAAA;AAC7E;AAQA,SAAS,aAAA,CAAc,YAA2B,OAAA,EAClD;AACI,EAAA,OAAA,CAAQ,QAAQ,CAAC,EAAE,IAAA,EAAM,SAAA,EAAW,OAAM,KAC1C;AACI,IAAA,IAAI,CAAC,cAAA,CAAe,KAAA,EAAO,UAAA,CAAW,OAAO,CAAA,EAC7C;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,CAAC,IAAA,EAAM,EAAE,CAAA,GAAI,IAAA,KAAS,UACtB,CAAC,UAAA,CAAW,MAAA,EAAQ,UAAA,CAAW,OAAO,CAAA,GACtC,CAAC,UAAA,CAAW,OAAA,EAAS,WAAW,MAAM,CAAA;AAE5C,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,SAAS,CAAA,EACzB;AACI,MAAA,EAAA,CAAG,IAAI,SAAS,CAAA;AAAA,IACpB;AAAA,EACJ,CAAC,CAAA;AACL;AAKA,SAAS,OAAA,CAAQ,YAA2B,SAAA,EAC5C;AACI,EAAA,IAAI,CAAC,UAAA,CAAW,MAAA,CAAO,GAAA,CAAI,SAAS,CAAA,EACpC;AACI,IAAA;AAAA,EACJ;AAEA,EAAA,UAAA,CAAW,aAAa,GAAA,CAAI,SAAA,EAAW,UAAA,CAAW,SAAA,CAAU,SAAS,CAAC,CAAA;AACtE,EAAA,UAAA,CAAW,MAAA,CAAO,OAAO,SAAS,CAAA;AAClC,EAAA,UAAA,CAAW,OAAA,CAAQ,IAAI,SAAS,CAAA;AAChC,EAAA,UAAA,CAAW,MAAA,CAAO,sBAAsB,SAAS,CAAA;AACrD;AAQA,SAAS,QAAA,CAAS,YAA2B,SAAA,EAC7C;AACI,EAAA,IAAI,CAAC,UAAA,CAAW,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA,EACrC;AACI,IAAA;AAAA,EACJ;AAEA,EAAA,UAAA,CAAW,YAAA,CAAa,GAAA,CAAI,SAAS,CAAA,IAAI;AACzC,EAAA,UAAA,CAAW,YAAA,CAAa,OAAO,SAAS,CAAA;AACxC,EAAA,UAAA,CAAW,OAAA,CAAQ,OAAO,SAAS,CAAA;AACnC,EAAA,UAAA,CAAW,MAAA,CAAO,IAAI,SAAS,CAAA;AAC/B,EAAA,UAAA,CAAW,MAAA,CAAO,sBAAsB,SAAS,CAAA;AACrD;AAMO,SAAS,eAAe,UAAA,EAC/B;AACI,EAAA,UAAA,CAAW,YAAA,CAAa,OAAA,CAAQ,CAAA,WAAA,KAAe,WAAA,EAAa,CAAA;AAC5D,EAAA,UAAA,CAAW,aAAa,KAAA,EAAM;AAClC;;;AC5QA,IAAM,SAAA,GAAYA,MAAAA,CAAO,KAAA,CAAM,gBAAgB,CAAA;AAU/C,IAAM,UAAA,uBAAiB,OAAA,EAAqC;AAErD,SAAS,cAAA,CAAe,WAAmB,OAAA,EAClD;AACI,EAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,OAAO,OAAA,KAAY,QAAA,EAC3C;AACI,IAAA,OAAO,KAAK,SAAA,CAAU,EAAE,OAAO,SAAA,EAAW,IAAA,EAAM,SAAS,CAAA;AAAA,EAC7D;AAEA,EAAA,IAAI,OAAA,GAAU,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AACpC,EAAA,IAAI,CAAC,OAAA,EACL;AACI,IAAA,OAAA,uBAAc,GAAA,EAAoB;AAClC,IAAA,UAAA,CAAW,GAAA,CAAI,SAAS,OAAO,CAAA;AAAA,EACnC;AAEA,EAAA,IAAI,UAAA,GAAa,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA;AACtC,EAAA,IAAI,eAAe,MAAA,EACnB;AACI,IAAA,UAAA,GAAa,KAAK,SAAA,CAAU,EAAE,OAAO,SAAA,EAAW,IAAA,EAAM,SAAS,CAAA;AAC/D,IAAA,OAAA,CAAQ,GAAA,CAAI,WAAW,UAAU,CAAA;AAAA,EACrC;AAEA,EAAA,OAAO,UAAA;AACX;AAGA,IAAM,iBAAA,GAAoB,GAAA;AAgCnB,SAAS,gBAAA,CACZ,MAAA,EACA,MAAA,GAA2B,IAC3B,YAAA,EAEJ;AACI,EAAA,MAAM;AAAA;AAAA;AAAA;AAAA,IAIF,YAAA,GAAe,GAAA;AAAA,IACf,IAAA,EAAM,UAAA;AAAA,IACN,QAAA,GAAW;AAAA,GACf,GAAI,MAAA;AAEJ,EAAA,MAAM,WAAW,wBAAA,EAAyB;AAC1C,EAAA,4BAAA,CAA6B,QAAQ,QAAQ,CAAA;AAE7C,EAAA,MAAM,eAA6B,EAAE,MAAA,EAAQ,UAAA,EAAY,QAAA,EAAU,cAAc,QAAA,EAAS;AAE1F,EAAA,MAAM,OAAA,GAAU,OAAO,CAAA,KACvB;AACI,IAAA,MAAM,SAAA,GAAY,MAAM,eAAA,CAAgB,CAAA,EAAG,cAAc,YAAY,CAAA;AACrE,IAAA,IAAI,qBAAqB,QAAA,EACzB;AACI,MAAA,OAAO,SAAA;AAAA,IACX;AAEA,IAAA,IAAI,CAAA,CAAE,GAAA,CAAI,GAAA,CAAI,MAAA,CAAO,OAAA,EACrB;AAGI,MAAA,SAAA,CAAU,SAAS,MAAA,EAAO;AAE1B,MAAA,OAAO,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,IAC7C;AAGA,IAAA,CAAA,CAAE,MAAA,CAAO,qBAAqB,IAAI,CAAA;AAElC,IAAA,OAAO,SAAA,CAAU,CAAA,EAAG,CAAA,MAAA,KAAU,SAAA,CAAU,CAAA,EAAG,QAAQ,SAAA,EAAW,YAAY,CAAA,EAAG,OAAO,GAAA,KACpF;AACI,MAAA,SAAA,CAAU,MAAM,kBAAA,EAAoB;AAAA,QAChC,OAAO,GAAA,CAAI;AAAA,OACd,CAAA;AAAA,IACL,CAAC,CAAA;AAAA,EACL,CAAA;AAEA,EAAA,OAAO,MAAA,CAAO,OAAO,OAAA,EAAS;AAAA;AAAA,IAE1B,gBAAA,EAAkB,CAAC,OAAA,KAA4B,QAAA,CAAS,MAAM,OAAO,CAAA;AAAA;AAAA,IAGrE,kBAAA,EAAoB,CAAC,OAAA,KAA4B,QAAA,CAAS,aAAa,OAAO;AAAA,GACjF,CAAA;AACL;AAoCA,SAAS,4BAAA,CAA6B,QAA6B,QAAA,EACnE;AACI,EAAA,KAAA,MAAW,SAAA,IAAa,OAAO,UAAA,EAC/B;AACI,IAAA,MAAA,CAAO,MAAA,CAAO,SAAS,CAAA,CAAE,4BAAA,CAA6B;AAAA,MAClD,KAAA,EAAO,CAAC,OAAA,EAAiB,KAAA,KAA8B,SAAS,KAAA,CAAM,OAAA,EAAS,WAAW,KAAK,CAAA;AAAA,MAC/F,MAAA,EAAQ,CAAC,OAAA,EAAiB,KAAA,KAA8B,SAAS,MAAA,CAAO,OAAA,EAAS,WAAW,KAAK;AAAA,KACpG,CAAA;AAAA,EACL;AACJ;AAKA,eAAe,eAAA,CACX,CAAA,EACA,MAAA,EACA,YAAA,EAEJ;AACI,EAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAY,QAAA,EAAS,GAAI,MAAA;AAGzC,EAAA,MAAM,OAAA,GAAU,MAAM,iBAAA,CAAkB,CAAA,EAAG,YAAY,CAAA;AACvD,EAAA,IAAI,YAAY,KAAA,EAChB;AACI,IAAA,OAAO,EAAE,IAAA,CAAK,EAAE,KAAA,EAAO,yBAAA,IAA6B,GAAG,CAAA;AAAA,EAC3D;AACA,EAAA,IAAI,YAAY,IAAA,EAChB;AACI,IAAA,OAAO,EAAE,IAAA,CAAK,EAAE,KAAA,EAAO,0BAAA,IAA8B,GAAG,CAAA;AAAA,EAC5D;AACA,EAAA,IAAI,OAAA,EACJ;AACI,IAAA,CAAA,CAAE,GAAA,CAAI,cAAc,OAAO,CAAA;AAAA,EAC/B;AAGA,EAAA,MAAM,SAAA,GAAY,qBAAqB,CAAC,CAAA;AACxC,EAAA,IAAI,CAAC,SAAA,EACL;AACI,IAAA,OAAO,EAAE,IAAA,CAAK,EAAE,KAAA,EAAO,0BAAA,IAA8B,GAAG,CAAA;AAAA,EAC5D;AAEA,EAAA,MAAM,kBAAkB,MAAA,CAAO,UAAA;AAC/B,EAAA,MAAM,aAAA,GAAgB,UAAU,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,eAAA,CAAgB,QAAA,CAAS,CAAC,CAAC,CAAA;AACxE,EAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAC3B;AACI,IAAA,OAAO,EAAE,IAAA,CAAK;AAAA,MACV,KAAA,EAAO,qBAAA;AAAA,MACP,aAAA;AAAA,MACA,WAAA,EAAa;AAAA,OACd,GAAG,CAAA;AAAA,EACV;AAEA,EAAA,OAAO,YAAA;AAAA,IACH,OAAA,GAAU,SAAS,cAAA,CAAe,OAAA,EAAS,EAAE,GAAA,CAAI,GAAA,CAAI,MAAM,CAAA,GAAI,MAAA;AAAA,IAC/D,MAAM,mBAAA,CAAoB,CAAA,EAAG,MAAA,EAAQ,UAAA,EAAY,SAAS,SAAS;AAAA,GACvE;AACJ;AASA,eAAe,YAAA,CACX,SACA,KAAA,EAEJ;AACI,EAAA,IAAI,OAAA;AAEJ,EAAA,IACA;AACI,IAAA,OAAA,GAAU,MAAM,KAAA,EAAM;AAEtB,IAAA,OAAO,mBAAmB,QAAA,GAAW,OAAA,GAAU,EAAE,GAAG,SAAS,OAAA,EAAQ;AAAA,EACzE,CAAA,SACA;AAEI,IAAA,IAAI,CAAC,OAAA,IAAW,OAAA,YAAmB,QAAA,EACnC;AACI,MAAA,OAAA,EAAS,MAAA,EAAO;AAAA,IACpB;AAAA,EACJ;AACJ;AAOA,eAAe,mBAAA,CACX,CAAA,EACA,MAAA,EACA,UAAA,EACA,SACA,SAAA,EAEJ;AAEI,EAAA,MAAM,UAAA,GAAa,MAAM,eAAA,CAAgB,OAAA,EAAS,WAAW,UAAU,CAAA;AACvE,EAAA,IAAI,eAAe,IAAA,EACnB;AACI,IAAA,OAAO,EAAE,IAAA,CAAK,EAAE,KAAA,EAAO,yCAAA,IAA6C,GAAG,CAAA;AAAA,EAC3E;AAGA,EAAA,MAAM,OAAA,GAA4B,EAAE,KAAA,EAAO,CAAA,CAAE,GAAA,CAAI,KAAA,EAAM,EAAG,OAAA,EAAS,CAAA,CAAE,GAAA,CAAI,MAAA,EAAO,EAAE;AAClF,EAAA,MAAM,QAAA,GAAW,MAAM,0BAAA,CAA2B,OAAA,EAAS,SAAS,UAAU,CAAA;AAC9E,EAAA,IAAI,CAAC,QAAA,EACL;AACI,IAAA,OAAO,EAAE,IAAA,CAAK,EAAE,KAAA,EAAO,iCAAA,IAAqC,GAAG,CAAA;AAAA,EACnE;AAEA,EAAA,MAAM,OAAA,GAAU,OAAA,GACV,MAAM,iBAAA,CAAkB,MAAA,EAAQ,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,QAAA,CAAS,SAAA,EAAW,OAAA,EAAS,CAAA,GAC/F,UAAA;AACN,EAAA,MAAM,MAAA,GAAS,UAAU,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,OAAA,CAAQ,QAAA,CAAS,CAAC,CAAC,CAAA;AAEzD,EAAA,SAAA,CAAU,MAAM,0BAAA,EAA4B;AAAA,IACxC,MAAA,EAAQ,OAAA;AAAA,IACR,YAAA,EAAc,MAAA;AAAA,IACd,SAAS,OAAA,IAAW,MAAA;AAAA,IACpB,QAAA,EAAU,EAAE,GAAA,CAAI,MAAA,CAAO,iBAAiB,CAAA,IAAK,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,WAAW;AAAA,GACxE,CAAA;AAED,EAAA,OAAO,EAAE,SAAS,SAAA,EAAW,OAAA,EAAS,QAAQ,OAAA,EAAS,SAAA,EAAW,SAAS,SAAA,EAAU;AACzF;AAOA,eAAe,0BAAA,CACX,OAAA,EACA,OAAA,EACA,UAAA,EAEJ;AACI,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,UAAA,EAAY,gBAAA,EAC7B;AACI,IAAA,OAAO,EAAE,WAAW,MAAA,EAAU;AAAA,EAClC;AAEA,EAAA,IACA;AACI,IAAA,OAAO,EAAE,SAAA,EAAW,MAAM,WAAW,gBAAA,CAAiB,OAAA,EAAS,OAAO,CAAA,EAAE;AAAA,EAC5E,CAAA,CAAA,MAEA;AACI,IAAA,SAAA,CAAU,IAAA,CAAK,+DAAA,EAA4D,EAAE,OAAA,EAAS,CAAA;AAEtF,IAAA,OAAO,IAAA;AAAA,EACX;AACJ;AAMA,eAAe,iBAAA,CACX,MAAA,EACA,MAAA,EACA,GAAA,EAEJ;AACI,EAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,GAAA,CAAI,OAAO,GAAA,CAAI,CAAA,SAAA,KAAa,eAAA,CAAgB,SAAA,EAAW,OAAO,MAAA,CAAO,SAAS,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA;AAErH,EAAA,OAAO,OAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,QAAA,CAAS,CAAC,CAAC,CAAA;AAC9C;AAMA,eAAe,eAAA,CACX,SAAA,EACA,QAAA,EACA,GAAA,EAEJ;AACI,EAAA,IAAI,CAAC,QAAA,EACL;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,IAAI,CAAC,SAAS,aAAA,EACd;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,IACA;AACI,IAAA,OAAO,MAAM,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA,KAAM,IAAA;AAAA,EACjD,CAAA,CAAA,MAEA;AACI,IAAA,SAAA,CAAU,KAAK,+DAAA,EAA4D;AAAA,MACvE,KAAA,EAAO,SAAA;AAAA,MACP,SAAS,GAAA,CAAI;AAAA,KAChB,CAAA;AAED,IAAA,OAAO,KAAA;AAAA,EACX;AACJ;AAWA,eAAe,SAAA,CACX,CAAA,EACA,MAAA,EACA,SAAA,EACA,MAAA,EAEJ;AACI,EAAA,MAAM,EAAE,OAAA,EAAS,MAAA,EAAQ,OAAA,EAAS,SAAQ,GAAI,SAAA;AAC9C,EAAA,IAAI,cAAA,GAAiB,KAAA;AACrB,EAAA,IAAI,SAAA;AAGJ,EAAA,IAAI,QAAA;AAEJ,EAAA,MAAM,OAAA,GAAU,CAAC,MAAA,KACjB;AACI,IAAA,IAAI,cAAA,EAAgB;AACpB,IAAA,cAAA,GAAiB,IAAA;AACjB,IAAA,aAAA,CAAc,SAAS,CAAA;AACvB,IAAA,cAAA,CAAe,UAAU,CAAA;AACzB,IAAA,MAAA,CAAO,QAAA,CAAS,OAAO,UAAU,CAAA;AACjC,IAAA,MAAA,CAAO,KAAA,EAAM;AACb,IAAA,QAAA,IAAW;AACX,IAAA,SAAA,CAAU,KAAK,gCAAA,EAAkC;AAAA,MAC7C,MAAA,EAAQ,CAAC,GAAG,UAAA,CAAW,OAAO,CAAA;AAAA,MAC9B;AAAA,KACH,CAAA;AAAA,EACL,CAAA;AAIA,EAAA,MAAM,SAAS,mBAAA,CAAoB,MAAA,EAAQ,MAAA,CAAO,QAAA,EAAU,CAAC,MAAA,KAC7D;AACI,IAAA,SAAA,CAAU,IAAA,CAAK,uBAAA,EAAyB,EAAE,MAAA,EAAQ,CAAC,GAAG,UAAA,CAAW,OAAO,CAAA,EAAG,MAAA,EAAQ,CAAA;AACnF,IAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,EAClB,CAAC,CAAA;AAID,EAAA,MAAM,IAAA,GAAO,gBAAgB,MAAM,CAAA;AACnC,EAAA,MAAM,iBAAA,GAAuC;AAAA,IACzC,QAAQ,MAAA,CAAO,MAAA;AAAA,IACf,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,OAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA,EAAY,kBAAkB,MAAM,CAAA;AAAA,IACpC,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,QAAQ,MAAM;AAAA,GAClB;AAEA,EAAA,MAAM,UAAA,GAA4B;AAAA,IAC9B,OAAA;AAAA,IACA,WAAW,SAAA,CAAU,SAAA;AAAA,IACrB,OAAA,EAAS,IAAI,GAAA,CAAI,OAAO,CAAA;AAAA,IACxB,MAAA,EAAQ,IAAI,GAAA,CAAI,MAAM,CAAA;AAAA,IACtB,YAAA,sBAAkB,GAAA,EAAI;AAAA,IACtB,OAAA;AAAA,IACA,SAAA,EAAW,sBAAsB,iBAAiB,CAAA;AAAA,IAClD,MAAA,EAAQ,CAAC,KAAA,EAAO,SAAA,KAChB;AACI,MAAA,IAAI,cAAA,EAAgB;AAGpB,MAAA,IAAA,CAAK,KAAK,MAAM,MAAA,CAAO,OAAA,CAAQ,EAAE,OAAO,KAAA,EAAO,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,SAAA,EAAW,CAAA,EAAG,CAAC,CAAA;AAAA,IAChG;AAAA,GACJ;AAOA,EAAA,IAAI,SAAA,CAAU,OAAA,EAAS,IAAA,CAAK,UAAU,MAAM,KAAA,EAC5C;AACI,IAAA,OAAA,CAAQ,0BAA0B,CAAA;AAElC,IAAA;AAAA,EACJ;AAGA,EAAA,KAAK,iBAAA,CAAkB,UAAA,EAAY,MAAA,EAAQ,IAAA,EAAM;AAAA,IAC7C,GAAG,iBAAA;AAAA,IACH,WAAW,SAAA,CAAU;AAAA,GACxB,CAAA;AAGD,EAAA,SAAA,GAAY,YAAY,MACxB;AACI,IAAA,IAAI,cAAA,EAAgB;AAEpB,IAAA,IAAA,CAAK,IAAA,CAAK,MAAM,MAAA,CAAO,OAAA,CAAQ;AAAA,MAC3B,KAAA,EAAO,MAAA;AAAA,MACP,IAAA,EAAM,KAAK,SAAA,CAAU,EAAE,WAAW,IAAA,CAAK,GAAA,IAAO;AAAA,KACjD,CAAC,CAAA;AAAA,EACN,CAAA,EAAG,OAAO,YAAY,CAAA;AAEtB,EAAA,MAAM,iBAAA,CAAkB,EAAE,GAAA,CAAI,GAAA,CAAI,QAAQ,MAAM,cAAA,EAAgB,CAAC,OAAA,KACjE;AACI,IAAA,QAAA,GAAW,OAAA;AAAA,EACf,CAAC,CAAA;AAGD,EAAA,OAAA,EAAQ;AACZ;AASA,eAAe,iBAAA,CACX,UAAA,EACA,MAAA,EACA,IAAA,EACA,GAAA,EAEJ;AACI,EAAA,MAAM,SAAA,GAAY,EAAE,gBAAA,EAAkB,CAAC,GAAG,UAAA,CAAW,OAAO,CAAA,EAAG,YAAA,EAAc,CAAC,GAAG,UAAA,CAAW,MAAM,CAAA,EAAE;AACpG,EAAA,MAAM,SAAA,GAAY,SAAA,CAAU,gBAAA,CAAiB,MAAA,CAAO,CAAA,SAAA,KAAa,IAAI,MAAA,CAAO,MAAA,CAAO,SAAS,CAAA,CAAE,QAAQ,CAAA;AAEtG,EAAA,IAAI,CAAC,GAAA,CAAI,OAAA,IAAW,SAAA,CAAU,WAAW,CAAA,EACzC;AACI,IAAA,gBAAA,CAAiB,QAAQ,EAAE,GAAG,WAAW,OAAA,EAAS,IAAI,CAAA;AACtD,IAAA,gBAAA,CAAiB,UAAU,CAAA;AAE3B,IAAA;AAAA,EACJ;AAEA,EAAA,IAAA,CAAK,IAAA,EAAK;AACV,EAAA,gBAAA,CAAiB,UAAU,CAAA;AAE3B,EAAA,MAAM,MAAA,GAAS,MAAM,oBAAA,CAAqB,SAAA,EAAW,IAAI,MAAA,EAAQ;AAAA,IAC7D,SAAS,GAAA,CAAI,OAAA;AAAA,IACb,WAAW,GAAA,CAAI,SAAA;AAAA,IACf,SAAS,GAAA,CAAI;AAAA,GAChB,CAAA;AACD,EAAA,IAAI,GAAA,CAAI,QAAO,EACf;AACI,IAAA;AAAA,EACJ;AAEA,EAAA,gBAAA,CAAiB,MAAA,EAAQ,EAAE,GAAG,SAAA,EAAW,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,CAAC,EAAE,SAAA,EAAU,KAAM,SAAS,GAAG,CAAA;AAC5F,EAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,EAAE,SAAA,EAAW,QAAO,KAAM,MAAA,CAAO,OAAA,CAAQ,CAAA,IAAA,KAAQ,GAAA,CAAI,UAAA,CAAW,SAAA,EAAW,IAAI,CAAC,CAAC,CAAA;AACjG,EAAA,IAAA,CAAK,OAAA,EAAQ;AACjB;AAUA,SAAS,gBAAA,CAAiB,QAAuB,SAAA,EACjD;AACI,EAAA,MAAA,CAAO,OAAA,CAAQ;AAAA,IACX,KAAA,EAAO,WAAA;AAAA,IACP,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,GAAG,WAAW,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,EAAG;AAAA,GAC/D,CAAA;AACL;AAEA,SAAS,iBAAiB,UAAA,EAC1B;AACI,EAAA,KAAA,MAAW,SAAA,IAAa,WAAW,OAAA,EACnC;AACI,IAAA,UAAA,CAAW,aAAa,GAAA,CAAI,SAAA,EAAW,UAAA,CAAW,SAAA,CAAU,SAAS,CAAC,CAAA;AAAA,EAC1E;AAEA,EAAA,SAAA,CAAU,KAAK,4BAAA,EAA8B;AAAA,IACzC,MAAA,EAAQ,CAAC,GAAG,UAAA,CAAW,OAAO,CAAA;AAAA,IAC9B,YAAA,EAAc,CAAC,GAAG,UAAA,CAAW,MAAM,CAAA;AAAA,IACnC,iBAAA,EAAmB,WAAW,YAAA,CAAa;AAAA,GAC9C,CAAA;AACL;AAgBA,eAAe,oBAAA,CACX,MAAA,EACA,MAAA,EACA,GAAA,EAEJ;AACI,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,GAAA,CAAI,OAAO,GAAA,CAAI,CAAA,SAAA,KAAa,UAAA,CAAW,SAAA,EAAW,OAAO,MAAA,CAAO,SAAS,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA;AAE/G,EAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,CAAC,MAAA,KAAoC,WAAW,IAAI,CAAA;AAC9E;AAOA,eAAe,UAAA,CAAW,SAAA,EAAmB,QAAA,EAAyB,GAAA,EACtE;AACI,EAAA,IACA;AACI,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,QAAA,CAAU,GAAG,CAAA;AAE7C,IAAA,OAAO,EAAE,SAAA,EAAW,MAAA,EAAQ,QAAA,CAAS,GAAA,CAAI,aAAW,cAAA,CAAe,SAAA,EAAW,OAAO,CAAC,CAAA,EAAE;AAAA,EAC5F,CAAA,CAAA,MAEA;AACI,IAAA,SAAA,CAAU,KAAK,0EAAA,EAAuE;AAAA,MAClF,KAAA,EAAO,SAAA;AAAA,MACP,SAAS,GAAA,CAAI;AAAA,KAChB,CAAA;AAED,IAAA,OAAO,IAAA;AAAA,EACX;AACJ;AAmBA,SAAS,gBAAgB,MAAA,EACzB;AACI,EAAA,IAAI,OAAA,GAAiC,IAAA;AAErC,EAAA,OAAO;AAAA,IACH,IAAA,EAAM,CAAC,KAAA,KACP;AACI,MAAA,IAAI,CAAC,OAAA,EACL;AACI,QAAA,KAAA,EAAM;AAAA,MACV,CAAA,MAAA,IACS,MAAA,CAAO,OAAA,EAAQ,EACxB;AACI,QAAA,OAAA,CAAQ,KAAK,KAAK,CAAA;AAAA,MACtB;AAAA,IACJ,CAAA;AAAA,IACA,MAAM,MACN;AACI,MAAA,OAAA,GAAU,EAAC;AAAA,IACf,CAAA;AAAA,IACA,SAAS,MACT;AACI,MAAA,MAAM,IAAA,GAAO,WAAW,EAAC;AACzB,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,MAAA,CAAO,aAAA,EAAc;AACrB,MAAA,IAAA,CAAK,OAAA,CAAQ,CAAA,KAAA,KAAS,KAAA,EAAO,CAAA;AAAA,IACjC;AAAA,GACJ;AACJ;AAMA,SAAS,kBAAkB,MAAA,EAC3B;AACI,EAAA,IAAI,SAAA,GAAY,CAAA;AAEhB,EAAA,OAAO,CAAC,WAAW,IAAA,KACnB;AACI,IAAA,SAAA,EAAA;AAEA,IAAA,SAAA,CAAU,MAAM,mBAAA,EAAqB;AAAA,MACjC,KAAA,EAAO,SAAA;AAAA,MACP;AAAA,KACH,CAAA;AAED,IAAA,MAAA,CAAO,OAAA,CAAQ,EAAE,EAAA,EAAI,MAAA,CAAO,SAAS,CAAA,EAAG,KAAA,EAAO,SAAA,EAAW,IAAA,EAAM,CAAA;AAAA,EACpE,CAAA;AACJ;AAOA,eAAe,iBAAA,CACX,WAAA,EACA,MAAA,EACA,aAAA,EAEJ;AACI,EAAA,IAAI,WAAA,CAAY,OAAA,IAAW,MAAA,EAAO,EAClC;AACI,IAAA;AAAA,EACJ;AAEA,EAAA,MAAM,IAAI,OAAA,CAAc,CAAC,OAAA,KACzB;AACI,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,EAAQ;AAC9B,IAAA,WAAA,CAAY,iBAAiB,OAAA,EAAS,OAAA,EAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAE7D,IAAA,aAAA,CAAc,MACd;AACI,MAAA,WAAA,CAAY,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAChD,MAAA,OAAA,EAAQ;AAAA,IACZ,CAAC,CAAA;AAAA,EACL,CAAC,CAAA;AACL;AA0BA,SAAS,sBAAsB,GAAA,EAC/B;AACI,EAAA,OAAO,CAAC,SAAA,KACR;AACI,IAAA,MAAM,QAAA,GAAW,GAAA,CAAI,MAAA,CAAO,MAAA,CAAO,SAAS,CAAA;AAC5C,IAAA,IAAI,MAAA,GAAS,IAAA;AAEb,IAAA,MAAM,WAAA,GAAc,QAAA,CAAS,SAAA,CAAU,CAAC,OAAA,KACxC;AACI,MAAA,IAAI,CAAC,MAAA,IAAU,GAAA,CAAI,MAAA,EAAO,IAAK,CAAC,kBAAA,CAAmB,SAAA,EAAW,QAAA,EAAU,OAAA,EAAS,GAAG,CAAA,EACpF;AACI,QAAA;AAAA,MACJ;AAEA,MAAA,MAAM,IAAA,GAAO,cAAA,CAAe,SAAA,EAAW,OAAO,CAAA;AAC9C,MAAA,GAAA,CAAI,KAAK,MAAM,GAAA,CAAI,UAAA,CAAW,SAAA,EAAW,IAAI,CAAC,CAAA;AAAA,IAClD,CAAC,CAAA;AAED,IAAA,OAAO,MACP;AACI,MAAA,MAAA,GAAS,KAAA;AACT,MAAA,WAAA,EAAY;AAAA,IAChB,CAAA;AAAA,EACJ,CAAA;AACJ;AAMA,SAAS,kBAAA,CACL,SAAA,EACA,QAAA,EACA,OAAA,EACA,GAAA,EAEJ;AACI,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,UAAA,EAAY,MAAA,GAAS,SAAS,CAAA;AACvD,EAAA,IAAI,GAAA,CAAI,WAAW,YAAA,IAAgB,CAAC,aAAa,GAAA,CAAI,OAAA,EAAS,OAAO,CAAA,EACrE;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,IAAI,CAAC,SAAS,OAAA,EACd;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,IACA;AACI,IAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,OAAA,EAAS,EAAE,OAAA,EAAS,GAAA,CAAI,OAAA,EAAS,OAAA,EAAS,GAAA,CAAI,OAAA,EAAS,CAAA,KAAM,IAAA;AAAA,EACzF,CAAA,CAAA,MAEA;AACI,IAAA,SAAA,CAAU,IAAA,CAAK,+CAA0C,EAAE,KAAA,EAAO,WAAW,OAAA,EAAS,GAAA,CAAI,SAAS,CAAA;AAEnG,IAAA,OAAO,KAAA;AAAA,EACX;AACJ;AAWA,eAAe,iBAAA,CACX,GACA,YAAA,EAEJ;AACI,EAAA,IAAI,CAAC,YAAA,EACL;AACI,IAAA,OAAO,MAAA;AAAA,EACX;AAEA,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AACjC,EAAA,IAAI,CAAC,KAAA,EACL;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,OAAO,MAAM,YAAA,CAAa,MAAA,CAAO,KAAK,CAAA;AAC1C;AAKA,SAAS,qBAAqB,CAAA,EAC9B;AACI,EAAA,MAAM,WAAA,GAAc,CAAA,CAAE,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA;AACxC,EAAA,IAAI,CAAC,WAAA,EACL;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAGA,EAAA,OAAO,CAAC,GAAG,IAAI,GAAA,CAAI,YAAY,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,IAAA,EAAM,CAAC,CAAC,CAAA;AACjE;AAMA,eAAe,eAAA,CACX,OAAA,EACA,eAAA,EACA,UAAA,EAEJ;AACI,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,UAAA,EAAY,SAAA,EAC7B;AACI,IAAA,OAAO,eAAA;AAAA,EACX;AAEA,EAAA,MAAM,OAAA,GAAU,MAAM,UAAA,CAAW,SAAA,CAAU,SAAS,eAAe,CAAA;AAEnE,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EACvB;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,OAAO,OAAA;AACX;ACv1BA,SAAS,UAAU,KAAA,EACnB;AACI,EAAA,OAAO,WAAW,QAAQ,CAAA,CAAE,OAAO,KAAK,CAAA,CAAE,OAAO,KAAK,CAAA;AAC1D;AAqEA,IAAM,qBAAN,MACA;AAAA,EACY,MAAA,uBAAa,GAAA,EAAsB;AAAA,EAE3C,MAAM,GAAA,CAAI,KAAA,EAAe,IAAA,EACzB;AACI,IAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,SAAA,CAAU,KAAK,GAAG,IAAI,CAAA;AAAA,EAC1C;AAAA,EAEA,MAAM,QAAQ,KAAA,EACd;AACI,IAAA,MAAM,GAAA,GAAM,UAAU,KAAK,CAAA;AAC3B,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,IAAA,EACL;AACI,MAAA,OAAO,IAAA;AAAA,IACX;AAEA,IAAA,IAAA,CAAK,MAAA,CAAO,OAAO,GAAG,CAAA;AAEtB,IAAA,OAAO,IAAA;AAAA,EACX;AAAA,EAEA,MAAM,OAAA,GACN;AACI,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAErB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,CAAA,IAAK,KAAK,MAAA,EAC/B;AACI,MAAA,IAAI,IAAA,CAAK,aAAa,GAAA,EACtB;AACI,QAAA,IAAA,CAAK,MAAA,CAAO,OAAO,GAAG,CAAA;AAAA,MAC1B;AAAA,IACJ;AAAA,EACJ;AACJ,CAAA;AAuBO,IAAM,kBAAN,MACP;AAAA,EAGI,YAAoB,KAAA,EACpB;AADoB,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA,EACnB;AAAA,EAHO,MAAA,GAAS,YAAA;AAAA,EAKjB,MAAM,GAAA,CAAI,KAAA,EAAe,IAAA,EACzB;AACI,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,IAAA,CAAA,CAAM,IAAA,CAAK,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,IAAK,GAAI,CAAC,CAAA;AAC9E,IAAA,MAAM,KAAK,KAAA,CAAM,GAAA;AAAA,MACb,IAAA,CAAK,MAAA,GAAS,SAAA,CAAU,KAAK,CAAA;AAAA,MAC7B,IAAA,CAAK,UAAU,IAAI,CAAA;AAAA,MACnB,IAAA;AAAA,MACA;AAAA,KACJ;AAAA,EACJ;AAAA,EAEA,MAAM,QAAQ,KAAA,EACd;AACI,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,MAAA,GAAS,SAAA,CAAU,KAAK,CAAA;AAGzC,IAAA,IAAI,GAAA,GAAqB,IAAA;AAEzB,IAAA,IAAI,IAAA,CAAK,MAAM,MAAA,EACf;AACI,MAAA,GAAA,GAAM,MAAM,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA;AAAA,IACrC,CAAA,MAEA;AACI,MAAA,GAAA,GAAM,MAAM,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC9B,MAAA,IAAI,GAAA,EACJ;AACI,QAAA,MAAM,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAAA,MAC5B;AAAA,IACJ;AAEA,IAAA,IAAI,CAAC,GAAA,EACL;AACI,MAAA,OAAO,IAAA;AAAA,IACX;AAEA,IAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACzB;AAAA,EAEA,MAAM,OAAA,GACN;AAAA,EAEA;AACJ;AAMO,IAAM,kBAAN,MACP;AAAA,EACY,KAAA;AAAA,EACA,GAAA;AAAA,EACA,YAAA,GAAsD,IAAA;AAAA,EAE9D,YAAY,MAAA,EACZ;AACI,IAAA,IAAA,CAAK,GAAA,GAAM,QAAQ,GAAA,IAAO,GAAA;AAC1B,IAAA,IAAA,CAAK,KAAA,GAAQ,MAAA,EAAQ,KAAA,IAAS,IAAI,kBAAA,EAAmB;AAErD,IAAA,MAAM,eAAA,GAAkB,QAAQ,eAAA,IAAmB,GAAA;AACnD,IAAA,IAAA,CAAK,YAAA,GAAe,YAAY,MAAM,KAAK,KAAK,KAAA,CAAM,OAAA,IAAW,eAAe,CAAA;AAChF,IAAA,IAAA,CAAK,aAAa,KAAA,EAAM;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,KAAA,GACJ;AACI,IAAA,OAAO,IAAA,CAAK,GAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,OAAA,EACZ;AACI,IAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,EAAE,CAAA,CAAE,SAAS,KAAK,CAAA;AAG5C,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,KAAA,EAAO;AAAA,MACxB,OAAA;AAAA,MACA,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK;AAAA,KAChC,CAAA;AAED,IAAA,OAAO,KAAA;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,KAAA,EACb;AACI,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,KAAA,CAAM,QAAQ,KAAK,CAAA;AAE3C,IAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,KAAI,EACxC;AACI,MAAA,OAAO,IAAA;AAAA,IACX;AAEA,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,GACA;AACI,IAAA,IAAI,KAAK,YAAA,EACT;AACI,MAAA,aAAA,CAAc,KAAK,YAAY,CAAA;AAC/B,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AAAA,IACxB;AAAA,EACJ;AACJ","file":"index.js","sourcesContent":["/**\n * Bounded SSE outbound writer\n *\n * Serializes writes to one SSE stream through a single drain loop that `await`s\n * each `writeSSE` — so a slow client applies real backpressure instead of letting\n * frames pile up unbounded in memory (OOM under a fast producer + slow consumer).\n *\n * The queue is per-connection, so a slow stream never blocks other connections.\n * When the queue exceeds `maxQueue`, the connection is closed (via `onClose`)\n * rather than silently dropping frames — dropping a chunk would corrupt an\n * ordered token/chat stream; the client reconnects and re-fetches instead.\n */\n\n/** A frame to write to the SSE stream (subset of Hono's SSEMessage). */\nexport interface SSEFrame\n{\n data: string;\n event?: string;\n id?: string;\n}\n\n/** Minimal stream surface this writer needs (Hono's SSEStreamingApi satisfies it). */\nexport interface SSEWritable\n{\n writeSSE(message: SSEFrame): Promise<void>;\n}\n\nexport interface BoundedWriter\n{\n /** Queue a frame for delivery; closes the connection if the queue overflows. */\n enqueue(frame: SSEFrame): void;\n\n /**\n * Count one frame held outside the queue against `maxQueue`, until\n * `clearReserved()`; closes the connection as a slow consumer when it does\n * not fit. `false` when it did not fit or the writer is closed.\n */\n reserve(): boolean;\n\n /** Stop counting the reserved frames — their writer is about to enqueue them. */\n clearReserved(): void;\n\n /** Stop draining and drop any queued frames (called on connection cleanup). */\n close(): void;\n\n /** Current queue depth (for tests/diagnostics). */\n readonly queued: number;\n}\n\n/**\n * Create a bounded, backpressure-aware writer for one SSE stream.\n *\n * @param stream the SSE stream (`writeSSE` returns a promise that resolves when\n * the socket has drained — that is what gives us backpressure)\n * @param maxQueue max frames buffered before the connection is closed\n * @param onClose called once when the writer closes the connection (overflow or\n * a write error); the caller cleans up and logs\n */\nexport function createBoundedWriter(\n stream: SSEWritable,\n maxQueue: number,\n onClose: (reason: string) => void,\n): BoundedWriter\n{\n const pending: SSEFrame[] = [];\n let flushing = false;\n let reserved = 0;\n let closed = false;\n\n // Slow consumer: the drain loop can't keep up, so the queue — with the frames\n // reserved for it — grew past the bound. Close rather than buffer unboundedly\n // or drop silently.\n const overflows = (): boolean =>\n {\n if (pending.length + reserved <= maxQueue)\n {\n return false;\n }\n\n onClose('outbound queue overflow (slow consumer)');\n\n return true;\n };\n\n const flush = async (): Promise<void> =>\n {\n if (flushing || closed)\n {\n return;\n }\n\n flushing = true;\n\n while (pending.length > 0 && !closed)\n {\n const frame = pending.shift() as SSEFrame;\n try\n {\n await stream.writeSSE(frame);\n }\n catch (err)\n {\n flushing = false;\n onClose(err instanceof Error ? err.message : String(err));\n\n return;\n }\n }\n\n flushing = false;\n };\n\n return {\n enqueue(frame: SSEFrame): void\n {\n if (closed)\n {\n return;\n }\n\n pending.push(frame);\n\n if (!overflows())\n {\n void flush();\n }\n },\n\n reserve(): boolean\n {\n if (closed)\n {\n return false;\n }\n\n reserved++;\n\n return !overflows();\n },\n\n clearReserved(): void\n {\n reserved = 0;\n },\n\n close(): void\n {\n closed = true;\n pending.length = 0;\n },\n\n get queued(): number\n {\n return pending.length;\n },\n };\n}\n","/**\n * SSE connection registry\n *\n * What one `createSSEHandler` knows about its open connections, so that\n * `EventDef.grant` / `EventDef.revoke` can change a connection after it opened.\n * One registry per handler: two handlers in one process (tests) never share one.\n *\n * Neither operation consults `canSubscribe` or `authorize`: they are an\n * operator's statement about an open connection. Permission lives in app data\n * and is re-read at the next connect. A `match` narrows either to the\n * connections whose connect query carries its fields.\n *\n * A connect still reading app data (`authorize`, `resolvePrincipal`,\n * `canSubscribe`) is a pending admission: a `grant` / `revoke` that lands then\n * is recorded on it and applied, without a frame, when the connection opens —\n * so a call the app made is never lost to that window.\n */\n\nimport { logger } from '@spfn/core/logger';\nimport type { SubscribeRequest, SubscriptionMatch } from '../types';\n\nconst registryLogger = logger.child('@spfn/core:sse');\n\n/** Stream-owned frame: an event was activated on the open connection. */\nexport const SUBSCRIPTION_GRANTED = 'subscription.granted';\n\n/** Stream-owned frame: an event was deactivated on the open connection. */\nexport const SUBSCRIPTION_REVOKED = 'subscription.revoked';\n\nexport type SubscriptionFrame = typeof SUBSCRIPTION_GRANTED | typeof SUBSCRIPTION_REVOKED;\n\n/**\n * One open SSE connection. `allowed` and `denied` partition `requested`\n * (plus anything `authorize` added); only `allowed` events hold a live\n * subscription in `unsubscribes`.\n */\nexport interface SSEConnection\n{\n /** `undefined` when the stream runs without auth — such a connection is never registered. */\n readonly subject: string | undefined;\n readonly requested: readonly string[];\n readonly allowed: Set<string>;\n readonly denied: Set<string>;\n readonly unsubscribes: Map<string, () => void>;\n readonly request: SubscribeRequest;\n\n /** The per-event subscribe the connection opened with — `grant` reuses it, never a copy. */\n readonly subscribe: (eventName: string) => () => void;\n\n /** Enqueue a protocol frame about one event (dropped once the connection is dead). */\n readonly notify: (frame: SubscriptionFrame, eventName: string) => void;\n}\n\nexport type ConnectionRegistry = ReturnType<typeof createConnectionRegistry>;\n\ntype SubscriptionChange = 'grant' | 'revoke';\n\n/**\n * A connect of one subject between its first app-data read and its open.\n * The first of `open` / `cancel` ends it; both are synchronous. A client abort\n * cancels it at once — even while an app predicate never settles.\n */\nexport interface PendingAdmission\n{\n /**\n * Apply the recorded changes to `connection`, then swap this entry for the\n * open connection. `false`, with nothing applied or registered, once cancelled.\n */\n readonly open: (connection: SSEConnection) => boolean;\n\n /** Drop this entry (refused, aborted, or thrown); idempotent. */\n readonly cancel: () => void;\n}\n\n/** One `grant` / `revoke` recorded on a pending admission. */\ninterface RecordedChange\n{\n kind: SubscriptionChange;\n eventName: string;\n match?: SubscriptionMatch;\n}\n\n/** The changes recorded on one pending admission, in call order (the last one wins). */\ntype RecordedChanges = RecordedChange[];\n\n/**\n * Create the registry of one SSE handler, keyed by subject → open connections\n * (a subject with two tabs has two).\n */\nexport function createConnectionRegistry()\n{\n const bySubject = new Map<string, Set<SSEConnection>>();\n const pendingBySubject = new Map<string, Set<RecordedChanges>>();\n\n /**\n * Register a pending admission of `subject`; `signal` is the connect's\n * abort signal, listened to until the admission ends either way.\n */\n const beginAdmission = (subject: string, signal: AbortSignal): PendingAdmission =>\n {\n const changes: RecordedChanges = [];\n addToSet(pendingBySubject, subject, changes);\n const end = endOnce(signal, () => removeFromSet(pendingBySubject, subject, changes));\n\n return {\n cancel: end,\n open: (connection: SSEConnection) =>\n {\n if (!end())\n {\n return false;\n }\n\n applyRecorded(connection, changes);\n addToSet(bySubject, subject, connection);\n\n return true;\n },\n };\n };\n\n const change = (kind: SubscriptionChange, subject: string, eventName: string, match?: SubscriptionMatch): void =>\n {\n const pending = pendingBySubject.get(subject);\n const connections = bySubject.get(subject);\n pending?.forEach(changes => changes.push({ kind, eventName, match }));\n connections?.forEach(connection =>\n {\n if (matchesRequest(match, connection.request))\n {\n (kind === 'grant' ? grantOn : revokeOn)(connection, eventName);\n }\n });\n\n if (!pending && !connections)\n {\n registryLogger.debug('SSE subscription change for a subject with no open connection ignored', {\n event: eventName,\n subject,\n });\n }\n };\n\n return {\n beginAdmission,\n\n remove: (connection: SSEConnection): void =>\n {\n if (connection.subject)\n {\n removeFromSet(bySubject, connection.subject, connection);\n }\n },\n\n /** Open connections only — a pending admission is not counted. */\n count: (subject: string): number => bySubject.get(subject)?.size ?? 0,\n\n pendingCount: (subject: string): number => pendingBySubject.get(subject)?.size ?? 0,\n\n grant: (subject: string, eventName: string, match?: SubscriptionMatch): void =>\n change('grant', subject, eventName, match),\n\n revoke: (subject: string, eventName: string, match?: SubscriptionMatch): void =>\n change('revoke', subject, eventName, match),\n };\n}\n\n/**\n * The end of one pending admission: `remove` runs on the first call only,\n * which returns `true`; later calls return `false`. An abort of `signal` is a\n * first call; the listener is dropped on every end, so it never outlives the\n * admission (an already aborted signal ends it at once).\n */\nfunction endOnce(signal: AbortSignal, remove: () => void): () => boolean\n{\n let ended = false;\n const end = (): boolean =>\n {\n if (ended)\n {\n return false;\n }\n\n ended = true;\n signal.removeEventListener('abort', end);\n remove();\n\n return true;\n };\n\n signal.addEventListener('abort', end, { once: true });\n if (signal.aborted)\n {\n end();\n }\n\n return end;\n}\n\nfunction addToSet<T>(map: Map<string, Set<T>>, key: string, value: T): void\n{\n const set = map.get(key) ?? new Set<T>();\n set.add(value);\n map.set(key, set);\n}\n\n/** Remove `value` and drop the key once its set is empty, so neither map grows on reconnects. */\nfunction removeFromSet<T>(map: Map<string, Set<T>>, key: string, value: T): void\n{\n const set = map.get(key);\n set?.delete(value);\n\n if (set?.size === 0)\n {\n map.delete(key);\n }\n}\n\n/**\n * Whether `match` reaches a connection: every named field equals the connect\n * query's value, string to string. A field the connection never declared\n * never matches, not even an empty string. No `match` reaches every one.\n */\nexport function matchesRequest(match: SubscriptionMatch | undefined, request: SubscribeRequest): boolean\n{\n return match === undefined || Object.entries(match.query).every(([field, value]) =>\n Object.hasOwn(request.query, field) && request.query[field] === value);\n}\n\n/**\n * Apply what was recorded during admission to a connection that has not\n * opened yet: move the event between `allowed` and `denied`, no frame. An\n * event the connection never requested is in neither set and stays out; a\n * change whose `match` the connection's query does not meet is skipped.\n */\nfunction applyRecorded(connection: SSEConnection, changes: RecordedChanges): void\n{\n changes.forEach(({ kind, eventName, match }) =>\n {\n if (!matchesRequest(match, connection.request))\n {\n return;\n }\n\n const [from, to] = kind === 'grant'\n ? [connection.denied, connection.allowed]\n : [connection.allowed, connection.denied];\n\n if (from.delete(eventName))\n {\n to.add(eventName);\n }\n });\n}\n\n/**\n * Activate a denied event on one connection; allowed or never requested → no change.\n */\nfunction grantOn(connection: SSEConnection, eventName: string): void\n{\n if (!connection.denied.has(eventName))\n {\n return;\n }\n\n connection.unsubscribes.set(eventName, connection.subscribe(eventName));\n connection.denied.delete(eventName);\n connection.allowed.add(eventName);\n connection.notify(SUBSCRIPTION_GRANTED, eventName);\n}\n\n/**\n * Deactivate an allowed event on one connection; denied or never requested → no change.\n *\n * A frame the event already enqueued before this call may still be written —\n * the queue is not rewritten — but no frame after the unsubscribe is.\n */\nfunction revokeOn(connection: SSEConnection, eventName: string): void\n{\n if (!connection.allowed.has(eventName))\n {\n return;\n }\n\n connection.unsubscribes.get(eventName)?.();\n connection.unsubscribes.delete(eventName);\n connection.allowed.delete(eventName);\n connection.denied.add(eventName);\n connection.notify(SUBSCRIPTION_REVOKED, eventName);\n}\n\n/**\n * Drop every live subscription of a closing connection. A revoked event was\n * already removed from `unsubscribes`, so nothing is unsubscribed twice.\n */\nexport function unsubscribeAll(connection: SSEConnection): void\n{\n connection.unsubscribes.forEach(unsubscribe => unsubscribe());\n connection.unsubscribes.clear();\n}\n","/**\n * SSE Handler for Hono\n *\n * Creates SSE stream endpoint for event subscription\n *\n * @example\n * ```typescript\n * import { Hono } from 'hono';\n * import { createSSEHandler } from '@spfn/core/event/sse';\n * import { eventRouter } from './events';\n *\n * const app = new Hono();\n *\n * // GET /events/stream?events=userCreated,orderPlaced\n * app.get('/events/stream', createSSEHandler(eventRouter));\n * ```\n */\n\nimport type { Context } from 'hono';\nimport { streamSSE } from 'hono/streaming';\nimport type { SSEStreamingApi } from 'hono/streaming';\nimport { logger } from '@spfn/core/logger';\nimport type { EventRouterDef } from '../router';\nimport type { EventDef, SubscribeContext, SubscribeRequest, SubscriptionMatch } from '../types';\nimport type { SSEHandlerConfig, SSEHandlerAuthConfig } from './types';\nimport type { SSETokenManager } from './token-manager';\nimport { createBoundedWriter } from './bounded-writer';\nimport type { BoundedWriter } from './bounded-writer';\nimport { createConnectionRegistry, unsubscribeAll } from './connection-registry';\nimport type { ConnectionRegistry, PendingAdmission, SSEConnection } from './connection-registry';\n\nconst sseLogger = logger.child('@spfn/core:sse');\n\n/**\n * Serialize a fan-out frame once per (event, payload).\n *\n * One `emit` delivers the same payload object to every subscribed connection, so\n * without memoization each connection re-`JSON.stringify`s the identical payload\n * — O(N) for N subscribers on a hot broadcast. Keyed by the payload object\n * (WeakMap → auto-GC after the emit); primitives are cheap to stringify directly.\n */\nconst frameCache = new WeakMap<object, Map<string, string>>();\n\nexport function serializeFrame(eventName: string, payload: unknown): string\n{\n if (payload === null || typeof payload !== 'object')\n {\n return JSON.stringify({ event: eventName, data: payload });\n }\n\n let byEvent = frameCache.get(payload);\n if (!byEvent)\n {\n byEvent = new Map<string, string>();\n frameCache.set(payload, byEvent);\n }\n\n let serialized = byEvent.get(eventName);\n if (serialized === undefined)\n {\n serialized = JSON.stringify({ event: eventName, data: payload });\n byEvent.set(eventName, serialized);\n }\n\n return serialized;\n}\n\n/** Default outbound queue cap per connection before a slow consumer is dropped. */\nconst DEFAULT_MAX_QUEUE = 1000;\n\n// Extend Hono context with SSE subject\ndeclare module 'hono'\n{\n interface ContextVariableMap\n {\n sseSubject?: string;\n }\n}\n\n/**\n * Create SSE handler for Hono\n *\n * Query parameters:\n * - events: Comma-separated list of event names to subscribe\n * - token: One-time auth token (when auth is enabled)\n *\n * Who receives what, in order: the global `authorize` (once, at connect) →\n * each requested event's `.canSubscribe()` (once per event, at connect) → the\n * global `filter` map and the event's `.filter()` (sync, per frame). An event\n * denied at connect stays in `deniedEvents`, so `EventDef.grant` can activate\n * it later; `EventDef.revoke` deactivates an allowed one. The handler\n * registers that control on every event of the router.\n *\n * @example\n * ```typescript\n * app.get('/events/stream', createSSEHandler(eventRouter, {\n * pingInterval: 30000,\n * }));\n * ```\n */\nexport function createSSEHandler<TRouter extends EventRouterDef<any>>(\n router: TRouter,\n config: SSEHandlerConfig = {},\n tokenManager?: SSETokenManager,\n)\n{\n const {\n // 10s — keep-alive가 중간 프록시(GCP LB/nginx)의 idle 타임아웃(흔히 ~15-30s)보다 자주 와야\n // 첫 ping 전 침묵 구간에 연결이 끊기지 않는다. 30s 기본은 idle 타임아웃보다 길어 SSE가\n // 데이터 없는 동안 끊기던 문제가 있었다.\n pingInterval = 10000,\n auth: authConfig,\n maxQueue = DEFAULT_MAX_QUEUE,\n } = config;\n\n const registry = createConnectionRegistry();\n registerSubscriptionControls(router, registry);\n\n const streamConfig: StreamConfig = { router, authConfig, registry, pingInterval, maxQueue };\n\n const handler = async (c: Context) =>\n {\n const admission = await admitConnection(c, streamConfig, tokenManager);\n if (admission instanceof Response)\n {\n return admission;\n }\n\n if (c.req.raw.signal.aborted)\n {\n // The client left during admission: nobody reads this response.\n // Its abort listener already cancelled `pending`; cancel is idempotent.\n admission.pending?.cancel();\n\n return new Response(null, { status: 499 });\n }\n\n // ── 6. SSE Stream ──\n c.header('X-Accel-Buffering', 'no');\n\n return streamSSE(c, stream => runStream(c, stream, admission, streamConfig), async (err: Error) =>\n {\n sseLogger.error('SSE stream error', {\n error: err.message,\n });\n });\n };\n\n return Object.assign(handler, {\n /** @internal Open registered connections of a subject (tests). */\n _openConnections: (subject: string): number => registry.count(subject),\n\n /** @internal Connects of a subject still in admission (tests). */\n _pendingAdmissions: (subject: string): number => registry.pendingCount(subject),\n });\n}\n\n// ============================================================================\n// Connect\n// ============================================================================\n\n/** What a request that passed every connect check opens the stream with. */\ninterface Admission\n{\n subject: string | undefined;\n requested: string[];\n allowed: string[];\n denied: string[];\n request: SubscribeRequest;\n\n /** What `resolvePrincipal` returned, for the initial pass; never logged. */\n principal: unknown;\n\n /** Registered before the first app-data read; `runStream` opens it. Absent without a subject. */\n pending?: PendingAdmission;\n}\n\n/** What every stream of one handler shares. */\ninterface StreamConfig\n{\n router: EventRouterDef<any>;\n authConfig: SSEHandlerAuthConfig | undefined;\n registry: ConnectionRegistry;\n pingInterval: number;\n maxQueue: number;\n}\n\n/**\n * Let `EventDef.grant` / `revoke` reach this handler's connections, once per\n * router key (an event under two keys gets two controls, each naming its key).\n */\nfunction registerSubscriptionControls(router: EventRouterDef<any>, registry: ConnectionRegistry): void\n{\n for (const eventName of router.eventNames as string[])\n {\n router.events[eventName]._registerSubscriptionControl({\n grant: (subject: string, match?: SubscriptionMatch) => registry.grant(subject, eventName, match),\n revoke: (subject: string, match?: SubscriptionMatch) => registry.revoke(subject, eventName, match),\n });\n }\n}\n\n/**\n * Run the connect checks; a `Response` is the refusal to return.\n */\nasync function admitConnection(\n c: Context,\n config: StreamConfig,\n tokenManager: SSETokenManager | undefined,\n): Promise<Admission | Response>\n{\n const { router, authConfig, registry } = config;\n\n // ── 1. Token Authentication ──\n const subject = await authenticateToken(c, tokenManager);\n if (subject === false)\n {\n return c.json({ error: 'Missing token parameter' }, 401);\n }\n if (subject === null)\n {\n return c.json({ error: 'Invalid or expired token' }, 401);\n }\n if (subject)\n {\n c.set('sseSubject', subject);\n }\n\n // ── 2-3. Requested events, validated against the router ──\n const requested = parseRequestedEvents(c);\n if (!requested)\n {\n return c.json({ error: 'Missing events parameter' }, 400);\n }\n\n const validEventNames = router.eventNames as string[];\n const invalidEvents = requested.filter(e => !validEventNames.includes(e));\n if (invalidEvents.length > 0)\n {\n return c.json({\n error: 'Invalid event names',\n invalidEvents,\n validEvents: validEventNames,\n }, 400);\n }\n\n return whilePending(\n subject ? registry.beginAdmission(subject, c.req.raw.signal) : undefined,\n () => authorizeConnection(c, router, authConfig, subject, requested),\n );\n}\n\n/**\n * Run the app-data checks with the admission already registered — `pending`\n * is written synchronously, before the first `await`, so a `grant` / `revoke`\n * issued meanwhile is recorded on it. A refusal or a throw unregisters it; a\n * client abort already did, the moment it happened; an admitted connection\n * carries it to `runStream`.\n */\nasync function whilePending(\n pending: PendingAdmission | undefined,\n check: () => Promise<Admission | Response>,\n): Promise<Admission | Response>\n{\n let outcome: Admission | Response | undefined;\n\n try\n {\n outcome = await check();\n\n return outcome instanceof Response ? outcome : { ...outcome, pending };\n }\n finally\n {\n if (!outcome || outcome instanceof Response)\n {\n pending?.cancel();\n }\n }\n}\n\n/**\n * Steps 4-5: the global `authorize`, then the principal and each event's\n * `canSubscribe`. Only an empty `authorize` result or a failed principal\n * refuses; a `canSubscribe` denial only lands the event in `denied`.\n */\nasync function authorizeConnection(\n c: Context,\n router: EventRouterDef<any>,\n authConfig: SSEHandlerAuthConfig | undefined,\n subject: string | undefined,\n requested: string[],\n): Promise<Admission | Response>\n{\n // ── 4. Subscription Authorization ──\n const authorized = await authorizeEvents(subject, requested, authConfig);\n if (authorized === null)\n {\n return c.json({ error: 'Not authorized for any requested events' }, 403);\n }\n\n // ── 5. Principal + per-event canSubscribe ──\n const request: SubscribeRequest = { query: c.req.query(), headers: c.req.header() };\n const resolved = await resolveConnectionPrincipal(subject, request, authConfig);\n if (!resolved)\n {\n return c.json({ error: 'Principal could not be resolved' }, 403);\n }\n\n const allowed = subject\n ? await checkCanSubscribe(router, authorized, { subject, principal: resolved.principal, request })\n : authorized;\n const denied = requested.filter(e => !allowed.includes(e));\n\n sseLogger.debug('SSE connection requested', {\n events: allowed,\n deniedEvents: denied,\n subject: subject || undefined,\n clientIp: c.req.header('x-forwarded-for') || c.req.header('x-real-ip'),\n });\n\n return { subject, requested, allowed, denied, request, principal: resolved.principal };\n}\n\n/**\n * Resolve the principal once per connection; `null` refuses the connection.\n * The warning names the subject only — never the thrown error's text, which\n * may carry what the resolver read.\n */\nasync function resolveConnectionPrincipal(\n subject: string | undefined,\n request: SubscribeRequest,\n authConfig: SSEHandlerAuthConfig | undefined,\n): Promise<{ principal: unknown } | null>\n{\n if (!subject || !authConfig?.resolvePrincipal)\n {\n return { principal: undefined };\n }\n\n try\n {\n return { principal: await authConfig.resolvePrincipal(subject, request) };\n }\n catch\n {\n sseLogger.warn('SSE principal could not be resolved — connection refused', { subject });\n\n return null;\n }\n}\n\n/**\n * Ask each authorized event's `canSubscribe`, in parallel; returns the events\n * it allows. An event without a predicate is allowed.\n */\nasync function checkCanSubscribe(\n router: EventRouterDef<any>,\n events: string[],\n ctx: SubscribeContext,\n): Promise<string[]>\n{\n const verdicts = await Promise.all(events.map(eventName => askCanSubscribe(eventName, router.events[eventName], ctx)));\n\n return events.filter((_, i) => verdicts[i]);\n}\n\n/**\n * `true` → allowed; `false` or a throw → denied. A throw is warned with the\n * event name and never fails the connection.\n */\nasync function askCanSubscribe(\n eventName: string,\n eventDef: EventDef<any> | undefined,\n ctx: SubscribeContext,\n): Promise<boolean>\n{\n if (!eventDef)\n {\n return false;\n }\n\n if (!eventDef._canSubscribe)\n {\n return true;\n }\n\n try\n {\n return await eventDef._canSubscribe(ctx) === true;\n }\n catch\n {\n sseLogger.warn('SSE canSubscribe threw — event denied on this connection', {\n event: eventName,\n subject: ctx.subject,\n });\n\n return false;\n }\n}\n\n// ============================================================================\n// Stream\n// ============================================================================\n\n/**\n * The life of one open connection: connected frame, subscriptions, initial\n * frames, registry entry, keep-alive, and teardown on disconnect or a\n * server-side close.\n */\nasync function runStream(\n c: Context,\n stream: SSEStreamingApi,\n admission: Admission,\n config: StreamConfig,\n): Promise<void>\n{\n const { allowed, denied, subject, request } = admission;\n let connectionDead = false;\n let pingTimer: ReturnType<typeof setInterval> | undefined;\n // Resolves the disconnect wait below when the connection is closed\n // server-side (overflow/error), so teardown doesn't wait for the loop.\n let onClosed: (() => void) | undefined;\n\n const cleanup = (reason?: string) =>\n {\n if (connectionDead) return;\n connectionDead = true;\n clearInterval(pingTimer);\n unsubscribeAll(connection);\n config.registry.remove(connection);\n writer.close();\n onClosed?.();\n sseLogger.info('SSE dead connection cleaned up', {\n events: [...connection.allowed],\n reason,\n });\n };\n\n // All writes go through one bounded, backpressure-aware drain loop so a\n // slow client can't pile frames up in memory (it's closed on overflow).\n const writer = createBoundedWriter(stream, config.maxQueue, (reason) =>\n {\n sseLogger.warn('SSE connection closed', { events: [...connection.allowed], reason });\n cleanup(reason);\n });\n\n // Every write after `connected` goes through `hold`, so the initial pass can\n // put `connected` and its frames ahead of whatever arrives while it runs.\n const hold = createFrameHold(writer);\n const subscriberContext: SubscriberContext = {\n router: config.router,\n authConfig: config.authConfig,\n subject,\n request,\n writeFrame: createFrameWriter(writer),\n send: hold.send,\n isDead: () => connectionDead,\n };\n\n const connection: SSEConnection = {\n subject,\n requested: admission.requested,\n allowed: new Set(allowed),\n denied: new Set(denied),\n unsubscribes: new Map(),\n request,\n subscribe: createEventSubscriber(subscriberContext),\n notify: (frame, eventName) =>\n {\n if (connectionDead) return;\n\n // A small literal — not routed through serializeFrame's per-payload cache.\n hold.send(() => writer.enqueue({ event: frame, data: JSON.stringify({ event: eventName }) }));\n },\n };\n\n // Synchronously before the `connected` frame: the changes recorded during\n // admission land in `allowed` / `denied` first, and the entry turns open.\n // A cancelled admission (the client left) opens nothing and sends no frame.\n // Defensive: unreachable today, since `streamSSE` runs this synchronously up\n // to here; the handler's `signal.aborted` → 499 check is the real path.\n if (admission.pending?.open(connection) === false)\n {\n cleanup('aborted during admission');\n\n return;\n }\n\n // Not awaited: a producer that hangs holds the frames, never the disconnect wait.\n void openSubscriptions(connection, writer, hold, {\n ...subscriberContext,\n principal: admission.principal,\n });\n\n // Keep-alive ping\n pingTimer = setInterval(() =>\n {\n if (connectionDead) return;\n\n hold.send(() => writer.enqueue({\n event: 'ping',\n data: JSON.stringify({ timestamp: Date.now() }),\n }));\n }, config.pingInterval);\n\n await waitForDisconnect(c.req.raw.signal, () => connectionDead, (resolve) =>\n {\n onClosed = resolve;\n });\n\n // Cleanup (normal disconnect path; idempotent if already closed)\n cleanup();\n}\n\n/**\n * Send the `connected` frame (first in the queue) and subscribe every allowed\n * event. For a connection with a subject whose allowed events declare\n * `.initial()`, `connected` waits for their frames and is followed by them;\n * live frames arriving meanwhile are held and follow both. Without one, all of\n * it happens synchronously, as before.\n */\nasync function openSubscriptions(\n connection: SSEConnection,\n writer: BoundedWriter,\n hold: FrameHold,\n ctx: InitialContext,\n): Promise<void>\n{\n const connected = { subscribedEvents: [...connection.allowed], deniedEvents: [...connection.denied] };\n const producing = connected.subscribedEvents.filter(eventName => ctx.router.events[eventName]._initial);\n\n if (!ctx.subject || producing.length === 0)\n {\n enqueueConnected(writer, { ...connected, initial: [] });\n subscribeAllowed(connection);\n\n return;\n }\n\n hold.hold();\n subscribeAllowed(connection);\n\n const served = await produceInitialFrames(producing, ctx.router, {\n subject: ctx.subject,\n principal: ctx.principal,\n request: ctx.request,\n });\n if (ctx.isDead())\n {\n return;\n }\n\n enqueueConnected(writer, { ...connected, initial: served.map(({ eventName }) => eventName) });\n served.forEach(({ eventName, frames }) => frames.forEach(data => ctx.writeFrame(eventName, data)));\n hold.release();\n}\n\n/** The `connected` frame's data, as the client's `ConnectedPayload` reads it. */\ninterface ConnectedFrame\n{\n subscribedEvents: string[];\n deniedEvents: string[];\n initial: string[];\n}\n\nfunction enqueueConnected(writer: BoundedWriter, connected: ConnectedFrame): void\n{\n writer.enqueue({\n event: 'connected',\n data: JSON.stringify({ ...connected, timestamp: Date.now() }),\n });\n}\n\nfunction subscribeAllowed(connection: SSEConnection): void\n{\n for (const eventName of connection.allowed)\n {\n connection.unsubscribes.set(eventName, connection.subscribe(eventName));\n }\n\n sseLogger.info('SSE connection established', {\n events: [...connection.allowed],\n deniedEvents: [...connection.denied],\n subscriptionCount: connection.unsubscribes.size,\n });\n}\n\n/** What the initial pass of one connection reads: the subscriber's context and the `canSubscribe` one. */\ntype InitialContext = SubscriberContext & { principal: unknown };\n\n/** The serialized initial frames of one event whose producer returned. */\ninterface InitialFrames\n{\n eventName: string;\n frames: string[];\n}\n\n/**\n * Ask every producing event for its initial frames, in parallel; returns, in\n * `events` order, those whose producer returned — an empty list counts.\n */\nasync function produceInitialFrames(\n events: string[],\n router: EventRouterDef<any>,\n ctx: SubscribeContext,\n): Promise<InitialFrames[]>\n{\n const results = await Promise.all(events.map(eventName => askInitial(eventName, router.events[eventName], ctx)));\n\n return results.filter((frames): frames is InitialFrames => frames !== null);\n}\n\n/**\n * One event's producer, its payloads serialized here so that a payload that\n * cannot be written fails like a throw: warned with the event name — never the\n * error text — and `null`, so nothing of the event is written.\n */\nasync function askInitial(eventName: string, eventDef: EventDef<any>, ctx: SubscribeContext): Promise<InitialFrames | null>\n{\n try\n {\n const payloads = await eventDef._initial!(ctx);\n\n return { eventName, frames: payloads.map(payload => serializeFrame(eventName, payload)) };\n }\n catch\n {\n sseLogger.warn('SSE initial frames could not be produced — none sent for this event', {\n event: eventName,\n subject: ctx.subject,\n });\n\n return null;\n }\n}\n\n/**\n * The writes of one connection, held in arrival order between `hold()` and\n * `release()`; written at once outside. Released once: `release()` writes the\n * held ones and every later write goes straight through.\n *\n * A held write reserves its place in the writer's queue, so held and queued\n * frames share one `maxQueue`: a hold that outgrows it closes the connection\n * as a slow consumer, and what the initial pass enqueues before `release()`\n * counts the held frames too.\n */\ninterface FrameHold\n{\n send: (write: () => void) => void;\n hold: () => void;\n release: () => void;\n}\n\nfunction createFrameHold(writer: BoundedWriter): FrameHold\n{\n let pending: (() => void)[] | null = null;\n\n return {\n send: (write) =>\n {\n if (!pending)\n {\n write();\n }\n else if (writer.reserve())\n {\n pending.push(write);\n }\n },\n hold: () =>\n {\n pending = [];\n },\n release: () =>\n {\n const held = pending ?? [];\n pending = null;\n writer.clearReserved();\n held.forEach(write => write());\n },\n };\n}\n\n/**\n * Enqueue an event frame with the connection's next `messageId` — one counter\n * for initial and live frames, numbered in the order they are written.\n */\nfunction createFrameWriter(writer: BoundedWriter): (eventName: string, data: string) => void\n{\n let messageId = 0;\n\n return (eventName, data) =>\n {\n messageId++;\n\n sseLogger.debug('SSE sending event', {\n event: eventName,\n messageId,\n });\n\n writer.enqueue({ id: String(messageId), event: eventName, data });\n };\n}\n\n/**\n * Wait for client disconnect (abort) or a server-side close. Waiting on the\n * abort event instead of polling sleep(pingInterval) means a dropped client is\n * torn down immediately, not up to one ping interval later.\n */\nasync function waitForDisconnect(\n abortSignal: AbortSignal,\n isDead: () => boolean,\n onServerClose: (resolve: () => void) => void,\n): Promise<void>\n{\n if (abortSignal.aborted || isDead())\n {\n return;\n }\n\n await new Promise<void>((resolve) =>\n {\n const onAbort = () => resolve();\n abortSignal.addEventListener('abort', onAbort, { once: true });\n // Server-side close (cleanup) resolves the wait and drops the listener.\n onServerClose(() =>\n {\n abortSignal.removeEventListener('abort', onAbort);\n resolve();\n });\n });\n}\n\n/** What the per-event subscribe of one connection closes over. */\ninterface SubscriberContext\n{\n router: EventRouterDef<any>;\n authConfig: SSEHandlerAuthConfig | undefined;\n subject: string | undefined;\n request: SubscribeRequest;\n\n /** Enqueue an event frame with the next `messageId`. */\n writeFrame: (eventName: string, data: string) => void;\n\n /** Write now, or hold until the initial pass ends. */\n send: (write: () => void) => void;\n\n isDead: () => boolean;\n}\n\n/**\n * The per-event subscribe of one connection, used at connect and by `grant`\n * alike, so filter order and `messageId` numbering are one code path.\n *\n * The returned unsubscribe also deactivates the callback, so a frame of an\n * `emit` already fanning out when `revoke` lands is not enqueued after it.\n */\nfunction createEventSubscriber(ctx: SubscriberContext): (eventName: string) => () => void\n{\n return (eventName: string) =>\n {\n const eventDef = ctx.router.events[eventName];\n let active = true;\n\n const unsubscribe = eventDef.subscribe((payload: unknown) =>\n {\n if (!active || ctx.isDead() || !passesFrameFilters(eventName, eventDef, payload, ctx))\n {\n return;\n }\n\n const data = serializeFrame(eventName, payload);\n ctx.send(() => ctx.writeFrame(eventName, data));\n });\n\n return () =>\n {\n active = false;\n unsubscribe();\n };\n };\n}\n\n/**\n * The global `filter` map (with a subject, as before) and the event's own\n * `.filter()` — both must pass.\n */\nfunction passesFrameFilters(\n eventName: string,\n eventDef: EventDef<any>,\n payload: unknown,\n ctx: SubscriberContext,\n): boolean\n{\n const globalFilter = ctx.authConfig?.filter?.[eventName];\n if (ctx.subject && globalFilter && !globalFilter(ctx.subject, payload))\n {\n return false;\n }\n\n if (!eventDef._filter)\n {\n return true;\n }\n\n try\n {\n return eventDef._filter(payload, { subject: ctx.subject, request: ctx.request }) === true;\n }\n catch\n {\n sseLogger.warn('SSE event filter threw — frame dropped', { event: eventName, subject: ctx.subject });\n\n return false;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Authenticate via one-time token\n * @returns subject string if authenticated, undefined if no auth required,\n * false if token missing, null if token invalid/expired\n */\nasync function authenticateToken(\n c: Context,\n tokenManager?: SSETokenManager,\n): Promise<string | undefined | false | null>\n{\n if (!tokenManager)\n {\n return undefined;\n }\n\n const token = c.req.query('token');\n if (!token)\n {\n return false;\n }\n\n return await tokenManager.verify(token);\n}\n\n/**\n * Parse requested events from query parameter\n */\nfunction parseRequestedEvents(c: Context): string[] | null\n{\n const eventsParam = c.req.query('events');\n if (!eventsParam)\n {\n return null;\n }\n\n // Deduplicated: one subscription per event, keyed by name in the registry.\n return [...new Set(eventsParam.split(',').map(e => e.trim()))];\n}\n\n/**\n * Authorize event subscription via auth hook\n * @returns allowed events array, or null if rejected\n */\nasync function authorizeEvents(\n subject: string | undefined,\n requestedEvents: string[],\n authConfig?: SSEHandlerAuthConfig,\n): Promise<string[] | null>\n{\n if (!subject || !authConfig?.authorize)\n {\n return requestedEvents;\n }\n\n const allowed = await authConfig.authorize(subject, requestedEvents);\n\n if (allowed.length === 0)\n {\n return null;\n }\n\n return allowed;\n}\n","/**\n * SSE Token Manager\n *\n * Auth-agnostic token issuance and verification for SSE connections.\n * Issues one-time-use tokens with TTL for Token Exchange pattern.\n *\n * @example\n * ```typescript\n * const manager = new SSETokenManager({ ttl: 30000 });\n *\n * // Issue token for authenticated user\n * const token = await manager.issue('user-123');\n *\n * // Verify and consume token (one-time use)\n * const subject = await manager.verify(token); // 'user-123'\n * const again = await manager.verify(token); // null (already consumed)\n *\n * // Cleanup on shutdown\n * manager.destroy();\n * ```\n */\n\nimport { randomBytes, createHash } from 'crypto';\n\n/**\n * Hash a token for storage. The raw token is a bearer secret — storing it at rest\n * (e.g. as a Redis key/value) means a store dump hands out live tokens. We persist\n * only sha256(token) and look up by the same hash; the raw token exists only in\n * transit and in the issue() return value.\n */\nfunction hashToken(token: string): string\n{\n return createHash('sha256').update(token).digest('hex');\n}\n\n/**\n * Minimal cache client interface (compatible with ioredis Redis | Cluster)\n */\ntype CacheClient = {\n set(key: string, value: string, ...args: any[]): Promise<any>;\n getdel?(key: string): Promise<string | null>;\n get(key: string): Promise<string | null>;\n del(...keys: string[]): Promise<number>;\n};\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Stored SSE token data\n */\nexport interface SSEToken\n{\n subject: string;\n expiresAt: number;\n}\n\n/**\n * Token storage interface\n *\n * Implement this for custom storage backends (e.g., Redis for multi-instance).\n */\nexport interface SSETokenStore\n{\n /** Store a token */\n set(token: string, data: SSEToken): Promise<void>;\n\n /** Get and delete a token (one-time use) */\n consume(token: string): Promise<SSEToken | null>;\n\n /** Remove expired tokens */\n cleanup(): Promise<void>;\n}\n\n/**\n * SSETokenManager configuration\n */\nexport interface SSETokenManagerConfig\n{\n /**\n * Token time-to-live in milliseconds\n * @default 30000\n */\n ttl?: number;\n\n /**\n * Custom token store (default: in-memory Map)\n */\n store?: SSETokenStore;\n\n /**\n * Cleanup interval in milliseconds\n * @default 60000\n */\n cleanupInterval?: number;\n}\n\n// ============================================================================\n// InMemoryTokenStore\n// ============================================================================\n\nclass InMemoryTokenStore implements SSETokenStore\n{\n private tokens = new Map<string, SSEToken>();\n\n async set(token: string, data: SSEToken): Promise<void>\n {\n this.tokens.set(hashToken(token), data);\n }\n\n async consume(token: string): Promise<SSEToken | null>\n {\n const key = hashToken(token);\n const data = this.tokens.get(key);\n if (!data)\n {\n return null;\n }\n\n this.tokens.delete(key);\n\n return data;\n }\n\n async cleanup(): Promise<void>\n {\n const now = Date.now();\n\n for (const [key, data] of this.tokens)\n {\n if (data.expiresAt <= now)\n {\n this.tokens.delete(key);\n }\n }\n }\n}\n\n// ============================================================================\n// CacheTokenStore (Redis/Valkey)\n// ============================================================================\n\n/**\n * Redis/Valkey-backed token store for multi-instance deployments.\n *\n * Uses SET EX for automatic TTL expiry and GETDEL for atomic one-time consumption.\n * No cleanup needed — Redis handles expiration automatically.\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * const store = new CacheTokenStore(cache);\n * const manager = new SSETokenManager({ store });\n * }\n * ```\n */\nexport class CacheTokenStore implements SSETokenStore\n{\n private prefix = 'sse:token:';\n\n constructor(private cache: CacheClient) \n {}\n\n async set(token: string, data: SSEToken): Promise<void>\n {\n const ttlSeconds = Math.max(1, Math.ceil((data.expiresAt - Date.now()) / 1000));\n await this.cache.set(\n this.prefix + hashToken(token),\n JSON.stringify(data),\n 'EX',\n ttlSeconds,\n );\n }\n\n async consume(token: string): Promise<SSEToken | null>\n {\n const key = this.prefix + hashToken(token);\n\n // GETDEL (Redis 6.2+) for atomic consume, fallback to GET+DEL\n let raw: string | null = null;\n\n if (this.cache.getdel)\n {\n raw = await this.cache.getdel(key);\n }\n else\n {\n raw = await this.cache.get(key);\n if (raw)\n {\n await this.cache.del(key);\n }\n }\n\n if (!raw)\n {\n return null;\n }\n\n return JSON.parse(raw) as SSEToken;\n }\n\n async cleanup(): Promise<void>\n {\n // No-op: Redis TTL handles expiration automatically\n }\n}\n\n// ============================================================================\n// SSETokenManager\n// ============================================================================\n\nexport class SSETokenManager\n{\n private store: SSETokenStore;\n private ttl: number;\n private cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(config?: SSETokenManagerConfig)\n {\n this.ttl = config?.ttl ?? 30000;\n this.store = config?.store ?? new InMemoryTokenStore();\n\n const cleanupInterval = config?.cleanupInterval ?? 60000;\n this.cleanupTimer = setInterval(() => void this.store.cleanup(), cleanupInterval);\n this.cleanupTimer.unref();\n }\n\n /**\n * How long an issued token stays valid, in milliseconds.\n *\n * Exposed so a caller that has to report an expiry to its client computes the same\n * number this manager stamps on the token, instead of assuming the default.\n */\n get ttlMs(): number\n {\n return this.ttl;\n }\n\n /**\n * Issue a new one-time-use token for the given subject\n */\n async issue(subject: string): Promise<string>\n {\n const token = randomBytes(32).toString('hex');\n\n // The store keys by sha256(token); the raw token is never persisted.\n await this.store.set(token, {\n subject,\n expiresAt: Date.now() + this.ttl,\n });\n\n return token;\n }\n\n /**\n * Verify and consume a token\n * @returns subject string if valid, null if invalid/expired/already consumed\n */\n async verify(token: string): Promise<string | null>\n {\n const data = await this.store.consume(token);\n\n if (!data || data.expiresAt <= Date.now())\n {\n return null;\n }\n\n return data.subject;\n }\n\n /**\n * Cleanup timer and resources\n */\n destroy(): void\n {\n if (this.cleanupTimer)\n {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = null;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/event/sse/bounded-writer.ts","../../../src/event/sse/connection-registry.ts","../../../src/event/sse/key-close.ts","../../../src/event/sse/handler.ts","../../../src/event/sse/token-manager.ts","../../../src/event/cache-transport.ts","../../../src/event/sse/close-by-key.ts"],"names":["change","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Bounded SSE outbound writer\n *\n * Serializes writes to one SSE stream through a single drain loop that `await`s\n * each `writeSSE` — so a slow client applies real backpressure instead of letting\n * frames pile up unbounded in memory (OOM under a fast producer + slow consumer).\n *\n * The queue is per-connection, so a slow stream never blocks other connections.\n * When the queue exceeds `maxQueue`, the connection is closed (via `onClose`)\n * rather than silently dropping frames — dropping a chunk would corrupt an\n * ordered token/chat stream; the client reconnects and re-fetches instead.\n */\n\n/** A frame to write to the SSE stream (subset of Hono's SSEMessage). */\nexport interface SSEFrame\n{\n data: string;\n event?: string;\n id?: string;\n}\n\n/** Minimal stream surface this writer needs (Hono's SSEStreamingApi satisfies it). */\nexport interface SSEWritable\n{\n writeSSE(message: SSEFrame): Promise<void>;\n}\n\nexport interface BoundedWriter\n{\n /** Queue a frame for delivery; closes the connection if the queue overflows. */\n enqueue(frame: SSEFrame): void;\n\n /**\n * Count one frame held outside the queue against `maxQueue`, until\n * `clearReserved()`; closes the connection as a slow consumer when it does\n * not fit. `false` when it did not fit or the writer is closed.\n */\n reserve(): boolean;\n\n /** Stop counting the reserved frames — their writer is about to enqueue them. */\n clearReserved(): void;\n\n /** Stop draining and drop any queued frames (called on connection cleanup). */\n close(): void;\n\n /** Current queue depth (for tests/diagnostics). */\n readonly queued: number;\n}\n\n/**\n * Create a bounded, backpressure-aware writer for one SSE stream.\n *\n * @param stream the SSE stream (`writeSSE` returns a promise that resolves when\n * the socket has drained — that is what gives us backpressure)\n * @param maxQueue max frames buffered before the connection is closed\n * @param onClose called once when the writer closes the connection (overflow or\n * a write error); the caller cleans up and logs\n */\nexport function createBoundedWriter(\n stream: SSEWritable,\n maxQueue: number,\n onClose: (reason: string) => void,\n): BoundedWriter\n{\n const pending: SSEFrame[] = [];\n let flushing = false;\n let reserved = 0;\n let closed = false;\n\n // Slow consumer: the drain loop can't keep up, so the queue — with the frames\n // reserved for it — grew past the bound. Close rather than buffer unboundedly\n // or drop silently.\n const overflows = (): boolean =>\n {\n if (pending.length + reserved <= maxQueue)\n {\n return false;\n }\n\n onClose('outbound queue overflow (slow consumer)');\n\n return true;\n };\n\n const flush = async (): Promise<void> =>\n {\n if (flushing || closed)\n {\n return;\n }\n\n flushing = true;\n\n while (pending.length > 0 && !closed)\n {\n const frame = pending.shift() as SSEFrame;\n try\n {\n await stream.writeSSE(frame);\n }\n catch (err)\n {\n flushing = false;\n onClose(err instanceof Error ? err.message : String(err));\n\n return;\n }\n }\n\n flushing = false;\n };\n\n return {\n enqueue(frame: SSEFrame): void\n {\n if (closed)\n {\n return;\n }\n\n pending.push(frame);\n\n if (!overflows())\n {\n void flush();\n }\n },\n\n reserve(): boolean\n {\n if (closed)\n {\n return false;\n }\n\n reserved++;\n\n return !overflows();\n },\n\n clearReserved(): void\n {\n reserved = 0;\n },\n\n close(): void\n {\n closed = true;\n pending.length = 0;\n },\n\n get queued(): number\n {\n return pending.length;\n },\n };\n}\n","/**\n * SSE connection registry\n *\n * What one `createSSEHandler` knows about its open connections, so that\n * `EventDef.grant` / `EventDef.revoke` can change a connection after it opened.\n * One registry per handler: two handlers in one process (tests) never share one.\n *\n * Neither operation consults `canSubscribe` or `authorize`: they are an\n * operator's statement about an open connection. Permission lives in app data\n * and is re-read at the next connect. A `match` narrows either to the\n * connections whose connect query carries its fields.\n *\n * A connect still reading app data (`authorize`, `resolvePrincipal`,\n * `canSubscribe`) is a pending admission: a `grant` / `revoke` that lands then\n * is recorded on it and applied, without a frame, when the connection opens —\n * so a call the app made is never lost to that window.\n *\n * Connections are also indexed by the device key their token was minted with,\n * so `closeByKey` ends every stream of a revoked key. A close that lands on a\n * pending admission of that key is recorded the same way, and the connection\n * never opens.\n */\n\nimport { logger } from '@spfn/core/logger';\nimport type { SubscribeRequest, SubscriptionMatch } from '../types';\n\nconst registryLogger = logger.child('@spfn/core:sse');\n\n/** Stream-owned frame: an event was activated on the open connection. */\nexport const SUBSCRIPTION_GRANTED = 'subscription.granted';\n\n/** Stream-owned frame: an event was deactivated on the open connection. */\nexport const SUBSCRIPTION_REVOKED = 'subscription.revoked';\n\nexport type SubscriptionFrame = typeof SUBSCRIPTION_GRANTED | typeof SUBSCRIPTION_REVOKED;\n\n/**\n * One open SSE connection. `allowed` and `denied` partition `requested`\n * (plus anything `authorize` added); only `allowed` events hold a live\n * subscription in `unsubscribes`.\n */\nexport interface SSEConnection\n{\n /** `undefined` when the stream runs without auth — such a connection is never registered. */\n readonly subject: string | undefined;\n\n /** The device key its token was minted with; `undefined` for an untagged token — never closed by key. */\n readonly keyId: string | undefined;\n readonly requested: readonly string[];\n readonly allowed: Set<string>;\n readonly denied: Set<string>;\n readonly unsubscribes: Map<string, () => void>;\n readonly request: SubscribeRequest;\n\n /** The per-event subscribe the connection opened with — `grant` reuses it, never a copy. */\n readonly subscribe: (eventName: string) => () => void;\n\n /** Enqueue a protocol frame about one event (dropped once the connection is dead). */\n readonly notify: (frame: SubscriptionFrame, eventName: string) => void;\n\n /** End the stream server-side: unsubscribe, leave the registry, close the writer (idempotent). */\n readonly close: (reason: string) => void;\n}\n\nexport type ConnectionRegistry = ReturnType<typeof createConnectionRegistry>;\n\ntype SubscriptionChange = 'grant' | 'revoke';\n\n/** Why a pending admission does not open: the client left, or its key was revoked meanwhile. */\nexport type AdmissionRefusal = 'aborted during admission' | 'key revoked';\n\n/** The reason `closeByKey` closes a connection with. */\nconst KEY_REVOKED = 'key revoked';\n\n/**\n * A connect of one subject between its first app-data read and its open.\n * The first of `open` / `cancel` ends it; both are synchronous. A client abort\n * cancels it at once — even while an app predicate never settles.\n */\nexport interface PendingAdmission\n{\n /**\n * Apply the recorded changes to `connection`, then swap this entry for the\n * open connection; `null` once open. Once cancelled, or after a recorded\n * key close, nothing is applied or registered and the answer is why.\n */\n readonly open: (connection: SSEConnection) => AdmissionRefusal | null;\n\n /** Drop this entry (refused, aborted, or thrown); idempotent. */\n readonly cancel: () => void;\n}\n\n/** One `grant` / `revoke` recorded on a pending admission, or a close of its key. */\ntype RecordedChange =\n | { kind: SubscriptionChange; eventName: string; match?: SubscriptionMatch }\n | { kind: 'close' };\n\n/** The changes recorded on one pending admission, in call order (the last one wins). */\ntype RecordedChanges = RecordedChange[];\n\n/**\n * Create the registry of one SSE handler, keyed by subject → open connections\n * (a subject with two tabs has two), and by device key → open connections.\n */\nexport function createConnectionRegistry()\n{\n const bySubject = new Map<string, Set<SSEConnection>>();\n const byKey = new Map<string, Set<SSEConnection>>();\n const pendingBySubject = new Map<string, Set<RecordedChanges>>();\n const pendingByKey = new Map<string, Set<RecordedChanges>>();\n\n /**\n * Register a pending admission of `subject` (its token minted with\n * `keyId`, if any); `signal` is the connect's abort signal, listened to\n * until the admission ends either way.\n */\n const beginAdmission = (subject: string, keyId: string | undefined, signal: AbortSignal): PendingAdmission =>\n {\n const changes: RecordedChanges = [];\n addToSet(pendingBySubject, subject, changes);\n addToOptionalSet(pendingByKey, keyId, changes);\n const end = endOnce(signal, () =>\n {\n removeFromSet(pendingBySubject, subject, changes);\n removeFromOptionalSet(pendingByKey, keyId, changes);\n });\n\n return {\n cancel: end,\n open: (connection: SSEConnection) =>\n {\n if (!end())\n {\n return 'aborted during admission';\n }\n\n if (changes.some(change => change.kind === 'close'))\n {\n return KEY_REVOKED;\n }\n\n applyRecorded(connection, changes);\n addToSet(bySubject, subject, connection);\n addToOptionalSet(byKey, connection.keyId, connection);\n\n return null;\n },\n };\n };\n\n const change = (kind: SubscriptionChange, subject: string, eventName: string, match?: SubscriptionMatch): void =>\n {\n const pending = pendingBySubject.get(subject);\n const connections = bySubject.get(subject);\n pending?.forEach(changes => changes.push({ kind, eventName, match }));\n connections?.forEach(connection =>\n {\n if (matchesRequest(match, connection.request))\n {\n (kind === 'grant' ? grantOn : revokeOn)(connection, eventName);\n }\n });\n\n if (!pending && !connections)\n {\n registryLogger.debug('SSE subscription change for a subject with no open connection ignored', {\n event: eventName,\n subject,\n });\n }\n };\n\n /**\n * Close every open connection whose token was minted with `keyId`, and\n * record the close on its pending admissions; answers how many open ones\n * it closed. Iterates a copy: each close removes its connection.\n */\n const closeByKey = (keyId: string): number =>\n {\n const connections = [...byKey.get(keyId) ?? []];\n pendingByKey.get(keyId)?.forEach(changes => changes.push({ kind: 'close' }));\n connections.forEach(connection => connection.close(KEY_REVOKED));\n\n return connections.length;\n };\n\n return {\n beginAdmission,\n closeByKey,\n\n remove: (connection: SSEConnection): void =>\n {\n if (connection.subject)\n {\n removeFromSet(bySubject, connection.subject, connection);\n }\n\n removeFromOptionalSet(byKey, connection.keyId, connection);\n },\n\n /** Open connections only — a pending admission is not counted. */\n count: (subject: string): number => bySubject.get(subject)?.size ?? 0,\n\n /** Open connections whose token was minted with `keyId`. */\n countByKey: (keyId: string): number => byKey.get(keyId)?.size ?? 0,\n\n pendingCount: (subject: string): number => pendingBySubject.get(subject)?.size ?? 0,\n\n grant: (subject: string, eventName: string, match?: SubscriptionMatch): void =>\n change('grant', subject, eventName, match),\n\n revoke: (subject: string, eventName: string, match?: SubscriptionMatch): void =>\n change('revoke', subject, eventName, match),\n };\n}\n\n/**\n * The end of one pending admission: `remove` runs on the first call only,\n * which returns `true`; later calls return `false`. An abort of `signal` is a\n * first call; the listener is dropped on every end, so it never outlives the\n * admission (an already aborted signal ends it at once).\n */\nfunction endOnce(signal: AbortSignal, remove: () => void): () => boolean\n{\n let ended = false;\n const end = (): boolean =>\n {\n if (ended)\n {\n return false;\n }\n\n ended = true;\n signal.removeEventListener('abort', end);\n remove();\n\n return true;\n };\n\n signal.addEventListener('abort', end, { once: true });\n if (signal.aborted)\n {\n end();\n }\n\n return end;\n}\n\nfunction addToSet<T>(map: Map<string, Set<T>>, key: string, value: T): void\n{\n const set = map.get(key) ?? new Set<T>();\n set.add(value);\n map.set(key, set);\n}\n\n/** `addToSet` under a key that may be absent (an untagged token): absent → not indexed. */\nfunction addToOptionalSet<T>(map: Map<string, Set<T>>, key: string | undefined, value: T): void\n{\n if (key)\n {\n addToSet(map, key, value);\n }\n}\n\nfunction removeFromOptionalSet<T>(map: Map<string, Set<T>>, key: string | undefined, value: T): void\n{\n if (key)\n {\n removeFromSet(map, key, value);\n }\n}\n\n/** Remove `value` and drop the key once its set is empty, so neither map grows on reconnects. */\nfunction removeFromSet<T>(map: Map<string, Set<T>>, key: string, value: T): void\n{\n const set = map.get(key);\n set?.delete(value);\n\n if (set?.size === 0)\n {\n map.delete(key);\n }\n}\n\n/**\n * Whether `match` reaches a connection: every named field equals the connect\n * query's value, string to string. A field the connection never declared\n * never matches, not even an empty string. No `match` reaches every one.\n */\nexport function matchesRequest(match: SubscriptionMatch | undefined, request: SubscribeRequest): boolean\n{\n return match === undefined || Object.entries(match.query).every(([field, value]) =>\n Object.hasOwn(request.query, field) && request.query[field] === value);\n}\n\n/**\n * Apply what was recorded during admission to a connection that has not\n * opened yet: move the event between `allowed` and `denied`, no frame. An\n * event the connection never requested is in neither set and stays out; a\n * change whose `match` the connection's query does not meet is skipped.\n */\nfunction applyRecorded(connection: SSEConnection, changes: RecordedChanges): void\n{\n changes.forEach((change) =>\n {\n if (change.kind === 'close' || !matchesRequest(change.match, connection.request))\n {\n return;\n }\n\n const { kind, eventName } = change;\n const [from, to] = kind === 'grant'\n ? [connection.denied, connection.allowed]\n : [connection.allowed, connection.denied];\n\n if (from.delete(eventName))\n {\n to.add(eventName);\n }\n });\n}\n\n/**\n * Activate a denied event on one connection; allowed or never requested → no change.\n */\nfunction grantOn(connection: SSEConnection, eventName: string): void\n{\n if (!connection.denied.has(eventName))\n {\n return;\n }\n\n connection.unsubscribes.set(eventName, connection.subscribe(eventName));\n connection.denied.delete(eventName);\n connection.allowed.add(eventName);\n connection.notify(SUBSCRIPTION_GRANTED, eventName);\n}\n\n/**\n * Deactivate an allowed event on one connection; denied or never requested → no change.\n *\n * A frame the event already enqueued before this call may still be written —\n * the queue is not rewritten — but no frame after the unsubscribe is.\n */\nfunction revokeOn(connection: SSEConnection, eventName: string): void\n{\n if (!connection.allowed.has(eventName))\n {\n return;\n }\n\n connection.unsubscribes.get(eventName)?.();\n connection.unsubscribes.delete(eventName);\n connection.allowed.delete(eventName);\n connection.denied.add(eventName);\n connection.notify(SUBSCRIPTION_REVOKED, eventName);\n}\n\n/**\n * Drop every live subscription of a closing connection. A revoked event was\n * already removed from `unsubscribes`, so nothing is unsubscribed twice.\n */\nexport function unsubscribeAll(connection: SSEConnection): void\n{\n connection.unsubscribes.forEach(unsubscribe => unsubscribe());\n connection.unsubscribes.clear();\n}\n","/**\n * Process-wide close of the SSE streams one device key opened\n *\n * Every `createSSEHandler` registers its connection registry here, so a key\n * revocation reaches every handler of the process (two routers, tests) — the\n * local half of `closeSSEConnectionsByKey`. The cross-pod half lives in the\n * cache transport, which calls `applyKeyClose` for a close another pod\n * published; this module imports nothing from it, so the two never cycle.\n */\n\nimport { logger } from '@spfn/core/logger';\nimport type { ConnectionRegistry } from './connection-registry';\n\nconst keyCloseLogger = logger.child('@spfn/core:sse');\n\n/** What this module needs of a registry. */\ntype KeyClosable = Pick<ConnectionRegistry, 'closeByKey'>;\n\nconst REGISTRIES_KEY = Symbol.for('@spfn/core:sse-key-close-registries');\n\n// globalThis, like the event transport: one set per process across the CJS/ESM dual package.\nconst registries: Set<KeyClosable> = ((globalThis as any)[REGISTRIES_KEY] ??= new Set<KeyClosable>());\n\n/** Let `applyKeyClose` reach one handler's connections. */\nexport function registerKeyCloseRegistry(registry: KeyClosable): void\n{\n registries.add(registry);\n}\n\n/**\n * Close, on this process, every open SSE stream whose token was minted with\n * `keyId`, and stop its pending connects from opening. Answers how many open\n * streams it closed. Logs the count and reason only — never the key id.\n */\nexport function applyKeyClose(keyId: string): number\n{\n let closed = 0;\n registries.forEach(registry =>\n {\n closed += registry.closeByKey(keyId);\n });\n\n if (closed > 0)\n {\n keyCloseLogger.info('SSE connections closed', { count: closed, reason: 'key revoked' });\n }\n else\n {\n keyCloseLogger.debug('SSE key close reached no open connection', { reason: 'key revoked' });\n }\n\n return closed;\n}\n","/**\n * SSE Handler for Hono\n *\n * Creates SSE stream endpoint for event subscription\n *\n * @example\n * ```typescript\n * import { Hono } from 'hono';\n * import { createSSEHandler } from '@spfn/core/event/sse';\n * import { eventRouter } from './events';\n *\n * const app = new Hono();\n *\n * // GET /events/stream?events=userCreated,orderPlaced\n * app.get('/events/stream', createSSEHandler(eventRouter));\n * ```\n */\n\nimport type { Context } from 'hono';\nimport { streamSSE } from 'hono/streaming';\nimport type { SSEStreamingApi } from 'hono/streaming';\nimport { logger } from '@spfn/core/logger';\nimport type { EventRouterDef } from '../router';\nimport type { EventDef, SubscribeContext, SubscribeRequest, SubscriptionMatch } from '../types';\nimport type { SSEHandlerConfig, SSEHandlerAuthConfig } from './types';\nimport type { SSEToken, SSETokenManager } from './token-manager';\nimport { createBoundedWriter } from './bounded-writer';\nimport type { BoundedWriter } from './bounded-writer';\nimport { createConnectionRegistry, unsubscribeAll } from './connection-registry';\nimport { registerKeyCloseRegistry } from './key-close';\nimport type { ConnectionRegistry, PendingAdmission, SSEConnection } from './connection-registry';\n\nconst sseLogger = logger.child('@spfn/core:sse');\n\n/**\n * Serialize a fan-out frame once per (event, payload).\n *\n * One `emit` delivers the same payload object to every subscribed connection, so\n * without memoization each connection re-`JSON.stringify`s the identical payload\n * — O(N) for N subscribers on a hot broadcast. Keyed by the payload object\n * (WeakMap → auto-GC after the emit); primitives are cheap to stringify directly.\n */\nconst frameCache = new WeakMap<object, Map<string, string>>();\n\nexport function serializeFrame(eventName: string, payload: unknown): string\n{\n if (payload === null || typeof payload !== 'object')\n {\n return JSON.stringify({ event: eventName, data: payload });\n }\n\n let byEvent = frameCache.get(payload);\n if (!byEvent)\n {\n byEvent = new Map<string, string>();\n frameCache.set(payload, byEvent);\n }\n\n let serialized = byEvent.get(eventName);\n if (serialized === undefined)\n {\n serialized = JSON.stringify({ event: eventName, data: payload });\n byEvent.set(eventName, serialized);\n }\n\n return serialized;\n}\n\n/** Default outbound queue cap per connection before a slow consumer is dropped. */\nconst DEFAULT_MAX_QUEUE = 1000;\n\n// Extend Hono context with SSE subject\ndeclare module 'hono'\n{\n interface ContextVariableMap\n {\n sseSubject?: string;\n }\n}\n\n/**\n * Create SSE handler for Hono\n *\n * Query parameters:\n * - events: Comma-separated list of event names to subscribe\n * - token: One-time auth token (when auth is enabled)\n *\n * Who receives what, in order: the global `authorize` (once, at connect) →\n * each requested event's `.canSubscribe()` (once per event, at connect) → the\n * global `filter` map and the event's `.filter()` (sync, per frame). An event\n * denied at connect stays in `deniedEvents`, so `EventDef.grant` can activate\n * it later; `EventDef.revoke` deactivates an allowed one. The handler\n * registers that control on every event of the router.\n *\n * @example\n * ```typescript\n * app.get('/events/stream', createSSEHandler(eventRouter, {\n * pingInterval: 30000,\n * }));\n * ```\n */\nexport function createSSEHandler<TRouter extends EventRouterDef<any>>(\n router: TRouter,\n config: SSEHandlerConfig = {},\n tokenManager?: SSETokenManager,\n)\n{\n const {\n // 10s — keep-alive가 중간 프록시(GCP LB/nginx)의 idle 타임아웃(흔히 ~15-30s)보다 자주 와야\n // 첫 ping 전 침묵 구간에 연결이 끊기지 않는다. 30s 기본은 idle 타임아웃보다 길어 SSE가\n // 데이터 없는 동안 끊기던 문제가 있었다.\n pingInterval = 10000,\n auth: authConfig,\n maxQueue = DEFAULT_MAX_QUEUE,\n } = config;\n\n const registry = createConnectionRegistry();\n registerSubscriptionControls(router, registry);\n registerKeyCloseRegistry(registry);\n\n const streamConfig: StreamConfig = { router, authConfig, registry, pingInterval, maxQueue };\n\n const handler = async (c: Context) =>\n {\n const admission = await admitConnection(c, streamConfig, tokenManager);\n if (admission instanceof Response)\n {\n return admission;\n }\n\n if (c.req.raw.signal.aborted)\n {\n // The client left during admission: nobody reads this response.\n // Its abort listener already cancelled `pending`; cancel is idempotent.\n admission.pending?.cancel();\n\n return new Response(null, { status: 499 });\n }\n\n // ── 6. SSE Stream ──\n c.header('X-Accel-Buffering', 'no');\n\n return streamSSE(c, stream => runStream(c, stream, admission, streamConfig), async (err: Error) =>\n {\n sseLogger.error('SSE stream error', {\n error: err.message,\n });\n });\n };\n\n return Object.assign(handler, {\n /** @internal Open registered connections of a subject (tests). */\n _openConnections: (subject: string): number => registry.count(subject),\n\n /** @internal Connects of a subject still in admission (tests). */\n _pendingAdmissions: (subject: string): number => registry.pendingCount(subject),\n\n /** @internal Open registered connections whose token was minted with a key (tests). */\n _openConnectionsByKey: (keyId: string): number => registry.countByKey(keyId),\n });\n}\n\n// ============================================================================\n// Connect\n// ============================================================================\n\n/** What a request that passed every connect check opens the stream with. */\ninterface Admission\n{\n subject: string | undefined;\n\n /** The device key the token was minted with; never logged. */\n keyId: string | undefined;\n requested: string[];\n allowed: string[];\n denied: string[];\n request: SubscribeRequest;\n\n /** What `resolvePrincipal` returned, for the initial pass; never logged. */\n principal: unknown;\n\n /** Registered before the first app-data read; `runStream` opens it. Absent without a subject. */\n pending?: PendingAdmission;\n}\n\n/** What every stream of one handler shares. */\ninterface StreamConfig\n{\n router: EventRouterDef<any>;\n authConfig: SSEHandlerAuthConfig | undefined;\n registry: ConnectionRegistry;\n pingInterval: number;\n maxQueue: number;\n}\n\n/**\n * Let `EventDef.grant` / `revoke` reach this handler's connections, once per\n * router key (an event under two keys gets two controls, each naming its key).\n */\nfunction registerSubscriptionControls(router: EventRouterDef<any>, registry: ConnectionRegistry): void\n{\n for (const eventName of router.eventNames as string[])\n {\n router.events[eventName]._registerSubscriptionControl({\n grant: (subject: string, match?: SubscriptionMatch) => registry.grant(subject, eventName, match),\n revoke: (subject: string, match?: SubscriptionMatch) => registry.revoke(subject, eventName, match),\n });\n }\n}\n\n/**\n * Run the connect checks; a `Response` is the refusal to return. The admission\n * is registered right after the token leaves the store, before its key check\n * awaits — a key close landing while `verifyKey` reads is recorded on it, and\n * `open` refuses the stream.\n */\nasync function admitConnection(\n c: Context,\n config: StreamConfig,\n tokenManager: SSETokenManager | undefined,\n): Promise<Admission | Response>\n{\n // ── 1. Token Authentication (the store half) ──\n const token = await takeToken(c, tokenManager);\n if (token === false)\n {\n return c.json({ error: 'Missing token parameter' }, 401);\n }\n if (token === null)\n {\n return invalidToken(c);\n }\n\n // Synchronously after the take: no `await` between it and the registration.\n return whilePending(\n token?.subject ? config.registry.beginAdmission(token.subject, token.keyId, c.req.raw.signal) : undefined,\n () => checkConnection(c, config, token, tokenManager),\n );\n}\n\n/** The refusal of a token that is unknown, expired, consumed, or whose key is gone. */\nfunction invalidToken(c: Context): Response\n{\n return c.json({ error: 'Invalid or expired token' }, 401);\n}\n\n/**\n * The checks that run with the admission registered: the token's key, the\n * requested events against the router, then authorization.\n */\nasync function checkConnection(\n c: Context,\n config: StreamConfig,\n token: SSEToken | undefined,\n tokenManager: SSETokenManager | undefined,\n): Promise<Omit<Admission, 'pending'> | Response>\n{\n // ── 1. Token Authentication (the key half) ──\n if (token && tokenManager && !await tokenManager.checkKey(token))\n {\n return invalidToken(c);\n }\n\n const subject = token?.subject;\n if (subject)\n {\n c.set('sseSubject', subject);\n }\n\n // ── 2-3. Requested events, validated against the router ──\n const requested = validateRequestedEvents(c, config.router);\n if (requested instanceof Response)\n {\n return requested;\n }\n\n const authorized = await authorizeConnection(c, config.router, config.authConfig, subject, requested);\n\n return authorized instanceof Response ? authorized : { ...authorized, keyId: token?.keyId };\n}\n\n/**\n * Steps 2-3: the requested events, each known to the router; a `Response` is\n * the 400 to return.\n */\nfunction validateRequestedEvents(c: Context, router: EventRouterDef<any>): string[] | Response\n{\n const requested = parseRequestedEvents(c);\n if (!requested)\n {\n return c.json({ error: 'Missing events parameter' }, 400);\n }\n\n const validEventNames = router.eventNames as string[];\n const invalidEvents = requested.filter(e => !validEventNames.includes(e));\n if (invalidEvents.length > 0)\n {\n return c.json({\n error: 'Invalid event names',\n invalidEvents,\n validEvents: validEventNames,\n }, 400);\n }\n\n return requested;\n}\n\n/**\n * Run the key and app-data checks with the admission already registered — `pending`\n * is written synchronously, before the first `await`, so a `grant` / `revoke`\n * or a key close issued meanwhile is recorded on it. A refusal or a throw unregisters it; a\n * client abort already did, the moment it happened; an admitted connection\n * carries it to `runStream`.\n */\nasync function whilePending(\n pending: PendingAdmission | undefined,\n check: () => Promise<Omit<Admission, 'pending'> | Response>,\n): Promise<Admission | Response>\n{\n let outcome: Omit<Admission, 'pending'> | Response | undefined;\n\n try\n {\n outcome = await check();\n\n return outcome instanceof Response ? outcome : { ...outcome, pending };\n }\n finally\n {\n if (!outcome || outcome instanceof Response)\n {\n pending?.cancel();\n }\n }\n}\n\n/**\n * Steps 4-5: the global `authorize`, then the principal and each event's\n * `canSubscribe`. Only an empty `authorize` result or a failed principal\n * refuses; a `canSubscribe` denial only lands the event in `denied`.\n */\nasync function authorizeConnection(\n c: Context,\n router: EventRouterDef<any>,\n authConfig: SSEHandlerAuthConfig | undefined,\n subject: string | undefined,\n requested: string[],\n): Promise<Omit<Admission, 'keyId' | 'pending'> | Response>\n{\n // ── 4. Subscription Authorization ──\n const authorized = await authorizeEvents(subject, requested, authConfig);\n if (authorized === null)\n {\n return c.json({ error: 'Not authorized for any requested events' }, 403);\n }\n\n // ── 5. Principal + per-event canSubscribe ──\n const request: SubscribeRequest = { query: c.req.query(), headers: c.req.header() };\n const resolved = await resolveConnectionPrincipal(subject, request, authConfig);\n if (!resolved)\n {\n return c.json({ error: 'Principal could not be resolved' }, 403);\n }\n\n const allowed = subject\n ? await checkCanSubscribe(router, authorized, { subject, principal: resolved.principal, request })\n : authorized;\n const denied = requested.filter(e => !allowed.includes(e));\n\n sseLogger.debug('SSE connection requested', {\n events: allowed,\n deniedEvents: denied,\n subject: subject || undefined,\n clientIp: c.req.header('x-forwarded-for') || c.req.header('x-real-ip'),\n });\n\n return { subject, requested, allowed, denied, request, principal: resolved.principal };\n}\n\n/**\n * Resolve the principal once per connection; `null` refuses the connection.\n * The warning names the subject only — never the thrown error's text, which\n * may carry what the resolver read.\n */\nasync function resolveConnectionPrincipal(\n subject: string | undefined,\n request: SubscribeRequest,\n authConfig: SSEHandlerAuthConfig | undefined,\n): Promise<{ principal: unknown } | null>\n{\n if (!subject || !authConfig?.resolvePrincipal)\n {\n return { principal: undefined };\n }\n\n try\n {\n return { principal: await authConfig.resolvePrincipal(subject, request) };\n }\n catch\n {\n sseLogger.warn('SSE principal could not be resolved — connection refused', { subject });\n\n return null;\n }\n}\n\n/**\n * Ask each authorized event's `canSubscribe`, in parallel; returns the events\n * it allows. An event without a predicate is allowed.\n */\nasync function checkCanSubscribe(\n router: EventRouterDef<any>,\n events: string[],\n ctx: SubscribeContext,\n): Promise<string[]>\n{\n const verdicts = await Promise.all(events.map(eventName => askCanSubscribe(eventName, router.events[eventName], ctx)));\n\n return events.filter((_, i) => verdicts[i]);\n}\n\n/**\n * `true` → allowed; `false` or a throw → denied. A throw is warned with the\n * event name and never fails the connection.\n */\nasync function askCanSubscribe(\n eventName: string,\n eventDef: EventDef<any> | undefined,\n ctx: SubscribeContext,\n): Promise<boolean>\n{\n if (!eventDef)\n {\n return false;\n }\n\n if (!eventDef._canSubscribe)\n {\n return true;\n }\n\n try\n {\n return await eventDef._canSubscribe(ctx) === true;\n }\n catch\n {\n sseLogger.warn('SSE canSubscribe threw — event denied on this connection', {\n event: eventName,\n subject: ctx.subject,\n });\n\n return false;\n }\n}\n\n// ============================================================================\n// Stream\n// ============================================================================\n\n/**\n * The life of one open connection: connected frame, subscriptions, initial\n * frames, registry entry, keep-alive, and teardown on disconnect or a\n * server-side close.\n */\nasync function runStream(\n c: Context,\n stream: SSEStreamingApi,\n admission: Admission,\n config: StreamConfig,\n): Promise<void>\n{\n const { allowed, denied, subject, request } = admission;\n let connectionDead = false;\n let pingTimer: ReturnType<typeof setInterval> | undefined;\n // Resolves the disconnect wait below when the connection is closed\n // server-side (overflow/error), so teardown doesn't wait for the loop.\n let onClosed: (() => void) | undefined;\n\n const cleanup = (reason?: string) =>\n {\n if (connectionDead) return;\n connectionDead = true;\n clearInterval(pingTimer);\n unsubscribeAll(connection);\n config.registry.remove(connection);\n writer.close();\n onClosed?.();\n sseLogger.info('SSE dead connection cleaned up', {\n events: [...connection.allowed],\n reason,\n });\n };\n\n // All writes go through one bounded, backpressure-aware drain loop so a\n // slow client can't pile frames up in memory (it's closed on overflow).\n const writer = createBoundedWriter(stream, config.maxQueue, (reason) =>\n {\n sseLogger.warn('SSE connection closed', { events: [...connection.allowed], reason });\n cleanup(reason);\n });\n\n // Every write after `connected` goes through `hold`, so the initial pass can\n // put `connected` and its frames ahead of whatever arrives while it runs.\n const hold = createFrameHold(writer);\n const subscriberContext: SubscriberContext = {\n router: config.router,\n authConfig: config.authConfig,\n subject,\n request,\n writeFrame: createFrameWriter(writer),\n send: hold.send,\n isDead: () => connectionDead,\n };\n\n const connection: SSEConnection = {\n subject,\n keyId: admission.keyId,\n requested: admission.requested,\n allowed: new Set(allowed),\n denied: new Set(denied),\n unsubscribes: new Map(),\n request,\n subscribe: createEventSubscriber(subscriberContext),\n notify: (frame, eventName) =>\n {\n if (connectionDead) return;\n\n // A small literal — not routed through serializeFrame's per-payload cache.\n hold.send(() => writer.enqueue({ event: frame, data: JSON.stringify({ event: eventName }) }));\n },\n close: cleanup,\n };\n\n // Synchronously before the `connected` frame: the changes recorded during\n // admission land in `allowed` / `denied` first, and the entry turns open.\n // A refused admission opens nothing and sends no frame: its key was\n // revoked meanwhile, or the client left (defensive — unreachable today,\n // since `streamSSE` runs this synchronously up to here; the handler's\n // `signal.aborted` → 499 check is the real path).\n const refusal = admission.pending?.open(connection);\n if (refusal)\n {\n cleanup(refusal);\n\n return;\n }\n\n // Not awaited: a producer that hangs holds the frames, never the disconnect wait.\n void openSubscriptions(connection, writer, hold, {\n ...subscriberContext,\n principal: admission.principal,\n });\n\n // Keep-alive ping\n pingTimer = setInterval(() =>\n {\n if (connectionDead) return;\n\n hold.send(() => writer.enqueue({\n event: 'ping',\n data: JSON.stringify({ timestamp: Date.now() }),\n }));\n }, config.pingInterval);\n\n await waitForDisconnect(c.req.raw.signal, () => connectionDead, (resolve) =>\n {\n onClosed = resolve;\n });\n\n // Cleanup (normal disconnect path; idempotent if already closed)\n cleanup();\n}\n\n/**\n * Send the `connected` frame (first in the queue) and subscribe every allowed\n * event. For a connection with a subject whose allowed events declare\n * `.initial()`, `connected` waits for their frames and is followed by them;\n * live frames arriving meanwhile are held and follow both. Without one, all of\n * it happens synchronously, as before.\n */\nasync function openSubscriptions(\n connection: SSEConnection,\n writer: BoundedWriter,\n hold: FrameHold,\n ctx: InitialContext,\n): Promise<void>\n{\n const connected = { subscribedEvents: [...connection.allowed], deniedEvents: [...connection.denied] };\n const producing = connected.subscribedEvents.filter(eventName => ctx.router.events[eventName]._initial);\n\n if (!ctx.subject || producing.length === 0)\n {\n enqueueConnected(writer, { ...connected, initial: [] });\n subscribeAllowed(connection);\n\n return;\n }\n\n hold.hold();\n subscribeAllowed(connection);\n\n const served = await produceInitialFrames(producing, ctx.router, {\n subject: ctx.subject,\n principal: ctx.principal,\n request: ctx.request,\n });\n if (ctx.isDead())\n {\n return;\n }\n\n enqueueConnected(writer, { ...connected, initial: served.map(({ eventName }) => eventName) });\n served.forEach(({ eventName, frames }) => frames.forEach(data => ctx.writeFrame(eventName, data)));\n hold.release();\n}\n\n/** The `connected` frame's data, as the client's `ConnectedPayload` reads it. */\ninterface ConnectedFrame\n{\n subscribedEvents: string[];\n deniedEvents: string[];\n initial: string[];\n}\n\nfunction enqueueConnected(writer: BoundedWriter, connected: ConnectedFrame): void\n{\n writer.enqueue({\n event: 'connected',\n data: JSON.stringify({ ...connected, timestamp: Date.now() }),\n });\n}\n\nfunction subscribeAllowed(connection: SSEConnection): void\n{\n for (const eventName of connection.allowed)\n {\n connection.unsubscribes.set(eventName, connection.subscribe(eventName));\n }\n\n sseLogger.info('SSE connection established', {\n events: [...connection.allowed],\n deniedEvents: [...connection.denied],\n subscriptionCount: connection.unsubscribes.size,\n });\n}\n\n/** What the initial pass of one connection reads: the subscriber's context and the `canSubscribe` one. */\ntype InitialContext = SubscriberContext & { principal: unknown };\n\n/** The serialized initial frames of one event whose producer returned. */\ninterface InitialFrames\n{\n eventName: string;\n frames: string[];\n}\n\n/**\n * Ask every producing event for its initial frames, in parallel; returns, in\n * `events` order, those whose producer returned — an empty list counts.\n */\nasync function produceInitialFrames(\n events: string[],\n router: EventRouterDef<any>,\n ctx: SubscribeContext,\n): Promise<InitialFrames[]>\n{\n const results = await Promise.all(events.map(eventName => askInitial(eventName, router.events[eventName], ctx)));\n\n return results.filter((frames): frames is InitialFrames => frames !== null);\n}\n\n/**\n * One event's producer, its payloads serialized here so that a payload that\n * cannot be written fails like a throw: warned with the event name — never the\n * error text — and `null`, so nothing of the event is written.\n */\nasync function askInitial(eventName: string, eventDef: EventDef<any>, ctx: SubscribeContext): Promise<InitialFrames | null>\n{\n try\n {\n const payloads = await eventDef._initial!(ctx);\n\n return { eventName, frames: payloads.map(payload => serializeFrame(eventName, payload)) };\n }\n catch\n {\n sseLogger.warn('SSE initial frames could not be produced — none sent for this event', {\n event: eventName,\n subject: ctx.subject,\n });\n\n return null;\n }\n}\n\n/**\n * The writes of one connection, held in arrival order between `hold()` and\n * `release()`; written at once outside. Released once: `release()` writes the\n * held ones and every later write goes straight through.\n *\n * A held write reserves its place in the writer's queue, so held and queued\n * frames share one `maxQueue`: a hold that outgrows it closes the connection\n * as a slow consumer, and what the initial pass enqueues before `release()`\n * counts the held frames too.\n */\ninterface FrameHold\n{\n send: (write: () => void) => void;\n hold: () => void;\n release: () => void;\n}\n\nfunction createFrameHold(writer: BoundedWriter): FrameHold\n{\n let pending: (() => void)[] | null = null;\n\n return {\n send: (write) =>\n {\n if (!pending)\n {\n write();\n }\n else if (writer.reserve())\n {\n pending.push(write);\n }\n },\n hold: () =>\n {\n pending = [];\n },\n release: () =>\n {\n const held = pending ?? [];\n pending = null;\n writer.clearReserved();\n held.forEach(write => write());\n },\n };\n}\n\n/**\n * Enqueue an event frame with the connection's next `messageId` — one counter\n * for initial and live frames, numbered in the order they are written.\n */\nfunction createFrameWriter(writer: BoundedWriter): (eventName: string, data: string) => void\n{\n let messageId = 0;\n\n return (eventName, data) =>\n {\n messageId++;\n\n sseLogger.debug('SSE sending event', {\n event: eventName,\n messageId,\n });\n\n writer.enqueue({ id: String(messageId), event: eventName, data });\n };\n}\n\n/**\n * Wait for client disconnect (abort) or a server-side close. Waiting on the\n * abort event instead of polling sleep(pingInterval) means a dropped client is\n * torn down immediately, not up to one ping interval later.\n */\nasync function waitForDisconnect(\n abortSignal: AbortSignal,\n isDead: () => boolean,\n onServerClose: (resolve: () => void) => void,\n): Promise<void>\n{\n if (abortSignal.aborted || isDead())\n {\n return;\n }\n\n await new Promise<void>((resolve) =>\n {\n const onAbort = () => resolve();\n abortSignal.addEventListener('abort', onAbort, { once: true });\n // Server-side close (cleanup) resolves the wait and drops the listener.\n onServerClose(() =>\n {\n abortSignal.removeEventListener('abort', onAbort);\n resolve();\n });\n });\n}\n\n/** What the per-event subscribe of one connection closes over. */\ninterface SubscriberContext\n{\n router: EventRouterDef<any>;\n authConfig: SSEHandlerAuthConfig | undefined;\n subject: string | undefined;\n request: SubscribeRequest;\n\n /** Enqueue an event frame with the next `messageId`. */\n writeFrame: (eventName: string, data: string) => void;\n\n /** Write now, or hold until the initial pass ends. */\n send: (write: () => void) => void;\n\n isDead: () => boolean;\n}\n\n/**\n * The per-event subscribe of one connection, used at connect and by `grant`\n * alike, so filter order and `messageId` numbering are one code path.\n *\n * The returned unsubscribe also deactivates the callback, so a frame of an\n * `emit` already fanning out when `revoke` lands is not enqueued after it.\n */\nfunction createEventSubscriber(ctx: SubscriberContext): (eventName: string) => () => void\n{\n return (eventName: string) =>\n {\n const eventDef = ctx.router.events[eventName];\n let active = true;\n\n const unsubscribe = eventDef.subscribe((payload: unknown) =>\n {\n if (!active || ctx.isDead() || !passesFrameFilters(eventName, eventDef, payload, ctx))\n {\n return;\n }\n\n const data = serializeFrame(eventName, payload);\n ctx.send(() => ctx.writeFrame(eventName, data));\n });\n\n return () =>\n {\n active = false;\n unsubscribe();\n };\n };\n}\n\n/**\n * The global `filter` map (with a subject, as before) and the event's own\n * `.filter()` — both must pass.\n */\nfunction passesFrameFilters(\n eventName: string,\n eventDef: EventDef<any>,\n payload: unknown,\n ctx: SubscriberContext,\n): boolean\n{\n const globalFilter = ctx.authConfig?.filter?.[eventName];\n if (ctx.subject && globalFilter && !globalFilter(ctx.subject, payload))\n {\n return false;\n }\n\n if (!eventDef._filter)\n {\n return true;\n }\n\n try\n {\n return eventDef._filter(payload, { subject: ctx.subject, request: ctx.request }) === true;\n }\n catch\n {\n sseLogger.warn('SSE event filter threw — frame dropped', { event: eventName, subject: ctx.subject });\n\n return false;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Take the one-time token out of the store — its key is checked later, with\n * the admission registered (`checkConnection`)\n * @returns what the token was minted with, undefined if no auth required,\n * false if token missing, null if token invalid/expired/consumed\n */\nasync function takeToken(\n c: Context,\n tokenManager?: SSETokenManager,\n): Promise<SSEToken | undefined | false | null>\n{\n if (!tokenManager)\n {\n return undefined;\n }\n\n const token = c.req.query('token');\n if (!token)\n {\n return false;\n }\n\n return tokenManager.take(token);\n}\n\n/**\n * Parse requested events from query parameter\n */\nfunction parseRequestedEvents(c: Context): string[] | null\n{\n const eventsParam = c.req.query('events');\n if (!eventsParam)\n {\n return null;\n }\n\n // Deduplicated: one subscription per event, keyed by name in the registry.\n return [...new Set(eventsParam.split(',').map(e => e.trim()))];\n}\n\n/**\n * Authorize event subscription via auth hook\n * @returns allowed events array, or null if rejected\n */\nasync function authorizeEvents(\n subject: string | undefined,\n requestedEvents: string[],\n authConfig?: SSEHandlerAuthConfig,\n): Promise<string[] | null>\n{\n if (!subject || !authConfig?.authorize)\n {\n return requestedEvents;\n }\n\n const allowed = await authConfig.authorize(subject, requestedEvents);\n\n if (allowed.length === 0)\n {\n return null;\n }\n\n return allowed;\n}\n","/**\n * SSE Token Manager\n *\n * Auth-agnostic token issuance and verification for SSE connections.\n * Issues one-time-use tokens with TTL for Token Exchange pattern.\n *\n * @example\n * ```typescript\n * const manager = new SSETokenManager({ ttl: 30000 });\n *\n * // Issue token for authenticated user\n * const token = await manager.issue('user-123');\n *\n * // Verify and consume token (one-time use)\n * const subject = await manager.verify(token); // 'user-123'\n * const again = await manager.verify(token); // null (already consumed)\n *\n * // A token tagged with the device key it was minted for is refused at\n * // consume once `verifyKey` says that key is gone\n * const tagged = await manager.issue('user-123', { keyId: 'key-1' });\n * const data = await manager.consume(tagged); // { subject, keyId, expiresAt } | null\n *\n * // Cleanup on shutdown\n * manager.destroy();\n * ```\n */\n\nimport { randomBytes, createHash } from 'crypto';\nimport { logger } from '@spfn/core/logger';\n\nconst tokenLogger = logger.child('@spfn/core:sse');\n\n/**\n * Hash a token for storage. The raw token is a bearer secret — storing it at rest\n * (e.g. as a Redis key/value) means a store dump hands out live tokens. We persist\n * only sha256(token) and look up by the same hash; the raw token exists only in\n * transit and in the issue() return value.\n */\nfunction hashToken(token: string): string\n{\n return createHash('sha256').update(token).digest('hex');\n}\n\n/**\n * Minimal cache client interface (compatible with ioredis Redis | Cluster)\n */\ntype CacheClient = {\n set(key: string, value: string, ...args: any[]): Promise<any>;\n getdel?(key: string): Promise<string | null>;\n get(key: string): Promise<string | null>;\n del(...keys: string[]): Promise<number>;\n};\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Stored SSE token data\n */\nexport interface SSEToken\n{\n subject: string;\n expiresAt: number;\n\n /** The device key the token was minted with; absent → an untagged token. */\n keyId?: string;\n}\n\n/**\n * Whether the device key a token was minted with still holds; `false` or a\n * throw refuses the token.\n */\nexport type SSEKeyVerifier = (keyId: string) => boolean | Promise<boolean>;\n\n/**\n * Token storage interface\n *\n * Implement this for custom storage backends (e.g., Redis for multi-instance).\n */\nexport interface SSETokenStore\n{\n /** Store a token */\n set(token: string, data: SSEToken): Promise<void>;\n\n /** Get and delete a token (one-time use) */\n consume(token: string): Promise<SSEToken | null>;\n\n /** Remove expired tokens */\n cleanup(): Promise<void>;\n}\n\n/**\n * SSETokenManager configuration\n */\nexport interface SSETokenManagerConfig\n{\n /**\n * Token time-to-live in milliseconds\n * @default 30000\n */\n ttl?: number;\n\n /**\n * Custom token store (default: in-memory Map)\n */\n store?: SSETokenStore;\n\n /**\n * Cleanup interval in milliseconds\n * @default 60000\n */\n cleanupInterval?: number;\n\n /**\n * Re-check, when a token is consumed, the device key it was minted with —\n * a token minted before its key was revoked is then refused at open.\n * Runs only for a token that carries a `keyId`; `false` or a throw\n * refuses it. The token is consumed first, so a refused one is never\n * retried. `@spfn/auth`'s one-time token manager supplies it.\n */\n verifyKey?: SSEKeyVerifier;\n}\n\n// ============================================================================\n// InMemoryTokenStore\n// ============================================================================\n\nclass InMemoryTokenStore implements SSETokenStore\n{\n private tokens = new Map<string, SSEToken>();\n\n async set(token: string, data: SSEToken): Promise<void>\n {\n this.tokens.set(hashToken(token), data);\n }\n\n async consume(token: string): Promise<SSEToken | null>\n {\n const key = hashToken(token);\n const data = this.tokens.get(key);\n if (!data)\n {\n return null;\n }\n\n this.tokens.delete(key);\n\n return data;\n }\n\n async cleanup(): Promise<void>\n {\n const now = Date.now();\n\n for (const [key, data] of this.tokens)\n {\n if (data.expiresAt <= now)\n {\n this.tokens.delete(key);\n }\n }\n }\n}\n\n// ============================================================================\n// CacheTokenStore (Redis/Valkey)\n// ============================================================================\n\n/**\n * Redis/Valkey-backed token store for multi-instance deployments.\n *\n * Uses SET EX for automatic TTL expiry and GETDEL for atomic one-time consumption.\n * No cleanup needed — Redis handles expiration automatically.\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * const store = new CacheTokenStore(cache);\n * const manager = new SSETokenManager({ store });\n * }\n * ```\n */\nexport class CacheTokenStore implements SSETokenStore\n{\n private prefix = 'sse:token:';\n\n constructor(private cache: CacheClient) \n {}\n\n async set(token: string, data: SSEToken): Promise<void>\n {\n const ttlSeconds = Math.max(1, Math.ceil((data.expiresAt - Date.now()) / 1000));\n await this.cache.set(\n this.prefix + hashToken(token),\n JSON.stringify(data),\n 'EX',\n ttlSeconds,\n );\n }\n\n async consume(token: string): Promise<SSEToken | null>\n {\n const key = this.prefix + hashToken(token);\n\n // GETDEL (Redis 6.2+) for atomic consume, fallback to GET+DEL\n let raw: string | null = null;\n\n if (this.cache.getdel)\n {\n raw = await this.cache.getdel(key);\n }\n else\n {\n raw = await this.cache.get(key);\n if (raw)\n {\n await this.cache.del(key);\n }\n }\n\n if (!raw)\n {\n return null;\n }\n\n return JSON.parse(raw) as SSEToken;\n }\n\n async cleanup(): Promise<void>\n {\n // No-op: Redis TTL handles expiration automatically\n }\n}\n\n// ============================================================================\n// SSETokenManager\n// ============================================================================\n\nexport class SSETokenManager\n{\n private store: SSETokenStore;\n private ttl: number;\n private verifyKey: SSEKeyVerifier | undefined;\n private cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(config?: SSETokenManagerConfig)\n {\n this.ttl = config?.ttl ?? 30000;\n this.store = config?.store ?? new InMemoryTokenStore();\n this.verifyKey = config?.verifyKey;\n\n const cleanupInterval = config?.cleanupInterval ?? 60000;\n this.cleanupTimer = setInterval(() => void this.store.cleanup(), cleanupInterval);\n this.cleanupTimer.unref();\n }\n\n /**\n * How long an issued token stays valid, in milliseconds.\n *\n * Exposed so a caller that has to report an expiry to its client computes the same\n * number this manager stamps on the token, instead of assuming the default.\n */\n get ttlMs(): number\n {\n return this.ttl;\n }\n\n /**\n * Issue a new one-time-use token for the given subject. `keyId` tags it\n * with the device key it is minted for; an empty one leaves it untagged.\n */\n async issue(subject: string, options?: { keyId?: string }): Promise<string>\n {\n const token = randomBytes(32).toString('hex');\n const keyId = options?.keyId || undefined;\n\n // The store keys by sha256(token); the raw token is never persisted.\n await this.store.set(token, {\n subject,\n expiresAt: Date.now() + this.ttl,\n ...(keyId ? { keyId } : {}),\n });\n\n return token;\n }\n\n /**\n * Verify and consume a token\n * @returns subject string if valid, null if invalid/expired/already consumed\n */\n async verify(token: string): Promise<string | null>\n {\n return (await this.consume(token))?.subject ?? null;\n }\n\n /**\n * Consume a token and answer what it was minted with: `null` when it is\n * unknown, expired, already consumed, or its key no longer holds\n * (`verifyKey`). The token is gone either way. `take` + `checkKey`.\n */\n async consume(token: string): Promise<SSEToken | null>\n {\n const data = await this.take(token);\n\n return data && await this.checkKey(data) ? data : null;\n }\n\n /**\n * The store half of `consume`: take the token out of the store and answer\n * what it was minted with, `null` when it is unknown, expired or already\n * consumed — its key is not checked yet (`checkKey`). For a caller that\n * has to register the connection before the key check, so a revocation\n * landing during the check reaches it.\n */\n async take(token: string): Promise<SSEToken | null>\n {\n const data = await this.store.consume(token);\n\n return data && data.expiresAt > Date.now() ? data : null;\n }\n\n /**\n * The key half of `consume`: `verifyKey` on a tagged token; an untagged\n * one, or no verifier, holds without a call. A throw refuses and is warned\n * without its text — it may carry what the verifier read.\n */\n async checkKey(data: SSEToken): Promise<boolean>\n {\n if (!data.keyId || !this.verifyKey)\n {\n return true;\n }\n\n try\n {\n return await this.verifyKey(data.keyId) === true;\n }\n catch\n {\n tokenLogger.warn('SSE token key check threw — token refused');\n\n return false;\n }\n }\n\n /**\n * Cleanup timer and resources\n */\n destroy(): void\n {\n if (this.cleanupTimer)\n {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = null;\n }\n }\n}\n","/**\n * Event cache transport (Redis/Valkey pub/sub)\n *\n * Wires each event in a router to a shared {@link PubSubCache} so that an\n * `emit` on one pod fans out to subscribers on every pod. This is the runtime\n * glue behind `EventDef.useCache` — applications don't call it directly; the\n * server's `.events()` / `.websockets()` registration calls it at startup when\n * a cache (`CACHE_URL`) is configured. Without a cache, events stay in-process\n * and this is a no-op.\n *\n * Mirrors the auto-detection pattern used by `CacheTokenStore` for SSE tokens.\n */\n\nimport { logger } from '@spfn/core/logger';\nimport type { EventRouterBase } from './router';\nimport type { PubSubCache, SubscriptionMatch } from './types';\nimport { applyKeyClose } from './sse/key-close';\n\nconst transportLogger = logger.child('@spfn/core:event-transport');\n\n/** Default channel prefix; isolates SPFN event channels from other Redis keys. */\nconst DEFAULT_CHANNEL_PREFIX = 'spfn:sse:';\n\n/** Cap on the subscriber `quit()` during shutdown so a wedged socket can't stall it. */\nconst SUBSCRIBER_QUIT_TIMEOUT_MS = 5000;\n\n/** Cap a Redis PUBLISH so a wedged socket (half-open, no FIN) can't hang `emit` forever. */\nconst PUBLISH_TIMEOUT_MS = 5000;\n\n/** Cap a Redis SUBSCRIBE so a wedged socket can't hang server startup; degrades to in-process. */\nconst SUBSCRIBE_TIMEOUT_MS = 5000;\n\n/** Fold repeated transport warnings during a sustained outage into one line per window. */\nconst WARN_THROTTLE_MS = 30000;\n\n/**\n * After a publish failure, skip Redis entirely (deliver locally) for this long before\n * probing again. Without it, a streaming `emit` loop pays the full PUBLISH_TIMEOUT_MS per\n * chunk during a sustained outage (50 chunks × 5s = minutes of request hang).\n */\nconst PUBLISH_BREAKER_COOLDOWN_MS = 10000;\n\n/**\n * A key close the transport could not publish is re-sent after this long: just past the\n * breaker's cooldown, so the retry is the half-open probe rather than another skip.\n */\nconst KEY_CLOSE_RETRY_MS = PUBLISH_BREAKER_COOLDOWN_MS + 500;\n\n/** Cap on key closes waiting to be re-sent; past it the oldest is dropped. */\nconst PENDING_KEY_CLOSE_CAP = 1000;\n\n/**\n * Reject if `promise` doesn't settle within `ms`. ioredis has no command timeout by\n * default, so a half-open socket leaves publish/subscribe pending indefinitely; this\n * bounds it so the caller can fall back instead of hanging.\n */\nfunction withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T>\n{\n return new Promise<T>((resolve, reject) =>\n {\n const timer = setTimeout(() =>\n {\n reject(new Error(`${label} timed out after ${ms}ms`));\n }, ms);\n timer.unref?.();\n\n promise.then(\n (value) =>\n {\n clearTimeout(timer);\n resolve(value);\n },\n (err) =>\n {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n\nconst warnThrottle = new Map<string, { last: number; suppressed: number }>();\n\n/**\n * Warn at most once per {@link WARN_THROTTLE_MS} per key, folding the suppressed count\n * into the next line — a sustained Redis outage must not flood logs (one WARN per emit\n * × a fast stream = thousands/sec).\n */\nfunction throttledWarn(key: string, message: string, fields: Record<string, unknown>): void\n{\n const now = Date.now();\n const entry = warnThrottle.get(key);\n\n if (entry && now - entry.last < WARN_THROTTLE_MS)\n {\n entry.suppressed++;\n\n return;\n }\n\n transportLogger.warn(message, entry && entry.suppressed > 0\n ? { ...fields, suppressedSinceLast: entry.suppressed }\n : fields);\n warnThrottle.set(key, { last: now, suppressed: 0 });\n}\n\n/**\n * Forget every throttle stamp, so a test's warnings do not depend on the ones before it.\n *\n * @internal Exported for tests\n */\nexport function _resetWarnThrottle(): void\n{\n warnThrottle.clear();\n}\n\n/**\n * Minimal pub/sub client interface (compatible with ioredis Redis | Cluster).\n * A duplicated connection is required for SUBSCRIBE — ioredis puts a connection\n * into subscriber mode, where regular commands are rejected.\n */\ntype PubSubClient = {\n publish(channel: string, message: string): Promise<unknown>;\n duplicate(): PubSubClient;\n subscribe(...channels: string[]): Promise<unknown>;\n on(event: string, listener: (...args: any[]) => void): unknown;\n quit?(): Promise<unknown>;\n /** ioredis connection status ('ready' | 'connecting' | 'close' | ...). */\n status?: string;\n};\n\ntype MessageHandler = (message: unknown) => void | Promise<void>;\n\n/** The slice of EventDef this module wires (kept minimal + duck-typed). */\ntype WirableEvent = {\n name: string;\n useCache: (cache: PubSubCache) => Promise<unknown>;\n _resetCache?: () => void;\n};\n\n// ── globalThis singleton ──────────────────────────────────────────\n// Survives the CJS/ESM dual-package hazard (same pattern as cache-manager):\n// one subscriber connection and one wired-event registry per process. The\n// registry holds the EventDefs (not just names) so closeEventTransport can\n// reset their cache binding — otherwise the per-event cacheSubscribed latch\n// and this state would diverge across a restart in the same process.\ninterface TransportState\n{\n pubSubCache: PubSubCache | undefined;\n subscriber: PubSubClient | undefined;\n channelPrefix: string | undefined;\n wired: Map<string, WirableEvent>;\n\n /** Each Redis-backed cache's key closes waiting to be re-sent, so shutdown can drop them. */\n keyCloseDeferrals: Map<PubSubCache, KeyCloseDeferral>;\n}\n\nconst STATE_KEY = Symbol.for('@spfn/core:event-transport');\n\nconst state: TransportState = ((globalThis as any)[STATE_KEY] ??= {\n pubSubCache: undefined,\n subscriber: undefined,\n channelPrefix: undefined,\n wired: new Map<string, WirableEvent>(),\n keyCloseDeferrals: new Map<PubSubCache, KeyCloseDeferral>(),\n});\n\n// ── Deferred key closes ───────────────────────────────────────────\n// A `$control` close skipped by the open breaker, or whose publish failed, must\n// not be lost: no other pod has anything else that ends a revoked key's stream.\n\ninterface KeyCloseDeferral\n{\n /** Hold a close for re-sending (once per key id) and arm the retry. */\n defer(message: KeyCloseControlMessage): void;\n\n /** Re-send every held close through the cache's `publish`; one that fails again is held again. */\n flush(): Promise<void>;\n\n /** Drop every held close and the retry, for good. */\n dispose(): void;\n\n /** The held key ids, oldest first. */\n keyIds(): string[];\n}\n\nfunction createKeyCloseDeferral(resend: (message: KeyCloseControlMessage) => Promise<void>): KeyCloseDeferral\n{\n const pending = new Map<string, KeyCloseControlMessage>();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let flushing = false;\n let disposed = false;\n\n const arm = (): void =>\n {\n if (timer || disposed)\n {\n return;\n }\n\n timer = setTimeout(() =>\n {\n timer = undefined;\n void flush();\n }, KEY_CLOSE_RETRY_MS);\n timer.unref?.();\n };\n\n const disarm = (): void =>\n {\n clearTimeout(timer);\n timer = undefined;\n };\n\n const flush = async (): Promise<void> =>\n {\n // A resend that succeeds flushes again: the flag keeps that from recursing.\n if (flushing || pending.size === 0)\n {\n return;\n }\n\n flushing = true;\n const snapshot = [...pending.values()];\n pending.clear();\n for (const message of snapshot)\n {\n await resend(message);\n }\n flushing = false;\n\n if (pending.size > 0)\n {\n arm();\n }\n else\n {\n disarm();\n }\n };\n\n return {\n defer: (message) =>\n {\n if (disposed)\n {\n return;\n }\n\n pending.delete(message.keyId);\n pending.set(message.keyId, message);\n dropOldestPastCap(pending);\n throttledWarn('key-close-deferred', 'SSE key close deferred — re-sent when the pub/sub transport recovers', {\n channel: CONTROL_CHANNEL,\n pending: pending.size,\n });\n arm();\n },\n flush,\n dispose: () =>\n {\n disposed = true;\n disarm();\n pending.clear();\n },\n keyIds: () => [...pending.keys()],\n };\n}\n\nfunction dropOldestPastCap(pending: Map<string, KeyCloseControlMessage>): void\n{\n const dropped = pending.size - PENDING_KEY_CLOSE_CAP;\n if (dropped <= 0)\n {\n return;\n }\n\n [...pending.keys()].slice(0, dropped).forEach(keyId => pending.delete(keyId));\n throttledWarn('key-close-dropped', 'SSE key closes dropped — too many waiting to be re-sent', { dropped });\n}\n\n/**\n * The key ids a cache holds for re-sending, oldest first.\n *\n * @internal Exported for tests\n */\nexport function _pendingKeyCloses(cache: PubSubCache): string[]\n{\n return state.keyCloseDeferrals.get(cache)?.keyIds() ?? [];\n}\n\n/**\n * Resolve the channel prefix: explicit option → env override → default.\n */\nfunction resolveChannelPrefix(override?: string): string\n{\n return override ?? process.env.SPFN_SSE_CHANNEL_PREFIX ?? DEFAULT_CHANNEL_PREFIX;\n}\n\n/**\n * Build a {@link PubSubCache} backed by a Redis/Valkey client.\n *\n * One duplicated connection is created lazily and shared across every channel.\n * Publish failures are logged and swallowed — SSE is lossy (at-most-once), so a\n * dropped event during a Redis blip must never crash `emit` or kill a stream.\n * The warning names the channel alone, nothing derived from the error: a client's\n * error can echo the command, and with it any published payload (an event's as\n * much as a control message's).\n */\nexport function createRedisPubSubCache(\n client: PubSubClient,\n channelPrefix: string,\n onSubscriber?: (connection: PubSubClient) => void,\n): PubSubCache\n{\n let subscriber: PubSubClient | undefined;\n const handlers = new Map<string, MessageHandler>();\n // Circuit breaker: epoch-ms until which Redis is treated as down (skip publish).\n let degradedUntil = 0;\n\n const ensureSubscriber = (): PubSubClient =>\n {\n if (subscriber)\n {\n return subscriber;\n }\n\n // ioredis re-issues SUBSCRIBE after a reconnect (autoResubscribe, default\n // true). Don't pass it as a duplicate() option — Cluster.duplicate()'s\n // first positional arg is the startup-nodes array, so an options object\n // lands in the wrong slot and is silently dropped. The 'message' listener\n // is attached once and survives reconnects.\n subscriber = client.duplicate();\n onSubscriber?.(subscriber);\n\n subscriber.on('message', (channel: string, raw: string) =>\n {\n const handler = handlers.get(channel);\n if (!handler)\n {\n return;\n }\n\n let payload: unknown;\n try\n {\n payload = JSON.parse(raw);\n }\n catch\n {\n transportLogger.warn('Pub/sub message parse failed — dropped', { channel });\n\n return;\n }\n\n void handler(payload);\n });\n\n // ioredis auto-reconnects (and re-subscribes via autoResubscribe above);\n // surface the blip, throttled so a sustained outage's reconnect loop can't\n // flood logs.\n subscriber.on('error', (err: Error) =>\n {\n throttledWarn('subscriber-error', 'Pub/sub subscriber error', { error: err.message });\n });\n\n return subscriber;\n };\n\n // Deliver to this pod's own subscribers without the Redis round-trip — the\n // fallback when publish can't reach Redis. Mostly avoids double-delivery (a\n // failed publish usually means Redis never echoed back), but a reply-lost\n // race — Redis broadcast, then the publisher's reply dropped — can deliver\n // both here and via the echo. We accept that rare duplicate over dropping,\n // since SSE is lossy and a frozen stream is worse than a repeated chunk.\n // Same-pod subscribers are fed ONLY by the echo on the subscriber connection\n // (a separate socket from the write client). If it isn't connected, a successful\n // publish still won't reach this pod's own streams — so they need the local\n // fallback. A status-less subscriber (test mocks) is treated as healthy.\n const subscriberHealthy = (): boolean =>\n !!subscriber && (subscriber.status === undefined || subscriber.status === 'ready');\n\n const deliverLocal = (channel: string, message: unknown): void =>\n {\n const handler = handlers.get(channel);\n if (handler)\n {\n void handler(message);\n }\n };\n\n // Set below, once `cache` exists: the deferral re-sends through its `publish`.\n let keyCloses: KeyCloseDeferral;\n\n // A key close Redis did not take is held for re-sending; every other message stays dropped (lossy).\n const deferIfKeyClose = (name: string, message: unknown): void =>\n {\n if (name === CONTROL_CHANNEL && isKeyCloseMessage(message))\n {\n keyCloses.defer(message);\n }\n };\n\n const cache: PubSubCache = {\n publish: async (name: string, message: unknown): Promise<void> =>\n {\n const channel = channelPrefix + name;\n\n // `string | undefined`: JSON.stringify returns undefined (no throw) for a\n // top-level function/symbol, and throws for bigint/circular. Both mean\n // \"can't cross Redis\" — handle each as a local-only delivery.\n let serialized: string | undefined;\n try\n {\n // `?? null` so void-payload events serialize to \"null\" rather than\n // `undefined` (JSON.stringify(undefined) → undefined → publish breaks).\n serialized = JSON.stringify(message ?? null);\n }\n catch (err)\n {\n // Non-serializable payload (bigint, circular ref). Cross-pod is\n // impossible; deliver the live object locally (best effort — a\n // downstream JSON.stringify may re-throw, but better than dropping).\n transportLogger.warn('Pub/sub payload serialization failed — local delivery only', {\n channel,\n error: err instanceof Error ? err.message : String(err),\n });\n deliverLocal(channel, message ?? null);\n\n return;\n }\n\n if (serialized === undefined)\n {\n // Top-level function/symbol payload — JSON.stringify yields undefined.\n // Publishing that is meaningless and re-parsing it later would throw,\n // so deliver locally and stop (guards the \"publish never kills emit\").\n transportLogger.warn('Pub/sub payload not serializable (function/symbol) — local delivery only', {\n channel,\n });\n deliverLocal(channel, message ?? null);\n\n return;\n }\n\n // Skip Redis when the breaker is open (recent failure) or the client isn't\n // connected. This (a) bounds a sustained outage to one timeout per cooldown\n // instead of one per emit, and (b) avoids ioredis's offline queue buffering\n // the publish and re-sending it on reconnect — which would deliver the event\n // twice (local fallback now + echo later). Treat a status-less client (test\n // mocks) as ready so behavior is unchanged there.\n const ready = client.status === undefined || client.status === 'ready';\n const now = Date.now();\n if (!ready || (degradedUntil > 0 && now < degradedUntil))\n {\n // Not connected, or breaker open → skip Redis, deliver locally.\n deferIfKeyClose(name, message);\n deliverLocal(channel, JSON.parse(serialized));\n\n return;\n }\n\n // Healthy (degradedUntil === 0) → publish directly; concurrent emits all\n // fan out. Half-open (breaker tripped but cooldown elapsed) → this emit is\n // the single probe: arm the breaker for the probe's duration so CONCURRENT\n // emits fast-path to local instead of each launching their own 5s probe.\n if (degradedUntil > 0)\n {\n degradedUntil = now + PUBLISH_TIMEOUT_MS;\n }\n\n try\n {\n // Timeout-bound: a wedged socket would otherwise leave this pending\n // forever and hang `emit` (and the request that called it).\n await withTimeout(client.publish(channel, serialized), PUBLISH_TIMEOUT_MS, 'pub/sub publish');\n degradedUntil = 0; // success → close the breaker\n void keyCloses.flush(); // Redis is back: re-send the key closes it missed\n\n // Publish reached Redis (remote pods get it via their echo), but if THIS\n // pod's subscriber socket is down its echo won't arrive — cover same-pod\n // subscribers locally. No double-delivery: Redis doesn't buffer for a\n // disconnected subscriber, so the missed echo never arrives later.\n if (!subscriberHealthy())\n {\n throttledWarn('subscriber-down', 'Subscriber connection down — same-pod local fallback', {\n channel,\n });\n deliverLocal(channel, JSON.parse(serialized));\n }\n }\n catch\n {\n // Redis blip/timeout: open the breaker so subsequent emits fast-path to\n // local delivery instead of each paying the timeout. The cross-pod echo\n // won't arrive, so deliver to this pod's subscribers directly. Parse the\n // serialized copy so handlers see the SAME shape as the echo path (a JSON\n // round-trip), never the live mutable object. Throttled WARN — a sustained\n // outage emits one line per window, not one per chunk; the channel alone\n // (see the JSDoc above). Keyed by channel, so one channel's failure never\n // silences another's — a key close on `$control` behind an event's.\n degradedUntil = Date.now() + PUBLISH_BREAKER_COOLDOWN_MS;\n throttledWarn(`publish-failed:${channel}`, 'Pub/sub publish failed — local delivery only', { channel });\n deferIfKeyClose(name, message);\n deliverLocal(channel, JSON.parse(serialized));\n }\n },\n\n subscribe: async (name: string, handler: MessageHandler): Promise<void> =>\n {\n const channel = channelPrefix + name;\n const sub = ensureSubscriber();\n\n try\n {\n // Timeout-bound: at startup this is awaited before the HTTP listener,\n // so a wedged subscriber would otherwise hang the entire boot.\n await withTimeout(sub.subscribe(channel), SUBSCRIBE_TIMEOUT_MS, 'pub/sub subscribe');\n }\n catch (err)\n {\n // SUBSCRIBE failed. If nothing else is live on this connection it\n // may be half-open — drop the memo (and quit it) so a later retry\n // rebuilds a fresh subscriber instead of reusing a dead one.\n if (handlers.size === 0 && subscriber === sub)\n {\n subscriber = undefined;\n void sub.quit?.().catch(() => undefined);\n }\n\n throw err;\n }\n\n // Register only after a confirmed subscribe — never leave a handler\n // bound to a channel the broker hasn't acknowledged.\n handlers.set(channel, handler);\n },\n };\n\n keyCloses = createKeyCloseDeferral(message => cache.publish(CONTROL_CHANNEL, message));\n state.keyCloseDeferrals.set(cache, keyCloses);\n\n return cache;\n}\n\n// ── Subscription control channel (grant / revoke / key close across pods) ─────\n\n/**\n * The channel `grant` / `revoke` and key closes travel on, published through the same\n * {@link PubSubCache} as events (so `<prefix>$control` on Redis — same timeouts,\n * breaker and warn throttling, no second connection). Event channels are\n * `<prefix><event name>`; an event named `control` would collide with a bare\n * `control`, and `$` never appears in a dotted event name, so it can't.\n */\nconst CONTROL_CHANNEL = '$control';\n\n/** What one `grant` / `revoke` publishes. */\nexport interface SubscriptionControlMessage\n{\n kind: 'grant' | 'revoke';\n\n /** The event's wire name (`defineEvent(name)`) — router keys differ per pod's router. */\n event: string;\n\n subject: string;\n\n /** Which of the subject's connections it reaches; absent → every one. */\n match?: SubscriptionMatch;\n\n /** The publishing binding, so a pod skips its own echo (it applied the change locally first). */\n origin: string;\n}\n\n/** What one key close publishes: close the SSE streams that device key opened. */\nexport interface KeyCloseControlMessage\n{\n kind: 'close';\n keyId: string;\n\n /** The publishing binding, so a pod skips its own echo (it closed locally first). */\n origin: string;\n}\n\n/** Everything the control channel carries. */\nexport type ControlMessage = SubscriptionControlMessage | KeyCloseControlMessage;\n\n/** Applies one control message to the local registries of one event. */\nexport type SubscriptionControlApply = (\n kind: SubscriptionControlMessage['kind'],\n subject: string,\n match?: SubscriptionMatch,\n) => void;\n\ninterface ControlBinding\n{\n origin: string;\n events: Map<string, Set<SubscriptionControlApply>>;\n subscribing: Promise<void> | undefined;\n}\n\n/** One binding per cache: in production one per process, in tests one per simulated pod. */\nconst controlBindings = new WeakMap<PubSubCache, ControlBinding>();\n\nfunction ensureControlBinding(cache: PubSubCache): ControlBinding\n{\n let binding = controlBindings.get(cache);\n if (!binding)\n {\n binding = {\n origin: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,\n events: new Map(),\n subscribing: undefined,\n };\n controlBindings.set(cache, binding);\n }\n\n return binding;\n}\n\nfunction isControlMessage(message: unknown): message is ControlMessage\n{\n return isSubscriptionControlMessage(message) || isKeyCloseMessage(message);\n}\n\nfunction isKeyCloseMessage(message: unknown): message is KeyCloseControlMessage\n{\n const candidate = message as Partial<KeyCloseControlMessage> | null;\n\n return !!candidate\n && candidate.kind === 'close'\n && typeof candidate.keyId === 'string'\n && candidate.keyId !== ''\n && typeof candidate.origin === 'string';\n}\n\nfunction isSubscriptionControlMessage(message: unknown): message is SubscriptionControlMessage\n{\n const candidate = message as Partial<SubscriptionControlMessage> | null;\n\n return !!candidate\n && (candidate.kind === 'grant' || candidate.kind === 'revoke')\n && typeof candidate.event === 'string'\n && typeof candidate.subject === 'string'\n && typeof candidate.origin === 'string'\n && (candidate.match === undefined || isSubscriptionMatch(candidate.match));\n}\n\n/**\n * `{ query: { [field]: string } }` with at least one field, and nothing looser:\n * a bad match must not widen a grant, and an empty `query` matches every\n * connection of the subject.\n */\nexport function isSubscriptionMatch(match: unknown): match is SubscriptionMatch\n{\n const query = (match as Partial<SubscriptionMatch> | null)?.query;\n\n return typeof match === 'object'\n && typeof query === 'object'\n && query !== null\n && !Array.isArray(query)\n && Object.keys(query).length > 0\n && Object.values(query).every(value => typeof value === 'string');\n}\n\nfunction dispatchControl(binding: ControlBinding, message: unknown): void\n{\n if (!isControlMessage(message))\n {\n transportLogger.debug('Malformed subscription control message ignored');\n\n return;\n }\n\n if (message.origin === binding.origin)\n {\n return;\n }\n\n // Not an event: a key close reaches every handler of this pod, whatever events it serves.\n if (message.kind === 'close')\n {\n applyKeyClose(message.keyId);\n\n return;\n }\n\n const appliers = binding.events.get(message.event);\n if (!appliers)\n {\n transportLogger.debug('Subscription control for an event this pod does not serve ignored', {\n event: message.event,\n });\n\n return;\n }\n\n appliers.forEach(apply => apply(message.kind, message.subject, message.match));\n}\n\n/**\n * Let `grant` / `revoke` published by other pods reach an event on this pod.\n *\n * Called by `EventDef.useCache`. Subscribes the control channel once per cache;\n * a failed subscribe is warned and retried by the next event's bind — inbound\n * control then stays local until it succeeds, never failing `useCache`.\n */\nexport async function bindSubscriptionControl(\n cache: PubSubCache,\n eventName: string,\n apply: SubscriptionControlApply,\n): Promise<void>\n{\n const binding = ensureControlBinding(cache);\n const appliers = binding.events.get(eventName) ?? new Set<SubscriptionControlApply>();\n appliers.add(apply);\n binding.events.set(eventName, appliers);\n\n binding.subscribing ??= cache\n .subscribe(CONTROL_CHANNEL, (message) => dispatchControl(binding, message))\n .catch((err: unknown) =>\n {\n binding.subscribing = undefined;\n throttledWarn('control-subscribe-failed', 'Subscription control subscribe failed — grant/revoke from other pods not received', {\n error: err instanceof Error ? err.message : String(err),\n });\n });\n\n await binding.subscribing;\n}\n\n/**\n * Tell every other pod about a `grant` / `revoke` this pod already applied.\n * A failure is warned (throttled) and swallowed: the change stays local, and\n * the next connect re-reads permission anyway.\n */\nexport async function publishSubscriptionControl(\n cache: PubSubCache,\n kind: SubscriptionControlMessage['kind'],\n eventName: string,\n subject: string,\n match?: SubscriptionMatch,\n): Promise<void>\n{\n const message: SubscriptionControlMessage = {\n kind,\n event: eventName,\n subject,\n ...(match ? { match } : {}),\n origin: ensureControlBinding(cache).origin,\n };\n\n await cache.publish(CONTROL_CHANNEL, message).catch(() =>\n {\n // Nothing from the error: an app-supplied cache's error can echo the command, and with it the subject.\n throttledWarn('control-publish-failed', 'Subscription control publish failed — applied on this pod only', {\n event: eventName,\n });\n });\n}\n\n/**\n * Tell every other pod to close the SSE streams of a device key this pod\n * already closed locally. A failure is warned (throttled) and swallowed. On a\n * cache from {@link createRedisPubSubCache}, a close Redis did not take is held\n * and re-sent after the breaker cooldown or with the next successful publish;\n * on any other cache it stays local. Neither the key id nor the error's subject is logged.\n */\nexport async function publishKeyClose(cache: PubSubCache, keyId: string): Promise<void>\n{\n const message: KeyCloseControlMessage = { kind: 'close', keyId, origin: ensureControlBinding(cache).origin };\n\n await cache.publish(CONTROL_CHANNEL, message).catch(() =>\n {\n // Nothing from the error: its message and name can both echo the command, and with it the key id.\n throttledWarn('key-close-publish-failed', 'SSE key close publish failed — closed on this pod only', {\n channel: CONTROL_CHANNEL,\n });\n });\n}\n\n/**\n * The pub/sub cache the event transport publishes on: the one wiring already\n * resolved, else resolved now from `@spfn/core/cache` (`getCache()`); undefined\n * without a cache. For a publisher that may run on a pod serving no events.\n */\nexport async function resolveEventPubSubCache(): Promise<PubSubCache | undefined>\n{\n return state.pubSubCache ?? await resolvePubSubCache();\n}\n\n/**\n * Options controlling event cache transport wiring.\n */\nexport interface WireEventCacheOptions\n{\n /**\n * Force in-process mode even when a cache is configured.\n * @default true (auto: Redis when CACHE_URL is set, else in-process)\n */\n multiInstance?: boolean;\n\n /**\n * Channel prefix override (env `SPFN_SSE_CHANNEL_PREFIX`, else `spfn:sse:`).\n * Use distinct prefixes to isolate apps/tenants sharing one Redis.\n */\n channelPrefix?: string;\n}\n\n/**\n * Wire every event in a router to the shared Redis pub/sub cache.\n *\n * Returns the transport that ended up active. Idempotent per event name across\n * routers (an event shared by the SSE and WS routers is wired once), so it is\n * safe to call for both `.events()` and `.websockets()`.\n */\nexport async function wireEventRouterCache(\n router: EventRouterBase<any>,\n options: WireEventCacheOptions = {},\n): Promise<'in-process' | 'redis'>\n{\n // Logged at INFO (not debug): operators must be able to confirm from prod logs\n // whether cross-pod fan-out is actually live, and whether any event degraded.\n if (options.multiInstance === false)\n {\n // A sibling router may have already wired shared events to Redis — this flag\n // is process-global and can't un-wire them, so be honest about that.\n const events = router.events as Record<string, WirableEvent>;\n const stillRedis = Object.keys(events).filter(k => state.wired.has(events[k].name)).length;\n\n if (stillRedis > 0)\n {\n transportLogger.warn('multiInstance:false has no effect on events already wired to redis by another router', {\n redisWired: stillRedis,\n });\n }\n else\n {\n transportLogger.info('Event transport: in-process (multiInstance disabled)');\n }\n\n return 'in-process';\n }\n\n const pubSubCache = await resolvePubSubCache(options.channelPrefix);\n if (!pubSubCache)\n {\n transportLogger.info('Event transport: in-process (no cache configured)');\n\n return 'in-process';\n }\n\n const events = router.events as Record<string, WirableEvent>;\n let wired = 0;\n let degraded = 0;\n let alreadyWired = 0;\n\n for (const key of Object.keys(events))\n {\n const event = events[key];\n if (state.wired.has(event.name))\n {\n alreadyWired++; // e.g. an event shared by the SSE and WS routers\n continue;\n }\n\n try\n {\n await event.useCache(pubSubCache);\n state.wired.set(event.name, event);\n wired++;\n }\n catch (err)\n {\n // Degrade this event to in-process rather than aborting startup — a\n // transient SUBSCRIBE failure must not crash the pod. Not marked\n // wired, so a later wiring pass can retry it.\n degraded++;\n transportLogger.warn('Event cache wiring failed — staying in-process for this event', {\n event: event.name,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // Every event degraded (and none were already wired) → fan-out is effectively\n // dead for this router; report in-process honestly instead of a false 'redis'.\n if (wired + alreadyWired === 0)\n {\n transportLogger.warn('Event transport: redis configured but no event wired — running in-process', {\n total: Object.keys(events).length,\n degraded,\n });\n\n return 'in-process';\n }\n\n transportLogger.info('Event transport: redis (cross-pod fan-out)', {\n total: Object.keys(events).length,\n wired,\n degraded,\n alreadyWired,\n });\n\n return 'redis';\n}\n\n/**\n * Resolve (and cache) the shared pub/sub cache from the global cache instance.\n * Returns undefined when no cache is available.\n */\nasync function resolvePubSubCache(channelPrefix?: string): Promise<PubSubCache | undefined>\n{\n const resolvedPrefix = resolveChannelPrefix(channelPrefix);\n\n if (state.pubSubCache)\n {\n // The transport (and its prefix) is process-global, resolved once on the\n // first wiring. Warn if a later router (.websockets() after .events())\n // asks for a different prefix — it's silently ignored, not applied.\n if (state.channelPrefix !== resolvedPrefix)\n {\n transportLogger.warn('Conflicting channelPrefix ignored — the transport prefix is process-global (first wiring wins).', {\n active: state.channelPrefix,\n ignored: resolvedPrefix,\n });\n }\n\n return state.pubSubCache;\n }\n\n let client: PubSubClient | undefined;\n try\n {\n const { getCache } = await import('@spfn/core/cache');\n client = getCache() as PubSubClient | undefined;\n }\n catch\n {\n // Cache module unavailable — fall back to in-process.\n return undefined;\n }\n\n if (!client)\n {\n return undefined;\n }\n\n // Cross-app isolation depends on the channel prefix. The default is shared by\n // every SPFN app, so warn (once, on first resolve) when running on it — apps\n // sharing one Redis with colliding event names would otherwise leak payloads.\n if (!channelPrefix && !process.env.SPFN_SSE_CHANNEL_PREFIX)\n {\n transportLogger.warn(\n 'Event fan-out is on the default channel prefix (spfn:sse:). Apps sharing one '\n + 'Redis must set channelPrefix or SPFN_SSE_CHANNEL_PREFIX to avoid cross-app leakage.',\n );\n }\n\n state.channelPrefix = resolvedPrefix;\n state.pubSubCache = createRedisPubSubCache(\n client,\n resolvedPrefix,\n (connection) =>\n {\n state.subscriber = connection;\n },\n );\n\n return state.pubSubCache;\n}\n\n/**\n * Close the shared pub/sub subscriber connection and reset transport state.\n *\n * Called during graceful shutdown — the subscriber is a `duplicate()` of the\n * cache write connection and is not tracked by the cache manager, so it must be\n * quit explicitly (before `closeCache`). No-op when no subscriber was opened.\n *\n * Also resets each wired EventDef's cache binding. EventDefs are module\n * singletons that outlive a server instance; without this, a second server start\n * in the same process (tests, hot-reload) hits the `cacheSubscribed` latch, never\n * rebinds, and silently runs with cross-pod fan-out dead.\n */\nexport async function closeEventTransport(): Promise<void>\n{\n const subscriber = state.subscriber;\n\n for (const event of state.wired.values())\n {\n event._resetCache?.();\n }\n\n // A held close would otherwise be re-sent by a transport that is shutting down.\n state.keyCloseDeferrals.forEach(deferral => deferral.dispose());\n state.keyCloseDeferrals.clear();\n\n state.subscriber = undefined;\n state.pubSubCache = undefined;\n state.channelPrefix = undefined;\n state.wired.clear();\n\n if (!subscriber?.quit)\n {\n return;\n }\n\n // Bound the quit: a wedged connection (broken socket, no FIN) must not stall\n // shutdown past this timeout — the process exits and the OS reclaims the socket.\n await Promise.race([\n subscriber.quit().catch(() => undefined),\n new Promise<void>((resolve) =>\n {\n const timer = setTimeout(resolve, SUBSCRIBER_QUIT_TIMEOUT_MS);\n timer.unref?.();\n }),\n ]);\n}\n","/**\n * `closeSSEConnectionsByKey` — the public close, local and cross-pod\n *\n * Imports both halves: `key-close` (this process) and the cache transport\n * (every other pod). Neither imports this module, so `check:circular` stays clean.\n */\n\nimport { publishKeyClose, resolveEventPubSubCache } from '../cache-transport';\nimport { applyKeyClose } from './key-close';\n\n/**\n * Close every SSE stream opened with a token minted for device key `keyId`,\n * on this pod and — when a cache (`CACHE_URL`) is configured — on every other\n * pod, through the event transport's control channel. A connect of that key\n * still in admission never opens. Untagged streams (no key) are never closed.\n *\n * Resolves once the local close is done and the publish settled; a failed\n * publish is warned and swallowed, never thrown. `@spfn/auth` calls it after\n * a key revocation commits. A close this pod cannot publish while the\n * transport's breaker is open, or whose publish fails, is re-sent when the\n * breaker cooldown ends and whenever the next publish succeeds; while it is\n * pending, the key's streams on other pods stay open. A held close is lost\n * only when the Redis outage outlasts this pod (its shutdown disposes held\n * closes) or when more than 1000 closes are held (the oldest are dropped);\n * those streams then stay open until they disconnect, and their next token\n * mint is refused.\n *\n * @example\n * ```typescript\n * import { closeSSEConnectionsByKey } from '@spfn/core/event/sse';\n * import { onAfterCommit } from '@spfn/core/db';\n *\n * onAfterCommit(() => closeSSEConnectionsByKey(revoked.keyId));\n * ```\n */\nexport async function closeSSEConnectionsByKey(keyId: string): Promise<void>\n{\n applyKeyClose(keyId);\n\n const cache = await resolveEventPubSubCache();\n if (cache)\n {\n await publishKeyClose(cache, keyId);\n }\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { W as WSRouterDef, a as WSSubscribeOptions, b as WSUnsubscribe, c as WSConnectionState, d as WSClientConfig } from '../../types-
|
|
1
|
+
import { W as WSRouterDef, a as WSSubscribeOptions, b as WSUnsubscribe, c as WSConnectionState, d as WSClientConfig } from '../../types-DH6d2eOb.js';
|
|
2
2
|
import 'hono';
|
|
3
3
|
import '../../router-CX5uVjmU.js';
|
|
4
4
|
import '@sinclair/typebox';
|
|
5
|
-
import '../../token-manager-
|
|
5
|
+
import '../../token-manager-CvOsAbnK.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* WebSocket Client
|
package/dist/event/ws/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as EventDef } from '../../router-CX5uVjmU.js';
|
|
2
|
-
import { e as WSMessageHandlers, W as WSRouterDef, f as WSHandlerConfig } from '../../types-
|
|
3
|
-
export { g as WSAuthConfig, d as WSClientConfig, c as WSConnectionState, h as WSEventHandlers, i as WSHandlerAuthConfig, j as WSMessageContext, k as WSMessageHandlerFn, l as WSRawConnection, a as WSSubscribeOptions, b as WSUnsubscribe } from '../../types-
|
|
2
|
+
import { e as WSMessageHandlers, W as WSRouterDef, f as WSHandlerConfig } from '../../types-DH6d2eOb.js';
|
|
3
|
+
export { g as WSAuthConfig, d as WSClientConfig, c as WSConnectionState, h as WSEventHandlers, i as WSHandlerAuthConfig, j as WSMessageContext, k as WSMessageHandlerFn, l as WSRawConnection, a as WSSubscribeOptions, b as WSUnsubscribe } from '../../types-DH6d2eOb.js';
|
|
4
4
|
import { Server } from 'node:http';
|
|
5
|
-
import { S as SSETokenManager } from '../../token-manager-
|
|
5
|
+
import { S as SSETokenManager } from '../../token-manager-CvOsAbnK.js';
|
|
6
6
|
import '@sinclair/typebox';
|
|
7
7
|
import 'hono';
|
|
8
8
|
|
package/dist/server/index.d.ts
CHANGED
|
@@ -7,15 +7,15 @@ import { OnErrorContext, ProxyGuardConfig, RateLimitOptions } from '@spfn/core/m
|
|
|
7
7
|
import { SafeFetchPolicy } from '@spfn/core/security';
|
|
8
8
|
import { c as JobRouter, f as BossOptions } from '../boss-BiDCbbAQ.js';
|
|
9
9
|
import { E as EventRouterDef, a as EventDef } from '../router-CX5uVjmU.js';
|
|
10
|
-
import { d as SSEHandlerConfig, e as SSEAuthConfig } from '../types-
|
|
11
|
-
import { W as WSRouterDef, f as WSHandlerConfig, e as WSMessageHandlers, g as WSAuthConfig } from '../types-
|
|
10
|
+
import { d as SSEHandlerConfig, e as SSEAuthConfig } from '../types-BrO7j6mR.js';
|
|
11
|
+
import { W as WSRouterDef, f as WSHandlerConfig, e as WSMessageHandlers, g as WSAuthConfig } from '../types-DH6d2eOb.js';
|
|
12
12
|
import { DatabaseProvider, MigrationStatus, MigrationStatusDb } from '@spfn/core/db';
|
|
13
13
|
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
14
14
|
import { Static } from '@sinclair/typebox';
|
|
15
15
|
import { R as RouteDef } from '../route-builder-DRW-MSCJ.js';
|
|
16
16
|
import { a as EnvRegistry } from '../registry-C2KQCh0m.js';
|
|
17
17
|
import 'pg-boss';
|
|
18
|
-
import '../token-manager-
|
|
18
|
+
import '../token-manager-CvOsAbnK.js';
|
|
19
19
|
import '../define-middleware-CVKgqo8S.js';
|
|
20
20
|
import 'hono/utils/http-status';
|
|
21
21
|
import '../route/types.js';
|