devflare 1.0.0-next.14 → 1.0.0-next.16
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/LLM.md +9360 -1784
- package/README.md +391 -32
- package/bin/devflare.js +17 -7
- package/dist/account-0w8wdzjv.js +475 -0
- package/dist/account-eygq6qx7.js +475 -0
- package/dist/account-fw8nafav.js +475 -0
- package/dist/account-pzq69nys.js +475 -0
- package/dist/account-s66jb15j.js +475 -0
- package/dist/api-d6ekexs5.js +25 -0
- package/dist/bridge/index.d.ts +1 -1
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/bridge/protocol.d.ts +1 -1
- package/dist/bridge/protocol.d.ts.map +1 -1
- package/dist/bridge/proxy.d.ts +0 -4
- package/dist/bridge/proxy.d.ts.map +1 -1
- package/dist/bridge/serialization.d.ts.map +1 -1
- package/dist/bridge/server.d.ts +1 -1
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/browser-shim/handler.d.ts +1 -1
- package/dist/browser-shim/handler.d.ts.map +1 -1
- package/dist/browser.d.ts +1651 -34
- package/dist/browser.d.ts.map +1 -1
- package/dist/build-1kmkwqgh.js +53 -0
- package/dist/build-506kjhcm.js +53 -0
- package/dist/build-66866ahs.js +53 -0
- package/dist/build-g1adm3ww.js +53 -0
- package/dist/build-p3r3117t.js +53 -0
- package/dist/bundler/do-bundler.d.ts.map +1 -1
- package/dist/bundler/rolldown-shared.d.ts +24 -0
- package/dist/bundler/rolldown-shared.d.ts.map +1 -0
- package/dist/bundler/worker-bundler.d.ts +0 -1
- package/dist/bundler/worker-bundler.d.ts.map +1 -1
- package/dist/cli/command-utils.d.ts +18 -0
- package/dist/cli/command-utils.d.ts.map +1 -0
- package/dist/cli/commands/account.d.ts +1 -1
- package/dist/cli/commands/account.d.ts.map +1 -1
- package/dist/cli/commands/build-artifacts.d.ts +27 -0
- package/dist/cli/commands/build-artifacts.d.ts.map +1 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/config.d.ts +4 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/login.d.ts +4 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/previews-support/cleanup.d.ts +9 -0
- package/dist/cli/commands/previews-support/cleanup.d.ts.map +1 -0
- package/dist/cli/commands/previews-support/family.d.ts +10 -0
- package/dist/cli/commands/previews-support/family.d.ts.map +1 -0
- package/dist/cli/commands/previews-support/render.d.ts +8 -0
- package/dist/cli/commands/previews-support/render.d.ts.map +1 -0
- package/dist/cli/commands/previews-support/theme.d.ts +10 -0
- package/dist/cli/commands/previews-support/theme.d.ts.map +1 -0
- package/dist/cli/commands/previews-support/types.d.ts +70 -0
- package/dist/cli/commands/previews-support/types.d.ts.map +1 -0
- package/dist/cli/commands/previews.d.ts +4 -0
- package/dist/cli/commands/previews.d.ts.map +1 -0
- package/dist/cli/commands/productions.d.ts +4 -0
- package/dist/cli/commands/productions.d.ts.map +1 -0
- package/dist/cli/commands/token.d.ts +4 -0
- package/dist/cli/commands/token.d.ts.map +1 -0
- package/dist/cli/commands/type-generation/discovery.d.ts +7 -0
- package/dist/cli/commands/type-generation/discovery.d.ts.map +1 -0
- package/dist/cli/commands/type-generation/generator.d.ts +44 -0
- package/dist/cli/commands/type-generation/generator.d.ts.map +1 -0
- package/dist/cli/commands/type-generation/models.d.ts +27 -0
- package/dist/cli/commands/type-generation/models.d.ts.map +1 -0
- package/dist/cli/commands/types.d.ts.map +1 -1
- package/dist/cli/commands/worker.d.ts +4 -0
- package/dist/cli/commands/worker.d.ts.map +1 -0
- package/dist/cli/config-path.d.ts +2 -1
- package/dist/cli/config-path.d.ts.map +1 -1
- package/dist/cli/deploy-strategy.d.ts +17 -0
- package/dist/cli/deploy-strategy.d.ts.map +1 -0
- package/dist/cli/deploy-target.d.ts +17 -0
- package/dist/cli/deploy-target.d.ts.map +1 -0
- package/dist/cli/generated-artifacts.d.ts +12 -0
- package/dist/cli/generated-artifacts.d.ts.map +1 -0
- package/dist/cli/help-pages/pages/account.d.ts +3 -0
- package/dist/cli/help-pages/pages/account.d.ts.map +1 -0
- package/dist/cli/help-pages/pages/core.d.ts +4 -0
- package/dist/cli/help-pages/pages/core.d.ts.map +1 -0
- package/dist/cli/help-pages/pages/index.d.ts +3 -0
- package/dist/cli/help-pages/pages/index.d.ts.map +1 -0
- package/dist/cli/help-pages/pages/misc.d.ts +3 -0
- package/dist/cli/help-pages/pages/misc.d.ts.map +1 -0
- package/dist/cli/help-pages/pages/previews.d.ts +3 -0
- package/dist/cli/help-pages/pages/previews.d.ts.map +1 -0
- package/dist/cli/help-pages/pages/productions.d.ts +3 -0
- package/dist/cli/help-pages/pages/productions.d.ts.map +1 -0
- package/dist/cli/help-pages/render.d.ts +12 -0
- package/dist/cli/help-pages/render.d.ts.map +1 -0
- package/dist/cli/help-pages/shared.d.ts +15 -0
- package/dist/cli/help-pages/shared.d.ts.map +1 -0
- package/dist/cli/help-pages/types.d.ts +23 -0
- package/dist/cli/help-pages/types.d.ts.map +1 -0
- package/dist/cli/help.d.ts +6 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/preview-bindings.d.ts +42 -0
- package/dist/cli/preview-bindings.d.ts.map +1 -0
- package/dist/cli/preview.d.ts +11 -0
- package/dist/cli/preview.d.ts.map +1 -0
- package/dist/cli/ui.d.ts +37 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/workspace-build-guard.d.ts +14 -0
- package/dist/cli/workspace-build-guard.d.ts.map +1 -0
- package/dist/cloudflare/account-core.d.ts +6 -0
- package/dist/cloudflare/account-core.d.ts.map +1 -0
- package/dist/cloudflare/account-resources.d.ts +40 -0
- package/dist/cloudflare/account-resources.d.ts.map +1 -0
- package/dist/cloudflare/account-status.d.ts +11 -0
- package/dist/cloudflare/account-status.d.ts.map +1 -0
- package/dist/cloudflare/account-workers.d.ts +14 -0
- package/dist/cloudflare/account-workers.d.ts.map +1 -0
- package/dist/cloudflare/account.d.ts +7 -64
- package/dist/cloudflare/account.d.ts.map +1 -1
- package/dist/cloudflare/api.d.ts +4 -0
- package/dist/cloudflare/api.d.ts.map +1 -1
- package/dist/cloudflare/index.d.ts +57 -2
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/kv-namespace.d.ts +3 -0
- package/dist/cloudflare/kv-namespace.d.ts.map +1 -0
- package/dist/cloudflare/preferences.d.ts.map +1 -1
- package/dist/cloudflare/preview-registry-cache.d.ts +6 -0
- package/dist/cloudflare/preview-registry-cache.d.ts.map +1 -0
- package/dist/cloudflare/preview-registry-records.d.ts +61 -0
- package/dist/cloudflare/preview-registry-records.d.ts.map +1 -0
- package/dist/cloudflare/preview-registry-store.d.ts +14 -0
- package/dist/cloudflare/preview-registry-store.d.ts.map +1 -0
- package/dist/cloudflare/preview-registry-types.d.ts +103 -0
- package/dist/cloudflare/preview-registry-types.d.ts.map +1 -0
- package/dist/cloudflare/preview-registry.d.ts +42 -0
- package/dist/cloudflare/preview-registry.d.ts.map +1 -0
- package/dist/cloudflare/registry-schema.d.ts +253 -0
- package/dist/cloudflare/registry-schema.d.ts.map +1 -0
- package/dist/cloudflare/tokens.d.ts +18 -0
- package/dist/cloudflare/tokens.d.ts.map +1 -0
- package/dist/cloudflare/types.d.ts +122 -5
- package/dist/cloudflare/types.d.ts.map +1 -1
- package/dist/cloudflare/usage.d.ts.map +1 -1
- package/dist/config/compiler.d.ts +4 -0
- package/dist/config/compiler.d.ts.map +1 -1
- package/dist/config/framework-providers.d.ts +9 -0
- package/dist/config/framework-providers.d.ts.map +1 -0
- package/dist/config/index.d.ts +5 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/loader.d.ts +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/preview-resources.d.ts +77 -0
- package/dist/config/preview-resources.d.ts.map +1 -0
- package/dist/config/preview.d.ts +31 -0
- package/dist/config/preview.d.ts.map +1 -0
- package/dist/config/ref.d.ts +0 -22
- package/dist/config/ref.d.ts.map +1 -1
- package/dist/config/resolve.d.ts +1 -0
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resource-resolution.d.ts +60 -0
- package/dist/config/resource-resolution.d.ts.map +1 -0
- package/dist/config/schema-bindings.d.ts +693 -0
- package/dist/config/schema-bindings.d.ts.map +1 -0
- package/dist/config/schema-build.d.ts +67 -0
- package/dist/config/schema-build.d.ts.map +1 -0
- package/dist/config/schema-env.d.ts +1341 -0
- package/dist/config/schema-env.d.ts.map +1 -0
- package/dist/config/schema-normalization.d.ts +64 -0
- package/dist/config/schema-normalization.d.ts.map +1 -0
- package/dist/config/schema-runtime.d.ts +230 -0
- package/dist/config/schema-runtime.d.ts.map +1 -0
- package/dist/config/schema.d.ts +640 -3669
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config-entry.d.ts +5 -0
- package/dist/config-entry.d.ts.map +1 -0
- package/dist/config-fjwke42y.js +59 -0
- package/dist/config-hwdqjse7.js +59 -0
- package/dist/config-pxvewrhv.js +59 -0
- package/dist/config-q0g5qdga.js +59 -0
- package/dist/decorators/durable-object.d.ts.map +1 -1
- package/dist/deploy-7nmzc9r8.js +609 -0
- package/dist/deploy-csfhdr64.js +691 -0
- package/dist/deploy-ex4g5avz.js +621 -0
- package/dist/deploy-jnb0bhka.js +609 -0
- package/dist/deploy-tp0g6qdp.js +609 -0
- package/dist/deploy-ykpcjkc2.js +690 -0
- package/dist/{dev-c1xc1gq9.js → dev-2pd33m28.js} +392 -348
- package/dist/dev-7ef5e2j1.js +2409 -0
- package/dist/dev-8nssqatr.js +2409 -0
- package/dist/dev-grznx8fn.js +2409 -0
- package/dist/dev-server/d1-migrations.d.ts +14 -0
- package/dist/dev-server/d1-migrations.d.ts.map +1 -0
- package/dist/dev-server/gateway-script.d.ts +8 -0
- package/dist/dev-server/gateway-script.d.ts.map +1 -0
- package/dist/dev-server/runtime-stdio.d.ts.map +1 -1
- package/dist/dev-server/server.d.ts.map +1 -1
- package/dist/dev-server/vite-process.d.ts +14 -0
- package/dist/dev-server/vite-process.d.ts.map +1 -0
- package/dist/dev-server/vite-utils.d.ts +1 -1
- package/dist/dev-server/vite-utils.d.ts.map +1 -1
- package/dist/dev-server/worker-source-watcher.d.ts +11 -0
- package/dist/dev-server/worker-source-watcher.d.ts.map +1 -0
- package/dist/dev-server/worker-surface-paths.d.ts +6 -0
- package/dist/dev-server/worker-surface-paths.d.ts.map +1 -0
- package/dist/{doctor-z4ffybce.js → doctor-04ammrrh.js} +67 -31
- package/dist/doctor-fmjj65mc.js +245 -0
- package/dist/doctor-fzkznce1.js +245 -0
- package/dist/doctor-sa5xv1bz.js +245 -0
- package/dist/index-091sh1ma.js +1229 -0
- package/dist/index-0apbm26n.js +788 -0
- package/dist/index-0eqksag4.js +418 -0
- package/dist/{index-dr6sbp8d.js → index-0kfzdywd.js} +15 -2
- package/dist/index-0w826dsr.js +379 -0
- package/dist/{index-rfhx0yd5.js → index-11m5a8wd.js} +110 -32
- package/dist/{index-xxwbb2nt.js → index-1sp39f2f.js} +114 -58
- package/dist/index-2jnrqbny.js +1301 -0
- package/dist/index-2pb7b9mw.js +378 -0
- package/dist/{index-0kzg8wed.js → index-2x53aqjm.js} +1071 -890
- package/dist/index-3ke5d2vn.js +1229 -0
- package/dist/index-43dq8yx8.js +788 -0
- package/dist/index-4rrttqj5.js +378 -0
- package/dist/index-4v9bc2pc.js +1367 -0
- package/dist/index-61jsjnsv.js +280 -0
- package/dist/index-6jef5emv.js +176 -0
- package/dist/index-6psz1h4c.js +788 -0
- package/dist/index-72mve6vh.js +168 -0
- package/dist/{index-zbvmtcn2.js → index-74198nxd.js} +179 -77
- package/dist/index-7g8zyws4.js +192 -0
- package/dist/index-7kcxjhta.js +456 -0
- package/dist/index-7v583xan.js +418 -0
- package/dist/index-7x0ybbtx.js +133 -0
- package/dist/index-816krz9p.js +52 -0
- package/dist/index-82f1z98k.js +41 -0
- package/dist/index-8t5nb4qx.js +133 -0
- package/dist/index-9az6s7ad.js +52 -0
- package/dist/{index-59df49vn.js → index-9ba1etyz.js} +29 -51
- package/dist/{index-001mw014.js → index-9fbtk7gv.js} +134 -248
- package/dist/index-9n6djthj.js +490 -0
- package/dist/index-aabgympv.js +39 -0
- package/dist/index-b8m6883k.js +74 -0
- package/dist/{index-5yxg30va.js → index-cgbvmse6.js} +15 -6
- package/dist/index-d8etnfef.js +1229 -0
- package/dist/index-e9yw4d6y.js +133 -0
- package/dist/index-epw1jxz5.js +1204 -0
- package/dist/index-f85s8gj3.js +2649 -0
- package/dist/index-fe2ngvh7.js +1229 -0
- package/dist/index-fvsadj32.js +192 -0
- package/dist/index-gs4y9gdf.js +456 -0
- package/dist/{index-fef08w43.js → index-h18pxvzs.js} +7 -6
- package/dist/index-hfj1a2c4.js +2649 -0
- package/dist/{index-8gtqgb3q.js → index-hjy8ctpc.js} +14 -92
- package/dist/index-htzf0py1.js +1204 -0
- package/dist/index-j185x270.js +897 -0
- package/dist/index-jb75kwa4.js +519 -0
- package/dist/index-jwd8pcb2.js +897 -0
- package/dist/index-k29yjhv0.js +52 -0
- package/dist/index-k6vq6kkt.js +456 -0
- package/dist/{index-vky23txa.js → index-m3fmw6mx.js} +2 -2
- package/dist/index-maxpsfk8.js +402 -0
- package/dist/index-mbdmrner.js +402 -0
- package/dist/index-mea5bc45.js +418 -0
- package/dist/index-mqekt778.js +185 -0
- package/dist/index-na3mnm1k.js +74 -0
- package/dist/index-p03n4qet.js +1367 -0
- package/dist/index-p296ban8.js +191 -0
- package/dist/index-pnbs1b8k.js +280 -0
- package/dist/index-q4kaz181.js +1207 -0
- package/dist/index-ry131z23.js +378 -0
- package/dist/index-sgb7c8nm.js +402 -0
- package/dist/index-sqrksgb2.js +133 -0
- package/dist/index-stgn34cr.js +148 -0
- package/dist/{index-v8vvsn9x.js → index-t08te69w.js} +1 -18
- package/dist/index-thna1tkd.js +280 -0
- package/dist/index-v5nmqthy.js +74 -0
- package/dist/{index-n932ytmq.js → index-vt4yxkmf.js} +2 -2
- package/dist/index-wyq6c6yj.js +402 -0
- package/dist/index-wztc9stx.js +418 -0
- package/dist/index-x9cwdxw5.js +456 -0
- package/dist/index-xk9djfjp.js +519 -0
- package/dist/index-yc0gcchc.js +418 -0
- package/dist/index-yqbxjysa.js +897 -0
- package/dist/index-yzddwp02.js +788 -0
- package/dist/index-zfhq6s96.js +74 -0
- package/dist/index-zt22fe2j.js +54 -0
- package/dist/index-zyt5byt6.js +2649 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/{init-na2atvz2.js → init-r4hnxan3.js} +24 -17
- package/dist/login-2hnz4m4n.js +77 -0
- package/dist/login-5bsxxpvc.js +77 -0
- package/dist/login-6tzvczw2.js +77 -0
- package/dist/login-bhaw72zc.js +77 -0
- package/dist/login-x8tgckqm.js +77 -0
- package/dist/previews-3rn8mz2c.js +1168 -0
- package/dist/previews-d487qde5.js +1200 -0
- package/dist/previews-gm3z0syj.js +1168 -0
- package/dist/previews-j9ymq4ys.js +1169 -0
- package/dist/previews-q031mx34.js +1168 -0
- package/dist/productions-120xg0aq.js +505 -0
- package/dist/productions-5ev5qweg.js +505 -0
- package/dist/productions-me3tdvr9.js +505 -0
- package/dist/productions-p5rbgp2f.js +505 -0
- package/dist/productions-x9p0pym1.js +505 -0
- package/dist/runtime/context-events.d.ts +13 -0
- package/dist/runtime/context-events.d.ts.map +1 -0
- package/dist/runtime/context-types.d.ts +82 -0
- package/dist/runtime/context-types.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +6 -267
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/exports.d.ts +3 -3
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/middleware.d.ts +8 -38
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/src/browser.js +23 -14
- package/dist/src/cli/index.js +3 -1
- package/dist/src/cloudflare/index.js +49 -2
- package/dist/src/config-entry.js +14 -0
- package/dist/src/index.js +33 -20
- package/dist/src/runtime/index.js +3 -9
- package/dist/src/sveltekit/index.js +10 -7
- package/dist/src/test/index.js +16 -18
- package/dist/src/vite/index.js +7 -4
- package/dist/sveltekit/platform.d.ts +1 -1
- package/dist/sveltekit/platform.d.ts.map +1 -1
- package/dist/test/cf.d.ts +10 -10
- package/dist/test/email.d.ts.map +1 -1
- package/dist/test/index.d.ts +1 -6
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/queue.d.ts.map +1 -1
- package/dist/test/remote-ai.d.ts.map +1 -1
- package/dist/test/remote-cloudflare.d.ts +13 -0
- package/dist/test/remote-cloudflare.d.ts.map +1 -0
- package/dist/test/remote-vectorize.d.ts.map +1 -1
- package/dist/test/resolve-service-bindings.d.ts.map +1 -1
- package/dist/test/scheduled.d.ts.map +1 -1
- package/dist/test/should-skip.d.ts +0 -18
- package/dist/test/should-skip.d.ts.map +1 -1
- package/dist/test/simple-context-durable-objects.d.ts +6 -0
- package/dist/test/simple-context-durable-objects.d.ts.map +1 -0
- package/dist/test/simple-context-gateway-script.d.ts +2 -0
- package/dist/test/simple-context-gateway-script.d.ts.map +1 -0
- package/dist/test/simple-context-paths.d.ts +40 -0
- package/dist/test/simple-context-paths.d.ts.map +1 -0
- package/dist/test/simple-context.d.ts +1 -23
- package/dist/test/simple-context.d.ts.map +1 -1
- package/dist/test/tail.d.ts.map +1 -1
- package/dist/test/worker.d.ts.map +1 -1
- package/dist/token-kedhcret.js +419 -0
- package/dist/token-m8jmnjwk.js +419 -0
- package/dist/{types-sffr9681.js → types-0sqwkp7x.js} +244 -139
- package/dist/types-1gwr2ex6.js +572 -0
- package/dist/types-6e5yx6km.js +572 -0
- package/dist/types-p0gckpn6.js +572 -0
- package/dist/utils/send-email.d.ts.map +1 -1
- package/dist/vite/config-file.d.ts.map +1 -1
- package/dist/vite/plugin.d.ts.map +1 -1
- package/dist/worker-0srh2jfr.js +513 -0
- package/dist/worker-4xrfd10a.js +513 -0
- package/dist/worker-entry/composed-worker.d.ts +0 -7
- package/dist/worker-entry/composed-worker.d.ts.map +1 -1
- package/dist/worker-entry/surface-paths.d.ts +15 -0
- package/dist/worker-entry/surface-paths.d.ts.map +1 -0
- package/dist/worker-qtam8grz.js +513 -0
- package/dist/worker-qzm0b7br.js +513 -0
- package/dist/worker-y9ha6g44.js +513 -0
- package/package.json +17 -10
- package/R2.md +0 -200
- package/dist/account-8psavtg6.js +0 -420
- package/dist/build-n639efmn.js +0 -101
- package/dist/deploy-zvnq6xh7.js +0 -117
- package/dist/index-2q3pmzrx.js +0 -90
- package/dist/index-f4q0jbnj.js +0 -195
- package/dist/index-n7rs26ft.js +0 -77
- package/dist/index-tfyxa77h.js +0 -850
- package/dist/index-wyf3s77s.js +0 -343
- package/dist/test/multi-worker-context.d.ts +0 -114
- package/dist/test/multi-worker-context.d.ts.map +0 -1
|
@@ -2,23 +2,28 @@ import {
|
|
|
2
2
|
getRemoteModeStatus,
|
|
3
3
|
isRemoteModeActive
|
|
4
4
|
} from "./index-d8bdkx2h.js";
|
|
5
|
+
import {
|
|
6
|
+
discoverEntrypointsSync
|
|
7
|
+
} from "./index-zt22fe2j.js";
|
|
8
|
+
import {
|
|
9
|
+
canProceedWithTest
|
|
10
|
+
} from "./index-7g8zyws4.js";
|
|
5
11
|
import {
|
|
6
12
|
transformWorkerEntrypoint
|
|
7
13
|
} from "./index-wfbfz02q.js";
|
|
8
14
|
import {
|
|
9
|
-
discoverEntrypointsSync,
|
|
10
15
|
resolvePackageSpecifier
|
|
11
|
-
} from "./index-
|
|
16
|
+
} from "./index-82f1z98k.js";
|
|
12
17
|
import {
|
|
13
18
|
__clearTestContext,
|
|
14
19
|
__setTestContext
|
|
15
|
-
} from "./index-
|
|
20
|
+
} from "./index-m3fmw6mx.js";
|
|
16
21
|
import {
|
|
17
22
|
createRouteResolve,
|
|
18
23
|
invokeFetchModule,
|
|
19
24
|
matchFetchRoute,
|
|
20
25
|
resolveFetchHandler
|
|
21
|
-
} from "./index-
|
|
26
|
+
} from "./index-hjy8ctpc.js";
|
|
22
27
|
import {
|
|
23
28
|
createEmailEvent,
|
|
24
29
|
createFetchEvent,
|
|
@@ -27,7 +32,7 @@ import {
|
|
|
27
32
|
createTailEvent,
|
|
28
33
|
runWithContext,
|
|
29
34
|
runWithEventContext
|
|
30
|
-
} from "./index-
|
|
35
|
+
} from "./index-cgbvmse6.js";
|
|
31
36
|
import {
|
|
32
37
|
discoverRoutes
|
|
33
38
|
} from "./index-1p814k7s.js";
|
|
@@ -42,44 +47,47 @@ import {
|
|
|
42
47
|
import {
|
|
43
48
|
startMiniflare,
|
|
44
49
|
startMiniflareFromConfig
|
|
45
|
-
} from "./index-
|
|
50
|
+
} from "./index-ry131z23.js";
|
|
46
51
|
import {
|
|
47
52
|
BridgeClient,
|
|
48
53
|
createEnvProxy,
|
|
49
54
|
setBindingHints
|
|
50
|
-
} from "./index-
|
|
55
|
+
} from "./index-9ba1etyz.js";
|
|
51
56
|
import {
|
|
52
57
|
createLocalSendEmailBinding,
|
|
53
58
|
wrapEnvSendEmailBindings
|
|
54
|
-
} from "./index-
|
|
59
|
+
} from "./index-h18pxvzs.js";
|
|
55
60
|
import {
|
|
61
|
+
getLocalD1DatabaseIdentifier,
|
|
56
62
|
loadConfig,
|
|
57
63
|
normalizeDOBinding,
|
|
58
64
|
resolveConfigPath
|
|
59
|
-
} from "./index-
|
|
65
|
+
} from "./index-6psz1h4c.js";
|
|
60
66
|
import {
|
|
61
|
-
canProceedWithTest,
|
|
62
|
-
getApiToken,
|
|
63
67
|
getEffectiveAccountId,
|
|
64
|
-
getPrimaryAccount
|
|
68
|
+
getPrimaryAccount
|
|
69
|
+
} from "./index-xk9djfjp.js";
|
|
70
|
+
import {
|
|
71
|
+
getApiToken,
|
|
65
72
|
isAuthenticated
|
|
66
|
-
} from "./index-
|
|
73
|
+
} from "./index-0w826dsr.js";
|
|
67
74
|
import {
|
|
68
75
|
__require
|
|
69
76
|
} from "./index-37x76zdn.js";
|
|
70
77
|
|
|
71
78
|
// src/test/simple-context.ts
|
|
72
|
-
import {
|
|
73
|
-
import { existsSync as existsSync2 } from "fs";
|
|
79
|
+
import { dirname as dirname4, join as join9, resolve as resolve2 } from "path";
|
|
74
80
|
|
|
75
|
-
// src/test/remote-
|
|
76
|
-
function
|
|
81
|
+
// src/test/remote-cloudflare.ts
|
|
82
|
+
function createRemoteCloudflareClient(accountId) {
|
|
77
83
|
let resolvedAccountId = null;
|
|
78
84
|
async function getAccountId() {
|
|
79
|
-
if (accountId)
|
|
85
|
+
if (accountId) {
|
|
80
86
|
return accountId;
|
|
81
|
-
|
|
87
|
+
}
|
|
88
|
+
if (resolvedAccountId) {
|
|
82
89
|
return resolvedAccountId;
|
|
90
|
+
}
|
|
83
91
|
const primary = await getPrimaryAccount();
|
|
84
92
|
if (!primary) {
|
|
85
93
|
throw new Error("No Cloudflare account found. Run: bunx wrangler login");
|
|
@@ -95,28 +103,45 @@ function createRemoteAI(accountId) {
|
|
|
95
103
|
}
|
|
96
104
|
return token;
|
|
97
105
|
}
|
|
106
|
+
async function jsonRequest(options) {
|
|
107
|
+
const [acctId, token] = await Promise.all([getAccountId(), getToken()]);
|
|
108
|
+
const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${acctId}${options.path}`, {
|
|
109
|
+
method: options.method,
|
|
110
|
+
headers: {
|
|
111
|
+
Authorization: `Bearer ${token}`,
|
|
112
|
+
"Content-Type": options.contentType ?? "application/json"
|
|
113
|
+
},
|
|
114
|
+
body: options.body
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const errorText = await response.text();
|
|
118
|
+
throw new Error(`${options.serviceLabel} API error (${response.status}): ${errorText}`);
|
|
119
|
+
}
|
|
120
|
+
const result = await response.json();
|
|
121
|
+
if (!result.success) {
|
|
122
|
+
const message = result.errors?.[0]?.message || `Unknown ${options.serviceLabel} error`;
|
|
123
|
+
throw new Error(`${options.serviceLabel} API error: ${message}`);
|
|
124
|
+
}
|
|
125
|
+
return result.result;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
getAccountId,
|
|
129
|
+
getToken,
|
|
130
|
+
jsonRequest
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/test/remote-ai.ts
|
|
135
|
+
function createRemoteAI(accountId) {
|
|
136
|
+
const cloudflare = createRemoteCloudflareClient(accountId);
|
|
98
137
|
const ai = {
|
|
99
138
|
async run(model, inputs) {
|
|
100
|
-
|
|
101
|
-
const url = `https://api.cloudflare.com/client/v4/accounts/${acctId}/ai/run/${model}`;
|
|
102
|
-
const response = await fetch(url, {
|
|
139
|
+
return cloudflare.jsonRequest({
|
|
103
140
|
method: "POST",
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"Content-Type": "application/json"
|
|
107
|
-
},
|
|
141
|
+
path: `/ai/run/${model}`,
|
|
142
|
+
serviceLabel: "AI",
|
|
108
143
|
body: JSON.stringify(inputs)
|
|
109
144
|
});
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
const errorText = await response.text();
|
|
112
|
-
throw new Error(`AI API error (${response.status}): ${errorText}`);
|
|
113
|
-
}
|
|
114
|
-
const result = await response.json();
|
|
115
|
-
if (!result.success) {
|
|
116
|
-
const message = result.errors?.[0]?.message || "Unknown AI error";
|
|
117
|
-
throw new Error(`AI API error: ${message}`);
|
|
118
|
-
}
|
|
119
|
-
return result.result;
|
|
120
145
|
},
|
|
121
146
|
gateway(_gatewayId) {
|
|
122
147
|
console.warn("AI Gateway is not supported in remote test mode");
|
|
@@ -128,72 +153,25 @@ function createRemoteAI(accountId) {
|
|
|
128
153
|
|
|
129
154
|
// src/test/remote-vectorize.ts
|
|
130
155
|
function createRemoteVectorize(indexName, accountId) {
|
|
131
|
-
|
|
132
|
-
async function getAccountId() {
|
|
133
|
-
if (accountId)
|
|
134
|
-
return accountId;
|
|
135
|
-
if (resolvedAccountId)
|
|
136
|
-
return resolvedAccountId;
|
|
137
|
-
const primary = await getPrimaryAccount();
|
|
138
|
-
if (!primary) {
|
|
139
|
-
throw new Error("No Cloudflare account found. Run: bunx wrangler login");
|
|
140
|
-
}
|
|
141
|
-
const { accountId: effectiveId } = await getEffectiveAccountId(primary.id);
|
|
142
|
-
resolvedAccountId = effectiveId;
|
|
143
|
-
return effectiveId;
|
|
144
|
-
}
|
|
145
|
-
async function getToken() {
|
|
146
|
-
const token = await getApiToken();
|
|
147
|
-
if (!token) {
|
|
148
|
-
throw new Error("Not authenticated. Run: bunx wrangler login");
|
|
149
|
-
}
|
|
150
|
-
return token;
|
|
151
|
-
}
|
|
156
|
+
const cloudflare = createRemoteCloudflareClient(accountId);
|
|
152
157
|
async function apiRequest(method, endpoint, body) {
|
|
153
|
-
|
|
154
|
-
const url = `https://api.cloudflare.com/client/v4/accounts/${acctId}/vectorize/v2/indexes/${indexName}${endpoint}`;
|
|
155
|
-
const response = await fetch(url, {
|
|
158
|
+
return cloudflare.jsonRequest({
|
|
156
159
|
method,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
"Content-Type": "application/json"
|
|
160
|
-
},
|
|
160
|
+
path: `/vectorize/v2/indexes/${indexName}${endpoint}`,
|
|
161
|
+
serviceLabel: "Vectorize",
|
|
161
162
|
body: body ? JSON.stringify(body) : undefined
|
|
162
163
|
});
|
|
163
|
-
if (!response.ok) {
|
|
164
|
-
const errorText = await response.text();
|
|
165
|
-
throw new Error(`Vectorize API error (${response.status}): ${errorText}`);
|
|
166
|
-
}
|
|
167
|
-
const result = await response.json();
|
|
168
|
-
if (!result.success) {
|
|
169
|
-
const message = result.errors?.[0]?.message || "Unknown Vectorize error";
|
|
170
|
-
throw new Error(`Vectorize API error: ${message}`);
|
|
171
|
-
}
|
|
172
|
-
return result.result;
|
|
173
164
|
}
|
|
174
165
|
async function ndjsonRequest(endpoint, vectors) {
|
|
175
|
-
const [acctId, token] = await Promise.all([getAccountId(), getToken()]);
|
|
176
|
-
const url = `https://api.cloudflare.com/client/v4/accounts/${acctId}/vectorize/v2/indexes/${indexName}${endpoint}`;
|
|
177
166
|
const ndjson = vectors.map((v) => JSON.stringify(v)).join(`
|
|
178
167
|
`);
|
|
179
|
-
|
|
168
|
+
return cloudflare.jsonRequest({
|
|
180
169
|
method: "POST",
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
},
|
|
170
|
+
path: `/vectorize/v2/indexes/${indexName}${endpoint}`,
|
|
171
|
+
serviceLabel: "Vectorize",
|
|
172
|
+
contentType: "application/x-ndjson",
|
|
185
173
|
body: ndjson
|
|
186
174
|
});
|
|
187
|
-
if (!response.ok) {
|
|
188
|
-
const errorText = await response.text();
|
|
189
|
-
throw new Error(`Vectorize API error (${response.status}): ${errorText}`);
|
|
190
|
-
}
|
|
191
|
-
const result = await response.json();
|
|
192
|
-
if (!result.success) {
|
|
193
|
-
const message = result.errors?.[0]?.message || "Unknown Vectorize error";
|
|
194
|
-
throw new Error(`Vectorize API error: ${message}`);
|
|
195
|
-
}
|
|
196
|
-
return result.result;
|
|
197
175
|
}
|
|
198
176
|
const vectorize = {
|
|
199
177
|
async describe() {
|
|
@@ -610,75 +588,755 @@ export default {
|
|
|
610
588
|
}
|
|
611
589
|
}
|
|
612
590
|
|
|
613
|
-
// src/test/
|
|
614
|
-
import {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
591
|
+
// src/test/simple-context-durable-objects.ts
|
|
592
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
593
|
+
import { readFile } from "fs/promises";
|
|
594
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
595
|
+
|
|
596
|
+
// src/test/simple-context-gateway-script.ts
|
|
597
|
+
function buildGatewayScript(bundledCode, wrappers) {
|
|
598
|
+
return `
|
|
599
|
+
// Bundled transport + DO classes
|
|
600
|
+
${bundledCode}
|
|
601
|
+
|
|
602
|
+
// DO Wrappers with RPC
|
|
603
|
+
${wrappers}
|
|
604
|
+
|
|
605
|
+
// Transport encoding helper
|
|
606
|
+
const __transportEncoders = typeof transport !== 'undefined' ? transport : {}
|
|
607
|
+
|
|
608
|
+
function __encodeTransport(value) {
|
|
609
|
+
if (value === null || value === undefined) return value
|
|
610
|
+
|
|
611
|
+
// Try each encoder
|
|
612
|
+
for (const [typeName, transporter] of Object.entries(__transportEncoders)) {
|
|
613
|
+
const encoded = transporter.encode(value)
|
|
614
|
+
if (encoded !== false && encoded !== undefined) {
|
|
615
|
+
return { __transport: typeName, value: encoded }
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Recursively encode arrays and objects
|
|
620
|
+
if (Array.isArray(value)) {
|
|
621
|
+
return value.map(__encodeTransport)
|
|
622
|
+
}
|
|
623
|
+
if (typeof value === 'object') {
|
|
624
|
+
const result = {}
|
|
625
|
+
for (const [k, v] of Object.entries(value)) {
|
|
626
|
+
result[k] = __encodeTransport(v)
|
|
627
|
+
}
|
|
628
|
+
return result
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return value
|
|
622
632
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
633
|
+
|
|
634
|
+
// Gateway with WebSocket RPC
|
|
635
|
+
export default {
|
|
636
|
+
async fetch(request, env) {
|
|
637
|
+
if (request.headers.get('Upgrade') === 'websocket') {
|
|
638
|
+
const { 0: client, 1: server } = new WebSocketPair()
|
|
639
|
+
server.accept()
|
|
640
|
+
server.addEventListener('message', async (e) => {
|
|
641
|
+
try {
|
|
642
|
+
const m = JSON.parse(e.data)
|
|
643
|
+
if (m.t === 'rpc.call') {
|
|
644
|
+
const result = await executeRpc(env, m.method, m.params)
|
|
645
|
+
server.send(JSON.stringify({ t: 'rpc.ok', id: m.id, result }))
|
|
646
|
+
}
|
|
647
|
+
} catch (error) {
|
|
648
|
+
server.send(JSON.stringify({ t: 'rpc.err', id: 'unknown', error: { code: 'RPC_ERROR', message: error.message } }))
|
|
649
|
+
}
|
|
650
|
+
})
|
|
651
|
+
return new Response(null, { status: 101, webSocket: client })
|
|
652
|
+
}
|
|
653
|
+
return new Response('Gateway')
|
|
654
|
+
}
|
|
627
655
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
656
|
+
|
|
657
|
+
async function executeRpc(env, method, params) {
|
|
658
|
+
const [bindingName, ...rest] = method.split('.')
|
|
659
|
+
const op = rest.join('.')
|
|
660
|
+
const binding = env[bindingName]
|
|
661
|
+
const RAW_EMAIL = 'EmailMessage::raw'
|
|
662
|
+
if (!binding) throw new Error('Binding not found: ' + bindingName)
|
|
663
|
+
|
|
664
|
+
// KV operations
|
|
665
|
+
if (op === 'get') return binding.get(params[0], params[1])
|
|
666
|
+
if (op === 'put') return binding.put(params[0], params[1], params[2])
|
|
667
|
+
if (op === 'delete') return binding.delete(params[0])
|
|
668
|
+
if (op === 'list') return binding.list(params[0])
|
|
669
|
+
if (op === 'getWithMetadata') return binding.getWithMetadata(params[0], params[1])
|
|
670
|
+
|
|
671
|
+
// R2 operations
|
|
672
|
+
if (op === 'r2.get') return binding.get(params[0], params[1])
|
|
673
|
+
if (op === 'r2.put') return binding.put(params[0], params[1], params[2])
|
|
674
|
+
if (op === 'r2.delete') return binding.delete(params[0])
|
|
675
|
+
if (op === 'r2.list') return binding.list(params[0])
|
|
676
|
+
if (op === 'head') return binding.head(params[0])
|
|
677
|
+
|
|
678
|
+
// D1 operations
|
|
679
|
+
if (op === 'exec') return binding.exec(params[0])
|
|
680
|
+
if (op === 'dump') return binding.dump()
|
|
681
|
+
if (op === 'batch') {
|
|
682
|
+
const stmts = params[0].map(s => {
|
|
683
|
+
const stmt = binding.prepare(s.sql)
|
|
684
|
+
return s.bindings?.length ? stmt.bind(...s.bindings) : stmt
|
|
685
|
+
})
|
|
686
|
+
return binding.batch(stmts)
|
|
687
|
+
}
|
|
688
|
+
if (op === 'prepare.run') return binding.prepare(params[0]).bind(...(params[1] || [])).run()
|
|
689
|
+
if (op === 'prepare.all') return binding.prepare(params[0]).bind(...(params[1] || [])).all()
|
|
690
|
+
if (op === 'prepare.first') return binding.prepare(params[0]).bind(...(params[1] || [])).first(params[2])
|
|
691
|
+
if (op === 'prepare.raw') return binding.prepare(params[0]).bind(...(params[1] || [])).raw({ columnNames: params[2] })
|
|
692
|
+
|
|
693
|
+
// Send email operations
|
|
694
|
+
if (op === 'email.send') {
|
|
695
|
+
return binding.send(__normalizeEmailMessage(params[0]))
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// DO operations
|
|
699
|
+
if (op === 'idFromName') {
|
|
700
|
+
return { __type: 'DOId', hex: binding.idFromName(params[0]).toString() }
|
|
701
|
+
}
|
|
702
|
+
if (op === 'stub.rpc') {
|
|
703
|
+
const [, idSerialized, rpcMethod, rpcParams] = params
|
|
704
|
+
const stub = binding.get(binding.idFromString(idSerialized.hex))
|
|
705
|
+
|
|
706
|
+
if (typeof stub[rpcMethod] === 'function') {
|
|
707
|
+
let result = await stub[rpcMethod](...(rpcParams || []))
|
|
708
|
+
result = __encodeTransport(result)
|
|
709
|
+
return result
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const response = await stub.fetch(new Request('http://do/_rpc', {
|
|
713
|
+
method: 'POST',
|
|
714
|
+
headers: { 'Content-Type': 'application/json' },
|
|
715
|
+
body: JSON.stringify({ method: rpcMethod, params: rpcParams || [] })
|
|
716
|
+
}))
|
|
717
|
+
|
|
718
|
+
const payload = await response.json()
|
|
719
|
+
if (!response.ok || !payload?.ok) {
|
|
720
|
+
throw new Error(payload?.error?.message || ('DO RPC failed with status ' + response.status))
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return payload.result
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
throw new Error('Unknown operation: ' + method)
|
|
651
727
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
retryAll() {
|
|
662
|
-
for (const msg of messages) {
|
|
663
|
-
msg.retry();
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
};
|
|
728
|
+
|
|
729
|
+
function __createEmailMessageRaw(raw) {
|
|
730
|
+
if (typeof raw === 'string' || raw instanceof ReadableStream) {
|
|
731
|
+
return raw
|
|
732
|
+
}
|
|
733
|
+
if (raw instanceof Uint8Array || raw instanceof ArrayBuffer) {
|
|
734
|
+
return new Response(raw).body
|
|
735
|
+
}
|
|
736
|
+
throw new Error('Unsupported EmailMessage raw payload')
|
|
667
737
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
738
|
+
|
|
739
|
+
function __buildRawEmail(message) {
|
|
740
|
+
const lines = []
|
|
741
|
+
const messageId = '<' + Date.now() + '-' + Math.random().toString(36).slice(2) + '@devflare.dev>'
|
|
742
|
+
|
|
743
|
+
lines.push('From: ' + message.from)
|
|
744
|
+
lines.push('To: ' + (Array.isArray(message.to) ? message.to.join(', ') : message.to))
|
|
745
|
+
lines.push('Date: ' + new Date().toUTCString())
|
|
746
|
+
lines.push('Message-ID: ' + messageId)
|
|
747
|
+
|
|
748
|
+
if (message.subject) lines.push('Subject: ' + message.subject)
|
|
749
|
+
if (message.replyTo) lines.push('Reply-To: ' + String(message.replyTo))
|
|
750
|
+
if (message.cc) lines.push('Cc: ' + (Array.isArray(message.cc) ? message.cc.join(', ') : message.cc))
|
|
751
|
+
if (message.bcc) lines.push('Bcc: ' + (Array.isArray(message.bcc) ? message.bcc.join(', ') : message.bcc))
|
|
752
|
+
|
|
753
|
+
for (const [key, value] of Object.entries(message.headers || {})) {
|
|
754
|
+
lines.push(key + ': ' + value)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
lines.push('MIME-Version: 1.0')
|
|
758
|
+
lines.push('Content-Type: ' + (message.html ? 'text/html' : 'text/plain') + '; charset=UTF-8')
|
|
759
|
+
lines.push('')
|
|
760
|
+
lines.push(String(message.html ?? message.text ?? '').replace(/\\r?\\n/g, '\\r\\n'))
|
|
761
|
+
|
|
762
|
+
return lines.join('\\r\\n')
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function __normalizeEmailMessage(message) {
|
|
766
|
+
if (!message || typeof message !== 'object' || !('from' in message) || !('to' in message)) {
|
|
767
|
+
return message
|
|
768
|
+
}
|
|
769
|
+
if ('EmailMessage::raw' in message) {
|
|
770
|
+
return message
|
|
771
|
+
}
|
|
772
|
+
if ('raw' in message && message.raw !== undefined) {
|
|
773
|
+
return {
|
|
774
|
+
from: message.from,
|
|
775
|
+
to: message.to,
|
|
776
|
+
[RAW_EMAIL]: __createEmailMessageRaw(message.raw)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
from: message.from,
|
|
781
|
+
to: message.to,
|
|
782
|
+
[RAW_EMAIL]: __createEmailMessageRaw(__buildRawEmail(message))
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
`;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/test/simple-context-paths.ts
|
|
789
|
+
import { existsSync as existsSync2 } from "fs";
|
|
790
|
+
import { createServer } from "net";
|
|
791
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
792
|
+
import { fileURLToPath } from "url";
|
|
793
|
+
var DEFAULT_TRANSPORT_ENTRY_FILES = [
|
|
794
|
+
"src/transport.ts",
|
|
795
|
+
"src/transport.js",
|
|
796
|
+
"src/transport.mts",
|
|
797
|
+
"src/transport.mjs"
|
|
798
|
+
];
|
|
799
|
+
var CURRENT_PACKAGE_ROOT = findPackageRoot(dirname2(fileURLToPath(import.meta.url)));
|
|
800
|
+
function getBunRuntime2() {
|
|
801
|
+
const g = globalThis;
|
|
802
|
+
if (typeof g.Bun === "object" && g.Bun !== null) {
|
|
803
|
+
return g.Bun;
|
|
804
|
+
}
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
function getCallerDirectory() {
|
|
808
|
+
const stackCallerDirectory = getStackCallerDirectory();
|
|
809
|
+
if (stackCallerDirectory) {
|
|
810
|
+
return stackCallerDirectory;
|
|
811
|
+
}
|
|
812
|
+
return process.cwd();
|
|
813
|
+
}
|
|
814
|
+
function getStackCallerDirectory() {
|
|
815
|
+
const originalPrepare = Error.prepareStackTrace;
|
|
816
|
+
Error.prepareStackTrace = (_, stack) => stack;
|
|
817
|
+
try {
|
|
818
|
+
const err = new Error;
|
|
819
|
+
const stack = err.stack;
|
|
820
|
+
for (const site of stack ?? []) {
|
|
821
|
+
const filename = site.getFileName?.();
|
|
822
|
+
if (filename && !isInsideCurrentPackage(filename) && !filename.includes("simple-context") && !filename.includes("node_modules") && !filename.includes("[") && existsSync2(filename)) {
|
|
823
|
+
return dirname2(filename);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
} finally {
|
|
827
|
+
Error.prepareStackTrace = originalPrepare;
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
function findPackageRoot(startDir) {
|
|
832
|
+
let currentDir = startDir;
|
|
833
|
+
while (true) {
|
|
834
|
+
if (existsSync2(join2(currentDir, "package.json"))) {
|
|
835
|
+
return currentDir;
|
|
836
|
+
}
|
|
837
|
+
const parentDir = dirname2(currentDir);
|
|
838
|
+
if (parentDir === currentDir) {
|
|
839
|
+
return startDir;
|
|
840
|
+
}
|
|
841
|
+
currentDir = parentDir;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function isInsideCurrentPackage(filePath) {
|
|
845
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
846
|
+
const normalizedPackageRoot = CURRENT_PACKAGE_ROOT.replace(/\\/g, "/");
|
|
847
|
+
return normalizedFilePath === normalizedPackageRoot || normalizedFilePath.startsWith(`${normalizedPackageRoot}/`);
|
|
848
|
+
}
|
|
849
|
+
async function findNearestConfig(startDir) {
|
|
850
|
+
let currentDir = startDir;
|
|
851
|
+
while (true) {
|
|
852
|
+
const configPath = await resolveConfigPath(currentDir);
|
|
853
|
+
if (configPath) {
|
|
854
|
+
return configPath;
|
|
855
|
+
}
|
|
856
|
+
const parentDir = dirname2(currentDir);
|
|
857
|
+
if (parentDir === currentDir) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
currentDir = parentDir;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async function getAvailablePort() {
|
|
864
|
+
return await new Promise((resolvePort, reject) => {
|
|
865
|
+
const server = createServer();
|
|
866
|
+
server.once("error", reject);
|
|
867
|
+
server.listen(0, "127.0.0.1", () => {
|
|
868
|
+
const address = server.address();
|
|
869
|
+
if (!address || typeof address === "string") {
|
|
870
|
+
server.close(() => reject(new Error("Could not determine an available port")));
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const { port } = address;
|
|
874
|
+
server.close((error) => {
|
|
875
|
+
if (error) {
|
|
876
|
+
reject(error);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
resolvePort(port);
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
function resolveTransportFile(configDir, configuredPath) {
|
|
885
|
+
if (typeof configuredPath === "string") {
|
|
886
|
+
return configuredPath;
|
|
887
|
+
}
|
|
888
|
+
if (configuredPath === null) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
for (const defaultEntry of DEFAULT_TRANSPORT_ENTRY_FILES) {
|
|
892
|
+
if (existsSync2(join2(configDir, defaultEntry))) {
|
|
893
|
+
return defaultEntry;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/test/simple-context-durable-objects.ts
|
|
900
|
+
function findExportedClasses(code) {
|
|
901
|
+
const classes = [];
|
|
902
|
+
const classPattern = /export\s+class\s+(\w+)/g;
|
|
903
|
+
let match;
|
|
904
|
+
while ((match = classPattern.exec(code)) !== null) {
|
|
905
|
+
classes.push(match[1]);
|
|
906
|
+
}
|
|
907
|
+
return classes;
|
|
908
|
+
}
|
|
909
|
+
function classSupportsNativeDurableObjectRpc(code, className) {
|
|
910
|
+
const nativeRpcPattern = new RegExp(`export\\s+class\\s+${className}\\s+extends\\s+DurableObject\\b`);
|
|
911
|
+
return nativeRpcPattern.test(code);
|
|
912
|
+
}
|
|
913
|
+
function toGeneratedIdentifier(value) {
|
|
914
|
+
const normalized = value.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
915
|
+
return /^[A-Za-z_$]/.test(normalized) ? normalized : `_${normalized}`;
|
|
916
|
+
}
|
|
917
|
+
async function discoverLocalDurableObjectClasses(config, configDir) {
|
|
918
|
+
const classToFilePath = new Map;
|
|
919
|
+
const doPatternConfig = config.files?.durableObjects;
|
|
920
|
+
const doPattern = typeof doPatternConfig === "string" ? doPatternConfig : DEFAULT_DO_PATTERN;
|
|
921
|
+
if (doPatternConfig === false) {
|
|
922
|
+
return classToFilePath;
|
|
923
|
+
}
|
|
924
|
+
const doFiles = await findFiles(doPattern, { cwd: configDir });
|
|
925
|
+
for (const filePath of doFiles) {
|
|
926
|
+
try {
|
|
927
|
+
const code = await readFile(filePath, "utf-8");
|
|
928
|
+
const classNames = findExportedClasses(code);
|
|
929
|
+
for (const className of classNames) {
|
|
930
|
+
classToFilePath.set(className, {
|
|
931
|
+
filePath,
|
|
932
|
+
nativeRpc: classSupportsNativeDurableObjectRpc(code, className)
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
} catch {}
|
|
936
|
+
}
|
|
937
|
+
return classToFilePath;
|
|
938
|
+
}
|
|
939
|
+
async function resolveLocalDurableObjects(config, configDir) {
|
|
940
|
+
const doConfig = {};
|
|
941
|
+
const doInfos = [];
|
|
942
|
+
const classToFilePath = await discoverLocalDurableObjectClasses(config, configDir);
|
|
943
|
+
for (const [name, rawDoInfo] of Object.entries(config.bindings?.durableObjects ?? {})) {
|
|
944
|
+
const doInfo = normalizeDOBinding(rawDoInfo);
|
|
945
|
+
if (doInfo.__ref) {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
let scriptPath;
|
|
949
|
+
let nativeRpc = false;
|
|
950
|
+
if (doInfo.scriptName) {
|
|
951
|
+
scriptPath = join3(configDir, "src", doInfo.scriptName);
|
|
952
|
+
try {
|
|
953
|
+
const code = await readFile(scriptPath, "utf-8");
|
|
954
|
+
nativeRpc = classSupportsNativeDurableObjectRpc(code, doInfo.className);
|
|
955
|
+
} catch {
|
|
956
|
+
nativeRpc = false;
|
|
957
|
+
}
|
|
958
|
+
} else {
|
|
959
|
+
const discoveredClass = classToFilePath.get(doInfo.className);
|
|
960
|
+
if (!discoveredClass) {
|
|
961
|
+
throw new Error(`Durable object ${name} (className: '${doInfo.className}') not found.
|
|
962
|
+
` + `Either:
|
|
963
|
+
` + ` 1. Set files.durableObjects pattern in config (e.g., 'src/do.*.ts')
|
|
964
|
+
` + ` 2. Use explicit scriptName: { className: '${doInfo.className}', scriptName: 'do.file.ts' }`);
|
|
965
|
+
}
|
|
966
|
+
scriptPath = discoveredClass.filePath;
|
|
967
|
+
nativeRpc = discoveredClass.nativeRpc;
|
|
968
|
+
}
|
|
969
|
+
const runtimeClassName = nativeRpc ? doInfo.className : `__Devflare${toGeneratedIdentifier(name)}RpcWrapper`;
|
|
970
|
+
doConfig[name] = runtimeClassName;
|
|
971
|
+
doInfos.push({
|
|
972
|
+
name,
|
|
973
|
+
className: doInfo.className,
|
|
974
|
+
scriptPath,
|
|
975
|
+
nativeRpc,
|
|
976
|
+
runtimeClassName
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
return {
|
|
980
|
+
doConfig,
|
|
981
|
+
doInfos
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function buildWrapperCode(doInfos) {
|
|
985
|
+
return doInfos.filter((info) => !info.nativeRpc).map((info) => `
|
|
986
|
+
export class ${info.runtimeClassName} {
|
|
987
|
+
constructor(state, env) {
|
|
988
|
+
this.__instance = new ${info.className}(state, env)
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
async fetch(request) {
|
|
992
|
+
const url = new URL(request.url)
|
|
993
|
+
if (request.method !== 'POST' || url.pathname !== '/_rpc') {
|
|
994
|
+
return new Response('Not found', { status: 404 })
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
try {
|
|
998
|
+
const payload = await request.json()
|
|
999
|
+
const method = payload?.method
|
|
1000
|
+
const params = Array.isArray(payload?.params) ? payload.params : []
|
|
1001
|
+
const target = this.__instance?.[method]
|
|
1002
|
+
|
|
1003
|
+
if (typeof target !== 'function') {
|
|
1004
|
+
return new Response(JSON.stringify({
|
|
1005
|
+
ok: false,
|
|
1006
|
+
error: { message: 'Method not found: ' + String(method) }
|
|
1007
|
+
}), {
|
|
1008
|
+
status: 404,
|
|
1009
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1010
|
+
})
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
let result = await target.apply(this.__instance, params)
|
|
1014
|
+
result = __encodeTransport(result)
|
|
1015
|
+
|
|
1016
|
+
return new Response(JSON.stringify({ ok: true, result }), {
|
|
1017
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1018
|
+
})
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
return new Response(JSON.stringify({
|
|
1021
|
+
ok: false,
|
|
1022
|
+
error: { message: error instanceof Error ? error.message : String(error) }
|
|
1023
|
+
}), {
|
|
1024
|
+
status: 500,
|
|
1025
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1026
|
+
})
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}`.trim()).join(`
|
|
1030
|
+
|
|
1031
|
+
`);
|
|
1032
|
+
}
|
|
1033
|
+
async function bundleDurableObjectModules(configDir, doInfos, transportFile) {
|
|
1034
|
+
const virtualImports = [];
|
|
1035
|
+
const virtualExports = [];
|
|
1036
|
+
if (transportFile) {
|
|
1037
|
+
const transportPath = join3(configDir, transportFile);
|
|
1038
|
+
virtualImports.push(`import { transport } from '${transportPath.replace(/\\/g, "/")}'`);
|
|
1039
|
+
virtualExports.push("export { transport }");
|
|
1040
|
+
}
|
|
1041
|
+
for (const info of doInfos) {
|
|
1042
|
+
virtualImports.push(`import { ${info.className} } from '${info.scriptPath.replace(/\\/g, "/")}'`);
|
|
1043
|
+
virtualExports.push(`export { ${info.className} }`);
|
|
1044
|
+
}
|
|
1045
|
+
if (virtualImports.length === 0) {
|
|
1046
|
+
return "";
|
|
1047
|
+
}
|
|
1048
|
+
const virtualEntry = [...virtualImports, "", ...virtualExports].join(`
|
|
1049
|
+
`);
|
|
1050
|
+
const virtualPath = join3(configDir, ".devflare", "__test_entry.ts");
|
|
1051
|
+
mkdirSync(dirname3(virtualPath), { recursive: true });
|
|
1052
|
+
writeFileSync(virtualPath, virtualEntry);
|
|
1053
|
+
const bun = getBunRuntime2();
|
|
1054
|
+
if (!bun) {
|
|
1055
|
+
throw new Error("Bun runtime is required for createTestContext with Durable Objects");
|
|
1056
|
+
}
|
|
1057
|
+
const result = await bun.build({
|
|
1058
|
+
entrypoints: [virtualPath],
|
|
1059
|
+
target: "browser",
|
|
1060
|
+
format: "esm",
|
|
1061
|
+
minify: false,
|
|
1062
|
+
external: ["cloudflare:workers", "cloudflare:*"]
|
|
1063
|
+
});
|
|
1064
|
+
if (!result.success) {
|
|
1065
|
+
throw new Error(`Failed to bundle test entry: ${result.logs.join(`
|
|
1066
|
+
`)}`);
|
|
1067
|
+
}
|
|
1068
|
+
return await result.outputs[0].text();
|
|
1069
|
+
}
|
|
1070
|
+
async function buildDurableObjectGateway(config, configDir, transportFile) {
|
|
1071
|
+
if (!config.bindings?.durableObjects) {
|
|
1072
|
+
return {
|
|
1073
|
+
script: buildGatewayScript("", "")
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
const { doConfig, doInfos } = await resolveLocalDurableObjects(config, configDir);
|
|
1077
|
+
const bundledCode = await bundleDurableObjectModules(configDir, doInfos, transportFile);
|
|
1078
|
+
const wrapperCode = buildWrapperCode(doInfos);
|
|
1079
|
+
return {
|
|
1080
|
+
durableObjects: doConfig,
|
|
1081
|
+
script: buildGatewayScript(bundledCode, wrapperCode)
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/test/email.ts
|
|
1086
|
+
import { join as join4 } from "path";
|
|
1087
|
+
var miniflarePort = 8787;
|
|
1088
|
+
var emailListeners = [];
|
|
1089
|
+
var sentEmails = [];
|
|
1090
|
+
var emailHandlerPath = null;
|
|
1091
|
+
var configDir = null;
|
|
1092
|
+
var testEnvGetter = null;
|
|
1093
|
+
function configureEmail(options = {}) {
|
|
1094
|
+
if (options.port) {
|
|
1095
|
+
miniflarePort = options.port;
|
|
1096
|
+
}
|
|
1097
|
+
emailHandlerPath = options.handlerPath ?? emailHandlerPath;
|
|
1098
|
+
configDir = options.configDir ?? configDir;
|
|
1099
|
+
testEnvGetter = options.getEnv ?? testEnvGetter;
|
|
1100
|
+
}
|
|
1101
|
+
function buildRawEmail(options) {
|
|
1102
|
+
if (options.raw) {
|
|
1103
|
+
return options.raw;
|
|
1104
|
+
}
|
|
1105
|
+
const lines = [];
|
|
1106
|
+
const messageId = `<${Date.now()}-${Math.random().toString(36).slice(2)}@devflare.dev>`;
|
|
1107
|
+
const date = new Date().toUTCString();
|
|
1108
|
+
lines.push(`From: ${options.from}`);
|
|
1109
|
+
lines.push(`To: ${options.to}`);
|
|
1110
|
+
lines.push(`Date: ${date}`);
|
|
1111
|
+
lines.push(`Message-ID: ${messageId}`);
|
|
1112
|
+
if (options.subject) {
|
|
1113
|
+
lines.push(`Subject: ${options.subject}`);
|
|
1114
|
+
}
|
|
1115
|
+
if (options.headers) {
|
|
1116
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
1117
|
+
lines.push(`${key}: ${value}`);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
lines.push("MIME-Version: 1.0");
|
|
1121
|
+
lines.push("Content-Type: text/plain; charset=UTF-8");
|
|
1122
|
+
lines.push("");
|
|
1123
|
+
lines.push(options.body ?? "");
|
|
1124
|
+
return lines.join(`\r
|
|
1125
|
+
`);
|
|
1126
|
+
}
|
|
1127
|
+
function createEmailHeaders(rawEmail) {
|
|
1128
|
+
const headers = new Headers;
|
|
1129
|
+
const lines = rawEmail.split(/\r?\n/);
|
|
1130
|
+
for (const line of lines) {
|
|
1131
|
+
if (!line.trim()) {
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1134
|
+
const colonIndex = line.indexOf(":");
|
|
1135
|
+
if (colonIndex <= 0) {
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim());
|
|
1139
|
+
}
|
|
1140
|
+
return headers;
|
|
1141
|
+
}
|
|
1142
|
+
function createRawEmailStream(rawEmail) {
|
|
1143
|
+
return new ReadableStream({
|
|
1144
|
+
start(controller) {
|
|
1145
|
+
controller.enqueue(new TextEncoder().encode(rawEmail));
|
|
1146
|
+
controller.close();
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
function resolveEmailHandler(module) {
|
|
1151
|
+
if (typeof module.default === "function") {
|
|
1152
|
+
return module.default;
|
|
1153
|
+
}
|
|
1154
|
+
if (module.default && typeof module.default.email === "function") {
|
|
1155
|
+
return module.default.email.bind(module.default);
|
|
1156
|
+
}
|
|
1157
|
+
if (typeof module.email === "function") {
|
|
1158
|
+
return module.email;
|
|
1159
|
+
}
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
function getRecordedRawContent(raw) {
|
|
1163
|
+
if (typeof raw === "string") {
|
|
1164
|
+
return raw;
|
|
1165
|
+
}
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
async function send(options) {
|
|
1169
|
+
const raw = buildRawEmail(options);
|
|
1170
|
+
if (emailHandlerPath && configDir && testEnvGetter) {
|
|
1171
|
+
const absolutePath = join4(configDir, emailHandlerPath);
|
|
1172
|
+
const handlerModule = await import(absolutePath);
|
|
1173
|
+
const emailHandler = resolveEmailHandler(handlerModule);
|
|
1174
|
+
if (!emailHandler) {
|
|
1175
|
+
throw new Error(`Email handler at "${emailHandlerPath}" must export a default function or named "email" export.
|
|
1176
|
+
NaN`);
|
|
1177
|
+
}
|
|
1178
|
+
const waitUntilPromises = [];
|
|
1179
|
+
const ctx = {
|
|
1180
|
+
waitUntil(promise) {
|
|
1181
|
+
waitUntilPromises.push(promise);
|
|
1182
|
+
},
|
|
1183
|
+
passThroughOnException() {},
|
|
1184
|
+
props: {}
|
|
1185
|
+
};
|
|
1186
|
+
const runtimeEnv = testEnvGetter();
|
|
1187
|
+
const timestamp = new Date;
|
|
1188
|
+
const message = {
|
|
1189
|
+
from: options.from,
|
|
1190
|
+
to: options.to,
|
|
1191
|
+
headers: createEmailHeaders(raw),
|
|
1192
|
+
raw: createRawEmailStream(raw),
|
|
1193
|
+
rawSize: raw.length,
|
|
1194
|
+
setReject(reason) {
|
|
1195
|
+
throw new Error(`Email rejected: ${reason}`);
|
|
1196
|
+
},
|
|
1197
|
+
async forward(rcptTo) {
|
|
1198
|
+
recordSentEmail({
|
|
1199
|
+
type: "forward",
|
|
1200
|
+
from: options.from,
|
|
1201
|
+
to: rcptTo,
|
|
1202
|
+
raw,
|
|
1203
|
+
timestamp
|
|
1204
|
+
});
|
|
1205
|
+
},
|
|
1206
|
+
async reply(message2) {
|
|
1207
|
+
recordSentEmail({
|
|
1208
|
+
type: "reply",
|
|
1209
|
+
from: message2.from ?? options.to,
|
|
1210
|
+
to: message2.to ?? options.from,
|
|
1211
|
+
raw: getRecordedRawContent(message2.raw),
|
|
1212
|
+
timestamp
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
const emailEvent = createEmailEvent(message, runtimeEnv, ctx);
|
|
1217
|
+
await runWithEventContext(emailEvent, () => emailHandler(emailEvent));
|
|
1218
|
+
await Promise.all(waitUntilPromises);
|
|
1219
|
+
return new Response(JSON.stringify({ ok: true, from: options.from, to: options.to }), {
|
|
1220
|
+
headers: { "Content-Type": "application/json" }
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
const url = new URL(`http://localhost:${miniflarePort}/cdn-cgi/handler/email`);
|
|
1224
|
+
url.searchParams.set("from", options.from);
|
|
1225
|
+
url.searchParams.set("to", options.to);
|
|
1226
|
+
const response = await fetch(url.toString(), {
|
|
1227
|
+
method: "POST",
|
|
1228
|
+
headers: {
|
|
1229
|
+
"Content-Type": "text/plain"
|
|
1230
|
+
},
|
|
1231
|
+
body: raw
|
|
1232
|
+
});
|
|
1233
|
+
return response;
|
|
1234
|
+
}
|
|
1235
|
+
function onReceive(callback) {
|
|
1236
|
+
emailListeners.push(callback);
|
|
1237
|
+
return () => {
|
|
1238
|
+
emailListeners = emailListeners.filter((cb) => cb !== callback);
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
function getSentEmails() {
|
|
1242
|
+
return [...sentEmails];
|
|
1243
|
+
}
|
|
1244
|
+
function clearSentEmails() {
|
|
1245
|
+
sentEmails = [];
|
|
1246
|
+
}
|
|
1247
|
+
function recordSentEmail(email) {
|
|
1248
|
+
sentEmails.push(email);
|
|
1249
|
+
for (const listener of emailListeners) {
|
|
1250
|
+
try {
|
|
1251
|
+
listener(email);
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
console.error("[devflare/test] Email listener error:", error);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
function resetEmailState() {
|
|
1258
|
+
miniflarePort = 8787;
|
|
1259
|
+
emailHandlerPath = null;
|
|
1260
|
+
configDir = null;
|
|
1261
|
+
testEnvGetter = null;
|
|
1262
|
+
emailListeners = [];
|
|
1263
|
+
sentEmails = [];
|
|
1264
|
+
}
|
|
1265
|
+
var email = {
|
|
1266
|
+
send,
|
|
1267
|
+
onReceive,
|
|
1268
|
+
getSentEmails,
|
|
1269
|
+
clearSentEmails
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// src/test/queue.ts
|
|
1273
|
+
import { join as join5 } from "path";
|
|
1274
|
+
var queueHandlerPath = null;
|
|
1275
|
+
var configDir2 = null;
|
|
1276
|
+
var testEnvGetter2 = null;
|
|
1277
|
+
function configureQueue(options) {
|
|
1278
|
+
queueHandlerPath = options.handlerPath;
|
|
1279
|
+
configDir2 = options.configDir;
|
|
1280
|
+
testEnvGetter2 = options.getEnv;
|
|
1281
|
+
}
|
|
1282
|
+
function resetQueueState() {
|
|
1283
|
+
queueHandlerPath = null;
|
|
1284
|
+
configDir2 = null;
|
|
1285
|
+
testEnvGetter2 = null;
|
|
1286
|
+
}
|
|
1287
|
+
function createMessage(options) {
|
|
1288
|
+
const id = options.id ?? crypto.randomUUID();
|
|
1289
|
+
const timestamp = options.timestamp ?? new Date;
|
|
1290
|
+
const attempts = options.attempts ?? 1;
|
|
1291
|
+
let state = "pending";
|
|
1292
|
+
return {
|
|
1293
|
+
id,
|
|
1294
|
+
timestamp,
|
|
1295
|
+
body: options.body,
|
|
1296
|
+
attempts,
|
|
1297
|
+
ack() {
|
|
1298
|
+
state = "acked";
|
|
1299
|
+
},
|
|
1300
|
+
retry(opts) {
|
|
1301
|
+
state = "retried";
|
|
1302
|
+
},
|
|
1303
|
+
retryAll() {
|
|
1304
|
+
state = "retried";
|
|
1305
|
+
},
|
|
1306
|
+
get _state() {
|
|
1307
|
+
return state;
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function createMessageBatch(messages) {
|
|
1312
|
+
return {
|
|
1313
|
+
queue: "test-queue",
|
|
1314
|
+
messages,
|
|
1315
|
+
ackAll() {
|
|
1316
|
+
for (const msg of messages) {
|
|
1317
|
+
msg.ack();
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
retryAll() {
|
|
1321
|
+
for (const msg of messages) {
|
|
1322
|
+
msg.retry();
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
async function trigger(messages) {
|
|
1328
|
+
if (!queueHandlerPath) {
|
|
1329
|
+
throw new Error("Queue handler not configured. Make sure your devflare.config.ts has files.queue set, " + "and the file exists at the specified path (default: src/queue.ts)");
|
|
1330
|
+
}
|
|
1331
|
+
if (!configDir2 || !testEnvGetter2) {
|
|
1332
|
+
throw new Error("Queue helper not initialized. Call createTestContext() before using cf.queue.trigger()");
|
|
1333
|
+
}
|
|
1334
|
+
const absolutePath = join5(configDir2, queueHandlerPath);
|
|
1335
|
+
const handlerModule = await import(absolutePath);
|
|
1336
|
+
const queueHandler = handlerModule.default ?? handlerModule.queue;
|
|
1337
|
+
if (typeof queueHandler !== "function") {
|
|
679
1338
|
throw new Error(`Queue handler at "${queueHandlerPath}" must export a default function or named "queue" export.
|
|
680
|
-
|
|
681
|
-
Legacy compatibility is still supported for queue(batch, env, ctx).`);
|
|
1339
|
+
NaN`);
|
|
682
1340
|
}
|
|
683
1341
|
const normalizedMessages = messages.map((msg) => {
|
|
684
1342
|
if (typeof msg === "object" && msg !== null && "body" in msg) {
|
|
@@ -696,9 +1354,9 @@ Legacy compatibility is still supported for queue(batch, env, ctx).`);
|
|
|
696
1354
|
passThroughOnException() {},
|
|
697
1355
|
props: {}
|
|
698
1356
|
};
|
|
699
|
-
const env =
|
|
1357
|
+
const env = testEnvGetter2();
|
|
700
1358
|
const queueEvent = createQueueEvent(batch, env, ctx);
|
|
701
|
-
await runWithEventContext(queueEvent, () => queueHandler(queueEvent
|
|
1359
|
+
await runWithEventContext(queueEvent, () => queueHandler(queueEvent));
|
|
702
1360
|
await Promise.all(waitUntilPromises);
|
|
703
1361
|
const acked = [];
|
|
704
1362
|
const retried = [];
|
|
@@ -723,46 +1381,45 @@ Legacy compatibility is still supported for queue(batch, env, ctx).`);
|
|
|
723
1381
|
total: mockMessages.length
|
|
724
1382
|
};
|
|
725
1383
|
}
|
|
726
|
-
async function
|
|
1384
|
+
async function send2(message) {
|
|
727
1385
|
return trigger([message]);
|
|
728
1386
|
}
|
|
729
1387
|
var queue = {
|
|
730
1388
|
trigger,
|
|
731
|
-
send
|
|
1389
|
+
send: send2
|
|
732
1390
|
};
|
|
733
1391
|
|
|
734
1392
|
// src/test/scheduled.ts
|
|
735
|
-
import { join as
|
|
1393
|
+
import { join as join6 } from "path";
|
|
736
1394
|
var scheduledHandlerPath = null;
|
|
737
|
-
var
|
|
738
|
-
var
|
|
1395
|
+
var configDir3 = null;
|
|
1396
|
+
var testEnvGetter3 = null;
|
|
739
1397
|
function configureScheduled(options) {
|
|
740
1398
|
scheduledHandlerPath = options.handlerPath;
|
|
741
|
-
|
|
742
|
-
|
|
1399
|
+
configDir3 = options.configDir;
|
|
1400
|
+
testEnvGetter3 = options.getEnv;
|
|
743
1401
|
}
|
|
744
1402
|
function resetScheduledState() {
|
|
745
1403
|
scheduledHandlerPath = null;
|
|
746
|
-
|
|
747
|
-
|
|
1404
|
+
configDir3 = null;
|
|
1405
|
+
testEnvGetter3 = null;
|
|
748
1406
|
}
|
|
749
1407
|
async function trigger2(cronOrOptions) {
|
|
750
1408
|
if (!scheduledHandlerPath) {
|
|
751
1409
|
throw new Error("Scheduled handler not configured. Make sure your devflare.config.ts has files.scheduled set, " + "and the file exists at the specified path (default: src/scheduled.ts)");
|
|
752
1410
|
}
|
|
753
|
-
if (!
|
|
1411
|
+
if (!configDir3 || !testEnvGetter3) {
|
|
754
1412
|
throw new Error("Scheduled helper not initialized. Call createTestContext() before using cf.scheduled.trigger()");
|
|
755
1413
|
}
|
|
756
1414
|
const options = typeof cronOrOptions === "string" ? { cron: cronOrOptions } : cronOrOptions ?? {};
|
|
757
1415
|
const cron = options.cron ?? "* * * * *";
|
|
758
1416
|
const scheduledTime = options.scheduledTime instanceof Date ? options.scheduledTime.getTime() : options.scheduledTime ?? Date.now();
|
|
759
|
-
const absolutePath =
|
|
1417
|
+
const absolutePath = join6(configDir3, scheduledHandlerPath);
|
|
760
1418
|
const handlerModule = await import(absolutePath);
|
|
761
1419
|
const scheduledHandler = handlerModule.default ?? handlerModule.scheduled;
|
|
762
1420
|
if (typeof scheduledHandler !== "function") {
|
|
763
1421
|
throw new Error(`Scheduled handler at "${scheduledHandlerPath}" must export a default function or named "scheduled" export.
|
|
764
|
-
|
|
765
|
-
Legacy compatibility is still supported for scheduled(controller, env, ctx).`);
|
|
1422
|
+
NaN`);
|
|
766
1423
|
}
|
|
767
1424
|
const controller = {
|
|
768
1425
|
scheduledTime,
|
|
@@ -777,10 +1434,10 @@ Legacy compatibility is still supported for scheduled(controller, env, ctx).`);
|
|
|
777
1434
|
passThroughOnException() {},
|
|
778
1435
|
props: {}
|
|
779
1436
|
};
|
|
780
|
-
const env =
|
|
1437
|
+
const env = testEnvGetter3();
|
|
781
1438
|
const scheduledEvent = createScheduledEvent(controller, env, ctx);
|
|
782
1439
|
try {
|
|
783
|
-
await runWithEventContext(scheduledEvent, () => scheduledHandler(scheduledEvent
|
|
1440
|
+
await runWithEventContext(scheduledEvent, () => scheduledHandler(scheduledEvent));
|
|
784
1441
|
await Promise.all(waitUntilPromises);
|
|
785
1442
|
return {
|
|
786
1443
|
success: true,
|
|
@@ -800,117 +1457,8 @@ var scheduled = {
|
|
|
800
1457
|
trigger: trigger2
|
|
801
1458
|
};
|
|
802
1459
|
|
|
803
|
-
// src/test/worker.ts
|
|
804
|
-
import { join as join4 } from "path";
|
|
805
|
-
var fetchHandlerPath = null;
|
|
806
|
-
var configDir3 = null;
|
|
807
|
-
var testEnvGetter3 = null;
|
|
808
|
-
var fileRoutes = [];
|
|
809
|
-
function configureWorker(options) {
|
|
810
|
-
fetchHandlerPath = options.handlerPath;
|
|
811
|
-
fileRoutes = options.routes ?? [];
|
|
812
|
-
configDir3 = options.configDir;
|
|
813
|
-
testEnvGetter3 = options.getEnv;
|
|
814
|
-
}
|
|
815
|
-
function resetWorkerState() {
|
|
816
|
-
fetchHandlerPath = null;
|
|
817
|
-
fileRoutes = [];
|
|
818
|
-
configDir3 = null;
|
|
819
|
-
testEnvGetter3 = null;
|
|
820
|
-
}
|
|
821
|
-
async function fetch2(request, options) {
|
|
822
|
-
if (!fetchHandlerPath && fileRoutes.length === 0) {
|
|
823
|
-
throw new Error("Fetch handler not configured. Make sure your devflare.config.ts has files.fetch set or a routes directory is available, " + "and that the corresponding files exist (defaults: src/fetch.ts and src/routes/**).");
|
|
824
|
-
}
|
|
825
|
-
if (!configDir3 || !testEnvGetter3) {
|
|
826
|
-
throw new Error("Worker helper not initialized. Call createTestContext() before using cf.worker.fetch()");
|
|
827
|
-
}
|
|
828
|
-
const workerConfigDir = configDir3;
|
|
829
|
-
const getEnv = testEnvGetter3;
|
|
830
|
-
let req;
|
|
831
|
-
if (typeof request === "string") {
|
|
832
|
-
const url = request.startsWith("http") ? request : `http://localhost${request.startsWith("/") ? "" : "/"}${request}`;
|
|
833
|
-
const headers = new Headers(options?.headers);
|
|
834
|
-
let body;
|
|
835
|
-
if (options?.body !== undefined) {
|
|
836
|
-
if (typeof options.body === "string") {
|
|
837
|
-
body = options.body;
|
|
838
|
-
} else {
|
|
839
|
-
body = JSON.stringify(options.body);
|
|
840
|
-
if (!headers.has("Content-Type")) {
|
|
841
|
-
headers.set("Content-Type", "application/json");
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
req = new Request(url, {
|
|
846
|
-
method: options?.method ?? "GET",
|
|
847
|
-
headers,
|
|
848
|
-
body
|
|
849
|
-
});
|
|
850
|
-
} else {
|
|
851
|
-
req = request;
|
|
852
|
-
}
|
|
853
|
-
const handlerModule = fetchHandlerPath ? await import(join4(workerConfigDir, fetchHandlerPath)) : {};
|
|
854
|
-
const routeModules = await Promise.all(fileRoutes.map(async (route) => {
|
|
855
|
-
return {
|
|
856
|
-
...route,
|
|
857
|
-
module: await import(join4(workerConfigDir, route.filePath))
|
|
858
|
-
};
|
|
859
|
-
}));
|
|
860
|
-
const methodExports = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "ALL"];
|
|
861
|
-
const hasMethodHandler = methodExports.some((method) => {
|
|
862
|
-
return typeof handlerModule[method] === "function" || typeof handlerModule.default?.[method] === "function";
|
|
863
|
-
});
|
|
864
|
-
if (!resolveFetchHandler(handlerModule) && !hasMethodHandler && routeModules.length === 0) {
|
|
865
|
-
throw new Error(`Fetch handler at "${fetchHandlerPath}" must export one of:
|
|
866
|
-
- request-wide "handle" middleware
|
|
867
|
-
- named "fetch"
|
|
868
|
-
- default fetch handler
|
|
869
|
-
- HTTP method exports such as "GET" or "POST"
|
|
870
|
-
Legacy compatibility is still supported for fetch(request, env, ctx).`);
|
|
871
|
-
}
|
|
872
|
-
const waitUntilPromises = [];
|
|
873
|
-
const ctx = {
|
|
874
|
-
waitUntil(promise) {
|
|
875
|
-
waitUntilPromises.push(promise);
|
|
876
|
-
},
|
|
877
|
-
passThroughOnException() {},
|
|
878
|
-
props: {}
|
|
879
|
-
};
|
|
880
|
-
const env = getEnv();
|
|
881
|
-
const initialRouteMatch = routeModules.length > 0 ? matchFetchRoute(routeModules, req) : null;
|
|
882
|
-
const fetchEvent = createFetchEvent(req, env, ctx, {
|
|
883
|
-
params: initialRouteMatch?.params ?? {}
|
|
884
|
-
});
|
|
885
|
-
const response = await runWithEventContext(fetchEvent, () => invokeFetchModule(handlerModule, fetchEvent, routeModules.length > 0 ? createRouteResolve(routeModules, fetchEvent) : undefined));
|
|
886
|
-
return response;
|
|
887
|
-
}
|
|
888
|
-
async function get(path, headers) {
|
|
889
|
-
return fetch2(path, { method: "GET", headers });
|
|
890
|
-
}
|
|
891
|
-
async function post(path, body, headers) {
|
|
892
|
-
return fetch2(path, { method: "POST", body, headers });
|
|
893
|
-
}
|
|
894
|
-
async function put(path, body, headers) {
|
|
895
|
-
return fetch2(path, { method: "PUT", body, headers });
|
|
896
|
-
}
|
|
897
|
-
async function del(path, headers) {
|
|
898
|
-
return fetch2(path, { method: "DELETE", headers });
|
|
899
|
-
}
|
|
900
|
-
async function patch(path, body, headers) {
|
|
901
|
-
return fetch2(path, { method: "PATCH", body, headers });
|
|
902
|
-
}
|
|
903
|
-
var worker = {
|
|
904
|
-
fetch: fetch2,
|
|
905
|
-
get,
|
|
906
|
-
post,
|
|
907
|
-
put,
|
|
908
|
-
delete: del,
|
|
909
|
-
patch
|
|
910
|
-
};
|
|
911
|
-
|
|
912
1460
|
// src/test/tail.ts
|
|
913
|
-
import { join as
|
|
1461
|
+
import { join as join7 } from "path";
|
|
914
1462
|
var tailHandlerPath = null;
|
|
915
1463
|
var configDir4 = null;
|
|
916
1464
|
var testEnvGetter4 = null;
|
|
@@ -956,13 +1504,12 @@ async function trigger3(items) {
|
|
|
956
1504
|
}
|
|
957
1505
|
return createTraceItem(item);
|
|
958
1506
|
});
|
|
959
|
-
const absolutePath =
|
|
1507
|
+
const absolutePath = join7(configDir4, tailHandlerPath);
|
|
960
1508
|
const handlerModule = await import(absolutePath);
|
|
961
1509
|
const tailHandler = handlerModule.default ?? handlerModule.tail;
|
|
962
1510
|
if (typeof tailHandler !== "function") {
|
|
963
1511
|
throw new Error(`Tail handler at "${tailHandlerPath}" must export a default function or named "tail" export.
|
|
964
|
-
|
|
965
|
-
Legacy compatibility is still supported for tail(events, env, ctx).`);
|
|
1512
|
+
NaN`);
|
|
966
1513
|
}
|
|
967
1514
|
const waitUntilPromises = [];
|
|
968
1515
|
const ctx = {
|
|
@@ -975,7 +1522,7 @@ Legacy compatibility is still supported for tail(events, env, ctx).`);
|
|
|
975
1522
|
const env = testEnvGetter4();
|
|
976
1523
|
const tailEvent = createTailEvent(traceItems, env, ctx);
|
|
977
1524
|
try {
|
|
978
|
-
await runWithEventContext(tailEvent, () => tailHandler(tailEvent
|
|
1525
|
+
await runWithEventContext(tailEvent, () => tailHandler(tailEvent));
|
|
979
1526
|
await Promise.all(waitUntilPromises);
|
|
980
1527
|
return {
|
|
981
1528
|
success: true,
|
|
@@ -986,282 +1533,204 @@ Legacy compatibility is still supported for tail(events, env, ctx).`);
|
|
|
986
1533
|
success: false,
|
|
987
1534
|
error: error instanceof Error ? error.message : String(error),
|
|
988
1535
|
itemCount: traceItems.length
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
function create(options = {}) {
|
|
993
|
-
return createTraceItem(options);
|
|
994
|
-
}
|
|
995
|
-
var tail = {
|
|
996
|
-
trigger: trigger3,
|
|
997
|
-
create
|
|
998
|
-
};
|
|
999
|
-
|
|
1000
|
-
// src/test/email.ts
|
|
1001
|
-
import { join as join6 } from "path";
|
|
1002
|
-
var miniflarePort = 8787;
|
|
1003
|
-
var emailListeners = [];
|
|
1004
|
-
var sentEmails = [];
|
|
1005
|
-
var emailHandlerPath = null;
|
|
1006
|
-
var configDir5 = null;
|
|
1007
|
-
var testEnvGetter5 = null;
|
|
1008
|
-
function configureEmail(options = {}) {
|
|
1009
|
-
if (options.port) {
|
|
1010
|
-
miniflarePort = options.port;
|
|
1011
|
-
}
|
|
1012
|
-
emailHandlerPath = options.handlerPath ?? emailHandlerPath;
|
|
1013
|
-
configDir5 = options.configDir ?? configDir5;
|
|
1014
|
-
testEnvGetter5 = options.getEnv ?? testEnvGetter5;
|
|
1015
|
-
}
|
|
1016
|
-
function buildRawEmail(options) {
|
|
1017
|
-
if (options.raw) {
|
|
1018
|
-
return options.raw;
|
|
1019
|
-
}
|
|
1020
|
-
const lines = [];
|
|
1021
|
-
const messageId = `<${Date.now()}-${Math.random().toString(36).slice(2)}@devflare.dev>`;
|
|
1022
|
-
const date = new Date().toUTCString();
|
|
1023
|
-
lines.push(`From: ${options.from}`);
|
|
1024
|
-
lines.push(`To: ${options.to}`);
|
|
1025
|
-
lines.push(`Date: ${date}`);
|
|
1026
|
-
lines.push(`Message-ID: ${messageId}`);
|
|
1027
|
-
if (options.subject) {
|
|
1028
|
-
lines.push(`Subject: ${options.subject}`);
|
|
1029
|
-
}
|
|
1030
|
-
if (options.headers) {
|
|
1031
|
-
for (const [key, value] of Object.entries(options.headers)) {
|
|
1032
|
-
lines.push(`${key}: ${value}`);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
lines.push("MIME-Version: 1.0");
|
|
1036
|
-
lines.push("Content-Type: text/plain; charset=UTF-8");
|
|
1037
|
-
lines.push("");
|
|
1038
|
-
lines.push(options.body ?? "");
|
|
1039
|
-
return lines.join(`\r
|
|
1040
|
-
`);
|
|
1041
|
-
}
|
|
1042
|
-
function createEmailHeaders(rawEmail) {
|
|
1043
|
-
const headers = new Headers;
|
|
1044
|
-
const lines = rawEmail.split(/\r?\n/);
|
|
1045
|
-
for (const line of lines) {
|
|
1046
|
-
if (!line.trim()) {
|
|
1047
|
-
break;
|
|
1048
|
-
}
|
|
1049
|
-
const colonIndex = line.indexOf(":");
|
|
1050
|
-
if (colonIndex <= 0) {
|
|
1051
|
-
continue;
|
|
1052
|
-
}
|
|
1053
|
-
headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim());
|
|
1536
|
+
};
|
|
1054
1537
|
}
|
|
1055
|
-
return headers;
|
|
1056
1538
|
}
|
|
1057
|
-
function
|
|
1058
|
-
return
|
|
1059
|
-
start(controller) {
|
|
1060
|
-
controller.enqueue(new TextEncoder().encode(rawEmail));
|
|
1061
|
-
controller.close();
|
|
1062
|
-
}
|
|
1063
|
-
});
|
|
1539
|
+
function create(options = {}) {
|
|
1540
|
+
return createTraceItem(options);
|
|
1064
1541
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1542
|
+
var tail = {
|
|
1543
|
+
trigger: trigger3,
|
|
1544
|
+
create
|
|
1545
|
+
};
|
|
1546
|
+
|
|
1547
|
+
// src/test/worker.ts
|
|
1548
|
+
import { join as join8 } from "path";
|
|
1549
|
+
var fetchHandlerPath = null;
|
|
1550
|
+
var configDir5 = null;
|
|
1551
|
+
var testEnvGetter5 = null;
|
|
1552
|
+
var fileRoutes = [];
|
|
1553
|
+
function configureWorker(options) {
|
|
1554
|
+
fetchHandlerPath = options.handlerPath;
|
|
1555
|
+
fileRoutes = options.routes ?? [];
|
|
1556
|
+
configDir5 = options.configDir;
|
|
1557
|
+
testEnvGetter5 = options.getEnv;
|
|
1076
1558
|
}
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1559
|
+
function resetWorkerState() {
|
|
1560
|
+
fetchHandlerPath = null;
|
|
1561
|
+
fileRoutes = [];
|
|
1562
|
+
configDir5 = null;
|
|
1563
|
+
testEnvGetter5 = null;
|
|
1082
1564
|
}
|
|
1083
|
-
async function
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
from: options.from,
|
|
1106
|
-
to: options.to,
|
|
1107
|
-
headers: createEmailHeaders(raw),
|
|
1108
|
-
raw: createRawEmailStream(raw),
|
|
1109
|
-
rawSize: raw.length,
|
|
1110
|
-
setReject(reason) {
|
|
1111
|
-
throw new Error(`Email rejected: ${reason}`);
|
|
1112
|
-
},
|
|
1113
|
-
async forward(rcptTo) {
|
|
1114
|
-
recordSentEmail({
|
|
1115
|
-
type: "forward",
|
|
1116
|
-
from: options.from,
|
|
1117
|
-
to: rcptTo,
|
|
1118
|
-
raw,
|
|
1119
|
-
timestamp
|
|
1120
|
-
});
|
|
1121
|
-
},
|
|
1122
|
-
async reply(message2) {
|
|
1123
|
-
recordSentEmail({
|
|
1124
|
-
type: "reply",
|
|
1125
|
-
from: message2.from ?? options.to,
|
|
1126
|
-
to: message2.to ?? options.from,
|
|
1127
|
-
raw: getRecordedRawContent(message2.raw),
|
|
1128
|
-
timestamp
|
|
1129
|
-
});
|
|
1565
|
+
async function fetch2(request, options) {
|
|
1566
|
+
if (!fetchHandlerPath && fileRoutes.length === 0) {
|
|
1567
|
+
throw new Error("Fetch handler not configured. Make sure your devflare.config.ts has files.fetch set or a routes directory is available, " + "and that the corresponding files exist (defaults: src/fetch.ts and src/routes/**).");
|
|
1568
|
+
}
|
|
1569
|
+
if (!configDir5 || !testEnvGetter5) {
|
|
1570
|
+
throw new Error("Worker helper not initialized. Call createTestContext() before using cf.worker.fetch()");
|
|
1571
|
+
}
|
|
1572
|
+
const workerConfigDir = configDir5;
|
|
1573
|
+
const getEnv = testEnvGetter5;
|
|
1574
|
+
let req;
|
|
1575
|
+
if (typeof request === "string") {
|
|
1576
|
+
const url = request.startsWith("http") ? request : `http://localhost${request.startsWith("/") ? "" : "/"}${request}`;
|
|
1577
|
+
const headers = new Headers(options?.headers);
|
|
1578
|
+
let body;
|
|
1579
|
+
if (options?.body !== undefined) {
|
|
1580
|
+
if (typeof options.body === "string") {
|
|
1581
|
+
body = options.body;
|
|
1582
|
+
} else {
|
|
1583
|
+
body = JSON.stringify(options.body);
|
|
1584
|
+
if (!headers.has("Content-Type")) {
|
|
1585
|
+
headers.set("Content-Type", "application/json");
|
|
1586
|
+
}
|
|
1130
1587
|
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
headers: { "Content-Type": "application/json" }
|
|
1588
|
+
}
|
|
1589
|
+
req = new Request(url, {
|
|
1590
|
+
method: options?.method ?? "GET",
|
|
1591
|
+
headers,
|
|
1592
|
+
body
|
|
1137
1593
|
});
|
|
1594
|
+
} else {
|
|
1595
|
+
req = request;
|
|
1138
1596
|
}
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1597
|
+
const handlerModule = fetchHandlerPath ? await import(join8(workerConfigDir, fetchHandlerPath)) : {};
|
|
1598
|
+
const routeModules = await Promise.all(fileRoutes.map(async (route) => {
|
|
1599
|
+
return {
|
|
1600
|
+
...route,
|
|
1601
|
+
module: await import(join8(workerConfigDir, route.filePath))
|
|
1602
|
+
};
|
|
1603
|
+
}));
|
|
1604
|
+
const methodExports = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "ALL"];
|
|
1605
|
+
const hasMethodHandler = methodExports.some((method) => {
|
|
1606
|
+
return typeof handlerModule[method] === "function" || typeof handlerModule.default?.[method] === "function";
|
|
1607
|
+
});
|
|
1608
|
+
if (!resolveFetchHandler(handlerModule) && !hasMethodHandler && routeModules.length === 0) {
|
|
1609
|
+
throw new Error(`Fetch handler at "${fetchHandlerPath}" must export one of:
|
|
1610
|
+
- request-wide "handle" middleware
|
|
1611
|
+
- named "fetch"
|
|
1612
|
+
- default fetch handler
|
|
1613
|
+
- HTTP method exports such as "GET" or "POST"`);
|
|
1614
|
+
}
|
|
1615
|
+
const waitUntilPromises = [];
|
|
1616
|
+
const ctx = {
|
|
1617
|
+
waitUntil(promise) {
|
|
1618
|
+
waitUntilPromises.push(promise);
|
|
1146
1619
|
},
|
|
1147
|
-
|
|
1620
|
+
passThroughOnException() {},
|
|
1621
|
+
props: {}
|
|
1622
|
+
};
|
|
1623
|
+
const env = getEnv();
|
|
1624
|
+
const initialRouteMatch = routeModules.length > 0 ? matchFetchRoute(routeModules, req) : null;
|
|
1625
|
+
const fetchEvent = createFetchEvent(req, env, ctx, {
|
|
1626
|
+
params: initialRouteMatch?.params ?? {}
|
|
1148
1627
|
});
|
|
1628
|
+
const response = await runWithEventContext(fetchEvent, () => invokeFetchModule(handlerModule, fetchEvent, routeModules.length > 0 ? createRouteResolve(routeModules, fetchEvent) : undefined));
|
|
1149
1629
|
return response;
|
|
1150
1630
|
}
|
|
1151
|
-
function
|
|
1152
|
-
|
|
1153
|
-
return () => {
|
|
1154
|
-
emailListeners = emailListeners.filter((cb) => cb !== callback);
|
|
1155
|
-
};
|
|
1631
|
+
async function get(path, headers) {
|
|
1632
|
+
return fetch2(path, { method: "GET", headers });
|
|
1156
1633
|
}
|
|
1157
|
-
function
|
|
1158
|
-
return
|
|
1634
|
+
async function post(path, body, headers) {
|
|
1635
|
+
return fetch2(path, { method: "POST", body, headers });
|
|
1159
1636
|
}
|
|
1160
|
-
function
|
|
1161
|
-
|
|
1637
|
+
async function put(path, body, headers) {
|
|
1638
|
+
return fetch2(path, { method: "PUT", body, headers });
|
|
1162
1639
|
}
|
|
1163
|
-
function
|
|
1164
|
-
|
|
1165
|
-
for (const listener of emailListeners) {
|
|
1166
|
-
try {
|
|
1167
|
-
listener(email);
|
|
1168
|
-
} catch (error) {
|
|
1169
|
-
console.error("[devflare/test] Email listener error:", error);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1640
|
+
async function del(path, headers) {
|
|
1641
|
+
return fetch2(path, { method: "DELETE", headers });
|
|
1172
1642
|
}
|
|
1173
|
-
function
|
|
1174
|
-
|
|
1175
|
-
emailHandlerPath = null;
|
|
1176
|
-
configDir5 = null;
|
|
1177
|
-
testEnvGetter5 = null;
|
|
1178
|
-
emailListeners = [];
|
|
1179
|
-
sentEmails = [];
|
|
1643
|
+
async function patch(path, body, headers) {
|
|
1644
|
+
return fetch2(path, { method: "PATCH", body, headers });
|
|
1180
1645
|
}
|
|
1181
|
-
var
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1646
|
+
var worker = {
|
|
1647
|
+
fetch: fetch2,
|
|
1648
|
+
get,
|
|
1649
|
+
post,
|
|
1650
|
+
put,
|
|
1651
|
+
delete: del,
|
|
1652
|
+
patch
|
|
1186
1653
|
};
|
|
1187
1654
|
|
|
1188
1655
|
// src/test/simple-context.ts
|
|
1189
|
-
function findExportedClasses(code) {
|
|
1190
|
-
const classes = [];
|
|
1191
|
-
const classPattern = /export\s+class\s+(\w+)/g;
|
|
1192
|
-
let match;
|
|
1193
|
-
while ((match = classPattern.exec(code)) !== null) {
|
|
1194
|
-
classes.push(match[1]);
|
|
1195
|
-
}
|
|
1196
|
-
return classes;
|
|
1197
|
-
}
|
|
1198
|
-
function getBunRuntime2() {
|
|
1199
|
-
const g = globalThis;
|
|
1200
|
-
if (typeof g.Bun === "object" && g.Bun !== null) {
|
|
1201
|
-
return g.Bun;
|
|
1202
|
-
}
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
1656
|
var globalClient = null;
|
|
1206
1657
|
var globalMiniflare = null;
|
|
1207
1658
|
var globalEnvProxy = null;
|
|
1208
1659
|
var globalTransportDecode = null;
|
|
1209
1660
|
var globalRemoteBindings = null;
|
|
1210
1661
|
var globalMiniflareBindings = null;
|
|
1211
|
-
var
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
const bun = getBunRuntime2();
|
|
1219
|
-
if (bun?.main) {
|
|
1220
|
-
const mainPath = bun.main;
|
|
1221
|
-
if (!mainPath.includes("[") && existsSync2(mainPath)) {
|
|
1222
|
-
return dirname2(mainPath);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
const originalPrepare = Error.prepareStackTrace;
|
|
1226
|
-
Error.prepareStackTrace = (_, stack2) => stack2;
|
|
1227
|
-
const err = new Error;
|
|
1228
|
-
const stack = err.stack;
|
|
1229
|
-
Error.prepareStackTrace = originalPrepare;
|
|
1230
|
-
for (const site of stack) {
|
|
1231
|
-
const filename = site.getFileName?.();
|
|
1232
|
-
if (filename && !filename.includes("simple-context") && !filename.includes("node_modules") && !filename.includes("[") && existsSync2(filename)) {
|
|
1233
|
-
return dirname2(filename);
|
|
1234
|
-
}
|
|
1662
|
+
var TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS = 3;
|
|
1663
|
+
var TEST_CONTEXT_STARTUP_RETRY_DELAY_MS = 75;
|
|
1664
|
+
var TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS = 8;
|
|
1665
|
+
var TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS = 150;
|
|
1666
|
+
function isRetriableTestContextStartupError(error) {
|
|
1667
|
+
if (!(error instanceof Error)) {
|
|
1668
|
+
return false;
|
|
1235
1669
|
}
|
|
1236
|
-
|
|
1670
|
+
const message = error.message.toLowerCase();
|
|
1671
|
+
return message.includes("websocket connection failed") || message.includes("connection timeout: ws://") || message.includes("econnrefused") || message.includes("eaddrinuse") || message.includes("address already in use");
|
|
1237
1672
|
}
|
|
1238
|
-
async function
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1673
|
+
async function waitForTestContextStartupRetry() {
|
|
1674
|
+
await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_STARTUP_RETRY_DELAY_MS));
|
|
1675
|
+
}
|
|
1676
|
+
async function waitForBridgeClientRetry() {
|
|
1677
|
+
await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS));
|
|
1678
|
+
}
|
|
1679
|
+
function shouldPreferBridgeBinding(hint) {
|
|
1680
|
+
return hint === "do" || hint === "service";
|
|
1681
|
+
}
|
|
1682
|
+
async function connectBridgeClientWithRetry(url) {
|
|
1683
|
+
let lastError;
|
|
1684
|
+
for (let attempt = 1;attempt <= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS; attempt++) {
|
|
1685
|
+
const client = new BridgeClient({ url });
|
|
1686
|
+
try {
|
|
1687
|
+
await client.connect();
|
|
1688
|
+
return client;
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
lastError = error;
|
|
1691
|
+
client.disconnect();
|
|
1692
|
+
if (attempt >= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) {
|
|
1693
|
+
throw error;
|
|
1694
|
+
}
|
|
1695
|
+
await waitForBridgeClientRetry();
|
|
1248
1696
|
}
|
|
1249
|
-
currentDir = parentDir;
|
|
1250
1697
|
}
|
|
1698
|
+
throw lastError instanceof Error ? lastError : new Error("Bridge-backed test context could not connect to the WebSocket gateway.");
|
|
1251
1699
|
}
|
|
1252
|
-
function
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1700
|
+
async function startBridgeBackedTestContext(mfConfig) {
|
|
1701
|
+
const { Miniflare } = await import("miniflare");
|
|
1702
|
+
for (let attempt = 1;attempt <= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS; attempt++) {
|
|
1703
|
+
const port = await getAvailablePort();
|
|
1704
|
+
let miniflare = null;
|
|
1705
|
+
let client = null;
|
|
1706
|
+
try {
|
|
1707
|
+
miniflare = new Miniflare({
|
|
1708
|
+
...mfConfig,
|
|
1709
|
+
port
|
|
1710
|
+
});
|
|
1711
|
+
await miniflare.ready;
|
|
1712
|
+
const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
1713
|
+
client = await connectBridgeClientWithRetry(`ws://localhost:${port}`);
|
|
1714
|
+
return {
|
|
1715
|
+
port,
|
|
1716
|
+
client,
|
|
1717
|
+
miniflare,
|
|
1718
|
+
miniflareBindings
|
|
1719
|
+
};
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
client?.disconnect();
|
|
1722
|
+
if (miniflare) {
|
|
1723
|
+
try {
|
|
1724
|
+
await miniflare.dispose();
|
|
1725
|
+
} catch {}
|
|
1726
|
+
}
|
|
1727
|
+
if (attempt >= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) {
|
|
1728
|
+
throw error;
|
|
1729
|
+
}
|
|
1730
|
+
await waitForTestContextStartupRetry();
|
|
1262
1731
|
}
|
|
1263
1732
|
}
|
|
1264
|
-
|
|
1733
|
+
throw new Error("Bridge-backed test context startup exhausted all retry attempts.");
|
|
1265
1734
|
}
|
|
1266
1735
|
async function createTestContext(configPath) {
|
|
1267
1736
|
const callerDir = getCallerDirectory();
|
|
@@ -1272,12 +1741,12 @@ async function createTestContext(configPath) {
|
|
|
1272
1741
|
const found = await findNearestConfig(callerDir);
|
|
1273
1742
|
if (!found) {
|
|
1274
1743
|
throw new Error(`Could not find a devflare config file. Searched upward from: ${callerDir}
|
|
1275
|
-
|
|
1276
|
-
|
|
1744
|
+
Expected one of: devflare.config.ts, devflare.config.mts, devflare.config.js, devflare.config.mjs
|
|
1745
|
+
Either create a config file or provide an explicit path: createTestContext('./path/to/config.ts')`);
|
|
1277
1746
|
}
|
|
1278
1747
|
absolutePath = found;
|
|
1279
1748
|
}
|
|
1280
|
-
const configDir6 =
|
|
1749
|
+
const configDir6 = dirname4(absolutePath);
|
|
1281
1750
|
const config = await loadConfig({
|
|
1282
1751
|
cwd: configDir6,
|
|
1283
1752
|
configFile: absolutePath.split(/[/\\]/).pop()
|
|
@@ -1306,28 +1775,34 @@ async function createTestContext(configPath) {
|
|
|
1306
1775
|
}
|
|
1307
1776
|
const hints = {};
|
|
1308
1777
|
if (config.bindings?.kv) {
|
|
1309
|
-
for (const name of Object.keys(config.bindings.kv))
|
|
1778
|
+
for (const name of Object.keys(config.bindings.kv)) {
|
|
1310
1779
|
hints[name] = "kv";
|
|
1780
|
+
}
|
|
1311
1781
|
}
|
|
1312
1782
|
if (config.bindings?.r2) {
|
|
1313
|
-
for (const name of Object.keys(config.bindings.r2))
|
|
1783
|
+
for (const name of Object.keys(config.bindings.r2)) {
|
|
1314
1784
|
hints[name] = "r2";
|
|
1785
|
+
}
|
|
1315
1786
|
}
|
|
1316
1787
|
if (config.bindings?.d1) {
|
|
1317
|
-
for (const name of Object.keys(config.bindings.d1))
|
|
1788
|
+
for (const name of Object.keys(config.bindings.d1)) {
|
|
1318
1789
|
hints[name] = "d1";
|
|
1790
|
+
}
|
|
1319
1791
|
}
|
|
1320
1792
|
if (config.bindings?.durableObjects) {
|
|
1321
|
-
for (const name of Object.keys(config.bindings.durableObjects))
|
|
1793
|
+
for (const name of Object.keys(config.bindings.durableObjects)) {
|
|
1322
1794
|
hints[name] = "do";
|
|
1795
|
+
}
|
|
1323
1796
|
}
|
|
1324
1797
|
if (config.bindings?.services) {
|
|
1325
|
-
for (const name of Object.keys(config.bindings.services))
|
|
1798
|
+
for (const name of Object.keys(config.bindings.services)) {
|
|
1326
1799
|
hints[name] = "service";
|
|
1800
|
+
}
|
|
1327
1801
|
}
|
|
1328
1802
|
if (config.bindings?.sendEmail) {
|
|
1329
|
-
for (const name of Object.keys(config.bindings.sendEmail))
|
|
1803
|
+
for (const name of Object.keys(config.bindings.sendEmail)) {
|
|
1330
1804
|
hints[name] = "sendEmail";
|
|
1805
|
+
}
|
|
1331
1806
|
}
|
|
1332
1807
|
const needsMultiWorkerForServices = hasServiceBindings(config);
|
|
1333
1808
|
const needsMultiWorkerForDOs = hasCrossWorkerDOs(config);
|
|
@@ -1340,18 +1815,21 @@ async function createTestContext(configPath) {
|
|
|
1340
1815
|
if (needsMultiWorkerForDOs) {
|
|
1341
1816
|
doBindingResolution = await resolveDOBindings(config, configDir6);
|
|
1342
1817
|
}
|
|
1343
|
-
const randomPort = 1e4 + Math.floor(Math.random() * 50000);
|
|
1344
1818
|
const localWorkerBindings = config.vars ?? {};
|
|
1345
1819
|
const mfConfig = {
|
|
1346
|
-
modules: true
|
|
1347
|
-
port: randomPort
|
|
1820
|
+
modules: true
|
|
1348
1821
|
};
|
|
1349
|
-
if (config.bindings?.kv)
|
|
1822
|
+
if (config.bindings?.kv) {
|
|
1350
1823
|
mfConfig.kvNamespaces = Object.keys(config.bindings.kv);
|
|
1351
|
-
|
|
1824
|
+
}
|
|
1825
|
+
if (config.bindings?.r2) {
|
|
1352
1826
|
mfConfig.r2Buckets = Object.keys(config.bindings.r2);
|
|
1353
|
-
|
|
1354
|
-
|
|
1827
|
+
}
|
|
1828
|
+
if (config.bindings?.d1) {
|
|
1829
|
+
mfConfig.d1Databases = Object.fromEntries(Object.entries(config.bindings.d1).map(([bindingName, bindingConfig]) => {
|
|
1830
|
+
return [bindingName, getLocalD1DatabaseIdentifier(bindingConfig)];
|
|
1831
|
+
}));
|
|
1832
|
+
}
|
|
1355
1833
|
if (config.bindings?.queues?.producers) {
|
|
1356
1834
|
const queueProducers = {};
|
|
1357
1835
|
for (const [bindingName, queueName] of Object.entries(config.bindings.queues.producers)) {
|
|
@@ -1380,7 +1858,7 @@ async function createTestContext(configPath) {
|
|
|
1380
1858
|
}
|
|
1381
1859
|
const transportFile = resolveTransportFile(configDir6, config.files?.transport);
|
|
1382
1860
|
if (transportFile) {
|
|
1383
|
-
const transportPath =
|
|
1861
|
+
const transportPath = join9(configDir6, transportFile);
|
|
1384
1862
|
const transportModule = await import(transportPath);
|
|
1385
1863
|
if (!transportModule.transport) {
|
|
1386
1864
|
console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.
|
|
@@ -1394,86 +1872,10 @@ Transport encoding/decoding will be disabled.`);
|
|
|
1394
1872
|
}
|
|
1395
1873
|
}
|
|
1396
1874
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
const doPatternConfig = config.files?.durableObjects;
|
|
1402
|
-
const doPattern = typeof doPatternConfig === "string" ? doPatternConfig : DEFAULT_DO_PATTERN;
|
|
1403
|
-
if (doPatternConfig !== false) {
|
|
1404
|
-
const fs = await import("fs/promises");
|
|
1405
|
-
const doFiles = await findFiles(doPattern, { cwd: configDir6 });
|
|
1406
|
-
for (const filePath of doFiles) {
|
|
1407
|
-
try {
|
|
1408
|
-
const code = await fs.readFile(filePath, "utf-8");
|
|
1409
|
-
const classNames = findExportedClasses(code);
|
|
1410
|
-
for (const className of classNames) {
|
|
1411
|
-
classToFilePath.set(className, filePath);
|
|
1412
|
-
}
|
|
1413
|
-
} catch {}
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
for (const [name, rawDoInfo] of Object.entries(config.bindings.durableObjects)) {
|
|
1417
|
-
const doInfo = normalizeDOBinding(rawDoInfo);
|
|
1418
|
-
if (doInfo.__ref) {
|
|
1419
|
-
continue;
|
|
1420
|
-
}
|
|
1421
|
-
let scriptPath;
|
|
1422
|
-
if (doInfo.scriptName) {
|
|
1423
|
-
scriptPath = join7(configDir6, "src", doInfo.scriptName);
|
|
1424
|
-
} else {
|
|
1425
|
-
const discoveredPath = classToFilePath.get(doInfo.className);
|
|
1426
|
-
if (!discoveredPath) {
|
|
1427
|
-
throw new Error(`Durable object ${name} (className: '${doInfo.className}') not found.
|
|
1428
|
-
Either:
|
|
1429
|
-
1. Set files.durableObjects pattern in config (e.g., 'src/do.*.ts')
|
|
1430
|
-
2. Use explicit scriptName: { className: '${doInfo.className}', scriptName: 'do.file.ts' }`);
|
|
1431
|
-
}
|
|
1432
|
-
scriptPath = discoveredPath;
|
|
1433
|
-
}
|
|
1434
|
-
doConfig[name] = doInfo.className;
|
|
1435
|
-
doInfos.push({ name, className: doInfo.className, scriptPath });
|
|
1436
|
-
}
|
|
1437
|
-
const virtualImports = [];
|
|
1438
|
-
const virtualExports = [];
|
|
1439
|
-
if (transportFile) {
|
|
1440
|
-
const transportPath = join7(configDir6, transportFile);
|
|
1441
|
-
virtualImports.push(`import { transport } from '${transportPath.replace(/\\/g, "/")}'`);
|
|
1442
|
-
virtualExports.push("export { transport }");
|
|
1443
|
-
}
|
|
1444
|
-
for (const info of doInfos) {
|
|
1445
|
-
virtualImports.push(`import { ${info.className} } from '${info.scriptPath.replace(/\\/g, "/")}'`);
|
|
1446
|
-
virtualExports.push(`export { ${info.className} }`);
|
|
1447
|
-
}
|
|
1448
|
-
let bundledCode = "";
|
|
1449
|
-
if (virtualImports.length > 0) {
|
|
1450
|
-
const virtualEntry = [...virtualImports, "", ...virtualExports].join(`
|
|
1451
|
-
`);
|
|
1452
|
-
const virtualPath = join7(configDir6, ".devflare", "__test_entry.ts");
|
|
1453
|
-
const { writeFileSync, mkdirSync } = await import("fs");
|
|
1454
|
-
mkdirSync(dirname2(virtualPath), { recursive: true });
|
|
1455
|
-
writeFileSync(virtualPath, virtualEntry);
|
|
1456
|
-
const bun = getBunRuntime2();
|
|
1457
|
-
if (!bun) {
|
|
1458
|
-
throw new Error("Bun runtime is required for createTestContext with Durable Objects");
|
|
1459
|
-
}
|
|
1460
|
-
const result = await bun.build({
|
|
1461
|
-
entrypoints: [virtualPath],
|
|
1462
|
-
target: "browser",
|
|
1463
|
-
format: "esm",
|
|
1464
|
-
minify: false,
|
|
1465
|
-
external: ["cloudflare:workers", "cloudflare:*"]
|
|
1466
|
-
});
|
|
1467
|
-
if (!result.success) {
|
|
1468
|
-
throw new Error(`Failed to bundle test entry: ${result.logs.join(`
|
|
1469
|
-
`)}`);
|
|
1470
|
-
}
|
|
1471
|
-
bundledCode = await result.outputs[0].text();
|
|
1472
|
-
}
|
|
1473
|
-
mfConfig.durableObjects = doConfig;
|
|
1474
|
-
mfConfig.script = buildGatewayScript(bundledCode, "");
|
|
1475
|
-
} else {
|
|
1476
|
-
mfConfig.script = buildGatewayScript("", "");
|
|
1875
|
+
const gateway = await buildDurableObjectGateway(config, configDir6, transportFile);
|
|
1876
|
+
mfConfig.script = gateway.script;
|
|
1877
|
+
if (gateway.durableObjects) {
|
|
1878
|
+
mfConfig.durableObjects = gateway.durableObjects;
|
|
1477
1879
|
}
|
|
1478
1880
|
const hasMultiWorkerServices = serviceBindingResolution && serviceBindingResolution.workers.length > 0;
|
|
1479
1881
|
const hasMultiWorkerDOs = doBindingResolution && doBindingResolution.workers.length > 0;
|
|
@@ -1502,14 +1904,14 @@ Either:
|
|
|
1502
1904
|
for (const worker2 of additionalWorkers) {
|
|
1503
1905
|
if (!workersByName.has(worker2.name)) {
|
|
1504
1906
|
workersByName.set(worker2.name, worker2);
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
}
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
const existing = workersByName.get(worker2.name);
|
|
1910
|
+
if (worker2.durableObjects) {
|
|
1911
|
+
existing.durableObjects = {
|
|
1912
|
+
...existing.durableObjects || {},
|
|
1913
|
+
...worker2.durableObjects
|
|
1914
|
+
};
|
|
1513
1915
|
}
|
|
1514
1916
|
}
|
|
1515
1917
|
const workers = [primaryWorker, ...workersByName.values()];
|
|
@@ -1521,10 +1923,23 @@ Either:
|
|
|
1521
1923
|
delete mfConfig.durableObjects;
|
|
1522
1924
|
mfConfig.workers = workers;
|
|
1523
1925
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1926
|
+
let activePort;
|
|
1927
|
+
if (hasMultiWorkerServices || hasMultiWorkerDOs) {
|
|
1928
|
+
const { Miniflare } = await import("miniflare");
|
|
1929
|
+
activePort = await getAvailablePort();
|
|
1930
|
+
globalMiniflare = new Miniflare({
|
|
1931
|
+
...mfConfig,
|
|
1932
|
+
port: activePort
|
|
1933
|
+
});
|
|
1934
|
+
await globalMiniflare.ready;
|
|
1935
|
+
globalMiniflareBindings = wrapEnvSendEmailBindings(await globalMiniflare.getBindings());
|
|
1936
|
+
} else {
|
|
1937
|
+
const startedBridgeBackedTestContext = await startBridgeBackedTestContext(mfConfig);
|
|
1938
|
+
activePort = startedBridgeBackedTestContext.port;
|
|
1939
|
+
globalMiniflare = startedBridgeBackedTestContext.miniflare;
|
|
1940
|
+
globalMiniflareBindings = startedBridgeBackedTestContext.miniflareBindings;
|
|
1941
|
+
globalClient = startedBridgeBackedTestContext.client;
|
|
1942
|
+
}
|
|
1528
1943
|
const disposeContext = async () => {
|
|
1529
1944
|
if (globalClient) {
|
|
1530
1945
|
await globalClient.disconnect();
|
|
@@ -1577,11 +1992,13 @@ Either:
|
|
|
1577
1992
|
const DEFAULT_EMAIL_PATH = "src/email.ts";
|
|
1578
1993
|
const DEFAULT_TAIL_PATH = "src/tail.ts";
|
|
1579
1994
|
const resolvePath = async (configValue, defaultPath) => {
|
|
1580
|
-
if (typeof configValue === "string")
|
|
1995
|
+
if (typeof configValue === "string") {
|
|
1581
1996
|
return configValue;
|
|
1582
|
-
|
|
1997
|
+
}
|
|
1998
|
+
if (configValue === false) {
|
|
1583
1999
|
return null;
|
|
1584
|
-
|
|
2000
|
+
}
|
|
2001
|
+
const defaultAbsolute = join9(configDir6, defaultPath);
|
|
1585
2002
|
try {
|
|
1586
2003
|
const fs = await import("fs/promises");
|
|
1587
2004
|
await fs.access(defaultAbsolute);
|
|
@@ -1624,7 +2041,7 @@ Either:
|
|
|
1624
2041
|
getEnv: getTestEnv
|
|
1625
2042
|
});
|
|
1626
2043
|
configureEmail({
|
|
1627
|
-
port:
|
|
2044
|
+
port: activePort,
|
|
1628
2045
|
handlerPath: resolvedEmailPath,
|
|
1629
2046
|
configDir: configDir6,
|
|
1630
2047
|
getEnv: getTestEnv
|
|
@@ -1648,29 +2065,31 @@ Either:
|
|
|
1648
2065
|
__setTestContext(envAccessor2, disposeContext);
|
|
1649
2066
|
return;
|
|
1650
2067
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
2068
|
+
const bridgeClient = globalClient;
|
|
2069
|
+
if (!bridgeClient) {
|
|
2070
|
+
throw new Error("Bridge-backed test context did not initialize a client.");
|
|
2071
|
+
}
|
|
1655
2072
|
setBindingHints(hints);
|
|
1656
2073
|
globalEnvProxy = createEnvProxy({
|
|
1657
|
-
client:
|
|
2074
|
+
client: bridgeClient,
|
|
1658
2075
|
transformResult: (result) => decodeTransport(result)
|
|
1659
2076
|
});
|
|
1660
2077
|
const envAccessor = new Proxy({}, {
|
|
1661
2078
|
get(_, prop) {
|
|
2079
|
+
const hint = hints[prop];
|
|
2080
|
+
const prefersBridgeBinding = shouldPreferBridgeBinding(hint);
|
|
1662
2081
|
if (globalRemoteBindings && prop in globalRemoteBindings) {
|
|
1663
2082
|
return globalRemoteBindings[prop];
|
|
1664
2083
|
}
|
|
1665
|
-
if (
|
|
1666
|
-
return globalEnvProxy[prop];
|
|
1667
|
-
}
|
|
1668
|
-
if (globalMiniflareBindings && prop in globalMiniflareBindings) {
|
|
2084
|
+
if (!prefersBridgeBinding && globalMiniflareBindings && prop in globalMiniflareBindings) {
|
|
1669
2085
|
return globalMiniflareBindings[prop];
|
|
1670
2086
|
}
|
|
1671
2087
|
if (globalEnvProxy) {
|
|
1672
2088
|
return globalEnvProxy[prop];
|
|
1673
2089
|
}
|
|
2090
|
+
if (prefersBridgeBinding && globalMiniflareBindings && prop in globalMiniflareBindings) {
|
|
2091
|
+
return globalMiniflareBindings[prop];
|
|
2092
|
+
}
|
|
1674
2093
|
return;
|
|
1675
2094
|
},
|
|
1676
2095
|
has(_, prop) {
|
|
@@ -1699,184 +2118,6 @@ function decodeTransport(value) {
|
|
|
1699
2118
|
}
|
|
1700
2119
|
return result;
|
|
1701
2120
|
}
|
|
1702
|
-
function buildGatewayScript(bundledCode, wrappers) {
|
|
1703
|
-
return `
|
|
1704
|
-
// Bundled transport + DO classes
|
|
1705
|
-
${bundledCode}
|
|
1706
|
-
|
|
1707
|
-
// DO Wrappers with RPC
|
|
1708
|
-
${wrappers}
|
|
1709
|
-
|
|
1710
|
-
// Transport encoding helper
|
|
1711
|
-
const __transportEncoders = typeof transport !== 'undefined' ? transport : {}
|
|
1712
|
-
|
|
1713
|
-
function __encodeTransport(value) {
|
|
1714
|
-
if (value === null || value === undefined) return value
|
|
1715
|
-
|
|
1716
|
-
// Try each encoder
|
|
1717
|
-
for (const [typeName, transporter] of Object.entries(__transportEncoders)) {
|
|
1718
|
-
const encoded = transporter.encode(value)
|
|
1719
|
-
if (encoded !== false && encoded !== undefined) {
|
|
1720
|
-
return { __transport: typeName, value: encoded }
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
// Recursively encode arrays and objects
|
|
1725
|
-
if (Array.isArray(value)) {
|
|
1726
|
-
return value.map(__encodeTransport)
|
|
1727
|
-
}
|
|
1728
|
-
if (typeof value === 'object') {
|
|
1729
|
-
const result = {}
|
|
1730
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1731
|
-
result[k] = __encodeTransport(v)
|
|
1732
|
-
}
|
|
1733
|
-
return result
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
return value
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
// Gateway with WebSocket RPC
|
|
1740
|
-
export default {
|
|
1741
|
-
async fetch(request, env) {
|
|
1742
|
-
if (request.headers.get('Upgrade') === 'websocket') {
|
|
1743
|
-
const { 0: client, 1: server } = new WebSocketPair()
|
|
1744
|
-
server.accept()
|
|
1745
|
-
server.addEventListener('message', async (e) => {
|
|
1746
|
-
try {
|
|
1747
|
-
const m = JSON.parse(e.data)
|
|
1748
|
-
if (m.t === 'rpc.call') {
|
|
1749
|
-
const result = await executeRpc(env, m.method, m.params)
|
|
1750
|
-
server.send(JSON.stringify({ t: 'rpc.ok', id: m.id, result }))
|
|
1751
|
-
}
|
|
1752
|
-
} catch (error) {
|
|
1753
|
-
server.send(JSON.stringify({ t: 'rpc.err', id: 'unknown', error: { code: 'RPC_ERROR', message: error.message } }))
|
|
1754
|
-
}
|
|
1755
|
-
})
|
|
1756
|
-
return new Response(null, { status: 101, webSocket: client })
|
|
1757
|
-
}
|
|
1758
|
-
return new Response('Gateway')
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
async function executeRpc(env, method, params) {
|
|
1763
|
-
const [bindingName, ...rest] = method.split('.')
|
|
1764
|
-
const op = rest.join('.')
|
|
1765
|
-
const binding = env[bindingName]
|
|
1766
|
-
const RAW_EMAIL = 'EmailMessage::raw'
|
|
1767
|
-
if (!binding) throw new Error('Binding not found: ' + bindingName)
|
|
1768
|
-
|
|
1769
|
-
// KV operations
|
|
1770
|
-
if (op === 'get') return binding.get(params[0], params[1])
|
|
1771
|
-
if (op === 'put') return binding.put(params[0], params[1], params[2])
|
|
1772
|
-
if (op === 'delete') return binding.delete(params[0])
|
|
1773
|
-
if (op === 'list') return binding.list(params[0])
|
|
1774
|
-
if (op === 'getWithMetadata') return binding.getWithMetadata(params[0], params[1])
|
|
1775
|
-
|
|
1776
|
-
// R2 operations
|
|
1777
|
-
if (op === 'r2.get') return binding.get(params[0], params[1])
|
|
1778
|
-
if (op === 'r2.put') return binding.put(params[0], params[1], params[2])
|
|
1779
|
-
if (op === 'r2.delete') return binding.delete(params[0])
|
|
1780
|
-
if (op === 'r2.list') return binding.list(params[0])
|
|
1781
|
-
if (op === 'head') return binding.head(params[0])
|
|
1782
|
-
|
|
1783
|
-
// D1 operations
|
|
1784
|
-
if (op === 'exec') return binding.exec(params[0])
|
|
1785
|
-
if (op === 'dump') return binding.dump()
|
|
1786
|
-
if (op === 'batch') {
|
|
1787
|
-
const stmts = params[0].map(s => {
|
|
1788
|
-
const stmt = binding.prepare(s.sql)
|
|
1789
|
-
return s.bindings?.length ? stmt.bind(...s.bindings) : stmt
|
|
1790
|
-
})
|
|
1791
|
-
return binding.batch(stmts)
|
|
1792
|
-
}
|
|
1793
|
-
if (op === 'prepare.run') return binding.prepare(params[0]).bind(...(params[1] || [])).run()
|
|
1794
|
-
if (op === 'prepare.all') return binding.prepare(params[0]).bind(...(params[1] || [])).all()
|
|
1795
|
-
if (op === 'prepare.first') return binding.prepare(params[0]).bind(...(params[1] || [])).first(params[2])
|
|
1796
|
-
if (op === 'prepare.raw') return binding.prepare(params[0]).bind(...(params[1] || [])).raw({ columnNames: params[2] })
|
|
1797
|
-
|
|
1798
|
-
// Send email operations
|
|
1799
|
-
if (op === 'email.send') {
|
|
1800
|
-
return binding.send(__normalizeEmailMessage(params[0]))
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
// DO operations
|
|
1804
|
-
if (op === 'idFromName') {
|
|
1805
|
-
return { __type: 'DOId', hex: binding.idFromName(params[0]).toString() }
|
|
1806
|
-
}
|
|
1807
|
-
if (op === 'stub.rpc') {
|
|
1808
|
-
const [, idSerialized, rpcMethod, rpcParams] = params
|
|
1809
|
-
const stub = binding.get(binding.idFromString(idSerialized.hex))
|
|
1810
|
-
|
|
1811
|
-
// Use native RPC: call method directly on stub
|
|
1812
|
-
// This works for DOs extending DurableObject from cloudflare:workers
|
|
1813
|
-
let result = await stub[rpcMethod](...(rpcParams || []))
|
|
1814
|
-
// Apply transport encoding
|
|
1815
|
-
result = __encodeTransport(result)
|
|
1816
|
-
return result
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
throw new Error('Unknown operation: ' + method)
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
function __createEmailMessageRaw(raw) {
|
|
1823
|
-
if (typeof raw === 'string' || raw instanceof ReadableStream) {
|
|
1824
|
-
return raw
|
|
1825
|
-
}
|
|
1826
|
-
if (raw instanceof Uint8Array || raw instanceof ArrayBuffer) {
|
|
1827
|
-
return new Response(raw).body
|
|
1828
|
-
}
|
|
1829
|
-
throw new Error('Unsupported EmailMessage raw payload')
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
function __buildRawEmail(message) {
|
|
1833
|
-
const lines = []
|
|
1834
|
-
const messageId = '<' + Date.now() + '-' + Math.random().toString(36).slice(2) + '@devflare.dev>'
|
|
1835
|
-
|
|
1836
|
-
lines.push('From: ' + message.from)
|
|
1837
|
-
lines.push('To: ' + (Array.isArray(message.to) ? message.to.join(', ') : message.to))
|
|
1838
|
-
lines.push('Date: ' + new Date().toUTCString())
|
|
1839
|
-
lines.push('Message-ID: ' + messageId)
|
|
1840
|
-
|
|
1841
|
-
if (message.subject) lines.push('Subject: ' + message.subject)
|
|
1842
|
-
if (message.replyTo) lines.push('Reply-To: ' + String(message.replyTo))
|
|
1843
|
-
if (message.cc) lines.push('Cc: ' + (Array.isArray(message.cc) ? message.cc.join(', ') : message.cc))
|
|
1844
|
-
if (message.bcc) lines.push('Bcc: ' + (Array.isArray(message.bcc) ? message.bcc.join(', ') : message.bcc))
|
|
1845
|
-
|
|
1846
|
-
for (const [key, value] of Object.entries(message.headers || {})) {
|
|
1847
|
-
lines.push(key + ': ' + value)
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
lines.push('MIME-Version: 1.0')
|
|
1851
|
-
lines.push('Content-Type: ' + (message.html ? 'text/html' : 'text/plain') + '; charset=UTF-8')
|
|
1852
|
-
lines.push('')
|
|
1853
|
-
lines.push(String(message.html ?? message.text ?? '').replace(/\\r?\\n/g, '\\r\\n'))
|
|
1854
|
-
|
|
1855
|
-
return lines.join('\\r\\n')
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
function __normalizeEmailMessage(message) {
|
|
1859
|
-
if (!message || typeof message !== 'object' || !('from' in message) || !('to' in message)) {
|
|
1860
|
-
return message
|
|
1861
|
-
}
|
|
1862
|
-
if ('EmailMessage::raw' in message) {
|
|
1863
|
-
return message
|
|
1864
|
-
}
|
|
1865
|
-
if ('raw' in message && message.raw !== undefined) {
|
|
1866
|
-
return {
|
|
1867
|
-
from: message.from,
|
|
1868
|
-
to: message.to,
|
|
1869
|
-
[RAW_EMAIL]: __createEmailMessageRaw(message.raw)
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
return {
|
|
1873
|
-
from: message.from,
|
|
1874
|
-
to: message.to,
|
|
1875
|
-
[RAW_EMAIL]: __createEmailMessageRaw(__buildRawEmail(message))
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
`;
|
|
1879
|
-
}
|
|
1880
2121
|
// src/test/cf.ts
|
|
1881
2122
|
var cf = {
|
|
1882
2123
|
email,
|
|
@@ -1885,71 +2126,11 @@ var cf = {
|
|
|
1885
2126
|
worker,
|
|
1886
2127
|
tail
|
|
1887
2128
|
};
|
|
1888
|
-
// src/test/multi-worker-context.ts
|
|
1889
|
-
async function createMultiWorkerContext(options) {
|
|
1890
|
-
const { workers, primary } = options;
|
|
1891
|
-
const primaryName = primary ?? workers[0]?.name;
|
|
1892
|
-
if (!primaryName) {
|
|
1893
|
-
throw new Error("At least one worker must be configured");
|
|
1894
|
-
}
|
|
1895
|
-
const { Miniflare } = await import("miniflare");
|
|
1896
|
-
const mfWorkers = workers.map((worker2) => {
|
|
1897
|
-
const serviceBindings = {};
|
|
1898
|
-
if (worker2.serviceBindings) {
|
|
1899
|
-
for (const [bindingName, target] of Object.entries(worker2.serviceBindings)) {
|
|
1900
|
-
if (typeof target === "string") {
|
|
1901
|
-
serviceBindings[bindingName] = { name: target };
|
|
1902
|
-
} else {
|
|
1903
|
-
serviceBindings[bindingName] = target;
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
return {
|
|
1908
|
-
name: worker2.name,
|
|
1909
|
-
modules: worker2.modules ?? true,
|
|
1910
|
-
script: worker2.script,
|
|
1911
|
-
compatibilityDate: worker2.compatibilityDate ?? "2025-01-01",
|
|
1912
|
-
...Object.keys(serviceBindings).length > 0 ? { serviceBindings } : {}
|
|
1913
|
-
};
|
|
1914
|
-
});
|
|
1915
|
-
const mf = new Miniflare({
|
|
1916
|
-
workers: mfWorkers
|
|
1917
|
-
});
|
|
1918
|
-
const env2 = await mf.getBindings();
|
|
1919
|
-
const fetch3 = async (input, init) => {
|
|
1920
|
-
const response = await mf.dispatchFetch(input, init);
|
|
1921
|
-
return response;
|
|
1922
|
-
};
|
|
1923
|
-
const dispose = async () => {
|
|
1924
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
1925
|
-
await mf.dispose();
|
|
1926
|
-
};
|
|
1927
|
-
return {
|
|
1928
|
-
mf,
|
|
1929
|
-
env: env2,
|
|
1930
|
-
fetch: fetch3,
|
|
1931
|
-
dispose
|
|
1932
|
-
};
|
|
1933
|
-
}
|
|
1934
|
-
function createEntrypointScript(className, methods) {
|
|
1935
|
-
const methodDefs = Object.entries(methods).map(([name, body]) => ` ${name}${body}`).join(`
|
|
1936
|
-
|
|
1937
|
-
`);
|
|
1938
|
-
return `import { WorkerEntrypoint } from 'cloudflare:workers'
|
|
1939
|
-
|
|
1940
|
-
export class ${className} extends WorkerEntrypoint {
|
|
1941
|
-
${methodDefs}
|
|
1942
|
-
}
|
|
1943
|
-
`;
|
|
1944
|
-
}
|
|
1945
2129
|
// src/test/should-skip.ts
|
|
1946
2130
|
var REMOTE_ONLY_SERVICES = new Set([
|
|
1947
2131
|
"ai",
|
|
1948
2132
|
"vectorize"
|
|
1949
2133
|
]);
|
|
1950
|
-
function isRemoteModeEnabled() {
|
|
1951
|
-
return isRemoteModeActive();
|
|
1952
|
-
}
|
|
1953
2134
|
var skipResults = new Map;
|
|
1954
2135
|
var EXPECTED_ERROR_PATTERNS = [
|
|
1955
2136
|
"ECONNREFUSED",
|
|
@@ -2465,4 +2646,4 @@ var testEnv = new Proxy({}, {
|
|
|
2465
2646
|
function isKVNamespace(binding) {
|
|
2466
2647
|
return typeof binding === "object" && binding !== null && "get" in binding && "put" in binding && "delete" in binding && "list" in binding;
|
|
2467
2648
|
}
|
|
2468
|
-
export { clearBundleCache, hasServiceBindings, resolveServiceBindings, hasCrossWorkerDOs, resolveDOBindings, queue, scheduled,
|
|
2649
|
+
export { clearBundleCache, hasServiceBindings, resolveServiceBindings, hasCrossWorkerDOs, resolveDOBindings, email, queue, scheduled, tail, worker, createTestContext, cf, shouldSkip, createMockTestContext, withTestContext, createMockKV, createMockD1, createMockR2, createMockQueue, createMockEnv, createBridgeTestContext, stopBridgeTestContext, getBridgeTestContext, testEnv };
|