@cloudflare/kv-asset-handler
Advanced tools
| {"version":3,"sources":["../src/types.ts","../src/index.ts"],"names":[],"mappings":";;;;;;AA0BO,IAAM,OAAA,GAAN,MAAM,QAAA,SAAgB,KAAA,CAAM;AAAA,EA1BnC;AA0BmC,IAAA,MAAA,CAAA,IAAA,EAAA,SAAA,CAAA;AAAA;AAAA,EAClC,WAAA,CAAY,OAAA,EAAkB,MAAA,GAAiB,GAAA,EAAK;AACnD,IAAA,KAAA,CAAM,OAAO,CAAA;AAEb,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAChD,IAAA,IAAA,CAAK,OAAO,QAAA,CAAQ,IAAA;AACpB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA;AACf,EACA,MAAA;AACD,CAAA;AACO,IAAM,qBAAA,GAAN,cAAoC,OAAA,CAAQ;AAAA,EApCnD;AAoCmD,IAAA,MAAA,CAAA,IAAA,EAAA,uBAAA,CAAA;AAAA;AAAA,EAClD,WAAA,CACC,OAAA,GAAkB,CAAA,0BAAA,CAAA,EAClB,MAAA,GAAiB,GAAA,EAChB;AACD,IAAA,KAAA,CAAM,SAAS,MAAM,CAAA;AAAA;AAEvB;AACO,IAAM,aAAA,GAAN,cAA4B,OAAA,CAAQ;AAAA,EA5C3C;AA4C2C,IAAA,MAAA,CAAA,IAAA,EAAA,eAAA,CAAA;AAAA;AAAA,EAC1C,WAAA,CAAY,OAAA,GAAkB,CAAA,SAAA,CAAA,EAAa,MAAA,GAAiB,GAAA,EAAK;AAChE,IAAA,KAAA,CAAM,SAAS,MAAM,CAAA;AAAA;AAEvB;AACO,IAAM,aAAA,GAAN,cAA4B,OAAA,CAAQ;AAAA,EAjD3C;AAiD2C,IAAA,MAAA,CAAA,IAAA,EAAA,eAAA,CAAA;AAAA;AAAA,EAC1C,WAAA,CACC,OAAA,GAAkB,CAAA,kCAAA,CAAA,EAClB,MAAA,GAAiB,GAAA,EAChB;AACD,IAAA,KAAA,CAAM,SAAS,MAAM,CAAA;AAAA;AAEvB;;;AC9CA,IAAM,mBAAA,GAAoC;AAAA,EACzC,UAAA,EAAY,IAAA;AAAA,EACZ,OAAA,EAAS,CAAA,GAAI,EAAA,GAAK,EAAA,GAAK,EAAA;AAAA;AAAA,EACvB,WAAA,EAAa;AAAA;AACd,CAAA;AAEA,IAAM,mBAAA,mBAAsB,MAAA,CAAA,CAAI,WAAA,KAC/B,OAAO,WAAA,KAAgB,WACnB,IAAA,CAAK,KAAA,CAAM,WAAW,CAAA,GACvB,WAAA,EAHwB,qBAAA,CAAA;AAK5B,SAAS,4BAAA,GAAiD;AACzD,EAAA,OAAO;AAAA,IACN,eAAA,EACC,OAAO,gBAAA,KAAqB,WAAA,GAAc,gBAAA,GAAmB,MAAA;AAAA,IAC9D,gBACC,OAAO,yBAAA,KAA8B,cAClC,mBAAA,CAAuC,yBAAyB,IAChE,EAAC;AAAA,IACL,YAAA,EAAc,mBAAA;AAAA,IACd,eAAA,EAAiB,YAAA;AAAA,IACjB,eAAA,EAAiB,YAAA;AAAA,IACjB,aAAA,EAAe,KAAA;AAAA,IACf,WAAA,EAAa;AAAA,GACd;AACD;AAdS,MAAA,CAAA,4BAAA,EAAA,8BAAA,CAAA;AAgBT,SAAS,cAAc,OAAA,EAAqC;AAG3D,EAAA,OAAgB,OAAO,MAAA,CAAO,EAAC,EAAG,4BAAA,IAAgC,OAAO,CAAA;AAC1E;AAJS,MAAA,CAAA,aAAA,EAAA,eAAA,CAAA;AAaT,IAAM,iBAAA,mBAAoB,MAAA,CAAA,CAAC,OAAA,EAAkB,OAAA,KAA+B;AAC3E,EAAA,OAAA,GAAU,cAAc,OAAO,CAAA;AAE/B,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AACrC,EAAA,IAAI,WAAW,SAAA,CAAU,QAAA;AAEzB,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,GAAG,CAAA,EAAG;AAG3B,IAAA,QAAA,GAAW,QAAA,CAAS,MAAA,CAAO,OAAA,CAAQ,eAAe,CAAA;AAAA,GACnD,MAAA,IAAW,CAAM,IAAA,CAAA,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAGnC,IAAA,QAAA,GAAW,QAAA,CAAS,MAAA,CAAO,GAAA,GAAM,OAAA,CAAQ,eAAe,CAAA;AAAA;AAGzD,EAAA,SAAA,CAAU,QAAA,GAAW,QAAA;AACrB,EAAA,OAAO,IAAI,OAAA,CAAQ,SAAA,CAAU,QAAA,IAAY,OAAO,CAAA;AACjD,CAAA,EAlB0B,mBAAA;AAyB1B,SAAS,kBAAA,CACR,SACA,OAAA,EACU;AACV,EAAA,OAAA,GAAU,cAAc,OAAO,CAAA;AAI/B,EAAA,OAAA,GAAU,iBAAA,CAAkB,SAAS,OAAO,CAAA;AAE5C,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAIrC,EAAA,IAAI,SAAA,CAAU,QAAA,CAAS,QAAA,CAAS,OAAO,CAAA,EAAG;AAEzC,IAAA,OAAO,IAAI,OAAA;AAAA,MACV,CAAA,EAAG,SAAA,CAAU,MAAM,CAAA,CAAA,EAAI,QAAQ,eAAe,CAAA,CAAA;AAAA,MAC9C;AAAA,KACD;AAAA,GACD,MAAO;AAGN,IAAA,OAAO,OAAA;AAAA;AAET;AAzBS,MAAA,CAAA,kBAAA,EAAA,oBAAA,CAAA;AA4CT,IAAM,cAAA,mBAAiB,MAAA,CAAA,OACtB,KAAA,EACA,OAAA,KACuB;AACvB,EAAA,OAAA,GAAU,cAAc,OAAO,CAAA;AAE/B,EAAA,MAAM,UAAU,KAAA,CAAM,OAAA;AACtB,EAAA,MAAM,kBAAkB,OAAA,CAAQ,eAAA;AAChC,EAAA,MAAM,cAAA,GAAiB,mBAAA;AAAA,IACtB,OAAA,CAAQ;AAAA,GACT;AAEA,EAAA,IAAI,OAAO,oBAAoB,WAAA,EAAa;AAC3C,IAAA,MAAM,IAAI,cAAc,CAAA,4CAAA,CAA8C,CAAA;AAAA;AAGvE,EAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA,CAAE,QAAA,CAAS,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACnE,EAAA,IAAI,gBAAgB,OAAA,CAAQ,aAAA;AAC5B,EAAA,IAAI,UAAA;AAGJ,EAAA,IAAI,QAAQ,iBAAA,EAAmB;AAC9B,IAAA,UAAA,GAAa,OAAA,CAAQ,kBAAkB,OAAO,CAAA;AAAA,GAC/C,MAAA,IAAW,cAAA,CAAe,UAAU,CAAA,EAAG;AACtC,IAAA,UAAA,GAAa,OAAA;AAAA,GACd,MAAA,IAAW,cAAA,CAAe,kBAAA,CAAmB,UAAU,CAAC,CAAA,EAAG;AAC1D,IAAA,aAAA,GAAgB,IAAA;AAChB,IAAA,UAAA,GAAa,OAAA;AAAA,GACd,MAAO;AACN,IAAA,MAAM,aAAA,GAAgB,kBAAkB,OAAO,CAAA;AAC/C,IAAA,MAAM,mBAAmB,IAAI,GAAA,CAAI,aAAA,CAAc,GAAG,EAAE,QAAA,CAAS,OAAA;AAAA,MAC5D,MAAA;AAAA,MACA;AAAA,KACD;AACA,IAAA,IAAI,cAAA,CAAe,kBAAA,CAAmB,gBAAgB,CAAC,CAAA,EAAG;AACzD,MAAA,aAAA,GAAgB,IAAA;AAChB,MAAA,UAAA,GAAa,aAAA;AAAA,KACd,MAAO;AAEN,MAAA,UAAA,GAAa,iBAAA,CAAkB,SAAS,OAAO,CAAA;AAAA;AAChD;AAGD,EAAA,MAAM,iBAAA,GAAoB,CAAC,KAAA,EAAO,MAAM,CAAA;AACxC,EAAA,IAAI,CAAC,iBAAA,CAAkB,QAAA,CAAS,UAAA,CAAW,MAAM,CAAA,EAAG;AACnD,IAAA,MAAM,IAAI,qBAAA;AAAA,MACT,CAAA,EAAG,WAAW,MAAM,CAAA,8BAAA;AAAA,KACrB;AAAA;AAGD,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA;AACxC,EAAA,MAAM,WAAW,aAAA,GACd,kBAAA,CAAmB,SAAA,CAAU,QAAQ,IACrC,SAAA,CAAU,QAAA;AAGb,EAAA,IAAI,OAAA,GAAU,QAAA,CAAS,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAGzC,EAAA,MAAM,QAAQ,MAAA,CAAO,OAAA;AACrB,EAAA,IAAI,QAAA,GAAgB,IAAA,CAAA,OAAA,CAAQ,OAAO,CAAA,IAAK,OAAA,CAAQ,eAAA;AAChD,EAAA,IAAI,QAAA,CAAS,UAAA,CAAW,MAAM,CAAA,IAAK,aAAa,wBAAA,EAA0B;AACzE,IAAA,QAAA,IAAY,iBAAA;AAAA;AAGb,EAAA,IAAI,eAAA,GAAkB,KAAA;AAEtB,EAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAC1C,IAAA,IAAI,cAAA,CAAe,OAAO,CAAA,EAAG;AAC5B,MAAA,OAAA,GAAU,eAAe,OAAO,CAAA;AAEhC,MAAA,eAAA,GAAkB,IAAA;AAAA;AACnB;AAID,EAAA,MAAM,QAAA,GAAW,IAAI,OAAA,CAAQ,CAAA,EAAG,UAAU,MAAM,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,EAAI,OAAO,CAAA;AAKtE,EAAA,MAAM,iBAAiB,MAAM;AAC5B,IAAA,QAAQ,OAAO,QAAQ,YAAA;AAAc,MACpC,KAAK,UAAA;AACJ,QAAA,OAAO,OAAA,CAAQ,aAAa,OAAO,CAAA;AAAA,MACpC,KAAK,QAAA;AACJ,QAAA,OAAO,OAAA,CAAQ,YAAA;AAAA,MAChB;AACC,QAAA,OAAO,mBAAA;AAAA;AACT,GACD,GAAG;AAMH,EAAA,MAAM,6BAAa,MAAA,CAAA,CAClB,QAAA,GAAmB,OAAA,EACnB,aAAA,GAAwB,QAAQ,WAAA,KAC5B;AACJ,IAAA,IAAI,CAAC,QAAA,EAAU;AACd,MAAA,OAAO,EAAA;AAAA;AAER,IAAA,QAAQ,aAAA;AAAe,MACtB,KAAK,MAAA;AACJ,QAAA,IAAI,CAAC,QAAA,CAAS,UAAA,CAAW,IAAI,CAAA,EAAG;AAC/B,UAAA,IAAI,SAAS,UAAA,CAAW,CAAA,CAAA,CAAG,KAAK,QAAA,CAAS,QAAA,CAAS,GAAG,CAAA,EAAG;AACvD,YAAA,OAAO,KAAK,QAAQ,CAAA,CAAA;AAAA;AAErB,UAAA,OAAO,MAAM,QAAQ,CAAA,CAAA,CAAA;AAAA;AAEtB,QAAA,OAAO,QAAA;AAAA,MACR,KAAK,QAAA;AACJ,QAAA,IAAI,QAAA,CAAS,UAAA,CAAW,CAAA,GAAA,CAAK,CAAA,EAAG;AAC/B,UAAA,QAAA,GAAW,QAAA,CAAS,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA;AAAA;AAErC,QAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,CAAA,CAAA,CAAG,CAAA,EAAG;AAC5B,UAAA,QAAA,GAAW,IAAI,QAAQ,CAAA,CAAA,CAAA;AAAA;AAExB,QAAA,OAAO,QAAA;AAAA,MACR;AACC,QAAA,OAAO,EAAA;AAAA;AACT,GACD,EA3BmB,YAAA,CAAA;AA6BnB,EAAA,OAAA,CAAQ,eAAe,MAAA,CAAO,MAAA,CAAO,EAAC,EAAG,qBAAqB,aAAa,CAAA;AAG3E,EAAA,IACC,OAAA,CAAQ,aAAa,WAAA,IACrB,OAAA,CAAQ,aAAa,OAAA,KAAY,IAAA,IACjC,OAAA,CAAQ,MAAA,IAAU,MAAA,EACjB;AACD,IAAA,eAAA,GAAkB,KAAA;AAAA;AAGnB,EAAA,MAAM,qBAAA,GACL,OAAO,OAAA,CAAQ,YAAA,CAAa,UAAA,KAAe,QAAA;AAE5C,EAAA,IAAI,QAAA,GAAW,IAAA;AACf,EAAA,IAAI,eAAA,EAAiB;AACpB,IAAA,QAAA,GAAW,MAAM,KAAA,CAAM,KAAA,CAAM,QAAQ,CAAA;AAAA;AAGtC,EAAA,IAAI,QAAA,EAAU;AACb,IAAA,IAAI,QAAA,CAAS,MAAA,GAAS,GAAA,IAAO,QAAA,CAAS,SAAS,GAAA,EAAK;AACnD,MAAA,IAAI,SAAS,IAAA,IAAQ,QAAA,IAAY,OAAO,cAAA,CAAe,QAAA,CAAS,IAAI,CAAA,EAAG;AAEtE,QAAA,QAAA,CAAS,KAAK,MAAA,EAAO;AAAA;AAItB,MAAA,QAAA,GAAW,IAAI,QAAA,CAAS,IAAA,EAAM,QAAQ,CAAA;AAAA,KACvC,MAAO;AAEN,MAAA,MAAM,IAAA,GAAO;AAAA,QACZ,OAAA,EAAS,IAAI,OAAA,CAAQ,QAAA,CAAS,OAAO,CAAA;AAAA,QACrC,MAAA,EAAQ,CAAA;AAAA,QACR,UAAA,EAAY;AAAA,OACb;AAEA,MAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,KAAK,CAAA;AAEzC,MAAA,IAAI,SAAS,MAAA,EAAQ;AACpB,QAAA,IAAA,CAAK,SAAS,QAAA,CAAS,MAAA;AACvB,QAAA,IAAA,CAAK,aAAa,QAAA,CAAS,UAAA;AAAA,OAC5B,MAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAA,EAAG;AAC7C,QAAA,IAAA,CAAK,MAAA,GAAS,GAAA;AACd,QAAA,IAAA,CAAK,UAAA,GAAa,iBAAA;AAAA,OACnB,MAAO;AACN,QAAA,IAAA,CAAK,MAAA,GAAS,GAAA;AACd,QAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAAA;AAEnB,MAAA,QAAA,GAAW,IAAI,QAAA,CAAS,QAAA,CAAS,IAAA,EAAM,IAAI,CAAA;AAAA;AAC5C,GACD,MAAO;AACN,IAAA,MAAM,IAAA,GAAO,MAAM,eAAA,CAAgB,GAAA,CAAI,SAAS,aAAa,CAAA;AAC7D,IAAA,IAAI,SAAS,IAAA,EAAM;AAClB,MAAA,MAAM,IAAI,aAAA;AAAA,QACT,kBAAkB,OAAO,CAAA,0BAAA;AAAA,OAC1B;AAAA;AAED,IAAA,QAAA,GAAW,IAAI,SAAS,IAAI,CAAA;AAE5B,IAAA,IAAI,eAAA,EAAiB;AACpB,MAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,eAAA,EAAiB,OAAO,CAAA;AAC7C,MAAA,QAAA,CAAS,QAAQ,GAAA,CAAI,gBAAA,EAAkB,MAAA,CAAO,IAAA,CAAK,UAAU,CAAC,CAAA;AAE9D,MAAA,IAAI,CAAC,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA,EAAG;AAClC,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,UAAA,CAAW,OAAO,CAAC,CAAA;AAAA;AAGjD,MAAA,QAAA,CAAS,OAAA,CAAQ,GAAA;AAAA,QAChB,eAAA;AAAA,QACA,CAAA,QAAA,EAAW,OAAA,CAAQ,YAAA,CAAa,OAAO,CAAA;AAAA,OACxC;AACA,MAAA,KAAA,CAAM,UAAU,KAAA,CAAM,GAAA,CAAI,UAAU,QAAA,CAAS,KAAA,EAAO,CAAC,CAAA;AACrD,MAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,MAAM,CAAA;AAAA;AAC/C;AAED,EAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAA,EAAgB,QAAQ,CAAA;AAE7C,EAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC5B,IAAA,MAAM,OAAO,UAAA,CAAW,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAC,CAAA;AACpD,IAAA,MAAM,WAAA,GAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAA;AACxD,IAAA,MAAM,gBAAA,GAAmB,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA;AAC/D,IAAA,IAAI,IAAA,EAAM;AACT,MAAA,IAAI,WAAA,IAAe,WAAA,KAAgB,IAAA,IAAQ,gBAAA,KAAqB,MAAA,EAAQ;AACvE,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,SAAS,CAAA;AAAA,OAClD,MAAO;AACN,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,aAAa,CAAA;AAAA;AAEtD,MAAA,QAAA,CAAS,QAAQ,GAAA,CAAI,MAAA,EAAQ,UAAA,CAAW,IAAA,EAAM,MAAM,CAAC,CAAA;AAAA;AACtD;AAED,EAAA,IAAI,qBAAA,EAAuB;AAC1B,IAAA,QAAA,CAAS,OAAA,CAAQ,GAAA;AAAA,MAChB,eAAA;AAAA,MACA,CAAA,QAAA,EAAW,OAAA,CAAQ,YAAA,CAAa,UAAU,CAAA;AAAA,KAC3C;AAAA,GACD,MAAO;AACN,IAAA,QAAA,CAAS,OAAA,CAAQ,OAAO,eAAe,CAAA;AAAA;AAExC,EAAA,OAAO,QAAA;AACR,CAAA,EAhOuB,gBAAA","file":"index.js","sourcesContent":["declare global {\n\tconst __STATIC_CONTENT: KVNamespace | undefined;\n\tconst __STATIC_CONTENT_MANIFEST: Record<string, string> | undefined;\n}\n\nexport type CacheControl = {\n\tbrowserTTL: number;\n\tedgeTTL: number;\n\tbypassCache: boolean;\n};\n\nexport type AssetManifestType = Record<string, string>;\n\nexport type Options = {\n\tcacheControl:\n\t\t| ((req: Request) => Partial<CacheControl>)\n\t\t| Partial<CacheControl>;\n\tASSET_NAMESPACE: KVNamespace;\n\tASSET_MANIFEST: AssetManifestType | string;\n\tmapRequestToAsset?: (req: Request, options?: Partial<Options>) => Request;\n\tdefaultMimeType: string;\n\tdefaultDocument: string;\n\tpathIsEncoded: boolean;\n\tdefaultETag: \"strong\" | \"weak\";\n};\n\nexport class KVError extends Error {\n\tconstructor(message?: string, status: number = 500) {\n\t\tsuper(message);\n\t\t// see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html\n\t\tObject.setPrototypeOf(this, new.target.prototype); // restore prototype chain\n\t\tthis.name = KVError.name; // stack traces display correctly now\n\t\tthis.status = status;\n\t}\n\tstatus: number;\n}\nexport class MethodNotAllowedError extends KVError {\n\tconstructor(\n\t\tmessage: string = `Not a valid request method`,\n\t\tstatus: number = 405\n\t) {\n\t\tsuper(message, status);\n\t}\n}\nexport class NotFoundError extends KVError {\n\tconstructor(message: string = `Not Found`, status: number = 404) {\n\t\tsuper(message, status);\n\t}\n}\nexport class InternalError extends KVError {\n\tconstructor(\n\t\tmessage: string = `Internal Error in KV Asset Handler`,\n\t\tstatus: number = 500\n\t) {\n\t\tsuper(message, status);\n\t}\n}\n","import * as mime from \"mime\";\nimport {\n\tCacheControl,\n\tInternalError,\n\tMethodNotAllowedError,\n\tNotFoundError,\n\tOptions,\n} from \"./types\";\nimport type { AssetManifestType } from \"./types\";\n\nconst defaultCacheControl: CacheControl = {\n\tbrowserTTL: null,\n\tedgeTTL: 2 * 60 * 60 * 24, // 2 days\n\tbypassCache: false, // do not bypass Cloudflare's cache\n};\n\nconst parseStringAsObject = <T>(maybeString: string | T): T =>\n\ttypeof maybeString === \"string\"\n\t\t? (JSON.parse(maybeString) as T)\n\t\t: maybeString;\n\nfunction getAssetFromKVDefaultOptions(): Partial<Options> {\n\treturn {\n\t\tASSET_NAMESPACE:\n\t\t\ttypeof __STATIC_CONTENT !== \"undefined\" ? __STATIC_CONTENT : undefined,\n\t\tASSET_MANIFEST:\n\t\t\ttypeof __STATIC_CONTENT_MANIFEST !== \"undefined\"\n\t\t\t\t? parseStringAsObject<AssetManifestType>(__STATIC_CONTENT_MANIFEST)\n\t\t\t\t: {},\n\t\tcacheControl: defaultCacheControl,\n\t\tdefaultMimeType: \"text/plain\",\n\t\tdefaultDocument: \"index.html\",\n\t\tpathIsEncoded: false,\n\t\tdefaultETag: \"strong\",\n\t};\n}\n\nfunction assignOptions(options?: Partial<Options>): Options {\n\t// Assign any missing options passed in to the default\n\t// options.mapRequestToAsset is handled manually later\n\treturn <Options>Object.assign({}, getAssetFromKVDefaultOptions(), options);\n}\n\n/**\n * maps the path of incoming request to the request pathKey to look up\n * in bucket and in cache\n * e.g. for a path '/' returns '/index.html' which serves\n * the content of bucket/index.html\n * @param {Request} request incoming request\n */\nconst mapRequestToAsset = (request: Request, options?: Partial<Options>) => {\n\toptions = assignOptions(options);\n\n\tconst parsedUrl = new URL(request.url);\n\tlet pathname = parsedUrl.pathname;\n\n\tif (pathname.endsWith(\"/\")) {\n\t\t// If path looks like a directory append options.defaultDocument\n\t\t// e.g. If path is /about/ -> /about/index.html\n\t\tpathname = pathname.concat(options.defaultDocument);\n\t} else if (!mime.getType(pathname)) {\n\t\t// If path doesn't look like valid content\n\t\t// e.g. /about.me -> /about.me/index.html\n\t\tpathname = pathname.concat(\"/\" + options.defaultDocument);\n\t}\n\n\tparsedUrl.pathname = pathname;\n\treturn new Request(parsedUrl.toString(), request);\n};\n\n/**\n * maps the path of incoming request to /index.html if it evaluates to\n * any HTML file.\n * @param {Request} request incoming request\n */\nfunction serveSinglePageApp(\n\trequest: Request,\n\toptions?: Partial<Options>\n): Request {\n\toptions = assignOptions(options);\n\n\t// First apply the default handler, which already has logic to detect\n\t// paths that should map to HTML files.\n\trequest = mapRequestToAsset(request, options);\n\n\tconst parsedUrl = new URL(request.url);\n\n\t// Detect if the default handler decided to map to\n\t// a HTML file in some specific directory.\n\tif (parsedUrl.pathname.endsWith(\".html\")) {\n\t\t// If expected HTML file was missing, just return the root index.html (or options.defaultDocument)\n\t\treturn new Request(\n\t\t\t`${parsedUrl.origin}/${options.defaultDocument}`,\n\t\t\trequest\n\t\t);\n\t} else {\n\t\t// The default handler decided this is not an HTML page. It's probably\n\t\t// an image, CSS, or JS file. Leave it as-is.\n\t\treturn request;\n\t}\n}\n\n/**\n * takes the path of the incoming request, gathers the appropriate content from KV, and returns\n * the response\n *\n * @param {FetchEvent} event the fetch event of the triggered request\n * @param {{mapRequestToAsset: (string: Request) => Request, cacheControl: {bypassCache:boolean, edgeTTL: number, browserTTL:number}, ASSET_NAMESPACE: any, ASSET_MANIFEST:any}} [options] configurable options\n * @param {CacheControl} [options.cacheControl] determine how to cache on Cloudflare and the browser\n * @param {typeof(options.mapRequestToAsset)} [options.mapRequestToAsset] maps the path of incoming request to the request pathKey to look up\n * @param {Object | string} [options.ASSET_NAMESPACE] the binding to the namespace that script references\n * @param {any} [options.ASSET_MANIFEST] the map of the key to cache and store in KV\n * */\n\ntype Evt = {\n\trequest: Request;\n\twaitUntil: (promise: Promise<unknown>) => void;\n};\n\nconst getAssetFromKV = async (\n\tevent: Evt,\n\toptions?: Partial<Options>\n): Promise<Response> => {\n\toptions = assignOptions(options);\n\n\tconst request = event.request;\n\tconst ASSET_NAMESPACE = options.ASSET_NAMESPACE;\n\tconst ASSET_MANIFEST = parseStringAsObject<AssetManifestType>(\n\t\toptions.ASSET_MANIFEST\n\t);\n\n\tif (typeof ASSET_NAMESPACE === \"undefined\") {\n\t\tthrow new InternalError(`there is no KV namespace bound to the script`);\n\t}\n\n\tconst rawPathKey = new URL(request.url).pathname.replace(/^\\/+/, \"\"); // strip any preceding /'s\n\tlet pathIsEncoded = options.pathIsEncoded;\n\tlet requestKey;\n\t// if options.mapRequestToAsset is explicitly passed in, always use it and assume user has own intentions\n\t// otherwise handle request as normal, with default mapRequestToAsset below\n\tif (options.mapRequestToAsset) {\n\t\trequestKey = options.mapRequestToAsset(request);\n\t} else if (ASSET_MANIFEST[rawPathKey]) {\n\t\trequestKey = request;\n\t} else if (ASSET_MANIFEST[decodeURIComponent(rawPathKey)]) {\n\t\tpathIsEncoded = true;\n\t\trequestKey = request;\n\t} else {\n\t\tconst mappedRequest = mapRequestToAsset(request);\n\t\tconst mappedRawPathKey = new URL(mappedRequest.url).pathname.replace(\n\t\t\t/^\\/+/,\n\t\t\t\"\"\n\t\t);\n\t\tif (ASSET_MANIFEST[decodeURIComponent(mappedRawPathKey)]) {\n\t\t\tpathIsEncoded = true;\n\t\t\trequestKey = mappedRequest;\n\t\t} else {\n\t\t\t// use default mapRequestToAsset\n\t\t\trequestKey = mapRequestToAsset(request, options);\n\t\t}\n\t}\n\n\tconst SUPPORTED_METHODS = [\"GET\", \"HEAD\"];\n\tif (!SUPPORTED_METHODS.includes(requestKey.method)) {\n\t\tthrow new MethodNotAllowedError(\n\t\t\t`${requestKey.method} is not a valid request method`\n\t\t);\n\t}\n\n\tconst parsedUrl = new URL(requestKey.url);\n\tconst pathname = pathIsEncoded\n\t\t? decodeURIComponent(parsedUrl.pathname)\n\t\t: parsedUrl.pathname; // decode percentage encoded path only when necessary\n\n\t// pathKey is the file path to look up in the manifest\n\tlet pathKey = pathname.replace(/^\\/+/, \"\"); // remove prepended /\n\n\t// @ts-expect-error we should pick cf types here\n\tconst cache = caches.default;\n\tlet mimeType = mime.getType(pathKey) || options.defaultMimeType;\n\tif (mimeType.startsWith(\"text\") || mimeType === \"application/javascript\") {\n\t\tmimeType += \"; charset=utf-8\";\n\t}\n\n\tlet shouldEdgeCache = false; // false if storing in KV by raw file path i.e. no hash\n\t// check manifest for map from file path to hash\n\tif (typeof ASSET_MANIFEST !== \"undefined\") {\n\t\tif (ASSET_MANIFEST[pathKey]) {\n\t\t\tpathKey = ASSET_MANIFEST[pathKey];\n\t\t\t// if path key is in asset manifest, we can assume it contains a content hash and can be cached\n\t\t\tshouldEdgeCache = true;\n\t\t}\n\t}\n\n\t// TODO this excludes search params from cache, investigate ideal behavior\n\tconst cacheKey = new Request(`${parsedUrl.origin}/${pathKey}`, request);\n\n\t// if argument passed in for cacheControl is a function then\n\t// evaluate that function. otherwise return the Object passed in\n\t// or default Object\n\tconst evalCacheOpts = (() => {\n\t\tswitch (typeof options.cacheControl) {\n\t\t\tcase \"function\":\n\t\t\t\treturn options.cacheControl(request);\n\t\t\tcase \"object\":\n\t\t\t\treturn options.cacheControl;\n\t\t\tdefault:\n\t\t\t\treturn defaultCacheControl;\n\t\t}\n\t})();\n\n\t// formats the etag depending on the response context. if the entityId\n\t// is invalid, returns an empty string (instead of null) to prevent the\n\t// the potentially disastrous scenario where the value of the Etag resp\n\t// header is \"null\". Could be modified in future to base64 encode etc\n\tconst formatETag = (\n\t\tentityId: string = pathKey,\n\t\tvalidatorType: string = options.defaultETag\n\t) => {\n\t\tif (!entityId) {\n\t\t\treturn \"\";\n\t\t}\n\t\tswitch (validatorType) {\n\t\t\tcase \"weak\":\n\t\t\t\tif (!entityId.startsWith(\"W/\")) {\n\t\t\t\t\tif (entityId.startsWith(`\"`) && entityId.endsWith(`\"`)) {\n\t\t\t\t\t\treturn `W/${entityId}`;\n\t\t\t\t\t}\n\t\t\t\t\treturn `W/\"${entityId}\"`;\n\t\t\t\t}\n\t\t\t\treturn entityId;\n\t\t\tcase \"strong\":\n\t\t\t\tif (entityId.startsWith(`W/\"`)) {\n\t\t\t\t\tentityId = entityId.replace(\"W/\", \"\");\n\t\t\t\t}\n\t\t\t\tif (!entityId.endsWith(`\"`)) {\n\t\t\t\t\tentityId = `\"${entityId}\"`;\n\t\t\t\t}\n\t\t\t\treturn entityId;\n\t\t\tdefault:\n\t\t\t\treturn \"\";\n\t\t}\n\t};\n\n\toptions.cacheControl = Object.assign({}, defaultCacheControl, evalCacheOpts);\n\n\t// override shouldEdgeCache if options say to bypassCache\n\tif (\n\t\toptions.cacheControl.bypassCache ||\n\t\toptions.cacheControl.edgeTTL === null ||\n\t\trequest.method == \"HEAD\"\n\t) {\n\t\tshouldEdgeCache = false;\n\t}\n\t// only set max-age if explicitly passed in a number as an arg\n\tconst shouldSetBrowserCache =\n\t\ttypeof options.cacheControl.browserTTL === \"number\";\n\n\tlet response = null;\n\tif (shouldEdgeCache) {\n\t\tresponse = await cache.match(cacheKey);\n\t}\n\n\tif (response) {\n\t\tif (response.status > 300 && response.status < 400) {\n\t\t\tif (response.body && \"cancel\" in Object.getPrototypeOf(response.body)) {\n\t\t\t\t// Body exists and environment supports readable streams\n\t\t\t\tresponse.body.cancel();\n\t\t\t} else {\n\t\t\t\t// Environment doesnt support readable streams, or null repsonse body. Nothing to do\n\t\t\t}\n\t\t\tresponse = new Response(null, response);\n\t\t} else {\n\t\t\t// fixes #165\n\t\t\tconst opts = {\n\t\t\t\theaders: new Headers(response.headers),\n\t\t\t\tstatus: 0,\n\t\t\t\tstatusText: \"\",\n\t\t\t};\n\n\t\t\topts.headers.set(\"cf-cache-status\", \"HIT\");\n\n\t\t\tif (response.status) {\n\t\t\t\topts.status = response.status;\n\t\t\t\topts.statusText = response.statusText;\n\t\t\t} else if (opts.headers.has(\"Content-Range\")) {\n\t\t\t\topts.status = 206;\n\t\t\t\topts.statusText = \"Partial Content\";\n\t\t\t} else {\n\t\t\t\topts.status = 200;\n\t\t\t\topts.statusText = \"OK\";\n\t\t\t}\n\t\t\tresponse = new Response(response.body, opts);\n\t\t}\n\t} else {\n\t\tconst body = await ASSET_NAMESPACE.get(pathKey, \"arrayBuffer\");\n\t\tif (body === null) {\n\t\t\tthrow new NotFoundError(\n\t\t\t\t`could not find ${pathKey} in your content namespace`\n\t\t\t);\n\t\t}\n\t\tresponse = new Response(body);\n\n\t\tif (shouldEdgeCache) {\n\t\t\tresponse.headers.set(\"Accept-Ranges\", \"bytes\");\n\t\t\tresponse.headers.set(\"Content-Length\", String(body.byteLength));\n\t\t\t// set etag before cache insertion\n\t\t\tif (!response.headers.has(\"etag\")) {\n\t\t\t\tresponse.headers.set(\"etag\", formatETag(pathKey));\n\t\t\t}\n\t\t\t// determine Cloudflare cache behavior\n\t\t\tresponse.headers.set(\n\t\t\t\t\"Cache-Control\",\n\t\t\t\t`max-age=${options.cacheControl.edgeTTL}`\n\t\t\t);\n\t\t\tevent.waitUntil(cache.put(cacheKey, response.clone()));\n\t\t\tresponse.headers.set(\"CF-Cache-Status\", \"MISS\");\n\t\t}\n\t}\n\tresponse.headers.set(\"Content-Type\", mimeType);\n\n\tif (response.status === 304) {\n\t\tconst etag = formatETag(response.headers.get(\"etag\"));\n\t\tconst ifNoneMatch = cacheKey.headers.get(\"if-none-match\");\n\t\tconst proxyCacheStatus = response.headers.get(\"CF-Cache-Status\");\n\t\tif (etag) {\n\t\t\tif (ifNoneMatch && ifNoneMatch === etag && proxyCacheStatus === \"MISS\") {\n\t\t\t\tresponse.headers.set(\"CF-Cache-Status\", \"EXPIRED\");\n\t\t\t} else {\n\t\t\t\tresponse.headers.set(\"CF-Cache-Status\", \"REVALIDATED\");\n\t\t\t}\n\t\t\tresponse.headers.set(\"etag\", formatETag(etag, \"weak\"));\n\t\t}\n\t}\n\tif (shouldSetBrowserCache) {\n\t\tresponse.headers.set(\n\t\t\t\"Cache-Control\",\n\t\t\t`max-age=${options.cacheControl.browserTTL}`\n\t\t);\n\t} else {\n\t\tresponse.headers.delete(\"Cache-Control\");\n\t}\n\treturn response;\n};\n\nexport { getAssetFromKV, mapRequestToAsset, serveSinglePageApp };\nexport {\n\tOptions,\n\tCacheControl,\n\tMethodNotAllowedError,\n\tNotFoundError,\n\tInternalError,\n};\n"]} |
| {"inputs":{"src/types.ts":{"bytes":1545,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":11207,"imports":[{"path":"mime","kind":"import-statement","external":true},{"path":"src/types.ts","kind":"import-statement","original":"./types"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":19598},"dist/index.js":{"imports":[{"path":"mime","kind":"import-statement","external":true}],"exports":["CacheControl","InternalError","MethodNotAllowedError","NotFoundError","Options","getAssetFromKV","mapRequestToAsset","serveSinglePageApp"],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":7750},"src/types.ts":{"bytesInOutput":881}},"bytes":8942}}} |
+34
-4
@@ -1,5 +0,35 @@ | ||
| import { CacheControl, InternalError, MethodNotAllowedError, NotFoundError, Options } from "./types"; | ||
| declare global { | ||
| const __STATIC_CONTENT: KVNamespace | undefined, __STATIC_CONTENT_MANIFEST: string; | ||
| const __STATIC_CONTENT: KVNamespace | undefined; | ||
| const __STATIC_CONTENT_MANIFEST: Record<string, string> | undefined; | ||
| } | ||
| type CacheControl = { | ||
| browserTTL: number; | ||
| edgeTTL: number; | ||
| bypassCache: boolean; | ||
| }; | ||
| type AssetManifestType = Record<string, string>; | ||
| type Options = { | ||
| cacheControl: ((req: Request) => Partial<CacheControl>) | Partial<CacheControl>; | ||
| ASSET_NAMESPACE: KVNamespace; | ||
| ASSET_MANIFEST: AssetManifestType | string; | ||
| mapRequestToAsset?: (req: Request, options?: Partial<Options>) => Request; | ||
| defaultMimeType: string; | ||
| defaultDocument: string; | ||
| pathIsEncoded: boolean; | ||
| defaultETag: "strong" | "weak"; | ||
| }; | ||
| declare class KVError extends Error { | ||
| constructor(message?: string, status?: number); | ||
| status: number; | ||
| } | ||
| declare class MethodNotAllowedError extends KVError { | ||
| constructor(message?: string, status?: number); | ||
| } | ||
| declare class NotFoundError extends KVError { | ||
| constructor(message?: string, status?: number); | ||
| } | ||
| declare class InternalError extends KVError { | ||
| constructor(message?: string, status?: number); | ||
| } | ||
| /** | ||
@@ -35,3 +65,3 @@ * maps the path of incoming request to the request pathKey to look up | ||
| declare const getAssetFromKV: (event: Evt, options?: Partial<Options>) => Promise<Response>; | ||
| export { getAssetFromKV, mapRequestToAsset, serveSinglePageApp }; | ||
| export { Options, CacheControl, MethodNotAllowedError, NotFoundError, InternalError, }; | ||
| export { type CacheControl, InternalError, MethodNotAllowedError, NotFoundError, type Options, getAssetFromKV, mapRequestToAsset, serveSinglePageApp }; |
+247
-287
@@ -1,56 +0,57 @@ | ||
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.InternalError = exports.NotFoundError = exports.MethodNotAllowedError = exports.mapRequestToAsset = exports.getAssetFromKV = void 0; | ||
| exports.serveSinglePageApp = serveSinglePageApp; | ||
| const mime = __importStar(require("mime")); | ||
| const types_1 = require("./types"); | ||
| Object.defineProperty(exports, "InternalError", { enumerable: true, get: function () { return types_1.InternalError; } }); | ||
| Object.defineProperty(exports, "MethodNotAllowedError", { enumerable: true, get: function () { return types_1.MethodNotAllowedError; } }); | ||
| Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return types_1.NotFoundError; } }); | ||
| const defaultCacheControl = { | ||
| browserTTL: null, | ||
| edgeTTL: 2 * 60 * 60 * 24, // 2 days | ||
| bypassCache: false, // do not bypass Cloudflare's cache | ||
| import * as mime from 'mime'; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| // src/types.ts | ||
| var KVError = class _KVError extends Error { | ||
| static { | ||
| __name(this, "KVError"); | ||
| } | ||
| constructor(message, status = 500) { | ||
| super(message); | ||
| Object.setPrototypeOf(this, new.target.prototype); | ||
| this.name = _KVError.name; | ||
| this.status = status; | ||
| } | ||
| status; | ||
| }; | ||
| const parseStringAsObject = (maybeString) => typeof maybeString === "string" | ||
| ? JSON.parse(maybeString) | ||
| : maybeString; | ||
| const getAssetFromKVDefaultOptions = { | ||
| ASSET_NAMESPACE: typeof __STATIC_CONTENT !== "undefined" ? __STATIC_CONTENT : undefined, | ||
| ASSET_MANIFEST: typeof __STATIC_CONTENT_MANIFEST !== "undefined" | ||
| ? parseStringAsObject(__STATIC_CONTENT_MANIFEST) | ||
| : {}, | ||
| var MethodNotAllowedError = class extends KVError { | ||
| static { | ||
| __name(this, "MethodNotAllowedError"); | ||
| } | ||
| constructor(message = `Not a valid request method`, status = 405) { | ||
| super(message, status); | ||
| } | ||
| }; | ||
| var NotFoundError = class extends KVError { | ||
| static { | ||
| __name(this, "NotFoundError"); | ||
| } | ||
| constructor(message = `Not Found`, status = 404) { | ||
| super(message, status); | ||
| } | ||
| }; | ||
| var InternalError = class extends KVError { | ||
| static { | ||
| __name(this, "InternalError"); | ||
| } | ||
| constructor(message = `Internal Error in KV Asset Handler`, status = 500) { | ||
| super(message, status); | ||
| } | ||
| }; | ||
| // src/index.ts | ||
| var defaultCacheControl = { | ||
| browserTTL: null, | ||
| edgeTTL: 2 * 60 * 60 * 24, | ||
| // 2 days | ||
| bypassCache: false | ||
| // do not bypass Cloudflare's cache | ||
| }; | ||
| var parseStringAsObject = /* @__PURE__ */ __name((maybeString) => typeof maybeString === "string" ? JSON.parse(maybeString) : maybeString, "parseStringAsObject"); | ||
| function getAssetFromKVDefaultOptions() { | ||
| return { | ||
| ASSET_NAMESPACE: typeof __STATIC_CONTENT !== "undefined" ? __STATIC_CONTENT : void 0, | ||
| ASSET_MANIFEST: typeof __STATIC_CONTENT_MANIFEST !== "undefined" ? parseStringAsObject(__STATIC_CONTENT_MANIFEST) : {}, | ||
| cacheControl: defaultCacheControl, | ||
@@ -60,250 +61,209 @@ defaultMimeType: "text/plain", | ||
| pathIsEncoded: false, | ||
| defaultETag: "strong", | ||
| }; | ||
| defaultETag: "strong" | ||
| }; | ||
| } | ||
| __name(getAssetFromKVDefaultOptions, "getAssetFromKVDefaultOptions"); | ||
| function assignOptions(options) { | ||
| // Assign any missing options passed in to the default | ||
| // options.mapRequestToAsset is handled manually later | ||
| return Object.assign({}, getAssetFromKVDefaultOptions, options); | ||
| return Object.assign({}, getAssetFromKVDefaultOptions(), options); | ||
| } | ||
| /** | ||
| * maps the path of incoming request to the request pathKey to look up | ||
| * in bucket and in cache | ||
| * e.g. for a path '/' returns '/index.html' which serves | ||
| * the content of bucket/index.html | ||
| * @param {Request} request incoming request | ||
| */ | ||
| const mapRequestToAsset = (request, options) => { | ||
| options = assignOptions(options); | ||
| const parsedUrl = new URL(request.url); | ||
| let pathname = parsedUrl.pathname; | ||
| if (pathname.endsWith("/")) { | ||
| // If path looks like a directory append options.defaultDocument | ||
| // e.g. If path is /about/ -> /about/index.html | ||
| pathname = pathname.concat(options.defaultDocument); | ||
| } | ||
| else if (!mime.getType(pathname)) { | ||
| // If path doesn't look like valid content | ||
| // e.g. /about.me -> /about.me/index.html | ||
| pathname = pathname.concat("/" + options.defaultDocument); | ||
| } | ||
| parsedUrl.pathname = pathname; | ||
| return new Request(parsedUrl.toString(), request); | ||
| }; | ||
| exports.mapRequestToAsset = mapRequestToAsset; | ||
| /** | ||
| * maps the path of incoming request to /index.html if it evaluates to | ||
| * any HTML file. | ||
| * @param {Request} request incoming request | ||
| */ | ||
| __name(assignOptions, "assignOptions"); | ||
| var mapRequestToAsset = /* @__PURE__ */ __name((request, options) => { | ||
| options = assignOptions(options); | ||
| const parsedUrl = new URL(request.url); | ||
| let pathname = parsedUrl.pathname; | ||
| if (pathname.endsWith("/")) { | ||
| pathname = pathname.concat(options.defaultDocument); | ||
| } else if (!mime.getType(pathname)) { | ||
| pathname = pathname.concat("/" + options.defaultDocument); | ||
| } | ||
| parsedUrl.pathname = pathname; | ||
| return new Request(parsedUrl.toString(), request); | ||
| }, "mapRequestToAsset"); | ||
| function serveSinglePageApp(request, options) { | ||
| options = assignOptions(options); | ||
| // First apply the default handler, which already has logic to detect | ||
| // paths that should map to HTML files. | ||
| request = mapRequestToAsset(request, options); | ||
| const parsedUrl = new URL(request.url); | ||
| // Detect if the default handler decided to map to | ||
| // a HTML file in some specific directory. | ||
| if (parsedUrl.pathname.endsWith(".html")) { | ||
| // If expected HTML file was missing, just return the root index.html (or options.defaultDocument) | ||
| return new Request(`${parsedUrl.origin}/${options.defaultDocument}`, request); | ||
| } | ||
| else { | ||
| // The default handler decided this is not an HTML page. It's probably | ||
| // an image, CSS, or JS file. Leave it as-is. | ||
| return request; | ||
| } | ||
| options = assignOptions(options); | ||
| request = mapRequestToAsset(request, options); | ||
| const parsedUrl = new URL(request.url); | ||
| if (parsedUrl.pathname.endsWith(".html")) { | ||
| return new Request( | ||
| `${parsedUrl.origin}/${options.defaultDocument}`, | ||
| request | ||
| ); | ||
| } else { | ||
| return request; | ||
| } | ||
| } | ||
| const getAssetFromKV = async (event, options) => { | ||
| options = assignOptions(options); | ||
| const request = event.request; | ||
| const ASSET_NAMESPACE = options.ASSET_NAMESPACE; | ||
| const ASSET_MANIFEST = parseStringAsObject(options.ASSET_MANIFEST); | ||
| if (typeof ASSET_NAMESPACE === "undefined") { | ||
| throw new types_1.InternalError(`there is no KV namespace bound to the script`); | ||
| __name(serveSinglePageApp, "serveSinglePageApp"); | ||
| var getAssetFromKV = /* @__PURE__ */ __name(async (event, options) => { | ||
| options = assignOptions(options); | ||
| const request = event.request; | ||
| const ASSET_NAMESPACE = options.ASSET_NAMESPACE; | ||
| const ASSET_MANIFEST = parseStringAsObject( | ||
| options.ASSET_MANIFEST | ||
| ); | ||
| if (typeof ASSET_NAMESPACE === "undefined") { | ||
| throw new InternalError(`there is no KV namespace bound to the script`); | ||
| } | ||
| const rawPathKey = new URL(request.url).pathname.replace(/^\/+/, ""); | ||
| let pathIsEncoded = options.pathIsEncoded; | ||
| let requestKey; | ||
| if (options.mapRequestToAsset) { | ||
| requestKey = options.mapRequestToAsset(request); | ||
| } else if (ASSET_MANIFEST[rawPathKey]) { | ||
| requestKey = request; | ||
| } else if (ASSET_MANIFEST[decodeURIComponent(rawPathKey)]) { | ||
| pathIsEncoded = true; | ||
| requestKey = request; | ||
| } else { | ||
| const mappedRequest = mapRequestToAsset(request); | ||
| const mappedRawPathKey = new URL(mappedRequest.url).pathname.replace( | ||
| /^\/+/, | ||
| "" | ||
| ); | ||
| if (ASSET_MANIFEST[decodeURIComponent(mappedRawPathKey)]) { | ||
| pathIsEncoded = true; | ||
| requestKey = mappedRequest; | ||
| } else { | ||
| requestKey = mapRequestToAsset(request, options); | ||
| } | ||
| const rawPathKey = new URL(request.url).pathname.replace(/^\/+/, ""); // strip any preceding /'s | ||
| let pathIsEncoded = options.pathIsEncoded; | ||
| let requestKey; | ||
| // if options.mapRequestToAsset is explicitly passed in, always use it and assume user has own intentions | ||
| // otherwise handle request as normal, with default mapRequestToAsset below | ||
| if (options.mapRequestToAsset) { | ||
| requestKey = options.mapRequestToAsset(request); | ||
| } | ||
| const SUPPORTED_METHODS = ["GET", "HEAD"]; | ||
| if (!SUPPORTED_METHODS.includes(requestKey.method)) { | ||
| throw new MethodNotAllowedError( | ||
| `${requestKey.method} is not a valid request method` | ||
| ); | ||
| } | ||
| const parsedUrl = new URL(requestKey.url); | ||
| const pathname = pathIsEncoded ? decodeURIComponent(parsedUrl.pathname) : parsedUrl.pathname; | ||
| let pathKey = pathname.replace(/^\/+/, ""); | ||
| const cache = caches.default; | ||
| let mimeType = mime.getType(pathKey) || options.defaultMimeType; | ||
| if (mimeType.startsWith("text") || mimeType === "application/javascript") { | ||
| mimeType += "; charset=utf-8"; | ||
| } | ||
| let shouldEdgeCache = false; | ||
| if (typeof ASSET_MANIFEST !== "undefined") { | ||
| if (ASSET_MANIFEST[pathKey]) { | ||
| pathKey = ASSET_MANIFEST[pathKey]; | ||
| shouldEdgeCache = true; | ||
| } | ||
| else if (ASSET_MANIFEST[rawPathKey]) { | ||
| requestKey = request; | ||
| } | ||
| const cacheKey = new Request(`${parsedUrl.origin}/${pathKey}`, request); | ||
| const evalCacheOpts = (() => { | ||
| switch (typeof options.cacheControl) { | ||
| case "function": | ||
| return options.cacheControl(request); | ||
| case "object": | ||
| return options.cacheControl; | ||
| default: | ||
| return defaultCacheControl; | ||
| } | ||
| else if (ASSET_MANIFEST[decodeURIComponent(rawPathKey)]) { | ||
| pathIsEncoded = true; | ||
| requestKey = request; | ||
| })(); | ||
| const formatETag = /* @__PURE__ */ __name((entityId = pathKey, validatorType = options.defaultETag) => { | ||
| if (!entityId) { | ||
| return ""; | ||
| } | ||
| else { | ||
| const mappedRequest = mapRequestToAsset(request); | ||
| const mappedRawPathKey = new URL(mappedRequest.url).pathname.replace(/^\/+/, ""); | ||
| if (ASSET_MANIFEST[decodeURIComponent(mappedRawPathKey)]) { | ||
| pathIsEncoded = true; | ||
| requestKey = mappedRequest; | ||
| switch (validatorType) { | ||
| case "weak": | ||
| if (!entityId.startsWith("W/")) { | ||
| if (entityId.startsWith(`"`) && entityId.endsWith(`"`)) { | ||
| return `W/${entityId}`; | ||
| } | ||
| return `W/"${entityId}"`; | ||
| } | ||
| else { | ||
| // use default mapRequestToAsset | ||
| requestKey = mapRequestToAsset(request, options); | ||
| return entityId; | ||
| case "strong": | ||
| if (entityId.startsWith(`W/"`)) { | ||
| entityId = entityId.replace("W/", ""); | ||
| } | ||
| if (!entityId.endsWith(`"`)) { | ||
| entityId = `"${entityId}"`; | ||
| } | ||
| return entityId; | ||
| default: | ||
| return ""; | ||
| } | ||
| const SUPPORTED_METHODS = ["GET", "HEAD"]; | ||
| if (!SUPPORTED_METHODS.includes(requestKey.method)) { | ||
| throw new types_1.MethodNotAllowedError(`${requestKey.method} is not a valid request method`); | ||
| }, "formatETag"); | ||
| options.cacheControl = Object.assign({}, defaultCacheControl, evalCacheOpts); | ||
| if (options.cacheControl.bypassCache || options.cacheControl.edgeTTL === null || request.method == "HEAD") { | ||
| shouldEdgeCache = false; | ||
| } | ||
| const shouldSetBrowserCache = typeof options.cacheControl.browserTTL === "number"; | ||
| let response = null; | ||
| if (shouldEdgeCache) { | ||
| response = await cache.match(cacheKey); | ||
| } | ||
| if (response) { | ||
| if (response.status > 300 && response.status < 400) { | ||
| if (response.body && "cancel" in Object.getPrototypeOf(response.body)) { | ||
| response.body.cancel(); | ||
| } | ||
| response = new Response(null, response); | ||
| } else { | ||
| const opts = { | ||
| headers: new Headers(response.headers), | ||
| status: 0, | ||
| statusText: "" | ||
| }; | ||
| opts.headers.set("cf-cache-status", "HIT"); | ||
| if (response.status) { | ||
| opts.status = response.status; | ||
| opts.statusText = response.statusText; | ||
| } else if (opts.headers.has("Content-Range")) { | ||
| opts.status = 206; | ||
| opts.statusText = "Partial Content"; | ||
| } else { | ||
| opts.status = 200; | ||
| opts.statusText = "OK"; | ||
| } | ||
| response = new Response(response.body, opts); | ||
| } | ||
| const parsedUrl = new URL(requestKey.url); | ||
| const pathname = pathIsEncoded | ||
| ? decodeURIComponent(parsedUrl.pathname) | ||
| : parsedUrl.pathname; // decode percentage encoded path only when necessary | ||
| // pathKey is the file path to look up in the manifest | ||
| let pathKey = pathname.replace(/^\/+/, ""); // remove prepended / | ||
| // @ts-expect-error we should pick cf types here | ||
| const cache = caches.default; | ||
| let mimeType = mime.getType(pathKey) || options.defaultMimeType; | ||
| if (mimeType.startsWith("text") || mimeType === "application/javascript") { | ||
| mimeType += "; charset=utf-8"; | ||
| } else { | ||
| const body = await ASSET_NAMESPACE.get(pathKey, "arrayBuffer"); | ||
| if (body === null) { | ||
| throw new NotFoundError( | ||
| `could not find ${pathKey} in your content namespace` | ||
| ); | ||
| } | ||
| let shouldEdgeCache = false; // false if storing in KV by raw file path i.e. no hash | ||
| // check manifest for map from file path to hash | ||
| if (typeof ASSET_MANIFEST !== "undefined") { | ||
| if (ASSET_MANIFEST[pathKey]) { | ||
| pathKey = ASSET_MANIFEST[pathKey]; | ||
| // if path key is in asset manifest, we can assume it contains a content hash and can be cached | ||
| shouldEdgeCache = true; | ||
| } | ||
| } | ||
| // TODO this excludes search params from cache, investigate ideal behavior | ||
| const cacheKey = new Request(`${parsedUrl.origin}/${pathKey}`, request); | ||
| // if argument passed in for cacheControl is a function then | ||
| // evaluate that function. otherwise return the Object passed in | ||
| // or default Object | ||
| const evalCacheOpts = (() => { | ||
| switch (typeof options.cacheControl) { | ||
| case "function": | ||
| return options.cacheControl(request); | ||
| case "object": | ||
| return options.cacheControl; | ||
| default: | ||
| return defaultCacheControl; | ||
| } | ||
| })(); | ||
| // formats the etag depending on the response context. if the entityId | ||
| // is invalid, returns an empty string (instead of null) to prevent the | ||
| // the potentially disastrous scenario where the value of the Etag resp | ||
| // header is "null". Could be modified in future to base64 encode etc | ||
| const formatETag = (entityId = pathKey, validatorType = options.defaultETag) => { | ||
| if (!entityId) { | ||
| return ""; | ||
| } | ||
| switch (validatorType) { | ||
| case "weak": | ||
| if (!entityId.startsWith("W/")) { | ||
| if (entityId.startsWith(`"`) && entityId.endsWith(`"`)) { | ||
| return `W/${entityId}`; | ||
| } | ||
| return `W/"${entityId}"`; | ||
| } | ||
| return entityId; | ||
| case "strong": | ||
| if (entityId.startsWith(`W/"`)) { | ||
| entityId = entityId.replace("W/", ""); | ||
| } | ||
| if (!entityId.endsWith(`"`)) { | ||
| entityId = `"${entityId}"`; | ||
| } | ||
| return entityId; | ||
| default: | ||
| return ""; | ||
| } | ||
| }; | ||
| options.cacheControl = Object.assign({}, defaultCacheControl, evalCacheOpts); | ||
| // override shouldEdgeCache if options say to bypassCache | ||
| if (options.cacheControl.bypassCache || | ||
| options.cacheControl.edgeTTL === null || | ||
| request.method == "HEAD") { | ||
| shouldEdgeCache = false; | ||
| } | ||
| // only set max-age if explicitly passed in a number as an arg | ||
| const shouldSetBrowserCache = typeof options.cacheControl.browserTTL === "number"; | ||
| let response = null; | ||
| response = new Response(body); | ||
| if (shouldEdgeCache) { | ||
| response = await cache.match(cacheKey); | ||
| response.headers.set("Accept-Ranges", "bytes"); | ||
| response.headers.set("Content-Length", String(body.byteLength)); | ||
| if (!response.headers.has("etag")) { | ||
| response.headers.set("etag", formatETag(pathKey)); | ||
| } | ||
| response.headers.set( | ||
| "Cache-Control", | ||
| `max-age=${options.cacheControl.edgeTTL}` | ||
| ); | ||
| event.waitUntil(cache.put(cacheKey, response.clone())); | ||
| response.headers.set("CF-Cache-Status", "MISS"); | ||
| } | ||
| if (response) { | ||
| if (response.status > 300 && response.status < 400) { | ||
| if (response.body && "cancel" in Object.getPrototypeOf(response.body)) { | ||
| // Body exists and environment supports readable streams | ||
| response.body.cancel(); | ||
| } | ||
| else { | ||
| // Environment doesnt support readable streams, or null repsonse body. Nothing to do | ||
| } | ||
| response = new Response(null, response); | ||
| } | ||
| else { | ||
| // fixes #165 | ||
| const opts = { | ||
| headers: new Headers(response.headers), | ||
| status: 0, | ||
| statusText: "", | ||
| }; | ||
| opts.headers.set("cf-cache-status", "HIT"); | ||
| if (response.status) { | ||
| opts.status = response.status; | ||
| opts.statusText = response.statusText; | ||
| } | ||
| else if (opts.headers.has("Content-Range")) { | ||
| opts.status = 206; | ||
| opts.statusText = "Partial Content"; | ||
| } | ||
| else { | ||
| opts.status = 200; | ||
| opts.statusText = "OK"; | ||
| } | ||
| response = new Response(response.body, opts); | ||
| } | ||
| } | ||
| response.headers.set("Content-Type", mimeType); | ||
| if (response.status === 304) { | ||
| const etag = formatETag(response.headers.get("etag")); | ||
| const ifNoneMatch = cacheKey.headers.get("if-none-match"); | ||
| const proxyCacheStatus = response.headers.get("CF-Cache-Status"); | ||
| if (etag) { | ||
| if (ifNoneMatch && ifNoneMatch === etag && proxyCacheStatus === "MISS") { | ||
| response.headers.set("CF-Cache-Status", "EXPIRED"); | ||
| } else { | ||
| response.headers.set("CF-Cache-Status", "REVALIDATED"); | ||
| } | ||
| response.headers.set("etag", formatETag(etag, "weak")); | ||
| } | ||
| else { | ||
| const body = await ASSET_NAMESPACE.get(pathKey, "arrayBuffer"); | ||
| if (body === null) { | ||
| throw new types_1.NotFoundError(`could not find ${pathKey} in your content namespace`); | ||
| } | ||
| response = new Response(body); | ||
| if (shouldEdgeCache) { | ||
| response.headers.set("Accept-Ranges", "bytes"); | ||
| response.headers.set("Content-Length", String(body.byteLength)); | ||
| // set etag before cache insertion | ||
| if (!response.headers.has("etag")) { | ||
| response.headers.set("etag", formatETag(pathKey)); | ||
| } | ||
| // determine Cloudflare cache behavior | ||
| response.headers.set("Cache-Control", `max-age=${options.cacheControl.edgeTTL}`); | ||
| event.waitUntil(cache.put(cacheKey, response.clone())); | ||
| response.headers.set("CF-Cache-Status", "MISS"); | ||
| } | ||
| } | ||
| response.headers.set("Content-Type", mimeType); | ||
| if (response.status === 304) { | ||
| const etag = formatETag(response.headers.get("etag")); | ||
| const ifNoneMatch = cacheKey.headers.get("if-none-match"); | ||
| const proxyCacheStatus = response.headers.get("CF-Cache-Status"); | ||
| if (etag) { | ||
| if (ifNoneMatch && ifNoneMatch === etag && proxyCacheStatus === "MISS") { | ||
| response.headers.set("CF-Cache-Status", "EXPIRED"); | ||
| } | ||
| else { | ||
| response.headers.set("CF-Cache-Status", "REVALIDATED"); | ||
| } | ||
| response.headers.set("etag", formatETag(etag, "weak")); | ||
| } | ||
| } | ||
| if (shouldSetBrowserCache) { | ||
| response.headers.set("Cache-Control", `max-age=${options.cacheControl.browserTTL}`); | ||
| } | ||
| else { | ||
| response.headers.delete("Cache-Control"); | ||
| } | ||
| return response; | ||
| }; | ||
| exports.getAssetFromKV = getAssetFromKV; | ||
| } | ||
| if (shouldSetBrowserCache) { | ||
| response.headers.set( | ||
| "Cache-Control", | ||
| `max-age=${options.cacheControl.browserTTL}` | ||
| ); | ||
| } else { | ||
| response.headers.delete("Cache-Control"); | ||
| } | ||
| return response; | ||
| }, "getAssetFromKV"); | ||
| export { InternalError, MethodNotAllowedError, NotFoundError, getAssetFromKV, mapRequestToAsset, serveSinglePageApp }; | ||
| //# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
+13
-13
| { | ||
| "name": "@cloudflare/kv-asset-handler", | ||
| "version": "0.4.0", | ||
| "version": "0.4.1", | ||
| "description": "Routes requests to KV assets", | ||
@@ -23,2 +23,3 @@ "keywords": [ | ||
| "author": "wrangler@cloudflare.com", | ||
| "type": "module", | ||
| "main": "dist/index.js", | ||
@@ -28,5 +29,3 @@ "types": "dist/index.d.ts", | ||
| "src", | ||
| "dist", | ||
| "!src/test", | ||
| "!dist/test" | ||
| "dist" | ||
| ], | ||
@@ -37,9 +36,10 @@ "dependencies": { | ||
| "devDependencies": { | ||
| "@ava/typescript": "^4.1.0", | ||
| "@cloudflare/workers-types": "^4.20250310.0", | ||
| "@cloudflare/vitest-pool-workers": "^0.7.0", | ||
| "@cloudflare/workers-types": "^4.20251125.0", | ||
| "@types/mime": "^3.0.4", | ||
| "@types/node": "^18.19.75", | ||
| "@types/service-worker-mock": "^2.0.1", | ||
| "ava": "^6.0.1", | ||
| "service-worker-mock": "^2.0.5" | ||
| "@types/node": "^20.19.9", | ||
| "eslint": "^9.39.1", | ||
| "tsup": "8.3.0", | ||
| "vitest": "~2.1.0", | ||
| "@cloudflare/eslint-config-shared": "1.1.0" | ||
| }, | ||
@@ -59,9 +59,9 @@ "engines": { | ||
| "scripts": { | ||
| "build": "tsc -d", | ||
| "build": "tsup", | ||
| "check:lint": "eslint . --max-warnings=0", | ||
| "check:type": "tsc", | ||
| "pretest": "npm run build", | ||
| "test": "ava dist/test/*.js --verbose", | ||
| "test:ci": "npm run build && ava dist/test/*.js --verbose" | ||
| "test": "vitest", | ||
| "test:ci": "vitest run" | ||
| } | ||
| } |
+16
-19
@@ -11,7 +11,2 @@ import * as mime from "mime"; | ||
| declare global { | ||
| const __STATIC_CONTENT: KVNamespace | undefined, | ||
| __STATIC_CONTENT_MANIFEST: string; | ||
| } | ||
| const defaultCacheControl: CacheControl = { | ||
@@ -28,15 +23,17 @@ browserTTL: null, | ||
| const getAssetFromKVDefaultOptions: Partial<Options> = { | ||
| ASSET_NAMESPACE: | ||
| typeof __STATIC_CONTENT !== "undefined" ? __STATIC_CONTENT : undefined, | ||
| ASSET_MANIFEST: | ||
| typeof __STATIC_CONTENT_MANIFEST !== "undefined" | ||
| ? parseStringAsObject<AssetManifestType>(__STATIC_CONTENT_MANIFEST) | ||
| : {}, | ||
| cacheControl: defaultCacheControl, | ||
| defaultMimeType: "text/plain", | ||
| defaultDocument: "index.html", | ||
| pathIsEncoded: false, | ||
| defaultETag: "strong", | ||
| }; | ||
| function getAssetFromKVDefaultOptions(): Partial<Options> { | ||
| return { | ||
| ASSET_NAMESPACE: | ||
| typeof __STATIC_CONTENT !== "undefined" ? __STATIC_CONTENT : undefined, | ||
| ASSET_MANIFEST: | ||
| typeof __STATIC_CONTENT_MANIFEST !== "undefined" | ||
| ? parseStringAsObject<AssetManifestType>(__STATIC_CONTENT_MANIFEST) | ||
| : {}, | ||
| cacheControl: defaultCacheControl, | ||
| defaultMimeType: "text/plain", | ||
| defaultDocument: "index.html", | ||
| pathIsEncoded: false, | ||
| defaultETag: "strong", | ||
| }; | ||
| } | ||
@@ -46,3 +43,3 @@ function assignOptions(options?: Partial<Options>): Options { | ||
| // options.mapRequestToAsset is handled manually later | ||
| return <Options>Object.assign({}, getAssetFromKVDefaultOptions, options); | ||
| return <Options>Object.assign({}, getAssetFromKVDefaultOptions(), options); | ||
| } | ||
@@ -49,0 +46,0 @@ |
+5
-0
@@ -0,1 +1,6 @@ | ||
| declare global { | ||
| const __STATIC_CONTENT: KVNamespace | undefined; | ||
| const __STATIC_CONTENT_MANIFEST: Record<string, string> | undefined; | ||
| } | ||
| export type CacheControl = { | ||
@@ -2,0 +7,0 @@ browserTTL: number; |
| export declare const getEvent: (request: Request) => Pick<FetchEvent, "request" | "waitUntil">; | ||
| export declare const mockKV: (kvStore: Record<string, string>) => { | ||
| get: (path: string) => string; | ||
| }; | ||
| export declare const mockManifest: () => string; | ||
| export declare const mockCaches: () => { | ||
| default: { | ||
| match(key: Request): Promise<Response>; | ||
| put(key: Request, val: Response): Promise<void>; | ||
| }; | ||
| }; | ||
| export declare function mockRequestScope(): void; | ||
| export declare function mockGlobalScope(): void; | ||
| export declare const sleep: (milliseconds: number) => Promise<unknown>; |
-153
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.sleep = exports.mockCaches = exports.mockManifest = exports.mockKV = exports.getEvent = void 0; | ||
| exports.mockRequestScope = mockRequestScope; | ||
| exports.mockGlobalScope = mockGlobalScope; | ||
| const service_worker_mock_1 = __importDefault(require("service-worker-mock")); | ||
| const HASH = "123HASHBROWN"; | ||
| const getEvent = (request) => { | ||
| const waitUntil = async (maybePromise) => { | ||
| await maybePromise; | ||
| }; | ||
| return { | ||
| request, | ||
| waitUntil, | ||
| }; | ||
| }; | ||
| exports.getEvent = getEvent; | ||
| const store = { | ||
| "key1.123HASHBROWN.txt": "val1", | ||
| "key1.123HASHBROWN.png": "val1", | ||
| "index.123HASHBROWN.html": "index.html", | ||
| "cache.123HASHBROWN.html": "cache me if you can", | ||
| "测试.123HASHBROWN.html": "My filename is non-ascii", | ||
| "%not-really-percent-encoded.123HASHBROWN.html": "browser percent encoded", | ||
| "%2F.123HASHBROWN.html": "user percent encoded", | ||
| "你好.123HASHBROWN.html": "I shouldnt be served", | ||
| "%E4%BD%A0%E5%A5%BD.123HASHBROWN.html": "Im important", | ||
| "nohash.txt": "no hash but still got some result", | ||
| "sub/blah.123HASHBROWN.png": "picturedis", | ||
| "sub/index.123HASHBROWN.html": "picturedis", | ||
| "client.123HASHBROWN": "important file", | ||
| "client.123HASHBROWN/index.html": "Im here but serve my big bro above", | ||
| "image.123HASHBROWN.png": "imagepng", | ||
| "image.123HASHBROWN.webp": "imagewebp", | ||
| "你好/index.123HASHBROWN.html": "My path is non-ascii", | ||
| }; | ||
| const mockKV = (kvStore) => { | ||
| return { | ||
| get: (path) => kvStore[path] || null, | ||
| }; | ||
| }; | ||
| exports.mockKV = mockKV; | ||
| const mockManifest = () => { | ||
| return JSON.stringify({ | ||
| "key1.txt": `key1.${HASH}.txt`, | ||
| "key1.png": `key1.${HASH}.png`, | ||
| "cache.html": `cache.${HASH}.html`, | ||
| "测试.html": `测试.${HASH}.html`, | ||
| "你好.html": `你好.${HASH}.html`, | ||
| "%not-really-percent-encoded.html": `%not-really-percent-encoded.${HASH}.html`, | ||
| "%2F.html": `%2F.${HASH}.html`, | ||
| "%E4%BD%A0%E5%A5%BD.html": `%E4%BD%A0%E5%A5%BD.${HASH}.html`, | ||
| "index.html": `index.${HASH}.html`, | ||
| "sub/blah.png": `sub/blah.${HASH}.png`, | ||
| "sub/index.html": `sub/index.${HASH}.html`, | ||
| client: `client.${HASH}`, | ||
| "client/index.html": `client.${HASH}`, | ||
| "image.png": `image.${HASH}.png`, | ||
| "image.webp": `image.${HASH}.webp`, | ||
| "你好/index.html": `你好/index.${HASH}.html`, | ||
| }); | ||
| }; | ||
| exports.mockManifest = mockManifest; | ||
| const cacheStore = new Map(); | ||
| const mockCaches = () => { | ||
| return { | ||
| default: { | ||
| async match(key) { | ||
| const cacheKey = { | ||
| url: key.url, | ||
| headers: {}, | ||
| }; | ||
| let response; | ||
| if (key.headers.has("if-none-match")) { | ||
| const makeStrongEtag = key.headers | ||
| .get("if-none-match") | ||
| .replace("W/", ""); | ||
| Reflect.set(cacheKey.headers, "etag", makeStrongEtag); | ||
| response = cacheStore.get(JSON.stringify(cacheKey)); | ||
| } | ||
| else { | ||
| // if client doesn't send if-none-match, we need to iterate through these keys | ||
| // and just test the URL | ||
| const activeCacheKeys = Array.from(cacheStore.keys()); | ||
| for (const cacheStoreKey of activeCacheKeys) { | ||
| if (JSON.parse(cacheStoreKey).url === key.url) { | ||
| response = cacheStore.get(cacheStoreKey); | ||
| } | ||
| } | ||
| } | ||
| // TODO: write test to accomodate for rare scenarios with where range requests accomodate etags | ||
| if (response && !key.headers.has("if-none-match")) { | ||
| // this appears overly verbose, but is necessary to document edge cache behavior | ||
| // The Range request header triggers the response header Content-Range ... | ||
| const range = key.headers.get("range"); | ||
| if (range) { | ||
| response.headers.set("content-range", `bytes ${range.split("=").pop()}/${response.headers.get("content-length")}`); | ||
| } | ||
| // ... which we are using in this repository to set status 206 | ||
| if (response.headers.has("content-range")) { | ||
| // @ts-expect-error overridding status in this mock | ||
| response.status = 206; | ||
| } | ||
| else { | ||
| // @ts-expect-error overridding status in this mock | ||
| response.status = 200; | ||
| } | ||
| const etag = response.headers.get("etag"); | ||
| if (etag && !etag.includes("W/")) { | ||
| response.headers.set("etag", `W/${etag}`); | ||
| } | ||
| } | ||
| return response; | ||
| }, | ||
| async put(key, val) { | ||
| const headers = new Headers(val.headers); | ||
| const url = new URL(key.url); | ||
| const resWithBody = new Response(val.body, { headers, status: 200 }); | ||
| const resNoBody = new Response(null, { headers, status: 304 }); | ||
| const cacheKey = { | ||
| url: key.url, | ||
| headers: { | ||
| etag: `"${url.pathname.replace("/", "")}"`, | ||
| }, | ||
| }; | ||
| cacheStore.set(JSON.stringify(cacheKey), resNoBody); | ||
| cacheKey.headers = {}; | ||
| cacheStore.set(JSON.stringify(cacheKey), resWithBody); | ||
| return; | ||
| }, | ||
| }, | ||
| }; | ||
| }; | ||
| exports.mockCaches = mockCaches; | ||
| // mocks functionality used inside worker request | ||
| function mockRequestScope() { | ||
| Object.assign(globalThis, (0, service_worker_mock_1.default)()); | ||
| Object.assign(globalThis, { __STATIC_CONTENT_MANIFEST: (0, exports.mockManifest)() }); | ||
| Object.assign(globalThis, { __STATIC_CONTENT: (0, exports.mockKV)(store) }); | ||
| Object.assign(globalThis, { caches: (0, exports.mockCaches)() }); | ||
| } | ||
| // mocks functionality used on global isolate scope. such as the KV namespace bind | ||
| function mockGlobalScope() { | ||
| Object.assign(globalThis, { __STATIC_CONTENT_MANIFEST: (0, exports.mockManifest)() }); | ||
| Object.assign(globalThis, { __STATIC_CONTENT: (0, exports.mockKV)(store) }); | ||
| } | ||
| const sleep = (milliseconds) => { | ||
| return new Promise((resolve) => setTimeout(resolve, milliseconds)); | ||
| }; | ||
| exports.sleep = sleep; |
| export type CacheControl = { | ||
| browserTTL: number; | ||
| edgeTTL: number; | ||
| bypassCache: boolean; | ||
| }; | ||
| export type AssetManifestType = Record<string, string>; | ||
| export type Options = { | ||
| cacheControl: ((req: Request) => Partial<CacheControl>) | Partial<CacheControl>; | ||
| ASSET_NAMESPACE: KVNamespace; | ||
| ASSET_MANIFEST: AssetManifestType | string; | ||
| mapRequestToAsset?: (req: Request, options?: Partial<Options>) => Request; | ||
| defaultMimeType: string; | ||
| defaultDocument: string; | ||
| pathIsEncoded: boolean; | ||
| defaultETag: "strong" | "weak"; | ||
| }; | ||
| export declare class KVError extends Error { | ||
| constructor(message?: string, status?: number); | ||
| status: number; | ||
| } | ||
| export declare class MethodNotAllowedError extends KVError { | ||
| constructor(message?: string, status?: number); | ||
| } | ||
| export declare class NotFoundError extends KVError { | ||
| constructor(message?: string, status?: number); | ||
| } | ||
| export declare class InternalError extends KVError { | ||
| constructor(message?: string, status?: number); | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.InternalError = exports.NotFoundError = exports.MethodNotAllowedError = exports.KVError = void 0; | ||
| class KVError extends Error { | ||
| constructor(message, status = 500) { | ||
| super(message); | ||
| // see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html | ||
| Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain | ||
| this.name = KVError.name; // stack traces display correctly now | ||
| this.status = status; | ||
| } | ||
| status; | ||
| } | ||
| exports.KVError = KVError; | ||
| class MethodNotAllowedError extends KVError { | ||
| constructor(message = `Not a valid request method`, status = 405) { | ||
| super(message, status); | ||
| } | ||
| } | ||
| exports.MethodNotAllowedError = MethodNotAllowedError; | ||
| class NotFoundError extends KVError { | ||
| constructor(message = `Not Found`, status = 404) { | ||
| super(message, status); | ||
| } | ||
| } | ||
| exports.NotFoundError = NotFoundError; | ||
| class InternalError extends KVError { | ||
| constructor(message = `Internal Error in KV Asset Handler`, status = 500) { | ||
| super(message, status); | ||
| } | ||
| } | ||
| exports.InternalError = InternalError; |
-156
| import makeServiceWorkerEnv from "service-worker-mock"; | ||
| const HASH = "123HASHBROWN"; | ||
| export const getEvent = ( | ||
| request: Request | ||
| ): Pick<FetchEvent, "request" | "waitUntil"> => { | ||
| const waitUntil = async (maybePromise: unknown) => { | ||
| await maybePromise; | ||
| }; | ||
| return { | ||
| request, | ||
| waitUntil, | ||
| }; | ||
| }; | ||
| const store = { | ||
| "key1.123HASHBROWN.txt": "val1", | ||
| "key1.123HASHBROWN.png": "val1", | ||
| "index.123HASHBROWN.html": "index.html", | ||
| "cache.123HASHBROWN.html": "cache me if you can", | ||
| "测试.123HASHBROWN.html": "My filename is non-ascii", | ||
| "%not-really-percent-encoded.123HASHBROWN.html": "browser percent encoded", | ||
| "%2F.123HASHBROWN.html": "user percent encoded", | ||
| "你好.123HASHBROWN.html": "I shouldnt be served", | ||
| "%E4%BD%A0%E5%A5%BD.123HASHBROWN.html": "Im important", | ||
| "nohash.txt": "no hash but still got some result", | ||
| "sub/blah.123HASHBROWN.png": "picturedis", | ||
| "sub/index.123HASHBROWN.html": "picturedis", | ||
| "client.123HASHBROWN": "important file", | ||
| "client.123HASHBROWN/index.html": "Im here but serve my big bro above", | ||
| "image.123HASHBROWN.png": "imagepng", | ||
| "image.123HASHBROWN.webp": "imagewebp", | ||
| "你好/index.123HASHBROWN.html": "My path is non-ascii", | ||
| }; | ||
| export const mockKV = (kvStore: Record<string, string>) => { | ||
| return { | ||
| get: (path: string) => kvStore[path] || null, | ||
| }; | ||
| }; | ||
| export const mockManifest = () => { | ||
| return JSON.stringify({ | ||
| "key1.txt": `key1.${HASH}.txt`, | ||
| "key1.png": `key1.${HASH}.png`, | ||
| "cache.html": `cache.${HASH}.html`, | ||
| "测试.html": `测试.${HASH}.html`, | ||
| "你好.html": `你好.${HASH}.html`, | ||
| "%not-really-percent-encoded.html": `%not-really-percent-encoded.${HASH}.html`, | ||
| "%2F.html": `%2F.${HASH}.html`, | ||
| "%E4%BD%A0%E5%A5%BD.html": `%E4%BD%A0%E5%A5%BD.${HASH}.html`, | ||
| "index.html": `index.${HASH}.html`, | ||
| "sub/blah.png": `sub/blah.${HASH}.png`, | ||
| "sub/index.html": `sub/index.${HASH}.html`, | ||
| client: `client.${HASH}`, | ||
| "client/index.html": `client.${HASH}`, | ||
| "image.png": `image.${HASH}.png`, | ||
| "image.webp": `image.${HASH}.webp`, | ||
| "你好/index.html": `你好/index.${HASH}.html`, | ||
| }); | ||
| }; | ||
| const cacheStore = new Map<string, Response>(); | ||
| interface CacheKey { | ||
| url: string; | ||
| headers: HeadersInit; | ||
| } | ||
| export const mockCaches = () => { | ||
| return { | ||
| default: { | ||
| async match(key: Request) { | ||
| const cacheKey: CacheKey = { | ||
| url: key.url, | ||
| headers: {}, | ||
| }; | ||
| let response; | ||
| if (key.headers.has("if-none-match")) { | ||
| const makeStrongEtag = key.headers | ||
| .get("if-none-match") | ||
| .replace("W/", ""); | ||
| Reflect.set(cacheKey.headers, "etag", makeStrongEtag); | ||
| response = cacheStore.get(JSON.stringify(cacheKey)); | ||
| } else { | ||
| // if client doesn't send if-none-match, we need to iterate through these keys | ||
| // and just test the URL | ||
| const activeCacheKeys: Array<string> = Array.from(cacheStore.keys()); | ||
| for (const cacheStoreKey of activeCacheKeys) { | ||
| if (JSON.parse(cacheStoreKey).url === key.url) { | ||
| response = cacheStore.get(cacheStoreKey); | ||
| } | ||
| } | ||
| } | ||
| // TODO: write test to accomodate for rare scenarios with where range requests accomodate etags | ||
| if (response && !key.headers.has("if-none-match")) { | ||
| // this appears overly verbose, but is necessary to document edge cache behavior | ||
| // The Range request header triggers the response header Content-Range ... | ||
| const range = key.headers.get("range"); | ||
| if (range) { | ||
| response.headers.set( | ||
| "content-range", | ||
| `bytes ${range.split("=").pop()}/${response.headers.get( | ||
| "content-length" | ||
| )}` | ||
| ); | ||
| } | ||
| // ... which we are using in this repository to set status 206 | ||
| if (response.headers.has("content-range")) { | ||
| // @ts-expect-error overridding status in this mock | ||
| response.status = 206; | ||
| } else { | ||
| // @ts-expect-error overridding status in this mock | ||
| response.status = 200; | ||
| } | ||
| const etag = response.headers.get("etag"); | ||
| if (etag && !etag.includes("W/")) { | ||
| response.headers.set("etag", `W/${etag}`); | ||
| } | ||
| } | ||
| return response; | ||
| }, | ||
| async put(key: Request, val: Response) { | ||
| const headers = new Headers(val.headers); | ||
| const url = new URL(key.url); | ||
| const resWithBody = new Response(val.body, { headers, status: 200 }); | ||
| const resNoBody = new Response(null, { headers, status: 304 }); | ||
| const cacheKey: CacheKey = { | ||
| url: key.url, | ||
| headers: { | ||
| etag: `"${url.pathname.replace("/", "")}"`, | ||
| }, | ||
| }; | ||
| cacheStore.set(JSON.stringify(cacheKey), resNoBody); | ||
| cacheKey.headers = {}; | ||
| cacheStore.set(JSON.stringify(cacheKey), resWithBody); | ||
| return; | ||
| }, | ||
| }, | ||
| }; | ||
| }; | ||
| // mocks functionality used inside worker request | ||
| export function mockRequestScope() { | ||
| Object.assign(globalThis, makeServiceWorkerEnv()); | ||
| Object.assign(globalThis, { __STATIC_CONTENT_MANIFEST: mockManifest() }); | ||
| Object.assign(globalThis, { __STATIC_CONTENT: mockKV(store) }); | ||
| Object.assign(globalThis, { caches: mockCaches() }); | ||
| } | ||
| // mocks functionality used on global isolate scope. such as the KV namespace bind | ||
| export function mockGlobalScope() { | ||
| Object.assign(globalThis, { __STATIC_CONTENT_MANIFEST: mockManifest() }); | ||
| Object.assign(globalThis, { __STATIC_CONTENT: mockKV(store) }); | ||
| } | ||
| export const sleep = (milliseconds: number) => { | ||
| return new Promise((resolve) => setTimeout(resolve, milliseconds)); | ||
| }; |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
63479
9.47%Yes
NaN8
14.29%8
-27.27%698
-35.96%