devflare 1.0.0-next.32 → 1.0.0-next.34
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 +9 -9
- package/bin/devflare.js +1 -3
- package/dist/{account-g99mzrqp.js → account-p9eywryw.js} +27 -16
- package/dist/ai-r3zqmxan.js +261 -0
- package/dist/{api-2n6dd2ws.js → api-6z01z734.js} +1 -1
- package/dist/bridge/client.d.ts.map +1 -1
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/bridge/proxy.d.ts.map +1 -1
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/bridge/v2/body-streams.d.ts.map +1 -1
- package/dist/bridge/v2/codec.d.ts.map +1 -1
- package/dist/bridge/v2/frames.d.ts.map +1 -1
- package/dist/bridge/v2/value-codec.d.ts.map +1 -1
- package/dist/bridge/v2/value-serialization.d.ts.map +1 -1
- package/dist/bridge/v2/wire.d.ts.map +1 -1
- package/dist/bridge/v2/ws-relay.d.ts +1 -1
- package/dist/bridge/v2/ws-relay.d.ts.map +1 -1
- package/dist/browser-shim/handler.d.ts +1 -1
- package/dist/browser-shim/handler.d.ts.map +1 -1
- package/dist/browser-shim/server.d.ts.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +5 -5
- package/dist/{build-4tyjywn9.js → build-8bn7frg8.js} +16 -16
- package/dist/bundler/do-bundler.d.ts.map +1 -1
- package/dist/bundler/rolldown-shared.d.ts.map +1 -1
- package/dist/bundler/worker-bundler.d.ts.map +1 -1
- package/dist/bundler/worker-compat.d.ts.map +1 -1
- package/dist/cli/build-manifest.d.ts.map +1 -1
- package/dist/cli/command-utils.d.ts.map +1 -1
- package/dist/cli/commands/account.d.ts +1 -1
- package/dist/cli/commands/account.d.ts.map +1 -1
- package/dist/cli/commands/ai.d.ts.map +1 -1
- package/dist/cli/commands/build-artifacts.d.ts +3 -3
- package/dist/cli/commands/build-artifacts.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +2 -2
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/config.d.ts +2 -2
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/dev.d.ts +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts +2 -2
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +2 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/login.d.ts +1 -1
- package/dist/cli/commands/login.d.ts.map +1 -1
- package/dist/cli/commands/previews-support/cleanup.d.ts.map +1 -1
- package/dist/cli/commands/previews-support/family.d.ts.map +1 -1
- package/dist/cli/commands/previews-support/render.d.ts +1 -1
- package/dist/cli/commands/previews-support/render.d.ts.map +1 -1
- package/dist/cli/commands/previews-support/theme.d.ts +1 -1
- package/dist/cli/commands/previews-support/theme.d.ts.map +1 -1
- package/dist/cli/commands/previews-support/types.d.ts +1 -1
- package/dist/cli/commands/previews-support/types.d.ts.map +1 -1
- package/dist/cli/commands/previews.d.ts.map +1 -1
- package/dist/cli/commands/productions.d.ts +1 -1
- package/dist/cli/commands/productions.d.ts.map +1 -1
- package/dist/cli/commands/remote.d.ts +1 -1
- package/dist/cli/commands/remote.d.ts.map +1 -1
- package/dist/cli/commands/secrets.d.ts.map +1 -1
- package/dist/cli/commands/token.d.ts +2 -2
- package/dist/cli/commands/token.d.ts.map +1 -1
- package/dist/cli/commands/type-generation/discovery.d.ts.map +1 -1
- package/dist/cli/commands/type-generation/generator.d.ts.map +1 -1
- package/dist/cli/commands/types.d.ts +2 -2
- package/dist/cli/commands/types.d.ts.map +1 -1
- package/dist/cli/commands/worker.d.ts +2 -2
- package/dist/cli/commands/worker.d.ts.map +1 -1
- package/dist/cli/dependencies.d.ts +1 -1
- package/dist/cli/dependencies.d.ts.map +1 -1
- package/dist/cli/deploy-strategy.d.ts.map +1 -1
- package/dist/cli/deploy-target.d.ts.map +1 -1
- package/dist/cli/generated-artifacts.d.ts.map +1 -1
- package/dist/cli/help-pages/pages/account.d.ts.map +1 -1
- package/dist/cli/help-pages/pages/core.d.ts.map +1 -1
- package/dist/cli/help-pages/pages/misc.d.ts.map +1 -1
- package/dist/cli/help-pages/pages/previews.d.ts.map +1 -1
- package/dist/cli/help-pages/pages/productions.d.ts.map +1 -1
- package/dist/cli/help-pages/render.d.ts +1 -1
- package/dist/cli/help-pages/render.d.ts.map +1 -1
- package/dist/cli/help-pages/shared.d.ts.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/preview-bindings.d.ts.map +1 -1
- package/dist/cli/preview.d.ts.map +1 -1
- package/dist/cli/ui.d.ts.map +1 -1
- package/dist/cli/workspace-build-guard.d.ts.map +1 -1
- package/dist/cloudflare/account-core.d.ts.map +1 -1
- package/dist/cloudflare/account-resources.d.ts.map +1 -1
- package/dist/cloudflare/account-status.d.ts.map +1 -1
- package/dist/cloudflare/account-workers.d.ts.map +1 -1
- package/dist/cloudflare/api.d.ts.map +1 -1
- package/dist/cloudflare/auth.d.ts.map +1 -1
- package/dist/cloudflare/index.d.ts +6 -6
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +6 -6
- package/dist/cloudflare/kv-namespace.d.ts.map +1 -1
- package/dist/cloudflare/preferences.d.ts.map +1 -1
- package/dist/cloudflare/preview-registry-cache.d.ts.map +1 -1
- package/dist/cloudflare/preview-registry-inference.d.ts +1 -1
- package/dist/cloudflare/preview-registry-inference.d.ts.map +1 -1
- package/dist/cloudflare/preview-registry-shape.d.ts +1 -1
- package/dist/cloudflare/preview-registry-shape.d.ts.map +1 -1
- package/dist/cloudflare/preview-registry-types.d.ts +1 -1
- package/dist/cloudflare/preview-registry-types.d.ts.map +1 -1
- package/dist/cloudflare/preview-registry.d.ts +1 -1
- package/dist/cloudflare/preview-registry.d.ts.map +1 -1
- package/dist/cloudflare/preview-urls.d.ts.map +1 -1
- package/dist/cloudflare/registry-schema.d.ts.map +1 -1
- package/dist/cloudflare/remote-config.d.ts.map +1 -1
- package/dist/cloudflare/tokens.d.ts.map +1 -1
- package/dist/cloudflare/usage.d.ts.map +1 -1
- package/dist/config/binding-resolution-helpers.d.ts.map +1 -1
- package/dist/config/define.d.ts +1 -1
- package/dist/config/define.d.ts.map +1 -1
- package/dist/config/deploy-resources.d.ts +2 -2
- package/dist/config/deploy-resources.d.ts.map +1 -1
- package/dist/config/env-vars.d.ts.map +1 -1
- package/dist/config/framework-providers.d.ts.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/preview-resources.d.ts.map +1 -1
- package/dist/config/preview.d.ts +1 -3
- package/dist/config/preview.d.ts.map +1 -1
- package/dist/config/ref.d.ts +1 -1
- package/dist/config/ref.d.ts.map +1 -1
- package/dist/config/resolve-phased.d.ts.map +1 -1
- package/dist/config/resource-resolution.d.ts +3 -3
- package/dist/config/resource-resolution.d.ts.map +1 -1
- package/dist/config/schema-bindings.d.ts.map +1 -1
- package/dist/config/schema-build.d.ts.map +1 -1
- package/dist/config/schema-env.d.ts.map +1 -1
- package/dist/config/schema-normalization.d.ts +1 -1
- package/dist/config/schema-normalization.d.ts.map +1 -1
- package/dist/config/schema-runtime.d.ts.map +1 -1
- package/dist/config/schema-types-bindings-resources.d.ts +1 -2
- package/dist/config/schema-types-bindings-resources.d.ts.map +1 -1
- package/dist/config/schema-types-bindings.d.ts +1 -1
- package/dist/config/schema-types-bindings.d.ts.map +1 -1
- package/dist/config/schema-types-runtime.d.ts +2 -2
- package/dist/config/schema-types-runtime.d.ts.map +1 -1
- package/dist/config/schema-types.d.ts +1 -1
- package/dist/config/schema-types.d.ts.map +1 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/service-bindings-validation.d.ts.map +1 -1
- package/dist/config-entry.js +2 -2
- package/dist/{config-88sdhx3w.js → config-gphd6wx7.js} +6 -6
- package/dist/decorators/durable-object.d.ts.map +1 -1
- package/dist/decorators/index.js +1 -1
- package/dist/{deploy-8fpav2cw.js → deploy-vpcp41fw.js} +19 -19
- package/dist/{dev-nxevkfdv.js → dev-9scy9gap.js} +251 -255
- package/dist/dev-server/d1-migrations.d.ts.map +1 -1
- package/dist/dev-server/dev-server-state.d.ts +2 -2
- package/dist/dev-server/dev-server-state.d.ts.map +1 -1
- package/dist/dev-server/gateway-script.d.ts.map +1 -1
- package/dist/dev-server/miniflare-bindings.d.ts.map +1 -1
- package/dist/dev-server/miniflare-dev-config.d.ts +2 -2
- package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -1
- package/dist/dev-server/miniflare-log.d.ts.map +1 -1
- package/dist/dev-server/miniflare-worker-config.d.ts +1 -1
- package/dist/dev-server/miniflare-worker-config.d.ts.map +1 -1
- package/dist/dev-server/server-startup-helpers.d.ts +2 -2
- package/dist/dev-server/server-startup-helpers.d.ts.map +1 -1
- package/dist/dev-server/server.d.ts.map +1 -1
- package/dist/dev-server/vite-process.d.ts.map +1 -1
- package/dist/dev-server/vite-utils.d.ts.map +1 -1
- package/dist/dev-server/worker-source-watcher.d.ts.map +1 -1
- package/dist/dev-server/worker-surface-paths.d.ts +1 -1
- package/dist/dev-server/worker-surface-paths.d.ts.map +1 -1
- package/dist/{doctor-ztwx1y95.js → doctor-swfeeqny.js} +12 -12
- package/dist/{durable-object-v3gsnybk.js → durable-object-pf6gabpn.js} +1 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/{index-mg8vwqxf.js → index-04wh9bxx.js} +2 -2
- package/dist/{index-fcmj6641.js → index-13sfensw.js} +1 -1
- package/dist/{index-6sj0v0dk.js → index-1trss579.js} +1373 -1358
- package/dist/{index-3jme4hgw.js → index-2xz4etye.js} +25 -33
- package/dist/{index-q8f4kawk.js → index-34dejneh.js} +12 -2
- package/dist/{index-rzp9ejrh.js → index-3t0e51a0.js} +1 -1
- package/dist/{index-ptwem1nt.js → index-3z20d3kd.js} +160 -208
- package/dist/{index-d8bdkx2h.js → index-4xmtkg9g.js} +2 -2
- package/dist/{index-qwgr4q7s.js → index-5dkjffqz.js} +2 -14
- package/dist/{index-aqrwyy57.js → index-7m557715.js} +2 -7
- package/dist/{index-85yjb686.js → index-97rfy7n2.js} +1 -1
- package/dist/{index-11p28kxx.js → index-atwc3csp.js} +1 -1
- package/dist/{index-ft3trvra.js → index-b20xx2vk.js} +5 -19
- package/dist/{index-s3km3y34.js → index-bhxj7ff6.js} +234 -219
- package/dist/{index-3edvz3hs.js → index-by27mzra.js} +4 -4
- package/dist/{index-t9y0sve5.js → index-c4zd39tc.js} +1 -1
- package/dist/{index-69c0ed4a.js → index-dem45895.js} +6 -11
- package/dist/{index-k9x5v335.js → index-dgx495pv.js} +30 -27
- package/dist/{index-7qd7nmdd.js → index-e7xn30bh.js} +2 -2
- package/dist/{index-1d4jg11n.js → index-h7r11y4a.js} +5 -8
- package/dist/{index-vez803pp.js → index-ksqth51q.js} +2 -2
- package/dist/{index-stgn34cr.js → index-maw0jjnn.js} +0 -1
- package/dist/{index-hpwa6vsw.js → index-n4xkaymm.js} +1 -4
- package/dist/{index-dfcp9hsd.js → index-pd97hk2j.js} +64 -72
- package/dist/{index-67gr49zr.js → index-pmnb7eke.js} +3 -3
- package/dist/{index-xz18tstn.js → index-t0r7evpw.js} +9 -9
- package/dist/{index-qz6nbwpw.js → index-t23wq5js.js} +1 -1
- package/dist/{index-hjjfxv02.js → index-w8c8tjc5.js} +1 -1
- package/dist/{index-c8p4njqy.js → index-wxdcrzcp.js} +1 -5
- package/dist/{index-15fpa5tx.js → index-x0wpeb3x.js} +2 -2
- package/dist/{index-qmmqww7h.js → index-x2fd1361.js} +5 -5
- package/dist/{index-a855bdsx.js → index-y7w3x9p1.js} +1 -1
- package/dist/{index-gn5wy09x.js → index-zdd0d9qa.js} +3 -9
- package/dist/index.js +12 -12
- package/dist/{init-cwpergap.js → init-tb8jqgvr.js} +2 -2
- package/dist/{login-r5ve73kf.js → login-7vyzeqy4.js} +10 -10
- package/dist/{previews-qjj98rpp.js → previews-jfbectpc.js} +25 -22
- package/dist/{productions-gzdtge0e.js → productions-49ct4kjm.js} +21 -18
- package/dist/{remote-q59qk463.js → remote-6876k6mz.js} +3 -3
- package/dist/runtime/context-events.d.ts.map +1 -1
- package/dist/runtime/context-types.d.ts.map +1 -1
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/exports.d.ts +21 -21
- package/dist/runtime/exports.d.ts.map +1 -1
- package/dist/runtime/index.js +5 -5
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/router/index.d.ts +1 -1
- package/dist/runtime/router/index.d.ts.map +1 -1
- package/dist/runtime/router/types.d.ts.map +1 -1
- package/dist/secrets/local-secrets.d.ts.map +1 -1
- package/dist/{secrets-4w5166km.js → secrets-9ne7armr.js} +7 -7
- package/dist/shims/local-hyperdrive.d.ts.map +1 -1
- package/dist/shims/local-media-bindings.d.ts.map +1 -1
- package/dist/shims/local-worker-loader.d.ts.map +1 -1
- package/dist/sveltekit/index.d.ts.map +1 -1
- package/dist/sveltekit/index.js +12 -15
- package/dist/sveltekit/local-bindings.d.ts.map +1 -1
- package/dist/sveltekit/platform.d.ts.map +1 -1
- package/dist/test/binding-hints.d.ts +3 -4
- package/dist/test/binding-hints.d.ts.map +1 -1
- package/dist/test/cf.d.ts +1 -1
- package/dist/test/containers.d.ts.map +1 -1
- package/dist/test/email.d.ts +4 -4
- package/dist/test/email.d.ts.map +1 -1
- package/dist/test/index.js +1404 -1402
- package/dist/test/offline-bindings.d.ts.map +1 -1
- package/dist/test/queue.d.ts.map +1 -1
- package/dist/test/remote-ai.d.ts.map +1 -1
- package/dist/test/remote-cloudflare.d.ts.map +1 -1
- package/dist/test/remote-vectorize.d.ts.map +1 -1
- package/dist/test/resolve-service-bindings.d.ts.map +1 -1
- package/dist/test/scheduled.d.ts.map +1 -1
- package/dist/test/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-env.d.ts.map +1 -1
- package/dist/test/simple-context-handlers.d.ts.map +1 -1
- package/dist/test/simple-context-lifecycle.d.ts +1 -1
- package/dist/test/simple-context-lifecycle.d.ts.map +1 -1
- package/dist/test/simple-context-mfconfig.d.ts.map +1 -1
- package/dist/test/simple-context-multi-worker.d.ts +1 -1
- package/dist/test/simple-context-multi-worker.d.ts.map +1 -1
- package/dist/test/simple-context-paths.d.ts.map +1 -1
- package/dist/test/simple-context-runtime.d.ts.map +1 -1
- package/dist/test/simple-context-startup.d.ts.map +1 -1
- package/dist/test/simple-context.d.ts.map +1 -1
- package/dist/test/tail.d.ts +1 -1
- package/dist/test/tail.d.ts.map +1 -1
- package/dist/test/utilities/env.d.ts.map +1 -1
- package/dist/test/worker.d.ts.map +1 -1
- package/dist/{token-35jcdqq8.js → token-yp1qa70y.js} +4 -4
- package/dist/transform/durable-object.d.ts.map +1 -1
- package/dist/transform/worker-entrypoint.d.ts.map +1 -1
- package/dist/{types-6vc02evc.js → types-zekmbebk.js} +16 -18
- package/dist/utils/glob.d.ts.map +1 -1
- package/dist/utils/resolve-package.d.ts.map +1 -1
- package/dist/utils/send-email.d.ts.map +1 -1
- package/dist/utils/send-email.js +1 -1
- package/dist/vite/config-file.d.ts +1 -1
- package/dist/vite/config-file.d.ts.map +1 -1
- package/dist/vite/index.js +12 -12
- package/dist/vite/plugin-config-hook.d.ts.map +1 -1
- package/dist/vite/plugin-context.d.ts.map +1 -1
- package/dist/vite/plugin-durable-objects.d.ts +1 -1
- package/dist/vite/plugin-durable-objects.d.ts.map +1 -1
- package/dist/vite/plugin-programmatic.d.ts.map +1 -1
- package/dist/vite/plugin-service-bindings.d.ts.map +1 -1
- package/dist/vite/plugin-transform.d.ts.map +1 -1
- package/dist/vite/plugin.d.ts.map +1 -1
- package/dist/{worker-f2v85f7d.js → worker-706g8qeh.js} +14 -14
- package/dist/worker-entry/composed-worker.d.ts.map +1 -1
- package/dist/worker-entry/extensions.d.ts.map +1 -1
- package/dist/worker-entry/routes.d.ts.map +1 -1
- package/dist/worker-entry/surface-paths.d.ts.map +1 -1
- package/dist/{worker-entrypoint-3rmzd4c1.js → worker-entrypoint-y8s9kdcx.js} +1 -1
- package/dist/workflows/local-workflow-entrypoints.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/ai-dx4fr9jh.js +0 -107
- package/dist/{index-vhqww6tt.js → index-p93cngdp.js} +1 -1
package/dist/test/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getRemoteModeStatus,
|
|
3
3
|
isRemoteModeActive
|
|
4
|
-
} from "../index-
|
|
4
|
+
} from "../index-4xmtkg9g.js";
|
|
5
5
|
import {
|
|
6
6
|
canProceedWithTest
|
|
7
|
-
} from "../index-
|
|
7
|
+
} from "../index-x0wpeb3x.js";
|
|
8
8
|
import {
|
|
9
9
|
bundleWorkflowEntrypointScript
|
|
10
|
-
} from "../index-
|
|
11
|
-
import"../index-
|
|
10
|
+
} from "../index-pmnb7eke.js";
|
|
11
|
+
import"../index-dem45895.js";
|
|
12
12
|
import {
|
|
13
13
|
createRouteResolve,
|
|
14
14
|
invokeFetchModule,
|
|
15
15
|
matchFetchRoute,
|
|
16
16
|
resolveFetchHandler
|
|
17
|
-
} from "../index-
|
|
17
|
+
} from "../index-97rfy7n2.js";
|
|
18
18
|
import {
|
|
19
19
|
__clearTestContext,
|
|
20
20
|
__setTestContext,
|
|
21
21
|
env
|
|
22
|
-
} from "../index-
|
|
23
|
-
import"../index-
|
|
22
|
+
} from "../index-t23wq5js.js";
|
|
23
|
+
import"../index-y7w3x9p1.js";
|
|
24
24
|
import {
|
|
25
25
|
buildHyperdrivesConfig,
|
|
26
26
|
clearBundleCache,
|
|
@@ -29,27 +29,27 @@ import {
|
|
|
29
29
|
hasServiceBindings,
|
|
30
30
|
resolveDOBindings,
|
|
31
31
|
resolveServiceBindings
|
|
32
|
-
} from "../index-
|
|
33
|
-
import"../index-
|
|
32
|
+
} from "../index-dgx495pv.js";
|
|
33
|
+
import"../index-by27mzra.js";
|
|
34
34
|
import {
|
|
35
35
|
DEFAULT_DO_PATTERN,
|
|
36
36
|
findFiles
|
|
37
|
-
} from "../index-
|
|
38
|
-
import"../index-
|
|
39
|
-
import"../index-
|
|
37
|
+
} from "../index-5dkjffqz.js";
|
|
38
|
+
import"../index-7m557715.js";
|
|
39
|
+
import"../index-p93cngdp.js";
|
|
40
40
|
import {
|
|
41
41
|
createLocalHyperdrive,
|
|
42
42
|
createLocalWorkerLoaderBinding,
|
|
43
43
|
disposeLocalWorkerLoaderBindings,
|
|
44
44
|
extractBindingHints
|
|
45
|
-
} from "../index-
|
|
45
|
+
} from "../index-c4zd39tc.js";
|
|
46
46
|
import {
|
|
47
47
|
buildLocalBindingShimServiceConfig
|
|
48
48
|
} from "../index-dm9q84c7.js";
|
|
49
49
|
import {
|
|
50
50
|
buildLocalSecretWrappedBindingConfig,
|
|
51
51
|
resolveLocalSecretValuesForBindings
|
|
52
|
-
} from "../index-
|
|
52
|
+
} from "../index-13sfensw.js";
|
|
53
53
|
import {
|
|
54
54
|
BridgeClient,
|
|
55
55
|
createEmailEvent,
|
|
@@ -61,14 +61,14 @@ import {
|
|
|
61
61
|
runWithContext,
|
|
62
62
|
runWithEventContext,
|
|
63
63
|
setBindingHints
|
|
64
|
-
} from "../index-
|
|
64
|
+
} from "../index-1trss579.js";
|
|
65
65
|
import {
|
|
66
66
|
createLocalSendEmailBinding,
|
|
67
67
|
wrapEnvSendEmailBindings
|
|
68
|
-
} from "../index-
|
|
68
|
+
} from "../index-n4xkaymm.js";
|
|
69
69
|
import {
|
|
70
70
|
applyLocalDevVarsToConfig
|
|
71
|
-
} from "../index-
|
|
71
|
+
} from "../index-w8c8tjc5.js";
|
|
72
72
|
import {
|
|
73
73
|
getLocalD1DatabaseIdentifier,
|
|
74
74
|
loadConfig,
|
|
@@ -83,544 +83,23 @@ import {
|
|
|
83
83
|
normalizeSecretsStoreBinding,
|
|
84
84
|
normalizeWorkflowBinding,
|
|
85
85
|
resolveConfigPath
|
|
86
|
-
} from "../index-
|
|
86
|
+
} from "../index-pd97hk2j.js";
|
|
87
87
|
import {
|
|
88
88
|
getEffectiveAccountId,
|
|
89
89
|
getPrimaryAccount
|
|
90
|
-
} from "../index-
|
|
90
|
+
} from "../index-h7r11y4a.js";
|
|
91
91
|
import {
|
|
92
92
|
getApiToken,
|
|
93
93
|
isAuthenticated
|
|
94
|
-
} from "../index-
|
|
94
|
+
} from "../index-04wh9bxx.js";
|
|
95
95
|
import {
|
|
96
96
|
resolveConfigEnvVars
|
|
97
|
-
} from "../index-
|
|
98
|
-
import"../index-
|
|
97
|
+
} from "../index-wxdcrzcp.js";
|
|
98
|
+
import"../index-34dejneh.js";
|
|
99
99
|
import {
|
|
100
100
|
__require
|
|
101
101
|
} from "../index-37x76zdn.js";
|
|
102
102
|
|
|
103
|
-
// src/test/simple-context-durable-objects.ts
|
|
104
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
105
|
-
import { readFile } from "fs/promises";
|
|
106
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
107
|
-
|
|
108
|
-
// src/test/simple-context-gateway-script.ts
|
|
109
|
-
function buildGatewayScript(bundledCode, wrappers, nativeRpcBindingNames = []) {
|
|
110
|
-
const nativeRpcBindingsLiteral = JSON.stringify(nativeRpcBindingNames);
|
|
111
|
-
return `
|
|
112
|
-
// Bundled transport + DO classes
|
|
113
|
-
${bundledCode}
|
|
114
|
-
|
|
115
|
-
// DO Wrappers with RPC
|
|
116
|
-
${wrappers}
|
|
117
|
-
|
|
118
|
-
const __nativeRpcBindings = new Set(${nativeRpcBindingsLiteral})
|
|
119
|
-
|
|
120
|
-
// Transport encoding helper
|
|
121
|
-
const __transportEncoders = typeof transport !== 'undefined' ? transport : {}
|
|
122
|
-
|
|
123
|
-
function __encodeTransport(value) {
|
|
124
|
-
if (value === null || value === undefined) return value
|
|
125
|
-
|
|
126
|
-
// Try each encoder
|
|
127
|
-
for (const [typeName, transporter] of Object.entries(__transportEncoders)) {
|
|
128
|
-
const encoded = transporter.encode(value)
|
|
129
|
-
if (encoded !== false && encoded !== undefined) {
|
|
130
|
-
return { __transport: typeName, value: encoded }
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Recursively encode arrays and objects
|
|
135
|
-
if (Array.isArray(value)) {
|
|
136
|
-
return value.map(__encodeTransport)
|
|
137
|
-
}
|
|
138
|
-
if (typeof value === 'object') {
|
|
139
|
-
const result = {}
|
|
140
|
-
for (const [k, v] of Object.entries(value)) {
|
|
141
|
-
result[k] = __encodeTransport(v)
|
|
142
|
-
}
|
|
143
|
-
return result
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return value
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Gateway with WebSocket RPC
|
|
150
|
-
export default {
|
|
151
|
-
async fetch(request, env) {
|
|
152
|
-
if (request.headers.get('Upgrade') === 'websocket') {
|
|
153
|
-
const { 0: client, 1: server } = new WebSocketPair()
|
|
154
|
-
server.accept()
|
|
155
|
-
server.addEventListener('message', async (e) => {
|
|
156
|
-
try {
|
|
157
|
-
const m = JSON.parse(e.data)
|
|
158
|
-
if (m.t === 'rpc.call') {
|
|
159
|
-
const result = await executeRpc(env, m.method, m.params)
|
|
160
|
-
server.send(JSON.stringify({ t: 'rpc.ok', id: m.id, result }))
|
|
161
|
-
}
|
|
162
|
-
} catch (error) {
|
|
163
|
-
server.send(JSON.stringify({ t: 'rpc.err', id: 'unknown', error: { code: 'RPC_ERROR', message: error.message } }))
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
return new Response(null, { status: 101, webSocket: client })
|
|
167
|
-
}
|
|
168
|
-
return new Response('Gateway')
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function executeRpc(env, method, params) {
|
|
173
|
-
const [bindingName, ...rest] = method.split('.')
|
|
174
|
-
let op = rest.join('.')
|
|
175
|
-
const binding = env[bindingName]
|
|
176
|
-
const RAW_EMAIL = 'EmailMessage::raw'
|
|
177
|
-
if (!binding) throw new Error('Binding not found: ' + bindingName)
|
|
178
|
-
|
|
179
|
-
// Normalize namespaced op names (kv.*, r2.*, d1.*, do.*, queue.*, ai.*, var.*)
|
|
180
|
-
// down to the legacy verbs this dispatcher historically used. The bridge
|
|
181
|
-
// proxy always emits namespaced forms (see src/bridge/proxy.ts and B3 in
|
|
182
|
-
// REMAINING.md); this keeps the dispatcher backwards-compatible while the
|
|
183
|
-
// rest of the codebase converges on the namespaced convention.
|
|
184
|
-
if (op.indexOf('kv.') === 0) op = op.slice(3)
|
|
185
|
-
else if (op.indexOf('do.') === 0) {
|
|
186
|
-
const tail = op.slice(3)
|
|
187
|
-
if (tail === 'fetch') op = 'stub.fetch'
|
|
188
|
-
else if (tail === 'rpc') op = 'stub.rpc'
|
|
189
|
-
else op = tail
|
|
190
|
-
}
|
|
191
|
-
else if (op.indexOf('queue.') === 0) op = op.slice(6)
|
|
192
|
-
else if (op.indexOf('ai.') === 0) op = op.slice(3)
|
|
193
|
-
else if (op.indexOf('var.') === 0) op = op.slice(4)
|
|
194
|
-
else if (op.indexOf('d1.stmt.') === 0) op = 'prepare.' + op.slice('d1.stmt.'.length)
|
|
195
|
-
else if (op.indexOf('d1.') === 0) op = op.slice(3)
|
|
196
|
-
// r2.* and email.* keep their existing prefixes
|
|
197
|
-
|
|
198
|
-
// KV operations
|
|
199
|
-
if (op === 'get') return binding.get(params[0], params[1])
|
|
200
|
-
if (op === 'put') return binding.put(params[0], params[1], params[2])
|
|
201
|
-
if (op === 'delete') return binding.delete(params[0])
|
|
202
|
-
if (op === 'list') return binding.list(params[0])
|
|
203
|
-
if (op === 'getWithMetadata') return binding.getWithMetadata(params[0], params[1])
|
|
204
|
-
|
|
205
|
-
// R2 operations
|
|
206
|
-
if (op === 'r2.get') return binding.get(params[0], params[1])
|
|
207
|
-
if (op === 'r2.put') return binding.put(params[0], params[1], params[2])
|
|
208
|
-
if (op === 'r2.delete') return binding.delete(params[0])
|
|
209
|
-
if (op === 'r2.list') return binding.list(params[0])
|
|
210
|
-
if (op === 'r2.head' || op === 'head') return binding.head(params[0])
|
|
211
|
-
|
|
212
|
-
// D1 operations
|
|
213
|
-
if (op === 'exec') return binding.exec(params[0])
|
|
214
|
-
if (op === 'dump') return binding.dump()
|
|
215
|
-
if (op === 'batch') {
|
|
216
|
-
const stmts = params[0].map(s => {
|
|
217
|
-
const stmt = binding.prepare(s.sql)
|
|
218
|
-
return s.bindings?.length ? stmt.bind(...s.bindings) : stmt
|
|
219
|
-
})
|
|
220
|
-
return binding.batch(stmts)
|
|
221
|
-
}
|
|
222
|
-
if (op === 'prepare.run') return binding.prepare(params[0]).bind(...(params[1] || [])).run()
|
|
223
|
-
if (op === 'prepare.all') return binding.prepare(params[0]).bind(...(params[1] || [])).all()
|
|
224
|
-
if (op === 'prepare.first') return binding.prepare(params[0]).bind(...(params[1] || [])).first(params[2])
|
|
225
|
-
if (op === 'prepare.raw') return binding.prepare(params[0]).bind(...(params[1] || [])).raw({ columnNames: params[2] })
|
|
226
|
-
|
|
227
|
-
// Send email operations
|
|
228
|
-
if (op === 'email.send') {
|
|
229
|
-
return binding.send(__normalizeEmailMessage(params[0]))
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// DO operations
|
|
233
|
-
if (op === 'idFromName') {
|
|
234
|
-
return { __type: 'DOId', hex: binding.idFromName(params[0]).toString() }
|
|
235
|
-
}
|
|
236
|
-
if (op === 'stub.rpc') {
|
|
237
|
-
const [, idSerialized, rpcMethod, rpcParams] = params
|
|
238
|
-
const stub = binding.get(binding.idFromString(idSerialized.hex))
|
|
239
|
-
|
|
240
|
-
if (__nativeRpcBindings.has(bindingName) && typeof stub[rpcMethod] === 'function') {
|
|
241
|
-
let result = await stub[rpcMethod](...(rpcParams || []))
|
|
242
|
-
result = __encodeTransport(result)
|
|
243
|
-
return result
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const response = await stub.fetch(new Request('http://do/_rpc', {
|
|
247
|
-
method: 'POST',
|
|
248
|
-
headers: { 'Content-Type': 'application/json' },
|
|
249
|
-
body: JSON.stringify({ method: rpcMethod, params: rpcParams || [] })
|
|
250
|
-
}))
|
|
251
|
-
|
|
252
|
-
const payload = await response.json()
|
|
253
|
-
if (!response.ok || !payload?.ok) {
|
|
254
|
-
throw new Error(payload?.error?.message || ('DO RPC failed with status ' + response.status))
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return payload.result
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
throw new Error('Unknown operation: ' + method)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function __createEmailMessageRaw(raw) {
|
|
264
|
-
if (typeof raw === 'string' || raw instanceof ReadableStream) {
|
|
265
|
-
return raw
|
|
266
|
-
}
|
|
267
|
-
if (raw instanceof Uint8Array || raw instanceof ArrayBuffer) {
|
|
268
|
-
return new Response(raw).body
|
|
269
|
-
}
|
|
270
|
-
throw new Error('Unsupported EmailMessage raw payload')
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function __buildRawEmail(message) {
|
|
274
|
-
const lines = []
|
|
275
|
-
const messageId = '<' + Date.now() + '-' + Math.random().toString(36).slice(2) + '@devflare.dev>'
|
|
276
|
-
|
|
277
|
-
lines.push('From: ' + message.from)
|
|
278
|
-
lines.push('To: ' + (Array.isArray(message.to) ? message.to.join(', ') : message.to))
|
|
279
|
-
lines.push('Date: ' + new Date().toUTCString())
|
|
280
|
-
lines.push('Message-ID: ' + messageId)
|
|
281
|
-
|
|
282
|
-
if (message.subject) lines.push('Subject: ' + message.subject)
|
|
283
|
-
if (message.replyTo) lines.push('Reply-To: ' + String(message.replyTo))
|
|
284
|
-
if (message.cc) lines.push('Cc: ' + (Array.isArray(message.cc) ? message.cc.join(', ') : message.cc))
|
|
285
|
-
if (message.bcc) lines.push('Bcc: ' + (Array.isArray(message.bcc) ? message.bcc.join(', ') : message.bcc))
|
|
286
|
-
|
|
287
|
-
for (const [key, value] of Object.entries(message.headers || {})) {
|
|
288
|
-
lines.push(key + ': ' + value)
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
lines.push('MIME-Version: 1.0')
|
|
292
|
-
lines.push('Content-Type: ' + (message.html ? 'text/html' : 'text/plain') + '; charset=UTF-8')
|
|
293
|
-
lines.push('')
|
|
294
|
-
lines.push(String(message.html ?? message.text ?? '').replace(/\\r?\\n/g, '\\r\\n'))
|
|
295
|
-
|
|
296
|
-
return lines.join('\\r\\n')
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function __normalizeEmailMessage(message) {
|
|
300
|
-
if (!message || typeof message !== 'object' || !('from' in message) || !('to' in message)) {
|
|
301
|
-
return message
|
|
302
|
-
}
|
|
303
|
-
if ('EmailMessage::raw' in message) {
|
|
304
|
-
return message
|
|
305
|
-
}
|
|
306
|
-
if ('raw' in message && message.raw !== undefined) {
|
|
307
|
-
return {
|
|
308
|
-
from: message.from,
|
|
309
|
-
to: message.to,
|
|
310
|
-
[RAW_EMAIL]: __createEmailMessageRaw(message.raw)
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return {
|
|
314
|
-
from: message.from,
|
|
315
|
-
to: message.to,
|
|
316
|
-
[RAW_EMAIL]: __createEmailMessageRaw(__buildRawEmail(message))
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
`;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// src/test/simple-context-paths.ts
|
|
323
|
-
import { existsSync } from "fs";
|
|
324
|
-
import { createServer } from "net";
|
|
325
|
-
import { dirname, join } from "path";
|
|
326
|
-
import { fileURLToPath } from "url";
|
|
327
|
-
var DEFAULT_TRANSPORT_ENTRY_FILES = [
|
|
328
|
-
"src/transport.ts",
|
|
329
|
-
"src/transport.js",
|
|
330
|
-
"src/transport.mts",
|
|
331
|
-
"src/transport.mjs"
|
|
332
|
-
];
|
|
333
|
-
var CURRENT_PACKAGE_ROOT = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
|
|
334
|
-
function getBunRuntime() {
|
|
335
|
-
const g = globalThis;
|
|
336
|
-
if (typeof g.Bun === "object" && g.Bun !== null) {
|
|
337
|
-
return g.Bun;
|
|
338
|
-
}
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
function getCallerDirectory() {
|
|
342
|
-
const stackCallerDirectory = getStackCallerDirectory();
|
|
343
|
-
if (stackCallerDirectory) {
|
|
344
|
-
return stackCallerDirectory;
|
|
345
|
-
}
|
|
346
|
-
return process.cwd();
|
|
347
|
-
}
|
|
348
|
-
function getStackCallerDirectory() {
|
|
349
|
-
const originalPrepare = Error.prepareStackTrace;
|
|
350
|
-
Error.prepareStackTrace = (_, stack) => stack;
|
|
351
|
-
try {
|
|
352
|
-
const err = new Error;
|
|
353
|
-
const stack = err.stack;
|
|
354
|
-
for (const site of stack ?? []) {
|
|
355
|
-
const filename = site.getFileName?.();
|
|
356
|
-
if (filename && !isInsideCurrentPackage(filename) && !filename.includes("simple-context") && !filename.includes("node_modules") && !filename.includes("[") && existsSync(filename)) {
|
|
357
|
-
return dirname(filename);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
} finally {
|
|
361
|
-
Error.prepareStackTrace = originalPrepare;
|
|
362
|
-
}
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
function findPackageRoot(startDir) {
|
|
366
|
-
let currentDir = startDir;
|
|
367
|
-
while (true) {
|
|
368
|
-
if (existsSync(join(currentDir, "package.json"))) {
|
|
369
|
-
return currentDir;
|
|
370
|
-
}
|
|
371
|
-
const parentDir = dirname(currentDir);
|
|
372
|
-
if (parentDir === currentDir) {
|
|
373
|
-
return startDir;
|
|
374
|
-
}
|
|
375
|
-
currentDir = parentDir;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
function isInsideCurrentPackage(filePath) {
|
|
379
|
-
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
380
|
-
const normalizedPackageRoot = CURRENT_PACKAGE_ROOT.replace(/\\/g, "/");
|
|
381
|
-
return normalizedFilePath === normalizedPackageRoot || normalizedFilePath.startsWith(`${normalizedPackageRoot}/`);
|
|
382
|
-
}
|
|
383
|
-
async function findNearestConfig(startDir) {
|
|
384
|
-
let currentDir = startDir;
|
|
385
|
-
while (true) {
|
|
386
|
-
const configPath = await resolveConfigPath(currentDir);
|
|
387
|
-
if (configPath) {
|
|
388
|
-
return configPath;
|
|
389
|
-
}
|
|
390
|
-
const parentDir = dirname(currentDir);
|
|
391
|
-
if (parentDir === currentDir) {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
currentDir = parentDir;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
async function getAvailablePort() {
|
|
398
|
-
return await new Promise((resolvePort, reject) => {
|
|
399
|
-
const server = createServer();
|
|
400
|
-
server.once("error", reject);
|
|
401
|
-
server.listen(0, "127.0.0.1", () => {
|
|
402
|
-
const address = server.address();
|
|
403
|
-
if (!address || typeof address === "string") {
|
|
404
|
-
server.close(() => reject(new Error("Could not determine an available port")));
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const { port } = address;
|
|
408
|
-
server.close((error) => {
|
|
409
|
-
if (error) {
|
|
410
|
-
reject(error);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
resolvePort(port);
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
function resolveTransportFile(configDir, configuredPath) {
|
|
419
|
-
if (typeof configuredPath === "string") {
|
|
420
|
-
return configuredPath;
|
|
421
|
-
}
|
|
422
|
-
if (configuredPath === null) {
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
for (const defaultEntry of DEFAULT_TRANSPORT_ENTRY_FILES) {
|
|
426
|
-
if (existsSync(join(configDir, defaultEntry))) {
|
|
427
|
-
return defaultEntry;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// src/test/simple-context-durable-objects.ts
|
|
434
|
-
function findExportedClasses(code) {
|
|
435
|
-
const classes = [];
|
|
436
|
-
const classPattern = /export\s+class\s+(\w+)/g;
|
|
437
|
-
let match;
|
|
438
|
-
while ((match = classPattern.exec(code)) !== null) {
|
|
439
|
-
classes.push(match[1]);
|
|
440
|
-
}
|
|
441
|
-
return classes;
|
|
442
|
-
}
|
|
443
|
-
function classSupportsNativeDurableObjectRpc(code, className) {
|
|
444
|
-
const nativeRpcPattern = new RegExp(`export\\s+class\\s+${className}\\s+extends\\s+DurableObject\\b`);
|
|
445
|
-
return nativeRpcPattern.test(code);
|
|
446
|
-
}
|
|
447
|
-
function toGeneratedIdentifier(value) {
|
|
448
|
-
const normalized = value.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
449
|
-
return /^[A-Za-z_$]/.test(normalized) ? normalized : `_${normalized}`;
|
|
450
|
-
}
|
|
451
|
-
async function discoverLocalDurableObjectClasses(config, configDir) {
|
|
452
|
-
const classToFilePath = new Map;
|
|
453
|
-
const doPatternConfig = config.files?.durableObjects;
|
|
454
|
-
const doPattern = typeof doPatternConfig === "string" ? doPatternConfig : DEFAULT_DO_PATTERN;
|
|
455
|
-
if (doPatternConfig === false) {
|
|
456
|
-
return classToFilePath;
|
|
457
|
-
}
|
|
458
|
-
const doFiles = await findFiles(doPattern, { cwd: configDir });
|
|
459
|
-
for (const filePath of doFiles) {
|
|
460
|
-
try {
|
|
461
|
-
const code = await readFile(filePath, "utf-8");
|
|
462
|
-
const classNames = findExportedClasses(code);
|
|
463
|
-
for (const className of classNames) {
|
|
464
|
-
classToFilePath.set(className, {
|
|
465
|
-
filePath,
|
|
466
|
-
nativeRpc: classSupportsNativeDurableObjectRpc(code, className)
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
} catch {}
|
|
470
|
-
}
|
|
471
|
-
return classToFilePath;
|
|
472
|
-
}
|
|
473
|
-
async function resolveLocalDurableObjects(config, configDir) {
|
|
474
|
-
const doConfig = {};
|
|
475
|
-
const doInfos = [];
|
|
476
|
-
const classToFilePath = await discoverLocalDurableObjectClasses(config, configDir);
|
|
477
|
-
for (const [name, rawDoInfo] of Object.entries(config.bindings?.durableObjects ?? {})) {
|
|
478
|
-
const doInfo = normalizeDOBinding(rawDoInfo);
|
|
479
|
-
if (doInfo.__ref) {
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
let scriptPath;
|
|
483
|
-
let nativeRpc = false;
|
|
484
|
-
if (doInfo.kind === "cross-worker" && doInfo.scriptName) {
|
|
485
|
-
scriptPath = join2(configDir, "src", doInfo.scriptName);
|
|
486
|
-
try {
|
|
487
|
-
const code = await readFile(scriptPath, "utf-8");
|
|
488
|
-
nativeRpc = classSupportsNativeDurableObjectRpc(code, doInfo.className);
|
|
489
|
-
} catch {
|
|
490
|
-
nativeRpc = false;
|
|
491
|
-
}
|
|
492
|
-
} else {
|
|
493
|
-
const discoveredClass = classToFilePath.get(doInfo.className);
|
|
494
|
-
if (!discoveredClass) {
|
|
495
|
-
throw new Error(`Durable object ${name} (className: '${doInfo.className}') not found.
|
|
496
|
-
` + `Either:
|
|
497
|
-
` + ` 1. Set files.durableObjects pattern in config (e.g., 'src/do.*.ts')
|
|
498
|
-
` + ` 2. Use explicit scriptName: { className: '${doInfo.className}', scriptName: 'do.file.ts' }`);
|
|
499
|
-
}
|
|
500
|
-
scriptPath = discoveredClass.filePath;
|
|
501
|
-
nativeRpc = discoveredClass.nativeRpc;
|
|
502
|
-
}
|
|
503
|
-
const runtimeClassName = nativeRpc ? doInfo.className : `__Devflare${toGeneratedIdentifier(name)}RpcWrapper`;
|
|
504
|
-
doConfig[name] = runtimeClassName;
|
|
505
|
-
doInfos.push({
|
|
506
|
-
name,
|
|
507
|
-
className: doInfo.className,
|
|
508
|
-
scriptPath,
|
|
509
|
-
nativeRpc,
|
|
510
|
-
runtimeClassName
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
return {
|
|
514
|
-
doConfig,
|
|
515
|
-
doInfos
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
function buildWrapperCode(doInfos) {
|
|
519
|
-
return doInfos.filter((info) => !info.nativeRpc).map((info) => `
|
|
520
|
-
export class ${info.runtimeClassName} {
|
|
521
|
-
constructor(state, env) {
|
|
522
|
-
this.__instance = new ${info.className}(state, env)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
async fetch(request) {
|
|
526
|
-
const url = new URL(request.url)
|
|
527
|
-
if (request.method !== 'POST' || url.pathname !== '/_rpc') {
|
|
528
|
-
return new Response('Not found', { status: 404 })
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
try {
|
|
532
|
-
const payload = await request.json()
|
|
533
|
-
const method = payload?.method
|
|
534
|
-
const params = Array.isArray(payload?.params) ? payload.params : []
|
|
535
|
-
const target = this.__instance?.[method]
|
|
536
|
-
|
|
537
|
-
if (typeof target !== 'function') {
|
|
538
|
-
return new Response(JSON.stringify({
|
|
539
|
-
ok: false,
|
|
540
|
-
error: { message: 'Method not found: ' + String(method) }
|
|
541
|
-
}), {
|
|
542
|
-
status: 404,
|
|
543
|
-
headers: { 'Content-Type': 'application/json' }
|
|
544
|
-
})
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
let result = await target.apply(this.__instance, params)
|
|
548
|
-
result = __encodeTransport(result)
|
|
549
|
-
|
|
550
|
-
return new Response(JSON.stringify({ ok: true, result }), {
|
|
551
|
-
headers: { 'Content-Type': 'application/json' }
|
|
552
|
-
})
|
|
553
|
-
} catch (error) {
|
|
554
|
-
return new Response(JSON.stringify({
|
|
555
|
-
ok: false,
|
|
556
|
-
error: { message: error instanceof Error ? error.message : String(error) }
|
|
557
|
-
}), {
|
|
558
|
-
status: 500,
|
|
559
|
-
headers: { 'Content-Type': 'application/json' }
|
|
560
|
-
})
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}`.trim()).join(`
|
|
564
|
-
|
|
565
|
-
`);
|
|
566
|
-
}
|
|
567
|
-
async function bundleDurableObjectModules(configDir, doInfos, transportFile) {
|
|
568
|
-
const virtualImports = [];
|
|
569
|
-
const virtualExports = [];
|
|
570
|
-
if (transportFile) {
|
|
571
|
-
const transportPath = join2(configDir, transportFile);
|
|
572
|
-
virtualImports.push(`import { transport } from '${transportPath.replace(/\\/g, "/")}'`);
|
|
573
|
-
virtualExports.push("export { transport }");
|
|
574
|
-
}
|
|
575
|
-
for (const info of doInfos) {
|
|
576
|
-
virtualImports.push(`import { ${info.className} } from '${info.scriptPath.replace(/\\/g, "/")}'`);
|
|
577
|
-
virtualExports.push(`export { ${info.className} }`);
|
|
578
|
-
}
|
|
579
|
-
if (virtualImports.length === 0) {
|
|
580
|
-
return "";
|
|
581
|
-
}
|
|
582
|
-
const virtualEntry = [...virtualImports, "", ...virtualExports].join(`
|
|
583
|
-
`);
|
|
584
|
-
const virtualPath = join2(configDir, ".devflare", "__test_entry.ts");
|
|
585
|
-
mkdirSync(dirname2(virtualPath), { recursive: true });
|
|
586
|
-
writeFileSync(virtualPath, virtualEntry);
|
|
587
|
-
const bun = getBunRuntime();
|
|
588
|
-
if (!bun) {
|
|
589
|
-
throw new Error("Bun runtime is required for createTestContext with Durable Objects");
|
|
590
|
-
}
|
|
591
|
-
const result = await bun.build({
|
|
592
|
-
entrypoints: [virtualPath],
|
|
593
|
-
target: "browser",
|
|
594
|
-
format: "esm",
|
|
595
|
-
minify: false,
|
|
596
|
-
external: ["cloudflare:workers", "cloudflare:*"]
|
|
597
|
-
});
|
|
598
|
-
if (!result.success) {
|
|
599
|
-
throw new Error(`Failed to bundle test entry: ${result.logs.join(`
|
|
600
|
-
`)}`);
|
|
601
|
-
}
|
|
602
|
-
return await result.outputs[0].text();
|
|
603
|
-
}
|
|
604
|
-
async function buildDurableObjectGateway(config, configDir, transportFile) {
|
|
605
|
-
const workflowEntrypointScript = await bundleWorkflowEntrypointScript(config, configDir);
|
|
606
|
-
if (!config.bindings?.durableObjects) {
|
|
607
|
-
return {
|
|
608
|
-
script: buildGatewayScript(workflowEntrypointScript, "")
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
const { doConfig, doInfos } = await resolveLocalDurableObjects(config, configDir);
|
|
612
|
-
const bundledCode = await bundleDurableObjectModules(configDir, doInfos, transportFile);
|
|
613
|
-
const wrapperCode = buildWrapperCode(doInfos);
|
|
614
|
-
const entrypointCode = [workflowEntrypointScript, bundledCode].filter(Boolean).join(`
|
|
615
|
-
|
|
616
|
-
`);
|
|
617
|
-
const nativeRpcBindingNames = doInfos.filter((info) => info.nativeRpc).map((info) => info.name);
|
|
618
|
-
return {
|
|
619
|
-
durableObjects: doConfig,
|
|
620
|
-
script: buildGatewayScript(entrypointCode, wrapperCode, nativeRpcBindingNames)
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
|
|
624
103
|
// src/test/remote-cloudflare.ts
|
|
625
104
|
function createRemoteCloudflareClient(accountId) {
|
|
626
105
|
let resolvedAccountId = null;
|
|
@@ -1248,572 +727,1093 @@ function createMockQueue() {
|
|
|
1248
727
|
}
|
|
1249
728
|
return response;
|
|
1250
729
|
},
|
|
1251
|
-
_getMessages() {
|
|
1252
|
-
return messages;
|
|
730
|
+
_getMessages() {
|
|
731
|
+
return messages;
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
// src/test/utilities/platform.ts
|
|
736
|
+
function createMockRateLimit(options = {}) {
|
|
737
|
+
const limit = options.limit ?? Number.MAX_SAFE_INTEGER;
|
|
738
|
+
const periodMs = (options.period ?? 60) * 1000;
|
|
739
|
+
const windows = new Map;
|
|
740
|
+
return {
|
|
741
|
+
async limit({ key }) {
|
|
742
|
+
const now = Date.now();
|
|
743
|
+
const existing = windows.get(key);
|
|
744
|
+
if (!existing || existing.resetAt <= now) {
|
|
745
|
+
windows.set(key, { count: 1, resetAt: now + periodMs });
|
|
746
|
+
return { success: limit >= 1 };
|
|
747
|
+
}
|
|
748
|
+
existing.count += 1;
|
|
749
|
+
return { success: existing.count <= limit };
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function createMockVersionMetadata(metadata = {}) {
|
|
754
|
+
return {
|
|
755
|
+
id: metadata.id ?? "devflare-local-version",
|
|
756
|
+
tag: metadata.tag ?? "local",
|
|
757
|
+
timestamp: metadata.timestamp ?? "1970-01-01T00:00:00.000Z"
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
function createMockSecretsStoreSecret(value) {
|
|
761
|
+
return {
|
|
762
|
+
async get() {
|
|
763
|
+
return value;
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function createMockHyperdrive(connectionString) {
|
|
768
|
+
return createLocalHyperdrive(connectionString);
|
|
769
|
+
}
|
|
770
|
+
function createDefaultWorkerStub() {
|
|
771
|
+
return {
|
|
772
|
+
getEntrypoint() {
|
|
773
|
+
throw new Error("Mock WorkerLoader stub has no entrypoint. Pass createMockWorkerLoader({ stub }) for behavior.");
|
|
774
|
+
},
|
|
775
|
+
getDurableObjectClass() {
|
|
776
|
+
throw new Error("Mock WorkerLoader stub cannot materialise a Durable Object class. " + "getDurableObjectClass() returns an opaque facet-spawning reference that workerd " + "only exposes inside the runtime. Pass createMockWorkerLoader({ stub }) to inject a stub, " + "or use createTestContext() (a real Miniflare worker) for Durable Object behavior.");
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function createMockWorkerLoader(options = {}) {
|
|
781
|
+
const stub = options.stub ?? createDefaultWorkerStub();
|
|
782
|
+
return {
|
|
783
|
+
get(_name, _getCode) {
|
|
784
|
+
return stub;
|
|
785
|
+
},
|
|
786
|
+
load(_code) {
|
|
787
|
+
return stub;
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function defaultMTLSCertificateHandler() {
|
|
792
|
+
throw new Error("Mock mTLS Certificate Fetcher has no handler. Pass createMockMTLSCertificate(handler) for behavior.");
|
|
793
|
+
}
|
|
794
|
+
function createMockMTLSCertificate(handler = defaultMTLSCertificateHandler) {
|
|
795
|
+
return {
|
|
796
|
+
async fetch(input, init) {
|
|
797
|
+
return handler(input, init);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function createMockDispatchNamespace(options = {}) {
|
|
802
|
+
return {
|
|
803
|
+
get(name) {
|
|
804
|
+
const worker = options.workers?.[name];
|
|
805
|
+
if (!worker) {
|
|
806
|
+
throw new Error(`Mock DispatchNamespace has no worker named "${name}".`);
|
|
807
|
+
}
|
|
808
|
+
return typeof worker === "function" ? createMockMTLSCertificate(worker) : worker;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
// src/test/utilities/workflows.ts
|
|
813
|
+
function createMockWorkflowInstance(id, options = {}) {
|
|
814
|
+
let status = options.status ?? "queued";
|
|
815
|
+
let output = options.output;
|
|
816
|
+
let error = options.error;
|
|
817
|
+
return {
|
|
818
|
+
id,
|
|
819
|
+
async pause() {
|
|
820
|
+
status = "paused";
|
|
821
|
+
},
|
|
822
|
+
async resume() {
|
|
823
|
+
status = "running";
|
|
824
|
+
},
|
|
825
|
+
async terminate() {
|
|
826
|
+
status = "terminated";
|
|
827
|
+
},
|
|
828
|
+
async restart() {
|
|
829
|
+
status = "queued";
|
|
830
|
+
error = undefined;
|
|
831
|
+
output = undefined;
|
|
832
|
+
},
|
|
833
|
+
async status() {
|
|
834
|
+
return {
|
|
835
|
+
status,
|
|
836
|
+
...error && { error },
|
|
837
|
+
...output !== undefined && { output }
|
|
838
|
+
};
|
|
839
|
+
},
|
|
840
|
+
async sendEvent(_event) {}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function createMockWorkflow(options = {}) {
|
|
844
|
+
const instances = new Map;
|
|
845
|
+
let sequence = 0;
|
|
846
|
+
for (const [id, instanceOptions] of Object.entries(options.instances ?? {})) {
|
|
847
|
+
instances.set(id, createMockWorkflowInstance(id, instanceOptions));
|
|
848
|
+
}
|
|
849
|
+
const createInstance = (id) => {
|
|
850
|
+
if (instances.has(id)) {
|
|
851
|
+
throw new Error(`Mock Workflow already has an instance named "${id}".`);
|
|
852
|
+
}
|
|
853
|
+
const instance = createMockWorkflowInstance(id);
|
|
854
|
+
instances.set(id, instance);
|
|
855
|
+
return instance;
|
|
856
|
+
};
|
|
857
|
+
return {
|
|
858
|
+
async get(id) {
|
|
859
|
+
const instance = instances.get(id);
|
|
860
|
+
if (!instance) {
|
|
861
|
+
throw new Error(`Mock Workflow has no instance named "${id}".`);
|
|
862
|
+
}
|
|
863
|
+
return instance;
|
|
864
|
+
},
|
|
865
|
+
async create(options2) {
|
|
866
|
+
const id = options2?.id ?? `mock-workflow-${++sequence}`;
|
|
867
|
+
return createInstance(id);
|
|
868
|
+
},
|
|
869
|
+
async createBatch(batch) {
|
|
870
|
+
return batch.map((options2) => {
|
|
871
|
+
const id = options2.id ?? `mock-workflow-${++sequence}`;
|
|
872
|
+
return createInstance(id);
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function createMockPipeline() {
|
|
878
|
+
const records = [];
|
|
879
|
+
return {
|
|
880
|
+
async send(batch) {
|
|
881
|
+
records.push(...batch);
|
|
882
|
+
},
|
|
883
|
+
_getRecords() {
|
|
884
|
+
return [...records];
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
// src/test/utilities/media.ts
|
|
889
|
+
function createEmptyImageStream() {
|
|
890
|
+
return new ReadableStream({
|
|
891
|
+
start(controller) {
|
|
892
|
+
controller.close();
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
function createMockImageTransformationResult(response) {
|
|
897
|
+
return {
|
|
898
|
+
response() {
|
|
899
|
+
return response.clone();
|
|
900
|
+
},
|
|
901
|
+
contentType() {
|
|
902
|
+
return response.headers.get("Content-Type") ?? "image/png";
|
|
903
|
+
},
|
|
904
|
+
image() {
|
|
905
|
+
const cloned = response.clone();
|
|
906
|
+
return cloned.body ?? createEmptyImageStream();
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function createMockImageTransformer(response) {
|
|
911
|
+
const transformer = {
|
|
912
|
+
transform(_transform) {
|
|
913
|
+
return transformer;
|
|
914
|
+
},
|
|
915
|
+
draw(_image, _options) {
|
|
916
|
+
return transformer;
|
|
917
|
+
},
|
|
918
|
+
async output(_options) {
|
|
919
|
+
return createMockImageTransformationResult(response);
|
|
1253
920
|
}
|
|
1254
921
|
};
|
|
922
|
+
return transformer;
|
|
1255
923
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
const periodMs = (options.period ?? 60) * 1000;
|
|
1260
|
-
const windows = new Map;
|
|
1261
|
-
return {
|
|
1262
|
-
async limit({ key }) {
|
|
1263
|
-
const now = Date.now();
|
|
1264
|
-
const existing = windows.get(key);
|
|
1265
|
-
if (!existing || existing.resetAt <= now) {
|
|
1266
|
-
windows.set(key, { count: 1, resetAt: now + periodMs });
|
|
1267
|
-
return { success: limit >= 1 };
|
|
1268
|
-
}
|
|
1269
|
-
existing.count += 1;
|
|
1270
|
-
return { success: existing.count <= limit };
|
|
1271
|
-
}
|
|
924
|
+
function createMockHostedImagesBinding() {
|
|
925
|
+
const unsupported = () => {
|
|
926
|
+
throw new Error("Mock Images hosted API is not implemented. Pass a custom ImagesBinding through createMockEnv({ images }) if your test needs hosted image behavior.");
|
|
1272
927
|
};
|
|
1273
|
-
}
|
|
1274
|
-
function createMockVersionMetadata(metadata = {}) {
|
|
1275
928
|
return {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
929
|
+
image(_imageId) {
|
|
930
|
+
return {
|
|
931
|
+
details: unsupported,
|
|
932
|
+
bytes: unsupported,
|
|
933
|
+
update: unsupported,
|
|
934
|
+
delete: unsupported
|
|
935
|
+
};
|
|
936
|
+
},
|
|
937
|
+
upload: unsupported,
|
|
938
|
+
list: unsupported
|
|
1279
939
|
};
|
|
1280
940
|
}
|
|
1281
|
-
function
|
|
941
|
+
function createMockImagesBinding(options = {}) {
|
|
942
|
+
const response = options.response ?? new Response("", {
|
|
943
|
+
headers: { "Content-Type": "image/png" }
|
|
944
|
+
});
|
|
945
|
+
const info = options.info ?? {
|
|
946
|
+
format: response.headers.get("Content-Type") ?? "image/png",
|
|
947
|
+
fileSize: 0,
|
|
948
|
+
width: 0,
|
|
949
|
+
height: 0
|
|
950
|
+
};
|
|
1282
951
|
return {
|
|
1283
|
-
async
|
|
1284
|
-
return
|
|
1285
|
-
}
|
|
952
|
+
async info(_stream, _options) {
|
|
953
|
+
return info;
|
|
954
|
+
},
|
|
955
|
+
input(_stream, _options) {
|
|
956
|
+
return createMockImageTransformer(response);
|
|
957
|
+
},
|
|
958
|
+
hosted: createMockHostedImagesBinding()
|
|
1286
959
|
};
|
|
1287
960
|
}
|
|
1288
|
-
function
|
|
1289
|
-
return
|
|
961
|
+
function createEmptyMediaStream() {
|
|
962
|
+
return new ReadableStream({
|
|
963
|
+
start(controller) {
|
|
964
|
+
controller.close();
|
|
965
|
+
}
|
|
966
|
+
});
|
|
1290
967
|
}
|
|
1291
|
-
function
|
|
968
|
+
function createMockMediaTransformationResult(response) {
|
|
1292
969
|
return {
|
|
1293
|
-
|
|
1294
|
-
|
|
970
|
+
async media() {
|
|
971
|
+
const cloned = response.clone();
|
|
972
|
+
return cloned.body ?? createEmptyMediaStream();
|
|
1295
973
|
},
|
|
1296
|
-
|
|
1297
|
-
|
|
974
|
+
async response() {
|
|
975
|
+
return response.clone();
|
|
976
|
+
},
|
|
977
|
+
async contentType() {
|
|
978
|
+
return response.headers.get("Content-Type") ?? "video/mp4";
|
|
1298
979
|
}
|
|
1299
980
|
};
|
|
1300
981
|
}
|
|
1301
|
-
function
|
|
1302
|
-
const
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
982
|
+
function createMockMediaTransformer(response) {
|
|
983
|
+
const transformer = {
|
|
984
|
+
transform(_transform) {
|
|
985
|
+
return {
|
|
986
|
+
output(_output) {
|
|
987
|
+
return createMockMediaTransformationResult(response);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
1306
990
|
},
|
|
1307
|
-
|
|
1308
|
-
return
|
|
991
|
+
output(_output) {
|
|
992
|
+
return createMockMediaTransformationResult(response);
|
|
1309
993
|
}
|
|
1310
994
|
};
|
|
995
|
+
return transformer;
|
|
1311
996
|
}
|
|
1312
|
-
function
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1315
|
-
|
|
997
|
+
function createMockMediaBinding(options = {}) {
|
|
998
|
+
const response = options.response ?? new Response("", {
|
|
999
|
+
headers: { "Content-Type": "video/mp4" }
|
|
1000
|
+
});
|
|
1316
1001
|
return {
|
|
1317
|
-
|
|
1318
|
-
return
|
|
1002
|
+
input(_media) {
|
|
1003
|
+
return createMockMediaTransformer(response);
|
|
1319
1004
|
}
|
|
1320
1005
|
};
|
|
1321
1006
|
}
|
|
1322
|
-
|
|
1007
|
+
// src/test/utilities/artifacts.ts
|
|
1008
|
+
function createArtifactTimestamp() {
|
|
1009
|
+
return new Date("2026-04-26T00:00:00.000Z").toISOString();
|
|
1010
|
+
}
|
|
1011
|
+
function createArtifactsRepoInfo(name, options = {}) {
|
|
1012
|
+
const now = createArtifactTimestamp();
|
|
1323
1013
|
return {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1014
|
+
id: `repo-${name}`,
|
|
1015
|
+
name,
|
|
1016
|
+
description: options.description ?? null,
|
|
1017
|
+
defaultBranch: options.defaultBranch ?? "main",
|
|
1018
|
+
createdAt: now,
|
|
1019
|
+
updatedAt: now,
|
|
1020
|
+
lastPushAt: null,
|
|
1021
|
+
source: options.source ?? null,
|
|
1022
|
+
readOnly: options.readOnly ?? false,
|
|
1023
|
+
remote: `https://example.com/artifacts/default/${name}.git`
|
|
1331
1024
|
};
|
|
1332
1025
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1026
|
+
function isArtifactsBinding(value) {
|
|
1027
|
+
return typeof value.create === "function";
|
|
1028
|
+
}
|
|
1029
|
+
function createMockArtifacts(options = {}) {
|
|
1030
|
+
const repos = new Map;
|
|
1031
|
+
const tokens = new Map;
|
|
1032
|
+
const addRepo = (info) => {
|
|
1033
|
+
repos.set(info.name, info);
|
|
1034
|
+
if (!tokens.has(info.name)) {
|
|
1035
|
+
tokens.set(info.name, []);
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
for (const repo of options.repos ?? []) {
|
|
1039
|
+
addRepo({
|
|
1040
|
+
...createArtifactsRepoInfo(repo.name),
|
|
1041
|
+
...repo
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
const createToken = (repoName, scope = "write", ttl = 86400) => {
|
|
1045
|
+
const existing = tokens.get(repoName) ?? [];
|
|
1046
|
+
const id = `token-${repoName}-${existing.length + 1}`;
|
|
1047
|
+
const expiresAt = new Date(Date.parse(createArtifactTimestamp()) + ttl * 1000).toISOString();
|
|
1048
|
+
const token = {
|
|
1049
|
+
id,
|
|
1050
|
+
scope,
|
|
1051
|
+
state: "active",
|
|
1052
|
+
createdAt: createArtifactTimestamp(),
|
|
1053
|
+
expiresAt
|
|
1054
|
+
};
|
|
1055
|
+
tokens.set(repoName, [...existing, token]);
|
|
1056
|
+
return {
|
|
1057
|
+
id,
|
|
1058
|
+
plaintext: `${id}-plaintext`,
|
|
1059
|
+
scope,
|
|
1060
|
+
expiresAt
|
|
1061
|
+
};
|
|
1062
|
+
};
|
|
1063
|
+
const createRepoHandle = (info) => ({
|
|
1064
|
+
...info,
|
|
1065
|
+
async createToken(scope, ttl) {
|
|
1066
|
+
return createToken(info.name, scope, ttl);
|
|
1342
1067
|
},
|
|
1343
|
-
async
|
|
1344
|
-
|
|
1068
|
+
async listTokens() {
|
|
1069
|
+
const repoTokens = tokens.get(info.name) ?? [];
|
|
1070
|
+
return {
|
|
1071
|
+
tokens: repoTokens,
|
|
1072
|
+
total: repoTokens.length
|
|
1073
|
+
};
|
|
1345
1074
|
},
|
|
1346
|
-
async
|
|
1347
|
-
|
|
1075
|
+
async revokeToken(tokenOrId) {
|
|
1076
|
+
const repoTokens = tokens.get(info.name) ?? [];
|
|
1077
|
+
const index = repoTokens.findIndex((token) => token.id === tokenOrId);
|
|
1078
|
+
if (index === -1) {
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
repoTokens[index] = {
|
|
1082
|
+
...repoTokens[index],
|
|
1083
|
+
state: "revoked"
|
|
1084
|
+
};
|
|
1085
|
+
tokens.set(info.name, repoTokens);
|
|
1086
|
+
return true;
|
|
1348
1087
|
},
|
|
1349
|
-
async
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1088
|
+
async fork(name, forkOptions) {
|
|
1089
|
+
return createRepo(name, {
|
|
1090
|
+
description: forkOptions?.description ?? info.description ?? undefined,
|
|
1091
|
+
readOnly: forkOptions?.readOnly ?? info.readOnly,
|
|
1092
|
+
setDefaultBranch: info.defaultBranch,
|
|
1093
|
+
source: `artifacts:default/${info.name}`
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
const createRepo = async (name, createOptions = {}) => {
|
|
1098
|
+
const info = createArtifactsRepoInfo(name, {
|
|
1099
|
+
description: createOptions.description,
|
|
1100
|
+
readOnly: createOptions.readOnly,
|
|
1101
|
+
defaultBranch: createOptions.setDefaultBranch,
|
|
1102
|
+
source: createOptions.source
|
|
1103
|
+
});
|
|
1104
|
+
addRepo(info);
|
|
1105
|
+
const token = createToken(name);
|
|
1106
|
+
return {
|
|
1107
|
+
id: info.id,
|
|
1108
|
+
name: info.name,
|
|
1109
|
+
description: info.description,
|
|
1110
|
+
defaultBranch: info.defaultBranch,
|
|
1111
|
+
remote: info.remote,
|
|
1112
|
+
token: token.plaintext,
|
|
1113
|
+
tokenExpiresAt: token.expiresAt
|
|
1114
|
+
};
|
|
1115
|
+
};
|
|
1116
|
+
return {
|
|
1117
|
+
create: createRepo,
|
|
1118
|
+
async get(name) {
|
|
1119
|
+
const repo = repos.get(name);
|
|
1120
|
+
return repo ? createRepoHandle(repo) : null;
|
|
1121
|
+
},
|
|
1122
|
+
async import(params) {
|
|
1123
|
+
return createRepo(params.target.name, {
|
|
1124
|
+
description: params.target.opts?.description,
|
|
1125
|
+
readOnly: params.target.opts?.readOnly,
|
|
1126
|
+
source: params.source.url
|
|
1127
|
+
});
|
|
1353
1128
|
},
|
|
1354
|
-
async
|
|
1129
|
+
async list(opts) {
|
|
1130
|
+
const limit = opts?.limit ?? 50;
|
|
1131
|
+
const repoList = Array.from(repos.values()).slice(0, limit).map((repo) => {
|
|
1132
|
+
const { remote: _remote, ...rest } = repo;
|
|
1133
|
+
return rest;
|
|
1134
|
+
});
|
|
1355
1135
|
return {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
...
|
|
1136
|
+
repos: repoList,
|
|
1137
|
+
total: repos.size,
|
|
1138
|
+
...repos.size > repoList.length && { cursor: String(repoList.length) }
|
|
1359
1139
|
};
|
|
1360
1140
|
},
|
|
1361
|
-
async
|
|
1141
|
+
async delete(name) {
|
|
1142
|
+
tokens.delete(name);
|
|
1143
|
+
return repos.delete(name);
|
|
1144
|
+
}
|
|
1362
1145
|
};
|
|
1363
1146
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1147
|
+
// src/test/utilities/env.ts
|
|
1148
|
+
function createMockEnv(options = {}) {
|
|
1149
|
+
const env2 = {};
|
|
1150
|
+
if (options.kv) {
|
|
1151
|
+
for (const name of options.kv) {
|
|
1152
|
+
env2[name] = createMockKV();
|
|
1153
|
+
}
|
|
1369
1154
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1155
|
+
if (options.d1) {
|
|
1156
|
+
for (const name of options.d1) {
|
|
1157
|
+
env2[name] = createMockD1();
|
|
1373
1158
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
return {
|
|
1379
|
-
async get(id) {
|
|
1380
|
-
const instance = instances.get(id);
|
|
1381
|
-
if (!instance) {
|
|
1382
|
-
throw new Error(`Mock Workflow has no instance named "${id}".`);
|
|
1383
|
-
}
|
|
1384
|
-
return instance;
|
|
1385
|
-
},
|
|
1386
|
-
async create(options2) {
|
|
1387
|
-
const id = options2?.id ?? `mock-workflow-${++sequence}`;
|
|
1388
|
-
return createInstance(id);
|
|
1389
|
-
},
|
|
1390
|
-
async createBatch(batch) {
|
|
1391
|
-
return batch.map((options2) => {
|
|
1392
|
-
const id = options2.id ?? `mock-workflow-${++sequence}`;
|
|
1393
|
-
return createInstance(id);
|
|
1394
|
-
});
|
|
1159
|
+
}
|
|
1160
|
+
if (options.r2) {
|
|
1161
|
+
for (const name of options.r2) {
|
|
1162
|
+
env2[name] = createMockR2();
|
|
1395
1163
|
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
return {
|
|
1401
|
-
async send(batch) {
|
|
1402
|
-
records.push(...batch);
|
|
1403
|
-
},
|
|
1404
|
-
_getRecords() {
|
|
1405
|
-
return [...records];
|
|
1164
|
+
}
|
|
1165
|
+
if (options.queues) {
|
|
1166
|
+
for (const name of options.queues) {
|
|
1167
|
+
env2[name] = createMockQueue();
|
|
1406
1168
|
}
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
return new ReadableStream({
|
|
1412
|
-
start(controller) {
|
|
1413
|
-
controller.close();
|
|
1169
|
+
}
|
|
1170
|
+
if (options.rateLimits) {
|
|
1171
|
+
for (const [name, rateLimitOptions] of Object.entries(options.rateLimits)) {
|
|
1172
|
+
env2[name] = createMockRateLimit(rateLimitOptions);
|
|
1414
1173
|
}
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
contentType() {
|
|
1423
|
-
return response.headers.get("Content-Type") ?? "image/png";
|
|
1424
|
-
},
|
|
1425
|
-
image() {
|
|
1426
|
-
const cloned = response.clone();
|
|
1427
|
-
return cloned.body ?? createEmptyImageStream();
|
|
1174
|
+
}
|
|
1175
|
+
if (options.versionMetadata) {
|
|
1176
|
+
env2[options.versionMetadata] = createMockVersionMetadata();
|
|
1177
|
+
}
|
|
1178
|
+
if (options.hyperdrive) {
|
|
1179
|
+
for (const [name, binding] of Object.entries(options.hyperdrive)) {
|
|
1180
|
+
env2[name] = typeof binding === "string" ? createMockHyperdrive(binding) : binding;
|
|
1428
1181
|
}
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
transform(_transform) {
|
|
1434
|
-
return transformer;
|
|
1435
|
-
},
|
|
1436
|
-
draw(_image, _options) {
|
|
1437
|
-
return transformer;
|
|
1438
|
-
},
|
|
1439
|
-
async output(_options) {
|
|
1440
|
-
return createMockImageTransformationResult(response);
|
|
1182
|
+
}
|
|
1183
|
+
if (Array.isArray(options.workerLoaders)) {
|
|
1184
|
+
for (const name of options.workerLoaders) {
|
|
1185
|
+
env2[name] = createMockWorkerLoader();
|
|
1441
1186
|
}
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1187
|
+
} else if (options.workerLoaders) {
|
|
1188
|
+
for (const [name, workerLoaderOptions] of Object.entries(options.workerLoaders)) {
|
|
1189
|
+
env2[name] = createMockWorkerLoader(workerLoaderOptions);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (Array.isArray(options.mtlsCertificates)) {
|
|
1193
|
+
for (const name of options.mtlsCertificates) {
|
|
1194
|
+
env2[name] = createMockMTLSCertificate();
|
|
1195
|
+
}
|
|
1196
|
+
} else if (options.mtlsCertificates) {
|
|
1197
|
+
for (const [name, handler] of Object.entries(options.mtlsCertificates)) {
|
|
1198
|
+
env2[name] = createMockMTLSCertificate(handler);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (Array.isArray(options.dispatchNamespaces)) {
|
|
1202
|
+
for (const name of options.dispatchNamespaces) {
|
|
1203
|
+
env2[name] = createMockDispatchNamespace();
|
|
1204
|
+
}
|
|
1205
|
+
} else if (options.dispatchNamespaces) {
|
|
1206
|
+
for (const [name, dispatchNamespaceOptions] of Object.entries(options.dispatchNamespaces)) {
|
|
1207
|
+
env2[name] = createMockDispatchNamespace(dispatchNamespaceOptions);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (Array.isArray(options.workflows)) {
|
|
1211
|
+
for (const name of options.workflows) {
|
|
1212
|
+
env2[name] = createMockWorkflow();
|
|
1213
|
+
}
|
|
1214
|
+
} else if (options.workflows) {
|
|
1215
|
+
for (const [name, workflowOptions] of Object.entries(options.workflows)) {
|
|
1216
|
+
env2[name] = "create" in workflowOptions ? workflowOptions : createMockWorkflow(workflowOptions);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
if (Array.isArray(options.pipelines)) {
|
|
1220
|
+
for (const name of options.pipelines) {
|
|
1221
|
+
env2[name] = createMockPipeline();
|
|
1222
|
+
}
|
|
1223
|
+
} else if (options.pipelines) {
|
|
1224
|
+
for (const [name, pipeline] of Object.entries(options.pipelines)) {
|
|
1225
|
+
env2[name] = pipeline;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (typeof options.images === "string") {
|
|
1229
|
+
env2[options.images] = createMockImagesBinding();
|
|
1230
|
+
} else if (options.images) {
|
|
1231
|
+
env2.IMAGES = options.images;
|
|
1232
|
+
}
|
|
1233
|
+
if (typeof options.media === "string") {
|
|
1234
|
+
env2[options.media] = createMockMediaBinding();
|
|
1235
|
+
} else if (options.media) {
|
|
1236
|
+
env2.MEDIA = options.media;
|
|
1237
|
+
}
|
|
1238
|
+
if (Array.isArray(options.artifacts)) {
|
|
1239
|
+
for (const name of options.artifacts) {
|
|
1240
|
+
env2[name] = createMockArtifacts();
|
|
1241
|
+
}
|
|
1242
|
+
} else if (options.artifacts) {
|
|
1243
|
+
for (const [name, artifactsOptions] of Object.entries(options.artifacts)) {
|
|
1244
|
+
env2[name] = isArtifactsBinding(artifactsOptions) ? artifactsOptions : createMockArtifacts(artifactsOptions);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (options.secretsStore) {
|
|
1248
|
+
for (const [name, value] of Object.entries(options.secretsStore)) {
|
|
1249
|
+
env2[name] = createMockSecretsStoreSecret(value);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (options.vars) {
|
|
1253
|
+
Object.assign(env2, options.vars);
|
|
1254
|
+
}
|
|
1255
|
+
if (options.secrets) {
|
|
1256
|
+
Object.assign(env2, options.secrets);
|
|
1257
|
+
}
|
|
1258
|
+
if (options.custom) {
|
|
1259
|
+
Object.assign(env2, options.custom);
|
|
1260
|
+
}
|
|
1261
|
+
return env2;
|
|
1481
1262
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1263
|
+
// src/test/simple-context-bindings.ts
|
|
1264
|
+
function buildRemoteAndStaticBindings(config) {
|
|
1265
|
+
const remoteBindings = {};
|
|
1266
|
+
if (isRemoteModeActive()) {
|
|
1267
|
+
if (config.bindings?.ai) {
|
|
1268
|
+
const aiBindingName = config.bindings.ai.binding || "AI";
|
|
1269
|
+
remoteBindings[aiBindingName] = createRemoteAI(config.accountId);
|
|
1270
|
+
}
|
|
1271
|
+
if (config.bindings?.vectorize) {
|
|
1272
|
+
for (const [name, vectorConfig] of Object.entries(config.bindings.vectorize)) {
|
|
1273
|
+
remoteBindings[name] = createRemoteVectorize(vectorConfig.indexName, config.accountId);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (config.vars) {
|
|
1278
|
+
for (const [key, value] of Object.entries(config.vars)) {
|
|
1279
|
+
remoteBindings[key] = value;
|
|
1486
1280
|
}
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
async media() {
|
|
1492
|
-
const cloned = response.clone();
|
|
1493
|
-
return cloned.body ?? createEmptyMediaStream();
|
|
1494
|
-
},
|
|
1495
|
-
async response() {
|
|
1496
|
-
return response.clone();
|
|
1497
|
-
},
|
|
1498
|
-
async contentType() {
|
|
1499
|
-
return response.headers.get("Content-Type") ?? "video/mp4";
|
|
1281
|
+
}
|
|
1282
|
+
if (config.bindings?.sendEmail) {
|
|
1283
|
+
for (const [name, binding] of Object.entries(config.bindings.sendEmail)) {
|
|
1284
|
+
remoteBindings[name] = createLocalSendEmailBinding(binding);
|
|
1500
1285
|
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
transform(_transform) {
|
|
1506
|
-
return {
|
|
1507
|
-
output(_output) {
|
|
1508
|
-
return createMockMediaTransformationResult(response);
|
|
1509
|
-
}
|
|
1510
|
-
};
|
|
1511
|
-
},
|
|
1512
|
-
output(_output) {
|
|
1513
|
-
return createMockMediaTransformationResult(response);
|
|
1286
|
+
}
|
|
1287
|
+
if (config.bindings?.workerLoaders) {
|
|
1288
|
+
for (const name of Object.keys(config.bindings.workerLoaders)) {
|
|
1289
|
+
remoteBindings[name] = createLocalWorkerLoaderBinding();
|
|
1514
1290
|
}
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1291
|
+
}
|
|
1292
|
+
if (config.bindings?.versionMetadata) {
|
|
1293
|
+
remoteBindings[config.bindings.versionMetadata.binding] = createMockVersionMetadata();
|
|
1294
|
+
}
|
|
1295
|
+
return remoteBindings;
|
|
1517
1296
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1297
|
+
|
|
1298
|
+
// src/test/simple-context-durable-objects.ts
|
|
1299
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
1300
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1301
|
+
import { readFile } from "fs/promises";
|
|
1302
|
+
|
|
1303
|
+
// src/test/simple-context-gateway-script.ts
|
|
1304
|
+
function buildGatewayScript(bundledCode, wrappers, nativeRpcBindingNames = []) {
|
|
1305
|
+
const nativeRpcBindingsLiteral = JSON.stringify(nativeRpcBindingNames);
|
|
1306
|
+
return `
|
|
1307
|
+
// Bundled transport + DO classes
|
|
1308
|
+
${bundledCode}
|
|
1309
|
+
|
|
1310
|
+
// DO Wrappers with RPC
|
|
1311
|
+
${wrappers}
|
|
1312
|
+
|
|
1313
|
+
const __nativeRpcBindings = new Set(${nativeRpcBindingsLiteral})
|
|
1314
|
+
|
|
1315
|
+
// Transport encoding helper
|
|
1316
|
+
const __transportEncoders = typeof transport !== 'undefined' ? transport : {}
|
|
1317
|
+
|
|
1318
|
+
function __encodeTransport(value) {
|
|
1319
|
+
if (value === null || value === undefined) return value
|
|
1320
|
+
|
|
1321
|
+
// Try each encoder
|
|
1322
|
+
for (const [typeName, transporter] of Object.entries(__transportEncoders)) {
|
|
1323
|
+
const encoded = transporter.encode(value)
|
|
1324
|
+
if (encoded !== false && encoded !== undefined) {
|
|
1325
|
+
return { __transport: typeName, value: encoded }
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// Recursively encode arrays and objects
|
|
1330
|
+
if (Array.isArray(value)) {
|
|
1331
|
+
return value.map(__encodeTransport)
|
|
1332
|
+
}
|
|
1333
|
+
if (typeof value === 'object') {
|
|
1334
|
+
const result = {}
|
|
1335
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1336
|
+
result[k] = __encodeTransport(v)
|
|
1337
|
+
}
|
|
1338
|
+
return result
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
return value
|
|
1527
1342
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1343
|
+
|
|
1344
|
+
// Gateway with WebSocket RPC
|
|
1345
|
+
export default {
|
|
1346
|
+
async fetch(request, env) {
|
|
1347
|
+
if (request.headers.get('Upgrade') === 'websocket') {
|
|
1348
|
+
const { 0: client, 1: server } = new WebSocketPair()
|
|
1349
|
+
server.accept()
|
|
1350
|
+
server.addEventListener('message', async (e) => {
|
|
1351
|
+
try {
|
|
1352
|
+
const m = JSON.parse(e.data)
|
|
1353
|
+
if (m.t === 'rpc.call') {
|
|
1354
|
+
const result = await executeRpc(env, m.method, m.params)
|
|
1355
|
+
server.send(JSON.stringify({ t: 'rpc.ok', id: m.id, result }))
|
|
1356
|
+
}
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
server.send(JSON.stringify({ t: 'rpc.err', id: 'unknown', error: { code: 'RPC_ERROR', message: error.message } }))
|
|
1359
|
+
}
|
|
1360
|
+
})
|
|
1361
|
+
return new Response(null, { status: 101, webSocket: client })
|
|
1362
|
+
}
|
|
1363
|
+
return new Response('Gateway')
|
|
1364
|
+
}
|
|
1531
1365
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1366
|
+
|
|
1367
|
+
async function executeRpc(env, method, params) {
|
|
1368
|
+
const [bindingName, ...rest] = method.split('.')
|
|
1369
|
+
let op = rest.join('.')
|
|
1370
|
+
const binding = env[bindingName]
|
|
1371
|
+
const RAW_EMAIL = 'EmailMessage::raw'
|
|
1372
|
+
if (!binding) throw new Error('Binding not found: ' + bindingName)
|
|
1373
|
+
|
|
1374
|
+
// Normalize namespaced op names (kv.*, r2.*, d1.*, do.*, queue.*, ai.*, var.*)
|
|
1375
|
+
// down to the legacy verbs this dispatcher historically used. The bridge
|
|
1376
|
+
// proxy always emits namespaced forms (see src/bridge/proxy.ts and B3 in
|
|
1377
|
+
// REMAINING.md); this keeps the dispatcher backwards-compatible while the
|
|
1378
|
+
// rest of the codebase converges on the namespaced convention.
|
|
1379
|
+
if (op.indexOf('kv.') === 0) op = op.slice(3)
|
|
1380
|
+
else if (op.indexOf('do.') === 0) {
|
|
1381
|
+
const tail = op.slice(3)
|
|
1382
|
+
if (tail === 'fetch') op = 'stub.fetch'
|
|
1383
|
+
else if (tail === 'rpc') op = 'stub.rpc'
|
|
1384
|
+
else op = tail
|
|
1385
|
+
}
|
|
1386
|
+
else if (op.indexOf('queue.') === 0) op = op.slice(6)
|
|
1387
|
+
else if (op.indexOf('ai.') === 0) op = op.slice(3)
|
|
1388
|
+
else if (op.indexOf('var.') === 0) op = op.slice(4)
|
|
1389
|
+
else if (op.indexOf('d1.stmt.') === 0) op = 'prepare.' + op.slice('d1.stmt.'.length)
|
|
1390
|
+
else if (op.indexOf('d1.') === 0) op = op.slice(3)
|
|
1391
|
+
// r2.* and email.* keep their existing prefixes
|
|
1392
|
+
|
|
1393
|
+
// KV operations
|
|
1394
|
+
if (op === 'get') return binding.get(params[0], params[1])
|
|
1395
|
+
if (op === 'put') return binding.put(params[0], params[1], params[2])
|
|
1396
|
+
if (op === 'delete') return binding.delete(params[0])
|
|
1397
|
+
if (op === 'list') return binding.list(params[0])
|
|
1398
|
+
if (op === 'getWithMetadata') return binding.getWithMetadata(params[0], params[1])
|
|
1399
|
+
|
|
1400
|
+
// R2 operations
|
|
1401
|
+
if (op === 'r2.get') return binding.get(params[0], params[1])
|
|
1402
|
+
if (op === 'r2.put') return binding.put(params[0], params[1], params[2])
|
|
1403
|
+
if (op === 'r2.delete') return binding.delete(params[0])
|
|
1404
|
+
if (op === 'r2.list') return binding.list(params[0])
|
|
1405
|
+
if (op === 'r2.head' || op === 'head') return binding.head(params[0])
|
|
1406
|
+
|
|
1407
|
+
// D1 operations
|
|
1408
|
+
if (op === 'exec') return binding.exec(params[0])
|
|
1409
|
+
if (op === 'dump') return binding.dump()
|
|
1410
|
+
if (op === 'batch') {
|
|
1411
|
+
const stmts = params[0].map(s => {
|
|
1412
|
+
const stmt = binding.prepare(s.sql)
|
|
1413
|
+
return s.bindings?.length ? stmt.bind(...s.bindings) : stmt
|
|
1414
|
+
})
|
|
1415
|
+
return binding.batch(stmts)
|
|
1416
|
+
}
|
|
1417
|
+
if (op === 'prepare.run') return binding.prepare(params[0]).bind(...(params[1] || [])).run()
|
|
1418
|
+
if (op === 'prepare.all') return binding.prepare(params[0]).bind(...(params[1] || [])).all()
|
|
1419
|
+
if (op === 'prepare.first') return binding.prepare(params[0]).bind(...(params[1] || [])).first(params[2])
|
|
1420
|
+
if (op === 'prepare.raw') return binding.prepare(params[0]).bind(...(params[1] || [])).raw({ columnNames: params[2] })
|
|
1421
|
+
|
|
1422
|
+
// Send email operations
|
|
1423
|
+
if (op === 'email.send') {
|
|
1424
|
+
return binding.send(__normalizeEmailMessage(params[0]))
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// DO operations
|
|
1428
|
+
if (op === 'idFromName') {
|
|
1429
|
+
return { __type: 'DOId', hex: binding.idFromName(params[0]).toString() }
|
|
1430
|
+
}
|
|
1431
|
+
if (op === 'stub.rpc') {
|
|
1432
|
+
const [, idSerialized, rpcMethod, rpcParams] = params
|
|
1433
|
+
const stub = binding.get(binding.idFromString(idSerialized.hex))
|
|
1434
|
+
|
|
1435
|
+
if (__nativeRpcBindings.has(bindingName) && typeof stub[rpcMethod] === 'function') {
|
|
1436
|
+
let result = await stub[rpcMethod](...(rpcParams || []))
|
|
1437
|
+
result = __encodeTransport(result)
|
|
1438
|
+
return result
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
const response = await stub.fetch(new Request('http://do/_rpc', {
|
|
1442
|
+
method: 'POST',
|
|
1443
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1444
|
+
body: JSON.stringify({ method: rpcMethod, params: rpcParams || [] })
|
|
1445
|
+
}))
|
|
1446
|
+
|
|
1447
|
+
const payload = await response.json()
|
|
1448
|
+
if (!response.ok || !payload?.ok) {
|
|
1449
|
+
throw new Error(payload?.error?.message || ('DO RPC failed with status ' + response.status))
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
return payload.result
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
throw new Error('Unknown operation: ' + method)
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function __createEmailMessageRaw(raw) {
|
|
1459
|
+
if (typeof raw === 'string' || raw instanceof ReadableStream) {
|
|
1460
|
+
return raw
|
|
1461
|
+
}
|
|
1462
|
+
if (raw instanceof Uint8Array || raw instanceof ArrayBuffer) {
|
|
1463
|
+
return new Response(raw).body
|
|
1464
|
+
}
|
|
1465
|
+
throw new Error('Unsupported EmailMessage raw payload')
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function __buildRawEmail(message) {
|
|
1469
|
+
const lines = []
|
|
1470
|
+
const messageId = '<' + Date.now() + '-' + Math.random().toString(36).slice(2) + '@devflare.dev>'
|
|
1471
|
+
|
|
1472
|
+
lines.push('From: ' + message.from)
|
|
1473
|
+
lines.push('To: ' + (Array.isArray(message.to) ? message.to.join(', ') : message.to))
|
|
1474
|
+
lines.push('Date: ' + new Date().toUTCString())
|
|
1475
|
+
lines.push('Message-ID: ' + messageId)
|
|
1476
|
+
|
|
1477
|
+
if (message.subject) lines.push('Subject: ' + message.subject)
|
|
1478
|
+
if (message.replyTo) lines.push('Reply-To: ' + String(message.replyTo))
|
|
1479
|
+
if (message.cc) lines.push('Cc: ' + (Array.isArray(message.cc) ? message.cc.join(', ') : message.cc))
|
|
1480
|
+
if (message.bcc) lines.push('Bcc: ' + (Array.isArray(message.bcc) ? message.bcc.join(', ') : message.bcc))
|
|
1481
|
+
|
|
1482
|
+
for (const [key, value] of Object.entries(message.headers || {})) {
|
|
1483
|
+
lines.push(key + ': ' + value)
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
lines.push('MIME-Version: 1.0')
|
|
1487
|
+
lines.push('Content-Type: ' + (message.html ? 'text/html' : 'text/plain') + '; charset=UTF-8')
|
|
1488
|
+
lines.push('')
|
|
1489
|
+
lines.push(String(message.html ?? message.text ?? '').replace(/\\r?\\n/g, '\\r\\n'))
|
|
1490
|
+
|
|
1491
|
+
return lines.join('\\r\\n')
|
|
1546
1492
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1493
|
+
|
|
1494
|
+
function __normalizeEmailMessage(message) {
|
|
1495
|
+
if (!message || typeof message !== 'object' || !('from' in message) || !('to' in message)) {
|
|
1496
|
+
return message
|
|
1497
|
+
}
|
|
1498
|
+
if ('EmailMessage::raw' in message) {
|
|
1499
|
+
return message
|
|
1500
|
+
}
|
|
1501
|
+
if ('raw' in message && message.raw !== undefined) {
|
|
1502
|
+
return {
|
|
1503
|
+
from: message.from,
|
|
1504
|
+
to: message.to,
|
|
1505
|
+
[RAW_EMAIL]: __createEmailMessageRaw(message.raw)
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
from: message.from,
|
|
1510
|
+
to: message.to,
|
|
1511
|
+
[RAW_EMAIL]: __createEmailMessageRaw(__buildRawEmail(message))
|
|
1512
|
+
}
|
|
1549
1513
|
}
|
|
1550
|
-
|
|
1551
|
-
const repos = new Map;
|
|
1552
|
-
const tokens = new Map;
|
|
1553
|
-
const addRepo = (info) => {
|
|
1554
|
-
repos.set(info.name, info);
|
|
1555
|
-
if (!tokens.has(info.name)) {
|
|
1556
|
-
tokens.set(info.name, []);
|
|
1557
|
-
}
|
|
1558
|
-
};
|
|
1559
|
-
for (const repo of options.repos ?? []) {
|
|
1560
|
-
addRepo({
|
|
1561
|
-
...createArtifactsRepoInfo(repo.name),
|
|
1562
|
-
...repo
|
|
1563
|
-
});
|
|
1564
|
-
}
|
|
1565
|
-
const createToken = (repoName, scope = "write", ttl = 86400) => {
|
|
1566
|
-
const existing = tokens.get(repoName) ?? [];
|
|
1567
|
-
const id = `token-${repoName}-${existing.length + 1}`;
|
|
1568
|
-
const expiresAt = new Date(Date.parse(createArtifactTimestamp()) + ttl * 1000).toISOString();
|
|
1569
|
-
const token = {
|
|
1570
|
-
id,
|
|
1571
|
-
scope,
|
|
1572
|
-
state: "active",
|
|
1573
|
-
createdAt: createArtifactTimestamp(),
|
|
1574
|
-
expiresAt
|
|
1575
|
-
};
|
|
1576
|
-
tokens.set(repoName, [...existing, token]);
|
|
1577
|
-
return {
|
|
1578
|
-
id,
|
|
1579
|
-
plaintext: `${id}-plaintext`,
|
|
1580
|
-
scope,
|
|
1581
|
-
expiresAt
|
|
1582
|
-
};
|
|
1583
|
-
};
|
|
1584
|
-
const createRepoHandle = (info) => ({
|
|
1585
|
-
...info,
|
|
1586
|
-
async createToken(scope, ttl) {
|
|
1587
|
-
return createToken(info.name, scope, ttl);
|
|
1588
|
-
},
|
|
1589
|
-
async listTokens() {
|
|
1590
|
-
const repoTokens = tokens.get(info.name) ?? [];
|
|
1591
|
-
return {
|
|
1592
|
-
tokens: repoTokens,
|
|
1593
|
-
total: repoTokens.length
|
|
1594
|
-
};
|
|
1595
|
-
},
|
|
1596
|
-
async revokeToken(tokenOrId) {
|
|
1597
|
-
const repoTokens = tokens.get(info.name) ?? [];
|
|
1598
|
-
const index = repoTokens.findIndex((token) => token.id === tokenOrId);
|
|
1599
|
-
if (index === -1) {
|
|
1600
|
-
return false;
|
|
1601
|
-
}
|
|
1602
|
-
repoTokens[index] = {
|
|
1603
|
-
...repoTokens[index],
|
|
1604
|
-
state: "revoked"
|
|
1605
|
-
};
|
|
1606
|
-
tokens.set(info.name, repoTokens);
|
|
1607
|
-
return true;
|
|
1608
|
-
},
|
|
1609
|
-
async fork(name, forkOptions) {
|
|
1610
|
-
return createRepo(name, {
|
|
1611
|
-
description: forkOptions?.description ?? info.description ?? undefined,
|
|
1612
|
-
readOnly: forkOptions?.readOnly ?? info.readOnly,
|
|
1613
|
-
setDefaultBranch: info.defaultBranch,
|
|
1614
|
-
source: `artifacts:default/${info.name}`
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
1617
|
-
});
|
|
1618
|
-
const createRepo = async (name, createOptions = {}) => {
|
|
1619
|
-
const info = createArtifactsRepoInfo(name, {
|
|
1620
|
-
description: createOptions.description,
|
|
1621
|
-
readOnly: createOptions.readOnly,
|
|
1622
|
-
defaultBranch: createOptions.setDefaultBranch,
|
|
1623
|
-
source: createOptions.source
|
|
1624
|
-
});
|
|
1625
|
-
addRepo(info);
|
|
1626
|
-
const token = createToken(name);
|
|
1627
|
-
return {
|
|
1628
|
-
id: info.id,
|
|
1629
|
-
name: info.name,
|
|
1630
|
-
description: info.description,
|
|
1631
|
-
defaultBranch: info.defaultBranch,
|
|
1632
|
-
remote: info.remote,
|
|
1633
|
-
token: token.plaintext,
|
|
1634
|
-
tokenExpiresAt: token.expiresAt
|
|
1635
|
-
};
|
|
1636
|
-
};
|
|
1637
|
-
return {
|
|
1638
|
-
create: createRepo,
|
|
1639
|
-
async get(name) {
|
|
1640
|
-
const repo = repos.get(name);
|
|
1641
|
-
return repo ? createRepoHandle(repo) : null;
|
|
1642
|
-
},
|
|
1643
|
-
async import(params) {
|
|
1644
|
-
return createRepo(params.target.name, {
|
|
1645
|
-
description: params.target.opts?.description,
|
|
1646
|
-
readOnly: params.target.opts?.readOnly,
|
|
1647
|
-
source: params.source.url
|
|
1648
|
-
});
|
|
1649
|
-
},
|
|
1650
|
-
async list(opts) {
|
|
1651
|
-
const limit = opts?.limit ?? 50;
|
|
1652
|
-
const repoList = Array.from(repos.values()).slice(0, limit).map((repo) => {
|
|
1653
|
-
const { remote: _remote, ...rest } = repo;
|
|
1654
|
-
return rest;
|
|
1655
|
-
});
|
|
1656
|
-
return {
|
|
1657
|
-
repos: repoList,
|
|
1658
|
-
total: repos.size,
|
|
1659
|
-
...repos.size > repoList.length && { cursor: String(repoList.length) }
|
|
1660
|
-
};
|
|
1661
|
-
},
|
|
1662
|
-
async delete(name) {
|
|
1663
|
-
tokens.delete(name);
|
|
1664
|
-
return repos.delete(name);
|
|
1665
|
-
}
|
|
1666
|
-
};
|
|
1514
|
+
`;
|
|
1667
1515
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
}
|
|
1686
|
-
if (options.queues) {
|
|
1687
|
-
for (const name of options.queues) {
|
|
1688
|
-
env2[name] = createMockQueue();
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
if (options.rateLimits) {
|
|
1692
|
-
for (const [name, rateLimitOptions] of Object.entries(options.rateLimits)) {
|
|
1693
|
-
env2[name] = createMockRateLimit(rateLimitOptions);
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
if (options.versionMetadata) {
|
|
1697
|
-
env2[options.versionMetadata] = createMockVersionMetadata();
|
|
1698
|
-
}
|
|
1699
|
-
if (options.hyperdrive) {
|
|
1700
|
-
for (const [name, binding] of Object.entries(options.hyperdrive)) {
|
|
1701
|
-
env2[name] = typeof binding === "string" ? createMockHyperdrive(binding) : binding;
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
if (Array.isArray(options.workerLoaders)) {
|
|
1705
|
-
for (const name of options.workerLoaders) {
|
|
1706
|
-
env2[name] = createMockWorkerLoader();
|
|
1707
|
-
}
|
|
1708
|
-
} else if (options.workerLoaders) {
|
|
1709
|
-
for (const [name, workerLoaderOptions] of Object.entries(options.workerLoaders)) {
|
|
1710
|
-
env2[name] = createMockWorkerLoader(workerLoaderOptions);
|
|
1711
|
-
}
|
|
1516
|
+
|
|
1517
|
+
// src/test/simple-context-paths.ts
|
|
1518
|
+
import { existsSync } from "fs";
|
|
1519
|
+
import { createServer } from "net";
|
|
1520
|
+
import { dirname, join } from "path";
|
|
1521
|
+
import { fileURLToPath } from "url";
|
|
1522
|
+
var DEFAULT_TRANSPORT_ENTRY_FILES = [
|
|
1523
|
+
"src/transport.ts",
|
|
1524
|
+
"src/transport.js",
|
|
1525
|
+
"src/transport.mts",
|
|
1526
|
+
"src/transport.mjs"
|
|
1527
|
+
];
|
|
1528
|
+
var CURRENT_PACKAGE_ROOT = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
|
|
1529
|
+
function getBunRuntime() {
|
|
1530
|
+
const g = globalThis;
|
|
1531
|
+
if (typeof g.Bun === "object" && g.Bun !== null) {
|
|
1532
|
+
return g.Bun;
|
|
1712
1533
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
env2[name] = createMockMTLSCertificate(handler);
|
|
1720
|
-
}
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
function getCallerDirectory() {
|
|
1537
|
+
const stackCallerDirectory = getStackCallerDirectory();
|
|
1538
|
+
if (stackCallerDirectory) {
|
|
1539
|
+
return stackCallerDirectory;
|
|
1721
1540
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1541
|
+
return process.cwd();
|
|
1542
|
+
}
|
|
1543
|
+
function getStackCallerDirectory() {
|
|
1544
|
+
const originalPrepare = Error.prepareStackTrace;
|
|
1545
|
+
Error.prepareStackTrace = (_, stack) => stack;
|
|
1546
|
+
try {
|
|
1547
|
+
const err = new Error;
|
|
1548
|
+
const stack = err.stack;
|
|
1549
|
+
for (const site of stack ?? []) {
|
|
1550
|
+
const filename = site.getFileName?.();
|
|
1551
|
+
if (filename && !isInsideCurrentPackage(filename) && !filename.includes("simple-context") && !filename.includes("node_modules") && !filename.includes("[") && existsSync(filename)) {
|
|
1552
|
+
return dirname(filename);
|
|
1553
|
+
}
|
|
1729
1554
|
}
|
|
1555
|
+
} finally {
|
|
1556
|
+
Error.prepareStackTrace = originalPrepare;
|
|
1730
1557
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
function findPackageRoot(startDir) {
|
|
1561
|
+
let currentDir = startDir;
|
|
1562
|
+
while (true) {
|
|
1563
|
+
if (existsSync(join(currentDir, "package.json"))) {
|
|
1564
|
+
return currentDir;
|
|
1734
1565
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1566
|
+
const parentDir = dirname(currentDir);
|
|
1567
|
+
if (parentDir === currentDir) {
|
|
1568
|
+
return startDir;
|
|
1738
1569
|
}
|
|
1570
|
+
currentDir = parentDir;
|
|
1739
1571
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1572
|
+
}
|
|
1573
|
+
function isInsideCurrentPackage(filePath) {
|
|
1574
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
1575
|
+
const normalizedPackageRoot = CURRENT_PACKAGE_ROOT.replace(/\\/g, "/");
|
|
1576
|
+
return normalizedFilePath === normalizedPackageRoot || normalizedFilePath.startsWith(`${normalizedPackageRoot}/`);
|
|
1577
|
+
}
|
|
1578
|
+
async function findNearestConfig(startDir) {
|
|
1579
|
+
let currentDir = startDir;
|
|
1580
|
+
while (true) {
|
|
1581
|
+
const configPath = await resolveConfigPath(currentDir);
|
|
1582
|
+
if (configPath) {
|
|
1583
|
+
return configPath;
|
|
1743
1584
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1585
|
+
const parentDir = dirname(currentDir);
|
|
1586
|
+
if (parentDir === currentDir) {
|
|
1587
|
+
return null;
|
|
1747
1588
|
}
|
|
1589
|
+
currentDir = parentDir;
|
|
1748
1590
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1591
|
+
}
|
|
1592
|
+
async function getAvailablePort() {
|
|
1593
|
+
return await new Promise((resolvePort, reject) => {
|
|
1594
|
+
const server = createServer();
|
|
1595
|
+
server.once("error", reject);
|
|
1596
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1597
|
+
const address = server.address();
|
|
1598
|
+
if (!address || typeof address === "string") {
|
|
1599
|
+
server.close(() => reject(new Error("Could not determine an available port")));
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
const { port } = address;
|
|
1603
|
+
server.close((error) => {
|
|
1604
|
+
if (error) {
|
|
1605
|
+
reject(error);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
resolvePort(port);
|
|
1609
|
+
});
|
|
1610
|
+
});
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
function resolveTransportFile(configDir, configuredPath) {
|
|
1614
|
+
if (typeof configuredPath === "string") {
|
|
1615
|
+
return configuredPath;
|
|
1758
1616
|
}
|
|
1759
|
-
if (
|
|
1760
|
-
|
|
1761
|
-
env2[name] = createMockArtifacts();
|
|
1762
|
-
}
|
|
1763
|
-
} else if (options.artifacts) {
|
|
1764
|
-
for (const [name, artifactsOptions] of Object.entries(options.artifacts)) {
|
|
1765
|
-
env2[name] = isArtifactsBinding(artifactsOptions) ? artifactsOptions : createMockArtifacts(artifactsOptions);
|
|
1766
|
-
}
|
|
1617
|
+
if (configuredPath === null) {
|
|
1618
|
+
return null;
|
|
1767
1619
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1620
|
+
for (const defaultEntry of DEFAULT_TRANSPORT_ENTRY_FILES) {
|
|
1621
|
+
if (existsSync(join(configDir, defaultEntry))) {
|
|
1622
|
+
return defaultEntry;
|
|
1771
1623
|
}
|
|
1772
1624
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1625
|
+
return null;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// src/test/simple-context-durable-objects.ts
|
|
1629
|
+
function findExportedClasses(code) {
|
|
1630
|
+
const classes = [];
|
|
1631
|
+
const classPattern = /export\s+class\s+(\w+)/g;
|
|
1632
|
+
let match;
|
|
1633
|
+
while ((match = classPattern.exec(code)) !== null) {
|
|
1634
|
+
classes.push(match[1]);
|
|
1775
1635
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1636
|
+
return classes;
|
|
1637
|
+
}
|
|
1638
|
+
function classSupportsNativeDurableObjectRpc(code, className) {
|
|
1639
|
+
const nativeRpcPattern = new RegExp(`export\\s+class\\s+${className}\\s+extends\\s+DurableObject\\b`);
|
|
1640
|
+
return nativeRpcPattern.test(code);
|
|
1641
|
+
}
|
|
1642
|
+
function toGeneratedIdentifier(value) {
|
|
1643
|
+
const normalized = value.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
1644
|
+
return /^[A-Za-z_$]/.test(normalized) ? normalized : `_${normalized}`;
|
|
1645
|
+
}
|
|
1646
|
+
async function discoverLocalDurableObjectClasses(config, configDir) {
|
|
1647
|
+
const classToFilePath = new Map;
|
|
1648
|
+
const doPatternConfig = config.files?.durableObjects;
|
|
1649
|
+
const doPattern = typeof doPatternConfig === "string" ? doPatternConfig : DEFAULT_DO_PATTERN;
|
|
1650
|
+
if (doPatternConfig === false) {
|
|
1651
|
+
return classToFilePath;
|
|
1778
1652
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1653
|
+
const doFiles = await findFiles(doPattern, { cwd: configDir });
|
|
1654
|
+
for (const filePath of doFiles) {
|
|
1655
|
+
try {
|
|
1656
|
+
const code = await readFile(filePath, "utf-8");
|
|
1657
|
+
const classNames = findExportedClasses(code);
|
|
1658
|
+
for (const className of classNames) {
|
|
1659
|
+
classToFilePath.set(className, {
|
|
1660
|
+
filePath,
|
|
1661
|
+
nativeRpc: classSupportsNativeDurableObjectRpc(code, className)
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
} catch {}
|
|
1781
1665
|
}
|
|
1782
|
-
return
|
|
1666
|
+
return classToFilePath;
|
|
1783
1667
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1668
|
+
async function resolveLocalDurableObjects(config, configDir) {
|
|
1669
|
+
const doConfig = {};
|
|
1670
|
+
const doInfos = [];
|
|
1671
|
+
const classToFilePath = await discoverLocalDurableObjectClasses(config, configDir);
|
|
1672
|
+
for (const [name, rawDoInfo] of Object.entries(config.bindings?.durableObjects ?? {})) {
|
|
1673
|
+
const doInfo = normalizeDOBinding(rawDoInfo);
|
|
1674
|
+
if (doInfo.__ref) {
|
|
1675
|
+
continue;
|
|
1791
1676
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1677
|
+
let scriptPath;
|
|
1678
|
+
let nativeRpc = false;
|
|
1679
|
+
if (doInfo.kind === "cross-worker" && doInfo.scriptName) {
|
|
1680
|
+
scriptPath = join2(configDir, "src", doInfo.scriptName);
|
|
1681
|
+
try {
|
|
1682
|
+
const code = await readFile(scriptPath, "utf-8");
|
|
1683
|
+
nativeRpc = classSupportsNativeDurableObjectRpc(code, doInfo.className);
|
|
1684
|
+
} catch {
|
|
1685
|
+
nativeRpc = false;
|
|
1686
|
+
}
|
|
1687
|
+
} else {
|
|
1688
|
+
const discoveredClass = classToFilePath.get(doInfo.className);
|
|
1689
|
+
if (!discoveredClass) {
|
|
1690
|
+
throw new Error(`Durable object ${name} (className: '${doInfo.className}') not found.
|
|
1691
|
+
` + `Either:
|
|
1692
|
+
` + ` 1. Set files.durableObjects pattern in config (e.g., 'src/do.*.ts')
|
|
1693
|
+
` + ` 2. Use explicit scriptName: { className: '${doInfo.className}', scriptName: 'do.file.ts' }`);
|
|
1795
1694
|
}
|
|
1695
|
+
scriptPath = discoveredClass.filePath;
|
|
1696
|
+
nativeRpc = discoveredClass.nativeRpc;
|
|
1796
1697
|
}
|
|
1698
|
+
const runtimeClassName = nativeRpc ? doInfo.className : `__Devflare${toGeneratedIdentifier(name)}RpcWrapper`;
|
|
1699
|
+
doConfig[name] = runtimeClassName;
|
|
1700
|
+
doInfos.push({
|
|
1701
|
+
name,
|
|
1702
|
+
className: doInfo.className,
|
|
1703
|
+
scriptPath,
|
|
1704
|
+
nativeRpc,
|
|
1705
|
+
runtimeClassName
|
|
1706
|
+
});
|
|
1797
1707
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1708
|
+
return {
|
|
1709
|
+
doConfig,
|
|
1710
|
+
doInfos
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
function buildWrapperCode(doInfos) {
|
|
1714
|
+
return doInfos.filter((info) => !info.nativeRpc).map((info) => `
|
|
1715
|
+
export class ${info.runtimeClassName} {
|
|
1716
|
+
constructor(state, env) {
|
|
1717
|
+
this.__instance = new ${info.className}(state, env)
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
async fetch(request) {
|
|
1721
|
+
const url = new URL(request.url)
|
|
1722
|
+
if (request.method !== 'POST' || url.pathname !== '/_rpc') {
|
|
1723
|
+
return new Response('Not found', { status: 404 })
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
try {
|
|
1727
|
+
const payload = await request.json()
|
|
1728
|
+
const method = payload?.method
|
|
1729
|
+
const params = Array.isArray(payload?.params) ? payload.params : []
|
|
1730
|
+
const target = this.__instance?.[method]
|
|
1731
|
+
|
|
1732
|
+
if (typeof target !== 'function') {
|
|
1733
|
+
return new Response(JSON.stringify({
|
|
1734
|
+
ok: false,
|
|
1735
|
+
error: { message: 'Method not found: ' + String(method) }
|
|
1736
|
+
}), {
|
|
1737
|
+
status: 404,
|
|
1738
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1739
|
+
})
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
let result = await target.apply(this.__instance, params)
|
|
1743
|
+
result = __encodeTransport(result)
|
|
1744
|
+
|
|
1745
|
+
return new Response(JSON.stringify({ ok: true, result }), {
|
|
1746
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1747
|
+
})
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
return new Response(JSON.stringify({
|
|
1750
|
+
ok: false,
|
|
1751
|
+
error: { message: error instanceof Error ? error.message : String(error) }
|
|
1752
|
+
}), {
|
|
1753
|
+
status: 500,
|
|
1754
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1755
|
+
})
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}`.trim()).join(`
|
|
1759
|
+
|
|
1760
|
+
`);
|
|
1761
|
+
}
|
|
1762
|
+
async function bundleDurableObjectModules(configDir, doInfos, transportFile) {
|
|
1763
|
+
const virtualImports = [];
|
|
1764
|
+
const virtualExports = [];
|
|
1765
|
+
if (transportFile) {
|
|
1766
|
+
const transportPath = join2(configDir, transportFile);
|
|
1767
|
+
virtualImports.push(`import { transport } from '${transportPath.replace(/\\/g, "/")}'`);
|
|
1768
|
+
virtualExports.push("export { transport }");
|
|
1802
1769
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1770
|
+
for (const info of doInfos) {
|
|
1771
|
+
virtualImports.push(`import { ${info.className} } from '${info.scriptPath.replace(/\\/g, "/")}'`);
|
|
1772
|
+
virtualExports.push(`export { ${info.className} }`);
|
|
1807
1773
|
}
|
|
1808
|
-
if (
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1774
|
+
if (virtualImports.length === 0) {
|
|
1775
|
+
return "";
|
|
1776
|
+
}
|
|
1777
|
+
const virtualEntry = [...virtualImports, "", ...virtualExports].join(`
|
|
1778
|
+
`);
|
|
1779
|
+
const virtualPath = join2(configDir, ".devflare", "__test_entry.ts");
|
|
1780
|
+
mkdirSync(dirname2(virtualPath), { recursive: true });
|
|
1781
|
+
writeFileSync(virtualPath, virtualEntry);
|
|
1782
|
+
const bun = getBunRuntime();
|
|
1783
|
+
if (!bun) {
|
|
1784
|
+
throw new Error("Bun runtime is required for createTestContext with Durable Objects");
|
|
1785
|
+
}
|
|
1786
|
+
const result = await bun.build({
|
|
1787
|
+
entrypoints: [virtualPath],
|
|
1788
|
+
target: "browser",
|
|
1789
|
+
format: "esm",
|
|
1790
|
+
minify: false,
|
|
1791
|
+
external: ["cloudflare:workers", "cloudflare:*"]
|
|
1792
|
+
});
|
|
1793
|
+
if (!result.success) {
|
|
1794
|
+
throw new Error(`Failed to bundle test entry: ${result.logs.join(`
|
|
1795
|
+
`)}`);
|
|
1812
1796
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1797
|
+
return await result.outputs[0].text();
|
|
1798
|
+
}
|
|
1799
|
+
async function buildDurableObjectGateway(config, configDir, transportFile) {
|
|
1800
|
+
const workflowEntrypointScript = await bundleWorkflowEntrypointScript(config, configDir);
|
|
1801
|
+
if (!config.bindings?.durableObjects) {
|
|
1802
|
+
return {
|
|
1803
|
+
script: buildGatewayScript(workflowEntrypointScript, "")
|
|
1804
|
+
};
|
|
1815
1805
|
}
|
|
1816
|
-
|
|
1806
|
+
const { doConfig, doInfos } = await resolveLocalDurableObjects(config, configDir);
|
|
1807
|
+
const bundledCode = await bundleDurableObjectModules(configDir, doInfos, transportFile);
|
|
1808
|
+
const wrapperCode = buildWrapperCode(doInfos);
|
|
1809
|
+
const entrypointCode = [workflowEntrypointScript, bundledCode].filter(Boolean).join(`
|
|
1810
|
+
|
|
1811
|
+
`);
|
|
1812
|
+
const nativeRpcBindingNames = doInfos.filter((info) => info.nativeRpc).map((info) => info.name);
|
|
1813
|
+
return {
|
|
1814
|
+
durableObjects: doConfig,
|
|
1815
|
+
script: buildGatewayScript(entrypointCode, wrapperCode, nativeRpcBindingNames)
|
|
1816
|
+
};
|
|
1817
1817
|
}
|
|
1818
1818
|
|
|
1819
1819
|
// src/test/email.ts
|
|
@@ -2880,386 +2880,131 @@ async function fetchWithStartupRetries(fetchImpl, request) {
|
|
|
2880
2880
|
}
|
|
2881
2881
|
const deadline = Date.now() + 5000;
|
|
2882
2882
|
let lastError;
|
|
2883
|
-
while (Date.now() < deadline) {
|
|
2884
|
-
try {
|
|
2885
|
-
return await fetchImpl(request.clone());
|
|
2886
|
-
} catch (error) {
|
|
2887
|
-
if (!isTransientContainerFetchError(error)) {
|
|
2888
|
-
throw error;
|
|
2889
|
-
}
|
|
2890
|
-
lastError = error;
|
|
2891
|
-
await delay(100);
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
throw lastError;
|
|
2895
|
-
}
|
|
2896
|
-
function canRetryRequest(request) {
|
|
2897
|
-
return (request.method === "GET" || request.method === "HEAD") && request.body === null;
|
|
2898
|
-
}
|
|
2899
|
-
function isTransientContainerFetchError(error) {
|
|
2900
|
-
const value = error;
|
|
2901
|
-
const code = value.code ?? value.cause?.code;
|
|
2902
|
-
if (code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "UND_ERR_SOCKET") {
|
|
2903
|
-
return true;
|
|
2904
|
-
}
|
|
2905
|
-
return typeof value.message === "string" && (value.message.includes("socket connection was closed") || value.message.includes("fetch failed"));
|
|
2906
|
-
}
|
|
2907
|
-
async function waitForTcpPort(host, port, timeoutMs) {
|
|
2908
|
-
const start = Date.now();
|
|
2909
|
-
let lastError;
|
|
2910
|
-
while (Date.now() - start < timeoutMs) {
|
|
2911
|
-
try {
|
|
2912
|
-
await connectOnce(host, port);
|
|
2913
|
-
return;
|
|
2914
|
-
} catch (error) {
|
|
2915
|
-
lastError = error;
|
|
2916
|
-
await delay(100);
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
const message = lastError instanceof Error ? lastError.message : "timed out";
|
|
2920
|
-
throw new Error(`Timed out waiting for container port ${host}:${port}: ${message}`);
|
|
2921
|
-
}
|
|
2922
|
-
function connectOnce(host, port) {
|
|
2923
|
-
return new Promise((resolveConnection, rejectConnection) => {
|
|
2924
|
-
const socket = createConnection({ host, port });
|
|
2925
|
-
socket.once("connect", () => {
|
|
2926
|
-
socket.destroy();
|
|
2927
|
-
resolveConnection();
|
|
2928
|
-
});
|
|
2929
|
-
socket.once("error", rejectConnection);
|
|
2930
|
-
});
|
|
2931
|
-
}
|
|
2932
|
-
function looksLikeLocalPath(image) {
|
|
2933
|
-
return image === "Dockerfile" || image.startsWith(".") || image.startsWith("/") || image.startsWith("\\") || isAbsolute(image) || image.endsWith("/Dockerfile") || image.endsWith("\\Dockerfile");
|
|
2934
|
-
}
|
|
2935
|
-
function getOfflineBuildPullArg(engine) {
|
|
2936
|
-
return engine === "podman" ? "--pull=never" : "--pull=false";
|
|
2937
|
-
}
|
|
2938
|
-
function makeLocalImageTag(className, configDir6, image) {
|
|
2939
|
-
const hash = createHash("sha256").update(`${configDir6}:${image}`).digest("hex").slice(0, 12);
|
|
2940
|
-
return `devflare-local-${sanitizeName(className)}:${hash}`;
|
|
2941
|
-
}
|
|
2942
|
-
function makeContainerName(className, instance) {
|
|
2943
|
-
return `devflare-${sanitizeName(className)}-${sanitizeName(instance ?? "default")}-${randomUUID().slice(0, 8)}`;
|
|
2944
|
-
}
|
|
2945
|
-
function sanitizeName(value) {
|
|
2946
|
-
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "container";
|
|
2947
|
-
}
|
|
2948
|
-
function formatCommandFailure(result) {
|
|
2949
|
-
return (result.stderr || result.stdout || `exit code ${result.exitCode}`).trim();
|
|
2950
|
-
}
|
|
2951
|
-
function isTruthyEnvFlag(value) {
|
|
2952
|
-
return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
// src/test/simple-context-lifecycle.ts
|
|
2956
|
-
async function resolveTestContextConfig(configPath, callerDir = getCallerDirectory()) {
|
|
2957
|
-
let absolutePath;
|
|
2958
|
-
if (configPath) {
|
|
2959
|
-
absolutePath = resolve2(callerDir, configPath);
|
|
2960
|
-
} else {
|
|
2961
|
-
const found = await findNearestConfig(callerDir);
|
|
2962
|
-
if (!found) {
|
|
2963
|
-
throw new Error(`Could not find a devflare config file. Searched upward from: ${callerDir}
|
|
2964
|
-
` + `Expected one of: devflare.config.ts, devflare.config.mts, devflare.config.js, devflare.config.mjs
|
|
2965
|
-
` + `Either create a config file or provide an explicit path: createTestContext('./path/to/config.ts')`);
|
|
2966
|
-
}
|
|
2967
|
-
absolutePath = found;
|
|
2968
|
-
}
|
|
2969
|
-
const configDir6 = dirname4(absolutePath);
|
|
2970
|
-
const loadedConfig = await loadConfig({
|
|
2971
|
-
cwd: configDir6,
|
|
2972
|
-
configFile: absolutePath.split(/[/\\]/).pop()
|
|
2973
|
-
});
|
|
2974
|
-
const envResolvedConfig = await resolveConfigEnvVars(loadedConfig, {
|
|
2975
|
-
cwd: configDir6,
|
|
2976
|
-
configPath: absolutePath,
|
|
2977
|
-
mode: "dev"
|
|
2978
|
-
});
|
|
2979
|
-
const config = await applyLocalDevVarsToConfig(envResolvedConfig, {
|
|
2980
|
-
cwd: configDir6,
|
|
2981
|
-
configPath: absolutePath
|
|
2982
|
-
});
|
|
2983
|
-
return { absolutePath, configDir: configDir6, config };
|
|
2984
|
-
}
|
|
2985
|
-
function createDisposeContext(state) {
|
|
2986
|
-
return async () => {
|
|
2987
|
-
if (state.client) {
|
|
2988
|
-
await state.client.disconnect();
|
|
2989
|
-
state.client = null;
|
|
2990
|
-
}
|
|
2991
|
-
if (state.miniflare) {
|
|
2992
|
-
await state.miniflare.dispose();
|
|
2993
|
-
state.miniflare = null;
|
|
2994
|
-
}
|
|
2995
|
-
await disposeLocalWorkerLoaderBindings();
|
|
2996
|
-
await stopActiveContainers();
|
|
2997
|
-
state.envProxy = null;
|
|
2998
|
-
state.transportDecode = null;
|
|
2999
|
-
state.remoteBindings = null;
|
|
3000
|
-
state.miniflareBindings = null;
|
|
3001
|
-
resetQueueState();
|
|
3002
|
-
resetScheduledState();
|
|
3003
|
-
resetWorkerState();
|
|
3004
|
-
resetTailState();
|
|
3005
|
-
resetEmailState();
|
|
3006
|
-
__clearTestContext();
|
|
3007
|
-
};
|
|
3008
|
-
}
|
|
3009
|
-
|
|
3010
|
-
// src/test/simple-context-startup.ts
|
|
3011
|
-
var TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS = 3;
|
|
3012
|
-
var TEST_CONTEXT_STARTUP_RETRY_DELAY_MS = 75;
|
|
3013
|
-
var TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS = 8;
|
|
3014
|
-
var TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS = 150;
|
|
3015
|
-
function isRetriableTestContextStartupError(error) {
|
|
3016
|
-
if (!(error instanceof Error)) {
|
|
3017
|
-
return false;
|
|
3018
|
-
}
|
|
3019
|
-
const message = error.message.toLowerCase();
|
|
3020
|
-
return message.includes("websocket connection failed") || message.includes("connection timeout: ws://") || message.includes("econnrefused") || message.includes("eaddrinuse") || message.includes("address already in use");
|
|
3021
|
-
}
|
|
3022
|
-
async function waitForTestContextStartupRetry() {
|
|
3023
|
-
await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_STARTUP_RETRY_DELAY_MS));
|
|
3024
|
-
}
|
|
3025
|
-
async function waitForBridgeClientRetry() {
|
|
3026
|
-
await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS));
|
|
3027
|
-
}
|
|
3028
|
-
async function connectBridgeClientWithRetry(url) {
|
|
3029
|
-
let lastError;
|
|
3030
|
-
for (let attempt = 1;attempt <= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS; attempt++) {
|
|
3031
|
-
const client = new BridgeClient({ url });
|
|
3032
|
-
try {
|
|
3033
|
-
await client.connect();
|
|
3034
|
-
return client;
|
|
3035
|
-
} catch (error) {
|
|
3036
|
-
lastError = error;
|
|
3037
|
-
client.disconnect();
|
|
3038
|
-
if (attempt >= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) {
|
|
3039
|
-
throw error;
|
|
3040
|
-
}
|
|
3041
|
-
await waitForBridgeClientRetry();
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
throw lastError instanceof Error ? lastError : new Error("Bridge-backed test context could not connect to the WebSocket gateway.");
|
|
3045
|
-
}
|
|
3046
|
-
function expandLocalBindingWorkers(mfConfig) {
|
|
3047
|
-
const auxiliaryWorkers = [
|
|
3048
|
-
...mfConfig.__devflareLocalSecretWorkers ?? [],
|
|
3049
|
-
...mfConfig.__devflareLocalBindingWorkers ?? []
|
|
3050
|
-
];
|
|
3051
|
-
if (!Array.isArray(auxiliaryWorkers) || auxiliaryWorkers.length === 0) {
|
|
3052
|
-
return mfConfig;
|
|
3053
|
-
}
|
|
3054
|
-
const {
|
|
3055
|
-
__devflareLocalSecretWorkers,
|
|
3056
|
-
__devflareLocalBindingWorkers,
|
|
3057
|
-
port,
|
|
3058
|
-
host,
|
|
3059
|
-
log,
|
|
3060
|
-
kvPersist,
|
|
3061
|
-
r2Persist,
|
|
3062
|
-
d1Persist,
|
|
3063
|
-
durableObjectsPersist,
|
|
3064
|
-
workflowsPersist,
|
|
3065
|
-
imagesPersist,
|
|
3066
|
-
...primaryWorker
|
|
3067
|
-
} = mfConfig;
|
|
3068
|
-
const primaryWorkerName = typeof primaryWorker.name === "string" ? primaryWorker.name : "primary";
|
|
3069
|
-
return {
|
|
3070
|
-
...port !== undefined && { port },
|
|
3071
|
-
...host && { host },
|
|
3072
|
-
...log && { log },
|
|
3073
|
-
...kvPersist && { kvPersist },
|
|
3074
|
-
...r2Persist && { r2Persist },
|
|
3075
|
-
...d1Persist && { d1Persist },
|
|
3076
|
-
...durableObjectsPersist && { durableObjectsPersist },
|
|
3077
|
-
...workflowsPersist && { workflowsPersist },
|
|
3078
|
-
...imagesPersist && { imagesPersist },
|
|
3079
|
-
workers: [
|
|
3080
|
-
{
|
|
3081
|
-
...primaryWorker,
|
|
3082
|
-
name: primaryWorkerName
|
|
3083
|
-
},
|
|
3084
|
-
...auxiliaryWorkers
|
|
3085
|
-
]
|
|
3086
|
-
};
|
|
3087
|
-
}
|
|
3088
|
-
async function startBridgeBackedTestContext(mfConfig) {
|
|
3089
|
-
const { Miniflare } = await import("miniflare");
|
|
3090
|
-
for (let attempt = 1;attempt <= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS; attempt++) {
|
|
3091
|
-
const port = await getAvailablePort();
|
|
3092
|
-
let miniflare = null;
|
|
3093
|
-
let client = null;
|
|
3094
|
-
try {
|
|
3095
|
-
miniflare = new Miniflare(expandLocalBindingWorkers({
|
|
3096
|
-
...mfConfig,
|
|
3097
|
-
port
|
|
3098
|
-
}));
|
|
3099
|
-
await miniflare.ready;
|
|
3100
|
-
const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
3101
|
-
client = await connectBridgeClientWithRetry(`ws://localhost:${port}`);
|
|
3102
|
-
return {
|
|
3103
|
-
port,
|
|
3104
|
-
client,
|
|
3105
|
-
miniflare,
|
|
3106
|
-
miniflareBindings
|
|
3107
|
-
};
|
|
2883
|
+
while (Date.now() < deadline) {
|
|
2884
|
+
try {
|
|
2885
|
+
return await fetchImpl(request.clone());
|
|
3108
2886
|
} catch (error) {
|
|
3109
|
-
|
|
3110
|
-
if (miniflare) {
|
|
3111
|
-
try {
|
|
3112
|
-
await miniflare.dispose();
|
|
3113
|
-
} catch {}
|
|
3114
|
-
}
|
|
3115
|
-
if (attempt >= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) {
|
|
2887
|
+
if (!isTransientContainerFetchError(error)) {
|
|
3116
2888
|
throw error;
|
|
3117
2889
|
}
|
|
3118
|
-
|
|
2890
|
+
lastError = error;
|
|
2891
|
+
await delay(100);
|
|
3119
2892
|
}
|
|
3120
2893
|
}
|
|
3121
|
-
throw
|
|
2894
|
+
throw lastError;
|
|
3122
2895
|
}
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
async function bootTestRuntime(mfConfig, usesMultiWorker) {
|
|
3126
|
-
if (usesMultiWorker) {
|
|
3127
|
-
const { Miniflare } = await import("miniflare");
|
|
3128
|
-
const activePort = await getAvailablePort();
|
|
3129
|
-
const miniflare = new Miniflare({
|
|
3130
|
-
...mfConfig,
|
|
3131
|
-
port: activePort
|
|
3132
|
-
});
|
|
3133
|
-
await miniflare.ready;
|
|
3134
|
-
const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
3135
|
-
return { activePort, miniflare, miniflareBindings, client: null };
|
|
3136
|
-
}
|
|
3137
|
-
const started = await startBridgeBackedTestContext(mfConfig);
|
|
3138
|
-
return {
|
|
3139
|
-
activePort: started.port,
|
|
3140
|
-
miniflare: started.miniflare,
|
|
3141
|
-
miniflareBindings: started.miniflareBindings,
|
|
3142
|
-
client: started.client
|
|
3143
|
-
};
|
|
2896
|
+
function canRetryRequest(request) {
|
|
2897
|
+
return (request.method === "GET" || request.method === "HEAD") && request.body === null;
|
|
3144
2898
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
const transportModule = await import(transportPath);
|
|
3151
|
-
if (!transportModule.transport) {
|
|
3152
|
-
console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.
|
|
3153
|
-
Expected: export const transport = { ... }
|
|
3154
|
-
Transport encoding/decoding will be disabled.`);
|
|
3155
|
-
return null;
|
|
3156
|
-
}
|
|
3157
|
-
const decoders = new Map;
|
|
3158
|
-
for (const [typeName, transporter] of Object.entries(transportModule.transport)) {
|
|
3159
|
-
const t = transporter;
|
|
3160
|
-
decoders.set(typeName, t.decode);
|
|
2899
|
+
function isTransientContainerFetchError(error) {
|
|
2900
|
+
const value = error;
|
|
2901
|
+
const code = value.code ?? value.cause?.code;
|
|
2902
|
+
if (code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "UND_ERR_SOCKET") {
|
|
2903
|
+
return true;
|
|
3161
2904
|
}
|
|
3162
|
-
return
|
|
2905
|
+
return typeof value.message === "string" && (value.message.includes("socket connection was closed") || value.message.includes("fetch failed"));
|
|
3163
2906
|
}
|
|
3164
|
-
function
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
2907
|
+
async function waitForTcpPort(host, port, timeoutMs) {
|
|
2908
|
+
const start = Date.now();
|
|
2909
|
+
let lastError;
|
|
2910
|
+
while (Date.now() - start < timeoutMs) {
|
|
2911
|
+
try {
|
|
2912
|
+
await connectOnce(host, port);
|
|
2913
|
+
return;
|
|
2914
|
+
} catch (error) {
|
|
2915
|
+
lastError = error;
|
|
2916
|
+
await delay(100);
|
|
3173
2917
|
}
|
|
3174
2918
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
2919
|
+
const message = lastError instanceof Error ? lastError.message : "timed out";
|
|
2920
|
+
throw new Error(`Timed out waiting for container port ${host}:${port}: ${message}`);
|
|
2921
|
+
}
|
|
2922
|
+
function connectOnce(host, port) {
|
|
2923
|
+
return new Promise((resolveConnection, rejectConnection) => {
|
|
2924
|
+
const socket = createConnection({ host, port });
|
|
2925
|
+
socket.once("connect", () => {
|
|
2926
|
+
socket.destroy();
|
|
2927
|
+
resolveConnection();
|
|
2928
|
+
});
|
|
2929
|
+
socket.once("error", rejectConnection);
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
function looksLikeLocalPath(image) {
|
|
2933
|
+
return image === "Dockerfile" || image.startsWith(".") || image.startsWith("/") || image.startsWith("\\") || isAbsolute(image) || image.endsWith("/Dockerfile") || image.endsWith("\\Dockerfile");
|
|
2934
|
+
}
|
|
2935
|
+
function getOfflineBuildPullArg(engine) {
|
|
2936
|
+
return engine === "podman" ? "--pull=never" : "--pull=false";
|
|
2937
|
+
}
|
|
2938
|
+
function makeLocalImageTag(className, configDir6, image) {
|
|
2939
|
+
const hash = createHash("sha256").update(`${configDir6}:${image}`).digest("hex").slice(0, 12);
|
|
2940
|
+
return `devflare-local-${sanitizeName(className)}:${hash}`;
|
|
2941
|
+
}
|
|
2942
|
+
function makeContainerName(className, instance) {
|
|
2943
|
+
return `devflare-${sanitizeName(className)}-${sanitizeName(instance ?? "default")}-${randomUUID().slice(0, 8)}`;
|
|
2944
|
+
}
|
|
2945
|
+
function sanitizeName(value) {
|
|
2946
|
+
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "container";
|
|
2947
|
+
}
|
|
2948
|
+
function formatCommandFailure(result) {
|
|
2949
|
+
return (result.stderr || result.stdout || `exit code ${result.exitCode}`).trim();
|
|
2950
|
+
}
|
|
2951
|
+
function isTruthyEnvFlag(value) {
|
|
2952
|
+
return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
|
|
3183
2953
|
}
|
|
3184
2954
|
|
|
3185
|
-
// src/test/simple-context-
|
|
3186
|
-
function
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
...mfConfig.kvNamespaces && { kvNamespaces: mfConfig.kvNamespaces },
|
|
3197
|
-
...mfConfig.r2Buckets && { r2Buckets: mfConfig.r2Buckets },
|
|
3198
|
-
...mfConfig.d1Databases && { d1Databases: mfConfig.d1Databases },
|
|
3199
|
-
...mfConfig.ratelimits && { ratelimits: mfConfig.ratelimits },
|
|
3200
|
-
...mfConfig.versionMetadata && { versionMetadata: mfConfig.versionMetadata },
|
|
3201
|
-
...mfConfig.workerLoaders && { workerLoaders: mfConfig.workerLoaders },
|
|
3202
|
-
...mfConfig.mtlsCertificates && { mtlsCertificates: mfConfig.mtlsCertificates },
|
|
3203
|
-
...mfConfig.dispatchNamespaces && { dispatchNamespaces: mfConfig.dispatchNamespaces },
|
|
3204
|
-
...mfConfig.workflows && { workflows: mfConfig.workflows },
|
|
3205
|
-
...mfConfig.pipelines && { pipelines: mfConfig.pipelines },
|
|
3206
|
-
...mfConfig.images && { images: mfConfig.images },
|
|
3207
|
-
...mfConfig.media && { media: mfConfig.media },
|
|
3208
|
-
...mfConfig.artifacts && { artifacts: mfConfig.artifacts },
|
|
3209
|
-
...mfConfig.secretsStoreSecrets && { secretsStoreSecrets: mfConfig.secretsStoreSecrets },
|
|
3210
|
-
...mfConfig.wrappedBindings && { wrappedBindings: mfConfig.wrappedBindings },
|
|
3211
|
-
...mfConfig.email && { email: mfConfig.email },
|
|
3212
|
-
...Object.keys(primaryDurableObjects).length > 0 && { durableObjects: primaryDurableObjects },
|
|
3213
|
-
...mfConfig.serviceBindings || serviceBindingResolution?.primaryServiceBindings ? {
|
|
3214
|
-
serviceBindings: {
|
|
3215
|
-
...mfConfig.serviceBindings ?? {},
|
|
3216
|
-
...serviceBindingResolution?.primaryServiceBindings ?? {}
|
|
3217
|
-
}
|
|
3218
|
-
} : {}
|
|
3219
|
-
};
|
|
3220
|
-
const additionalWorkers = [
|
|
3221
|
-
...serviceBindingResolution?.workers || [],
|
|
3222
|
-
...doBindingResolution?.workers || [],
|
|
3223
|
-
...mfConfig.__devflareLocalSecretWorkers || [],
|
|
3224
|
-
...mfConfig.__devflareLocalBindingWorkers || []
|
|
3225
|
-
];
|
|
3226
|
-
const workersByName = new Map;
|
|
3227
|
-
for (const worker2 of additionalWorkers) {
|
|
3228
|
-
if (!workersByName.has(worker2.name)) {
|
|
3229
|
-
workersByName.set(worker2.name, worker2);
|
|
3230
|
-
continue;
|
|
3231
|
-
}
|
|
3232
|
-
const existing = workersByName.get(worker2.name);
|
|
3233
|
-
if (worker2.durableObjects) {
|
|
3234
|
-
existing.durableObjects = {
|
|
3235
|
-
...existing.durableObjects || {},
|
|
3236
|
-
...worker2.durableObjects
|
|
3237
|
-
};
|
|
2955
|
+
// src/test/simple-context-lifecycle.ts
|
|
2956
|
+
async function resolveTestContextConfig(configPath, callerDir = getCallerDirectory()) {
|
|
2957
|
+
let absolutePath;
|
|
2958
|
+
if (configPath) {
|
|
2959
|
+
absolutePath = resolve2(callerDir, configPath);
|
|
2960
|
+
} else {
|
|
2961
|
+
const found = await findNearestConfig(callerDir);
|
|
2962
|
+
if (!found) {
|
|
2963
|
+
throw new Error(`Could not find a devflare config file. Searched upward from: ${callerDir}
|
|
2964
|
+
` + `Expected one of: devflare.config.ts, devflare.config.mts, devflare.config.js, devflare.config.mjs
|
|
2965
|
+
` + `Either create a config file or provide an explicit path: createTestContext('./path/to/config.ts')`);
|
|
3238
2966
|
}
|
|
2967
|
+
absolutePath = found;
|
|
3239
2968
|
}
|
|
3240
|
-
const
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
2969
|
+
const configDir6 = dirname4(absolutePath);
|
|
2970
|
+
const loadedConfig = await loadConfig({
|
|
2971
|
+
cwd: configDir6,
|
|
2972
|
+
configFile: absolutePath.split(/[/\\]/).pop()
|
|
2973
|
+
});
|
|
2974
|
+
const envResolvedConfig = await resolveConfigEnvVars(loadedConfig, {
|
|
2975
|
+
cwd: configDir6,
|
|
2976
|
+
configPath: absolutePath,
|
|
2977
|
+
mode: "dev"
|
|
2978
|
+
});
|
|
2979
|
+
const config = await applyLocalDevVarsToConfig(envResolvedConfig, {
|
|
2980
|
+
cwd: configDir6,
|
|
2981
|
+
configPath: absolutePath
|
|
2982
|
+
});
|
|
2983
|
+
return { absolutePath, configDir: configDir6, config };
|
|
2984
|
+
}
|
|
2985
|
+
function createDisposeContext(state) {
|
|
2986
|
+
return async () => {
|
|
2987
|
+
if (state.client) {
|
|
2988
|
+
await state.client.disconnect();
|
|
2989
|
+
state.client = null;
|
|
2990
|
+
}
|
|
2991
|
+
if (state.miniflare) {
|
|
2992
|
+
await state.miniflare.dispose();
|
|
2993
|
+
state.miniflare = null;
|
|
2994
|
+
}
|
|
2995
|
+
await disposeLocalWorkerLoaderBindings();
|
|
2996
|
+
await stopActiveContainers();
|
|
2997
|
+
state.envProxy = null;
|
|
2998
|
+
state.transportDecode = null;
|
|
2999
|
+
state.remoteBindings = null;
|
|
3000
|
+
state.miniflareBindings = null;
|
|
3001
|
+
resetQueueState();
|
|
3002
|
+
resetScheduledState();
|
|
3003
|
+
resetWorkerState();
|
|
3004
|
+
resetTailState();
|
|
3005
|
+
resetEmailState();
|
|
3006
|
+
__clearTestContext();
|
|
3007
|
+
};
|
|
3263
3008
|
}
|
|
3264
3009
|
|
|
3265
3010
|
// src/test/simple-context-mfconfig.ts
|
|
@@ -3410,13 +3155,15 @@ function buildInlineBridgeMfConfig(config, options = {}) {
|
|
|
3410
3155
|
return [];
|
|
3411
3156
|
}
|
|
3412
3157
|
const normalized = normalizeSecretsStoreBinding(binding, config.secretsStoreId, bindingName);
|
|
3413
|
-
return [
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3158
|
+
return [
|
|
3159
|
+
[
|
|
3160
|
+
bindingName,
|
|
3161
|
+
{
|
|
3162
|
+
store_id: normalized.storeId,
|
|
3163
|
+
secret_name: normalized.secretName
|
|
3164
|
+
}
|
|
3165
|
+
]
|
|
3166
|
+
];
|
|
3420
3167
|
});
|
|
3421
3168
|
if (secretsStoreEntries.length > 0) {
|
|
3422
3169
|
mfConfig.secretsStoreSecrets = Object.fromEntries(secretsStoreEntries);
|
|
@@ -3463,6 +3210,261 @@ function buildInlineBridgeMfConfig(config, options = {}) {
|
|
|
3463
3210
|
return mfConfig;
|
|
3464
3211
|
}
|
|
3465
3212
|
|
|
3213
|
+
// src/test/simple-context-multi-worker.ts
|
|
3214
|
+
function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBindingResolution) {
|
|
3215
|
+
const primaryDurableObjects = {
|
|
3216
|
+
...mfConfig.durableObjects || {},
|
|
3217
|
+
...doBindingResolution?.crossWorkerDOBindings || {}
|
|
3218
|
+
};
|
|
3219
|
+
const primaryWorker = {
|
|
3220
|
+
name: config.name ?? "primary",
|
|
3221
|
+
modules: true,
|
|
3222
|
+
script: mfConfig.script,
|
|
3223
|
+
compatibilityDate: config.compatibilityDate ?? "2025-01-01",
|
|
3224
|
+
...mfConfig.kvNamespaces && { kvNamespaces: mfConfig.kvNamespaces },
|
|
3225
|
+
...mfConfig.r2Buckets && { r2Buckets: mfConfig.r2Buckets },
|
|
3226
|
+
...mfConfig.d1Databases && { d1Databases: mfConfig.d1Databases },
|
|
3227
|
+
...mfConfig.ratelimits && { ratelimits: mfConfig.ratelimits },
|
|
3228
|
+
...mfConfig.versionMetadata && { versionMetadata: mfConfig.versionMetadata },
|
|
3229
|
+
...mfConfig.workerLoaders && { workerLoaders: mfConfig.workerLoaders },
|
|
3230
|
+
...mfConfig.mtlsCertificates && { mtlsCertificates: mfConfig.mtlsCertificates },
|
|
3231
|
+
...mfConfig.dispatchNamespaces && { dispatchNamespaces: mfConfig.dispatchNamespaces },
|
|
3232
|
+
...mfConfig.workflows && { workflows: mfConfig.workflows },
|
|
3233
|
+
...mfConfig.pipelines && { pipelines: mfConfig.pipelines },
|
|
3234
|
+
...mfConfig.images && { images: mfConfig.images },
|
|
3235
|
+
...mfConfig.media && { media: mfConfig.media },
|
|
3236
|
+
...mfConfig.artifacts && { artifacts: mfConfig.artifacts },
|
|
3237
|
+
...mfConfig.secretsStoreSecrets && { secretsStoreSecrets: mfConfig.secretsStoreSecrets },
|
|
3238
|
+
...mfConfig.wrappedBindings && { wrappedBindings: mfConfig.wrappedBindings },
|
|
3239
|
+
...mfConfig.email && { email: mfConfig.email },
|
|
3240
|
+
...Object.keys(primaryDurableObjects).length > 0 && { durableObjects: primaryDurableObjects },
|
|
3241
|
+
...mfConfig.serviceBindings || serviceBindingResolution?.primaryServiceBindings ? {
|
|
3242
|
+
serviceBindings: {
|
|
3243
|
+
...mfConfig.serviceBindings ?? {},
|
|
3244
|
+
...serviceBindingResolution?.primaryServiceBindings ?? {}
|
|
3245
|
+
}
|
|
3246
|
+
} : {}
|
|
3247
|
+
};
|
|
3248
|
+
const additionalWorkers = [
|
|
3249
|
+
...serviceBindingResolution?.workers || [],
|
|
3250
|
+
...doBindingResolution?.workers || [],
|
|
3251
|
+
...mfConfig.__devflareLocalSecretWorkers || [],
|
|
3252
|
+
...mfConfig.__devflareLocalBindingWorkers || []
|
|
3253
|
+
];
|
|
3254
|
+
const workersByName = new Map;
|
|
3255
|
+
for (const worker2 of additionalWorkers) {
|
|
3256
|
+
if (!workersByName.has(worker2.name)) {
|
|
3257
|
+
workersByName.set(worker2.name, worker2);
|
|
3258
|
+
continue;
|
|
3259
|
+
}
|
|
3260
|
+
const existing = workersByName.get(worker2.name);
|
|
3261
|
+
if (worker2.durableObjects) {
|
|
3262
|
+
existing.durableObjects = {
|
|
3263
|
+
...existing.durableObjects || {},
|
|
3264
|
+
...worker2.durableObjects
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
const workers = [primaryWorker, ...workersByName.values()];
|
|
3269
|
+
delete mfConfig.script;
|
|
3270
|
+
delete mfConfig.modules;
|
|
3271
|
+
delete mfConfig.kvNamespaces;
|
|
3272
|
+
delete mfConfig.r2Buckets;
|
|
3273
|
+
delete mfConfig.d1Databases;
|
|
3274
|
+
delete mfConfig.ratelimits;
|
|
3275
|
+
delete mfConfig.versionMetadata;
|
|
3276
|
+
delete mfConfig.workerLoaders;
|
|
3277
|
+
delete mfConfig.mtlsCertificates;
|
|
3278
|
+
delete mfConfig.dispatchNamespaces;
|
|
3279
|
+
delete mfConfig.workflows;
|
|
3280
|
+
delete mfConfig.pipelines;
|
|
3281
|
+
delete mfConfig.images;
|
|
3282
|
+
delete mfConfig.media;
|
|
3283
|
+
delete mfConfig.artifacts;
|
|
3284
|
+
delete mfConfig.secretsStoreSecrets;
|
|
3285
|
+
delete mfConfig.wrappedBindings;
|
|
3286
|
+
delete mfConfig.serviceBindings;
|
|
3287
|
+
delete mfConfig.__devflareLocalSecretWorkers;
|
|
3288
|
+
delete mfConfig.__devflareLocalBindingWorkers;
|
|
3289
|
+
delete mfConfig.durableObjects;
|
|
3290
|
+
mfConfig.workers = workers;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
// src/test/simple-context-startup.ts
|
|
3294
|
+
var TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS = 3;
|
|
3295
|
+
var TEST_CONTEXT_STARTUP_RETRY_DELAY_MS = 75;
|
|
3296
|
+
var TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS = 8;
|
|
3297
|
+
var TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS = 150;
|
|
3298
|
+
function isRetriableTestContextStartupError(error) {
|
|
3299
|
+
if (!(error instanceof Error)) {
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3302
|
+
const message = error.message.toLowerCase();
|
|
3303
|
+
return message.includes("websocket connection failed") || message.includes("connection timeout: ws://") || message.includes("econnrefused") || message.includes("eaddrinuse") || message.includes("address already in use");
|
|
3304
|
+
}
|
|
3305
|
+
async function waitForTestContextStartupRetry() {
|
|
3306
|
+
await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_STARTUP_RETRY_DELAY_MS));
|
|
3307
|
+
}
|
|
3308
|
+
async function waitForBridgeClientRetry() {
|
|
3309
|
+
await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS));
|
|
3310
|
+
}
|
|
3311
|
+
async function connectBridgeClientWithRetry(url) {
|
|
3312
|
+
let lastError;
|
|
3313
|
+
for (let attempt = 1;attempt <= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS; attempt++) {
|
|
3314
|
+
const client = new BridgeClient({ url });
|
|
3315
|
+
try {
|
|
3316
|
+
await client.connect();
|
|
3317
|
+
return client;
|
|
3318
|
+
} catch (error) {
|
|
3319
|
+
lastError = error;
|
|
3320
|
+
client.disconnect();
|
|
3321
|
+
if (attempt >= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) {
|
|
3322
|
+
throw error;
|
|
3323
|
+
}
|
|
3324
|
+
await waitForBridgeClientRetry();
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
throw lastError instanceof Error ? lastError : new Error("Bridge-backed test context could not connect to the WebSocket gateway.");
|
|
3328
|
+
}
|
|
3329
|
+
function expandLocalBindingWorkers(mfConfig) {
|
|
3330
|
+
const auxiliaryWorkers = [
|
|
3331
|
+
...mfConfig.__devflareLocalSecretWorkers ?? [],
|
|
3332
|
+
...mfConfig.__devflareLocalBindingWorkers ?? []
|
|
3333
|
+
];
|
|
3334
|
+
if (!Array.isArray(auxiliaryWorkers) || auxiliaryWorkers.length === 0) {
|
|
3335
|
+
return mfConfig;
|
|
3336
|
+
}
|
|
3337
|
+
const {
|
|
3338
|
+
__devflareLocalSecretWorkers,
|
|
3339
|
+
__devflareLocalBindingWorkers,
|
|
3340
|
+
port,
|
|
3341
|
+
host,
|
|
3342
|
+
log,
|
|
3343
|
+
kvPersist,
|
|
3344
|
+
r2Persist,
|
|
3345
|
+
d1Persist,
|
|
3346
|
+
durableObjectsPersist,
|
|
3347
|
+
workflowsPersist,
|
|
3348
|
+
imagesPersist,
|
|
3349
|
+
...primaryWorker
|
|
3350
|
+
} = mfConfig;
|
|
3351
|
+
const primaryWorkerName = typeof primaryWorker.name === "string" ? primaryWorker.name : "primary";
|
|
3352
|
+
return {
|
|
3353
|
+
...port !== undefined && { port },
|
|
3354
|
+
...host && { host },
|
|
3355
|
+
...log && { log },
|
|
3356
|
+
...kvPersist && { kvPersist },
|
|
3357
|
+
...r2Persist && { r2Persist },
|
|
3358
|
+
...d1Persist && { d1Persist },
|
|
3359
|
+
...durableObjectsPersist && { durableObjectsPersist },
|
|
3360
|
+
...workflowsPersist && { workflowsPersist },
|
|
3361
|
+
...imagesPersist && { imagesPersist },
|
|
3362
|
+
workers: [
|
|
3363
|
+
{
|
|
3364
|
+
...primaryWorker,
|
|
3365
|
+
name: primaryWorkerName
|
|
3366
|
+
},
|
|
3367
|
+
...auxiliaryWorkers
|
|
3368
|
+
]
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
async function startBridgeBackedTestContext(mfConfig) {
|
|
3372
|
+
const { Miniflare } = await import("miniflare");
|
|
3373
|
+
for (let attempt = 1;attempt <= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS; attempt++) {
|
|
3374
|
+
const port = await getAvailablePort();
|
|
3375
|
+
let miniflare = null;
|
|
3376
|
+
let client = null;
|
|
3377
|
+
try {
|
|
3378
|
+
miniflare = new Miniflare(expandLocalBindingWorkers({
|
|
3379
|
+
...mfConfig,
|
|
3380
|
+
port
|
|
3381
|
+
}));
|
|
3382
|
+
await miniflare.ready;
|
|
3383
|
+
const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
3384
|
+
client = await connectBridgeClientWithRetry(`ws://localhost:${port}`);
|
|
3385
|
+
return {
|
|
3386
|
+
port,
|
|
3387
|
+
client,
|
|
3388
|
+
miniflare,
|
|
3389
|
+
miniflareBindings
|
|
3390
|
+
};
|
|
3391
|
+
} catch (error) {
|
|
3392
|
+
client?.disconnect();
|
|
3393
|
+
if (miniflare) {
|
|
3394
|
+
try {
|
|
3395
|
+
await miniflare.dispose();
|
|
3396
|
+
} catch {}
|
|
3397
|
+
}
|
|
3398
|
+
if (attempt >= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) {
|
|
3399
|
+
throw error;
|
|
3400
|
+
}
|
|
3401
|
+
await waitForTestContextStartupRetry();
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
throw new Error("Bridge-backed test context startup exhausted all retry attempts.");
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
// src/test/simple-context-runtime.ts
|
|
3408
|
+
async function bootTestRuntime(mfConfig, usesMultiWorker) {
|
|
3409
|
+
if (usesMultiWorker) {
|
|
3410
|
+
const { Miniflare } = await import("miniflare");
|
|
3411
|
+
const activePort = await getAvailablePort();
|
|
3412
|
+
const miniflare = new Miniflare({
|
|
3413
|
+
...mfConfig,
|
|
3414
|
+
port: activePort
|
|
3415
|
+
});
|
|
3416
|
+
await miniflare.ready;
|
|
3417
|
+
const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
3418
|
+
return { activePort, miniflare, miniflareBindings, client: null };
|
|
3419
|
+
}
|
|
3420
|
+
const started = await startBridgeBackedTestContext(mfConfig);
|
|
3421
|
+
return {
|
|
3422
|
+
activePort: started.port,
|
|
3423
|
+
miniflare: started.miniflare,
|
|
3424
|
+
miniflareBindings: started.miniflareBindings,
|
|
3425
|
+
client: started.client
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
// src/test/simple-context-transport.ts
|
|
3430
|
+
import { join as join10 } from "path";
|
|
3431
|
+
async function loadTransportDecoders(configDir6, transportFile) {
|
|
3432
|
+
const transportPath = join10(configDir6, transportFile);
|
|
3433
|
+
const transportModule = await import(transportPath);
|
|
3434
|
+
if (!transportModule.transport) {
|
|
3435
|
+
console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.
|
|
3436
|
+
Expected: export const transport = { ... }
|
|
3437
|
+
Transport encoding/decoding will be disabled.`);
|
|
3438
|
+
return null;
|
|
3439
|
+
}
|
|
3440
|
+
const decoders = new Map;
|
|
3441
|
+
for (const [typeName, transporter] of Object.entries(transportModule.transport)) {
|
|
3442
|
+
const t = transporter;
|
|
3443
|
+
decoders.set(typeName, t.decode);
|
|
3444
|
+
}
|
|
3445
|
+
return decoders;
|
|
3446
|
+
}
|
|
3447
|
+
function decodeTransportValue(decoders, value) {
|
|
3448
|
+
if (!decoders || value === null || typeof value !== "object") {
|
|
3449
|
+
return value;
|
|
3450
|
+
}
|
|
3451
|
+
if ("__transport" in value) {
|
|
3452
|
+
const encoded = value;
|
|
3453
|
+
const decoder = decoders.get(encoded.__transport);
|
|
3454
|
+
if (decoder) {
|
|
3455
|
+
return decoder(encoded.value);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
if (Array.isArray(value)) {
|
|
3459
|
+
return value.map((item) => decodeTransportValue(decoders, item));
|
|
3460
|
+
}
|
|
3461
|
+
const result = {};
|
|
3462
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3463
|
+
result[k] = decodeTransportValue(decoders, v);
|
|
3464
|
+
}
|
|
3465
|
+
return result;
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3466
3468
|
// src/test/simple-context.ts
|
|
3467
3469
|
function createTestContextState() {
|
|
3468
3470
|
return {
|