devflare 1.0.0-next.19 → 1.0.0-next.20
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 +6807 -888
- package/README.md +375 -957
- package/dist/account-05zgta47.js +475 -0
- package/dist/account-b2ag1esh.js +475 -0
- package/dist/account-bxtcz61a.js +475 -0
- package/dist/account-gyfqg964.js +475 -0
- package/dist/account-q6pvs9d9.js +475 -0
- package/dist/account-rp4zbvw1.js +475 -0
- package/dist/bridge/client.d.ts +5 -0
- package/dist/bridge/client.d.ts.map +1 -1
- package/dist/bridge/gateway-runtime.d.ts +1 -1
- package/dist/bridge/gateway-runtime.d.ts.map +1 -1
- package/dist/bridge/miniflare.d.ts +67 -0
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/bridge/proxy.d.ts +2 -1
- package/dist/bridge/proxy.d.ts.map +1 -1
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/browser.d.ts +2520 -40
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -3
- package/dist/build-2s5paw5p.js +54 -0
- package/dist/build-4c350cp7.js +54 -0
- package/dist/build-e7wym63t.js +54 -0
- package/dist/build-ge6qp3t4.js +54 -0
- package/dist/build-ta8c6t11.js +54 -0
- package/dist/build-wvjj8f28.js +54 -0
- package/dist/build-ypg6f2kw.js +54 -0
- package/dist/build-yts8wwgf.js +54 -0
- package/dist/build-yzkdqexs.js +54 -0
- package/dist/cli/build-manifest.d.ts +12 -0
- package/dist/cli/build-manifest.d.ts.map +1 -1
- package/dist/cli/commands/deploy/metadata.d.ts +18 -0
- package/dist/cli/commands/deploy/metadata.d.ts.map +1 -0
- package/dist/cli/commands/deploy/prepare.d.ts +23 -0
- package/dist/cli/commands/deploy/prepare.d.ts.map +1 -0
- package/dist/cli/commands/deploy/runtime.d.ts +4 -0
- package/dist/cli/commands/deploy/runtime.d.ts.map +1 -0
- package/dist/cli/commands/deploy/verification.d.ts +36 -0
- package/dist/cli/commands/deploy/verification.d.ts.map +1 -0
- package/dist/cli/commands/deploy.d.ts +2 -2
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/secrets.d.ts +4 -0
- package/dist/cli/commands/secrets.d.ts.map +1 -0
- package/dist/cli/commands/type-generation/generator.d.ts +67 -1
- package/dist/cli/commands/type-generation/generator.d.ts.map +1 -1
- package/dist/cli/help-pages/pages/core.d.ts +1 -1
- package/dist/cli/help-pages/pages/core.d.ts.map +1 -1
- package/dist/cli/help-pages/shared.d.ts +1 -1
- package/dist/cli/help-pages/shared.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/package-metadata.d.ts.map +1 -1
- package/dist/cli/preview-bindings.d.ts.map +1 -1
- package/dist/cloudflare/index.js +2 -2
- package/dist/cloudflare/preview-registry-store.d.ts +1 -1
- package/dist/cloudflare/preview-registry-store.d.ts.map +1 -1
- package/dist/cloudflare/types.d.ts +1 -1
- package/dist/cloudflare/types.d.ts.map +1 -1
- package/dist/config/binding-resolution-helpers.d.ts +5 -0
- package/dist/config/binding-resolution-helpers.d.ts.map +1 -1
- package/dist/config/compiler/bindings.d.ts +14 -0
- package/dist/config/compiler/bindings.d.ts.map +1 -0
- package/dist/config/compiler/core-helpers.d.ts +6 -0
- package/dist/config/compiler/core-helpers.d.ts.map +1 -0
- package/dist/config/compiler/do-workers.d.ts +34 -0
- package/dist/config/compiler/do-workers.d.ts.map +1 -0
- package/dist/config/compiler/paths.d.ts +18 -0
- package/dist/config/compiler/paths.d.ts.map +1 -0
- package/dist/config/compiler/types.d.ts +267 -0
- package/dist/config/compiler/types.d.ts.map +1 -0
- package/dist/config/compiler.d.ts +11 -175
- package/dist/config/compiler.d.ts.map +1 -1
- package/dist/config/deploy-resources.d.ts.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/local-dev-vars.d.ts +15 -0
- package/dist/config/local-dev-vars.d.ts.map +1 -0
- package/dist/config/preview-resources.d.ts.map +1 -1
- package/dist/config/preview.d.ts.map +1 -1
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resource-resolution.d.ts.map +1 -1
- package/dist/config/schema-bindings.d.ts +559 -19
- package/dist/config/schema-bindings.d.ts.map +1 -1
- package/dist/config/schema-env.d.ts +1306 -34
- package/dist/config/schema-env.d.ts.map +1 -1
- package/dist/config/schema-normalization.d.ts +97 -1
- package/dist/config/schema-normalization.d.ts.map +1 -1
- package/dist/config/schema-runtime.d.ts +245 -7
- package/dist/config/schema-runtime.d.ts.map +1 -1
- package/dist/config/schema.d.ts +2976 -57
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config-6m0n7d84.js +59 -0
- package/dist/config-7cf004ag.js +59 -0
- package/dist/config-b98dp58n.js +59 -0
- package/dist/config-cf3djhqy.js +59 -0
- package/dist/config-entry.js +1 -1
- package/dist/config-wa7hm0w9.js +59 -0
- package/dist/deploy-1jfagtn9.js +1055 -0
- package/dist/deploy-2afw0jfg.js +1055 -0
- package/dist/deploy-2fzj68kq.js +1055 -0
- package/dist/deploy-57nzn9wj.js +1045 -0
- package/dist/deploy-asyryrvm.js +1055 -0
- package/dist/deploy-hc927rw6.js +1045 -0
- package/dist/deploy-pnnf8tgy.js +1045 -0
- package/dist/deploy-q33bw715.js +1055 -0
- package/dist/deploy-tmdgecs3.js +1055 -0
- package/dist/deploy-v0y8kczr.js +1055 -0
- package/dist/deploy-xhj6zbcx.js +1055 -0
- package/dist/dev-1mvcts8w.js +2515 -0
- package/dist/dev-2a283xts.js +2515 -0
- package/dist/dev-62nhytf8.js +2505 -0
- package/dist/dev-75acm2xj.js +2478 -0
- package/dist/dev-802rg9dp.js +2515 -0
- package/dist/dev-d1bb2t0f.js +2515 -0
- package/dist/dev-dwry8494.js +2489 -0
- package/dist/dev-g6112y4w.js +2515 -0
- package/dist/dev-h2kneh95.js +2496 -0
- package/dist/dev-kybq3mwr.js +2489 -0
- package/dist/dev-n8qndkyg.js +2512 -0
- package/dist/dev-p32fkbwf.js +2489 -0
- package/dist/dev-qm9d4mfh.js +2478 -0
- package/dist/dev-rcthnse5.js +2473 -0
- package/dist/dev-server/dev-server-state.d.ts +1 -0
- package/dist/dev-server/dev-server-state.d.ts.map +1 -1
- package/dist/dev-server/miniflare-bindings.d.ts +44 -1
- package/dist/dev-server/miniflare-bindings.d.ts.map +1 -1
- package/dist/dev-server/miniflare-dev-config.d.ts +1 -0
- package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -1
- package/dist/dev-server/miniflare-log.d.ts +8 -0
- package/dist/dev-server/miniflare-log.d.ts.map +1 -1
- package/dist/dev-server/miniflare-worker-config.d.ts +31 -1
- package/dist/dev-server/miniflare-worker-config.d.ts.map +1 -1
- package/dist/dev-server/server.d.ts.map +1 -1
- package/dist/dev-server/vite-process.d.ts +1 -0
- package/dist/dev-server/vite-process.d.ts.map +1 -1
- package/dist/dev-tgwja5mz.js +2496 -0
- package/dist/doctor-2shhdak6.js +245 -0
- package/dist/doctor-5g73w40j.js +245 -0
- package/dist/doctor-gamefzcs.js +245 -0
- package/dist/doctor-rn53ctfs.js +245 -0
- package/dist/index-01kehw41.js +348 -0
- package/dist/index-06bg0z9y.js +185 -0
- package/dist/index-0d7tw5r4.js +136 -0
- package/dist/index-0m6e4mxz.js +133 -0
- package/dist/index-0vah20er.js +1410 -0
- package/dist/index-0wa0ebm1.js +68 -0
- package/dist/index-1714y3cz.js +1410 -0
- package/dist/index-1qs5gcm7.js +895 -0
- package/dist/index-29k04v43.js +574 -0
- package/dist/index-2jywf4pz.js +1372 -0
- package/dist/index-2qhk9nbx.js +1372 -0
- package/dist/index-2vq6bveq.js +574 -0
- package/dist/index-36h8gkhb.js +1088 -0
- package/dist/index-38fq7pww.js +560 -0
- package/dist/index-3bxqn033.js +1410 -0
- package/dist/index-3jme4hgw.js +1234 -0
- package/dist/index-3p7s9mk9.js +360 -0
- package/dist/index-47w35sft.js +244 -0
- package/dist/index-4by4c7rm.js +52 -0
- package/dist/index-4phjwd6h.js +412 -0
- package/dist/index-4z5jrw0j.js +594 -0
- package/dist/index-51mzqy0d.js +895 -0
- package/dist/index-53pqqpq9.js +74 -0
- package/dist/index-5enq8ntr.js +1766 -0
- package/dist/index-5fnq9r9m.js +1410 -0
- package/dist/index-5w9f2b17.js +695 -0
- package/dist/index-627srx16.js +45 -0
- package/dist/index-6bqgf5x8.js +227 -0
- package/dist/index-6xknvbyk.js +1088 -0
- package/dist/index-7ef3ktz5.js +1372 -0
- package/dist/index-7hpjfdzh.js +185 -0
- package/dist/index-8052df4m.js +627 -0
- package/dist/index-82epjzrr.js +1410 -0
- package/dist/index-82z7rvz6.js +1238 -0
- package/dist/index-8atc1yb9.js +68 -0
- package/dist/index-8tj0awnv.js +476 -0
- package/dist/index-8x745h59.js +1069 -0
- package/dist/index-9bawzcny.js +574 -0
- package/dist/index-9bjjqdfc.js +236 -0
- package/dist/index-9d7x3vfr.js +236 -0
- package/dist/index-9nf8zs4p.js +1069 -0
- package/dist/index-acwbmagz.js +412 -0
- package/dist/index-aqjdaem7.js +74 -0
- package/dist/index-b6448fd0.js +133 -0
- package/dist/index-b9j55r7q.js +240 -0
- package/dist/index-bdatd1za.js +1372 -0
- package/dist/index-c3nxftnp.js +699 -0
- package/dist/index-c643s0gv.js +488 -0
- package/dist/index-d2md1j3d.js +185 -0
- package/dist/index-dbr6bfz6.js +528 -0
- package/dist/index-dd1g0g7e.js +360 -0
- package/dist/index-dktb9az5.js +1372 -0
- package/dist/index-dm9q84c7.js +360 -0
- package/dist/index-f51mkh13.js +1088 -0
- package/dist/index-f86n1fpd.js +55 -0
- package/dist/index-fnk0tkw7.js +412 -0
- package/dist/index-g5aq66bj.js +1534 -0
- package/dist/index-gj5qh491.js +54 -0
- package/dist/index-gq39t0rx.js +895 -0
- package/dist/index-h5dqna7q.js +1410 -0
- package/dist/index-hjs9j2g9.js +895 -0
- package/dist/index-hn5nbxbt.js +147 -0
- package/dist/index-hpjh0qjx.js +1723 -0
- package/dist/index-hs6ekcfs.js +412 -0
- package/dist/index-jdzrvnfj.js +52 -0
- package/dist/index-jg720mq7.js +476 -0
- package/dist/index-jrzddxvt.js +2167 -0
- package/dist/index-kgstnk6g.js +239 -0
- package/dist/index-khnw972v.js +117 -0
- package/dist/index-kwqff3ba.js +1410 -0
- package/dist/index-m2v0fj08.js +74 -0
- package/dist/index-mjve6tqn.js +447 -0
- package/dist/index-mkxzgn0q.js +1372 -0
- package/dist/index-mzmq3v0d.js +1088 -0
- package/dist/index-ng9n3znd.js +1372 -0
- package/dist/index-nhbkm2ba.js +467 -0
- package/dist/index-nrfhk0k5.js +1088 -0
- package/dist/index-p0zppqxm.js +467 -0
- package/dist/index-pkxf6h87.js +895 -0
- package/dist/index-pqp4312v.js +52 -0
- package/dist/index-pw9jn6kz.js +574 -0
- package/dist/index-q31ne0xa.js +412 -0
- package/dist/index-qf2dkqxh.js +249 -0
- package/dist/index-qmtdf7k5.js +639 -0
- package/dist/index-qwgr4q7s.js +37 -0
- package/dist/index-rab2dfh3.js +494 -0
- package/dist/index-rz7rx80s.js +1410 -0
- package/dist/index-s37h3jgk.js +572 -0
- package/dist/index-sb705m7d.js +52 -0
- package/dist/index-syscwrjp.js +1576 -0
- package/dist/index-t14zr0ys.js +1063 -0
- package/dist/index-tjc99447.js +68 -0
- package/dist/index-v35460hf.js +574 -0
- package/dist/index-v7q00d1e.js +1410 -0
- package/dist/index-vkkmx4xe.js +1372 -0
- package/dist/index-vrps1gky.js +2202 -0
- package/dist/index-w4c9vmvg.js +1517 -0
- package/dist/index-wqd8n2qk.js +574 -0
- package/dist/index-x12e6fzy.js +476 -0
- package/dist/index-xagpz645.js +2199 -0
- package/dist/index-xbth1r6e.js +572 -0
- package/dist/index-xm9fqhcb.js +447 -0
- package/dist/index-y59hnmd0.js +132 -0
- package/dist/index-y7mkb00x.js +133 -0
- package/dist/index-z40mjts9.js +212 -0
- package/dist/index-z5k5bjc7.js +1218 -0
- package/dist/index-z73sytma.js +895 -0
- package/dist/index-zjv6apef.js +1410 -0
- package/dist/index.js +8 -8
- package/dist/init-cwpergap.js +180 -0
- package/dist/login-83bjfhvz.js +77 -0
- package/dist/login-ddw888xb.js +77 -0
- package/dist/login-e7pytkdc.js +77 -0
- package/dist/login-fe0brfcr.js +77 -0
- package/dist/login-h7sm5trm.js +77 -0
- package/dist/login-vd0m3xr4.js +77 -0
- package/dist/previews-2wfvsjfy.js +1337 -0
- package/dist/previews-31feb8r3.js +1337 -0
- package/dist/previews-3w4pxqby.js +1337 -0
- package/dist/previews-93ttrf5f.js +1337 -0
- package/dist/previews-bdrefjzx.js +1337 -0
- package/dist/previews-cfcn56b4.js +1337 -0
- package/dist/previews-mssq1hrm.js +1337 -0
- package/dist/previews-tcaz1gt8.js +1337 -0
- package/dist/productions-4h80j2c7.js +505 -0
- package/dist/productions-86jaqt7m.js +505 -0
- package/dist/productions-bn2q31my.js +505 -0
- package/dist/productions-dv8g7f6g.js +505 -0
- package/dist/productions-e2m9s4tr.js +505 -0
- package/dist/productions-fgshs1m7.js +505 -0
- package/dist/productions-hphmt68n.js +505 -0
- package/dist/productions-vhq7yx86.js +505 -0
- package/dist/runtime/index.js +8 -8
- package/dist/secrets/local-secrets.d.ts +46 -0
- package/dist/secrets/local-secrets.d.ts.map +1 -0
- package/dist/secrets-8wcj47nh.js +91 -0
- package/dist/secrets-b2ww34ta.js +91 -0
- package/dist/secrets-b7g4z621.js +91 -0
- package/dist/shims/local-media-bindings.d.ts +19 -0
- package/dist/shims/local-media-bindings.d.ts.map +1 -0
- package/dist/shims/local-worker-loader.d.ts +3 -0
- package/dist/shims/local-worker-loader.d.ts.map +1 -0
- package/dist/sveltekit/index.js +163 -26
- package/dist/sveltekit/local-bindings.d.ts +4 -0
- package/dist/sveltekit/local-bindings.d.ts.map +1 -0
- package/dist/sveltekit/platform.d.ts +8 -0
- package/dist/sveltekit/platform.d.ts.map +1 -1
- package/dist/test/ai-search.d.ts +39 -0
- package/dist/test/ai-search.d.ts.map +1 -0
- package/dist/test/binding-hints.d.ts.map +1 -1
- package/dist/test/cf.d.ts +3 -3
- package/dist/test/containers.d.ts +87 -0
- package/dist/test/containers.d.ts.map +1 -0
- package/dist/test/index.d.ts +4 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +2833 -543
- package/dist/test/local-worker-loader.d.ts +3 -0
- package/dist/test/local-worker-loader.d.ts.map +1 -0
- package/dist/test/offline-bindings.d.ts +65 -0
- package/dist/test/offline-bindings.d.ts.map +1 -0
- package/dist/test/queue.d.ts.map +1 -1
- package/dist/test/remote-ai.d.ts.map +1 -1
- package/dist/test/should-skip.d.ts +14 -0
- package/dist/test/should-skip.d.ts.map +1 -1
- package/dist/test/simple-context-bindings.d.ts.map +1 -1
- package/dist/test/simple-context-durable-objects.d.ts.map +1 -1
- package/dist/test/simple-context-gateway-script.d.ts +1 -1
- package/dist/test/simple-context-gateway-script.d.ts.map +1 -1
- package/dist/test/simple-context-lifecycle.d.ts.map +1 -1
- package/dist/test/simple-context-mfconfig.d.ts +4 -1
- package/dist/test/simple-context-mfconfig.d.ts.map +1 -1
- package/dist/test/simple-context-multi-worker.d.ts.map +1 -1
- package/dist/test/simple-context-startup.d.ts.map +1 -1
- package/dist/test/tail.d.ts.map +1 -1
- package/dist/test/utilities/artifacts.d.ts +11 -0
- package/dist/test/utilities/artifacts.d.ts.map +1 -0
- package/dist/test/utilities/context.d.ts +39 -0
- package/dist/test/utilities/context.d.ts.map +1 -0
- package/dist/test/utilities/d1.d.ts +21 -0
- package/dist/test/utilities/d1.d.ts.map +1 -0
- package/dist/test/utilities/env.d.ts +40 -0
- package/dist/test/utilities/env.d.ts.map +1 -0
- package/dist/test/utilities/kv.d.ts +11 -0
- package/dist/test/utilities/kv.d.ts.map +1 -0
- package/dist/test/utilities/media.d.ts +16 -0
- package/dist/test/utilities/media.d.ts.map +1 -0
- package/dist/test/utilities/platform.d.ts +38 -0
- package/dist/test/utilities/platform.d.ts.map +1 -0
- package/dist/test/utilities/queue.d.ts +5 -0
- package/dist/test/utilities/queue.d.ts.map +1 -0
- package/dist/test/utilities/r2.d.ts +12 -0
- package/dist/test/utilities/r2.d.ts.map +1 -0
- package/dist/test/utilities/workflows.d.ts +26 -0
- package/dist/test/utilities/workflows.d.ts.map +1 -0
- package/dist/test/utilities.d.ts +10 -106
- package/dist/test/utilities.d.ts.map +1 -1
- package/dist/types-2ejrbba1.js +695 -0
- package/dist/types-7jkbm95a.js +695 -0
- package/dist/types-a2fk9yns.js +695 -0
- package/dist/types-dyb3c6zw.js +695 -0
- package/dist/types-e2n9f3pd.js +695 -0
- package/dist/types-j4s6qcrc.js +695 -0
- package/dist/utils/send-email.d.ts.map +1 -1
- package/dist/utils/send-email.js +1 -1
- package/dist/vite/index.js +6 -6
- package/dist/vite/plugin-context.d.ts.map +1 -1
- package/dist/worker-663em30d.js +513 -0
- package/dist/worker-argxc7fb.js +513 -0
- package/dist/worker-entry/composed-worker.d.ts.map +1 -1
- package/dist/worker-entry/surface-paths.d.ts +2 -0
- package/dist/worker-entry/surface-paths.d.ts.map +1 -1
- package/dist/worker-fcdsnj14.js +513 -0
- package/dist/worker-fk42rzse.js +513 -0
- package/dist/worker-jkemk8d2.js +513 -0
- package/dist/worker-m4ze8djx.js +513 -0
- package/dist/worker-wnan5dca.js +513 -0
- package/dist/worker-yw3atfb1.js +513 -0
- package/dist/workflows/local-workflow-entrypoints.d.ts +7 -0
- package/dist/workflows/local-workflow-entrypoints.d.ts.map +1 -0
- package/package.json +13 -12
package/dist/test/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "../index-d8bdkx2h.js";
|
|
5
5
|
import {
|
|
6
6
|
discoverEntrypointsSync
|
|
7
|
-
} from "../index-
|
|
7
|
+
} from "../index-gj5qh491.js";
|
|
8
8
|
import {
|
|
9
9
|
resolvePackageSpecifier
|
|
10
10
|
} from "../index-t4fhcx1n.js";
|
|
@@ -14,56 +14,81 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
transformWorkerEntrypoint
|
|
16
16
|
} from "../index-7k278fgz.js";
|
|
17
|
+
import {
|
|
18
|
+
buildHyperdrivesConfig,
|
|
19
|
+
bundleWorkflowEntrypointScript
|
|
20
|
+
} from "../index-01kehw41.js";
|
|
21
|
+
import"../index-gq39t0rx.js";
|
|
17
22
|
import {
|
|
18
23
|
createRouteResolve,
|
|
19
24
|
invokeFetchModule,
|
|
20
25
|
matchFetchRoute,
|
|
21
26
|
resolveFetchHandler
|
|
22
|
-
} from "../index-
|
|
27
|
+
} from "../index-fnk0tkw7.js";
|
|
23
28
|
import {
|
|
24
29
|
__clearTestContext,
|
|
25
30
|
__setTestContext,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
createQueueEvent,
|
|
29
|
-
createScheduledEvent,
|
|
30
|
-
createTailEvent,
|
|
31
|
-
env,
|
|
32
|
-
runWithContext,
|
|
33
|
-
runWithEventContext
|
|
34
|
-
} from "../index-esjrgt3y.js";
|
|
31
|
+
env
|
|
32
|
+
} from "../index-0wa0ebm1.js";
|
|
35
33
|
import"../index-a855bdsx.js";
|
|
36
34
|
import {
|
|
37
35
|
discoverRoutes
|
|
38
|
-
} from "../index-
|
|
36
|
+
} from "../index-qf2dkqxh.js";
|
|
39
37
|
import {
|
|
40
38
|
DEFAULT_DO_PATTERN,
|
|
41
39
|
findFiles,
|
|
42
40
|
findFilesSync
|
|
43
|
-
} from "../index-
|
|
41
|
+
} from "../index-qwgr4q7s.js";
|
|
44
42
|
import"../index-65e7xx1a.js";
|
|
45
43
|
import {
|
|
46
44
|
findDurableObjectClasses
|
|
47
45
|
} from "../index-vhqww6tt.js";
|
|
48
46
|
import {
|
|
47
|
+
createLocalWorkerLoaderBinding,
|
|
48
|
+
disposeLocalWorkerLoaderBindings,
|
|
49
49
|
extractBindingHints
|
|
50
|
-
} from "../index-
|
|
50
|
+
} from "../index-y59hnmd0.js";
|
|
51
|
+
import {
|
|
52
|
+
buildLocalBindingShimServiceConfig
|
|
53
|
+
} from "../index-dm9q84c7.js";
|
|
54
|
+
import {
|
|
55
|
+
buildLocalSecretWrappedBindingConfig,
|
|
56
|
+
resolveLocalSecretValuesForBindings
|
|
57
|
+
} from "../index-hn5nbxbt.js";
|
|
51
58
|
import {
|
|
52
59
|
BridgeClient,
|
|
60
|
+
createEmailEvent,
|
|
53
61
|
createEnvProxy,
|
|
62
|
+
createFetchEvent,
|
|
63
|
+
createQueueEvent,
|
|
64
|
+
createScheduledEvent,
|
|
65
|
+
createTailEvent,
|
|
66
|
+
runWithContext,
|
|
67
|
+
runWithEventContext,
|
|
54
68
|
setBindingHints
|
|
55
|
-
} from "../index-
|
|
69
|
+
} from "../index-vrps1gky.js";
|
|
56
70
|
import {
|
|
57
71
|
createLocalSendEmailBinding,
|
|
58
72
|
wrapEnvSendEmailBindings
|
|
59
|
-
} from "../index-
|
|
60
|
-
import
|
|
73
|
+
} from "../index-kgstnk6g.js";
|
|
74
|
+
import {
|
|
75
|
+
applyLocalDevVarsToConfig
|
|
76
|
+
} from "../index-c3nxftnp.js";
|
|
61
77
|
import {
|
|
62
78
|
getLocalD1DatabaseIdentifier,
|
|
63
79
|
loadConfig,
|
|
80
|
+
normalizeArtifactsBinding,
|
|
64
81
|
normalizeDOBinding,
|
|
82
|
+
normalizeDispatchNamespaceBinding,
|
|
83
|
+
normalizeHyperdriveBinding,
|
|
84
|
+
normalizeImagesBinding,
|
|
85
|
+
normalizeMediaBinding,
|
|
86
|
+
normalizeMtlsCertificateBinding,
|
|
87
|
+
normalizePipelineBinding,
|
|
88
|
+
normalizeSecretsStoreBinding,
|
|
89
|
+
normalizeWorkflowBinding,
|
|
65
90
|
resolveConfigPath
|
|
66
|
-
} from "../index-
|
|
91
|
+
} from "../index-syscwrjp.js";
|
|
67
92
|
import {
|
|
68
93
|
getEffectiveAccountId,
|
|
69
94
|
getPrimaryAccount
|
|
@@ -72,7 +97,7 @@ import {
|
|
|
72
97
|
getApiToken,
|
|
73
98
|
isAuthenticated
|
|
74
99
|
} from "../index-mg8vwqxf.js";
|
|
75
|
-
import"../index-
|
|
100
|
+
import"../index-z40mjts9.js";
|
|
76
101
|
import"../index-q8f4kawk.js";
|
|
77
102
|
import {
|
|
78
103
|
__require
|
|
@@ -456,7 +481,8 @@ import { readFile } from "fs/promises";
|
|
|
456
481
|
import { dirname as dirname3, join as join3 } from "path";
|
|
457
482
|
|
|
458
483
|
// src/test/simple-context-gateway-script.ts
|
|
459
|
-
function buildGatewayScript(bundledCode, wrappers) {
|
|
484
|
+
function buildGatewayScript(bundledCode, wrappers, nativeRpcBindingNames = []) {
|
|
485
|
+
const nativeRpcBindingsLiteral = JSON.stringify(nativeRpcBindingNames);
|
|
460
486
|
return `
|
|
461
487
|
// Bundled transport + DO classes
|
|
462
488
|
${bundledCode}
|
|
@@ -464,6 +490,8 @@ ${bundledCode}
|
|
|
464
490
|
// DO Wrappers with RPC
|
|
465
491
|
${wrappers}
|
|
466
492
|
|
|
493
|
+
const __nativeRpcBindings = new Set(${nativeRpcBindingsLiteral})
|
|
494
|
+
|
|
467
495
|
// Transport encoding helper
|
|
468
496
|
const __transportEncoders = typeof transport !== 'undefined' ? transport : {}
|
|
469
497
|
|
|
@@ -584,7 +612,7 @@ async function executeRpc(env, method, params) {
|
|
|
584
612
|
const [, idSerialized, rpcMethod, rpcParams] = params
|
|
585
613
|
const stub = binding.get(binding.idFromString(idSerialized.hex))
|
|
586
614
|
|
|
587
|
-
if (typeof stub[rpcMethod] === 'function') {
|
|
615
|
+
if (__nativeRpcBindings.has(bindingName) && typeof stub[rpcMethod] === 'function') {
|
|
588
616
|
let result = await stub[rpcMethod](...(rpcParams || []))
|
|
589
617
|
result = __encodeTransport(result)
|
|
590
618
|
return result
|
|
@@ -949,17 +977,22 @@ async function bundleDurableObjectModules(configDir, doInfos, transportFile) {
|
|
|
949
977
|
return await result.outputs[0].text();
|
|
950
978
|
}
|
|
951
979
|
async function buildDurableObjectGateway(config, configDir, transportFile) {
|
|
980
|
+
const workflowEntrypointScript = await bundleWorkflowEntrypointScript(config, configDir);
|
|
952
981
|
if (!config.bindings?.durableObjects) {
|
|
953
982
|
return {
|
|
954
|
-
script: buildGatewayScript(
|
|
983
|
+
script: buildGatewayScript(workflowEntrypointScript, "")
|
|
955
984
|
};
|
|
956
985
|
}
|
|
957
986
|
const { doConfig, doInfos } = await resolveLocalDurableObjects(config, configDir);
|
|
958
987
|
const bundledCode = await bundleDurableObjectModules(configDir, doInfos, transportFile);
|
|
959
988
|
const wrapperCode = buildWrapperCode(doInfos);
|
|
989
|
+
const entrypointCode = [workflowEntrypointScript, bundledCode].filter(Boolean).join(`
|
|
990
|
+
|
|
991
|
+
`);
|
|
992
|
+
const nativeRpcBindingNames = doInfos.filter((info) => info.nativeRpc).map((info) => info.name);
|
|
960
993
|
return {
|
|
961
994
|
durableObjects: doConfig,
|
|
962
|
-
script: buildGatewayScript(
|
|
995
|
+
script: buildGatewayScript(entrypointCode, wrapperCode, nativeRpcBindingNames)
|
|
963
996
|
};
|
|
964
997
|
}
|
|
965
998
|
|
|
@@ -1017,9 +1050,70 @@ function createRemoteCloudflareClient(accountId) {
|
|
|
1017
1050
|
}
|
|
1018
1051
|
|
|
1019
1052
|
// src/test/remote-ai.ts
|
|
1053
|
+
function encodePathSegment(value) {
|
|
1054
|
+
return encodeURIComponent(value);
|
|
1055
|
+
}
|
|
1056
|
+
function applyExtraHeaders(headers, extraHeaders) {
|
|
1057
|
+
if (!extraHeaders) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
for (const [key, value] of Object.entries(extraHeaders)) {
|
|
1061
|
+
headers.set(key, String(value));
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function createRemoteAIGateway(cloudflare, gatewayId, owner) {
|
|
1065
|
+
const encodedGatewayId = encodePathSegment(gatewayId);
|
|
1066
|
+
const gateway = {
|
|
1067
|
+
async patchLog(logId, data) {
|
|
1068
|
+
await cloudflare.jsonRequest({
|
|
1069
|
+
method: "PATCH",
|
|
1070
|
+
path: `/ai-gateway/gateways/${encodedGatewayId}/logs/${encodePathSegment(logId)}`,
|
|
1071
|
+
serviceLabel: "AI Gateway",
|
|
1072
|
+
body: JSON.stringify(data)
|
|
1073
|
+
});
|
|
1074
|
+
},
|
|
1075
|
+
async getLog(logId) {
|
|
1076
|
+
return cloudflare.jsonRequest({
|
|
1077
|
+
method: "GET",
|
|
1078
|
+
path: `/ai-gateway/gateways/${encodedGatewayId}/logs/${encodePathSegment(logId)}`,
|
|
1079
|
+
serviceLabel: "AI Gateway"
|
|
1080
|
+
});
|
|
1081
|
+
},
|
|
1082
|
+
async getUrl(provider) {
|
|
1083
|
+
const accountId = await cloudflare.getAccountId();
|
|
1084
|
+
const baseUrl = `https://gateway.ai.cloudflare.com/v1/${encodePathSegment(accountId)}/${encodedGatewayId}`;
|
|
1085
|
+
return provider ? `${baseUrl}/${encodePathSegment(provider)}` : `${baseUrl}/`;
|
|
1086
|
+
},
|
|
1087
|
+
async run(data, options) {
|
|
1088
|
+
const [url, token] = await Promise.all([gateway.getUrl(), cloudflare.getToken()]);
|
|
1089
|
+
const headers = new Headers({
|
|
1090
|
+
Authorization: `Bearer ${token}`,
|
|
1091
|
+
"Content-Type": "application/json"
|
|
1092
|
+
});
|
|
1093
|
+
applyExtraHeaders(headers, options?.extraHeaders);
|
|
1094
|
+
const response = await fetch(url, {
|
|
1095
|
+
method: "POST",
|
|
1096
|
+
headers,
|
|
1097
|
+
body: JSON.stringify(data),
|
|
1098
|
+
signal: options?.signal
|
|
1099
|
+
});
|
|
1100
|
+
const logId = response.headers.get("cf-aig-log-id") ?? response.headers.get("cf-ai-gateway-log-id");
|
|
1101
|
+
if (logId) {
|
|
1102
|
+
owner.aiGatewayLogId = logId;
|
|
1103
|
+
}
|
|
1104
|
+
if (!response.ok) {
|
|
1105
|
+
const errorText = await response.text();
|
|
1106
|
+
throw new Error(`AI Gateway API error (${response.status}): ${errorText}`);
|
|
1107
|
+
}
|
|
1108
|
+
return response;
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
return gateway;
|
|
1112
|
+
}
|
|
1020
1113
|
function createRemoteAI(accountId) {
|
|
1021
1114
|
const cloudflare = createRemoteCloudflareClient(accountId);
|
|
1022
1115
|
const ai = {
|
|
1116
|
+
aiGatewayLogId: null,
|
|
1023
1117
|
async run(model, inputs) {
|
|
1024
1118
|
return cloudflare.jsonRequest({
|
|
1025
1119
|
method: "POST",
|
|
@@ -1028,9 +1122,8 @@ function createRemoteAI(accountId) {
|
|
|
1028
1122
|
body: JSON.stringify(inputs)
|
|
1029
1123
|
});
|
|
1030
1124
|
},
|
|
1031
|
-
gateway(
|
|
1032
|
-
|
|
1033
|
-
return ai;
|
|
1125
|
+
gateway(gatewayId) {
|
|
1126
|
+
return createRemoteAIGateway(cloudflare, gatewayId, ai);
|
|
1034
1127
|
}
|
|
1035
1128
|
};
|
|
1036
1129
|
return ai;
|
|
@@ -1101,169 +1194,1106 @@ function createRemoteVectorize(indexName, accountId) {
|
|
|
1101
1194
|
return vectorize;
|
|
1102
1195
|
}
|
|
1103
1196
|
|
|
1104
|
-
// src/test/
|
|
1105
|
-
function
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
remoteBindings[key] = value;
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
if (config.bindings?.sendEmail) {
|
|
1124
|
-
for (const [name, binding] of Object.entries(config.bindings.sendEmail)) {
|
|
1125
|
-
remoteBindings[name] = createLocalSendEmailBinding(binding);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
return remoteBindings;
|
|
1197
|
+
// src/test/utilities/context.ts
|
|
1198
|
+
function createMockTestContext(options = {}) {
|
|
1199
|
+
const waitUntilPromises = [];
|
|
1200
|
+
const ctx = {
|
|
1201
|
+
waitUntil(promise) {
|
|
1202
|
+
waitUntilPromises.push(promise);
|
|
1203
|
+
},
|
|
1204
|
+
passThroughOnException() {},
|
|
1205
|
+
props: {}
|
|
1206
|
+
};
|
|
1207
|
+
return {
|
|
1208
|
+
env: options.env ?? {},
|
|
1209
|
+
ctx,
|
|
1210
|
+
request: options.request ?? null,
|
|
1211
|
+
waitUntilPromises
|
|
1212
|
+
};
|
|
1129
1213
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
var miniflarePort = 8787;
|
|
1134
|
-
var emailListeners = [];
|
|
1135
|
-
var sentEmails = [];
|
|
1136
|
-
var emailHandlerPath = null;
|
|
1137
|
-
var configDir = null;
|
|
1138
|
-
var testEnvGetter = null;
|
|
1139
|
-
function configureEmail(options = {}) {
|
|
1140
|
-
if (options.port) {
|
|
1141
|
-
miniflarePort = options.port;
|
|
1142
|
-
}
|
|
1143
|
-
emailHandlerPath = options.handlerPath ?? emailHandlerPath;
|
|
1144
|
-
configDir = options.configDir ?? configDir;
|
|
1145
|
-
testEnvGetter = options.getEnv ?? testEnvGetter;
|
|
1214
|
+
async function withTestContext(options, handler) {
|
|
1215
|
+
const testCtx = createMockTestContext(options);
|
|
1216
|
+
return runWithContext(testCtx.env, testCtx.ctx, options.request ?? null, handler, options.type ?? "fetch");
|
|
1146
1217
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const
|
|
1152
|
-
const
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
lines.push(`To: ${options.to}`);
|
|
1156
|
-
lines.push(`Date: ${date}`);
|
|
1157
|
-
lines.push(`Message-ID: ${messageId}`);
|
|
1158
|
-
if (options.subject) {
|
|
1159
|
-
lines.push(`Subject: ${options.subject}`);
|
|
1218
|
+
// src/test/utilities/kv.ts
|
|
1219
|
+
function createMockKV(initialData = {}) {
|
|
1220
|
+
const store = new Map;
|
|
1221
|
+
const metadata = new Map;
|
|
1222
|
+
const encoder = new TextEncoder;
|
|
1223
|
+
const decoder = new TextDecoder;
|
|
1224
|
+
for (const [key, value] of Object.entries(initialData)) {
|
|
1225
|
+
store.set(key, encoder.encode(value));
|
|
1160
1226
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1227
|
+
const toBytes = async (value) => {
|
|
1228
|
+
if (typeof value === "string") {
|
|
1229
|
+
return encoder.encode(value);
|
|
1164
1230
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
lines.push("Content-Type: text/plain; charset=UTF-8");
|
|
1168
|
-
lines.push("");
|
|
1169
|
-
lines.push(options.body ?? "");
|
|
1170
|
-
return lines.join(`\r
|
|
1171
|
-
`);
|
|
1172
|
-
}
|
|
1173
|
-
function createEmailHeaders(rawEmail) {
|
|
1174
|
-
const headers = new Headers;
|
|
1175
|
-
const lines = rawEmail.split(/\r?\n/);
|
|
1176
|
-
for (const line of lines) {
|
|
1177
|
-
if (!line.trim()) {
|
|
1178
|
-
break;
|
|
1231
|
+
if (value instanceof ArrayBuffer) {
|
|
1232
|
+
return new Uint8Array(value.slice(0));
|
|
1179
1233
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1234
|
+
if (ArrayBuffer.isView(value)) {
|
|
1235
|
+
const view = value;
|
|
1236
|
+
const copy = new Uint8Array(view.byteLength);
|
|
1237
|
+
copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
1238
|
+
return copy;
|
|
1183
1239
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1240
|
+
const reader = value.getReader();
|
|
1241
|
+
const chunks = [];
|
|
1242
|
+
let total = 0;
|
|
1243
|
+
while (true) {
|
|
1244
|
+
const result = await reader.read();
|
|
1245
|
+
if (result.done)
|
|
1246
|
+
break;
|
|
1247
|
+
if (result.value) {
|
|
1248
|
+
const chunk = result.value instanceof Uint8Array ? result.value : new Uint8Array(result.value);
|
|
1249
|
+
chunks.push(chunk);
|
|
1250
|
+
total += chunk.length;
|
|
1251
|
+
}
|
|
1193
1252
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
}
|
|
1200
|
-
if (module.default && typeof module.default.email === "function") {
|
|
1201
|
-
return module.default.email.bind(module.default);
|
|
1202
|
-
}
|
|
1203
|
-
if (typeof module.email === "function") {
|
|
1204
|
-
return module.email;
|
|
1205
|
-
}
|
|
1206
|
-
return null;
|
|
1207
|
-
}
|
|
1208
|
-
function getRecordedRawContent(raw) {
|
|
1209
|
-
if (typeof raw === "string") {
|
|
1210
|
-
return raw;
|
|
1211
|
-
}
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
async function send(options) {
|
|
1215
|
-
const raw = buildRawEmail(options);
|
|
1216
|
-
if (emailHandlerPath && configDir && testEnvGetter) {
|
|
1217
|
-
const absolutePath = join4(configDir, emailHandlerPath);
|
|
1218
|
-
const handlerModule = await import(absolutePath);
|
|
1219
|
-
const emailHandler = resolveEmailHandler(handlerModule);
|
|
1220
|
-
if (!emailHandler) {
|
|
1221
|
-
throw new Error(`Email handler at "${emailHandlerPath}" must export a default function or named "email" export.
|
|
1222
|
-
NaN`);
|
|
1253
|
+
const combined = new Uint8Array(total);
|
|
1254
|
+
let offset = 0;
|
|
1255
|
+
for (const chunk of chunks) {
|
|
1256
|
+
combined.set(chunk, offset);
|
|
1257
|
+
offset += chunk.length;
|
|
1223
1258
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
const message = {
|
|
1235
|
-
from: options.from,
|
|
1236
|
-
to: options.to,
|
|
1237
|
-
headers: createEmailHeaders(raw),
|
|
1238
|
-
raw: createRawEmailStream(raw),
|
|
1239
|
-
rawSize: raw.length,
|
|
1240
|
-
setReject(reason) {
|
|
1241
|
-
throw new Error(`Email rejected: ${reason}`);
|
|
1242
|
-
},
|
|
1243
|
-
async forward(rcptTo) {
|
|
1244
|
-
recordSentEmail({
|
|
1245
|
-
type: "forward",
|
|
1246
|
-
from: options.from,
|
|
1247
|
-
to: rcptTo,
|
|
1248
|
-
raw,
|
|
1249
|
-
timestamp
|
|
1250
|
-
});
|
|
1251
|
-
},
|
|
1252
|
-
async reply(message2) {
|
|
1253
|
-
recordSentEmail({
|
|
1254
|
-
type: "reply",
|
|
1255
|
-
from: message2.from ?? options.to,
|
|
1256
|
-
to: message2.to ?? options.from,
|
|
1257
|
-
raw: getRecordedRawContent(message2.raw),
|
|
1258
|
-
timestamp
|
|
1259
|
-
});
|
|
1259
|
+
return combined;
|
|
1260
|
+
};
|
|
1261
|
+
const decodeBytes = (bytes, type) => {
|
|
1262
|
+
switch (type) {
|
|
1263
|
+
case "json":
|
|
1264
|
+
return JSON.parse(decoder.decode(bytes));
|
|
1265
|
+
case "arrayBuffer": {
|
|
1266
|
+
const copy = new Uint8Array(bytes.length);
|
|
1267
|
+
copy.set(bytes);
|
|
1268
|
+
return copy.buffer;
|
|
1260
1269
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1270
|
+
case "stream": {
|
|
1271
|
+
const copy = new Uint8Array(bytes.length);
|
|
1272
|
+
copy.set(bytes);
|
|
1273
|
+
return new ReadableStream({
|
|
1274
|
+
start(controller) {
|
|
1275
|
+
controller.enqueue(copy);
|
|
1276
|
+
controller.close();
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
default:
|
|
1281
|
+
return decoder.decode(bytes);
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
const resolveType = (options) => {
|
|
1285
|
+
const type = typeof options === "string" ? options : options?.type ?? "text";
|
|
1286
|
+
return type;
|
|
1287
|
+
};
|
|
1288
|
+
return {
|
|
1289
|
+
async get(key, options) {
|
|
1290
|
+
const bytes = store.get(key);
|
|
1291
|
+
if (bytes === undefined)
|
|
1292
|
+
return null;
|
|
1293
|
+
return decodeBytes(bytes, resolveType(options));
|
|
1294
|
+
},
|
|
1295
|
+
async put(key, value, _options) {
|
|
1296
|
+
const bytes = await toBytes(value);
|
|
1297
|
+
store.set(key, bytes);
|
|
1298
|
+
},
|
|
1299
|
+
async delete(key) {
|
|
1300
|
+
store.delete(key);
|
|
1301
|
+
metadata.delete(key);
|
|
1302
|
+
},
|
|
1303
|
+
async list(options) {
|
|
1304
|
+
const prefix = options?.prefix ?? "";
|
|
1305
|
+
const limit = options?.limit ?? 1000;
|
|
1306
|
+
const keys = Array.from(store.keys()).filter((key) => key.startsWith(prefix)).slice(0, limit).map((name) => ({ name }));
|
|
1307
|
+
return {
|
|
1308
|
+
keys,
|
|
1309
|
+
list_complete: keys.length < limit,
|
|
1310
|
+
cursor: undefined
|
|
1311
|
+
};
|
|
1312
|
+
},
|
|
1313
|
+
async getWithMetadata(key, options) {
|
|
1314
|
+
const bytes = store.get(key);
|
|
1315
|
+
return {
|
|
1316
|
+
value: bytes === undefined ? null : decodeBytes(bytes, resolveType(options)),
|
|
1317
|
+
metadata: metadata.get(key) ?? null
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
// src/test/utilities/d1.ts
|
|
1323
|
+
var TABLE_NAME_RE = /(?:from|into|update)\s+["'`]?([a-zA-Z_][a-zA-Z0-9_]*)["'`]?/i;
|
|
1324
|
+
var extractTable = (sql) => {
|
|
1325
|
+
const match = TABLE_NAME_RE.exec(sql);
|
|
1326
|
+
return match ? match[1] : null;
|
|
1327
|
+
};
|
|
1328
|
+
var detectOp = (sql) => {
|
|
1329
|
+
const trimmed = sql.trimStart().toLowerCase();
|
|
1330
|
+
if (trimmed.startsWith("select"))
|
|
1331
|
+
return "select";
|
|
1332
|
+
if (trimmed.startsWith("insert"))
|
|
1333
|
+
return "insert";
|
|
1334
|
+
if (trimmed.startsWith("update"))
|
|
1335
|
+
return "update";
|
|
1336
|
+
if (trimmed.startsWith("delete"))
|
|
1337
|
+
return "delete";
|
|
1338
|
+
return "other";
|
|
1339
|
+
};
|
|
1340
|
+
function createMockD1(mockResultsOrOptions = []) {
|
|
1341
|
+
const options = Array.isArray(mockResultsOrOptions) ? { results: mockResultsOrOptions } : mockResultsOrOptions;
|
|
1342
|
+
const tables = new Map;
|
|
1343
|
+
for (const [name, rows] of Object.entries(options.fixtures ?? {})) {
|
|
1344
|
+
tables.set(name, [...rows]);
|
|
1345
|
+
}
|
|
1346
|
+
const fallback = options.results ?? [];
|
|
1347
|
+
const resolveRows = (sql) => {
|
|
1348
|
+
const op = detectOp(sql);
|
|
1349
|
+
const table = extractTable(sql);
|
|
1350
|
+
if (table && tables.has(table)) {
|
|
1351
|
+
return { rows: tables.get(table) ?? [], op, table };
|
|
1352
|
+
}
|
|
1353
|
+
return { rows: [...fallback], op, table };
|
|
1354
|
+
};
|
|
1355
|
+
const createStatement = (sql) => {
|
|
1356
|
+
let boundValues = [];
|
|
1357
|
+
const statement = {
|
|
1358
|
+
bind(...values) {
|
|
1359
|
+
boundValues = values;
|
|
1360
|
+
return statement;
|
|
1361
|
+
},
|
|
1362
|
+
async first(column) {
|
|
1363
|
+
const { rows } = resolveRows(sql);
|
|
1364
|
+
const row = rows[0];
|
|
1365
|
+
if (!row)
|
|
1366
|
+
return null;
|
|
1367
|
+
if (column)
|
|
1368
|
+
return row[column];
|
|
1369
|
+
return row;
|
|
1370
|
+
},
|
|
1371
|
+
async all() {
|
|
1372
|
+
const { rows } = resolveRows(sql);
|
|
1373
|
+
return {
|
|
1374
|
+
results: rows,
|
|
1375
|
+
success: true,
|
|
1376
|
+
meta: { duration: 0, changes: 0, last_row_id: 0 }
|
|
1377
|
+
};
|
|
1378
|
+
},
|
|
1379
|
+
async run() {
|
|
1380
|
+
const { op, table } = resolveRows(sql);
|
|
1381
|
+
let changes = 0;
|
|
1382
|
+
let lastRowId = 0;
|
|
1383
|
+
if (op === "insert" && table) {
|
|
1384
|
+
const rows = tables.get(table) ?? [];
|
|
1385
|
+
const bound = boundValues.length > 0 ? Object.fromEntries(boundValues.map((v, i) => [`col${i}`, v])) : {};
|
|
1386
|
+
rows.push(bound);
|
|
1387
|
+
tables.set(table, rows);
|
|
1388
|
+
changes = 1;
|
|
1389
|
+
lastRowId = rows.length;
|
|
1390
|
+
} else if (op === "delete" && table) {
|
|
1391
|
+
const rows = tables.get(table) ?? [];
|
|
1392
|
+
changes = rows.length;
|
|
1393
|
+
tables.set(table, []);
|
|
1394
|
+
} else if (op === "update" && table) {
|
|
1395
|
+
changes = (tables.get(table) ?? []).length;
|
|
1396
|
+
}
|
|
1397
|
+
return {
|
|
1398
|
+
results: [],
|
|
1399
|
+
success: true,
|
|
1400
|
+
meta: { duration: 0, changes, last_row_id: lastRowId }
|
|
1401
|
+
};
|
|
1402
|
+
},
|
|
1403
|
+
async raw(_options) {
|
|
1404
|
+
const { rows } = resolveRows(sql);
|
|
1405
|
+
return rows.map((row) => Object.values(row));
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
return statement;
|
|
1409
|
+
};
|
|
1410
|
+
return {
|
|
1411
|
+
prepare(query) {
|
|
1412
|
+
return createStatement(query);
|
|
1413
|
+
},
|
|
1414
|
+
async exec(_query) {
|
|
1415
|
+
return {
|
|
1416
|
+
results: [],
|
|
1417
|
+
success: true,
|
|
1418
|
+
meta: { duration: 0, changes: 0, last_row_id: 0 }
|
|
1419
|
+
};
|
|
1420
|
+
},
|
|
1421
|
+
async batch(statements) {
|
|
1422
|
+
return statements.map(() => ({
|
|
1423
|
+
results: [],
|
|
1424
|
+
success: true,
|
|
1425
|
+
meta: { duration: 0, changes: 0, last_row_id: 0 }
|
|
1426
|
+
}));
|
|
1427
|
+
},
|
|
1428
|
+
async dump() {
|
|
1429
|
+
return new ArrayBuffer(0);
|
|
1430
|
+
},
|
|
1431
|
+
withSession(_constraintOrBookmark) {
|
|
1432
|
+
return this;
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
// src/test/utilities/r2.ts
|
|
1437
|
+
function createMockR2() {
|
|
1438
|
+
const store = new Map;
|
|
1439
|
+
const createR2Object = (key, content, metadata) => {
|
|
1440
|
+
const encoder = new TextEncoder;
|
|
1441
|
+
const data = encoder.encode(content);
|
|
1442
|
+
return {
|
|
1443
|
+
key,
|
|
1444
|
+
version: "1",
|
|
1445
|
+
size: data.length,
|
|
1446
|
+
etag: `"${key}-etag"`,
|
|
1447
|
+
httpEtag: `"${key}-etag"`,
|
|
1448
|
+
uploaded: new Date,
|
|
1449
|
+
httpMetadata: metadata ?? {},
|
|
1450
|
+
customMetadata: {},
|
|
1451
|
+
checksums: {},
|
|
1452
|
+
storageClass: "Standard",
|
|
1453
|
+
body: new ReadableStream({
|
|
1454
|
+
start(controller) {
|
|
1455
|
+
controller.enqueue(data);
|
|
1456
|
+
controller.close();
|
|
1457
|
+
}
|
|
1458
|
+
}),
|
|
1459
|
+
bodyUsed: false,
|
|
1460
|
+
async arrayBuffer() {
|
|
1461
|
+
return new Uint8Array(data).buffer;
|
|
1462
|
+
},
|
|
1463
|
+
async text() {
|
|
1464
|
+
return content;
|
|
1465
|
+
},
|
|
1466
|
+
async json() {
|
|
1467
|
+
return JSON.parse(content);
|
|
1468
|
+
},
|
|
1469
|
+
async blob() {
|
|
1470
|
+
return new Blob([data]);
|
|
1471
|
+
},
|
|
1472
|
+
writeHttpMetadata(headers) {}
|
|
1473
|
+
};
|
|
1474
|
+
};
|
|
1475
|
+
return {
|
|
1476
|
+
async put(key, value, options) {
|
|
1477
|
+
let content;
|
|
1478
|
+
if (typeof value === "string") {
|
|
1479
|
+
content = value;
|
|
1480
|
+
} else if (value instanceof ArrayBuffer) {
|
|
1481
|
+
content = new TextDecoder().decode(value);
|
|
1482
|
+
} else if (value instanceof Blob) {
|
|
1483
|
+
content = await value.text();
|
|
1484
|
+
} else if (value === null) {
|
|
1485
|
+
content = "";
|
|
1486
|
+
} else {
|
|
1487
|
+
const reader = value.getReader();
|
|
1488
|
+
const chunks = [];
|
|
1489
|
+
let done = false;
|
|
1490
|
+
while (!done) {
|
|
1491
|
+
const result = await reader.read();
|
|
1492
|
+
done = result.done;
|
|
1493
|
+
if (result.value) {
|
|
1494
|
+
chunks.push(new TextDecoder().decode(result.value));
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
content = chunks.join("");
|
|
1498
|
+
}
|
|
1499
|
+
store.set(key, { content });
|
|
1500
|
+
return createR2Object(key, content);
|
|
1501
|
+
},
|
|
1502
|
+
async get(key, options) {
|
|
1503
|
+
const item = store.get(key);
|
|
1504
|
+
if (!item)
|
|
1505
|
+
return null;
|
|
1506
|
+
return createR2Object(key, item.content, item.metadata);
|
|
1507
|
+
},
|
|
1508
|
+
async head(key) {
|
|
1509
|
+
const item = store.get(key);
|
|
1510
|
+
if (!item)
|
|
1511
|
+
return null;
|
|
1512
|
+
return {
|
|
1513
|
+
key,
|
|
1514
|
+
version: "1",
|
|
1515
|
+
size: new TextEncoder().encode(item.content).length,
|
|
1516
|
+
etag: `"${key}-etag"`,
|
|
1517
|
+
httpEtag: `"${key}-etag"`,
|
|
1518
|
+
uploaded: new Date,
|
|
1519
|
+
httpMetadata: item.metadata ?? {},
|
|
1520
|
+
customMetadata: {},
|
|
1521
|
+
checksums: {},
|
|
1522
|
+
storageClass: "Standard",
|
|
1523
|
+
writeHttpMetadata(headers) {}
|
|
1524
|
+
};
|
|
1525
|
+
},
|
|
1526
|
+
async delete(keys) {
|
|
1527
|
+
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
1528
|
+
for (const key of keyArray) {
|
|
1529
|
+
store.delete(key);
|
|
1530
|
+
}
|
|
1531
|
+
},
|
|
1532
|
+
async list(options) {
|
|
1533
|
+
const objects = Array.from(store.entries()).map(([key, { content, metadata }]) => createR2Object(key, content, metadata));
|
|
1534
|
+
return {
|
|
1535
|
+
objects,
|
|
1536
|
+
truncated: false,
|
|
1537
|
+
delimitedPrefixes: []
|
|
1538
|
+
};
|
|
1539
|
+
},
|
|
1540
|
+
async createMultipartUpload(key, options) {
|
|
1541
|
+
throw new Error("Multipart upload not implemented in mock");
|
|
1542
|
+
},
|
|
1543
|
+
async resumeMultipartUpload(key, uploadId) {
|
|
1544
|
+
throw new Error("Multipart upload not implemented in mock");
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
// src/test/utilities/queue.ts
|
|
1549
|
+
function createMockQueue() {
|
|
1550
|
+
const messages = [];
|
|
1551
|
+
const metrics = {
|
|
1552
|
+
backlogCount: 0,
|
|
1553
|
+
backlogBytes: 0
|
|
1554
|
+
};
|
|
1555
|
+
const response = { metadata: { metrics } };
|
|
1556
|
+
return {
|
|
1557
|
+
async metrics() {
|
|
1558
|
+
return metrics;
|
|
1559
|
+
},
|
|
1560
|
+
async send(message, options) {
|
|
1561
|
+
messages.push({ body: message, options });
|
|
1562
|
+
return response;
|
|
1563
|
+
},
|
|
1564
|
+
async sendBatch(batch, options) {
|
|
1565
|
+
for (const message of batch) {
|
|
1566
|
+
messages.push({
|
|
1567
|
+
body: message.body,
|
|
1568
|
+
options: {
|
|
1569
|
+
contentType: message.contentType,
|
|
1570
|
+
delaySeconds: message.delaySeconds ?? options?.delaySeconds
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
return response;
|
|
1575
|
+
},
|
|
1576
|
+
_getMessages() {
|
|
1577
|
+
return messages;
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
// src/test/utilities/platform.ts
|
|
1582
|
+
function createMockRateLimit(options = {}) {
|
|
1583
|
+
const limit = options.limit ?? Number.MAX_SAFE_INTEGER;
|
|
1584
|
+
const periodMs = (options.period ?? 60) * 1000;
|
|
1585
|
+
const windows = new Map;
|
|
1586
|
+
return {
|
|
1587
|
+
async limit({ key }) {
|
|
1588
|
+
const now = Date.now();
|
|
1589
|
+
const existing = windows.get(key);
|
|
1590
|
+
if (!existing || existing.resetAt <= now) {
|
|
1591
|
+
windows.set(key, { count: 1, resetAt: now + periodMs });
|
|
1592
|
+
return { success: limit >= 1 };
|
|
1593
|
+
}
|
|
1594
|
+
existing.count += 1;
|
|
1595
|
+
return { success: existing.count <= limit };
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
function createMockVersionMetadata(metadata = {}) {
|
|
1600
|
+
return {
|
|
1601
|
+
id: metadata.id ?? "devflare-local-version",
|
|
1602
|
+
tag: metadata.tag ?? "local",
|
|
1603
|
+
timestamp: metadata.timestamp ?? "1970-01-01T00:00:00.000Z"
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function createMockSecretsStoreSecret(value) {
|
|
1607
|
+
return {
|
|
1608
|
+
async get() {
|
|
1609
|
+
return value;
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function defaultPortForDatabaseUrl(url) {
|
|
1614
|
+
if (url.port) {
|
|
1615
|
+
return Number(url.port);
|
|
1616
|
+
}
|
|
1617
|
+
return url.protocol === "mysql:" ? 3306 : 5432;
|
|
1618
|
+
}
|
|
1619
|
+
function createMockHyperdrive(connectionString) {
|
|
1620
|
+
const url = new URL(connectionString);
|
|
1621
|
+
return {
|
|
1622
|
+
connectionString,
|
|
1623
|
+
host: url.hostname,
|
|
1624
|
+
port: defaultPortForDatabaseUrl(url),
|
|
1625
|
+
user: decodeURIComponent(url.username),
|
|
1626
|
+
password: decodeURIComponent(url.password),
|
|
1627
|
+
database: decodeURIComponent(url.pathname.replace(/^\//, "")),
|
|
1628
|
+
connect() {
|
|
1629
|
+
throw new Error("Mock Hyperdrive connect() is not implemented. Use connectionString with your database client, or run a Miniflare-backed test for socket behavior.");
|
|
1630
|
+
}
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
function createDefaultWorkerStub() {
|
|
1634
|
+
return {
|
|
1635
|
+
getEntrypoint() {
|
|
1636
|
+
throw new Error("Mock WorkerLoader stub has no entrypoint. Pass createMockWorkerLoader({ stub }) for behavior.");
|
|
1637
|
+
},
|
|
1638
|
+
getDurableObjectClass() {
|
|
1639
|
+
throw new Error("Mock WorkerLoader stub has no Durable Object class. Pass createMockWorkerLoader({ stub }) for behavior.");
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
function createMockWorkerLoader(options = {}) {
|
|
1644
|
+
const stub = options.stub ?? createDefaultWorkerStub();
|
|
1645
|
+
return {
|
|
1646
|
+
get(_name, _getCode) {
|
|
1647
|
+
return stub;
|
|
1648
|
+
},
|
|
1649
|
+
load(_code) {
|
|
1650
|
+
return stub;
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
function defaultMTLSCertificateHandler() {
|
|
1655
|
+
throw new Error("Mock mTLS Certificate Fetcher has no handler. Pass createMockMTLSCertificate(handler) for behavior.");
|
|
1656
|
+
}
|
|
1657
|
+
function createMockMTLSCertificate(handler = defaultMTLSCertificateHandler) {
|
|
1658
|
+
return {
|
|
1659
|
+
async fetch(input, init) {
|
|
1660
|
+
return handler(input, init);
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function createMockDispatchNamespace(options = {}) {
|
|
1665
|
+
return {
|
|
1666
|
+
get(name) {
|
|
1667
|
+
const worker = options.workers?.[name];
|
|
1668
|
+
if (!worker) {
|
|
1669
|
+
throw new Error(`Mock DispatchNamespace has no worker named "${name}".`);
|
|
1670
|
+
}
|
|
1671
|
+
return typeof worker === "function" ? createMockMTLSCertificate(worker) : worker;
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
// src/test/utilities/workflows.ts
|
|
1676
|
+
function createMockWorkflowInstance(id, options = {}) {
|
|
1677
|
+
let status = options.status ?? "queued";
|
|
1678
|
+
let output = options.output;
|
|
1679
|
+
let error = options.error;
|
|
1680
|
+
return {
|
|
1681
|
+
id,
|
|
1682
|
+
async pause() {
|
|
1683
|
+
status = "paused";
|
|
1684
|
+
},
|
|
1685
|
+
async resume() {
|
|
1686
|
+
status = "running";
|
|
1687
|
+
},
|
|
1688
|
+
async terminate() {
|
|
1689
|
+
status = "terminated";
|
|
1690
|
+
},
|
|
1691
|
+
async restart() {
|
|
1692
|
+
status = "queued";
|
|
1693
|
+
error = undefined;
|
|
1694
|
+
output = undefined;
|
|
1695
|
+
},
|
|
1696
|
+
async status() {
|
|
1697
|
+
return {
|
|
1698
|
+
status,
|
|
1699
|
+
...error && { error },
|
|
1700
|
+
...output !== undefined && { output }
|
|
1701
|
+
};
|
|
1702
|
+
},
|
|
1703
|
+
async sendEvent(_event) {}
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
function createMockWorkflow(options = {}) {
|
|
1707
|
+
const instances = new Map;
|
|
1708
|
+
let sequence = 0;
|
|
1709
|
+
for (const [id, instanceOptions] of Object.entries(options.instances ?? {})) {
|
|
1710
|
+
instances.set(id, createMockWorkflowInstance(id, instanceOptions));
|
|
1711
|
+
}
|
|
1712
|
+
const createInstance = (id) => {
|
|
1713
|
+
if (instances.has(id)) {
|
|
1714
|
+
throw new Error(`Mock Workflow already has an instance named "${id}".`);
|
|
1715
|
+
}
|
|
1716
|
+
const instance = createMockWorkflowInstance(id);
|
|
1717
|
+
instances.set(id, instance);
|
|
1718
|
+
return instance;
|
|
1719
|
+
};
|
|
1720
|
+
return {
|
|
1721
|
+
async get(id) {
|
|
1722
|
+
const instance = instances.get(id);
|
|
1723
|
+
if (!instance) {
|
|
1724
|
+
throw new Error(`Mock Workflow has no instance named "${id}".`);
|
|
1725
|
+
}
|
|
1726
|
+
return instance;
|
|
1727
|
+
},
|
|
1728
|
+
async create(options2) {
|
|
1729
|
+
const id = options2?.id ?? `mock-workflow-${++sequence}`;
|
|
1730
|
+
return createInstance(id);
|
|
1731
|
+
},
|
|
1732
|
+
async createBatch(batch) {
|
|
1733
|
+
return batch.map((options2) => {
|
|
1734
|
+
const id = options2.id ?? `mock-workflow-${++sequence}`;
|
|
1735
|
+
return createInstance(id);
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function createMockPipeline() {
|
|
1741
|
+
const records = [];
|
|
1742
|
+
return {
|
|
1743
|
+
async send(batch) {
|
|
1744
|
+
records.push(...batch);
|
|
1745
|
+
},
|
|
1746
|
+
_getRecords() {
|
|
1747
|
+
return [...records];
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
// src/test/utilities/media.ts
|
|
1752
|
+
function createEmptyImageStream() {
|
|
1753
|
+
return new ReadableStream({
|
|
1754
|
+
start(controller) {
|
|
1755
|
+
controller.close();
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
function createMockImageTransformationResult(response) {
|
|
1760
|
+
return {
|
|
1761
|
+
response() {
|
|
1762
|
+
return response.clone();
|
|
1763
|
+
},
|
|
1764
|
+
contentType() {
|
|
1765
|
+
return response.headers.get("Content-Type") ?? "image/png";
|
|
1766
|
+
},
|
|
1767
|
+
image() {
|
|
1768
|
+
const cloned = response.clone();
|
|
1769
|
+
return cloned.body ?? createEmptyImageStream();
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
function createMockImageTransformer(response) {
|
|
1774
|
+
const transformer = {
|
|
1775
|
+
transform(_transform) {
|
|
1776
|
+
return transformer;
|
|
1777
|
+
},
|
|
1778
|
+
draw(_image, _options) {
|
|
1779
|
+
return transformer;
|
|
1780
|
+
},
|
|
1781
|
+
async output(_options) {
|
|
1782
|
+
return createMockImageTransformationResult(response);
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
return transformer;
|
|
1786
|
+
}
|
|
1787
|
+
function createMockHostedImagesBinding() {
|
|
1788
|
+
const unsupported = () => {
|
|
1789
|
+
throw new Error("Mock Images hosted API is not implemented. Pass a custom ImagesBinding through createMockEnv({ images }) if your test needs hosted image behavior.");
|
|
1790
|
+
};
|
|
1791
|
+
return {
|
|
1792
|
+
image(_imageId) {
|
|
1793
|
+
return {
|
|
1794
|
+
details: unsupported,
|
|
1795
|
+
bytes: unsupported,
|
|
1796
|
+
update: unsupported,
|
|
1797
|
+
delete: unsupported
|
|
1798
|
+
};
|
|
1799
|
+
},
|
|
1800
|
+
upload: unsupported,
|
|
1801
|
+
list: unsupported
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function createMockImagesBinding(options = {}) {
|
|
1805
|
+
const response = options.response ?? new Response("", {
|
|
1806
|
+
headers: { "Content-Type": "image/png" }
|
|
1807
|
+
});
|
|
1808
|
+
const info = options.info ?? {
|
|
1809
|
+
format: response.headers.get("Content-Type") ?? "image/png",
|
|
1810
|
+
fileSize: 0,
|
|
1811
|
+
width: 0,
|
|
1812
|
+
height: 0
|
|
1813
|
+
};
|
|
1814
|
+
return {
|
|
1815
|
+
async info(_stream, _options) {
|
|
1816
|
+
return info;
|
|
1817
|
+
},
|
|
1818
|
+
input(_stream, _options) {
|
|
1819
|
+
return createMockImageTransformer(response);
|
|
1820
|
+
},
|
|
1821
|
+
hosted: createMockHostedImagesBinding()
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
function createEmptyMediaStream() {
|
|
1825
|
+
return new ReadableStream({
|
|
1826
|
+
start(controller) {
|
|
1827
|
+
controller.close();
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
function createMockMediaTransformationResult(response) {
|
|
1832
|
+
return {
|
|
1833
|
+
async media() {
|
|
1834
|
+
const cloned = response.clone();
|
|
1835
|
+
return cloned.body ?? createEmptyMediaStream();
|
|
1836
|
+
},
|
|
1837
|
+
async response() {
|
|
1838
|
+
return response.clone();
|
|
1839
|
+
},
|
|
1840
|
+
async contentType() {
|
|
1841
|
+
return response.headers.get("Content-Type") ?? "video/mp4";
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
function createMockMediaTransformer(response) {
|
|
1846
|
+
const transformer = {
|
|
1847
|
+
transform(_transform) {
|
|
1848
|
+
return {
|
|
1849
|
+
output(_output) {
|
|
1850
|
+
return createMockMediaTransformationResult(response);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
},
|
|
1854
|
+
output(_output) {
|
|
1855
|
+
return createMockMediaTransformationResult(response);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
return transformer;
|
|
1859
|
+
}
|
|
1860
|
+
function createMockMediaBinding(options = {}) {
|
|
1861
|
+
const response = options.response ?? new Response("", {
|
|
1862
|
+
headers: { "Content-Type": "video/mp4" }
|
|
1863
|
+
});
|
|
1864
|
+
return {
|
|
1865
|
+
input(_media) {
|
|
1866
|
+
return createMockMediaTransformer(response);
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
// src/test/utilities/artifacts.ts
|
|
1871
|
+
function createArtifactTimestamp() {
|
|
1872
|
+
return new Date("2026-04-26T00:00:00.000Z").toISOString();
|
|
1873
|
+
}
|
|
1874
|
+
function createArtifactsRepoInfo(name, options = {}) {
|
|
1875
|
+
const now = createArtifactTimestamp();
|
|
1876
|
+
return {
|
|
1877
|
+
id: `repo-${name}`,
|
|
1878
|
+
name,
|
|
1879
|
+
description: options.description ?? null,
|
|
1880
|
+
defaultBranch: options.defaultBranch ?? "main",
|
|
1881
|
+
createdAt: now,
|
|
1882
|
+
updatedAt: now,
|
|
1883
|
+
lastPushAt: null,
|
|
1884
|
+
source: options.source ?? null,
|
|
1885
|
+
readOnly: options.readOnly ?? false,
|
|
1886
|
+
remote: `https://example.com/artifacts/default/${name}.git`
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
function isArtifactsBinding(value) {
|
|
1890
|
+
return typeof value.create === "function";
|
|
1891
|
+
}
|
|
1892
|
+
function createMockArtifacts(options = {}) {
|
|
1893
|
+
const repos = new Map;
|
|
1894
|
+
const tokens = new Map;
|
|
1895
|
+
const addRepo = (info) => {
|
|
1896
|
+
repos.set(info.name, info);
|
|
1897
|
+
if (!tokens.has(info.name)) {
|
|
1898
|
+
tokens.set(info.name, []);
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
for (const repo of options.repos ?? []) {
|
|
1902
|
+
addRepo({
|
|
1903
|
+
...createArtifactsRepoInfo(repo.name),
|
|
1904
|
+
...repo
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
const createToken = (repoName, scope = "write", ttl = 86400) => {
|
|
1908
|
+
const existing = tokens.get(repoName) ?? [];
|
|
1909
|
+
const id = `token-${repoName}-${existing.length + 1}`;
|
|
1910
|
+
const expiresAt = new Date(Date.parse(createArtifactTimestamp()) + ttl * 1000).toISOString();
|
|
1911
|
+
const token = {
|
|
1912
|
+
id,
|
|
1913
|
+
scope,
|
|
1914
|
+
state: "active",
|
|
1915
|
+
createdAt: createArtifactTimestamp(),
|
|
1916
|
+
expiresAt
|
|
1917
|
+
};
|
|
1918
|
+
tokens.set(repoName, [...existing, token]);
|
|
1919
|
+
return {
|
|
1920
|
+
id,
|
|
1921
|
+
plaintext: `${id}-plaintext`,
|
|
1922
|
+
scope,
|
|
1923
|
+
expiresAt
|
|
1924
|
+
};
|
|
1925
|
+
};
|
|
1926
|
+
const createRepoHandle = (info) => ({
|
|
1927
|
+
...info,
|
|
1928
|
+
async createToken(scope, ttl) {
|
|
1929
|
+
return createToken(info.name, scope, ttl);
|
|
1930
|
+
},
|
|
1931
|
+
async listTokens() {
|
|
1932
|
+
const repoTokens = tokens.get(info.name) ?? [];
|
|
1933
|
+
return {
|
|
1934
|
+
tokens: repoTokens,
|
|
1935
|
+
total: repoTokens.length
|
|
1936
|
+
};
|
|
1937
|
+
},
|
|
1938
|
+
async revokeToken(tokenOrId) {
|
|
1939
|
+
const repoTokens = tokens.get(info.name) ?? [];
|
|
1940
|
+
const index = repoTokens.findIndex((token) => token.id === tokenOrId);
|
|
1941
|
+
if (index === -1) {
|
|
1942
|
+
return false;
|
|
1943
|
+
}
|
|
1944
|
+
repoTokens[index] = {
|
|
1945
|
+
...repoTokens[index],
|
|
1946
|
+
state: "revoked"
|
|
1947
|
+
};
|
|
1948
|
+
tokens.set(info.name, repoTokens);
|
|
1949
|
+
return true;
|
|
1950
|
+
},
|
|
1951
|
+
async fork(name, forkOptions) {
|
|
1952
|
+
return createRepo(name, {
|
|
1953
|
+
description: forkOptions?.description ?? info.description ?? undefined,
|
|
1954
|
+
readOnly: forkOptions?.readOnly ?? info.readOnly,
|
|
1955
|
+
setDefaultBranch: info.defaultBranch,
|
|
1956
|
+
source: `artifacts:default/${info.name}`
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
});
|
|
1960
|
+
const createRepo = async (name, createOptions = {}) => {
|
|
1961
|
+
const info = createArtifactsRepoInfo(name, {
|
|
1962
|
+
description: createOptions.description,
|
|
1963
|
+
readOnly: createOptions.readOnly,
|
|
1964
|
+
defaultBranch: createOptions.setDefaultBranch,
|
|
1965
|
+
source: createOptions.source
|
|
1966
|
+
});
|
|
1967
|
+
addRepo(info);
|
|
1968
|
+
const token = createToken(name);
|
|
1969
|
+
return {
|
|
1970
|
+
id: info.id,
|
|
1971
|
+
name: info.name,
|
|
1972
|
+
description: info.description,
|
|
1973
|
+
defaultBranch: info.defaultBranch,
|
|
1974
|
+
remote: info.remote,
|
|
1975
|
+
token: token.plaintext,
|
|
1976
|
+
tokenExpiresAt: token.expiresAt
|
|
1977
|
+
};
|
|
1978
|
+
};
|
|
1979
|
+
return {
|
|
1980
|
+
create: createRepo,
|
|
1981
|
+
async get(name) {
|
|
1982
|
+
const repo = repos.get(name);
|
|
1983
|
+
return repo ? createRepoHandle(repo) : null;
|
|
1984
|
+
},
|
|
1985
|
+
async import(params) {
|
|
1986
|
+
return createRepo(params.target.name, {
|
|
1987
|
+
description: params.target.opts?.description,
|
|
1988
|
+
readOnly: params.target.opts?.readOnly,
|
|
1989
|
+
source: params.source.url
|
|
1990
|
+
});
|
|
1991
|
+
},
|
|
1992
|
+
async list(opts) {
|
|
1993
|
+
const limit = opts?.limit ?? 50;
|
|
1994
|
+
const repoList = Array.from(repos.values()).slice(0, limit).map((repo) => {
|
|
1995
|
+
const { remote: _remote, ...rest } = repo;
|
|
1996
|
+
return rest;
|
|
1997
|
+
});
|
|
1998
|
+
return {
|
|
1999
|
+
repos: repoList,
|
|
2000
|
+
total: repos.size,
|
|
2001
|
+
...repos.size > repoList.length && { cursor: String(repoList.length) }
|
|
2002
|
+
};
|
|
2003
|
+
},
|
|
2004
|
+
async delete(name) {
|
|
2005
|
+
tokens.delete(name);
|
|
2006
|
+
return repos.delete(name);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
// src/test/utilities/env.ts
|
|
2011
|
+
function createMockEnv(options = {}) {
|
|
2012
|
+
const env2 = {};
|
|
2013
|
+
if (options.kv) {
|
|
2014
|
+
for (const name of options.kv) {
|
|
2015
|
+
env2[name] = createMockKV();
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
if (options.d1) {
|
|
2019
|
+
for (const name of options.d1) {
|
|
2020
|
+
env2[name] = createMockD1();
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
if (options.r2) {
|
|
2024
|
+
for (const name of options.r2) {
|
|
2025
|
+
env2[name] = createMockR2();
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
if (options.queues) {
|
|
2029
|
+
for (const name of options.queues) {
|
|
2030
|
+
env2[name] = createMockQueue();
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
if (options.rateLimits) {
|
|
2034
|
+
for (const [name, rateLimitOptions] of Object.entries(options.rateLimits)) {
|
|
2035
|
+
env2[name] = createMockRateLimit(rateLimitOptions);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (options.versionMetadata) {
|
|
2039
|
+
env2[options.versionMetadata] = createMockVersionMetadata();
|
|
2040
|
+
}
|
|
2041
|
+
if (options.hyperdrive) {
|
|
2042
|
+
for (const [name, binding] of Object.entries(options.hyperdrive)) {
|
|
2043
|
+
env2[name] = typeof binding === "string" ? createMockHyperdrive(binding) : binding;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (Array.isArray(options.workerLoaders)) {
|
|
2047
|
+
for (const name of options.workerLoaders) {
|
|
2048
|
+
env2[name] = createMockWorkerLoader();
|
|
2049
|
+
}
|
|
2050
|
+
} else if (options.workerLoaders) {
|
|
2051
|
+
for (const [name, workerLoaderOptions] of Object.entries(options.workerLoaders)) {
|
|
2052
|
+
env2[name] = createMockWorkerLoader(workerLoaderOptions);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
if (Array.isArray(options.mtlsCertificates)) {
|
|
2056
|
+
for (const name of options.mtlsCertificates) {
|
|
2057
|
+
env2[name] = createMockMTLSCertificate();
|
|
2058
|
+
}
|
|
2059
|
+
} else if (options.mtlsCertificates) {
|
|
2060
|
+
for (const [name, handler] of Object.entries(options.mtlsCertificates)) {
|
|
2061
|
+
env2[name] = createMockMTLSCertificate(handler);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (Array.isArray(options.dispatchNamespaces)) {
|
|
2065
|
+
for (const name of options.dispatchNamespaces) {
|
|
2066
|
+
env2[name] = createMockDispatchNamespace();
|
|
2067
|
+
}
|
|
2068
|
+
} else if (options.dispatchNamespaces) {
|
|
2069
|
+
for (const [name, dispatchNamespaceOptions] of Object.entries(options.dispatchNamespaces)) {
|
|
2070
|
+
env2[name] = createMockDispatchNamespace(dispatchNamespaceOptions);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
if (Array.isArray(options.workflows)) {
|
|
2074
|
+
for (const name of options.workflows) {
|
|
2075
|
+
env2[name] = createMockWorkflow();
|
|
2076
|
+
}
|
|
2077
|
+
} else if (options.workflows) {
|
|
2078
|
+
for (const [name, workflowOptions] of Object.entries(options.workflows)) {
|
|
2079
|
+
env2[name] = "create" in workflowOptions ? workflowOptions : createMockWorkflow(workflowOptions);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
if (Array.isArray(options.pipelines)) {
|
|
2083
|
+
for (const name of options.pipelines) {
|
|
2084
|
+
env2[name] = createMockPipeline();
|
|
2085
|
+
}
|
|
2086
|
+
} else if (options.pipelines) {
|
|
2087
|
+
for (const [name, pipeline] of Object.entries(options.pipelines)) {
|
|
2088
|
+
env2[name] = pipeline;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (typeof options.images === "string") {
|
|
2092
|
+
env2[options.images] = createMockImagesBinding();
|
|
2093
|
+
} else if (options.images) {
|
|
2094
|
+
env2.IMAGES = options.images;
|
|
2095
|
+
}
|
|
2096
|
+
if (typeof options.media === "string") {
|
|
2097
|
+
env2[options.media] = createMockMediaBinding();
|
|
2098
|
+
} else if (options.media) {
|
|
2099
|
+
env2.MEDIA = options.media;
|
|
2100
|
+
}
|
|
2101
|
+
if (Array.isArray(options.artifacts)) {
|
|
2102
|
+
for (const name of options.artifacts) {
|
|
2103
|
+
env2[name] = createMockArtifacts();
|
|
2104
|
+
}
|
|
2105
|
+
} else if (options.artifacts) {
|
|
2106
|
+
for (const [name, artifactsOptions] of Object.entries(options.artifacts)) {
|
|
2107
|
+
env2[name] = isArtifactsBinding(artifactsOptions) ? artifactsOptions : createMockArtifacts(artifactsOptions);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
if (options.secretsStore) {
|
|
2111
|
+
for (const [name, value] of Object.entries(options.secretsStore)) {
|
|
2112
|
+
env2[name] = createMockSecretsStoreSecret(value);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
if (options.vars) {
|
|
2116
|
+
Object.assign(env2, options.vars);
|
|
2117
|
+
}
|
|
2118
|
+
if (options.secrets) {
|
|
2119
|
+
Object.assign(env2, options.secrets);
|
|
2120
|
+
}
|
|
2121
|
+
if (options.custom) {
|
|
2122
|
+
Object.assign(env2, options.custom);
|
|
2123
|
+
}
|
|
2124
|
+
return env2;
|
|
2125
|
+
}
|
|
2126
|
+
// src/test/simple-context-bindings.ts
|
|
2127
|
+
function buildRemoteAndStaticBindings(config) {
|
|
2128
|
+
const remoteBindings = {};
|
|
2129
|
+
if (isRemoteModeActive()) {
|
|
2130
|
+
if (config.bindings?.ai) {
|
|
2131
|
+
const aiBindingName = config.bindings.ai.binding || "AI";
|
|
2132
|
+
remoteBindings[aiBindingName] = createRemoteAI(config.accountId);
|
|
2133
|
+
}
|
|
2134
|
+
if (config.bindings?.vectorize) {
|
|
2135
|
+
for (const [name, vectorConfig] of Object.entries(config.bindings.vectorize)) {
|
|
2136
|
+
remoteBindings[name] = createRemoteVectorize(vectorConfig.indexName, config.accountId);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
if (config.vars) {
|
|
2141
|
+
for (const [key, value] of Object.entries(config.vars)) {
|
|
2142
|
+
remoteBindings[key] = value;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
if (config.bindings?.sendEmail) {
|
|
2146
|
+
for (const [name, binding] of Object.entries(config.bindings.sendEmail)) {
|
|
2147
|
+
remoteBindings[name] = createLocalSendEmailBinding(binding);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
if (config.bindings?.workerLoaders) {
|
|
2151
|
+
for (const name of Object.keys(config.bindings.workerLoaders)) {
|
|
2152
|
+
remoteBindings[name] = createLocalWorkerLoaderBinding();
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
if (config.bindings?.versionMetadata) {
|
|
2156
|
+
remoteBindings[config.bindings.versionMetadata.binding] = createMockVersionMetadata();
|
|
2157
|
+
}
|
|
2158
|
+
return remoteBindings;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
// src/test/email.ts
|
|
2162
|
+
import { join as join4 } from "path";
|
|
2163
|
+
var miniflarePort = 8787;
|
|
2164
|
+
var emailListeners = [];
|
|
2165
|
+
var sentEmails = [];
|
|
2166
|
+
var emailHandlerPath = null;
|
|
2167
|
+
var configDir = null;
|
|
2168
|
+
var testEnvGetter = null;
|
|
2169
|
+
function configureEmail(options = {}) {
|
|
2170
|
+
if (options.port) {
|
|
2171
|
+
miniflarePort = options.port;
|
|
2172
|
+
}
|
|
2173
|
+
emailHandlerPath = options.handlerPath ?? emailHandlerPath;
|
|
2174
|
+
configDir = options.configDir ?? configDir;
|
|
2175
|
+
testEnvGetter = options.getEnv ?? testEnvGetter;
|
|
2176
|
+
}
|
|
2177
|
+
function buildRawEmail(options) {
|
|
2178
|
+
if (options.raw) {
|
|
2179
|
+
return options.raw;
|
|
2180
|
+
}
|
|
2181
|
+
const lines = [];
|
|
2182
|
+
const messageId = `<${Date.now()}-${Math.random().toString(36).slice(2)}@devflare.dev>`;
|
|
2183
|
+
const date = new Date().toUTCString();
|
|
2184
|
+
lines.push(`From: ${options.from}`);
|
|
2185
|
+
lines.push(`To: ${options.to}`);
|
|
2186
|
+
lines.push(`Date: ${date}`);
|
|
2187
|
+
lines.push(`Message-ID: ${messageId}`);
|
|
2188
|
+
if (options.subject) {
|
|
2189
|
+
lines.push(`Subject: ${options.subject}`);
|
|
2190
|
+
}
|
|
2191
|
+
if (options.headers) {
|
|
2192
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
2193
|
+
lines.push(`${key}: ${value}`);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
lines.push("MIME-Version: 1.0");
|
|
2197
|
+
lines.push("Content-Type: text/plain; charset=UTF-8");
|
|
2198
|
+
lines.push("");
|
|
2199
|
+
lines.push(options.body ?? "");
|
|
2200
|
+
return lines.join(`\r
|
|
2201
|
+
`);
|
|
2202
|
+
}
|
|
2203
|
+
function createEmailHeaders(rawEmail) {
|
|
2204
|
+
const headers = new Headers;
|
|
2205
|
+
const lines = rawEmail.split(/\r?\n/);
|
|
2206
|
+
for (const line of lines) {
|
|
2207
|
+
if (!line.trim()) {
|
|
2208
|
+
break;
|
|
2209
|
+
}
|
|
2210
|
+
const colonIndex = line.indexOf(":");
|
|
2211
|
+
if (colonIndex <= 0) {
|
|
2212
|
+
continue;
|
|
2213
|
+
}
|
|
2214
|
+
headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim());
|
|
2215
|
+
}
|
|
2216
|
+
return headers;
|
|
2217
|
+
}
|
|
2218
|
+
function createRawEmailStream(rawEmail) {
|
|
2219
|
+
return new ReadableStream({
|
|
2220
|
+
start(controller) {
|
|
2221
|
+
controller.enqueue(new TextEncoder().encode(rawEmail));
|
|
2222
|
+
controller.close();
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
function resolveEmailHandler(module) {
|
|
2227
|
+
if (typeof module.default === "function") {
|
|
2228
|
+
return module.default;
|
|
2229
|
+
}
|
|
2230
|
+
if (module.default && typeof module.default.email === "function") {
|
|
2231
|
+
return module.default.email.bind(module.default);
|
|
2232
|
+
}
|
|
2233
|
+
if (typeof module.email === "function") {
|
|
2234
|
+
return module.email;
|
|
2235
|
+
}
|
|
2236
|
+
return null;
|
|
2237
|
+
}
|
|
2238
|
+
function getRecordedRawContent(raw) {
|
|
2239
|
+
if (typeof raw === "string") {
|
|
2240
|
+
return raw;
|
|
2241
|
+
}
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
async function send(options) {
|
|
2245
|
+
const raw = buildRawEmail(options);
|
|
2246
|
+
if (emailHandlerPath && configDir && testEnvGetter) {
|
|
2247
|
+
const absolutePath = join4(configDir, emailHandlerPath);
|
|
2248
|
+
const handlerModule = await import(absolutePath);
|
|
2249
|
+
const emailHandler = resolveEmailHandler(handlerModule);
|
|
2250
|
+
if (!emailHandler) {
|
|
2251
|
+
throw new Error(`Email handler at "${emailHandlerPath}" must export a default function or named "email" export.
|
|
2252
|
+
NaN`);
|
|
2253
|
+
}
|
|
2254
|
+
const waitUntilPromises = [];
|
|
2255
|
+
const ctx = {
|
|
2256
|
+
waitUntil(promise) {
|
|
2257
|
+
waitUntilPromises.push(promise);
|
|
2258
|
+
},
|
|
2259
|
+
passThroughOnException() {},
|
|
2260
|
+
props: {}
|
|
2261
|
+
};
|
|
2262
|
+
const runtimeEnv = testEnvGetter();
|
|
2263
|
+
const timestamp = new Date;
|
|
2264
|
+
const message = {
|
|
2265
|
+
from: options.from,
|
|
2266
|
+
to: options.to,
|
|
2267
|
+
headers: createEmailHeaders(raw),
|
|
2268
|
+
raw: createRawEmailStream(raw),
|
|
2269
|
+
rawSize: raw.length,
|
|
2270
|
+
setReject(reason) {
|
|
2271
|
+
throw new Error(`Email rejected: ${reason}`);
|
|
2272
|
+
},
|
|
2273
|
+
async forward(rcptTo) {
|
|
2274
|
+
recordSentEmail({
|
|
2275
|
+
type: "forward",
|
|
2276
|
+
from: options.from,
|
|
2277
|
+
to: rcptTo,
|
|
2278
|
+
raw,
|
|
2279
|
+
timestamp
|
|
2280
|
+
});
|
|
2281
|
+
},
|
|
2282
|
+
async reply(message2) {
|
|
2283
|
+
recordSentEmail({
|
|
2284
|
+
type: "reply",
|
|
2285
|
+
from: message2.from ?? options.to,
|
|
2286
|
+
to: message2.to ?? options.from,
|
|
2287
|
+
raw: getRecordedRawContent(message2.raw),
|
|
2288
|
+
timestamp
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
const emailEvent = createEmailEvent(message, runtimeEnv, ctx);
|
|
2293
|
+
await runWithEventContext(emailEvent, () => emailHandler(emailEvent));
|
|
2294
|
+
await Promise.all(waitUntilPromises);
|
|
2295
|
+
return new Response(JSON.stringify({ ok: true, from: options.from, to: options.to }), {
|
|
2296
|
+
headers: { "Content-Type": "application/json" }
|
|
1267
2297
|
});
|
|
1268
2298
|
}
|
|
1269
2299
|
const url = new URL(`http://localhost:${miniflarePort}/cdn-cgi/handler/email`);
|
|
@@ -1330,6 +2360,12 @@ function resetQueueState() {
|
|
|
1330
2360
|
configDir2 = null;
|
|
1331
2361
|
testEnvGetter2 = null;
|
|
1332
2362
|
}
|
|
2363
|
+
var EMPTY_QUEUE_METADATA = {
|
|
2364
|
+
metrics: {
|
|
2365
|
+
backlogCount: 0,
|
|
2366
|
+
backlogBytes: 0
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
1333
2369
|
function createMessage(options) {
|
|
1334
2370
|
const id = options.id ?? crypto.randomUUID();
|
|
1335
2371
|
const timestamp = options.timestamp ?? new Date;
|
|
@@ -1357,6 +2393,7 @@ function createMessage(options) {
|
|
|
1357
2393
|
function createMessageBatch(messages) {
|
|
1358
2394
|
return {
|
|
1359
2395
|
queue: "test-queue",
|
|
2396
|
+
metadata: EMPTY_QUEUE_METADATA,
|
|
1360
2397
|
messages,
|
|
1361
2398
|
ackAll() {
|
|
1362
2399
|
for (const msg of messages) {
|
|
@@ -1400,8 +2437,8 @@ NaN`);
|
|
|
1400
2437
|
passThroughOnException() {},
|
|
1401
2438
|
props: {}
|
|
1402
2439
|
};
|
|
1403
|
-
const
|
|
1404
|
-
const queueEvent = createQueueEvent(batch,
|
|
2440
|
+
const env3 = testEnvGetter2();
|
|
2441
|
+
const queueEvent = createQueueEvent(batch, env3, ctx);
|
|
1405
2442
|
await runWithEventContext(queueEvent, () => queueHandler(queueEvent));
|
|
1406
2443
|
await Promise.all(waitUntilPromises);
|
|
1407
2444
|
const acked = [];
|
|
@@ -1430,7 +2467,7 @@ NaN`);
|
|
|
1430
2467
|
async function send2(message) {
|
|
1431
2468
|
return trigger([message]);
|
|
1432
2469
|
}
|
|
1433
|
-
var
|
|
2470
|
+
var queue2 = {
|
|
1434
2471
|
trigger,
|
|
1435
2472
|
send: send2
|
|
1436
2473
|
};
|
|
@@ -1480,8 +2517,8 @@ NaN`);
|
|
|
1480
2517
|
passThroughOnException() {},
|
|
1481
2518
|
props: {}
|
|
1482
2519
|
};
|
|
1483
|
-
const
|
|
1484
|
-
const scheduledEvent = createScheduledEvent(controller,
|
|
2520
|
+
const env3 = testEnvGetter3();
|
|
2521
|
+
const scheduledEvent = createScheduledEvent(controller, env3, ctx);
|
|
1485
2522
|
try {
|
|
1486
2523
|
await runWithEventContext(scheduledEvent, () => scheduledHandler(scheduledEvent));
|
|
1487
2524
|
await Promise.all(waitUntilPromises);
|
|
@@ -1552,7 +2589,8 @@ async function trigger3(items) {
|
|
|
1552
2589
|
});
|
|
1553
2590
|
const absolutePath = join7(configDir4, tailHandlerPath);
|
|
1554
2591
|
const handlerModule = await import(absolutePath);
|
|
1555
|
-
const
|
|
2592
|
+
const defaultExport = handlerModule.default;
|
|
2593
|
+
const tailHandler = typeof defaultExport === "function" ? defaultExport : defaultExport && typeof defaultExport.tail === "function" ? defaultExport.tail.bind(defaultExport) : handlerModule.tail;
|
|
1556
2594
|
if (typeof tailHandler !== "function") {
|
|
1557
2595
|
throw new Error(`Tail handler at "${tailHandlerPath}" must export a default function or named "tail" export.
|
|
1558
2596
|
NaN`);
|
|
@@ -1565,10 +2603,10 @@ NaN`);
|
|
|
1565
2603
|
passThroughOnException() {},
|
|
1566
2604
|
props: {}
|
|
1567
2605
|
};
|
|
1568
|
-
const
|
|
1569
|
-
const tailEvent = createTailEvent(traceItems,
|
|
2606
|
+
const env3 = testEnvGetter4();
|
|
2607
|
+
const tailEvent = createTailEvent(traceItems, env3, ctx);
|
|
1570
2608
|
try {
|
|
1571
|
-
await runWithEventContext(tailEvent, () => tailHandler(tailEvent));
|
|
2609
|
+
await runWithEventContext(tailEvent, () => tailHandler.length >= 2 ? tailHandler(traceItems, env3, ctx) : tailHandler(tailEvent, env3, ctx));
|
|
1572
2610
|
await Promise.all(waitUntilPromises);
|
|
1573
2611
|
return {
|
|
1574
2612
|
success: true,
|
|
@@ -1666,9 +2704,9 @@ async function fetch2(request, options) {
|
|
|
1666
2704
|
passThroughOnException() {},
|
|
1667
2705
|
props: {}
|
|
1668
2706
|
};
|
|
1669
|
-
const
|
|
2707
|
+
const env3 = getEnv();
|
|
1670
2708
|
const initialRouteMatch = routeModules.length > 0 ? matchFetchRoute(routeModules, req) : null;
|
|
1671
|
-
const fetchEvent = createFetchEvent(req,
|
|
2709
|
+
const fetchEvent = createFetchEvent(req, env3, ctx, {
|
|
1672
2710
|
params: initialRouteMatch?.params ?? {}
|
|
1673
2711
|
});
|
|
1674
2712
|
const response = await runWithEventContext(fetchEvent, () => invokeFetchModule(handlerModule, fetchEvent, routeModules.length > 0 ? createRouteResolve(routeModules, fetchEvent) : undefined));
|
|
@@ -1798,23 +2836,464 @@ async function resolveHandlerPath(configDir6, configValue, defaultPath) {
|
|
|
1798
2836
|
}
|
|
1799
2837
|
}
|
|
1800
2838
|
async function resolveHandlerPaths(configDir6, config) {
|
|
1801
|
-
const [fetch3,
|
|
2839
|
+
const [fetch3, queue3, scheduled2, email2, tail2, routes] = await Promise.all([
|
|
1802
2840
|
resolveHandlerPath(configDir6, config.files?.fetch, DEFAULT_FETCH_PATH),
|
|
1803
2841
|
resolveHandlerPath(configDir6, config.files?.queue, DEFAULT_QUEUE_PATH),
|
|
1804
2842
|
resolveHandlerPath(configDir6, config.files?.scheduled, DEFAULT_SCHEDULED_PATH),
|
|
1805
2843
|
resolveHandlerPath(configDir6, config.files?.email, DEFAULT_EMAIL_PATH),
|
|
1806
|
-
resolveHandlerPath(configDir6,
|
|
2844
|
+
resolveHandlerPath(configDir6, config.files?.tail, DEFAULT_TAIL_PATH),
|
|
1807
2845
|
discoverRoutes(configDir6, config)
|
|
1808
2846
|
]);
|
|
1809
|
-
return { fetch: fetch3, queue:
|
|
2847
|
+
return { fetch: fetch3, queue: queue3, scheduled: scheduled2, email: email2, tail: tail2, routes };
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// src/test/simple-context-lifecycle.ts
|
|
2851
|
+
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
2852
|
+
|
|
2853
|
+
// src/test/containers.ts
|
|
2854
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2855
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2856
|
+
import { stat } from "node:fs/promises";
|
|
2857
|
+
import { createConnection } from "node:net";
|
|
2858
|
+
import { basename, dirname as dirname4, isAbsolute, join as join10, resolve as resolve2 } from "node:path";
|
|
2859
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2860
|
+
import { execa } from "execa";
|
|
2861
|
+
var realContainerCommandRunner = {
|
|
2862
|
+
async exec(command, args, options = {}) {
|
|
2863
|
+
try {
|
|
2864
|
+
const result = await execa(command, args, {
|
|
2865
|
+
cwd: options.cwd,
|
|
2866
|
+
env: options.env,
|
|
2867
|
+
reject: false,
|
|
2868
|
+
timeout: options.timeoutMs ?? 15000
|
|
2869
|
+
});
|
|
2870
|
+
return {
|
|
2871
|
+
exitCode: result.exitCode ?? 0,
|
|
2872
|
+
stdout: String(result.stdout ?? ""),
|
|
2873
|
+
stderr: String(result.stderr ?? "")
|
|
2874
|
+
};
|
|
2875
|
+
} catch (error) {
|
|
2876
|
+
const err = error;
|
|
2877
|
+
return {
|
|
2878
|
+
exitCode: err.exitCode ?? 1,
|
|
2879
|
+
stdout: String(err.stdout ?? ""),
|
|
2880
|
+
stderr: String(err.stderr ?? err.message ?? "Container command failed")
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
async function detectContainerEngine(options = {}) {
|
|
2886
|
+
const runner = options.runner ?? realContainerCommandRunner;
|
|
2887
|
+
const candidates = options.engine && options.engine !== "auto" ? [options.engine] : ["docker", "podman"];
|
|
2888
|
+
const checked = [];
|
|
2889
|
+
for (const engine of candidates) {
|
|
2890
|
+
let result;
|
|
2891
|
+
try {
|
|
2892
|
+
result = await runner.exec(engine, ["info"], { timeoutMs: 1e4 });
|
|
2893
|
+
} catch (error) {
|
|
2894
|
+
result = {
|
|
2895
|
+
exitCode: 1,
|
|
2896
|
+
stdout: "",
|
|
2897
|
+
stderr: error instanceof Error ? error.message : String(error)
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
if (result.exitCode === 0) {
|
|
2901
|
+
checked.push({ engine, available: true });
|
|
2902
|
+
return { available: true, engine, checked };
|
|
2903
|
+
}
|
|
2904
|
+
checked.push({
|
|
2905
|
+
engine,
|
|
2906
|
+
available: false,
|
|
2907
|
+
reason: formatCommandFailure(result)
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
return {
|
|
2911
|
+
available: false,
|
|
2912
|
+
reason: checked.map((check) => `${check.engine}: ${check.reason ?? "not available"}`).join("; "),
|
|
2913
|
+
checked
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
async function getContainerSkipReason(options = {}) {
|
|
2917
|
+
const env3 = options.env ?? process.env;
|
|
2918
|
+
if (!isTruthyEnvFlag(env3.DEVFLARE_CONTAINER_TESTS)) {
|
|
2919
|
+
return "Container tests require DEVFLARE_CONTAINER_TESTS=1 because they launch local Docker/Podman containers.";
|
|
2920
|
+
}
|
|
2921
|
+
const status = await detectContainerEngine(options);
|
|
2922
|
+
if (!status.available) {
|
|
2923
|
+
return `No reachable Docker or Podman engine found: ${status.reason}`;
|
|
2924
|
+
}
|
|
2925
|
+
return null;
|
|
2926
|
+
}
|
|
2927
|
+
function createContainerManager(options = {}) {
|
|
2928
|
+
const active = new Set;
|
|
2929
|
+
const getRunner = () => options.runner ?? realContainerCommandRunner;
|
|
2930
|
+
const manager = {
|
|
2931
|
+
detectEngine(engineOptions = {}) {
|
|
2932
|
+
return detectContainerEngine({
|
|
2933
|
+
engine: engineOptions.engine ?? options.engine,
|
|
2934
|
+
runner: engineOptions.runner ?? getRunner()
|
|
2935
|
+
});
|
|
2936
|
+
},
|
|
2937
|
+
async start(className, startOptions) {
|
|
2938
|
+
const cwd = options.cwd ?? getCallerDirectory();
|
|
2939
|
+
const resolved = await resolveContainerStart(className, startOptions, cwd);
|
|
2940
|
+
const status = await manager.detectEngine();
|
|
2941
|
+
const engine = getAvailableContainerEngine(status, className);
|
|
2942
|
+
const runner = getRunner();
|
|
2943
|
+
const offline = startOptions.offline ?? true;
|
|
2944
|
+
const prepared = await prepareImage({
|
|
2945
|
+
engine,
|
|
2946
|
+
runner,
|
|
2947
|
+
image: resolved.image,
|
|
2948
|
+
className,
|
|
2949
|
+
configDir: resolved.configDir,
|
|
2950
|
+
imageBuildContext: resolved.config?.imageBuildContext,
|
|
2951
|
+
offline
|
|
2952
|
+
});
|
|
2953
|
+
const host = startOptions.host ?? "127.0.0.1";
|
|
2954
|
+
const hostPort = startOptions.hostPort ?? await (options.allocatePort ?? getAvailablePort)();
|
|
2955
|
+
const containerName = makeContainerName(className, startOptions.instance);
|
|
2956
|
+
const runArgs = buildRunArgs({
|
|
2957
|
+
name: containerName,
|
|
2958
|
+
className,
|
|
2959
|
+
image: prepared.image,
|
|
2960
|
+
host,
|
|
2961
|
+
hostPort,
|
|
2962
|
+
containerPort: startOptions.port,
|
|
2963
|
+
envVars: startOptions.envVars,
|
|
2964
|
+
entrypoint: startOptions.entrypoint,
|
|
2965
|
+
command: startOptions.command
|
|
2966
|
+
});
|
|
2967
|
+
const runResult = await runner.exec(engine, runArgs, {
|
|
2968
|
+
cwd: resolved.configDir,
|
|
2969
|
+
env: options.env ?? process.env
|
|
2970
|
+
});
|
|
2971
|
+
if (runResult.exitCode !== 0) {
|
|
2972
|
+
throw new Error(`Failed to start Devflare container "${className}": ${formatCommandFailure(runResult)}`);
|
|
2973
|
+
}
|
|
2974
|
+
const instance = new LocalDevflareContainer({
|
|
2975
|
+
id: runResult.stdout.trim() || containerName,
|
|
2976
|
+
name: containerName,
|
|
2977
|
+
className,
|
|
2978
|
+
engine,
|
|
2979
|
+
host,
|
|
2980
|
+
hostPort,
|
|
2981
|
+
port: startOptions.port,
|
|
2982
|
+
runner,
|
|
2983
|
+
fetchImpl: options.fetch ?? fetch,
|
|
2984
|
+
onDispose: (container) => active.delete(container)
|
|
2985
|
+
});
|
|
2986
|
+
active.add(instance);
|
|
2987
|
+
await waitForContainerReadiness(instance, startOptions, options.waitForPort ?? waitForTcpPort);
|
|
2988
|
+
return instance;
|
|
2989
|
+
},
|
|
2990
|
+
async stopAll() {
|
|
2991
|
+
await Promise.all([...active].map((container) => container.stop()));
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
return manager;
|
|
2995
|
+
}
|
|
2996
|
+
var defaultContainerManager = createContainerManager();
|
|
2997
|
+
var containers = defaultContainerManager;
|
|
2998
|
+
async function stopActiveContainers() {
|
|
2999
|
+
await defaultContainerManager.stopAll();
|
|
3000
|
+
}
|
|
3001
|
+
function getAvailableContainerEngine(status, className) {
|
|
3002
|
+
if (!status.available) {
|
|
3003
|
+
throw new Error(`Cannot start Devflare container "${className}": ${status.reason}`);
|
|
3004
|
+
}
|
|
3005
|
+
return status.engine;
|
|
3006
|
+
}
|
|
3007
|
+
async function waitForContainerReadiness(instance, options, waitForPort) {
|
|
3008
|
+
if (!(options.waitForReady ?? true)) {
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
try {
|
|
3012
|
+
await waitForPort(instance.host, instance.hostPort, options.readyTimeoutMs ?? 20000);
|
|
3013
|
+
} catch (error) {
|
|
3014
|
+
await instance.destroy();
|
|
3015
|
+
throw error;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
async function resolveContainerStart(className, options, cwd) {
|
|
3019
|
+
if (options.image) {
|
|
3020
|
+
return {
|
|
3021
|
+
configDir: cwd,
|
|
3022
|
+
image: options.image
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
const { config, configDir: configDir6 } = await loadContainerConfig(options.configPath, cwd);
|
|
3026
|
+
const container = config.containers?.find((candidate) => candidate.className === className || candidate.name === className);
|
|
3027
|
+
if (!container) {
|
|
3028
|
+
throw new Error(`Container "${className}" was not found in devflare config.`);
|
|
3029
|
+
}
|
|
3030
|
+
return {
|
|
3031
|
+
configDir: configDir6,
|
|
3032
|
+
image: container.image,
|
|
3033
|
+
config: container
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
async function loadContainerConfig(configPath, cwd) {
|
|
3037
|
+
const absolutePath = configPath ? resolve2(cwd, configPath) : await findNearestConfig(cwd);
|
|
3038
|
+
if (!absolutePath) {
|
|
3039
|
+
throw new Error(`Could not find a devflare config file for container lookup. Searched upward from: ${cwd}`);
|
|
3040
|
+
}
|
|
3041
|
+
const configDir6 = dirname4(absolutePath);
|
|
3042
|
+
const loadedConfig = await loadConfig({
|
|
3043
|
+
cwd: configDir6,
|
|
3044
|
+
configFile: basename(absolutePath)
|
|
3045
|
+
});
|
|
3046
|
+
const config = await applyLocalDevVarsToConfig(loadedConfig, {
|
|
3047
|
+
cwd: configDir6,
|
|
3048
|
+
configPath: absolutePath
|
|
3049
|
+
});
|
|
3050
|
+
return { config, configDir: configDir6 };
|
|
3051
|
+
}
|
|
3052
|
+
async function prepareImage(options) {
|
|
3053
|
+
if (looksLikeLocalPath(options.image)) {
|
|
3054
|
+
return {
|
|
3055
|
+
image: await buildLocalImage(options)
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
const inspect = await options.runner.exec(options.engine, ["image", "inspect", options.image], {
|
|
3059
|
+
cwd: options.configDir
|
|
3060
|
+
});
|
|
3061
|
+
if (inspect.exitCode === 0) {
|
|
3062
|
+
return { image: options.image };
|
|
3063
|
+
}
|
|
3064
|
+
if (options.offline) {
|
|
3065
|
+
throw new Error(`Container image "${options.image}" is not present locally. Devflare container tests are offline-first; pull/build the image ahead of time or pass offline: false.`);
|
|
3066
|
+
}
|
|
3067
|
+
const pull = await options.runner.exec(options.engine, ["pull", options.image], {
|
|
3068
|
+
cwd: options.configDir
|
|
3069
|
+
});
|
|
3070
|
+
if (pull.exitCode !== 0) {
|
|
3071
|
+
throw new Error(`Failed to pull container image "${options.image}": ${formatCommandFailure(pull)}`);
|
|
3072
|
+
}
|
|
3073
|
+
return { image: options.image };
|
|
3074
|
+
}
|
|
3075
|
+
async function buildLocalImage(options) {
|
|
3076
|
+
const imagePath = resolve2(options.configDir, options.image);
|
|
3077
|
+
const imageStat = await stat(imagePath);
|
|
3078
|
+
const dockerfile = imageStat.isDirectory() ? join10(imagePath, "Dockerfile") : imagePath;
|
|
3079
|
+
const context2 = resolve2(options.configDir, options.imageBuildContext ?? (imageStat.isDirectory() ? options.image : dirname4(options.image)));
|
|
3080
|
+
if (!existsSync3(dockerfile)) {
|
|
3081
|
+
throw new Error(`Container Dockerfile does not exist: ${dockerfile}`);
|
|
3082
|
+
}
|
|
3083
|
+
const tag = makeLocalImageTag(options.className, options.configDir, options.image);
|
|
3084
|
+
const args = [
|
|
3085
|
+
"build",
|
|
3086
|
+
...options.offline ? [getOfflineBuildPullArg(options.engine)] : [],
|
|
3087
|
+
"-t",
|
|
3088
|
+
tag,
|
|
3089
|
+
"-f",
|
|
3090
|
+
dockerfile,
|
|
3091
|
+
context2
|
|
3092
|
+
];
|
|
3093
|
+
const result = await options.runner.exec(options.engine, args, {
|
|
3094
|
+
cwd: options.configDir
|
|
3095
|
+
});
|
|
3096
|
+
if (result.exitCode !== 0) {
|
|
3097
|
+
throw new Error(`Failed to build local container image "${options.image}": ${formatCommandFailure(result)}`);
|
|
3098
|
+
}
|
|
3099
|
+
return tag;
|
|
3100
|
+
}
|
|
3101
|
+
function buildRunArgs(options) {
|
|
3102
|
+
const args = [
|
|
3103
|
+
"run",
|
|
3104
|
+
"-d",
|
|
3105
|
+
"--name",
|
|
3106
|
+
options.name,
|
|
3107
|
+
"--label",
|
|
3108
|
+
"devflare.managed=true",
|
|
3109
|
+
"--label",
|
|
3110
|
+
`devflare.container.class=${options.className}`,
|
|
3111
|
+
"-p",
|
|
3112
|
+
`${options.host}:${options.hostPort}:${options.containerPort}`
|
|
3113
|
+
];
|
|
3114
|
+
for (const [key, value] of Object.entries(options.envVars ?? {})) {
|
|
3115
|
+
args.push("-e", `${key}=${value}`);
|
|
3116
|
+
}
|
|
3117
|
+
const command = [...options.command ?? []];
|
|
3118
|
+
if (options.entrypoint && options.entrypoint.length > 0) {
|
|
3119
|
+
args.push("--entrypoint", options.entrypoint[0]);
|
|
3120
|
+
command.unshift(...options.entrypoint.slice(1));
|
|
3121
|
+
}
|
|
3122
|
+
args.push(options.image, ...command);
|
|
3123
|
+
return args;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
class LocalDevflareContainer {
|
|
3127
|
+
id;
|
|
3128
|
+
name;
|
|
3129
|
+
className;
|
|
3130
|
+
engine;
|
|
3131
|
+
host;
|
|
3132
|
+
hostPort;
|
|
3133
|
+
port;
|
|
3134
|
+
runner;
|
|
3135
|
+
fetchImpl;
|
|
3136
|
+
onDispose;
|
|
3137
|
+
disposed = false;
|
|
3138
|
+
constructor(options) {
|
|
3139
|
+
this.id = options.id;
|
|
3140
|
+
this.name = options.name;
|
|
3141
|
+
this.className = options.className;
|
|
3142
|
+
this.engine = options.engine;
|
|
3143
|
+
this.host = options.host;
|
|
3144
|
+
this.hostPort = options.hostPort;
|
|
3145
|
+
this.port = options.port;
|
|
3146
|
+
this.runner = options.runner;
|
|
3147
|
+
this.fetchImpl = options.fetchImpl;
|
|
3148
|
+
this.onDispose = options.onDispose;
|
|
3149
|
+
}
|
|
3150
|
+
fetch(input, init) {
|
|
3151
|
+
return fetchWithStartupRetries(this.fetchImpl, this.toLocalRequest(input, init));
|
|
3152
|
+
}
|
|
3153
|
+
async logs() {
|
|
3154
|
+
const result = await this.runner.exec(this.engine, ["logs", this.name]);
|
|
3155
|
+
if (result.exitCode !== 0) {
|
|
3156
|
+
throw new Error(`Failed to read logs for Devflare container "${this.name}": ${formatCommandFailure(result)}`);
|
|
3157
|
+
}
|
|
3158
|
+
return result.stdout;
|
|
3159
|
+
}
|
|
3160
|
+
async getState() {
|
|
3161
|
+
const result = await this.runner.exec(this.engine, [
|
|
3162
|
+
"inspect",
|
|
3163
|
+
"--format",
|
|
3164
|
+
"{{json .State}}",
|
|
3165
|
+
this.name
|
|
3166
|
+
]);
|
|
3167
|
+
if (result.exitCode !== 0) {
|
|
3168
|
+
throw new Error(`Failed to inspect Devflare container "${this.name}": ${formatCommandFailure(result)}`);
|
|
3169
|
+
}
|
|
3170
|
+
const parsed = JSON.parse(result.stdout || "{}");
|
|
3171
|
+
return {
|
|
3172
|
+
status: parsed.Status ?? (parsed.Running ? "running" : "stopped"),
|
|
3173
|
+
running: Boolean(parsed.Running),
|
|
3174
|
+
...typeof parsed.ExitCode === "number" && { exitCode: parsed.ExitCode }
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
async stop() {
|
|
3178
|
+
if (this.disposed)
|
|
3179
|
+
return;
|
|
3180
|
+
try {
|
|
3181
|
+
await this.runner.exec(this.engine, ["stop", this.name]);
|
|
3182
|
+
} finally {
|
|
3183
|
+
await this.runner.exec(this.engine, ["rm", "-f", this.name]);
|
|
3184
|
+
this.disposed = true;
|
|
3185
|
+
this.onDispose(this);
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
async destroy() {
|
|
3189
|
+
if (this.disposed)
|
|
3190
|
+
return;
|
|
3191
|
+
await this.runner.exec(this.engine, ["rm", "-f", this.name]);
|
|
3192
|
+
this.disposed = true;
|
|
3193
|
+
this.onDispose(this);
|
|
3194
|
+
}
|
|
3195
|
+
toLocalRequest(input, init) {
|
|
3196
|
+
const base = `http://${this.host}:${this.hostPort}/`;
|
|
3197
|
+
if (input instanceof Request) {
|
|
3198
|
+
const source = init ? new Request(input, init) : input;
|
|
3199
|
+
const sourceUrl2 = new URL(source.url);
|
|
3200
|
+
const localUrl2 = new URL(`${sourceUrl2.pathname}${sourceUrl2.search}`, base);
|
|
3201
|
+
return new Request(localUrl2, {
|
|
3202
|
+
method: source.method,
|
|
3203
|
+
headers: source.headers,
|
|
3204
|
+
body: source.body,
|
|
3205
|
+
redirect: source.redirect,
|
|
3206
|
+
signal: source.signal
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
const sourceUrl = new URL(String(input), base);
|
|
3210
|
+
const localUrl = new URL(`${sourceUrl.pathname}${sourceUrl.search}`, base);
|
|
3211
|
+
return new Request(localUrl, init);
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
async function fetchWithStartupRetries(fetchImpl, request) {
|
|
3215
|
+
if (!canRetryRequest(request)) {
|
|
3216
|
+
return fetchImpl(request);
|
|
3217
|
+
}
|
|
3218
|
+
const deadline = Date.now() + 5000;
|
|
3219
|
+
let lastError;
|
|
3220
|
+
while (Date.now() < deadline) {
|
|
3221
|
+
try {
|
|
3222
|
+
return await fetchImpl(request.clone());
|
|
3223
|
+
} catch (error) {
|
|
3224
|
+
if (!isTransientContainerFetchError(error)) {
|
|
3225
|
+
throw error;
|
|
3226
|
+
}
|
|
3227
|
+
lastError = error;
|
|
3228
|
+
await delay(100);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
throw lastError;
|
|
3232
|
+
}
|
|
3233
|
+
function canRetryRequest(request) {
|
|
3234
|
+
return (request.method === "GET" || request.method === "HEAD") && request.body === null;
|
|
3235
|
+
}
|
|
3236
|
+
function isTransientContainerFetchError(error) {
|
|
3237
|
+
const value = error;
|
|
3238
|
+
const code = value.code ?? value.cause?.code;
|
|
3239
|
+
if (code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "UND_ERR_SOCKET") {
|
|
3240
|
+
return true;
|
|
3241
|
+
}
|
|
3242
|
+
return typeof value.message === "string" && (value.message.includes("socket connection was closed") || value.message.includes("fetch failed"));
|
|
3243
|
+
}
|
|
3244
|
+
async function waitForTcpPort(host, port, timeoutMs) {
|
|
3245
|
+
const start = Date.now();
|
|
3246
|
+
let lastError;
|
|
3247
|
+
while (Date.now() - start < timeoutMs) {
|
|
3248
|
+
try {
|
|
3249
|
+
await connectOnce(host, port);
|
|
3250
|
+
return;
|
|
3251
|
+
} catch (error) {
|
|
3252
|
+
lastError = error;
|
|
3253
|
+
await delay(100);
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
const message = lastError instanceof Error ? lastError.message : "timed out";
|
|
3257
|
+
throw new Error(`Timed out waiting for container port ${host}:${port}: ${message}`);
|
|
3258
|
+
}
|
|
3259
|
+
function connectOnce(host, port) {
|
|
3260
|
+
return new Promise((resolveConnection, rejectConnection) => {
|
|
3261
|
+
const socket = createConnection({ host, port });
|
|
3262
|
+
socket.once("connect", () => {
|
|
3263
|
+
socket.destroy();
|
|
3264
|
+
resolveConnection();
|
|
3265
|
+
});
|
|
3266
|
+
socket.once("error", rejectConnection);
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
function looksLikeLocalPath(image) {
|
|
3270
|
+
return image === "Dockerfile" || image.startsWith(".") || image.startsWith("/") || image.startsWith("\\") || isAbsolute(image) || image.endsWith("/Dockerfile") || image.endsWith("\\Dockerfile");
|
|
3271
|
+
}
|
|
3272
|
+
function getOfflineBuildPullArg(engine) {
|
|
3273
|
+
return engine === "podman" ? "--pull=never" : "--pull=false";
|
|
3274
|
+
}
|
|
3275
|
+
function makeLocalImageTag(className, configDir6, image) {
|
|
3276
|
+
const hash = createHash("sha256").update(`${configDir6}:${image}`).digest("hex").slice(0, 12);
|
|
3277
|
+
return `devflare-local-${sanitizeName(className)}:${hash}`;
|
|
3278
|
+
}
|
|
3279
|
+
function makeContainerName(className, instance) {
|
|
3280
|
+
return `devflare-${sanitizeName(className)}-${sanitizeName(instance ?? "default")}-${randomUUID().slice(0, 8)}`;
|
|
3281
|
+
}
|
|
3282
|
+
function sanitizeName(value) {
|
|
3283
|
+
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "container";
|
|
3284
|
+
}
|
|
3285
|
+
function formatCommandFailure(result) {
|
|
3286
|
+
return (result.stderr || result.stdout || `exit code ${result.exitCode}`).trim();
|
|
3287
|
+
}
|
|
3288
|
+
function isTruthyEnvFlag(value) {
|
|
3289
|
+
return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
|
|
1810
3290
|
}
|
|
1811
3291
|
|
|
1812
3292
|
// src/test/simple-context-lifecycle.ts
|
|
1813
|
-
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
1814
3293
|
async function resolveTestContextConfig(configPath, callerDir = getCallerDirectory()) {
|
|
1815
3294
|
let absolutePath;
|
|
1816
3295
|
if (configPath) {
|
|
1817
|
-
absolutePath =
|
|
3296
|
+
absolutePath = resolve3(callerDir, configPath);
|
|
1818
3297
|
} else {
|
|
1819
3298
|
const found = await findNearestConfig(callerDir);
|
|
1820
3299
|
if (!found) {
|
|
@@ -1824,11 +3303,15 @@ async function resolveTestContextConfig(configPath, callerDir = getCallerDirecto
|
|
|
1824
3303
|
}
|
|
1825
3304
|
absolutePath = found;
|
|
1826
3305
|
}
|
|
1827
|
-
const configDir6 =
|
|
1828
|
-
const
|
|
3306
|
+
const configDir6 = dirname5(absolutePath);
|
|
3307
|
+
const loadedConfig = await loadConfig({
|
|
1829
3308
|
cwd: configDir6,
|
|
1830
3309
|
configFile: absolutePath.split(/[/\\]/).pop()
|
|
1831
3310
|
});
|
|
3311
|
+
const config = await applyLocalDevVarsToConfig(loadedConfig, {
|
|
3312
|
+
cwd: configDir6,
|
|
3313
|
+
configPath: absolutePath
|
|
3314
|
+
});
|
|
1832
3315
|
return { absolutePath, configDir: configDir6, config };
|
|
1833
3316
|
}
|
|
1834
3317
|
function createDisposeContext(state) {
|
|
@@ -1841,6 +3324,8 @@ function createDisposeContext(state) {
|
|
|
1841
3324
|
await state.miniflare.dispose();
|
|
1842
3325
|
state.miniflare = null;
|
|
1843
3326
|
}
|
|
3327
|
+
await disposeLocalWorkerLoaderBindings();
|
|
3328
|
+
await stopActiveContainers();
|
|
1844
3329
|
state.envProxy = null;
|
|
1845
3330
|
state.transportDecode = null;
|
|
1846
3331
|
state.remoteBindings = null;
|
|
@@ -1867,10 +3352,10 @@ function isRetriableTestContextStartupError(error) {
|
|
|
1867
3352
|
return message.includes("websocket connection failed") || message.includes("connection timeout: ws://") || message.includes("econnrefused") || message.includes("eaddrinuse") || message.includes("address already in use");
|
|
1868
3353
|
}
|
|
1869
3354
|
async function waitForTestContextStartupRetry() {
|
|
1870
|
-
await new Promise((
|
|
3355
|
+
await new Promise((resolve4) => setTimeout(resolve4, TEST_CONTEXT_STARTUP_RETRY_DELAY_MS));
|
|
1871
3356
|
}
|
|
1872
3357
|
async function waitForBridgeClientRetry() {
|
|
1873
|
-
await new Promise((
|
|
3358
|
+
await new Promise((resolve4) => setTimeout(resolve4, TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS));
|
|
1874
3359
|
}
|
|
1875
3360
|
async function connectBridgeClientWithRetry(url) {
|
|
1876
3361
|
let lastError;
|
|
@@ -1890,6 +3375,48 @@ async function connectBridgeClientWithRetry(url) {
|
|
|
1890
3375
|
}
|
|
1891
3376
|
throw lastError instanceof Error ? lastError : new Error("Bridge-backed test context could not connect to the WebSocket gateway.");
|
|
1892
3377
|
}
|
|
3378
|
+
function expandLocalBindingWorkers(mfConfig) {
|
|
3379
|
+
const auxiliaryWorkers = [
|
|
3380
|
+
...mfConfig.__devflareLocalSecretWorkers ?? [],
|
|
3381
|
+
...mfConfig.__devflareLocalBindingWorkers ?? []
|
|
3382
|
+
];
|
|
3383
|
+
if (!Array.isArray(auxiliaryWorkers) || auxiliaryWorkers.length === 0) {
|
|
3384
|
+
return mfConfig;
|
|
3385
|
+
}
|
|
3386
|
+
const {
|
|
3387
|
+
__devflareLocalSecretWorkers,
|
|
3388
|
+
__devflareLocalBindingWorkers,
|
|
3389
|
+
port,
|
|
3390
|
+
host,
|
|
3391
|
+
log,
|
|
3392
|
+
kvPersist,
|
|
3393
|
+
r2Persist,
|
|
3394
|
+
d1Persist,
|
|
3395
|
+
durableObjectsPersist,
|
|
3396
|
+
workflowsPersist,
|
|
3397
|
+
imagesPersist,
|
|
3398
|
+
...primaryWorker
|
|
3399
|
+
} = mfConfig;
|
|
3400
|
+
const primaryWorkerName = typeof primaryWorker.name === "string" ? primaryWorker.name : "primary";
|
|
3401
|
+
return {
|
|
3402
|
+
...port !== undefined && { port },
|
|
3403
|
+
...host && { host },
|
|
3404
|
+
...log && { log },
|
|
3405
|
+
...kvPersist && { kvPersist },
|
|
3406
|
+
...r2Persist && { r2Persist },
|
|
3407
|
+
...d1Persist && { d1Persist },
|
|
3408
|
+
...durableObjectsPersist && { durableObjectsPersist },
|
|
3409
|
+
...workflowsPersist && { workflowsPersist },
|
|
3410
|
+
...imagesPersist && { imagesPersist },
|
|
3411
|
+
workers: [
|
|
3412
|
+
{
|
|
3413
|
+
...primaryWorker,
|
|
3414
|
+
name: primaryWorkerName
|
|
3415
|
+
},
|
|
3416
|
+
...auxiliaryWorkers
|
|
3417
|
+
]
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
1893
3420
|
async function startBridgeBackedTestContext(mfConfig) {
|
|
1894
3421
|
const { Miniflare } = await import("miniflare");
|
|
1895
3422
|
for (let attempt = 1;attempt <= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS; attempt++) {
|
|
@@ -1897,10 +3424,10 @@ async function startBridgeBackedTestContext(mfConfig) {
|
|
|
1897
3424
|
let miniflare = null;
|
|
1898
3425
|
let client = null;
|
|
1899
3426
|
try {
|
|
1900
|
-
miniflare = new Miniflare({
|
|
3427
|
+
miniflare = new Miniflare(expandLocalBindingWorkers({
|
|
1901
3428
|
...mfConfig,
|
|
1902
3429
|
port
|
|
1903
|
-
});
|
|
3430
|
+
}));
|
|
1904
3431
|
await miniflare.ready;
|
|
1905
3432
|
const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
1906
3433
|
client = await connectBridgeClientWithRetry(`ws://localhost:${port}`);
|
|
@@ -1949,9 +3476,9 @@ async function bootTestRuntime(mfConfig, usesMultiWorker) {
|
|
|
1949
3476
|
}
|
|
1950
3477
|
|
|
1951
3478
|
// src/test/simple-context-transport.ts
|
|
1952
|
-
import { join as
|
|
3479
|
+
import { join as join11 } from "path";
|
|
1953
3480
|
async function loadTransportDecoders(configDir6, transportFile) {
|
|
1954
|
-
const transportPath =
|
|
3481
|
+
const transportPath = join11(configDir6, transportFile);
|
|
1955
3482
|
const transportModule = await import(transportPath);
|
|
1956
3483
|
if (!transportModule.transport) {
|
|
1957
3484
|
console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.
|
|
@@ -2001,15 +3528,32 @@ function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBi
|
|
|
2001
3528
|
...mfConfig.kvNamespaces && { kvNamespaces: mfConfig.kvNamespaces },
|
|
2002
3529
|
...mfConfig.r2Buckets && { r2Buckets: mfConfig.r2Buckets },
|
|
2003
3530
|
...mfConfig.d1Databases && { d1Databases: mfConfig.d1Databases },
|
|
3531
|
+
...mfConfig.ratelimits && { ratelimits: mfConfig.ratelimits },
|
|
3532
|
+
...mfConfig.versionMetadata && { versionMetadata: mfConfig.versionMetadata },
|
|
3533
|
+
...mfConfig.workerLoaders && { workerLoaders: mfConfig.workerLoaders },
|
|
3534
|
+
...mfConfig.mtlsCertificates && { mtlsCertificates: mfConfig.mtlsCertificates },
|
|
3535
|
+
...mfConfig.dispatchNamespaces && { dispatchNamespaces: mfConfig.dispatchNamespaces },
|
|
3536
|
+
...mfConfig.workflows && { workflows: mfConfig.workflows },
|
|
3537
|
+
...mfConfig.pipelines && { pipelines: mfConfig.pipelines },
|
|
3538
|
+
...mfConfig.images && { images: mfConfig.images },
|
|
3539
|
+
...mfConfig.media && { media: mfConfig.media },
|
|
3540
|
+
...mfConfig.artifacts && { artifacts: mfConfig.artifacts },
|
|
3541
|
+
...mfConfig.secretsStoreSecrets && { secretsStoreSecrets: mfConfig.secretsStoreSecrets },
|
|
3542
|
+
...mfConfig.wrappedBindings && { wrappedBindings: mfConfig.wrappedBindings },
|
|
2004
3543
|
...mfConfig.email && { email: mfConfig.email },
|
|
2005
3544
|
...Object.keys(primaryDurableObjects).length > 0 && { durableObjects: primaryDurableObjects },
|
|
2006
|
-
...serviceBindingResolution?.primaryServiceBindings
|
|
2007
|
-
serviceBindings:
|
|
2008
|
-
|
|
3545
|
+
...mfConfig.serviceBindings || serviceBindingResolution?.primaryServiceBindings ? {
|
|
3546
|
+
serviceBindings: {
|
|
3547
|
+
...mfConfig.serviceBindings ?? {},
|
|
3548
|
+
...serviceBindingResolution?.primaryServiceBindings ?? {}
|
|
3549
|
+
}
|
|
3550
|
+
} : {}
|
|
2009
3551
|
};
|
|
2010
3552
|
const additionalWorkers = [
|
|
2011
3553
|
...serviceBindingResolution?.workers || [],
|
|
2012
|
-
...doBindingResolution?.workers || []
|
|
3554
|
+
...doBindingResolution?.workers || [],
|
|
3555
|
+
...mfConfig.__devflareLocalSecretWorkers || [],
|
|
3556
|
+
...mfConfig.__devflareLocalBindingWorkers || []
|
|
2013
3557
|
];
|
|
2014
3558
|
const workersByName = new Map;
|
|
2015
3559
|
for (const worker2 of additionalWorkers) {
|
|
@@ -2031,15 +3575,35 @@ function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBi
|
|
|
2031
3575
|
delete mfConfig.kvNamespaces;
|
|
2032
3576
|
delete mfConfig.r2Buckets;
|
|
2033
3577
|
delete mfConfig.d1Databases;
|
|
3578
|
+
delete mfConfig.ratelimits;
|
|
3579
|
+
delete mfConfig.versionMetadata;
|
|
3580
|
+
delete mfConfig.workerLoaders;
|
|
3581
|
+
delete mfConfig.mtlsCertificates;
|
|
3582
|
+
delete mfConfig.dispatchNamespaces;
|
|
3583
|
+
delete mfConfig.workflows;
|
|
3584
|
+
delete mfConfig.pipelines;
|
|
3585
|
+
delete mfConfig.images;
|
|
3586
|
+
delete mfConfig.media;
|
|
3587
|
+
delete mfConfig.artifacts;
|
|
3588
|
+
delete mfConfig.secretsStoreSecrets;
|
|
3589
|
+
delete mfConfig.wrappedBindings;
|
|
3590
|
+
delete mfConfig.serviceBindings;
|
|
3591
|
+
delete mfConfig.__devflareLocalSecretWorkers;
|
|
3592
|
+
delete mfConfig.__devflareLocalBindingWorkers;
|
|
2034
3593
|
delete mfConfig.durableObjects;
|
|
2035
3594
|
mfConfig.workers = workers;
|
|
2036
3595
|
}
|
|
2037
3596
|
|
|
2038
3597
|
// src/test/simple-context-mfconfig.ts
|
|
2039
|
-
function buildInlineBridgeMfConfig(config) {
|
|
3598
|
+
function buildInlineBridgeMfConfig(config, options = {}) {
|
|
2040
3599
|
const localWorkerBindings = config.vars ?? {};
|
|
3600
|
+
const localSecretWrappedBindingConfig = options.cwd ? buildLocalSecretWrappedBindingConfig(config, options.cwd) : undefined;
|
|
3601
|
+
const localBindingShimServiceConfig = buildLocalBindingShimServiceConfig(config);
|
|
3602
|
+
const localSecretBindingNames = new Set(localSecretWrappedBindingConfig?.localBindingNames ?? []);
|
|
2041
3603
|
const mfConfig = {
|
|
2042
|
-
modules: true
|
|
3604
|
+
modules: true,
|
|
3605
|
+
compatibilityDate: config.compatibilityDate ?? "2025-01-01",
|
|
3606
|
+
...config.compatibilityFlags && { compatibilityFlags: config.compatibilityFlags }
|
|
2043
3607
|
};
|
|
2044
3608
|
if (config.bindings?.kv) {
|
|
2045
3609
|
mfConfig.kvNamespaces = Object.keys(config.bindings.kv);
|
|
@@ -2052,6 +3616,10 @@ function buildInlineBridgeMfConfig(config) {
|
|
|
2052
3616
|
return [bindingName, getLocalD1DatabaseIdentifier(bindingConfig)];
|
|
2053
3617
|
}));
|
|
2054
3618
|
}
|
|
3619
|
+
const hyperdrivesConfig = buildHyperdrivesConfig(config.bindings ?? {});
|
|
3620
|
+
if (hyperdrivesConfig) {
|
|
3621
|
+
mfConfig.hyperdrives = hyperdrivesConfig;
|
|
3622
|
+
}
|
|
2055
3623
|
if (config.bindings?.queues?.producers) {
|
|
2056
3624
|
const queueProducers = {};
|
|
2057
3625
|
for (const [bindingName, queueName] of Object.entries(config.bindings.queues.producers)) {
|
|
@@ -2059,6 +3627,152 @@ function buildInlineBridgeMfConfig(config) {
|
|
|
2059
3627
|
}
|
|
2060
3628
|
mfConfig.queueProducers = queueProducers;
|
|
2061
3629
|
}
|
|
3630
|
+
if (config.bindings?.rateLimits) {
|
|
3631
|
+
mfConfig.ratelimits = Object.fromEntries(Object.entries(config.bindings.rateLimits).map(([bindingName, binding]) => [
|
|
3632
|
+
bindingName,
|
|
3633
|
+
{
|
|
3634
|
+
simple: {
|
|
3635
|
+
limit: binding.simple.limit,
|
|
3636
|
+
period: binding.simple.period
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
]));
|
|
3640
|
+
}
|
|
3641
|
+
if (config.bindings?.versionMetadata) {
|
|
3642
|
+
mfConfig.versionMetadata = config.bindings.versionMetadata.binding;
|
|
3643
|
+
}
|
|
3644
|
+
if (config.bindings?.workerLoaders) {
|
|
3645
|
+
mfConfig.workerLoaders = Object.fromEntries(Object.keys(config.bindings.workerLoaders).map((bindingName) => [bindingName, {}]));
|
|
3646
|
+
}
|
|
3647
|
+
if (config.bindings?.mtlsCertificates) {
|
|
3648
|
+
mfConfig.mtlsCertificates = Object.fromEntries(Object.entries(config.bindings.mtlsCertificates).map(([bindingName, binding]) => {
|
|
3649
|
+
const normalized = normalizeMtlsCertificateBinding(binding);
|
|
3650
|
+
return [
|
|
3651
|
+
bindingName,
|
|
3652
|
+
{
|
|
3653
|
+
certificate_id: normalized.certificateId
|
|
3654
|
+
}
|
|
3655
|
+
];
|
|
3656
|
+
}));
|
|
3657
|
+
}
|
|
3658
|
+
if (config.bindings?.dispatchNamespaces) {
|
|
3659
|
+
mfConfig.dispatchNamespaces = Object.fromEntries(Object.entries(config.bindings.dispatchNamespaces).map(([bindingName, binding]) => {
|
|
3660
|
+
const normalized = normalizeDispatchNamespaceBinding(binding);
|
|
3661
|
+
return [
|
|
3662
|
+
bindingName,
|
|
3663
|
+
{
|
|
3664
|
+
namespace: normalized.namespace
|
|
3665
|
+
}
|
|
3666
|
+
];
|
|
3667
|
+
}));
|
|
3668
|
+
}
|
|
3669
|
+
if (config.bindings?.workflows) {
|
|
3670
|
+
mfConfig.workflows = Object.fromEntries(Object.entries(config.bindings.workflows).map(([bindingName, binding]) => {
|
|
3671
|
+
const normalized = normalizeWorkflowBinding(binding);
|
|
3672
|
+
return [
|
|
3673
|
+
bindingName,
|
|
3674
|
+
{
|
|
3675
|
+
name: normalized.name,
|
|
3676
|
+
className: normalized.className,
|
|
3677
|
+
...normalized.scriptName && { scriptName: normalized.scriptName },
|
|
3678
|
+
...normalized.limits && { stepLimit: normalized.limits.steps }
|
|
3679
|
+
}
|
|
3680
|
+
];
|
|
3681
|
+
}));
|
|
3682
|
+
}
|
|
3683
|
+
if (config.bindings?.pipelines) {
|
|
3684
|
+
mfConfig.pipelines = Object.fromEntries(Object.entries(config.bindings.pipelines).map(([bindingName, binding]) => {
|
|
3685
|
+
const normalized = normalizePipelineBinding(binding);
|
|
3686
|
+
return [
|
|
3687
|
+
bindingName,
|
|
3688
|
+
typeof binding === "string" ? normalized.pipeline : { pipeline: normalized.pipeline }
|
|
3689
|
+
];
|
|
3690
|
+
}));
|
|
3691
|
+
}
|
|
3692
|
+
if (config.bindings?.images && localBindingShimServiceConfig.localBindingNames.length === 0) {
|
|
3693
|
+
const [entry] = Object.entries(config.bindings.images);
|
|
3694
|
+
if (entry) {
|
|
3695
|
+
const [bindingName, binding] = entry;
|
|
3696
|
+
const normalized = normalizeImagesBinding(bindingName, binding);
|
|
3697
|
+
mfConfig.images = {
|
|
3698
|
+
binding: normalized.binding
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
if (config.bindings?.media && localBindingShimServiceConfig.localBindingNames.length === 0) {
|
|
3703
|
+
const [entry] = Object.entries(config.bindings.media);
|
|
3704
|
+
if (entry) {
|
|
3705
|
+
const [bindingName, binding] = entry;
|
|
3706
|
+
const normalized = normalizeMediaBinding(bindingName, binding);
|
|
3707
|
+
mfConfig.media = {
|
|
3708
|
+
binding: normalized.binding
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
if (config.bindings?.artifacts) {
|
|
3713
|
+
mfConfig.artifacts = Object.fromEntries(Object.entries(config.bindings.artifacts).map(([bindingName, binding]) => {
|
|
3714
|
+
const normalized = normalizeArtifactsBinding(binding);
|
|
3715
|
+
return [
|
|
3716
|
+
bindingName,
|
|
3717
|
+
{
|
|
3718
|
+
namespace: normalized.namespace
|
|
3719
|
+
}
|
|
3720
|
+
];
|
|
3721
|
+
}));
|
|
3722
|
+
}
|
|
3723
|
+
if (config.bindings?.aiSearchNamespaces) {
|
|
3724
|
+
mfConfig.aiSearchNamespaces = Object.fromEntries(Object.entries(config.bindings.aiSearchNamespaces).map(([bindingName, binding]) => [
|
|
3725
|
+
bindingName,
|
|
3726
|
+
{
|
|
3727
|
+
namespace: binding.namespace
|
|
3728
|
+
}
|
|
3729
|
+
]));
|
|
3730
|
+
}
|
|
3731
|
+
if (config.bindings?.aiSearch) {
|
|
3732
|
+
mfConfig.aiSearchInstances = Object.fromEntries(Object.entries(config.bindings.aiSearch).map(([bindingName, binding]) => [
|
|
3733
|
+
bindingName,
|
|
3734
|
+
{
|
|
3735
|
+
instance_name: binding.instanceName
|
|
3736
|
+
}
|
|
3737
|
+
]));
|
|
3738
|
+
}
|
|
3739
|
+
if (config.bindings?.secretsStore) {
|
|
3740
|
+
const secretsStoreEntries = Object.entries(config.bindings.secretsStore).flatMap(([bindingName, binding]) => {
|
|
3741
|
+
if (localSecretBindingNames.has(bindingName)) {
|
|
3742
|
+
return [];
|
|
3743
|
+
}
|
|
3744
|
+
const normalized = normalizeSecretsStoreBinding(binding, config.secretsStoreId, bindingName);
|
|
3745
|
+
return [[
|
|
3746
|
+
bindingName,
|
|
3747
|
+
{
|
|
3748
|
+
store_id: normalized.storeId,
|
|
3749
|
+
secret_name: normalized.secretName
|
|
3750
|
+
}
|
|
3751
|
+
]];
|
|
3752
|
+
});
|
|
3753
|
+
if (secretsStoreEntries.length > 0) {
|
|
3754
|
+
mfConfig.secretsStoreSecrets = Object.fromEntries(secretsStoreEntries);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
const wrappedBindings = {
|
|
3758
|
+
...localSecretWrappedBindingConfig?.wrappedBindings ?? {}
|
|
3759
|
+
};
|
|
3760
|
+
const localBindingWorkers = [
|
|
3761
|
+
...localSecretWrappedBindingConfig?.workers ?? [],
|
|
3762
|
+
...localBindingShimServiceConfig.workers
|
|
3763
|
+
];
|
|
3764
|
+
if (Object.keys(wrappedBindings).length > 0) {
|
|
3765
|
+
mfConfig.wrappedBindings = wrappedBindings;
|
|
3766
|
+
}
|
|
3767
|
+
if (localBindingShimServiceConfig.localBindingNames.length > 0) {
|
|
3768
|
+
mfConfig.serviceBindings = {
|
|
3769
|
+
...mfConfig.serviceBindings ?? {},
|
|
3770
|
+
...localBindingShimServiceConfig.serviceBindings
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
if (localBindingWorkers.length > 0) {
|
|
3774
|
+
mfConfig.__devflareLocalBindingWorkers = localBindingWorkers;
|
|
3775
|
+
}
|
|
2062
3776
|
if (Object.keys(localWorkerBindings).length > 0) {
|
|
2063
3777
|
mfConfig.bindings = localWorkerBindings;
|
|
2064
3778
|
}
|
|
@@ -2112,7 +3826,7 @@ async function createTestContext(configPath) {
|
|
|
2112
3826
|
if (needsMultiWorkerForDOs) {
|
|
2113
3827
|
doBindingResolution = await resolveDOBindings(config, configDir6);
|
|
2114
3828
|
}
|
|
2115
|
-
const mfConfig = buildInlineBridgeMfConfig(config);
|
|
3829
|
+
const mfConfig = buildInlineBridgeMfConfig(config, { cwd: configDir6 });
|
|
2116
3830
|
const transportFile = resolveTransportFile(configDir6, config.files?.transport);
|
|
2117
3831
|
if (transportFile) {
|
|
2118
3832
|
state.transportDecode = await loadTransportDecoders(configDir6, transportFile);
|
|
@@ -2182,7 +3896,7 @@ async function createTestContext(configPath) {
|
|
|
2182
3896
|
// src/test/cf.ts
|
|
2183
3897
|
var cf = {
|
|
2184
3898
|
email,
|
|
2185
|
-
queue,
|
|
3899
|
+
queue: queue2,
|
|
2186
3900
|
scheduled,
|
|
2187
3901
|
worker,
|
|
2188
3902
|
tail
|
|
@@ -2190,9 +3904,16 @@ var cf = {
|
|
|
2190
3904
|
// src/test/should-skip.ts
|
|
2191
3905
|
var REMOTE_ONLY_SERVICES = new Set([
|
|
2192
3906
|
"ai",
|
|
3907
|
+
"ai_search",
|
|
3908
|
+
"ai_gateway",
|
|
3909
|
+
"media",
|
|
3910
|
+
"mtls_certificates",
|
|
3911
|
+
"artifacts",
|
|
3912
|
+
"builds",
|
|
2193
3913
|
"vectorize"
|
|
2194
3914
|
]);
|
|
2195
3915
|
var skipResults = new Map;
|
|
3916
|
+
var containerSkipResult = null;
|
|
2196
3917
|
var EXPECTED_ERROR_PATTERNS = [
|
|
2197
3918
|
"ECONNREFUSED",
|
|
2198
3919
|
"ETIMEDOUT",
|
|
@@ -2263,12 +3984,30 @@ function getSkipResult(service) {
|
|
|
2263
3984
|
result = computeSkip(service);
|
|
2264
3985
|
skipResults.set(service, result);
|
|
2265
3986
|
}
|
|
2266
|
-
return result;
|
|
3987
|
+
return result;
|
|
3988
|
+
}
|
|
3989
|
+
function getContainerSkipResult() {
|
|
3990
|
+
if (!containerSkipResult) {
|
|
3991
|
+
containerSkipResult = getContainerSkipReason().then((reason) => {
|
|
3992
|
+
if (reason) {
|
|
3993
|
+
console.log(`CONTAINERS tests skipped: ${reason}`);
|
|
3994
|
+
return true;
|
|
3995
|
+
}
|
|
3996
|
+
return false;
|
|
3997
|
+
});
|
|
3998
|
+
}
|
|
3999
|
+
return containerSkipResult;
|
|
2267
4000
|
}
|
|
2268
4001
|
var shouldSkip = {
|
|
2269
4002
|
get ai() {
|
|
2270
4003
|
return getSkipResult("ai");
|
|
2271
4004
|
},
|
|
4005
|
+
get aiSearch() {
|
|
4006
|
+
return getSkipResult("ai_search");
|
|
4007
|
+
},
|
|
4008
|
+
get aiGateway() {
|
|
4009
|
+
return getSkipResult("ai_gateway");
|
|
4010
|
+
},
|
|
2272
4011
|
get vectorize() {
|
|
2273
4012
|
return getSkipResult("vectorize");
|
|
2274
4013
|
},
|
|
@@ -2289,423 +4028,974 @@ var shouldSkip = {
|
|
|
2289
4028
|
},
|
|
2290
4029
|
get durableObjects() {
|
|
2291
4030
|
return getSkipResult("durable_objects");
|
|
4031
|
+
},
|
|
4032
|
+
get media() {
|
|
4033
|
+
return getSkipResult("media");
|
|
4034
|
+
},
|
|
4035
|
+
get mtlsCertificates() {
|
|
4036
|
+
return getSkipResult("mtls_certificates");
|
|
4037
|
+
},
|
|
4038
|
+
get artifacts() {
|
|
4039
|
+
return getSkipResult("artifacts");
|
|
4040
|
+
},
|
|
4041
|
+
get builds() {
|
|
4042
|
+
return getSkipResult("builds");
|
|
4043
|
+
},
|
|
4044
|
+
get containers() {
|
|
4045
|
+
return getContainerSkipResult();
|
|
2292
4046
|
}
|
|
2293
4047
|
};
|
|
2294
|
-
// src/test/
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
4048
|
+
// src/test/ai-search.ts
|
|
4049
|
+
var MOCK_TIMESTAMP = "2026-04-26T00:00:00.000Z";
|
|
4050
|
+
function cloneInfo(value) {
|
|
4051
|
+
return { ...value };
|
|
4052
|
+
}
|
|
4053
|
+
function extractSearchQuery(params) {
|
|
4054
|
+
if ("query" in params && typeof params.query === "string") {
|
|
4055
|
+
return params.query;
|
|
4056
|
+
}
|
|
4057
|
+
const messages = "messages" in params ? params.messages : [];
|
|
4058
|
+
return messages.filter((message) => message.role === "user" && message.content).map((message) => message.content).join(" ");
|
|
4059
|
+
}
|
|
4060
|
+
function tokenize(value) {
|
|
4061
|
+
return value.toLowerCase().split(/[^a-z0-9_]+/).filter((token) => token.length > 2);
|
|
4062
|
+
}
|
|
4063
|
+
function scoreText(query, text) {
|
|
4064
|
+
const tokens = tokenize(query);
|
|
4065
|
+
if (tokens.length === 0) {
|
|
4066
|
+
return 1;
|
|
4067
|
+
}
|
|
4068
|
+
const haystack = text.toLowerCase();
|
|
4069
|
+
const matches = tokens.filter((token) => haystack.includes(token)).length;
|
|
4070
|
+
return matches === 0 ? 0 : Math.max(0.1, matches / tokens.length);
|
|
4071
|
+
}
|
|
4072
|
+
function streamFromText(text) {
|
|
4073
|
+
const encoded = new TextEncoder().encode(text);
|
|
4074
|
+
return new ReadableStream({
|
|
4075
|
+
start(controller) {
|
|
4076
|
+
controller.enqueue(encoded);
|
|
4077
|
+
controller.close();
|
|
4078
|
+
}
|
|
4079
|
+
});
|
|
4080
|
+
}
|
|
4081
|
+
async function contentToText(content) {
|
|
4082
|
+
if (typeof content === "string") {
|
|
4083
|
+
return content;
|
|
4084
|
+
}
|
|
4085
|
+
if (content instanceof Blob) {
|
|
4086
|
+
return content.text();
|
|
4087
|
+
}
|
|
4088
|
+
return new Response(content).text();
|
|
4089
|
+
}
|
|
4090
|
+
function createItemInfo(id, key, content, options = {}) {
|
|
4091
|
+
return {
|
|
4092
|
+
id,
|
|
4093
|
+
key,
|
|
4094
|
+
status: options.status ?? "completed",
|
|
4095
|
+
next_action: null,
|
|
4096
|
+
namespace: options.namespace,
|
|
4097
|
+
chunks_count: content.length > 0 ? 1 : 0,
|
|
4098
|
+
file_size: new TextEncoder().encode(content).byteLength,
|
|
4099
|
+
source_id: options.sourceId ?? null,
|
|
4100
|
+
last_seen_at: MOCK_TIMESTAMP,
|
|
4101
|
+
created_at: MOCK_TIMESTAMP,
|
|
4102
|
+
metadata: options.metadata
|
|
2303
4103
|
};
|
|
4104
|
+
}
|
|
4105
|
+
function createItemChunks(item, chunks) {
|
|
4106
|
+
let offset = 0;
|
|
4107
|
+
return chunks.map((text, index) => {
|
|
4108
|
+
const start = offset;
|
|
4109
|
+
const end = start + new TextEncoder().encode(text).byteLength;
|
|
4110
|
+
offset = end;
|
|
4111
|
+
return {
|
|
4112
|
+
id: `${item.id}:chunk-${index + 1}`,
|
|
4113
|
+
text,
|
|
4114
|
+
start_byte: start,
|
|
4115
|
+
end_byte: end,
|
|
4116
|
+
item: {
|
|
4117
|
+
timestamp: Date.parse(MOCK_TIMESTAMP),
|
|
4118
|
+
key: item.key,
|
|
4119
|
+
metadata: item.metadata
|
|
4120
|
+
}
|
|
4121
|
+
};
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
function createStoredItem(sequence, fixture, namespace) {
|
|
4125
|
+
const content = fixture.content ?? fixture.chunks?.join(`
|
|
4126
|
+
`) ?? "";
|
|
4127
|
+
const id = fixture.id ?? `item-${sequence}`;
|
|
4128
|
+
const info = createItemInfo(id, fixture.key, content, {
|
|
4129
|
+
metadata: fixture.metadata,
|
|
4130
|
+
status: fixture.status,
|
|
4131
|
+
namespace
|
|
4132
|
+
});
|
|
4133
|
+
const chunks = createItemChunks(info, fixture.chunks ?? [content]);
|
|
2304
4134
|
return {
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
4135
|
+
info,
|
|
4136
|
+
content,
|
|
4137
|
+
contentType: fixture.contentType ?? "text/plain",
|
|
4138
|
+
chunks,
|
|
4139
|
+
logs: [
|
|
4140
|
+
{
|
|
4141
|
+
timestamp: MOCK_TIMESTAMP,
|
|
4142
|
+
action: "index",
|
|
4143
|
+
message: `Indexed ${fixture.key}`,
|
|
4144
|
+
fileKey: fixture.key,
|
|
4145
|
+
chunkCount: chunks.length,
|
|
4146
|
+
processingTimeMs: 0
|
|
4147
|
+
}
|
|
4148
|
+
]
|
|
2309
4149
|
};
|
|
2310
4150
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
return runWithContext(testCtx.env, testCtx.ctx, options.request ?? null, handler, options.type ?? "fetch");
|
|
4151
|
+
function isAISearchInstance(value) {
|
|
4152
|
+
return typeof value.search === "function";
|
|
2314
4153
|
}
|
|
2315
|
-
function
|
|
2316
|
-
const
|
|
2317
|
-
const
|
|
2318
|
-
const
|
|
2319
|
-
const
|
|
2320
|
-
|
|
2321
|
-
|
|
4154
|
+
function createMockAISearchInstance(options = {}) {
|
|
4155
|
+
const instanceId = options.id ?? options.info?.id ?? "mock-ai-search";
|
|
4156
|
+
const namespace = options.namespace ?? options.info?.namespace;
|
|
4157
|
+
const items = new Map;
|
|
4158
|
+
const jobs = new Map;
|
|
4159
|
+
const searches = [];
|
|
4160
|
+
let itemSequence = 0;
|
|
4161
|
+
let jobSequence = 0;
|
|
4162
|
+
let info = {
|
|
4163
|
+
id: instanceId,
|
|
4164
|
+
type: "builtin",
|
|
4165
|
+
namespace,
|
|
4166
|
+
status: "ready",
|
|
4167
|
+
created_at: MOCK_TIMESTAMP,
|
|
4168
|
+
modified_at: MOCK_TIMESTAMP,
|
|
4169
|
+
index_method: {
|
|
4170
|
+
vector: true,
|
|
4171
|
+
keyword: true
|
|
4172
|
+
},
|
|
4173
|
+
fusion_method: "rrf",
|
|
4174
|
+
...options.info
|
|
4175
|
+
};
|
|
4176
|
+
for (const fixture of options.items ?? []) {
|
|
4177
|
+
const stored = createStoredItem(++itemSequence, fixture, namespace);
|
|
4178
|
+
items.set(stored.info.id, stored);
|
|
2322
4179
|
}
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
if (value instanceof ArrayBuffer) {
|
|
2328
|
-
return new Uint8Array(value.slice(0));
|
|
2329
|
-
}
|
|
2330
|
-
if (ArrayBuffer.isView(value)) {
|
|
2331
|
-
const view = value;
|
|
2332
|
-
const copy = new Uint8Array(view.byteLength);
|
|
2333
|
-
copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
2334
|
-
return copy;
|
|
2335
|
-
}
|
|
2336
|
-
const reader = value.getReader();
|
|
4180
|
+
const findMatches = (params) => {
|
|
4181
|
+
searches.push(params);
|
|
4182
|
+
const query = extractSearchQuery(params);
|
|
4183
|
+
const maxResults = params.ai_search_options?.retrieval?.max_num_results ?? 10;
|
|
2337
4184
|
const chunks = [];
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
return JSON.parse(decoder.decode(bytes));
|
|
2361
|
-
case "arrayBuffer": {
|
|
2362
|
-
const copy = new Uint8Array(bytes.length);
|
|
2363
|
-
copy.set(bytes);
|
|
2364
|
-
return copy.buffer;
|
|
2365
|
-
}
|
|
2366
|
-
case "stream": {
|
|
2367
|
-
const copy = new Uint8Array(bytes.length);
|
|
2368
|
-
copy.set(bytes);
|
|
2369
|
-
return new ReadableStream({
|
|
2370
|
-
start(controller) {
|
|
2371
|
-
controller.enqueue(copy);
|
|
2372
|
-
controller.close();
|
|
4185
|
+
for (const item of items.values()) {
|
|
4186
|
+
for (const chunk of item.chunks) {
|
|
4187
|
+
const score = scoreText(query, `${item.info.key} ${chunk.text}`);
|
|
4188
|
+
if (score === 0) {
|
|
4189
|
+
continue;
|
|
4190
|
+
}
|
|
4191
|
+
chunks.push({
|
|
4192
|
+
id: chunk.id,
|
|
4193
|
+
type: "text",
|
|
4194
|
+
score,
|
|
4195
|
+
text: chunk.text,
|
|
4196
|
+
item: {
|
|
4197
|
+
timestamp: Date.parse(MOCK_TIMESTAMP),
|
|
4198
|
+
key: item.info.key,
|
|
4199
|
+
metadata: item.info.metadata
|
|
4200
|
+
},
|
|
4201
|
+
scoring_details: {
|
|
4202
|
+
keyword_score: score,
|
|
4203
|
+
vector_score: score,
|
|
4204
|
+
keyword_rank: chunks.length + 1,
|
|
4205
|
+
vector_rank: chunks.length + 1,
|
|
4206
|
+
fusion_method: "rrf"
|
|
2373
4207
|
}
|
|
2374
4208
|
});
|
|
2375
4209
|
}
|
|
2376
|
-
default:
|
|
2377
|
-
return decoder.decode(bytes);
|
|
2378
4210
|
}
|
|
4211
|
+
return {
|
|
4212
|
+
search_query: query,
|
|
4213
|
+
chunks: chunks.slice(0, maxResults)
|
|
4214
|
+
};
|
|
2379
4215
|
};
|
|
2380
|
-
const
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
if (bytes === undefined)
|
|
2388
|
-
return null;
|
|
2389
|
-
return decodeBytes(bytes, resolveType(options));
|
|
4216
|
+
const createItemHandle = (itemId) => ({
|
|
4217
|
+
async info() {
|
|
4218
|
+
const item = items.get(itemId);
|
|
4219
|
+
if (!item) {
|
|
4220
|
+
throw new Error(`Mock AI Search item "${itemId}" was not found.`);
|
|
4221
|
+
}
|
|
4222
|
+
return cloneInfo(item.info);
|
|
2390
4223
|
},
|
|
2391
|
-
async
|
|
2392
|
-
const
|
|
2393
|
-
|
|
4224
|
+
async download() {
|
|
4225
|
+
const item = items.get(itemId);
|
|
4226
|
+
if (!item) {
|
|
4227
|
+
throw new Error(`Mock AI Search item "${itemId}" was not found.`);
|
|
4228
|
+
}
|
|
4229
|
+
return {
|
|
4230
|
+
body: streamFromText(item.content),
|
|
4231
|
+
contentType: item.contentType,
|
|
4232
|
+
filename: item.info.key,
|
|
4233
|
+
size: new TextEncoder().encode(item.content).byteLength
|
|
4234
|
+
};
|
|
2394
4235
|
},
|
|
2395
|
-
async
|
|
2396
|
-
|
|
2397
|
-
|
|
4236
|
+
async sync() {
|
|
4237
|
+
const item = items.get(itemId);
|
|
4238
|
+
if (!item) {
|
|
4239
|
+
throw new Error(`Mock AI Search item "${itemId}" was not found.`);
|
|
4240
|
+
}
|
|
4241
|
+
item.info.status = "completed";
|
|
4242
|
+
item.info.last_seen_at = MOCK_TIMESTAMP;
|
|
4243
|
+
return cloneInfo(item.info);
|
|
2398
4244
|
},
|
|
2399
|
-
async
|
|
2400
|
-
const
|
|
2401
|
-
|
|
2402
|
-
|
|
4245
|
+
async logs(params) {
|
|
4246
|
+
const item = items.get(itemId);
|
|
4247
|
+
if (!item) {
|
|
4248
|
+
throw new Error(`Mock AI Search item "${itemId}" was not found.`);
|
|
4249
|
+
}
|
|
4250
|
+
const limit = params?.limit ?? 50;
|
|
4251
|
+
const result = item.logs.slice(0, limit);
|
|
2403
4252
|
return {
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
4253
|
+
result,
|
|
4254
|
+
result_info: {
|
|
4255
|
+
count: result.length,
|
|
4256
|
+
per_page: limit,
|
|
4257
|
+
cursor: null,
|
|
4258
|
+
truncated: item.logs.length > result.length
|
|
4259
|
+
}
|
|
2407
4260
|
};
|
|
2408
4261
|
},
|
|
2409
|
-
async
|
|
2410
|
-
const
|
|
4262
|
+
async chunks(params) {
|
|
4263
|
+
const item = items.get(itemId);
|
|
4264
|
+
if (!item) {
|
|
4265
|
+
throw new Error(`Mock AI Search item "${itemId}" was not found.`);
|
|
4266
|
+
}
|
|
4267
|
+
const offset = params?.offset ?? 0;
|
|
4268
|
+
const limit = params?.limit ?? 20;
|
|
4269
|
+
const result = item.chunks.slice(offset, offset + limit);
|
|
2411
4270
|
return {
|
|
2412
|
-
|
|
2413
|
-
|
|
4271
|
+
result,
|
|
4272
|
+
result_info: {
|
|
4273
|
+
count: result.length,
|
|
4274
|
+
total: item.chunks.length,
|
|
4275
|
+
limit,
|
|
4276
|
+
offset
|
|
4277
|
+
}
|
|
2414
4278
|
};
|
|
2415
4279
|
}
|
|
4280
|
+
});
|
|
4281
|
+
const uploadItem = async (name, content, uploadOptions) => {
|
|
4282
|
+
const text = await contentToText(content);
|
|
4283
|
+
const existing = Array.from(items.values()).find((item) => item.info.key === name);
|
|
4284
|
+
const stored = createStoredItem(existing ? Number(existing.info.id.replace(/^item-/, "")) || ++itemSequence : ++itemSequence, {
|
|
4285
|
+
id: existing?.info.id,
|
|
4286
|
+
key: name,
|
|
4287
|
+
content: text,
|
|
4288
|
+
metadata: uploadOptions?.metadata
|
|
4289
|
+
}, namespace);
|
|
4290
|
+
items.set(stored.info.id, stored);
|
|
4291
|
+
return cloneInfo(stored.info);
|
|
2416
4292
|
};
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
return
|
|
4293
|
+
const itemsApi = {
|
|
4294
|
+
async list(params) {
|
|
4295
|
+
let result = Array.from(items.values()).map((item) => cloneInfo(item.info));
|
|
4296
|
+
if (params?.search) {
|
|
4297
|
+
const search = params.search.toLowerCase();
|
|
4298
|
+
result = result.filter((item) => item.key.toLowerCase().includes(search));
|
|
4299
|
+
}
|
|
4300
|
+
if (params?.status) {
|
|
4301
|
+
result = result.filter((item) => item.status === params.status);
|
|
4302
|
+
}
|
|
4303
|
+
const page = params?.page ?? 1;
|
|
4304
|
+
const perPage = (params?.per_page ?? result.length) || 50;
|
|
4305
|
+
const start = (page - 1) * perPage;
|
|
4306
|
+
const paged = result.slice(start, start + perPage);
|
|
4307
|
+
return {
|
|
4308
|
+
result: paged,
|
|
4309
|
+
result_info: {
|
|
4310
|
+
count: paged.length,
|
|
4311
|
+
page,
|
|
4312
|
+
per_page: perPage,
|
|
4313
|
+
total_count: result.length
|
|
4314
|
+
}
|
|
4315
|
+
};
|
|
4316
|
+
},
|
|
4317
|
+
upload: uploadItem,
|
|
4318
|
+
async uploadAndPoll(name, content, uploadOptions) {
|
|
4319
|
+
return uploadItem(name, content, uploadOptions);
|
|
4320
|
+
},
|
|
4321
|
+
get(itemId) {
|
|
4322
|
+
return createItemHandle(itemId);
|
|
4323
|
+
},
|
|
4324
|
+
async delete(itemId) {
|
|
4325
|
+
items.delete(itemId);
|
|
2447
4326
|
}
|
|
2448
|
-
return { rows: [...fallback], op, table };
|
|
2449
4327
|
};
|
|
2450
|
-
const
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
async run() {
|
|
2475
|
-
const { op, table } = resolveRows(sql);
|
|
2476
|
-
let changes = 0;
|
|
2477
|
-
let lastRowId = 0;
|
|
2478
|
-
if (op === "insert" && table) {
|
|
2479
|
-
const rows = tables.get(table) ?? [];
|
|
2480
|
-
const bound = boundValues.length > 0 ? Object.fromEntries(boundValues.map((v, i) => [`col${i}`, v])) : {};
|
|
2481
|
-
rows.push(bound);
|
|
2482
|
-
tables.set(table, rows);
|
|
2483
|
-
changes = 1;
|
|
2484
|
-
lastRowId = rows.length;
|
|
2485
|
-
} else if (op === "delete" && table) {
|
|
2486
|
-
const rows = tables.get(table) ?? [];
|
|
2487
|
-
changes = rows.length;
|
|
2488
|
-
tables.set(table, []);
|
|
2489
|
-
} else if (op === "update" && table) {
|
|
2490
|
-
changes = (tables.get(table) ?? []).length;
|
|
4328
|
+
const createJobHandle = (jobId) => ({
|
|
4329
|
+
async info() {
|
|
4330
|
+
const job = jobs.get(jobId);
|
|
4331
|
+
if (!job) {
|
|
4332
|
+
throw new Error(`Mock AI Search job "${jobId}" was not found.`);
|
|
4333
|
+
}
|
|
4334
|
+
return cloneInfo(job);
|
|
4335
|
+
},
|
|
4336
|
+
async logs(params) {
|
|
4337
|
+
const perPage = params?.per_page ?? 50;
|
|
4338
|
+
return {
|
|
4339
|
+
result: [
|
|
4340
|
+
{
|
|
4341
|
+
id: 1,
|
|
4342
|
+
message: `Mock job ${jobId}`,
|
|
4343
|
+
message_type: 0,
|
|
4344
|
+
created_at: Date.parse(MOCK_TIMESTAMP)
|
|
4345
|
+
}
|
|
4346
|
+
].slice(0, perPage),
|
|
4347
|
+
result_info: {
|
|
4348
|
+
count: 1,
|
|
4349
|
+
page: params?.page ?? 1,
|
|
4350
|
+
per_page: perPage,
|
|
4351
|
+
total_count: 1
|
|
2491
4352
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
async raw(_options) {
|
|
2499
|
-
const { rows } = resolveRows(sql);
|
|
2500
|
-
return rows.map((row) => Object.values(row));
|
|
4353
|
+
};
|
|
4354
|
+
},
|
|
4355
|
+
async cancel() {
|
|
4356
|
+
const job = jobs.get(jobId);
|
|
4357
|
+
if (!job) {
|
|
4358
|
+
throw new Error(`Mock AI Search job "${jobId}" was not found.`);
|
|
2501
4359
|
}
|
|
2502
|
-
|
|
2503
|
-
|
|
4360
|
+
const updated = {
|
|
4361
|
+
...job,
|
|
4362
|
+
ended_at: MOCK_TIMESTAMP,
|
|
4363
|
+
end_reason: "cancelled"
|
|
4364
|
+
};
|
|
4365
|
+
jobs.set(jobId, updated);
|
|
4366
|
+
return cloneInfo(updated);
|
|
4367
|
+
}
|
|
4368
|
+
});
|
|
4369
|
+
const jobsApi = {
|
|
4370
|
+
async list(params) {
|
|
4371
|
+
const allJobs = Array.from(jobs.values()).map((job) => cloneInfo(job));
|
|
4372
|
+
const page = params?.page ?? 1;
|
|
4373
|
+
const perPage = (params?.per_page ?? allJobs.length) || 50;
|
|
4374
|
+
const start = (page - 1) * perPage;
|
|
4375
|
+
const result = allJobs.slice(start, start + perPage);
|
|
4376
|
+
return {
|
|
4377
|
+
result,
|
|
4378
|
+
result_info: {
|
|
4379
|
+
count: result.length,
|
|
4380
|
+
page,
|
|
4381
|
+
per_page: perPage,
|
|
4382
|
+
total_count: allJobs.length
|
|
4383
|
+
}
|
|
4384
|
+
};
|
|
4385
|
+
},
|
|
4386
|
+
async create(params) {
|
|
4387
|
+
const id = `job-${++jobSequence}`;
|
|
4388
|
+
const job = {
|
|
4389
|
+
id,
|
|
4390
|
+
source: "user",
|
|
4391
|
+
description: params?.description,
|
|
4392
|
+
started_at: MOCK_TIMESTAMP
|
|
4393
|
+
};
|
|
4394
|
+
jobs.set(id, job);
|
|
4395
|
+
return cloneInfo(job);
|
|
4396
|
+
},
|
|
4397
|
+
get(jobId) {
|
|
4398
|
+
return createJobHandle(jobId);
|
|
4399
|
+
}
|
|
2504
4400
|
};
|
|
2505
4401
|
return {
|
|
2506
|
-
|
|
2507
|
-
return
|
|
4402
|
+
async search(params) {
|
|
4403
|
+
return findMatches(params);
|
|
2508
4404
|
},
|
|
2509
|
-
async
|
|
4405
|
+
async chatCompletions(params) {
|
|
4406
|
+
const search = findMatches({
|
|
4407
|
+
messages: params.messages,
|
|
4408
|
+
ai_search_options: params.ai_search_options
|
|
4409
|
+
});
|
|
4410
|
+
const content = typeof options.chatMessage === "function" ? options.chatMessage(search.chunks, params) : options.chatMessage ?? (search.chunks.map((chunk) => chunk.text).join(`
|
|
4411
|
+
`) || "No matching offline AI Search chunks.");
|
|
4412
|
+
if (params.stream) {
|
|
4413
|
+
const payload = JSON.stringify({
|
|
4414
|
+
choices: [{ delta: { content } }],
|
|
4415
|
+
chunks: search.chunks
|
|
4416
|
+
});
|
|
4417
|
+
return streamFromText(`data: ${payload}
|
|
4418
|
+
|
|
4419
|
+
`);
|
|
4420
|
+
}
|
|
2510
4421
|
return {
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
4422
|
+
id: "mock-ai-search-chat",
|
|
4423
|
+
object: "chat.completion",
|
|
4424
|
+
model: params.model ?? "mock-ai-search",
|
|
4425
|
+
choices: [
|
|
4426
|
+
{
|
|
4427
|
+
index: 0,
|
|
4428
|
+
message: {
|
|
4429
|
+
role: "assistant",
|
|
4430
|
+
content
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
],
|
|
4434
|
+
chunks: search.chunks
|
|
2514
4435
|
};
|
|
2515
4436
|
},
|
|
2516
|
-
async
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
}
|
|
4437
|
+
async update(config) {
|
|
4438
|
+
info = {
|
|
4439
|
+
...info,
|
|
4440
|
+
...config,
|
|
4441
|
+
modified_at: MOCK_TIMESTAMP
|
|
4442
|
+
};
|
|
4443
|
+
return cloneInfo(info);
|
|
2522
4444
|
},
|
|
2523
|
-
async
|
|
2524
|
-
return
|
|
4445
|
+
async info() {
|
|
4446
|
+
return cloneInfo(info);
|
|
2525
4447
|
},
|
|
2526
|
-
|
|
2527
|
-
|
|
4448
|
+
async stats() {
|
|
4449
|
+
const stats = {
|
|
4450
|
+
queued: 0,
|
|
4451
|
+
running: 0,
|
|
4452
|
+
completed: 0,
|
|
4453
|
+
error: 0,
|
|
4454
|
+
skipped: 0,
|
|
4455
|
+
outdated: 0,
|
|
4456
|
+
last_activity: MOCK_TIMESTAMP,
|
|
4457
|
+
engine: {
|
|
4458
|
+
vectorize: {
|
|
4459
|
+
vectorsCount: items.size,
|
|
4460
|
+
dimensions: 0
|
|
4461
|
+
},
|
|
4462
|
+
r2: {
|
|
4463
|
+
payloadSizeBytes: Array.from(items.values()).reduce((sum, item) => sum + item.info.file_size, 0),
|
|
4464
|
+
metadataSizeBytes: 0,
|
|
4465
|
+
objectCount: items.size
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
};
|
|
4469
|
+
for (const item of items.values()) {
|
|
4470
|
+
stats[item.info.status] = (stats[item.info.status] ?? 0) + 1;
|
|
4471
|
+
}
|
|
4472
|
+
return stats;
|
|
4473
|
+
},
|
|
4474
|
+
get items() {
|
|
4475
|
+
return itemsApi;
|
|
4476
|
+
},
|
|
4477
|
+
get jobs() {
|
|
4478
|
+
return jobsApi;
|
|
4479
|
+
},
|
|
4480
|
+
_getSearches() {
|
|
4481
|
+
return [...searches];
|
|
4482
|
+
},
|
|
4483
|
+
_getItems() {
|
|
4484
|
+
return Array.from(items.values()).map((item) => cloneInfo(item.info));
|
|
4485
|
+
},
|
|
4486
|
+
_getJobs() {
|
|
4487
|
+
return Array.from(jobs.values()).map((job) => cloneInfo(job));
|
|
2528
4488
|
}
|
|
2529
4489
|
};
|
|
2530
4490
|
}
|
|
2531
|
-
function
|
|
2532
|
-
const
|
|
2533
|
-
const
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
body: new ReadableStream({
|
|
2548
|
-
start(controller) {
|
|
2549
|
-
controller.enqueue(data);
|
|
2550
|
-
controller.close();
|
|
2551
|
-
}
|
|
2552
|
-
}),
|
|
2553
|
-
bodyUsed: false,
|
|
2554
|
-
async arrayBuffer() {
|
|
2555
|
-
return new Uint8Array(data).buffer;
|
|
2556
|
-
},
|
|
2557
|
-
async text() {
|
|
2558
|
-
return content;
|
|
2559
|
-
},
|
|
2560
|
-
async json() {
|
|
2561
|
-
return JSON.parse(content);
|
|
2562
|
-
},
|
|
2563
|
-
async blob() {
|
|
2564
|
-
return new Blob([data]);
|
|
2565
|
-
},
|
|
2566
|
-
writeHttpMetadata(headers) {}
|
|
2567
|
-
};
|
|
4491
|
+
function createMockAISearchNamespace(options = {}) {
|
|
4492
|
+
const namespaceName = options.namespace ?? "default";
|
|
4493
|
+
const instances = new Map;
|
|
4494
|
+
for (const [name, instanceOptions] of Object.entries(options.instances ?? {})) {
|
|
4495
|
+
instances.set(name, isAISearchInstance(instanceOptions) ? instanceOptions : createMockAISearchInstance({
|
|
4496
|
+
id: name,
|
|
4497
|
+
namespace: namespaceName,
|
|
4498
|
+
...instanceOptions
|
|
4499
|
+
}));
|
|
4500
|
+
}
|
|
4501
|
+
const getInstance = (name) => {
|
|
4502
|
+
const instance = instances.get(name);
|
|
4503
|
+
if (!instance) {
|
|
4504
|
+
throw new Error(`Mock AI Search namespace has no instance named "${name}".`);
|
|
4505
|
+
}
|
|
4506
|
+
return instance;
|
|
2568
4507
|
};
|
|
2569
4508
|
return {
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
const chunks = [];
|
|
2583
|
-
let done = false;
|
|
2584
|
-
while (!done) {
|
|
2585
|
-
const result = await reader.read();
|
|
2586
|
-
done = result.done;
|
|
2587
|
-
if (result.value) {
|
|
2588
|
-
chunks.push(new TextDecoder().decode(result.value));
|
|
2589
|
-
}
|
|
2590
|
-
}
|
|
2591
|
-
content = chunks.join("");
|
|
4509
|
+
get(name) {
|
|
4510
|
+
return getInstance(name);
|
|
4511
|
+
},
|
|
4512
|
+
async list(params) {
|
|
4513
|
+
let result = await Promise.all(Array.from(instances.entries()).map(async ([name, instance]) => ({
|
|
4514
|
+
...await instance.info(),
|
|
4515
|
+
namespace: namespaceName,
|
|
4516
|
+
id: name
|
|
4517
|
+
})));
|
|
4518
|
+
if (params?.search) {
|
|
4519
|
+
const search = params.search.toLowerCase();
|
|
4520
|
+
result = result.filter((instance) => instance.id.toLowerCase().includes(search));
|
|
2592
4521
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
4522
|
+
const page = params?.page ?? 1;
|
|
4523
|
+
const perPage = (params?.per_page ?? result.length) || 50;
|
|
4524
|
+
const start = (page - 1) * perPage;
|
|
4525
|
+
const paged = result.slice(start, start + perPage);
|
|
4526
|
+
return {
|
|
4527
|
+
result: paged,
|
|
4528
|
+
result_info: {
|
|
4529
|
+
count: paged.length,
|
|
4530
|
+
page,
|
|
4531
|
+
per_page: perPage,
|
|
4532
|
+
total_count: result.length
|
|
4533
|
+
}
|
|
4534
|
+
};
|
|
2595
4535
|
},
|
|
2596
|
-
async
|
|
2597
|
-
const
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
4536
|
+
async create(config) {
|
|
4537
|
+
const instance = createMockAISearchInstance({
|
|
4538
|
+
id: config.id,
|
|
4539
|
+
namespace: namespaceName,
|
|
4540
|
+
info: config
|
|
4541
|
+
});
|
|
4542
|
+
instances.set(config.id, instance);
|
|
4543
|
+
return instance;
|
|
2601
4544
|
},
|
|
2602
|
-
async
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
4545
|
+
async delete(name) {
|
|
4546
|
+
instances.delete(name);
|
|
4547
|
+
},
|
|
4548
|
+
async search(params) {
|
|
4549
|
+
const query = extractSearchQuery(params);
|
|
4550
|
+
const chunks = [];
|
|
4551
|
+
const errors = [];
|
|
4552
|
+
for (const instanceId of params.ai_search_options.instance_ids) {
|
|
4553
|
+
const instance = instances.get(instanceId);
|
|
4554
|
+
if (!instance) {
|
|
4555
|
+
errors.push({
|
|
4556
|
+
instance_id: instanceId,
|
|
4557
|
+
message: `Mock AI Search namespace has no instance named "${instanceId}".`
|
|
4558
|
+
});
|
|
4559
|
+
continue;
|
|
4560
|
+
}
|
|
4561
|
+
const response = "query" in params ? await instance.search({ query, ai_search_options: params.ai_search_options }) : await instance.search({
|
|
4562
|
+
messages: params.messages,
|
|
4563
|
+
ai_search_options: params.ai_search_options
|
|
4564
|
+
});
|
|
4565
|
+
chunks.push(...response.chunks.map((chunk) => ({
|
|
4566
|
+
...chunk,
|
|
4567
|
+
instance_id: instanceId
|
|
4568
|
+
})));
|
|
4569
|
+
}
|
|
2606
4570
|
return {
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
etag: `"${key}-etag"`,
|
|
2611
|
-
httpEtag: `"${key}-etag"`,
|
|
2612
|
-
uploaded: new Date,
|
|
2613
|
-
httpMetadata: item.metadata ?? {},
|
|
2614
|
-
customMetadata: {},
|
|
2615
|
-
checksums: {},
|
|
2616
|
-
storageClass: "Standard",
|
|
2617
|
-
writeHttpMetadata(headers) {}
|
|
4571
|
+
search_query: query,
|
|
4572
|
+
chunks,
|
|
4573
|
+
...errors.length > 0 && { errors }
|
|
2618
4574
|
};
|
|
2619
4575
|
},
|
|
2620
|
-
async
|
|
2621
|
-
const
|
|
2622
|
-
|
|
2623
|
-
|
|
4576
|
+
async chatCompletions(params) {
|
|
4577
|
+
const search = await this.search({
|
|
4578
|
+
messages: params.messages,
|
|
4579
|
+
ai_search_options: params.ai_search_options
|
|
4580
|
+
});
|
|
4581
|
+
const content = search.chunks.map((chunk) => chunk.text).join(`
|
|
4582
|
+
`) || "No matching offline AI Search chunks.";
|
|
4583
|
+
if (params.stream) {
|
|
4584
|
+
return streamFromText(`data: ${JSON.stringify({ chunks: search.chunks })}
|
|
4585
|
+
|
|
4586
|
+
`);
|
|
2624
4587
|
}
|
|
2625
|
-
},
|
|
2626
|
-
async list(options) {
|
|
2627
|
-
const objects = Array.from(store.entries()).map(([key, { content, metadata }]) => createR2Object(key, content, metadata));
|
|
2628
4588
|
return {
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
4589
|
+
id: "mock-ai-search-namespace-chat",
|
|
4590
|
+
object: "chat.completion",
|
|
4591
|
+
model: params.model ?? "mock-ai-search",
|
|
4592
|
+
choices: [
|
|
4593
|
+
{
|
|
4594
|
+
index: 0,
|
|
4595
|
+
message: {
|
|
4596
|
+
role: "assistant",
|
|
4597
|
+
content
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
],
|
|
4601
|
+
chunks: search.chunks,
|
|
4602
|
+
errors: search.errors
|
|
2632
4603
|
};
|
|
2633
4604
|
},
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
},
|
|
2637
|
-
async resumeMultipartUpload(key, uploadId) {
|
|
2638
|
-
throw new Error("Multipart upload not implemented in mock");
|
|
4605
|
+
_getInstances() {
|
|
4606
|
+
return Array.from(instances.keys());
|
|
2639
4607
|
}
|
|
2640
4608
|
};
|
|
2641
4609
|
}
|
|
2642
|
-
|
|
2643
|
-
|
|
4610
|
+
|
|
4611
|
+
// src/test/offline-bindings.ts
|
|
4612
|
+
var SUPPORT_MATRIX = {
|
|
4613
|
+
rateLimits: {
|
|
4614
|
+
service: "rateLimits",
|
|
4615
|
+
tier: "offline-native",
|
|
4616
|
+
reason: "Miniflare and devflare/test can simulate fixed-window RateLimit bindings locally.",
|
|
4617
|
+
recommendation: "Use createOfflineEnv() or createMockRateLimit() for pure tests; use createTestContext() for Miniflare-backed tests."
|
|
4618
|
+
},
|
|
4619
|
+
versionMetadata: {
|
|
4620
|
+
service: "versionMetadata",
|
|
4621
|
+
tier: "offline-native",
|
|
4622
|
+
reason: "Version metadata can be deterministic in tests without Cloudflare state.",
|
|
4623
|
+
recommendation: "Use createOfflineEnv() or createMockVersionMetadata() when asserting version-aware code."
|
|
4624
|
+
},
|
|
4625
|
+
secretsStore: {
|
|
4626
|
+
service: "secretsStore",
|
|
4627
|
+
tier: "offline-native",
|
|
4628
|
+
reason: "Secrets Store binding shape is local-testable with local secret-store values or fixed fixtures.",
|
|
4629
|
+
recommendation: "Use devflare secrets --local for dev/test values, or pass fixtures.secretsStore values for pure tests."
|
|
4630
|
+
},
|
|
4631
|
+
hyperdrive: {
|
|
4632
|
+
service: "hyperdrive",
|
|
4633
|
+
tier: "offline-native",
|
|
4634
|
+
reason: "Hyperdrive can run locally through Miniflare when Devflare has a local connection string or test fixture for the target database.",
|
|
4635
|
+
recommendation: "Use bindings.hyperdrive.*.localConnectionString, CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>, or fixtures.hyperdrive for offline tests."
|
|
4636
|
+
},
|
|
4637
|
+
workerLoaders: {
|
|
4638
|
+
service: "workerLoaders",
|
|
4639
|
+
tier: "offline-native",
|
|
4640
|
+
reason: "Worker Loader bindings run locally through Miniflare and can use explicit Worker stubs for pure tests.",
|
|
4641
|
+
recommendation: "Use createTestContext() for local WorkerLoader execution; pass fixtures.workerLoaders with a WorkerStub for pure tests."
|
|
4642
|
+
},
|
|
4643
|
+
mtlsCertificates: {
|
|
4644
|
+
service: "mtlsCertificates",
|
|
4645
|
+
tier: "offline-fixture",
|
|
4646
|
+
reason: "Fetcher call paths can be tested locally, but real certificate presentation is Cloudflare/Wrangler remote behavior.",
|
|
4647
|
+
recommendation: "Pass fixtures.mtlsCertificates handlers for unit tests; use remote/deployed tests for certificate presentation."
|
|
4648
|
+
},
|
|
4649
|
+
dispatchNamespaces: {
|
|
4650
|
+
service: "dispatchNamespaces",
|
|
4651
|
+
tier: "offline-fixture",
|
|
4652
|
+
reason: "Tenant dispatch can be backed by explicit test fetchers, but namespace uploads and lifecycle are Cloudflare-managed.",
|
|
4653
|
+
recommendation: "Pass fixtures.dispatchNamespaces workers for deterministic tenant routing tests."
|
|
4654
|
+
},
|
|
4655
|
+
workflows: {
|
|
4656
|
+
service: "workflows",
|
|
4657
|
+
tier: "offline-native",
|
|
4658
|
+
reason: "Workflow binding calls can run through Miniflare or a deterministic local Workflow mock for app-level tests.",
|
|
4659
|
+
recommendation: "Use createOfflineEnv() for pure tests and createTestContext() when Miniflare execution semantics matter."
|
|
4660
|
+
},
|
|
4661
|
+
pipelines: {
|
|
4662
|
+
service: "pipelines",
|
|
4663
|
+
tier: "offline-native",
|
|
4664
|
+
reason: "Pipeline sends can be recorded locally; Cloudflare owns production batching and sinks.",
|
|
4665
|
+
recommendation: "Use createOfflineEnv() or createMockPipeline() to assert records sent by application code."
|
|
4666
|
+
},
|
|
4667
|
+
images: {
|
|
4668
|
+
service: "images",
|
|
4669
|
+
tier: "offline-native",
|
|
4670
|
+
reason: "Images has local development support and Devflare provides a low-fidelity deterministic pure mock.",
|
|
4671
|
+
recommendation: "Use createOfflineEnv() for chain-shape tests; use Cloudflare for hosted image APIs and transform fidelity."
|
|
4672
|
+
},
|
|
4673
|
+
media: {
|
|
4674
|
+
service: "media",
|
|
4675
|
+
tier: "offline-native",
|
|
4676
|
+
reason: "Media Transformations can run through Miniflare wiring locally, and Devflare provides a deterministic pure mock for app-level chain tests.",
|
|
4677
|
+
recommendation: "Use createTestContext() for local Worker binding tests and createMockMediaBinding() for pure tests; use Cloudflare for codec/output fidelity."
|
|
4678
|
+
},
|
|
4679
|
+
artifacts: {
|
|
4680
|
+
service: "artifacts",
|
|
4681
|
+
tier: "offline-fixture",
|
|
4682
|
+
reason: "Artifacts repo metadata and token flows can be modeled in memory; real Git remotes are Cloudflare-managed.",
|
|
4683
|
+
recommendation: "Use createMockArtifacts() for unit tests; use Cloudflare for Git protocol and namespace access."
|
|
4684
|
+
},
|
|
4685
|
+
aiSearch: {
|
|
4686
|
+
service: "aiSearch",
|
|
4687
|
+
tier: "offline-fixture",
|
|
4688
|
+
reason: "AI Search application flows can use deterministic in-memory instances and namespaces, but indexing/ranking/crawling are hosted Cloudflare behavior.",
|
|
4689
|
+
recommendation: "Use createMockAISearchInstance(), createMockAISearchNamespace(), or createOfflineEnv(); use remote tests for real relevance behavior."
|
|
4690
|
+
},
|
|
4691
|
+
aiSearchNamespaces: {
|
|
4692
|
+
service: "aiSearchNamespaces",
|
|
4693
|
+
tier: "offline-fixture",
|
|
4694
|
+
reason: "AI Search namespace management can be backed by an explicit in-memory instance registry for tests.",
|
|
4695
|
+
recommendation: "Use fixtures.aiSearchNamespaces to model tenant instances and multi-instance searches."
|
|
4696
|
+
},
|
|
4697
|
+
containers: {
|
|
4698
|
+
service: "containers",
|
|
4699
|
+
tier: "offline-native",
|
|
4700
|
+
reason: "Devflare can launch local Docker/Podman containers in explicit tests when an engine and cached images are available.",
|
|
4701
|
+
recommendation: "Use devflare/test containers helpers and shouldSkip.containers; keep ordinary unit tests engine-free."
|
|
4702
|
+
},
|
|
4703
|
+
browser: {
|
|
4704
|
+
service: "browser",
|
|
4705
|
+
tier: "offline-native",
|
|
4706
|
+
reason: "Cloudflare lists Browser Run as locally simulatable, while live view, HITL, recordings, and external CDP remain hosted features.",
|
|
4707
|
+
recommendation: "Use Cloudflare/Wrangler Browser local development for browser execution and remote/deployed tests for hosted-only features."
|
|
4708
|
+
},
|
|
4709
|
+
ai: {
|
|
4710
|
+
service: "ai",
|
|
4711
|
+
tier: "remote-boundary",
|
|
4712
|
+
reason: "Workers AI inference has no local simulation in Cloudflare local development.",
|
|
4713
|
+
recommendation: "Use DEVFLARE_REMOTE=1/devflare remote enable for real calls, or inject a custom fake for pure tests."
|
|
4714
|
+
},
|
|
4715
|
+
aiGateway: {
|
|
4716
|
+
service: "aiGateway",
|
|
4717
|
+
tier: "remote-boundary",
|
|
4718
|
+
reason: "AI Gateway routing and logs are Cloudflare account resources reached through the Workers AI binding.",
|
|
4719
|
+
recommendation: "Use remote-mode AI Gateway helpers for integration tests and custom fakes for offline unit tests."
|
|
4720
|
+
},
|
|
4721
|
+
vectorize: {
|
|
4722
|
+
service: "vectorize",
|
|
4723
|
+
tier: "remote-boundary",
|
|
4724
|
+
reason: "Cloudflare lists Vectorize with no local simulation.",
|
|
4725
|
+
recommendation: "Use DEVFLARE_REMOTE=1/devflare remote enable for real indexes, or inject a fake Vectorize binding for pure tests."
|
|
4726
|
+
},
|
|
4727
|
+
builds: {
|
|
4728
|
+
service: "builds",
|
|
4729
|
+
tier: "remote-boundary",
|
|
4730
|
+
reason: "Cloudflare Builds/Git-connected Workers are CI/CD orchestration, not a Worker runtime binding.",
|
|
4731
|
+
recommendation: "Run Devflare commands inside your CI; validate Cloudflare build integration with Cloudflare/Wrangler tests."
|
|
4732
|
+
}
|
|
4733
|
+
};
|
|
4734
|
+
function copySupport(entry) {
|
|
4735
|
+
return { ...entry };
|
|
4736
|
+
}
|
|
4737
|
+
function getOfflineSupportMatrix() {
|
|
4738
|
+
return Object.fromEntries(Object.entries(SUPPORT_MATRIX).map(([service, entry]) => [service, copySupport(entry)]));
|
|
4739
|
+
}
|
|
4740
|
+
function describeOfflineSupport(service) {
|
|
4741
|
+
const entry = SUPPORT_MATRIX[service];
|
|
4742
|
+
if (entry) {
|
|
4743
|
+
return copySupport(entry);
|
|
4744
|
+
}
|
|
2644
4745
|
return {
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
messages.push(...batch);
|
|
2650
|
-
},
|
|
2651
|
-
_getMessages() {
|
|
2652
|
-
return messages;
|
|
2653
|
-
}
|
|
4746
|
+
service,
|
|
4747
|
+
tier: "remote-boundary",
|
|
4748
|
+
reason: `No offline support classification exists for "${service}".`,
|
|
4749
|
+
recommendation: "Treat this as a remote Cloudflare boundary until Devflare documents a local simulator or fixture."
|
|
2654
4750
|
};
|
|
2655
4751
|
}
|
|
2656
|
-
function
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
env2[name] = createMockKV();
|
|
4752
|
+
function createMissingSecret(binding) {
|
|
4753
|
+
return {
|
|
4754
|
+
async get() {
|
|
4755
|
+
throw new Error(`Offline Secrets Store binding "${binding}" has no value. Pass fixtures.secretsStore.${binding} or write a local secret with devflare secrets --local.`);
|
|
2661
4756
|
}
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
function isWorkflowBinding(value) {
|
|
4760
|
+
return typeof value.create === "function";
|
|
4761
|
+
}
|
|
4762
|
+
function isPipelineBinding(value) {
|
|
4763
|
+
return typeof value?.send === "function";
|
|
4764
|
+
}
|
|
4765
|
+
function isImagesBinding(value) {
|
|
4766
|
+
return typeof value?.input === "function";
|
|
4767
|
+
}
|
|
4768
|
+
function isHyperdriveBinding(value) {
|
|
4769
|
+
return typeof value?.connectionString === "string";
|
|
4770
|
+
}
|
|
4771
|
+
function isMediaBinding(value) {
|
|
4772
|
+
return typeof value?.input === "function";
|
|
4773
|
+
}
|
|
4774
|
+
function isArtifactsBinding2(value) {
|
|
4775
|
+
return typeof value?.create === "function";
|
|
4776
|
+
}
|
|
4777
|
+
function isAISearchInstance2(value) {
|
|
4778
|
+
return typeof value?.search === "function";
|
|
4779
|
+
}
|
|
4780
|
+
function isAISearchNamespace(value) {
|
|
4781
|
+
return typeof value?.get === "function";
|
|
4782
|
+
}
|
|
4783
|
+
function addBoundary(remoteBoundaries, service, binding, reason) {
|
|
4784
|
+
remoteBoundaries.push({
|
|
4785
|
+
service,
|
|
4786
|
+
binding,
|
|
4787
|
+
reason,
|
|
4788
|
+
recommendation: describeOfflineSupport(service).recommendation
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4791
|
+
function addStaticBindings(env3, config) {
|
|
4792
|
+
if (config.vars) {
|
|
4793
|
+
Object.assign(env3, config.vars);
|
|
2662
4794
|
}
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
4795
|
+
}
|
|
4796
|
+
function addRateLimitBindings(env3, bindings) {
|
|
4797
|
+
for (const [name, binding] of Object.entries(bindings?.rateLimits ?? {})) {
|
|
4798
|
+
env3[name] = createMockRateLimit(binding.simple);
|
|
2667
4799
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
4800
|
+
}
|
|
4801
|
+
function addVersionMetadataBinding(env3, bindings) {
|
|
4802
|
+
if (bindings?.versionMetadata) {
|
|
4803
|
+
env3[bindings.versionMetadata.binding] = createMockVersionMetadata();
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
function getHyperdriveConnectionString(name, binding, fixture) {
|
|
4807
|
+
if (typeof fixture === "string") {
|
|
4808
|
+
return fixture;
|
|
4809
|
+
}
|
|
4810
|
+
const envValue = process.env[`CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_${name}`] ?? process.env[`WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_${name}`];
|
|
4811
|
+
if (envValue?.trim()) {
|
|
4812
|
+
return envValue;
|
|
4813
|
+
}
|
|
4814
|
+
return normalizeHyperdriveBinding(binding).localConnectionString;
|
|
4815
|
+
}
|
|
4816
|
+
function addHyperdriveBindings(env3, bindings, fixtures, missingFixtures) {
|
|
4817
|
+
for (const [name, binding] of Object.entries(bindings?.hyperdrive ?? {})) {
|
|
4818
|
+
const fixture = fixtures.hyperdrive?.[name];
|
|
4819
|
+
if (isHyperdriveBinding(fixture)) {
|
|
4820
|
+
env3[name] = fixture;
|
|
4821
|
+
continue;
|
|
4822
|
+
}
|
|
4823
|
+
const connectionString = getHyperdriveConnectionString(name, binding, fixture);
|
|
4824
|
+
if (connectionString) {
|
|
4825
|
+
env3[name] = createMockHyperdrive(connectionString);
|
|
4826
|
+
continue;
|
|
2671
4827
|
}
|
|
4828
|
+
missingFixtures.push({
|
|
4829
|
+
service: "hyperdrive",
|
|
4830
|
+
binding: name,
|
|
4831
|
+
reason: `Hyperdrive binding "${name}" has no local connection string. Configure bindings.hyperdrive.${name}.localConnectionString or pass fixtures.hyperdrive.${name}.`
|
|
4832
|
+
});
|
|
2672
4833
|
}
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
4834
|
+
}
|
|
4835
|
+
function addWorkerLoaderBindings(env3, bindings, fixtures) {
|
|
4836
|
+
for (const name of Object.keys(bindings?.workerLoaders ?? {})) {
|
|
4837
|
+
env3[name] = createMockWorkerLoader(fixtures.workerLoaders?.[name]);
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
function addMTLSCertificateBindings(env3, bindings, fixtures) {
|
|
4841
|
+
for (const name of Object.keys(bindings?.mtlsCertificates ?? {})) {
|
|
4842
|
+
env3[name] = createMockMTLSCertificate(fixtures.mtlsCertificates?.[name]);
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
function addDispatchNamespaceBindings(env3, bindings, fixtures) {
|
|
4846
|
+
for (const name of Object.keys(bindings?.dispatchNamespaces ?? {})) {
|
|
4847
|
+
env3[name] = createMockDispatchNamespace(fixtures.dispatchNamespaces?.[name]);
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
function addWorkflowBindings(env3, bindings, fixtures) {
|
|
4851
|
+
for (const name of Object.keys(bindings?.workflows ?? {})) {
|
|
4852
|
+
const fixture = fixtures.workflows?.[name];
|
|
4853
|
+
env3[name] = fixture && isWorkflowBinding(fixture) ? fixture : createMockWorkflow(fixture);
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
function addPipelineBindings(env3, bindings, fixtures) {
|
|
4857
|
+
for (const name of Object.keys(bindings?.pipelines ?? {})) {
|
|
4858
|
+
const fixture = fixtures.pipelines?.[name];
|
|
4859
|
+
env3[name] = isPipelineBinding(fixture) ? fixture : createMockPipeline();
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
function addImagesBindings(env3, bindings, fixtures) {
|
|
4863
|
+
for (const name of Object.keys(bindings?.images ?? {})) {
|
|
4864
|
+
const fixture = fixtures.images?.[name];
|
|
4865
|
+
env3[name] = isImagesBinding(fixture) ? fixture : createMockImagesBinding(fixture);
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
function addMediaBindings(env3, bindings, fixtures) {
|
|
4869
|
+
for (const name of Object.keys(bindings?.media ?? {})) {
|
|
4870
|
+
const fixture = fixtures.media?.[name];
|
|
4871
|
+
env3[name] = isMediaBinding(fixture) ? fixture : createMockMediaBinding(fixture);
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
function addArtifactsBindings(env3, bindings, fixtures) {
|
|
4875
|
+
for (const name of Object.keys(bindings?.artifacts ?? {})) {
|
|
4876
|
+
const fixture = fixtures.artifacts?.[name];
|
|
4877
|
+
env3[name] = isArtifactsBinding2(fixture) ? fixture : createMockArtifacts(fixture);
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
function addSecretsStoreBindings(env3, bindings, fixtures, localSecretValues, missingFixtures) {
|
|
4881
|
+
for (const name of Object.keys(bindings?.secretsStore ?? {})) {
|
|
4882
|
+
const value = fixtures.secretsStore?.[name] ?? localSecretValues[name];
|
|
4883
|
+
if (value === undefined) {
|
|
4884
|
+
missingFixtures.push({
|
|
4885
|
+
service: "secretsStore",
|
|
4886
|
+
binding: name,
|
|
4887
|
+
reason: `Secrets Store values are not present in fixtures or the local secret store; pass fixtures.secretsStore.${name} or run devflare secrets --local.`
|
|
4888
|
+
});
|
|
4889
|
+
env3[name] = createMissingSecret(name);
|
|
4890
|
+
} else {
|
|
4891
|
+
env3[name] = createMockSecretsStoreSecret(value);
|
|
2676
4892
|
}
|
|
2677
4893
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
4894
|
+
}
|
|
4895
|
+
function addAISearchBindings(env3, bindings, fixtures) {
|
|
4896
|
+
for (const [name, binding] of Object.entries(bindings?.aiSearch ?? {})) {
|
|
4897
|
+
const fixture = fixtures.aiSearch?.[name];
|
|
4898
|
+
env3[name] = isAISearchInstance2(fixture) ? fixture : createMockAISearchInstance({
|
|
4899
|
+
id: binding.instanceName,
|
|
4900
|
+
...fixture
|
|
4901
|
+
});
|
|
2680
4902
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
4903
|
+
}
|
|
4904
|
+
function addAISearchNamespaceBindings(env3, bindings, fixtures) {
|
|
4905
|
+
for (const [name, binding] of Object.entries(bindings?.aiSearchNamespaces ?? {})) {
|
|
4906
|
+
const fixture = fixtures.aiSearchNamespaces?.[name];
|
|
4907
|
+
env3[name] = isAISearchNamespace(fixture) ? fixture : createMockAISearchNamespace({
|
|
4908
|
+
namespace: binding.namespace,
|
|
4909
|
+
...fixture
|
|
4910
|
+
});
|
|
2683
4911
|
}
|
|
2684
|
-
|
|
2685
|
-
|
|
4912
|
+
}
|
|
4913
|
+
function addRemoteBoundaries(remoteBoundaries, bindings) {
|
|
4914
|
+
if (bindings?.ai) {
|
|
4915
|
+
addBoundary(remoteBoundaries, "ai", bindings.ai.binding || "AI", "Workers AI inference is not available in offline local simulations.");
|
|
2686
4916
|
}
|
|
2687
|
-
|
|
4917
|
+
for (const name of Object.keys(bindings?.vectorize ?? {})) {
|
|
4918
|
+
addBoundary(remoteBoundaries, "vectorize", name, "Vectorize has no offline local simulation in Cloudflare local development.");
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
function createOfflineBindings(config, fixtures = {}, options = {}) {
|
|
4922
|
+
const env3 = {};
|
|
4923
|
+
const remoteBoundaries = [];
|
|
4924
|
+
const missingFixtures = [];
|
|
4925
|
+
const bindings = config.bindings;
|
|
4926
|
+
const localSecretValues = options.cwd && options.useLocalSecrets !== false ? resolveLocalSecretValuesForBindings(config, options.cwd) : {};
|
|
4927
|
+
addStaticBindings(env3, config);
|
|
4928
|
+
addRateLimitBindings(env3, bindings);
|
|
4929
|
+
addVersionMetadataBinding(env3, bindings);
|
|
4930
|
+
addHyperdriveBindings(env3, bindings, fixtures, missingFixtures);
|
|
4931
|
+
addWorkerLoaderBindings(env3, bindings, fixtures);
|
|
4932
|
+
addMTLSCertificateBindings(env3, bindings, fixtures);
|
|
4933
|
+
addDispatchNamespaceBindings(env3, bindings, fixtures);
|
|
4934
|
+
addWorkflowBindings(env3, bindings, fixtures);
|
|
4935
|
+
addPipelineBindings(env3, bindings, fixtures);
|
|
4936
|
+
addImagesBindings(env3, bindings, fixtures);
|
|
4937
|
+
addMediaBindings(env3, bindings, fixtures);
|
|
4938
|
+
addArtifactsBindings(env3, bindings, fixtures);
|
|
4939
|
+
addSecretsStoreBindings(env3, bindings, fixtures, localSecretValues, missingFixtures);
|
|
4940
|
+
addAISearchBindings(env3, bindings, fixtures);
|
|
4941
|
+
addAISearchNamespaceBindings(env3, bindings, fixtures);
|
|
4942
|
+
addRemoteBoundaries(remoteBoundaries, bindings);
|
|
4943
|
+
if (fixtures.custom) {
|
|
4944
|
+
Object.assign(env3, fixtures.custom);
|
|
4945
|
+
}
|
|
4946
|
+
return {
|
|
4947
|
+
env: env3,
|
|
4948
|
+
support: getOfflineSupportMatrix(),
|
|
4949
|
+
remoteBoundaries,
|
|
4950
|
+
missingFixtures
|
|
4951
|
+
};
|
|
4952
|
+
}
|
|
4953
|
+
function createOfflineEnv(config, fixtures = {}, options = {}) {
|
|
4954
|
+
return createOfflineBindings(config, fixtures, options).env;
|
|
2688
4955
|
}
|
|
2689
4956
|
export {
|
|
2690
4957
|
worker,
|
|
2691
4958
|
withTestContext,
|
|
2692
4959
|
tail,
|
|
4960
|
+
stopActiveContainers,
|
|
2693
4961
|
shouldSkip,
|
|
2694
4962
|
scheduled,
|
|
2695
4963
|
resolveServiceBindings,
|
|
2696
4964
|
resolveDOBindings,
|
|
2697
|
-
queue,
|
|
4965
|
+
queue2 as queue,
|
|
2698
4966
|
hasServiceBindings,
|
|
2699
4967
|
hasCrossWorkerDOs,
|
|
4968
|
+
getOfflineSupportMatrix,
|
|
4969
|
+
getContainerSkipReason,
|
|
2700
4970
|
env,
|
|
2701
4971
|
email,
|
|
4972
|
+
detectContainerEngine,
|
|
4973
|
+
describeOfflineSupport,
|
|
2702
4974
|
createTestContext,
|
|
4975
|
+
createOfflineEnv,
|
|
4976
|
+
createOfflineBindings,
|
|
4977
|
+
createMockWorkflow,
|
|
4978
|
+
createMockWorkerLoader,
|
|
4979
|
+
createMockVersionMetadata,
|
|
2703
4980
|
createMockTestContext,
|
|
4981
|
+
createMockSecretsStoreSecret,
|
|
4982
|
+
createMockRateLimit,
|
|
2704
4983
|
createMockR2,
|
|
2705
4984
|
createMockQueue,
|
|
4985
|
+
createMockPipeline,
|
|
4986
|
+
createMockMediaBinding,
|
|
4987
|
+
createMockMTLSCertificate,
|
|
2706
4988
|
createMockKV,
|
|
4989
|
+
createMockImagesBinding,
|
|
4990
|
+
createMockHyperdrive,
|
|
2707
4991
|
createMockEnv,
|
|
4992
|
+
createMockDispatchNamespace,
|
|
2708
4993
|
createMockD1,
|
|
4994
|
+
createMockArtifacts,
|
|
4995
|
+
createMockAISearchNamespace,
|
|
4996
|
+
createMockAISearchInstance,
|
|
4997
|
+
createContainerManager,
|
|
4998
|
+
containers,
|
|
2709
4999
|
clearBundleCache,
|
|
2710
5000
|
cf
|
|
2711
5001
|
};
|