@spfn/core 0.2.0-beta.9 → 0.3.0-beta.10
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/LICENSE +1 -1
- package/README.md +693 -279
- package/dist/app-config/index.d.ts +96 -0
- package/dist/app-config/index.js +58 -0
- package/dist/app-config/index.js.map +1 -0
- package/dist/authz/index.d.ts +34 -0
- package/dist/authz/index.js +430 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/boss-BiDCbbAQ.d.ts +337 -0
- package/dist/cache/index.js +42 -30
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +121 -13
- package/dist/codegen/index.js +212 -15
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +576 -30
- package/dist/config/index.js +136 -17
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +221 -0
- package/dist/contract/index.js +558 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/db/index.d.ts +704 -94
- package/dist/db/index.js +1105 -260
- package/dist/db/index.js.map +1 -1
- package/dist/define-middleware-CVKgqo8S.d.ts +167 -0
- package/dist/env/index.d.ts +26 -2
- package/dist/env/index.js +15 -5
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +26 -19
- package/dist/env/loader.js +55 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.js +40 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +33 -3
- package/dist/event/index.js +24 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +55 -3
- package/dist/event/sse/client.js +164 -45
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +12 -5
- package/dist/event/sse/index.js +280 -32
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +59 -0
- package/dist/event/ws/client.js +273 -0
- package/dist/event/ws/client.js.map +1 -0
- package/dist/event/ws/index.d.ts +94 -0
- package/dist/event/ws/index.js +272 -0
- package/dist/event/ws/index.js.map +1 -0
- package/dist/job/index.d.ts +48 -3
- package/dist/job/index.js +265 -74
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -12
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +352 -10
- package/dist/middleware/index.js +1079 -13
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +39 -3
- package/dist/nextjs/index.js +82 -29
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +69 -52
- package/dist/nextjs/server.js +140 -60
- package/dist/nextjs/server.js.map +1 -1
- package/dist/ops/index.d.ts +208 -0
- package/dist/ops/index.js +800 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/route/index.d.ts +10 -694
- package/dist/route/index.js +112 -23
- package/dist/route/index.js.map +1 -1
- package/dist/route-builder-2ani2jEI.d.ts +553 -0
- package/dist/router-DJdpwuB6.d.ts +127 -0
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +619 -31
- package/dist/server/index.js +2583 -360
- package/dist/server/index.js.map +1 -1
- package/dist/token-manager-BT5EnUAR.d.ts +278 -0
- package/dist/types-2AbaW4Ie.d.ts +205 -0
- package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
- package/dist/types-ClQVomgV.d.ts +115 -0
- package/dist/types-ZQODsBft.d.ts +282 -0
- package/docs/file-upload.md +195 -333
- package/package.json +250 -208
- package/src/cache/README.md +330 -0
- package/src/codegen/README.md +516 -0
- package/src/config/README.md +326 -0
- package/src/contract/README.md +326 -0
- package/src/db/README.md +589 -0
- package/src/db/manager/README.md +500 -0
- package/src/db/schema/README.md +354 -0
- package/src/db/transaction/README.md +822 -0
- package/src/env/README.md +651 -0
- package/src/errors/README.md +429 -0
- package/src/event/README.md +741 -0
- package/src/job/README.md +613 -0
- package/src/logger/README.md +321 -0
- package/src/middleware/README.md +634 -0
- package/src/nextjs/README.md +608 -0
- package/src/route/README.md +738 -0
- package/src/security/README.md +100 -0
- package/src/server/README.md +704 -0
- package/dist/boss-DI1r4kTS.d.ts +0 -244
- package/dist/router-Di7ENoah.d.ts +0 -151
- package/dist/types-B-e_f2dQ.d.ts +0 -121
- package/docs/cache.md +0 -133
- package/docs/codegen.md +0 -74
- package/docs/database.md +0 -346
- package/docs/entity.md +0 -539
- package/docs/env.md +0 -477
- package/docs/errors.md +0 -319
- package/docs/event.md +0 -116
- package/docs/job.md +0 -131
- package/docs/logger.md +0 -108
- package/docs/middleware.md +0 -337
- package/docs/nextjs.md +0 -241
- package/docs/repository.md +0 -496
- package/docs/route.md +0 -497
- package/docs/server.md +0 -307
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors/error-registry.ts","../../src/errors/serializable-error.ts","../../src/errors/http-errors.ts","../../src/errors/database-errors.ts","../../src/errors/index.ts","../../src/authz/index.ts"],"names":[],"mappings":";;;AAmDO,IAAM,aAAA,GAAN,MAAM,cAAA,CACb;AAAA,EACY,MAAA,uBAAa,GAAA,EAA0C;AAAA,EAE/D,YAAY,aAAA,EACZ;AACI,IAAA,IAAI,aAAA,EACJ;AACI,MAAA,KAAA,MAAW,SAAS,aAAA,EACpB;AACI,QAAA,IAAI,iBAAiB,cAAA,EACrB;AACI,UAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,QACrB,CAAA,MAAA,IACS,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAC5B;AACI,UAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,QACrB,CAAA,MAEA;AACI,UAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,QACrB;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,OAAO,KAAA,EACP;AACI,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EACvB;AACI,MAAA,KAAA,MAAW,cAAc,KAAA,EACzB;AACI,QAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,IAAA,EAAM,UAAU,CAAA;AAAA,MAC/C;AAAA,IACJ,CAAA,MAEA;AACI,MAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,IACrC;AAEA,IAAA,OAAO,IAAA;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,QAAA,EACP;AACI,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,UAAU,CAAA,IAAK,SAAS,MAAA,EAC1C;AACI,MAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAA,EAAM,UAAU,CAAA;AAAA,IACpC;AAEA,IAAA,OAAO,IAAA;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,IAAA,EACJ;AACI,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,YAAY,IAAA,EACZ;AACI,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,KAAK,MAAM,CAAA;AAE9C,IAAA,IAAI,CAAC,UAAA,EACL;AACI,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,IAAA,CAAK,MAAM,CAAA,CAAE,CAAA;AAAA,IACxD;AAOA,IAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAO,OAAO,SAAA,EAAW,GAAG,QAAO,GAAI,IAAA;AAEvD,IAAA,OAAO,IAAI,WAAW,MAAM,CAAA;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,IAAA,EACf;AACI,IAAA,IAAI,CAAC,KAAK,MAAA,IAAU,CAAC,KAAK,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA,EACzC;AACI,MAAA,OAAO,IAAA;AAAA,IACX;AAEA,IAAA,IACA;AACI,MAAA,OAAO,IAAA,CAAK,YAAY,IAAW,CAAA;AAAA,IACvC,CAAA,CAAA,MAEA;AACI,MAAA,OAAO,IAAA;AAAA,IACX;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAA,GACA;AACI,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAAA,EACxC;AACJ,CAAA;ACrKA,IAAM,yCAAyB,IAAI,GAAA,CAAI,CAAC,QAAA,EAAU,SAAA,EAAW,OAAO,CAAC,CAAA;AAQrE,IAAM,yCAAyB,IAAI,GAAA,CAAI,CAAC,OAAA,EAAS,aAAA,EAAe,MAAM,CAAC,CAAA;AASvE,SAAS,iBAAA,CAAkB,WAAmB,GAAA,EAC9C;AACI,EAAA,MAAM,MAAA,GAAS,CAAA,EAAG,SAAS,CAAA,8BAAA,EAAiC,GAAG,CAAA,2BAAA,EAC9B,CAAC,GAAG,sBAAsB,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,mBAAA,CAAA;AAEvE,EAAA,IAAI,uBAAuB,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,IAAY,OAAO,CAAA,EAC9D;AACI,IAAA,MAAM,IAAI,MAAM,MAAM,CAAA;AAAA,EAC1B;AAEA,EAAA,MAAA,CAAO,KAAA,CAAM,CAAA,oBAAA,EAAuB,MAAM,CAAA,wCAAA,CAA0C,CAAA;AACxF;AA+BO,IAAe,iBAAA,GAAf,cAAyC,KAAA,CAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcI,MAAA,GACA;AACI,IAAA,MAAM,IAAA,GAAwB;AAAA,MAC1B,MAAA,EAAQ,KAAK,WAAA,CAAY,IAAA;AAAA,MACzB,SAAS,IAAA,CAAK;AAAA,KAClB;AAGA,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAClC;AAEI,MAAA,IAAI,GAAA,KAAQ,MAAA,IAAU,GAAA,KAAQ,OAAA,IAAW,QAAQ,YAAA,EACjD;AACI,QAAA;AAAA,MACJ;AAEA,MAAA,IAAI,sBAAA,CAAuB,GAAA,CAAI,GAAG,CAAA,EAClC;AACI,QAAA,iBAAA,CAAkB,IAAA,CAAK,WAAA,CAAY,IAAA,EAAM,GAAG,CAAA;AAC5C,QAAA;AAAA,MACJ;AAEA,MAAA,IAAA,CAAK,GAAG,CAAA,GAAK,IAAA,CAA4C,GAAG,CAAA;AAAA,IAChE;AAEA,IAAA,OAAO,IAAA;AAAA,EACX;AACJ,CAAA;;;ACxHO,IAAM,SAAA,GAAN,cAAwB,iBAAA,CAC/B;AAAA,EACoB,UAAA;AAAA,EACT,OAAA;AAAA,EAEP,YAAY,IAAA,EAKZ;AACI,IAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAElB,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AACZ,IAAA,IAAA,CAAK,aAAa,IAAA,CAAK,UAAA;AAEvB,IAAA,IAAI,KAAK,OAAA,EACT;AACI,MAAA,IAAA,CAAK,UAAU,IAAA,CAAK,OAAA;AAAA,IACxB;AAEA,IAAA,KAAA,CAAM,iBAAA,CAAkB,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EAClD;AACJ,CAAA;AAOO,IAAM,eAAA,GAAN,cAA8B,SAAA,CACrC;AAAA,EACI,WAAA,CAAY,IAAA,GAA4D,EAAC,EACzE;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,aAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EAChB;AACJ,CAAA;AAQO,IAAM,eAAA,GAAN,cAA8B,SAAA,CACrC;AAAA,EACI,MAAA;AAAA,EAEA,YAAY,IAAA,EAKZ;AACI,IAAA,KAAA,CAAM;AAAA,MACF,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAEZ,IAAA,IAAI,KAAK,MAAA,EACT;AACI,MAAA,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA;AAAA,IACvB;AAAA,EACJ;AACJ,CAAA;AAOO,IAAM,iBAAA,GAAN,cAAgC,SAAA,CACvC;AAAA,EACI,WAAA,CAAY,IAAA,GAA4D,EAAC,EACzE;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,yBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,cAAA,GAAN,cAA6B,SAAA,CACpC;AAAA,EACI,WAAA,CAAY,IAAA,GAA4D,EAAC,EACzE;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,kBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,aAAA,GAAN,cAA4B,SAAA,CACnC;AAAA,EACI,QAAA;AAAA,EAEA,WAAA,CAAY,IAAA,GAA+E,EAAC,EAC5F;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,oBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AAEZ,IAAA,IAAI,KAAK,QAAA,EACT;AACI,MAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AAAA,IACzB;AAAA,EACJ;AACJ,CAAA;AAOO,IAAM,aAAA,GAAN,cAA4B,SAAA,CACnC;AAAA,EACI,WAAA,CAAY,IAAA,GAA4D,EAAC,EACzE;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,mBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,SAAA,GAAN,cAAwB,SAAA,CAC/B;AAAA,EACI,QAAA;AAAA,EAEA,WAAA,CAAY,IAAA,GAA+E,EAAC,EAC5F;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,8BAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AAEZ,IAAA,IAAI,KAAK,QAAA,EACT;AACI,MAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AAAA,IACzB;AAAA,EACJ;AACJ,CAAA;AAOO,IAAM,oBAAA,GAAN,cAAmC,SAAA,CAC1C;AAAA,EACI,UAAA;AAAA,EAEA,WAAA,CAAY,IAAA,GAIR,EAAC,EACL;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,mBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AAEZ,IAAA,IAAI,KAAK,UAAA,EACT;AACI,MAAA,IAAA,CAAK,aAAa,IAAA,CAAK,UAAA;AAAA,IAC3B;AAAA,EACJ;AACJ,CAAA;AAOO,IAAM,mBAAA,GAAN,cAAkC,SAAA,CACzC;AAAA,EACI,WAAA,CAAY,IAAA,GAA4D,EAAC,EACzE;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,uBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,yBAAA,GAAN,cAAwC,SAAA,CAC/C;AAAA,EACI,SAAA;AAAA,EACA,cAAA;AAAA,EAEA,WAAA,CAAY,IAAA,GAKR,EAAC,EACL;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,wBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,2BAAA;AAEZ,IAAA,IAAI,KAAK,SAAA,EACT;AACI,MAAA,IAAA,CAAK,YAAY,IAAA,CAAK,SAAA;AAAA,IAC1B;AAEA,IAAA,IAAI,KAAK,cAAA,EACT;AACI,MAAA,IAAA,CAAK,iBAAiB,IAAA,CAAK,cAAA;AAAA,IAC/B;AAAA,EACJ;AACJ,CAAA;AAOO,IAAM,wBAAA,GAAN,cAAuC,SAAA,CAC9C;AAAA,EACI,WAAA,CAAY,IAAA,GAA4D,EAAC,EACzE;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,sBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,0BAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,uBAAA,GAAN,cAAsC,SAAA,CAC7C;AAAA,EACI,UAAA;AAAA,EAEA,WAAA,CAAY,IAAA,GAIR,EAAC,EACL;AACI,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,KAAK,OAAA,IAAW,qBAAA;AAAA,MACzB,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,GAAO,yBAAA;AAEZ,IAAA,IAAI,KAAK,UAAA,EACT;AACI,MAAA,IAAA,CAAK,aAAa,IAAA,CAAK,UAAA;AAAA,IAC3B;AAAA,EACJ;AACJ,CAAA;;;AC5TO,IAAM,aAAA,GAAN,cACK,iBAAA,CACZ;AAAA,EACoB,UAAA;AAAA,EACA,OAAA;AAAA,EAEhB,YAAY,IAAA,EAKZ;AACI,IAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAClB,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AACZ,IAAA,IAAA,CAAK,UAAA,GAAa,KAAK,UAAA,IAAc,GAAA;AACrC,IAAA,IAAA,CAAK,UAAU,IAAA,CAAK,OAAA;AACpB,IAAA,KAAA,CAAM,iBAAA,CAAkB,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAI,QAAA,GACJ;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AACJ,CAAA;AAOO,IAAM,eAAA,GAAN,cAA8B,aAAA,CACrC;AAAA,EACI,YAAY,IAAA,EACZ;AACI,IAAA,KAAA,CAAM,EAAE,SAAS,IAAA,CAAK,OAAA,EAAS,YAAY,GAAA,EAAK,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,CAAA;AACvE,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,UAAA,GAAN,cAAyB,aAAA,CAChC;AAAA,EACI,YAAY,IAAA,EAKZ;AACI,IAAA,KAAA,CAAM;AAAA,MACF,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,UAAA,EAAY,KAAK,UAAA,IAAc,GAAA;AAAA,MAC/B,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AACD,IAAA,IAAA,CAAK,IAAA,GAAO,YAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,mBAAA,GAAN,cAAkC,UAAA,CACzC;AAAA,EACoB,QAAA;AAAA,EACA,EAAA;AAAA,EAEhB,YAAY,IAAA,EACZ;AACI,IAAA,KAAA,CAAM;AAAA,MACF,SAAS,CAAA,EAAG,IAAA,CAAK,QAAQ,CAAA,SAAA,EAAY,KAAK,EAAE,CAAA,UAAA,CAAA;AAAA,MAC5C,UAAA,EAAY,GAAA;AAAA,MACZ,SAAS,EAAE,QAAA,EAAU,KAAK,QAAA,EAAU,EAAA,EAAI,KAAK,EAAA;AAAG,KACnD,CAAA;AACD,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AACZ,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AACrB,IAAA,IAAA,CAAK,KAAK,IAAA,CAAK,EAAA;AAAA,EACnB;AAAA;AAAA,EAGA,IAAa,QAAA,GACb;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AACJ,CAAA;AAQO,IAAM,wBAAA,GAAN,cAAuC,UAAA,CAC9C;AAAA,EACI,YAAY,IAAA,EACZ;AACI,IAAA,KAAA,CAAM,EAAE,SAAS,IAAA,CAAK,OAAA,EAAS,YAAY,GAAA,EAAK,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,CAAA;AACvE,IAAA,IAAA,CAAK,IAAA,GAAO,0BAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,gBAAA,GAAN,cAA+B,aAAA,CACtC;AAAA,EACI,YAAY,IAAA,EAKZ;AACI,IAAA,KAAA,CAAM;AAAA,MACF,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,UAAA,EAAY,KAAK,UAAA,IAAc,GAAA;AAAA,MAC/B,SAAS,IAAA,CAAK;AAAA,KACjB,CAAA;AACD,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,aAAA,GAAN,cAA4B,gBAAA,CACnC;AAAA,EACI,YAAY,IAAA,EACZ;AACI,IAAA,KAAA,CAAM,EAAE,SAAS,IAAA,CAAK,OAAA,EAAS,YAAY,GAAA,EAAK,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,CAAA;AACvE,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AAAA,EAChB;AACJ,CAAA;AAOO,IAAM,mBAAA,GAAN,cAAkC,UAAA,CACzC;AAAA,EACoB,KAAA;AAAA,EACA,KAAA;AAAA,EAEhB,YAAY,IAAA,EACZ;AAII,IAAA,KAAA,CAAM;AAAA,MACF,OAAA,EAAS,CAAA,EAAG,IAAA,CAAK,KAAK,CAAA,eAAA,CAAA;AAAA,MACtB,UAAA,EAAY,GAAA;AAAA,MACZ,OAAA,EAAS,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA;AAAM,KAChC,CAAA;AACD,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AACZ,IAAA,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA;AAClB,IAAA,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA;AAAA,EACtB;AAAA;AAAA,EAGA,IAAa,QAAA,GACb;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AACJ,CAAA;;;ACpHO,IAAM,aAAA,GAAgB,IAAI,aAAA,EAAc;AAG/C,aAAA,CAAc,MAAA,CAAO;AAAA,EACjB,SAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,oBAAA;AAAA,EACA,yBAAA;AAAA,EACA,wBAAA;AAAA,EACA,mBAAA;AAAA,EACA;AACJ,CAAC,CAAA;AAGD,aAAA,CAAc,MAAA,CAAO;AAAA,EACjB,aAAA;AAAA,EACA,eAAA;AAAA,EACA,UAAA;AAAA,EACA,mBAAA;AAAA,EACA,wBAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA;AACJ,CAAC,CAAA;;;ACrEM,SAAS,YAAA,CACZ,QAAA,EACA,OAAA,EACA,OAAA,GAAkC,EAAC,EAEvC;AACI,EAAA,MAAM,QAAA,GAAY,QAAQ,QAAA,IAAY,SAAA;AACtC,EAAA,MAAM,KAAA,GAAQ,QAAA,GAAY,QAAA,CAA0C,QAAQ,CAAA,GAAI,MAAA;AAEhF,EAAA,IAAI,CAAC,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,IAAa,MAAA,CAAO,KAAK,CAAA,KAAM,MAAA,CAAO,OAAO,CAAA,EAC1F;AACI,IAAA,MAAM,IAAI,aAAA,CAAc,EAAE,SAAS,OAAA,CAAQ,OAAA,IAAW,aAAa,CAAA;AAAA,EACvE;AAEA,EAAA,OAAO,QAAA;AACX","file":"index.js","sourcesContent":["/**\n * Error Registry\n *\n * Central registry for serializable error types.\n * Enables automatic error deserialization on the client side.\n */\n\nimport type { SerializableError } from './serializable-error';\n\n/**\n * Error constructor type\n */\nexport type SerializableErrorConstructor = new (data: any) => SerializableError;\n\n/**\n * Input type for ErrorRegistry constructor\n */\nexport type ErrorRegistryInput =\n | SerializableErrorConstructor\n | SerializableErrorConstructor[]\n | ErrorRegistry;\n\n/**\n * Error registry for serialization/deserialization\n *\n * @example\n * ```typescript\n * // Create registry with initial errors\n * const registry = new ErrorRegistry([\n * errorRegistry, // Another ErrorRegistry\n * PaymentFailedError, // Single error class\n * [RefundError, OrderError], // Array of error classes\n * ]);\n *\n * // Or create empty and append\n * const registry = new ErrorRegistry();\n * registry\n * .append(ValidationError)\n * .append([PaymentFailedError, RefundError]);\n *\n * // Deserialize from JSON\n * const error = registry.deserialize({\n * __type: 'PaymentFailedError',\n * message: 'Payment failed',\n * transactionId: 'tx_123',\n * reason: 'insufficient_funds'\n * });\n *\n * // error instanceof PaymentFailedError === true\n * ```\n */\nexport class ErrorRegistry\n{\n private errors = new Map<string, SerializableErrorConstructor>();\n\n constructor(initialErrors?: ErrorRegistryInput[])\n {\n if (initialErrors)\n {\n for (const input of initialErrors)\n {\n if (input instanceof ErrorRegistry)\n {\n this.concat(input);\n }\n else if (Array.isArray(input))\n {\n this.append(input);\n }\n else\n {\n this.append(input);\n }\n }\n }\n }\n\n /**\n * Append error class(es) to the registry\n *\n * @param ErrorClass - Single error constructor\n * @returns This registry for chaining\n */\n append(ErrorClass: SerializableErrorConstructor): this;\n\n /**\n * Append error class(es) to the registry\n *\n * @param ErrorClasses - Array of error constructors\n * @returns This registry for chaining\n */\n append(ErrorClasses: SerializableErrorConstructor[]): this;\n\n /**\n * Append error class(es) to the registry\n *\n * @param input - Error constructor or array of constructors\n * @returns This registry for chaining\n */\n append(input: SerializableErrorConstructor | SerializableErrorConstructor[]): this\n {\n if (Array.isArray(input))\n {\n for (const ErrorClass of input)\n {\n this.errors.set(ErrorClass.name, ErrorClass);\n }\n }\n else\n {\n this.errors.set(input.name, input);\n }\n\n return this;\n }\n\n /**\n * Concatenate another ErrorRegistry into this one\n *\n * @param registry - Another ErrorRegistry to merge\n * @returns This registry for chaining\n */\n concat(registry: ErrorRegistry): this\n {\n for (const [name, ErrorClass] of registry.errors)\n {\n this.errors.set(name, ErrorClass);\n }\n\n return this;\n }\n\n /**\n * Check if error type is registered\n *\n * @param name - Error class name\n */\n has(name: string): boolean\n {\n return this.errors.has(name);\n }\n\n /**\n * Deserialize error from JSON data\n *\n * @param data - Serialized error data with __type field\n * @returns Deserialized error instance\n * @throws Error if error type is not registered\n */\n deserialize(data: { __type: string; [key: string]: any }): SerializableError\n {\n const ErrorClass = this.errors.get(data.__type);\n\n if (!ErrorClass)\n {\n throw new Error(`Unknown error type: ${data.__type}`);\n }\n\n // `__type` routed this lookup and `error` is the response envelope: both\n // describe the response, not the error. The documented constructor shape\n // is Object.assign(this, data), which would otherwise copy them onto the\n // instance as reserved fields — and a reserved field refuses to serialize\n // the moment a server re-throws this error.\n const { __type: _type, error: _envelope, ...fields } = data;\n\n return new ErrorClass(fields);\n }\n\n /**\n * Try to deserialize error, return null if type unknown\n *\n * @param data - Serialized error data\n * @returns Deserialized error or null\n */\n tryDeserialize(data: { __type?: string; [key: string]: any }): SerializableError | null\n {\n if (!data.__type || !this.has(data.__type))\n {\n return null;\n }\n\n try\n {\n return this.deserialize(data as any);\n }\n catch\n {\n return null;\n }\n }\n\n /**\n * Get all registered error types\n */\n getRegisteredTypes(): string[]\n {\n return Array.from(this.errors.keys());\n }\n}\n","/**\n * Serializable Error Base Class\n *\n * Base class for errors that can be serialized/deserialized across HTTP boundary.\n * Automatically serializes public fields for transmission to client.\n */\n\n// Package specifier, not '../logger': tsup marks `@spfn/*` external, so a\n// relative import would inline a SECOND logger singleton into this entrypoint's\n// bundle — and into every bundle that imports errors (issue #136).\nimport { logger } from '@spfn/core/logger';\n\n/**\n * Serialized error format for JSON transmission\n */\nexport interface SerializedError\n{\n __type: string;\n message: string;\n [key: string]: unknown;\n}\n\n/**\n * Field names an error class may not use.\n *\n * Public fields are spread into the response body next to `__type`, `message`\n * and the `error` envelope the error handler attaches, so a field sharing one\n * of those names collides with the response shape every consumer reads: the\n * web client restores an error class from `__type` and a generated mobile\n * client reads `error.code`. Reserving the three names removes the collision\n * instead of picking a winner for it — either winner loses something the other\n * side needs.\n */\nconst RESERVED_RESPONSE_KEYS = new Set(['__type', 'message', 'error']);\n\n/**\n * Environments where a developer is watching and can still rename the class.\n * Anything else is a running deployment, `staging` included — there the throw\n * would replace a real failure with a failure about serializing it, and the\n * original error would never reach the log or the client.\n */\nconst AUTHORING_ENVIRONMENTS = new Set(['local', 'development', 'test']);\n\n/**\n * Answer a reserved field name: loudly while it can still be renamed, quietly\n * once a rename is no longer possible.\n *\n * While authoring, the throw is the point — the class is renamed the first time\n * a test serializes it.\n */\nfunction refuseReservedKey(className: string, key: string): never | void\n{\n const detail = `${className} declares the reserved field \"${key}\". `\n + `Reserved response keys: ${[...RESERVED_RESPONSE_KEYS].join(', ')}. Rename the field.`;\n\n if (AUTHORING_ENVIRONMENTS.has(process.env.NODE_ENV ?? 'local'))\n {\n throw new Error(detail);\n }\n\n logger.error(`[SerializableError] ${detail} The field is omitted from the response.`);\n}\n\n/**\n * Base class for all serializable errors\n *\n * Features:\n * - Auto-serialization of public fields via toJSON()\n * - Constructor accepts object with all fields\n * - Type-safe error handling with instanceof\n *\n * @example\n * ```typescript\n * class PaymentFailedError extends SerializableError\n * {\n * readonly statusCode = 402;\n * transactionId!: string;\n * reason!: 'insufficient_funds' | 'card_declined';\n *\n * constructor(data: {\n * message: string;\n * transactionId: string;\n * reason: 'insufficient_funds' | 'card_declined';\n * })\n * {\n * super(data.message);\n * this.name = 'PaymentFailedError';\n * Object.assign(this, data);\n * }\n * }\n * ```\n */\nexport abstract class SerializableError extends Error\n{\n /**\n * HTTP status code for this error type\n */\n abstract readonly statusCode: number;\n\n /**\n * Serialize error to JSON-compatible object\n *\n * Automatically includes:\n * - __type: Constructor name for deserialization\n * - message: Error message\n * - All public instance properties (except name, stack)\n */\n toJSON(): SerializedError\n {\n const json: SerializedError = {\n __type: this.constructor.name,\n message: this.message,\n };\n\n // Extract all public instance properties\n for (const key of Object.keys(this))\n {\n // Skip Error built-ins and statusCode (inferred from type)\n if (key === 'name' || key === 'stack' || key === 'statusCode')\n {\n continue;\n }\n\n if (RESERVED_RESPONSE_KEYS.has(key))\n {\n refuseReservedKey(this.constructor.name, key);\n continue;\n }\n\n json[key] = (this as unknown as Record<string, unknown>)[key];\n }\n\n return json;\n }\n}\n","/**\n * HTTP Error Classes\n *\n * Standard HTTP error classes for API responses\n * All errors are serializable for type-safe client-side error handling\n */\n\nimport { SerializableError } from './serializable-error';\n\n/**\n * Base HTTP Error\n *\n * Base class for all HTTP-related errors\n */\nexport class HttpError extends SerializableError\n{\n public readonly statusCode: number;\n public details?: Record<string, unknown>;\n\n constructor(data: {\n message: string;\n statusCode: number;\n details?: Record<string, unknown>;\n })\n {\n super(data.message);\n\n this.name = 'HttpError';\n this.statusCode = data.statusCode;\n\n if (data.details)\n {\n this.details = data.details;\n }\n\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\n/**\n * Bad Request Error (400)\n *\n * Generic bad request - malformed syntax, invalid parameters, etc.\n */\nexport class BadRequestError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Bad request',\n statusCode: 400,\n details: data.details,\n });\n\n this.name = 'BadRequestError';\n }\n}\n\n/**\n * Validation Error (400)\n *\n * Input validation failure (request params, query, body)\n * Used by define-route system for automatic validation\n */\nexport class ValidationError extends HttpError\n{\n fields?: Array<{ path: string; message: string; value?: any }>;\n\n constructor(data: {\n message: string;\n fields?: Array<{ path: string; message: string; value?: any }>;\n details?: Record<string, any>;\n })\n {\n super({\n message: data.message,\n statusCode: 400,\n details: data.details,\n });\n\n this.name = 'ValidationError';\n\n if (data.fields)\n {\n this.fields = data.fields;\n }\n }\n}\n\n/**\n * Unauthorized Error (401)\n *\n * Authentication required or authentication failed\n */\nexport class UnauthorizedError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Authentication required',\n statusCode: 401,\n details: data.details,\n });\n\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Forbidden Error (403)\n *\n * Authenticated but lacks permission to access resource\n */\nexport class ForbiddenError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Access forbidden',\n statusCode: 403,\n details: data.details,\n });\n\n this.name = 'ForbiddenError';\n }\n}\n\n/**\n * Not Found Error (404)\n *\n * Requested resource does not exist\n */\nexport class NotFoundError extends HttpError\n{\n resource?: string;\n\n constructor(data: { message?: string; resource?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource not found',\n statusCode: 404,\n details: data.details,\n });\n\n this.name = 'NotFoundError';\n\n if (data.resource)\n {\n this.resource = data.resource;\n }\n }\n}\n\n/**\n * Conflict Error (409)\n *\n * Generic conflict - resource state conflict, concurrent modification, etc.\n */\nexport class ConflictError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource conflict',\n statusCode: 409,\n details: data.details,\n });\n\n this.name = 'ConflictError';\n }\n}\n\n/**\n * Gone Error (410)\n *\n * Resource permanently deleted and no longer available\n */\nexport class GoneError extends HttpError\n{\n resource?: string;\n\n constructor(data: { message?: string; resource?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource permanently deleted',\n statusCode: 410,\n details: data.details,\n });\n\n this.name = 'GoneError';\n\n if (data.resource)\n {\n this.resource = data.resource;\n }\n }\n}\n\n/**\n * Too Many Requests Error (429)\n *\n * Rate limit exceeded\n */\nexport class TooManyRequestsError extends HttpError\n{\n retryAfter?: number;\n\n constructor(data: {\n message?: string;\n retryAfter?: number;\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Too many requests',\n statusCode: 429,\n details: data.details,\n });\n\n this.name = 'TooManyRequestsError';\n\n if (data.retryAfter)\n {\n this.retryAfter = data.retryAfter;\n }\n }\n}\n\n/**\n * Internal Server Error (500)\n *\n * Generic server error when no specific error type applies\n */\nexport class InternalServerError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Internal server error',\n statusCode: 500,\n details: data.details,\n });\n\n this.name = 'InternalServerError';\n }\n}\n\n/**\n * Unsupported Media Type Error (415)\n *\n * Media type not supported - invalid file type, content type, etc.\n */\nexport class UnsupportedMediaTypeError extends HttpError\n{\n mediaType?: string;\n supportedTypes?: string[];\n\n constructor(data: {\n message?: string;\n mediaType?: string;\n supportedTypes?: string[];\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Unsupported media type',\n statusCode: 415,\n details: data.details,\n });\n\n this.name = 'UnsupportedMediaTypeError';\n\n if (data.mediaType)\n {\n this.mediaType = data.mediaType;\n }\n\n if (data.supportedTypes)\n {\n this.supportedTypes = data.supportedTypes;\n }\n }\n}\n\n/**\n * Unprocessable Entity Error (422)\n *\n * Request is well-formed but contains semantic errors\n */\nexport class UnprocessableEntityError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Unprocessable entity',\n statusCode: 422,\n details: data.details,\n });\n\n this.name = 'UnprocessableEntityError';\n }\n}\n\n/**\n * Service Unavailable Error (503)\n *\n * Service temporarily unavailable (maintenance, overload, etc.)\n */\nexport class ServiceUnavailableError extends HttpError\n{\n retryAfter?: number;\n\n constructor(data: {\n message?: string;\n retryAfter?: number;\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Service unavailable',\n statusCode: 503,\n details: data.details,\n });\n\n this.name = 'ServiceUnavailableError';\n\n if (data.retryAfter)\n {\n this.retryAfter = data.retryAfter;\n }\n }\n}\n","/**\n * Database Error Classes\n *\n * Type-safe error handling with custom error class hierarchy\n * Mapped to HTTP status codes for API responses\n * All errors extend SerializableError for consistent JSON serialization\n */\n\nimport { SerializableError } from './serializable-error';\n\n/**\n * Base Database Error\n *\n * Base class for all database-related errors\n */\nexport class DatabaseError<TDetails extends Record<string, unknown> = Record<string, unknown>>\n extends SerializableError\n{\n public readonly statusCode: number;\n public readonly details?: TDetails;\n\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: TDetails;\n })\n {\n super(data.message);\n this.name = 'DatabaseError';\n this.statusCode = data.statusCode ?? 500;\n this.details = data.details;\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Whether this error's message/details are derived from the raw database\n * driver (SQL text, table/column/constraint names, parameter values) and\n * therefore must NOT be exposed to clients in production. Defined as a\n * prototype getter so it is never serialized into the response by toJSON().\n * Subclasses with a safe, constructed message override this to `false`.\n */\n get internal(): boolean\n {\n return true;\n }\n}\n\n/**\n * Connection Error (503 Service Unavailable)\n *\n * Database connection failure, connection pool exhaustion, etc.\n */\nexport class ConnectionError extends DatabaseError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 503, details: data.details });\n this.name = 'ConnectionError';\n }\n}\n\n/**\n * Query Error (500 Internal Server Error)\n *\n * SQL query execution failure, syntax errors, etc.\n */\nexport class QueryError extends DatabaseError\n{\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: Record<string, unknown>;\n })\n {\n super({\n message: data.message,\n statusCode: data.statusCode ?? 500,\n details: data.details,\n });\n this.name = 'QueryError';\n }\n}\n\n/**\n * Entity Not Found Error (404 Not Found)\n *\n * Database entity does not exist\n */\nexport class EntityNotFoundError extends QueryError\n{\n public readonly resource: string;\n public readonly id: string | number;\n\n constructor(data: { resource: string; id: string | number })\n {\n super({\n message: `${data.resource} with id ${data.id} not found`,\n statusCode: 404,\n details: { resource: data.resource, id: data.id },\n });\n this.name = 'EntityNotFoundError';\n this.resource = data.resource;\n this.id = data.id;\n }\n\n // Message/details are constructed from the resource + id, not driver text\n override get internal(): boolean\n {\n return false;\n }\n}\n\n/**\n * Constraint Violation Error (400 Bad Request)\n *\n * Database constraint violation (NOT NULL, CHECK, FOREIGN KEY, etc.)\n * This is different from HTTP ValidationError which validates request input\n */\nexport class ConstraintViolationError extends QueryError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 400, details: data.details });\n this.name = 'ConstraintViolationError';\n }\n}\n\n/**\n * Transaction Error (500 Internal Server Error)\n *\n * Transaction start/commit/rollback failure\n */\nexport class TransactionError extends DatabaseError\n{\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: Record<string, unknown>;\n })\n {\n super({\n message: data.message,\n statusCode: data.statusCode ?? 500,\n details: data.details,\n });\n this.name = 'TransactionError';\n }\n}\n\n/**\n * Deadlock Error (409 Conflict)\n *\n * Database deadlock detected\n */\nexport class DeadlockError extends TransactionError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 409, details: data.details });\n this.name = 'DeadlockError';\n }\n}\n\n/**\n * Duplicate Entry Error (409 Conflict)\n *\n * Unique constraint violation (e.g., duplicate email)\n */\nexport class DuplicateEntryError extends QueryError\n{\n public readonly field: string;\n public readonly value: string | number;\n\n constructor(data: { field: string; value: string | number })\n {\n // The client-facing message/details name the field but NOT the value: echoing\n // the value back (e.g. an email) confirms it exists, an enumeration aid. The\n // raw value stays on the instance for server-side handling/logging.\n super({\n message: `${data.field} already exists`,\n statusCode: 409,\n details: { field: data.field },\n });\n this.name = 'DuplicateEntryError';\n this.field = data.field;\n this.value = data.value;\n }\n\n // Field-only message/details — safe to surface to the client (not internal)\n override get internal(): boolean\n {\n return false;\n }\n}\n","/**\n * Error Module Exports\n *\n * Entry point for error handling module with serialization support\n */\n\n// Core Error Registry\n// Pre-configured registry with all built-in HTTP and Database errors\nimport { ErrorRegistry } from './error-registry';\nimport {\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n} from './http-errors';\nimport {\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n} from './database-errors';\n\n// Base Classes\nexport { SerializableError } from './serializable-error';\nexport type { SerializedError } from './serializable-error';\nexport { ErrorRegistry } from './error-registry';\nexport type { SerializableErrorConstructor, ErrorRegistryInput } from './error-registry';\n\n// Database Error Classes\nexport {\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n} from './database-errors';\n\n// HTTP Error Classes\nexport {\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n} from './http-errors';\n\n// Error Utilities\nexport {\n isDatabaseError,\n isHttpError,\n hasStatusCode,\n} from './error-utils';\n\nexport const errorRegistry = new ErrorRegistry();\n\n// HTTP Errors\nerrorRegistry.append([\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n]);\n\n// Database Errors\nerrorRegistry.append([\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n]);\n\nexport * as HttpErrors from './http-errors';\nexport * as DatabaseErrors from './database-errors';\n","/**\n * @spfn/core/authz - Authorization primitives\n *\n * Small, domain-agnostic guards for resource ownership. The point is to make the\n * \"load, then check it belongs to the requester\" step a single call so a handler\n * can't forget it and leak another user's data (IDOR).\n */\n\nimport { NotFoundError } from '../errors';\n\nexport interface RequireOwnerOptions<T>\n{\n /**\n * Property on the resource holding the owner id. Defaults to `'ownerId'`.\n * SPFN entities commonly use `userId` / `createdBy` — pass it explicitly:\n * `requireOwner(doc, userId, { ownerKey: 'userId' })`.\n */\n ownerKey?: keyof T;\n\n /** Message for the thrown NotFoundError. @default 'not found' */\n message?: string;\n}\n\n/**\n * Return the resource only if it exists and belongs to `ownerId`; otherwise throw\n * `NotFoundError`. Not-found and not-owned are deliberately the same response, so\n * the endpoint never reveals that a resource exists to a non-owner.\n *\n * Ids are compared as strings, so number/string/bigint ids interoperate. A\n * missing or null owner value never matches, so it throws.\n *\n * @example\n * ```ts\n * const chat = requireOwner(await chats.findById(id), userId, { ownerKey: 'userId' });\n * // chat is non-null here, narrowed from T | null\n * ```\n */\nexport function requireOwner<T extends object>(\n resource: T | null | undefined,\n ownerId: string | number | bigint,\n options: RequireOwnerOptions<T> = {},\n): T\n{\n const ownerKey = (options.ownerKey ?? 'ownerId') as keyof T;\n const value = resource ? (resource as Record<PropertyKey, unknown>)[ownerKey] : undefined;\n\n if (!resource || value === null || value === undefined || String(value) !== String(ownerId))\n {\n throw new NotFoundError({ message: options.message ?? 'not found' });\n }\n\n return resource;\n}\n"]}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { TSchema } from '@sinclair/typebox';
|
|
2
|
+
import PgBoss from 'pg-boss';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Job System Types
|
|
6
|
+
*
|
|
7
|
+
* Type definitions for the pg-boss based job system
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* pg-boss queue policy
|
|
12
|
+
*
|
|
13
|
+
* The policy decides which unique index pg-boss puts on
|
|
14
|
+
* `(queue name, singletonKey)`, and therefore what `singletonKey` actually
|
|
15
|
+
* deduplicates. A `standard` queue has no such index: the key is stored and
|
|
16
|
+
* ignored.
|
|
17
|
+
*
|
|
18
|
+
* | policy | one job per `(queue, singletonKey)` among states… |
|
|
19
|
+
* |---|---|
|
|
20
|
+
* | `standard` | none — the key is ignored |
|
|
21
|
+
* | `short` | `created` (queued, not yet picked up) |
|
|
22
|
+
* | `singleton` | `active` (running) — many may queue, one runs at a time |
|
|
23
|
+
* | `stately` | one per state among `created`, `retry`, `active` |
|
|
24
|
+
* | `exclusive` | `created` + `retry` + `active` together |
|
|
25
|
+
*/
|
|
26
|
+
type JobQueuePolicy = 'standard' | 'short' | 'singleton' | 'stately' | 'exclusive';
|
|
27
|
+
/**
|
|
28
|
+
* Job options passed to pg-boss
|
|
29
|
+
*/
|
|
30
|
+
interface JobOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Maximum retry attempts
|
|
33
|
+
* @default 3
|
|
34
|
+
*/
|
|
35
|
+
retryLimit?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Delay between retries in milliseconds
|
|
38
|
+
* @default 1000
|
|
39
|
+
*/
|
|
40
|
+
retryDelay?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Exponential backoff between retries (retryDelay * 2^attempt). Defaults to
|
|
43
|
+
* true so a failed cohort (e.g. on a provider 429) doesn't all retry at the
|
|
44
|
+
* same fixed offset — a thundering herd. Set false for fixed retryDelay.
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
retryBackoff?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Job expiration in seconds
|
|
50
|
+
* @default 300 (5 minutes)
|
|
51
|
+
*/
|
|
52
|
+
expireInSeconds?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Job priority (higher = more important)
|
|
55
|
+
* @default 0
|
|
56
|
+
*/
|
|
57
|
+
priority?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Deduplication key. What it deduplicates depends entirely on `policy`:
|
|
60
|
+
* on a `standard` queue (the default for a plain job) pg-boss stores the
|
|
61
|
+
* key and never conflicts on it, so nothing is deduplicated.
|
|
62
|
+
*
|
|
63
|
+
* Setting this without an explicit `policy` makes the queue `exclusive`.
|
|
64
|
+
*/
|
|
65
|
+
singletonKey?: string;
|
|
66
|
+
/**
|
|
67
|
+
* pg-boss queue policy — what `singletonKey` deduplicates on this queue.
|
|
68
|
+
*
|
|
69
|
+
* | policy | one job per `(queue, singletonKey)` among states… |
|
|
70
|
+
* |---|---|
|
|
71
|
+
* | `standard` | none — the key is ignored |
|
|
72
|
+
* | `short` | `created` |
|
|
73
|
+
* | `singleton` | `active` |
|
|
74
|
+
* | `stately` | one per state among `created`, `retry`, `active` |
|
|
75
|
+
* | `exclusive` | `created` + `retry` + `active` together |
|
|
76
|
+
*
|
|
77
|
+
* Deduplication is scoped by queue name: different queues never
|
|
78
|
+
* deduplicate against each other. `exclusive` with no `singletonKey`
|
|
79
|
+
* collapses the whole queue to one pending job, which is why plain jobs
|
|
80
|
+
* stay `standard`.
|
|
81
|
+
*
|
|
82
|
+
* @default 'exclusive' when the job is `.runOnce()` or sets `singletonKey`, otherwise 'standard'
|
|
83
|
+
*/
|
|
84
|
+
policy?: JobQueuePolicy;
|
|
85
|
+
/**
|
|
86
|
+
* Keep completed jobs for this many seconds
|
|
87
|
+
* @default 604800 (7 days)
|
|
88
|
+
*/
|
|
89
|
+
retentionSeconds?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Number of jobs to fetch per worker poll.
|
|
92
|
+
* When > 1, jobs in each batch are processed in parallel.
|
|
93
|
+
* Failed jobs are individually marked and retried by pg-boss.
|
|
94
|
+
* @default 1
|
|
95
|
+
*/
|
|
96
|
+
batchSize?: number;
|
|
97
|
+
/**
|
|
98
|
+
* How often this worker polls the DB for new jobs, in seconds. Lower =
|
|
99
|
+
* faster pickup but more idle SELECT load; higher = less DB chatter but
|
|
100
|
+
* slower pickup. Overrides the JOB_POLLING_INTERVAL_SECONDS default.
|
|
101
|
+
* @default 2 (pg-boss default) or JOB_POLLING_INTERVAL_SECONDS
|
|
102
|
+
*/
|
|
103
|
+
pollingIntervalSeconds?: number;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Send options for individual job dispatch
|
|
107
|
+
*/
|
|
108
|
+
interface JobSendOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Delay execution by this many seconds
|
|
111
|
+
*/
|
|
112
|
+
startAfter?: number | Date;
|
|
113
|
+
/**
|
|
114
|
+
* Deduplication key for this specific job instance.
|
|
115
|
+
*
|
|
116
|
+
* Only takes effect when the job's queue has a non-standard `policy`: see
|
|
117
|
+
* {@link JobOptions.policy}. On a `standard` queue pg-boss stores the key
|
|
118
|
+
* and never conflicts on it, and the first such send logs a warning.
|
|
119
|
+
*/
|
|
120
|
+
singletonKey?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Priority override
|
|
123
|
+
*/
|
|
124
|
+
priority?: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Job handler function type
|
|
128
|
+
*
|
|
129
|
+
* The check is wrapped in a tuple — `[TInput] extends [void]`, not
|
|
130
|
+
* `TInput extends void` — to stop the conditional from distributing. A bare
|
|
131
|
+
* type parameter in the check position makes the conditional distributive, so a
|
|
132
|
+
* union input `A | B | C` is evaluated per member and the results are unioned
|
|
133
|
+
* into three function types whose parameters intersect to `never`: no caller
|
|
134
|
+
* can then pass any member. The tuple makes the union one opaque type, which is
|
|
135
|
+
* what a handler taking `A | B | C` needs. The same wrap is on `send`, `run`
|
|
136
|
+
* and `sendBatch` below, for the same reason. Do not "simplify" it away.
|
|
137
|
+
*/
|
|
138
|
+
type JobHandler<TInput, TOutput = void> = [TInput] extends [void] ? () => Promise<TOutput> : (input: TInput) => Promise<TOutput>;
|
|
139
|
+
/**
|
|
140
|
+
* Compensate handler function type (for rollback)
|
|
141
|
+
*/
|
|
142
|
+
type CompensateHandler<TInput, TOutput> = (input: TInput, output: TOutput) => Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Job definition interface
|
|
145
|
+
*/
|
|
146
|
+
interface JobDef<TInput = void, TOutput = void> {
|
|
147
|
+
/**
|
|
148
|
+
* Unique job name
|
|
149
|
+
*/
|
|
150
|
+
readonly name: string;
|
|
151
|
+
/**
|
|
152
|
+
* TypeBox input schema (optional)
|
|
153
|
+
*/
|
|
154
|
+
readonly inputSchema?: TSchema;
|
|
155
|
+
/**
|
|
156
|
+
* TypeBox output schema (optional, for workflow integration)
|
|
157
|
+
*/
|
|
158
|
+
readonly outputSchema?: TSchema;
|
|
159
|
+
/**
|
|
160
|
+
* Cron expression for scheduled jobs
|
|
161
|
+
*/
|
|
162
|
+
readonly cronExpression?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Run once on server start
|
|
165
|
+
*/
|
|
166
|
+
readonly runOnce?: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Event name this job subscribes to
|
|
169
|
+
*/
|
|
170
|
+
readonly subscribedEvent?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Event definition this job subscribes to (internal use)
|
|
173
|
+
*/
|
|
174
|
+
readonly _subscribedEventDef?: unknown;
|
|
175
|
+
/**
|
|
176
|
+
* Job options
|
|
177
|
+
*/
|
|
178
|
+
readonly options?: JobOptions;
|
|
179
|
+
/**
|
|
180
|
+
* Job handler
|
|
181
|
+
*/
|
|
182
|
+
readonly handler: JobHandler<TInput, TOutput>;
|
|
183
|
+
/**
|
|
184
|
+
* Compensate handler for rollback (optional, for workflow integration)
|
|
185
|
+
*/
|
|
186
|
+
readonly compensate?: CompensateHandler<TInput, TOutput>;
|
|
187
|
+
/**
|
|
188
|
+
* Send job to queue (returns immediately, executes in background)
|
|
189
|
+
*/
|
|
190
|
+
send: [TInput] extends [void] ? (options?: JobSendOptions) => Promise<string | null> : (input: TInput, options?: JobSendOptions) => Promise<string | null>;
|
|
191
|
+
/**
|
|
192
|
+
* Run job synchronously (for testing/debugging)
|
|
193
|
+
*/
|
|
194
|
+
run: [TInput] extends [void] ? () => Promise<TOutput> : (input: TInput) => Promise<TOutput>;
|
|
195
|
+
/**
|
|
196
|
+
* Bulk insert jobs into the queue (pg-boss insert).
|
|
197
|
+
* Much faster than calling send() in a loop.
|
|
198
|
+
* Only available for jobs with input schema.
|
|
199
|
+
*/
|
|
200
|
+
sendBatch: [TInput] extends [void] ? (options?: JobSendOptions) => Promise<void> : (inputs: TInput[], options?: JobSendOptions) => Promise<void>;
|
|
201
|
+
/**
|
|
202
|
+
* Type inference helpers
|
|
203
|
+
*/
|
|
204
|
+
_input: TInput;
|
|
205
|
+
_output: TOutput;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Job router entry - can be a job or nested router
|
|
209
|
+
*/
|
|
210
|
+
type JobRouterEntry = JobDef<any, any> | JobRouter<any>;
|
|
211
|
+
/**
|
|
212
|
+
* Job router interface
|
|
213
|
+
*/
|
|
214
|
+
interface JobRouter<TJobs extends Record<string, JobRouterEntry> = Record<string, JobRouterEntry>> {
|
|
215
|
+
readonly jobs: TJobs;
|
|
216
|
+
readonly _jobs: TJobs;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Infer input type from JobDef
|
|
220
|
+
*/
|
|
221
|
+
type InferJobInput<TJob> = TJob extends JobDef<infer TInput, any> ? TInput : never;
|
|
222
|
+
/**
|
|
223
|
+
* Infer output type from JobDef
|
|
224
|
+
*/
|
|
225
|
+
type InferJobOutput<TJob> = TJob extends JobDef<any, infer TOutput> ? TOutput : never;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* pg-boss Wrapper
|
|
229
|
+
*
|
|
230
|
+
* Manages pg-boss instance lifecycle
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Options for pg-boss initialization
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* await initBoss({
|
|
239
|
+
* connectionString: process.env.DATABASE_URL,
|
|
240
|
+
* schema: 'spfn_queue',
|
|
241
|
+
* clearOnStart: process.env.NODE_ENV === 'development',
|
|
242
|
+
* });
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
interface BossOptions {
|
|
246
|
+
/**
|
|
247
|
+
* PostgreSQL connection string
|
|
248
|
+
*
|
|
249
|
+
* @example 'postgresql://user:password@localhost:5432/mydb'
|
|
250
|
+
*/
|
|
251
|
+
connectionString: string;
|
|
252
|
+
/**
|
|
253
|
+
* Schema name for pg-boss tables
|
|
254
|
+
*
|
|
255
|
+
* pg-boss creates its own tables in this schema.
|
|
256
|
+
*
|
|
257
|
+
* @default 'spfn_queue'
|
|
258
|
+
*/
|
|
259
|
+
schema?: string;
|
|
260
|
+
/**
|
|
261
|
+
* Maintenance interval in seconds
|
|
262
|
+
*
|
|
263
|
+
* pg-boss runs maintenance tasks (cleanup, archiving) at this interval.
|
|
264
|
+
*
|
|
265
|
+
* @default 120
|
|
266
|
+
*/
|
|
267
|
+
maintenanceIntervalSeconds?: number;
|
|
268
|
+
/**
|
|
269
|
+
* Monitor state changes interval in seconds
|
|
270
|
+
*
|
|
271
|
+
* When set, pg-boss emits state change events at this interval.
|
|
272
|
+
*
|
|
273
|
+
* @default undefined (disabled)
|
|
274
|
+
*/
|
|
275
|
+
monitorIntervalSeconds?: number;
|
|
276
|
+
/**
|
|
277
|
+
* Clear all pending/scheduled jobs on startup
|
|
278
|
+
*
|
|
279
|
+
* Useful for development mode to start with a clean queue.
|
|
280
|
+
* Should be false in production.
|
|
281
|
+
*
|
|
282
|
+
* @default false
|
|
283
|
+
*/
|
|
284
|
+
clearOnStart?: boolean;
|
|
285
|
+
/**
|
|
286
|
+
* Unschedule cron schedules that are no longer declared on any registered
|
|
287
|
+
* job router, once at startup after job registration. Queues and job rows
|
|
288
|
+
* are never deleted — only the schedule row is removed.
|
|
289
|
+
*
|
|
290
|
+
* Leave disabled when multiple apps share the same pg-boss schema, when
|
|
291
|
+
* schedules are created directly via getBoss(), or during rolling deploys
|
|
292
|
+
* that mix router versions — the sweep only knows the routers registered
|
|
293
|
+
* in this process and would unschedule everything else.
|
|
294
|
+
*
|
|
295
|
+
* @default false
|
|
296
|
+
*/
|
|
297
|
+
sweepOrphanSchedules?: boolean;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* @deprecated Use BossOptions instead
|
|
301
|
+
*/
|
|
302
|
+
type BossConfig = BossOptions;
|
|
303
|
+
/**
|
|
304
|
+
* Initialize pg-boss with the given configuration
|
|
305
|
+
*
|
|
306
|
+
* Must be called before registerJobs(). Typically handled by defineServerConfig().
|
|
307
|
+
*
|
|
308
|
+
* @param options - pg-boss configuration options
|
|
309
|
+
* @returns The pg-boss instance
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* const boss = await initBoss({
|
|
314
|
+
* connectionString: process.env.DATABASE_URL!,
|
|
315
|
+
* schema: 'spfn_queue',
|
|
316
|
+
* });
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
declare function initBoss(options: BossOptions): Promise<PgBoss>;
|
|
320
|
+
/**
|
|
321
|
+
* Get the current pg-boss instance
|
|
322
|
+
*/
|
|
323
|
+
declare function getBoss(): PgBoss | null;
|
|
324
|
+
/**
|
|
325
|
+
* Stop pg-boss gracefully
|
|
326
|
+
*/
|
|
327
|
+
declare function stopBoss(): Promise<void>;
|
|
328
|
+
/**
|
|
329
|
+
* Check if pg-boss is initialized and running
|
|
330
|
+
*/
|
|
331
|
+
declare function isBossRunning(): boolean;
|
|
332
|
+
/**
|
|
333
|
+
* Check if jobs should be cleared on start
|
|
334
|
+
*/
|
|
335
|
+
declare function shouldClearOnStart(): boolean;
|
|
336
|
+
|
|
337
|
+
export { type BossConfig as B, type CompensateHandler as C, type InferJobInput as I, type JobOptions as J, type JobHandler as a, type JobDef as b, type JobRouter as c, type JobRouterEntry as d, type JobQueuePolicy as e, type BossOptions as f, type InferJobOutput as g, type JobSendOptions as h, getBoss as i, initBoss as j, isBossRunning as k, stopBoss as l, shouldClearOnStart as s };
|
package/dist/cache/index.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { logger } from '@spfn/core/logger';
|
|
2
|
+
import { env } from '@spfn/core/config';
|
|
2
3
|
|
|
3
4
|
// src/cache/cache-factory.ts
|
|
5
|
+
function resilienceOptions() {
|
|
6
|
+
return {
|
|
7
|
+
maxRetriesPerRequest: env.CACHE_MAX_RETRIES_PER_REQUEST,
|
|
8
|
+
enableOfflineQueue: env.CACHE_ENABLE_OFFLINE_QUEUE
|
|
9
|
+
};
|
|
10
|
+
}
|
|
4
11
|
var cacheLogger = logger.child("@spfn/core:cache");
|
|
5
12
|
function hasCacheConfig() {
|
|
6
13
|
return !!(process.env.CACHE_URL || process.env.CACHE_WRITE_URL || process.env.CACHE_READ_URL || process.env.CACHE_SENTINEL_HOSTS || process.env.CACHE_CLUSTER_NODES);
|
|
7
14
|
}
|
|
8
15
|
function createClient(RedisClient, url) {
|
|
9
|
-
const options = {};
|
|
16
|
+
const options = { ...resilienceOptions() };
|
|
10
17
|
if (url.startsWith("rediss://")) {
|
|
11
18
|
options.tls = {
|
|
12
19
|
rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== "false"
|
|
@@ -46,6 +53,7 @@ async function createCacheFromEnv() {
|
|
|
46
53
|
return { host: hostname, port: Number(port) || 26379 };
|
|
47
54
|
});
|
|
48
55
|
const options = {
|
|
56
|
+
...resilienceOptions(),
|
|
49
57
|
sentinels,
|
|
50
58
|
name: masterName,
|
|
51
59
|
password
|
|
@@ -61,6 +69,7 @@ async function createCacheFromEnv() {
|
|
|
61
69
|
});
|
|
62
70
|
const clusterOptions = {
|
|
63
71
|
redisOptions: {
|
|
72
|
+
...resilienceOptions(),
|
|
64
73
|
password
|
|
65
74
|
}
|
|
66
75
|
};
|
|
@@ -107,26 +116,29 @@ async function createSingleCacheFromEnv() {
|
|
|
107
116
|
return write;
|
|
108
117
|
}
|
|
109
118
|
var cacheLogger2 = logger.child("@spfn/core:cache");
|
|
110
|
-
var
|
|
111
|
-
var
|
|
112
|
-
|
|
119
|
+
var CACHE_KEY = Symbol.for("@spfn/core:cache");
|
|
120
|
+
var state = globalThis[CACHE_KEY] ??= {
|
|
121
|
+
write: void 0,
|
|
122
|
+
read: void 0,
|
|
123
|
+
disabled: false
|
|
124
|
+
};
|
|
113
125
|
function getCache() {
|
|
114
|
-
return
|
|
126
|
+
return state.write;
|
|
115
127
|
}
|
|
116
128
|
function getCacheRead() {
|
|
117
|
-
return
|
|
129
|
+
return state.read ?? state.write;
|
|
118
130
|
}
|
|
119
131
|
function isCacheDisabled() {
|
|
120
|
-
return
|
|
132
|
+
return state.disabled;
|
|
121
133
|
}
|
|
122
134
|
function setCache(write, read) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
state.write = write;
|
|
136
|
+
state.read = read ?? write;
|
|
137
|
+
state.disabled = !write;
|
|
126
138
|
}
|
|
127
139
|
async function initCache() {
|
|
128
|
-
if (
|
|
129
|
-
return { write:
|
|
140
|
+
if (state.write) {
|
|
141
|
+
return { write: state.write, read: state.read, disabled: state.disabled };
|
|
130
142
|
}
|
|
131
143
|
const { write, read } = await createCacheFromEnv();
|
|
132
144
|
if (write) {
|
|
@@ -135,15 +147,15 @@ async function initCache() {
|
|
|
135
147
|
if (read && read !== write) {
|
|
136
148
|
await read.ping();
|
|
137
149
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
state.write = write;
|
|
151
|
+
state.read = read;
|
|
152
|
+
state.disabled = false;
|
|
141
153
|
const hasReplica = read && read !== write;
|
|
142
154
|
cacheLogger2.info(
|
|
143
155
|
hasReplica ? "Cache connected (Master-Replica)" : "Cache connected",
|
|
144
156
|
{ mode: "enabled" }
|
|
145
157
|
);
|
|
146
|
-
return { write:
|
|
158
|
+
return { write: state.write, read: state.read, disabled: false };
|
|
147
159
|
} catch (error) {
|
|
148
160
|
cacheLogger2.error(
|
|
149
161
|
"Cache connection failed - running in disabled mode",
|
|
@@ -157,46 +169,46 @@ async function initCache() {
|
|
|
157
169
|
}
|
|
158
170
|
} catch {
|
|
159
171
|
}
|
|
160
|
-
|
|
172
|
+
state.disabled = true;
|
|
161
173
|
return { write: void 0, read: void 0, disabled: true };
|
|
162
174
|
}
|
|
163
175
|
}
|
|
164
|
-
|
|
176
|
+
state.disabled = true;
|
|
165
177
|
cacheLogger2.info("Cache disabled - no configuration or library not installed", { mode: "disabled" });
|
|
166
178
|
return { write: void 0, read: void 0, disabled: true };
|
|
167
179
|
}
|
|
168
180
|
async function closeCache() {
|
|
169
|
-
if (
|
|
181
|
+
if (state.disabled) {
|
|
170
182
|
cacheLogger2.debug("Cache already disabled, nothing to close");
|
|
171
183
|
return;
|
|
172
184
|
}
|
|
173
185
|
const closePromises = [];
|
|
174
|
-
if (
|
|
186
|
+
if (state.write) {
|
|
175
187
|
closePromises.push(
|
|
176
|
-
|
|
188
|
+
state.write.quit().catch((err) => {
|
|
177
189
|
cacheLogger2.error("Error closing cache write instance", err);
|
|
178
190
|
})
|
|
179
191
|
);
|
|
180
192
|
}
|
|
181
|
-
if (
|
|
193
|
+
if (state.read && state.read !== state.write) {
|
|
182
194
|
closePromises.push(
|
|
183
|
-
|
|
195
|
+
state.read.quit().catch((err) => {
|
|
184
196
|
cacheLogger2.error("Error closing cache read instance", err);
|
|
185
197
|
})
|
|
186
198
|
);
|
|
187
199
|
}
|
|
188
200
|
await Promise.all(closePromises);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
state.write = void 0;
|
|
202
|
+
state.read = void 0;
|
|
203
|
+
state.disabled = true;
|
|
192
204
|
cacheLogger2.info("Cache connections closed", { mode: "disabled" });
|
|
193
205
|
}
|
|
194
206
|
function getCacheInfo() {
|
|
195
207
|
return {
|
|
196
|
-
hasWrite: !!
|
|
197
|
-
hasRead: !!
|
|
198
|
-
isReplica: !!(
|
|
199
|
-
disabled:
|
|
208
|
+
hasWrite: !!state.write,
|
|
209
|
+
hasRead: !!state.read,
|
|
210
|
+
isReplica: !!(state.read && state.read !== state.write),
|
|
211
|
+
disabled: state.disabled
|
|
200
212
|
};
|
|
201
213
|
}
|
|
202
214
|
|