From 28cd7361a6386913b62389705c335dd1b12d1dd6 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 26 Sep 2022 13:59:36 +0100 Subject: [PATCH] D1 support (#1687) * First version of D1 support in Wrangler * Bumped to miniflare v2.9.0 * Changes based on PR feedback * Added clarifying comment to performApiFetch * Cast Request objects in test to avoid typing issue * moved npx-import to be a dev dep, since it is bundled * added comment to readableRelative * added Beta warning message on all D1 commands (and --help) * adding d1_database to the safe bindings list * Added D1 warning, and env var to silence it, on detecting D1 bindings * updated changeset Co-authored-by: Glen Maddern Co-authored-by: Pete Bacon Darwin --- .changeset/witty-rice-pull.md | 5 + CONTRIBUTING.md | 35 + package-lock.json | 708 +++++++++++++----- packages/pages-shared/package.json | 6 +- packages/wrangler/package.json | 14 +- .../src/__tests__/configuration.test.ts | 147 +++- packages/wrangler/src/__tests__/dev.test.tsx | 2 - .../src/__tests__/helpers/mock-oauth-flow.ts | 6 +- packages/wrangler/src/__tests__/index.test.ts | 2 + packages/wrangler/src/__tests__/paths.test.ts | 24 +- .../wrangler/src/__tests__/publish.test.ts | 18 +- packages/wrangler/src/__tests__/user.test.ts | 8 +- .../wrangler/src/__tests__/whoami.test.tsx | 1 - packages/wrangler/src/api/dev.ts | 2 + packages/wrangler/src/bundle.ts | 48 ++ packages/wrangler/src/cfetch/internal.ts | 58 +- packages/wrangler/src/config/environment.ts | 20 + packages/wrangler/src/config/index.ts | 32 + packages/wrangler/src/config/validation.ts | 59 ++ .../wrangler/src/create-worker-upload-form.ts | 9 + packages/wrangler/src/d1/backups.tsx | 212 ++++++ packages/wrangler/src/d1/create.tsx | 54 ++ packages/wrangler/src/d1/delete.tsx | 56 ++ packages/wrangler/src/d1/execute.tsx | 294 ++++++++ packages/wrangler/src/d1/formatTimeAgo.ts | 14 + packages/wrangler/src/d1/index.ts | 75 ++ packages/wrangler/src/d1/list.tsx | 48 ++ packages/wrangler/src/d1/options.ts | 12 + packages/wrangler/src/d1/types.tsx | 14 + packages/wrangler/src/d1/utils.ts | 39 + packages/wrangler/src/dev.tsx | 39 +- packages/wrangler/src/dev/dev.tsx | 27 +- .../src/dev/get-local-persistence-path.tsx | 31 + packages/wrangler/src/dev/local.tsx | 7 + packages/wrangler/src/dev/start-server.ts | 2 + packages/wrangler/src/dev/use-esbuild.ts | 9 +- packages/wrangler/src/dialogs.tsx | 4 + packages/wrangler/src/index.tsx | 34 +- packages/wrangler/src/pages/dev.tsx | 11 + packages/wrangler/src/paths.ts | 34 +- packages/wrangler/src/publish.ts | 6 + packages/wrangler/src/user/user.tsx | 1 + packages/wrangler/src/worker.ts | 30 + packages/wrangler/templates/d1-beta-facade.js | 174 +++++ 44 files changed, 2129 insertions(+), 302 deletions(-) create mode 100644 .changeset/witty-rice-pull.md create mode 100644 packages/wrangler/src/d1/backups.tsx create mode 100644 packages/wrangler/src/d1/create.tsx create mode 100644 packages/wrangler/src/d1/delete.tsx create mode 100644 packages/wrangler/src/d1/execute.tsx create mode 100644 packages/wrangler/src/d1/formatTimeAgo.ts create mode 100644 packages/wrangler/src/d1/index.ts create mode 100644 packages/wrangler/src/d1/list.tsx create mode 100644 packages/wrangler/src/d1/options.ts create mode 100644 packages/wrangler/src/d1/types.tsx create mode 100644 packages/wrangler/src/d1/utils.ts create mode 100644 packages/wrangler/src/dev/get-local-persistence-path.tsx create mode 100644 packages/wrangler/templates/d1-beta-facade.js diff --git a/.changeset/witty-rice-pull.md b/.changeset/witty-rice-pull.md new file mode 100644 index 00000000000..42eb8f6b039 --- /dev/null +++ b/.changeset/witty-rice-pull.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +Wrangler now supports the beta release of D1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab04fef60df..fc57e8e9b4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -224,3 +224,38 @@ We use the following guidelines to determine the kind of change for a PR: - Bugfixes and new features are considered to be 'patch' changes. If the new feature is experimental and its behaviour may functionally change, be sure to log warnings whenever they're used. (You'll note that this is where we deviate from semver, which otherwise suggests that behaviour/api changes should go into minor releases. We may revisit this in the future.) - New deprecation warnings for future breaking changes are considered as 'minor' changes. These changes shouldn't break existing code, but the deprecation warnings should suggest alternate solutions to not trigger the warning. - Breaking changes are considered to be 'major' changes. These are usually when deprecations take effect, or functional breaking behaviour is added with relevant logs (either as errors or warnings.) + +## Miniflare Development + +Wrangler builds upon, and provides a new entry point for, [Miniflare](https://github.com/cloudflare/miniflare), a local Cloudflare Workers simulator. To develop on both Wrangler and Miniflare together, you need to link the two projects, but as of NodeJS `v18.3.0` and NPM `v8.15.0`, relative NPM installs between two workspaces don't work, so you need things to be manual: + +Assume you have the two directories checked out right beside each other: + +``` +❯ ll src +drwxr-xr-x - user 30 Jun 14:12 src +drwxr-xr-x - user 26 Jul 17:34 ├── miniflare +drwxr-xr-x - user 27 Jul 17:51 └── wrangler2 +``` + +> Note: recommend using [exa](https://the.exa.website/) and `alias ll='exa --icons -laTL 1'` for the above output + +Inside `packages/wrangler/package.json`, replace: + +``` +"@miniflare/d1": "^2.x.x", +"@miniflare/core": "^2.x.x", +"@miniflare/durable-objects": "^2.x.x", +"miniflare": "^2.x.x", +``` + +with + +``` +"miniflare": "file:../../../miniflare/packages/miniflare", +"@miniflare/d1": "file:../../../miniflare/packages/d1", +"@miniflare/core": "file:../../../miniflare/packages/core", +"@miniflare/durable-objects": "file:../../../miniflare/packages/durable-objects", +``` + +Then run `npm install` in the root of this monorepo. diff --git a/package-lock.json b/package-lock.json index 4b943446316..b9ca8b4e5da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -888,6 +888,21 @@ "node": ">=0.1.95" } }, + "node_modules/@databases/split-sql-query": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@databases/split-sql-query/-/split-sql-query-1.0.3.tgz", + "integrity": "sha512-Q3UYX85e34yE9KXa095AJtJhBQ0NpLfC0kS9ydFKuNB25cto4YddY52RuXN81m2t0pS1Atg31ylNpKfNCnUPdA==", + "dev": true, + "peerDependencies": { + "@databases/sql": "*" + } + }, + "node_modules/@databases/sql": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@databases/sql/-/sql-3.2.0.tgz", + "integrity": "sha512-xQZzKIa0lvcdo0MYxnyFMVS1TRla9lpDSCYkobJl19vQEOJ9TqE4o8QBGRJNUfhSkbQIWyvMeBl3KBBbqyUVQQ==", + "dev": true + }, "node_modules/@esbuild-plugins/node-globals-polyfill": { "version": "0.1.1", "license": "ISC", @@ -2459,11 +2474,12 @@ } }, "node_modules/@miniflare/cache": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.9.0.tgz", + "integrity": "sha512-lriPxUEva9TJ01vU9P7pI60s3SsFnb4apWkNwZ+D7CRqyXPipSbapY8BWI2FUIwkEG7xap6UhzeTS76NettCXQ==", "dependencies": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "http-cache-semantics": "^4.1.0", "undici": "5.9.1" }, @@ -2472,10 +2488,11 @@ } }, "node_modules/@miniflare/cli-parser": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.9.0.tgz", + "integrity": "sha512-gu8Z7NWNcYw6514/yOvajaj3GmebRucx+EEt3p1vKirO+gvFgKAt/puyUN3p7u8ZZmLuLF/B+wVnH3lj8BWKlg==", "dependencies": { - "@miniflare/shared": "2.8.1", + "@miniflare/shared": "2.9.0", "kleur": "^4.1.4" }, "engines": { @@ -2483,13 +2500,14 @@ } }, "node_modules/@miniflare/core": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.9.0.tgz", + "integrity": "sha512-QqSwF6oHvgrFvN5lnrLc6EEagFlZWW+UMU8QdrE8305cNGHrIOxKCA2nte4PVFZUVw/Ts13a0tVhUk3a2fAyxQ==", "dependencies": { "@iarna/toml": "^2.2.5", - "@miniflare/queues": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/watcher": "2.8.1", + "@miniflare/queues": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/watcher": "2.9.0", "busboy": "^1.6.0", "dotenv": "^10.0.0", "kleur": "^4.1.4", @@ -2503,18 +2521,32 @@ }, "node_modules/@miniflare/core/node_modules/dotenv": { "version": "10.0.0", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", "engines": { "node": ">=10" } }, + "node_modules/@miniflare/d1": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.9.0.tgz", + "integrity": "sha512-swK9nzxw1SvVh/4cH3bRR1SBuHQU/YsB8WvuHojxufmgviAD1xhms3XO3rkpAzfKoGM5Oy6DovMe0xUXV/GS0w==", + "dependencies": { + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0" + }, + "engines": { + "node": ">=16.7" + } + }, "node_modules/@miniflare/durable-objects": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.9.0.tgz", + "integrity": "sha512-7uTvfEUXS7xqwrsWOwWrFUuKc4EiMpVkAWPeYGLB/0TJaJ6N+sZMpYYymdW79TQwPIDfgtpfkIy93MRydqpnrw==", "dependencies": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/storage-memory": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/storage-memory": "2.9.0", "undici": "5.9.1" }, "engines": { @@ -2522,11 +2554,12 @@ } }, "node_modules/@miniflare/html-rewriter": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.9.0.tgz", + "integrity": "sha512-K5OB70PtkMo7M+tU46s/cX/j/qtjD9AlJ0hecYswrxVsfrT/YWyrCQJevmShFfJ92h7jPNigbeC3Od3JiVb6QA==", "dependencies": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "html-rewriter-wasm": "^0.4.1", "undici": "5.9.1" }, @@ -2535,12 +2568,13 @@ } }, "node_modules/@miniflare/http-server": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.9.0.tgz", + "integrity": "sha512-IVJMkFfMpecq9WiCTvATEKhMuKPK9fMs2E6zmgexaefr3u1VlNtj2QxBxoPUXkT9xMJQlT5sSKstlRR1XKDz9Q==", "dependencies": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/web-sockets": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/web-sockets": "2.9.0", "kleur": "^4.1.4", "selfsigned": "^2.0.0", "undici": "5.9.1", @@ -2552,8 +2586,9 @@ } }, "node_modules/@miniflare/http-server/node_modules/ws": { - "version": "8.8.1", - "license": "MIT", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "engines": { "node": ">=10.0.0" }, @@ -2571,30 +2606,33 @@ } }, "node_modules/@miniflare/kv": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.9.0.tgz", + "integrity": "sha512-EqG51okY5rDtgjYs2Ny6j6IUVdTlJzDjwBKBIuW+wOV9NsAAzEchKVdYAXc8CyxvkggpYX481HydTD2OzK3INQ==", "dependencies": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/queues": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.9.0.tgz", + "integrity": "sha512-cAHWIlLF57rxQaJl19AzXw1k0SOM/uLTlx8r2PylHajZ/RRSs7CkCox3oKA6E5zKyfyxk2M64bmsAFZ9RCA0gw==", "dependencies": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" }, "engines": { "node": ">=16.7" } }, "node_modules/@miniflare/r2": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.9.0.tgz", + "integrity": "sha512-aMFWxxciAE3YsVok2OLy3A7hP5+2j/NaK7txmadgoe1CA8HYZyNuvv7v6bn8HKM5gWnJdT8sk4yEbMbBQ7Jv/A==", "dependencies": { - "@miniflare/shared": "2.8.1", + "@miniflare/shared": "2.9.0", "undici": "5.9.1" }, "engines": { @@ -2602,21 +2640,23 @@ } }, "node_modules/@miniflare/runner-vm": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.9.0.tgz", + "integrity": "sha512-vewP+Fy7Czb261GmB9x/YtQkoDs/QP9B5LbP0YfJ35bI2C2j940eJLm8JP72IHV7ILtWNOqMc3Ure8uAbpf9NQ==", "dependencies": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/scheduler": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.9.0.tgz", + "integrity": "sha512-eodSCGkJYi4Z+Imbx/bNScDfDSt5HOypVSYjbFHj+hA2aNOdkGw6a1b6mzwx49jJD3GadIkonZAKD0S114yWMA==", "dependencies": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "cron-schedule": "^3.0.4" }, "engines": { @@ -2624,10 +2664,13 @@ } }, "node_modules/@miniflare/shared": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.9.0.tgz", + "integrity": "sha512-5Ew/Ph0cHDQqKvOlmN70kz+qZW0hdgE9fQBStKLY3vDYhnBEhopbCUChSS+FCcL7WtxVJJVE7iB6J09NQTnQ/A==", "dependencies": { + "@types/better-sqlite3": "^7.6.0", "kleur": "^4.1.4", + "npx-import": "^1.1.3", "picomatch": "^2.3.1" }, "engines": { @@ -2635,54 +2678,59 @@ } }, "node_modules/@miniflare/sites": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.9.0.tgz", + "integrity": "sha512-+tWf7znxSQqXWGzPup8Xqkl8EmLmx+HaLC+UBtWPNnaJZrsjbbVxKwHpmGIdm+wZasEGfQk/82R21gUs9wdZnw==", "dependencies": { - "@miniflare/kv": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/storage-file": "2.8.1" + "@miniflare/kv": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/storage-file": "2.9.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/storage-file": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.9.0.tgz", + "integrity": "sha512-HZHtHfJaLoDzQFddoIMcDGgAJ3/Nee98gwUYusQam7rj9pbEXnWmk54dzjzsDlkQpB/3MBFQNbtN5Bj1NIt0pg==", "dependencies": { - "@miniflare/shared": "2.8.1", - "@miniflare/storage-memory": "2.8.1" + "@miniflare/shared": "2.9.0", + "@miniflare/storage-memory": "2.9.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/storage-memory": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.9.0.tgz", + "integrity": "sha512-p2yrr0omQhv6teDbdzhdBKzoQAFmUBMLEx+PtrO7CJHX15ICD08/pFAFAp96IcljNwZZDchU20Z3AcbldMj6Tw==", "dependencies": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/watcher": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.9.0.tgz", + "integrity": "sha512-Yqz8Q1He/2chebXvmCft8sMamuUiDQ4FIn0bwiF0+GBP2vvGCmy6SejXZY4ZD4REluPqQSis3CLKcIOWlHnIsw==", "dependencies": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/web-sockets": { - "version": "2.8.1", - "license": "MIT", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.9.0.tgz", + "integrity": "sha512-Nob9e84m78qeQCka6OQf/JdNOmMkKCkX+i3rg+TYKSSITiMVuyzWp3vz3Ma184lAZiLg44lxBF4ZzENEdi99Kg==", "dependencies": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "undici": "5.9.1", "ws": "^8.2.2" }, @@ -2691,8 +2739,9 @@ } }, "node_modules/@miniflare/web-sockets/node_modules/ws": { - "version": "8.8.1", - "license": "MIT", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "engines": { "node": ">=10.0.0" }, @@ -3257,6 +3306,14 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.0.tgz", + "integrity": "sha512-rnSP9vY+fVsF3iJja5yRGBJV63PNBiezJlYrCkqUmQWFoB16cxAHwOkjsAYEu317miOfKaJpa65cbp0P4XJ/jw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "dev": true, @@ -3402,6 +3459,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/javascript-time-ago": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/javascript-time-ago/-/javascript-time-ago-2.0.3.tgz", + "integrity": "sha512-G6SdYh6gHxgCTU0s4cMIRHwRO4p3f7jQSZbDPfUOZpUAG1od3rTjT0e8rxGThUiTTWQHwpBRws8eHO8D2QqfkA==", + "dev": true + }, "node_modules/@types/jest": { "version": "28.1.6", "license": "MIT", @@ -3715,7 +3778,8 @@ }, "node_modules/@types/stack-trace": { "version": "0.0.29", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", + "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==" }, "node_modules/@types/stack-utils": { "version": "2.0.1", @@ -5150,6 +5214,28 @@ "dev": true, "license": "MIT" }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/busboy": { "version": "1.6.0", "dependencies": { @@ -6147,7 +6233,8 @@ }, "node_modules/cron-schedule": { "version": "3.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-3.0.6.tgz", + "integrity": "sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==" }, "node_modules/cross-env": { "version": "7.0.3", @@ -7629,7 +7716,6 @@ }, "node_modules/execa": { "version": "6.1.0", - "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -9139,7 +9225,6 @@ }, "node_modules/human-signals": { "version": "3.0.1", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.20.0" @@ -10080,7 +10165,6 @@ }, "node_modules/is-stream": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -10290,6 +10374,15 @@ "node": ">=8" } }, + "node_modules/javascript-time-ago": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.5.7.tgz", + "integrity": "sha512-EGvh6K4hpJz0S0aZinbW2EfXDqT/JBB84HfMOFDTzGg7yjpjql9feSgtlG1JQ6b6/NkIxl+PoKSUTEMsatTuTg==", + "dev": true, + "dependencies": { + "relative-time-format": "^1.1.4" + } + }, "node_modules/jest": { "version": "28.1.3", "license": "MIT", @@ -15394,7 +15487,6 @@ }, "node_modules/mimic-fn": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -15419,25 +15511,27 @@ } }, "node_modules/miniflare": { - "version": "2.8.1", - "license": "MIT", - "dependencies": { - "@miniflare/cache": "2.8.1", - "@miniflare/cli-parser": "2.8.1", - "@miniflare/core": "2.8.1", - "@miniflare/durable-objects": "2.8.1", - "@miniflare/html-rewriter": "2.8.1", - "@miniflare/http-server": "2.8.1", - "@miniflare/kv": "2.8.1", - "@miniflare/queues": "2.8.1", - "@miniflare/r2": "2.8.1", - "@miniflare/runner-vm": "2.8.1", - "@miniflare/scheduler": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/sites": "2.8.1", - "@miniflare/storage-file": "2.8.1", - "@miniflare/storage-memory": "2.8.1", - "@miniflare/web-sockets": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.9.0.tgz", + "integrity": "sha512-HBGQ5Jj6sMU1B1hX6G3ML46ThtUvu1nvxgXjDDmhp2RhWKYj0XvcohW/nPPL/MTP1gpvfT880De9EHmobVsDsw==", + "dependencies": { + "@miniflare/cache": "2.9.0", + "@miniflare/cli-parser": "2.9.0", + "@miniflare/core": "2.9.0", + "@miniflare/d1": "2.9.0", + "@miniflare/durable-objects": "2.9.0", + "@miniflare/html-rewriter": "2.9.0", + "@miniflare/http-server": "2.9.0", + "@miniflare/kv": "2.9.0", + "@miniflare/queues": "2.9.0", + "@miniflare/r2": "2.9.0", + "@miniflare/runner-vm": "2.9.0", + "@miniflare/scheduler": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/sites": "2.9.0", + "@miniflare/storage-file": "2.9.0", + "@miniflare/storage-memory": "2.9.0", + "@miniflare/web-sockets": "2.9.0", "kleur": "^4.1.4", "semiver": "^1.1.0", "source-map-support": "^0.5.20", @@ -15450,7 +15544,7 @@ "node": ">=16.13" }, "peerDependencies": { - "@miniflare/storage-redis": "2.8.1", + "@miniflare/storage-redis": "2.9.0", "cron-schedule": "^3.0.4", "ioredis": "^4.27.9" }, @@ -15806,7 +15900,8 @@ }, "node_modules/mustache": { "version": "4.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "bin": { "mustache": "bin/mustache" } @@ -16074,7 +16169,6 @@ }, "node_modules/npm-run-path": { "version": "5.1.0", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^4.0.0" @@ -16088,7 +16182,6 @@ }, "node_modules/npm-run-path/node_modules/path-key": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -16097,6 +16190,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npx-import": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npx-import/-/npx-import-1.1.3.tgz", + "integrity": "sha512-zy6249FJ81OtPsvz2y0+rgis31EN5wbdwBG2umtEh65W/4onYArHuoUSZ+W+T7BQYK7YF+h9G4CuGPusMCcLOw==", + "dependencies": { + "execa": "^6.1.0", + "parse-package-name": "^1.0.0", + "semver": "^7.3.7", + "validate-npm-package-name": "^4.0.0" + } + }, + "node_modules/npx-import/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/nwsapi": { "version": "2.2.1", "dev": true, @@ -16317,7 +16435,6 @@ }, "node_modules/onetime": { "version": "6.0.0", - "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^4.0.0" @@ -16704,6 +16821,11 @@ "node": ">=6" } }, + "node_modules/parse-package-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", + "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==" + }, "node_modules/parse5": { "version": "6.0.1", "dev": true, @@ -17589,6 +17711,12 @@ "node": ">=0.10.0" } }, + "node_modules/relative-time-format": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/relative-time-format/-/relative-time-format-1.1.4.tgz", + "integrity": "sha512-WRWEDEZFTD/95oYMd58kzlYHI6QzexMpPkxvi4/P+NLdzli/tteNonmAa5jYLm8ehJVTGiM2siNg7i1Dkn/gDw==", + "dev": true + }, "node_modules/remark-frontmatter": { "version": "4.0.1", "dev": true, @@ -17676,6 +17804,12 @@ "resolved": "fixtures/remix-pages-app", "link": true }, + "node_modules/remove-accents-esm": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/remove-accents-esm/-/remove-accents-esm-0.0.1.tgz", + "integrity": "sha512-Nbwv7X61l7a8Dgj1hdt1rM8scr4NntlrtSMW0r2YJPK7/NygnjrdVXwhs8HaQBy2x735WvNqHHuRNeswhR4ngQ==", + "dev": true + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "license": "ISC" @@ -18978,7 +19112,8 @@ }, "node_modules/stack-trace": { "version": "0.0.10", - "license": "MIT", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "engines": { "node": "*" } @@ -19293,7 +19428,6 @@ }, "node_modules/strip-final-newline": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -20410,6 +20544,17 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/validator": { "version": "13.7.0", "dev": true, @@ -21448,7 +21593,8 @@ }, "node_modules/youch": { "version": "2.2.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/youch/-/youch-2.2.2.tgz", + "integrity": "sha512-/FaCeG3GkuJwaMR34GHVg0l8jCbafZLHiFowSjqLlqhC6OMyf2tPJBu8UirF7/NI9X/R5ai4QfEKUCOxMAGxZQ==", "dependencies": { "@types/stack-trace": "0.0.29", "cookie": "^0.4.1", @@ -21488,11 +21634,11 @@ "name": "@cloudflare/pages-shared", "version": "0.0.6", "dependencies": { - "@miniflare/core": "2.8.1" + "@miniflare/core": "2.9.0" }, "devDependencies": { - "@miniflare/cache": "2.8.1", - "@miniflare/html-rewriter": "2.8.1", + "@miniflare/cache": "2.9.0", + "@miniflare/html-rewriter": "2.9.0", "@types/service-worker-mock": "^2.0.1", "concurrently": "^7.3.0", "glob": "^8.0.3", @@ -21554,11 +21700,15 @@ "@cloudflare/kv-asset-handler": "^0.2.0", "@esbuild-plugins/node-globals-polyfill": "^0.1.1", "@esbuild-plugins/node-modules-polyfill": "^0.1.4", + "@miniflare/core": "2.9.0", + "@miniflare/d1": "2.9.0", + "@miniflare/durable-objects": "2.9.0", "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.14.51", - "miniflare": "^2.8.1", + "miniflare": "2.9.0", "nanoid": "^3.3.3", + "npx-import": "^1.0.2", "path-to-regexp": "^6.2.0", "selfsigned": "^2.0.1", "source-map": "^0.7.4", @@ -21569,12 +21719,16 @@ "wrangler2": "bin/wrangler.js" }, "devDependencies": { + "@databases/split-sql-query": "^1.0.3", + "@databases/sql": "^3.2.0", "@iarna/toml": "^3.0.0", "@microsoft/api-extractor": "^7.28.3", + "@types/better-sqlite3": "^7.6.0", "@types/busboy": "^1.5.0", "@types/command-exists": "^1.2.0", "@types/express": "^4.17.13", "@types/glob-to-regexp": "0.4.1", + "@types/javascript-time-ago": "^2.0.3", "@types/mime": "^2.0.3", "@types/prompts": "^2.0.14", "@types/react": "^17.0.37", @@ -21609,6 +21763,7 @@ "ink-testing-library": "^2.1.0", "ink-text-input": "^4.0.3", "is-ci": "^3.0.1", + "javascript-time-ago": "^2.5.4", "jest-fetch-mock": "^3.0.3", "jest-websocket-mock": "^2.3.0", "mime": "^3.0.0", @@ -21619,6 +21774,7 @@ "prompts": "^2.4.2", "react": "^17.0.2", "react-error-boundary": "^3.1.4", + "remove-accents-esm": "^0.0.1", "serve-static": "^1.15.0", "signal-exit": "^3.0.7", "supports-color": "^9.2.2", @@ -23342,9 +23498,9 @@ "@cloudflare/pages-shared": { "version": "file:packages/pages-shared", "requires": { - "@miniflare/cache": "2.8.1", - "@miniflare/core": "2.8.1", - "@miniflare/html-rewriter": "2.8.1", + "@miniflare/cache": "2.9.0", + "@miniflare/core": "2.9.0", + "@miniflare/html-rewriter": "2.9.0", "@types/service-worker-mock": "^2.0.1", "concurrently": "^7.3.0", "glob": "^8.0.3", @@ -23402,6 +23558,19 @@ "minimist": "^1.2.0" } }, + "@databases/split-sql-query": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@databases/split-sql-query/-/split-sql-query-1.0.3.tgz", + "integrity": "sha512-Q3UYX85e34yE9KXa095AJtJhBQ0NpLfC0kS9ydFKuNB25cto4YddY52RuXN81m2t0pS1Atg31ylNpKfNCnUPdA==", + "dev": true, + "requires": {} + }, + "@databases/sql": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@databases/sql/-/sql-3.2.0.tgz", + "integrity": "sha512-xQZzKIa0lvcdo0MYxnyFMVS1TRla9lpDSCYkobJl19vQEOJ9TqE4o8QBGRJNUfhSkbQIWyvMeBl3KBBbqyUVQQ==", + "dev": true + }, "@esbuild-plugins/node-globals-polyfill": { "version": "0.1.1", "requires": {} @@ -24443,28 +24612,34 @@ } }, "@miniflare/cache": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.9.0.tgz", + "integrity": "sha512-lriPxUEva9TJ01vU9P7pI60s3SsFnb4apWkNwZ+D7CRqyXPipSbapY8BWI2FUIwkEG7xap6UhzeTS76NettCXQ==", "requires": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "http-cache-semantics": "^4.1.0", "undici": "5.9.1" } }, "@miniflare/cli-parser": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.9.0.tgz", + "integrity": "sha512-gu8Z7NWNcYw6514/yOvajaj3GmebRucx+EEt3p1vKirO+gvFgKAt/puyUN3p7u8ZZmLuLF/B+wVnH3lj8BWKlg==", "requires": { - "@miniflare/shared": "2.8.1", + "@miniflare/shared": "2.9.0", "kleur": "^4.1.4" } }, "@miniflare/core": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.9.0.tgz", + "integrity": "sha512-QqSwF6oHvgrFvN5lnrLc6EEagFlZWW+UMU8QdrE8305cNGHrIOxKCA2nte4PVFZUVw/Ts13a0tVhUk3a2fAyxQ==", "requires": { "@iarna/toml": "^2.2.5", - "@miniflare/queues": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/watcher": "2.8.1", + "@miniflare/queues": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/watcher": "2.9.0", "busboy": "^1.6.0", "dotenv": "^10.0.0", "kleur": "^4.1.4", @@ -24474,34 +24649,51 @@ }, "dependencies": { "dotenv": { - "version": "10.0.0" + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" } } }, + "@miniflare/d1": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.9.0.tgz", + "integrity": "sha512-swK9nzxw1SvVh/4cH3bRR1SBuHQU/YsB8WvuHojxufmgviAD1xhms3XO3rkpAzfKoGM5Oy6DovMe0xUXV/GS0w==", + "requires": { + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0" + } + }, "@miniflare/durable-objects": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.9.0.tgz", + "integrity": "sha512-7uTvfEUXS7xqwrsWOwWrFUuKc4EiMpVkAWPeYGLB/0TJaJ6N+sZMpYYymdW79TQwPIDfgtpfkIy93MRydqpnrw==", "requires": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/storage-memory": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/storage-memory": "2.9.0", "undici": "5.9.1" } }, "@miniflare/html-rewriter": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.9.0.tgz", + "integrity": "sha512-K5OB70PtkMo7M+tU46s/cX/j/qtjD9AlJ0hecYswrxVsfrT/YWyrCQJevmShFfJ92h7jPNigbeC3Od3JiVb6QA==", "requires": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "html-rewriter-wasm": "^0.4.1", "undici": "5.9.1" } }, "@miniflare/http-server": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.9.0.tgz", + "integrity": "sha512-IVJMkFfMpecq9WiCTvATEKhMuKPK9fMs2E6zmgexaefr3u1VlNtj2QxBxoPUXkT9xMJQlT5sSKstlRR1XKDz9Q==", "requires": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/web-sockets": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/web-sockets": "2.9.0", "kleur": "^4.1.4", "selfsigned": "^2.0.0", "undici": "5.9.1", @@ -24510,89 +24702,117 @@ }, "dependencies": { "ws": { - "version": "8.8.1", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "requires": {} } } }, "@miniflare/kv": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.9.0.tgz", + "integrity": "sha512-EqG51okY5rDtgjYs2Ny6j6IUVdTlJzDjwBKBIuW+wOV9NsAAzEchKVdYAXc8CyxvkggpYX481HydTD2OzK3INQ==", "requires": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" } }, "@miniflare/queues": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.9.0.tgz", + "integrity": "sha512-cAHWIlLF57rxQaJl19AzXw1k0SOM/uLTlx8r2PylHajZ/RRSs7CkCox3oKA6E5zKyfyxk2M64bmsAFZ9RCA0gw==", "requires": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" } }, "@miniflare/r2": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.9.0.tgz", + "integrity": "sha512-aMFWxxciAE3YsVok2OLy3A7hP5+2j/NaK7txmadgoe1CA8HYZyNuvv7v6bn8HKM5gWnJdT8sk4yEbMbBQ7Jv/A==", "requires": { - "@miniflare/shared": "2.8.1", + "@miniflare/shared": "2.9.0", "undici": "5.9.1" } }, "@miniflare/runner-vm": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.9.0.tgz", + "integrity": "sha512-vewP+Fy7Czb261GmB9x/YtQkoDs/QP9B5LbP0YfJ35bI2C2j940eJLm8JP72IHV7ILtWNOqMc3Ure8uAbpf9NQ==", "requires": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" } }, "@miniflare/scheduler": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.9.0.tgz", + "integrity": "sha512-eodSCGkJYi4Z+Imbx/bNScDfDSt5HOypVSYjbFHj+hA2aNOdkGw6a1b6mzwx49jJD3GadIkonZAKD0S114yWMA==", "requires": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "cron-schedule": "^3.0.4" } }, "@miniflare/shared": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.9.0.tgz", + "integrity": "sha512-5Ew/Ph0cHDQqKvOlmN70kz+qZW0hdgE9fQBStKLY3vDYhnBEhopbCUChSS+FCcL7WtxVJJVE7iB6J09NQTnQ/A==", "requires": { + "@types/better-sqlite3": "^7.6.0", "kleur": "^4.1.4", + "npx-import": "^1.1.3", "picomatch": "^2.3.1" } }, "@miniflare/sites": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.9.0.tgz", + "integrity": "sha512-+tWf7znxSQqXWGzPup8Xqkl8EmLmx+HaLC+UBtWPNnaJZrsjbbVxKwHpmGIdm+wZasEGfQk/82R21gUs9wdZnw==", "requires": { - "@miniflare/kv": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/storage-file": "2.8.1" + "@miniflare/kv": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/storage-file": "2.9.0" } }, "@miniflare/storage-file": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.9.0.tgz", + "integrity": "sha512-HZHtHfJaLoDzQFddoIMcDGgAJ3/Nee98gwUYusQam7rj9pbEXnWmk54dzjzsDlkQpB/3MBFQNbtN5Bj1NIt0pg==", "requires": { - "@miniflare/shared": "2.8.1", - "@miniflare/storage-memory": "2.8.1" + "@miniflare/shared": "2.9.0", + "@miniflare/storage-memory": "2.9.0" } }, "@miniflare/storage-memory": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.9.0.tgz", + "integrity": "sha512-p2yrr0omQhv6teDbdzhdBKzoQAFmUBMLEx+PtrO7CJHX15ICD08/pFAFAp96IcljNwZZDchU20Z3AcbldMj6Tw==", "requires": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" } }, "@miniflare/watcher": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.9.0.tgz", + "integrity": "sha512-Yqz8Q1He/2chebXvmCft8sMamuUiDQ4FIn0bwiF0+GBP2vvGCmy6SejXZY4ZD4REluPqQSis3CLKcIOWlHnIsw==", "requires": { - "@miniflare/shared": "2.8.1" + "@miniflare/shared": "2.9.0" } }, "@miniflare/web-sockets": { - "version": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.9.0.tgz", + "integrity": "sha512-Nob9e84m78qeQCka6OQf/JdNOmMkKCkX+i3rg+TYKSSITiMVuyzWp3vz3Ma184lAZiLg44lxBF4ZzENEdi99Kg==", "requires": { - "@miniflare/core": "2.8.1", - "@miniflare/shared": "2.8.1", + "@miniflare/core": "2.9.0", + "@miniflare/shared": "2.9.0", "undici": "5.9.1", "ws": "^8.2.2" }, "dependencies": { "ws": { - "version": "8.8.1", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "requires": {} } } @@ -25013,6 +25233,14 @@ "@babel/types": "^7.3.0" } }, + "@types/better-sqlite3": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.0.tgz", + "integrity": "sha512-rnSP9vY+fVsF3iJja5yRGBJV63PNBiezJlYrCkqUmQWFoB16cxAHwOkjsAYEu317miOfKaJpa65cbp0P4XJ/jw==", + "requires": { + "@types/node": "*" + } + }, "@types/body-parser": { "version": "1.19.2", "dev": true, @@ -25138,6 +25366,12 @@ "@types/istanbul-lib-report": "*" } }, + "@types/javascript-time-ago": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/javascript-time-ago/-/javascript-time-ago-2.0.3.tgz", + "integrity": "sha512-G6SdYh6gHxgCTU0s4cMIRHwRO4p3f7jQSZbDPfUOZpUAG1od3rTjT0e8rxGThUiTTWQHwpBRws8eHO8D2QqfkA==", + "dev": true + }, "@types/jest": { "version": "28.1.6", "requires": { @@ -25372,7 +25606,9 @@ "dev": true }, "@types/stack-trace": { - "version": "0.0.29" + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", + "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==" }, "@types/stack-utils": { "version": "2.0.1" @@ -26318,6 +26554,24 @@ "version": "3.0.0", "dev": true }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "requires": { + "semver": "^7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "busboy": { "version": "1.6.0", "requires": { @@ -26946,7 +27200,9 @@ } }, "cron-schedule": { - "version": "3.0.6" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-3.0.6.tgz", + "integrity": "sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==" }, "cross-env": { "version": "7.0.3", @@ -27881,7 +28137,6 @@ }, "execa": { "version": "6.1.0", - "dev": true, "requires": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.1", @@ -28874,8 +29129,7 @@ "version": "1.0.2" }, "human-signals": { - "version": "3.0.1", - "dev": true + "version": "3.0.1" }, "iconv-lite": { "version": "0.4.24", @@ -29421,8 +29675,7 @@ } }, "is-stream": { - "version": "3.0.0", - "dev": true + "version": "3.0.0" }, "is-string": { "version": "1.0.7", @@ -29546,6 +29799,15 @@ "istanbul-lib-report": "^3.0.0" } }, + "javascript-time-ago": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.5.7.tgz", + "integrity": "sha512-EGvh6K4hpJz0S0aZinbW2EfXDqT/JBB84HfMOFDTzGg7yjpjql9feSgtlG1JQ6b6/NkIxl+PoKSUTEMsatTuTg==", + "dev": true, + "requires": { + "relative-time-format": "^1.1.4" + } + }, "jest": { "version": "28.1.3", "requires": { @@ -32802,8 +33064,7 @@ } }, "mimic-fn": { - "version": "4.0.0", - "dev": true + "version": "4.0.0" }, "mimic-response": { "version": "1.0.1", @@ -32813,24 +33074,27 @@ "version": "1.0.1" }, "miniflare": { - "version": "2.8.1", - "requires": { - "@miniflare/cache": "2.8.1", - "@miniflare/cli-parser": "2.8.1", - "@miniflare/core": "2.8.1", - "@miniflare/durable-objects": "2.8.1", - "@miniflare/html-rewriter": "2.8.1", - "@miniflare/http-server": "2.8.1", - "@miniflare/kv": "2.8.1", - "@miniflare/queues": "2.8.1", - "@miniflare/r2": "2.8.1", - "@miniflare/runner-vm": "2.8.1", - "@miniflare/scheduler": "2.8.1", - "@miniflare/shared": "2.8.1", - "@miniflare/sites": "2.8.1", - "@miniflare/storage-file": "2.8.1", - "@miniflare/storage-memory": "2.8.1", - "@miniflare/web-sockets": "2.8.1", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.9.0.tgz", + "integrity": "sha512-HBGQ5Jj6sMU1B1hX6G3ML46ThtUvu1nvxgXjDDmhp2RhWKYj0XvcohW/nPPL/MTP1gpvfT880De9EHmobVsDsw==", + "requires": { + "@miniflare/cache": "2.9.0", + "@miniflare/cli-parser": "2.9.0", + "@miniflare/core": "2.9.0", + "@miniflare/d1": "2.9.0", + "@miniflare/durable-objects": "2.9.0", + "@miniflare/html-rewriter": "2.9.0", + "@miniflare/http-server": "2.9.0", + "@miniflare/kv": "2.9.0", + "@miniflare/queues": "2.9.0", + "@miniflare/r2": "2.9.0", + "@miniflare/runner-vm": "2.9.0", + "@miniflare/scheduler": "2.9.0", + "@miniflare/shared": "2.9.0", + "@miniflare/sites": "2.9.0", + "@miniflare/storage-file": "2.9.0", + "@miniflare/storage-memory": "2.9.0", + "@miniflare/web-sockets": "2.9.0", "kleur": "^4.1.4", "semiver": "^1.1.0", "source-map-support": "^0.5.20", @@ -33062,7 +33326,9 @@ } }, "mustache": { - "version": "4.2.0" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" }, "mute-stream": { "version": "0.0.8", @@ -33259,14 +33525,33 @@ }, "npm-run-path": { "version": "5.1.0", - "dev": true, "requires": { "path-key": "^4.0.0" }, "dependencies": { "path-key": { - "version": "4.0.0", - "dev": true + "version": "4.0.0" + } + } + }, + "npx-import": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npx-import/-/npx-import-1.1.3.tgz", + "integrity": "sha512-zy6249FJ81OtPsvz2y0+rgis31EN5wbdwBG2umtEh65W/4onYArHuoUSZ+W+T7BQYK7YF+h9G4CuGPusMCcLOw==", + "requires": { + "execa": "^6.1.0", + "parse-package-name": "^1.0.0", + "semver": "^7.3.7", + "validate-npm-package-name": "^4.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -33408,7 +33693,6 @@ }, "onetime": { "version": "6.0.0", - "dev": true, "requires": { "mimic-fn": "^4.0.0" } @@ -33672,6 +33956,11 @@ "version": "2.1.0", "dev": true }, + "parse-package-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", + "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==" + }, "parse5": { "version": "6.0.1", "dev": true @@ -34221,6 +34510,12 @@ "rc": "^1.0.1" } }, + "relative-time-format": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/relative-time-format/-/relative-time-format-1.1.4.tgz", + "integrity": "sha512-WRWEDEZFTD/95oYMd58kzlYHI6QzexMpPkxvi4/P+NLdzli/tteNonmAa5jYLm8ehJVTGiM2siNg7i1Dkn/gDw==", + "dev": true + }, "remark-frontmatter": { "version": "4.0.1", "dev": true, @@ -34299,6 +34594,12 @@ "undici": "^5.9.1" } }, + "remove-accents-esm": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/remove-accents-esm/-/remove-accents-esm-0.0.1.tgz", + "integrity": "sha512-Nbwv7X61l7a8Dgj1hdt1rM8scr4NntlrtSMW0r2YJPK7/NygnjrdVXwhs8HaQBy2x735WvNqHHuRNeswhR4ngQ==", + "dev": true + }, "remove-trailing-separator": { "version": "1.1.0" }, @@ -35203,7 +35504,9 @@ } }, "stack-trace": { - "version": "0.0.10" + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" }, "stack-utils": { "version": "2.0.5", @@ -35420,8 +35723,7 @@ "version": "1.0.0" }, "strip-final-newline": { - "version": "3.0.0", - "dev": true + "version": "3.0.0" }, "strip-indent": { "version": "3.0.0", @@ -36158,6 +36460,14 @@ "spdx-expression-parse": "^3.0.0" } }, + "validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "requires": { + "builtins": "^5.0.0" + } + }, "validator": { "version": "13.7.0", "dev": true @@ -36742,14 +37052,21 @@ "version": "file:packages/wrangler", "requires": { "@cloudflare/kv-asset-handler": "^0.2.0", + "@databases/split-sql-query": "^1.0.3", + "@databases/sql": "^3.2.0", "@esbuild-plugins/node-globals-polyfill": "^0.1.1", "@esbuild-plugins/node-modules-polyfill": "^0.1.4", "@iarna/toml": "^3.0.0", "@microsoft/api-extractor": "^7.28.3", + "@miniflare/core": "2.9.0", + "@miniflare/d1": "2.9.0", + "@miniflare/durable-objects": "2.9.0", + "@types/better-sqlite3": "^7.6.0", "@types/busboy": "^1.5.0", "@types/command-exists": "^1.2.0", "@types/express": "^4.17.13", "@types/glob-to-regexp": "0.4.1", + "@types/javascript-time-ago": "^2.0.3", "@types/mime": "^2.0.3", "@types/prompts": "^2.0.14", "@types/react": "^17.0.37", @@ -36788,12 +37105,14 @@ "ink-testing-library": "^2.1.0", "ink-text-input": "^4.0.3", "is-ci": "^3.0.1", + "javascript-time-ago": "^2.5.4", "jest-fetch-mock": "^3.0.3", "jest-websocket-mock": "^2.3.0", "mime": "^3.0.0", - "miniflare": "^2.8.1", + "miniflare": "2.9.0", "msw": "^0.47.1", "nanoid": "^3.3.3", + "npx-import": "^1.0.2", "open": "^8.4.0", "p-queue": "^7.2.0", "path-to-regexp": "^6.2.0", @@ -36801,6 +37120,7 @@ "prompts": "^2.4.2", "react": "^17.0.2", "react-error-boundary": "^3.1.4", + "remove-accents-esm": "^0.0.1", "selfsigned": "^2.0.1", "serve-static": "^1.15.0", "signal-exit": "^3.0.7", @@ -37788,6 +38108,8 @@ }, "youch": { "version": "2.2.2", + "resolved": "https://registry.npmjs.org/youch/-/youch-2.2.2.tgz", + "integrity": "sha512-/FaCeG3GkuJwaMR34GHVg0l8jCbafZLHiFowSjqLlqhC6OMyf2tPJBu8UirF7/NI9X/R5ai4QfEKUCOxMAGxZQ==", "requires": { "@types/stack-trace": "0.0.29", "cookie": "^0.4.1", diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index 54ce6b3eeee..92517bd7cb3 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -36,11 +36,11 @@ ] }, "dependencies": { - "@miniflare/core": "2.8.1" + "@miniflare/core": "2.9.0" }, "devDependencies": { - "@miniflare/cache": "2.8.1", - "@miniflare/html-rewriter": "2.8.1", + "@miniflare/cache": "2.9.0", + "@miniflare/html-rewriter": "2.9.0", "@types/service-worker-mock": "^2.0.1", "concurrently": "^7.3.0", "glob": "^8.0.3", diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 875ccd1c4a4..fd5aa887939 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -87,17 +87,20 @@ ] }, "transformIgnorePatterns": [ - "node_modules/(?!find-up|locate-path|p-locate|p-limit|p-timeout|p-queue|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port|supports-color|pretty-bytes)" + "node_modules/(?!find-up|locate-path|p-locate|p-limit|p-timeout|p-queue|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port|supports-color|pretty-bytes|npx-import)" ] }, "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", "@esbuild-plugins/node-globals-polyfill": "^0.1.1", "@esbuild-plugins/node-modules-polyfill": "^0.1.4", + "@miniflare/core": "2.9.0", + "@miniflare/d1": "2.9.0", + "@miniflare/durable-objects": "2.9.0", "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.14.51", - "miniflare": "^2.8.1", + "miniflare": "2.9.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "selfsigned": "^2.0.1", @@ -105,12 +108,16 @@ "xxhash-wasm": "^1.0.1" }, "devDependencies": { + "@databases/split-sql-query": "^1.0.3", + "@databases/sql": "^3.2.0", "@iarna/toml": "^3.0.0", "@microsoft/api-extractor": "^7.28.3", + "@types/better-sqlite3": "^7.6.0", "@types/busboy": "^1.5.0", "@types/command-exists": "^1.2.0", "@types/express": "^4.17.13", "@types/glob-to-regexp": "0.4.1", + "@types/javascript-time-ago": "^2.0.3", "@types/mime": "^2.0.3", "@types/prompts": "^2.0.14", "@types/react": "^17.0.37", @@ -145,16 +152,19 @@ "ink-testing-library": "^2.1.0", "ink-text-input": "^4.0.3", "is-ci": "^3.0.1", + "javascript-time-ago": "^2.5.4", "jest-fetch-mock": "^3.0.3", "jest-websocket-mock": "^2.3.0", "mime": "^3.0.0", "msw": "^0.47.1", + "npx-import": "^1.0.2", "open": "^8.4.0", "p-queue": "^7.2.0", "pretty-bytes": "^6.0.0", "prompts": "^2.4.2", "react": "^17.0.2", "react-error-boundary": "^3.1.4", + "remove-accents-esm": "^0.0.1", "serve-static": "^1.15.0", "signal-exit": "^3.0.7", "supports-color": "^9.2.2", diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index e14e0fb1519..842ee1a2be2 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -25,6 +25,7 @@ describe("normalizeAndValidateConfig()", () => { compatibility_date: undefined, compatibility_flags: [], configPath: undefined, + d1_databases: [], dev: { ip: "0.0.0.0", local_protocol: "http", @@ -1664,6 +1665,108 @@ describe("normalizeAndValidateConfig()", () => { }); }); + describe("[d1_databases]", () => { + it("should error if d1_databases is an object", () => { + const { config, diagnostics } = normalizeAndValidateConfig( + { d1_databases: {} } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(config).toEqual( + expect.not.objectContaining({ d1_databases: expect.anything }) + ); + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"d1_databases\\" should be an array but got {}." + `); + }); + + it("should error if d1_databases is a string", () => { + const { config, diagnostics } = normalizeAndValidateConfig( + { d1_databases: "BAD" } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(config).toEqual( + expect.not.objectContaining({ d1_databases: expect.anything }) + ); + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"d1_databases\\" should be an array but got \\"BAD\\"." + `); + }); + + it("should error if d1_databases is a number", () => { + const { config, diagnostics } = normalizeAndValidateConfig( + { d1_databases: 999 } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(config).toEqual( + expect.not.objectContaining({ d1_databases: expect.anything }) + ); + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"d1_databases\\" should be an array but got 999." + `); + }); + + it("should error if d1_databases is null", () => { + const { config, diagnostics } = normalizeAndValidateConfig( + { d1_databases: null } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(config).toEqual( + expect.not.objectContaining({ d1_databases: expect.anything }) + ); + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"d1_databases\\" should be an array but got null." + `); + }); + + it("should error if d1_databases.bindings are not valid", () => { + const { diagnostics } = normalizeAndValidateConfig( + { + d1_databases: [ + {}, + { binding: "VALID" }, + { binding: 2000, id: 2111 }, + { + binding: "D1_BINDING_2", + id: "my-db", + preview_id: 2222, + }, + { binding: "VALID", id: "" }, + ], + } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - \\"d1_databases[0]\\" bindings should have a string \\"binding\\" field but got {}. + - \\"d1_databases[0]\\" bindings must have a \\"database_id\\" field but got {}. + - \\"d1_databases[1]\\" bindings must have a \\"database_id\\" field but got {\\"binding\\":\\"VALID\\"}. + - \\"d1_databases[2]\\" bindings should have a string \\"binding\\" field but got {\\"binding\\":2000,\\"id\\":2111}. + - \\"d1_databases[2]\\" bindings must have a \\"database_id\\" field but got {\\"binding\\":2000,\\"id\\":2111}. + - \\"d1_databases[3]\\" bindings must have a \\"database_id\\" field but got {\\"binding\\":\\"D1_BINDING_2\\",\\"id\\":\\"my-db\\",\\"preview_id\\":2222}. + - \\"d1_databases[4]\\" bindings must have a \\"database_id\\" field but got {\\"binding\\":\\"VALID\\",\\"id\\":\\"\\"}." + `); + }); + }); + describe("[r2_buckets]", () => { it("should error if r2_buckets is an object", () => { const { config, diagnostics } = normalizeAndValidateConfig( @@ -2664,28 +2767,28 @@ describe("normalizeAndValidateConfig()", () => { ); expect(diagnostics.hasErrors()).toBe(false); expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` - "Processing wrangler configuration: - - \\"unsafe\\" fields are experimental and may change or break at any time. - - \\"env.ENV1\\" environment configuration - - \\"vars\\" exists at the top level, but not on \\"env.ENV1\\". - This is not what you probably want, since \\"vars\\" is not inherited by environments. - Please add \\"vars\\" to \\"env.ENV1\\". - - \\"define\\" exists at the top level, but not on \\"env.ENV1\\". - This is not what you probably want, since \\"define\\" is not inherited by environments. - Please add \\"define\\" to \\"env.ENV1\\". - - \\"durable_objects\\" exists at the top level, but not on \\"env.ENV1\\". - This is not what you probably want, since \\"durable_objects\\" is not inherited by environments. - Please add \\"durable_objects\\" to \\"env.ENV1\\". - - \\"kv_namespaces\\" exists at the top level, but not on \\"env.ENV1\\". - This is not what you probably want, since \\"kv_namespaces\\" is not inherited by environments. - Please add \\"kv_namespaces\\" to \\"env.ENV1\\". - - \\"r2_buckets\\" exists at the top level, but not on \\"env.ENV1\\". - This is not what you probably want, since \\"r2_buckets\\" is not inherited by environments. - Please add \\"r2_buckets\\" to \\"env.ENV1\\". - - \\"unsafe\\" exists at the top level, but not on \\"env.ENV1\\". - This is not what you probably want, since \\"unsafe\\" is not inherited by environments. - Please add \\"unsafe\\" to \\"env.ENV1\\"." - `); + "Processing wrangler configuration: + - \\"unsafe\\" fields are experimental and may change or break at any time. + - \\"env.ENV1\\" environment configuration + - \\"vars\\" exists at the top level, but not on \\"env.ENV1\\". + This is not what you probably want, since \\"vars\\" is not inherited by environments. + Please add \\"vars\\" to \\"env.ENV1\\". + - \\"define\\" exists at the top level, but not on \\"env.ENV1\\". + This is not what you probably want, since \\"define\\" is not inherited by environments. + Please add \\"define\\" to \\"env.ENV1\\". + - \\"durable_objects\\" exists at the top level, but not on \\"env.ENV1\\". + This is not what you probably want, since \\"durable_objects\\" is not inherited by environments. + Please add \\"durable_objects\\" to \\"env.ENV1\\". + - \\"kv_namespaces\\" exists at the top level, but not on \\"env.ENV1\\". + This is not what you probably want, since \\"kv_namespaces\\" is not inherited by environments. + Please add \\"kv_namespaces\\" to \\"env.ENV1\\". + - \\"r2_buckets\\" exists at the top level, but not on \\"env.ENV1\\". + This is not what you probably want, since \\"r2_buckets\\" is not inherited by environments. + Please add \\"r2_buckets\\" to \\"env.ENV1\\". + - \\"unsafe\\" exists at the top level, but not on \\"env.ENV1\\". + This is not what you probably want, since \\"unsafe\\" is not inherited by environments. + Please add \\"unsafe\\" to \\"env.ENV1\\"." + `); }); it("should error on invalid environment values", () => { diff --git a/packages/wrangler/src/__tests__/dev.test.tsx b/packages/wrangler/src/__tests__/dev.test.tsx index 946076b1ae7..136f6f57427 100644 --- a/packages/wrangler/src/__tests__/dev.test.tsx +++ b/packages/wrangler/src/__tests__/dev.test.tsx @@ -183,7 +183,6 @@ describe("wrangler dev", () => { ); }); }); - describe("host", () => { it("should resolve a host to its zone", async () => { writeWranglerToml({ @@ -1108,7 +1107,6 @@ describe("wrangler dev", () => { await runWrangler("dev --assets abc"); expect((Dev as jest.Mock).mock.calls[2][0].isWorkersSite).toEqual(false); }); - it("should warn if --assets is used", async () => { writeWranglerToml({ main: "./index.js", diff --git a/packages/wrangler/src/__tests__/helpers/mock-oauth-flow.ts b/packages/wrangler/src/__tests__/helpers/mock-oauth-flow.ts index 3beadec6fd4..c733287506c 100644 --- a/packages/wrangler/src/__tests__/helpers/mock-oauth-flow.ts +++ b/packages/wrangler/src/__tests__/helpers/mock-oauth-flow.ts @@ -97,7 +97,8 @@ export const mockOAuthFlow = () => { }; fetchMock.mockIf(outcome.expected.url, async (req) => { - outcome.actual = req; + // TODO: update Miniflare typings to match full undici Request + outcome.actual = req as unknown as Request; return ""; }); @@ -117,7 +118,8 @@ export const mockOAuthFlow = () => { }; fetchMock.mockOnceIf(outcome.expected.url, async (req) => { - outcome.actual = req; + // TODO: update Miniflare typings to match full undici Request + outcome.actual = req as unknown as Request; return makeTokenResponse(respondWith); }); diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index 963c89ebdd3..9af1a661c23 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -43,6 +43,7 @@ describe("wrangler", () => { wrangler pages ⚡️ Configure Cloudflare Pages wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace + wrangler d1 🗄 Interact with a D1 database wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler login 🔓 Login to Cloudflare wrangler logout 🚪 Logout from Cloudflare @@ -83,6 +84,7 @@ describe("wrangler", () => { wrangler pages ⚡️ Configure Cloudflare Pages wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace + wrangler d1 🗄 Interact with a D1 database wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler login 🔓 Login to Cloudflare wrangler logout 🚪 Logout from Cloudflare diff --git a/packages/wrangler/src/__tests__/paths.test.ts b/packages/wrangler/src/__tests__/paths.test.ts index 073fa0b6cb6..608f223bfa4 100644 --- a/packages/wrangler/src/__tests__/paths.test.ts +++ b/packages/wrangler/src/__tests__/paths.test.ts @@ -1,5 +1,5 @@ import * as path from "node:path"; -import { getBasePath } from "../paths"; +import { getBasePath, readableRelative } from "../paths"; describe("paths", () => { describe("getBasePath()", () => { @@ -15,3 +15,25 @@ describe("paths", () => { }); }); }); + +describe("readableRelative", () => { + const base = process.cwd(); + + it("should leave paths to files in the current directory as-is", () => { + expect(readableRelative(path.join(base, "wrangler.toml"))).toBe( + `wrangler.toml` + ); + }); + + it("should leave files in the parent directory as-is", () => { + expect(readableRelative(path.resolve(base, "../wrangler.toml"))).toMatch( + /^\..[/\\]wrangler.toml$/ + ); + }); + + it("should add ./ to nested paths", () => { + expect( + readableRelative(path.join(base, "subdir", "wrangler.toml")) + ).toMatch(/^\.[/\\]subdir[/\\]wrangler\.toml$/); + }); +}); diff --git a/packages/wrangler/src/__tests__/publish.test.ts b/packages/wrangler/src/__tests__/publish.test.ts index a8200453858..da196f87c1e 100644 --- a/packages/wrangler/src/__tests__/publish.test.ts +++ b/packages/wrangler/src/__tests__/publish.test.ts @@ -123,14 +123,14 @@ describe("publish", () => { ); expect(std.out).toMatchInlineSnapshot(` - "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20pages%3Awrite%20zone%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 - Successfully logged in. - Total Upload: xx KiB / gzip: xx KiB - Uploaded test-name (TIMINGS) - Published test-name (TIMINGS) - https://test-name.test-sub-domain.workers.dev" - `); + "Attempting to login via OAuth... + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Successfully logged in. + Total Upload: xx KiB / gzip: xx KiB + Uploaded test-name (TIMINGS) + Published test-name (TIMINGS) + https://test-name.test-sub-domain.workers.dev" + `); expect(std.warn).toMatchInlineSnapshot(`""`); expect(std.err).toMatchInlineSnapshot(`""`); }); @@ -559,7 +559,6 @@ describe("publish", () => { } `); }); - it("should publish to a route with a pattern/{zone_id|zone_name} combo", async () => { writeWranglerToml({ routes: [ @@ -3669,7 +3668,6 @@ addEventListener('fetch', event => {});` ); }); }); - describe("custom builds", () => { beforeEach(() => { // @ts-expect-error disable the mock we'd setup earlier diff --git a/packages/wrangler/src/__tests__/user.test.ts b/packages/wrangler/src/__tests__/user.test.ts index 38f18323adb..9c6e37f8af4 100644 --- a/packages/wrangler/src/__tests__/user.test.ts +++ b/packages/wrangler/src/__tests__/user.test.ts @@ -52,10 +52,10 @@ describe("User", () => { ); expect(std.out).toMatchInlineSnapshot(` - "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20pages%3Awrite%20zone%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 - Successfully logged in." - `); + "Attempting to login via OAuth... + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Successfully logged in." + `); expect(readAuthConfigFile()).toEqual({ api_token: undefined, diff --git a/packages/wrangler/src/__tests__/whoami.test.tsx b/packages/wrangler/src/__tests__/whoami.test.tsx index 3d3c18cba6d..8a8a8b2ab7e 100644 --- a/packages/wrangler/src/__tests__/whoami.test.tsx +++ b/packages/wrangler/src/__tests__/whoami.test.tsx @@ -83,7 +83,6 @@ describe("getUserInfo()", () => { const userInfo = await getUserInfo(); expect(userInfo?.email).toBeUndefined(); }); - it("should say it's using an API token when one is set", async () => { process.env = { CLOUDFLARE_API_TOKEN: "123456789", diff --git a/packages/wrangler/src/api/dev.ts b/packages/wrangler/src/api/dev.ts index 578620ecf06..47ee4a5ee44 100644 --- a/packages/wrangler/src/api/dev.ts +++ b/packages/wrangler/src/api/dev.ts @@ -2,6 +2,7 @@ import { fetch, Request } from "undici"; import { startApiDev, startDev } from "../dev"; import { logger } from "../logger"; +import type { Environment } from "../config"; import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli"; import type { RequestInit, Response, RequestInfo } from "undici"; @@ -43,6 +44,7 @@ interface DevOptions { bucket_name: string; preview_bucket_name?: string; }[]; + d1Databases?: Environment["d1_databases"]; showInteractiveDevSession?: boolean; logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; logPrefix?: string; diff --git a/packages/wrangler/src/bundle.ts b/packages/wrangler/src/bundle.ts index c4e4ec3c249..9495e974261 100644 --- a/packages/wrangler/src/bundle.ts +++ b/packages/wrangler/src/bundle.ts @@ -63,6 +63,7 @@ export async function bundleWorker( options: { serveAssetsFromWorker: boolean; assets: StaticAssetsConfig; + betaD1Shims?: string[]; jsxFactory: string | undefined; jsxFragment: string | undefined; rules: Config["rules"]; @@ -76,11 +77,13 @@ export async function bundleWorker( workerDefinitions: WorkerRegistry | undefined; firstPartyWorkerDevFacade: boolean | undefined; targetConsumer: "dev" | "publish"; + local: boolean; testScheduled?: boolean | undefined; } ): Promise { const { serveAssetsFromWorker, + betaD1Shims, jsxFactory, jsxFragment, rules, @@ -89,6 +92,7 @@ export async function bundleWorker( minify, nodeCompat, checkFetch, + local, assets, workerDefinitions, services, @@ -188,6 +192,12 @@ export async function bundleWorker( return applyFirstPartyWorkerDevFacade(currentEntry, tmpDir.path); }), + Array.isArray(betaD1Shims) && + betaD1Shims.length > 0 && + ((currentEntry: Entry) => { + return applyD1BetaFacade(currentEntry, tmpDir.path, betaD1Shims, local); + }), + // Middleware loader: to add middleware, we add the path to the middleware // Currently for demonstration purposes we have two example middlewares // Middlewares are togglable by changing the `publish` (default=false) and `dev` (default=true) options @@ -686,6 +696,44 @@ async function applyFirstPartyWorkerDevFacade( }; } +/** + * A middleware that injects the beta D1 API in JS. + * + * This code be removed from here when the API is in Workers core, + * but moved inside Miniflare for simulating D1. + */ + +async function applyD1BetaFacade( + entry: Entry, + tmpDirPath: string, + betaD1Shims: string[], + local: boolean +): Promise { + const targetPath = path.join(tmpDirPath, "d1-beta-facade.entry.js"); + + await esbuild.build({ + entryPoints: [path.resolve(getBasePath(), "templates/d1-beta-facade.js")], + bundle: true, + format: "esm", + sourcemap: true, + plugins: [ + esbuildAliasExternalPlugin({ + __ENTRY_POINT__: entry.file, + }), + ], + define: { + __D1_IMPORTS__: JSON.stringify(betaD1Shims), + __LOCAL_MODE__: JSON.stringify(local), + }, + outfile: targetPath, + }); + + return { + ...entry, + file: targetPath, + }; +} + /** * Generate a string that describes the entry-points that were identified by esbuild. */ diff --git a/packages/wrangler/src/cfetch/internal.ts b/packages/wrangler/src/cfetch/internal.ts index e20f9da7025..29a3a86bce2 100644 --- a/packages/wrangler/src/cfetch/internal.ts +++ b/packages/wrangler/src/cfetch/internal.ts @@ -19,21 +19,18 @@ export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({ defaultValue: "https://api.cloudflare.com/client/v4", }); -/** - * Make a fetch request to the Cloudflare API. - * - * This function handles acquiring the API token and logging the caller in, as necessary. - * - * Check out https://api.cloudflare.com/ for API docs. - * - * This function should not be used directly, instead use the functions in `cfetch/index.ts`. - */ -export async function fetchInternal( +/* + * performApiFetch does everything required to make a CF API request, + * but doesn't parse the response as JSON. For normal V4 API responses, + * use `fetchInternal` + * */ +export async function performApiFetch( resource: string, init: RequestInit = {}, queryParams?: URLSearchParams, abortSignal?: AbortSignal -): Promise { +) { + const method = init.method ?? "GET"; assert( resource.startsWith("/"), `CF API fetch - resource path must start with a "/" but got "${resource}"` @@ -45,22 +42,41 @@ export async function fetchInternal( addUserAgent(headers); const queryString = queryParams ? `?${queryParams.toString()}` : ""; - const method = init.method ?? "GET"; - logger.debug( `-- START CF API REQUEST: ${method} ${getCloudflareAPIBaseURL()}${resource}${queryString}` ); logger.debug("HEADERS:", JSON.stringify(headers, null, 2)); logger.debug("INIT:", JSON.stringify(init, null, 2)); logger.debug("-- END CF API REQUEST"); - const response = await fetch( - `${getCloudflareAPIBaseURL()}${resource}${queryString}`, - { - method, - ...init, - headers, - signal: abortSignal, - } + return await fetch(`${getCloudflareAPIBaseURL()}${resource}${queryString}`, { + method, + ...init, + headers, + signal: abortSignal, + }); +} + +/** + * Make a fetch request to the Cloudflare API. + * + * This function handles acquiring the API token and logging the caller in, as necessary. + * + * Check out https://api.cloudflare.com/ for API docs. + * + * This function should not be used directly, instead use the functions in `cfetch/index.ts`. + */ +export async function fetchInternal( + resource: string, + init: RequestInit = {}, + queryParams?: URLSearchParams, + abortSignal?: AbortSignal +): Promise { + const method = init.method ?? "GET"; + const response = await performApiFetch( + resource, + init, + queryParams, + abortSignal ); const jsonText = await response.text(); logger.debug( diff --git a/packages/wrangler/src/config/environment.ts b/packages/wrangler/src/config/environment.ts index 2ba3dd69b89..966fa071dc4 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -343,6 +343,26 @@ interface EnvironmentNonInheritable { preview_bucket_name?: string; }[]; + /** + * Specifies D1 databases that are bound to this Worker environment. + * + * NOTE: This field is not automatically inherited from the top level environment, + * and so must be specified in every named environment. + * + * @default `[]` + * @nonInheritable + */ + d1_databases: { + /** The binding name used to refer to the D1 database in the worker. */ + binding: string; + /** The name of this D1 database. */ + database_name: string; + /** The UUID of this D1 database (not required). */ + database_id: string; + /** The UUID of this D1 database for Wrangler Dev (if specified). */ + preview_database_id?: string; + }[]; + /** * Specifies service bindings (worker-to-worker) that are bound to this Worker environment. * diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index 2588fa9ebcb..cf4edb6a927 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -1,9 +1,11 @@ import { findUpSync } from "find-up"; import { logger } from "../logger"; import { parseTOML, readFileSync } from "../parse"; +import { removeD1BetaPrefix } from "../worker"; import { normalizeAndValidateConfig } from "./validation"; import type { CfWorkerInit } from "../worker"; import type { Config, RawConfig } from "./config"; +import type { CamelCaseKey } from "yargs"; export type { Config, @@ -84,6 +86,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { data_blobs, durable_objects, kv_namespaces, + d1_databases, r2_buckets, logfwdr, services, @@ -138,6 +141,20 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { }); } + if (d1_databases !== undefined && d1_databases.length > 0) { + output.push({ + type: "D1 Databases", + entries: d1_databases.map(({ binding, database_name, database_id }) => { + return { + key: removeD1BetaPrefix(binding), + value: database_name + ? `${database_name} (${database_id})` + : database_id, + }; + }), + }); + } + if (r2_buckets !== undefined && r2_buckets.length > 0) { output.push({ type: "R2 Buckets", @@ -249,3 +266,18 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { logger.log(message); } + +type CamelCase = { + [key in keyof T as key | CamelCaseKey]: T[key]; +}; + +export function withConfig( + handler: ( + t: Omit, "config"> & { config: Config } + ) => Promise +) { + return (t: CamelCase) => { + const { config: configPath, ...rest } = t; + return handler({ ...rest, config: readConfig(configPath, rest) }); + }; +} diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index abef2b070e7..dcb2b3f19f4 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -898,6 +898,7 @@ function normalizeAndValidateEnvironment( ); // The field "experimental_services" doesn't exist anymore in the config, but we still want to error about any older usage. + deprecated( diagnostics, rawEnv, @@ -1084,6 +1085,16 @@ function normalizeAndValidateEnvironment( validateBindingArray(envName, validateR2Binding), [] ), + d1_databases: notInheritable( + diagnostics, + topLevelEnv, + rawConfig, + rawEnv, + envName, + "d1_databases", + validateBindingArray(envName, validateD1Binding), + [] + ), services: notInheritable( diagnostics, topLevelEnv, @@ -1548,6 +1559,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "text_blob", "kv_namespace", "durable_object_namespace", + "d1_database", "r2_bucket", "service", "logfwdr", @@ -1702,6 +1714,53 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => { return isValid; }; +const validateD1Binding: ValidatorFn = (diagnostics, field, value) => { + if (typeof value !== "object" || value === null) { + diagnostics.errors.push( + `"d1_databases" bindings should be objects, but got ${JSON.stringify( + value + )}` + ); + return false; + } + let isValid = true; + // D1 databases must have a binding and either a database_name or database_id. + if (!isRequiredProperty(value, "binding", "string")) { + diagnostics.errors.push( + `"${field}" bindings should have a string "binding" field but got ${JSON.stringify( + value + )}.` + ); + isValid = false; + } + if ( + // TODO: allow name only, where we look up the ID dynamically + // !isOptionalProperty(value, "database_name", "string") && + !isRequiredProperty(value, "database_id", "string") + ) { + diagnostics.errors.push( + `"${field}" bindings must have a "database_id" field but got ${JSON.stringify( + value + )}.` + ); + isValid = false; + } + if (!isOptionalProperty(value, "preview_database_id", "string")) { + diagnostics.errors.push( + `"${field}" bindings should, optionally, have a string "preview_database_id" field but got ${JSON.stringify( + value + )}.` + ); + isValid = false; + } + if (isValid && !process.env.NO_D1_WARNING) { + diagnostics.warnings.push( + `D1 Bindings are currently in beta to allow the API to evolve before general availability.\nPlease report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose\nNote: set NO_D1_WARNING=true to hide this message` + ); + } + return isValid; +}; + /** * Check that bindings whose names might conflict, don't. * diff --git a/packages/wrangler/src/create-worker-upload-form.ts b/packages/wrangler/src/create-worker-upload-form.ts index c1eaca2ac5f..4402f3e21d6 100644 --- a/packages/wrangler/src/create-worker-upload-form.ts +++ b/packages/wrangler/src/create-worker-upload-form.ts @@ -40,6 +40,7 @@ type WorkerMetadataBinding = environment?: string; } | { type: "r2_bucket"; name: string; bucket_name: string } + | { type: "d1"; name: string; id: string } | { type: "service"; name: string; service: string; environment?: string } | { type: "namespace"; name: string; namespace: string } | { @@ -117,6 +118,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { }); }); + bindings.d1_databases?.forEach(({ binding, database_id }) => { + metadataBindings.push({ + name: binding, + type: "d1", + id: database_id, + }); + }); + bindings.services?.forEach(({ binding, service, environment }) => { metadataBindings.push({ name: binding, diff --git a/packages/wrangler/src/d1/backups.tsx b/packages/wrangler/src/d1/backups.tsx new file mode 100644 index 00000000000..c8546e44cdf --- /dev/null +++ b/packages/wrangler/src/d1/backups.tsx @@ -0,0 +1,212 @@ +import fs from "node:fs/promises"; +import { render } from "ink"; +import Table from "ink-table"; +import React from "react"; +import { fetchResult } from "../cfetch"; +import { performApiFetch } from "../cfetch/internal"; +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { formatBytes, formatTimeAgo } from "./formatTimeAgo"; +import { Name } from "./options"; +import { d1BetaWarning, getDatabaseByNameOrBinding } from "./utils"; +import type { Backup, Database } from "./types"; +import type { Response } from "undici"; +import type { Argv } from "yargs"; + +type BackupListArgs = { config?: string; name: string }; + +export function ListOptions(yargs: Argv): Argv { + return Name(yargs); +} + +export const ListHandler = withConfig( + async ({ config, name }): Promise => { + const accountId = await requireAuth({}); + logger.log(d1BetaWarning); + const db: Database = await getDatabaseByNameOrBinding( + config, + accountId, + name + ); + + const backups: Backup[] = await listBackups(accountId, db.uuid); + render( +
+ ); + } +); + +export const listBackups = async ( + accountId: string, + uuid: string +): Promise> => { + const json: Backup[] = await fetchResult( + `/accounts/${accountId}/d1/database/${uuid}/backup`, + {} + ); + const results: Record = {}; + + json + // First, convert created_at to a Date + .map((backup) => ({ + ...backup, + created_at: new Date(backup.created_at), + })) + // Then, sort descending based on created_at + .sort((a, b) => +b.created_at - +a.created_at) + // then group_by their human-readable timestamp i.e. "2 days ago" + // (storing only the first of each group) + // and replace the Date version with this new human-readable one + .forEach((backup) => { + const timeAgo = formatTimeAgo(backup.created_at); + if (!results[timeAgo]) { + results[timeAgo] = { + ...backup, + created_at: timeAgo, + size: formatBytes(backup.file_size), + }; + } + }); + + // Take advantage of JS objects' sorting to return the newest backup of a certain age + return Object.values(results); +}; + +type BackupCreateArgs = BackupListArgs; + +export function CreateOptions(yargs: Argv): Argv { + return ListOptions(yargs); +} + +export const CreateHandler = withConfig( + async ({ config, name }): Promise => { + const accountId = await requireAuth({}); + logger.log(d1BetaWarning); + const db: Database = await getDatabaseByNameOrBinding( + config, + accountId, + name + ); + + const backup: Backup = await createBackup(accountId, db.uuid); + render( +
+ ); + } +); + +export const createBackup = async ( + accountId: string, + uuid: string +): Promise => { + const backup: Backup = await fetchResult( + `/accounts/${accountId}/d1/database/${uuid}/backup`, + { + method: "POST", + } + ); + return { + ...backup, + size: formatBytes(backup.file_size), + }; +}; + +type BackupRestoreArgs = BackupListArgs & { + "backup-id": string; +}; + +export function RestoreOptions(yargs: Argv): Argv { + return ListOptions(yargs).positional("backup-id", { + describe: "The Backup ID to restore", + type: "string", + demandOption: true, + }); +} + +export const RestoreHandler = withConfig( + async ({ config, name, backupId }): Promise => { + const accountId = await requireAuth({}); + logger.log(d1BetaWarning); + const db: Database = await getDatabaseByNameOrBinding( + config, + accountId, + name + ); + + console.log(`Restoring ${name} from backup ${backupId}....`); + await restoreBackup(accountId, db.uuid, backupId); + console.log(`Done!`); + } +); + +export const restoreBackup = async ( + accountId: string, + uuid: string, + backupId: string +): Promise => { + await fetchResult( + `/accounts/${accountId}/d1/database/${uuid}/backup/${backupId}/restore`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + } + ); +}; + +type BackupDownloadArgs = BackupRestoreArgs & { + output?: string; +}; + +export function DownloadOptions(yargs: Argv): Argv { + return ListOptions(yargs) + .positional("backup-id", { + describe: "The Backup ID to download", + type: "string", + demandOption: true, + }) + .option("output", { + describe: + "The .sqlite3 file to write to (defaults to '..sqlite3'", + type: "string", + }); +} + +export const DownloadHandler = withConfig( + async ({ name, backupId, output, config }): Promise => { + const accountId = await requireAuth({}); + logger.log(d1BetaWarning); + const db: Database = await getDatabaseByNameOrBinding( + config, + accountId, + name + ); + const filename = output || `./${name}.${backupId.slice(0, 8)}.sqlite3`; + + console.log(`Downloading backup ${backupId} of ${name} to: ${filename}`); + const response = await getBackupResponse(accountId, db.uuid, backupId); + console.log(`Got file. Saving...`); + // TODO: stream this once we upgrade to Node18 and can use Writable.fromWeb + const buffer = await response.arrayBuffer(); + await fs.writeFile(filename, new Buffer(buffer)); + console.log(`Done! Wrote ${filename} (${formatBytes(buffer.byteLength)})`); + } +); + +export const getBackupResponse = async ( + accountId: string, + uuid: string, + backupId: string +): Promise => { + return await performApiFetch( + `/accounts/${accountId}/d1/database/${uuid}/backup/${backupId}/download` + ); +}; diff --git a/packages/wrangler/src/d1/create.tsx b/packages/wrangler/src/d1/create.tsx new file mode 100644 index 00000000000..5bf1ff38f09 --- /dev/null +++ b/packages/wrangler/src/d1/create.tsx @@ -0,0 +1,54 @@ +import { render, Text, Box } from "ink"; +import React from "react"; +import { fetchResult } from "../cfetch"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { d1BetaWarning } from "./utils"; +import type { Database } from "./types"; +import type { ArgumentsCamelCase, Argv } from "yargs"; + +type CreateArgs = { name: string }; + +export function Options(yargs: Argv): Argv { + return yargs + .positional("name", { + describe: "The name of the new DB", + type: "string", + demandOption: true, + }) + .epilogue(d1BetaWarning); +} + +export async function Handler({ + name, +}: ArgumentsCamelCase): Promise { + const accountId = await requireAuth({}); + logger.log(d1BetaWarning); + + const db: Database = await fetchResult(`/accounts/${accountId}/d1/database`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name, + }), + }); + + render( + + ✅ Successfully created DB '{db.name}'! +   + + Add the following to your wrangler.toml to connect to it from a Worker: + +   + [[ d1_databases ]] + + binding = "DB" # i.e. available in your Worker on env.DB + + database_name = "{db.name}" + database_id = "{db.uuid}" + + ); +} diff --git a/packages/wrangler/src/d1/delete.tsx b/packages/wrangler/src/d1/delete.tsx new file mode 100644 index 00000000000..baf50788be3 --- /dev/null +++ b/packages/wrangler/src/d1/delete.tsx @@ -0,0 +1,56 @@ +import { fetchResult } from "../cfetch"; +import { withConfig } from "../config"; +import { confirm } from "../dialogs"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { Name } from "./options"; +import { d1BetaWarning, getDatabaseByNameOrBinding } from "./utils"; +import type { Database } from "./types"; +import type { Argv } from "yargs"; + +type CreateArgs = { + config?: string; + name: string; + "skip-confirmation": boolean; +}; + +export function Options(d1ListYargs: Argv): Argv { + return Name(d1ListYargs) + .option("skip-confirmation", { + describe: "Skip confirmation", + type: "boolean", + alias: "y", + default: false, + }) + .epilogue(d1BetaWarning); +} + +export const Handler = withConfig( + async ({ name, skipConfirmation, config }): Promise => { + const accountId = await requireAuth({}); + logger.log(d1BetaWarning); + + const db: Database = await getDatabaseByNameOrBinding( + config, + accountId, + name + ); + + console.log(`About to delete DB '${name}' (${db.uuid}).`); + if (!skipConfirmation) { + const response = await confirm(`Ok to proceed?`); + if (!response) { + console.log(`Not deleting.`); + return; + } + } + + console.log("Deleting..."); + + await fetchResult(`/accounts/${accountId}/d1/database/${db.uuid}`, { + method: "DELETE", + }); + + console.log(`Deleted '${name}' successfully.`); + } +); diff --git a/packages/wrangler/src/d1/execute.tsx b/packages/wrangler/src/d1/execute.tsx new file mode 100644 index 00000000000..7081be861ad --- /dev/null +++ b/packages/wrangler/src/d1/execute.tsx @@ -0,0 +1,294 @@ +import { existsSync } from "node:fs"; +import { mkdir } from "node:fs/promises"; +import path from "node:path"; +import chalk from "chalk"; +import { render, Static, Text } from "ink"; +import Table from "ink-table"; +import { npxImport } from "npx-import"; +import React from "react"; +import { fetchResult } from "../cfetch"; +import { withConfig } from "../config"; +import { getLocalPersistencePath } from "../dev/get-local-persistence-path"; +import { confirm, logDim } from "../dialogs"; +import { logger } from "../logger"; +import { readableRelative } from "../paths"; +import { requireAuth } from "../user"; +import { Name } from "./options"; +import { + d1BetaWarning, + getDatabaseByNameOrBinding, + getDatabaseInfoFromConfig, +} from "./utils"; +import type { Config } from "../config"; +import type { Database } from "./types"; +import type splitSqlQuery from "@databases/split-sql-query"; +import type { SQL, SQLQuery } from "@databases/sql"; +import type { Statement as StatementType } from "@miniflare/d1"; +import type { createSQLiteDB as createSQLiteDBType } from "@miniflare/shared"; +import type { Argv } from "yargs"; + +type MiniflareNpxImportTypes = [ + { + Statement: typeof StatementType; + }, + { + createSQLiteDB: typeof createSQLiteDBType; + } +]; + +type ExecuteArgs = { + config?: string; + name: string; + file?: string; + command?: string; + local?: boolean; + "persist-to"?: string; +}; + +type QueryResult = { + results: Record[]; + success: boolean; + duration: number; + query?: string; +}; +// Max number of bytes to send in a single /execute call +const QUERY_LIMIT = 1_000_000; // 1MB + +export function Options(yargs: Argv): Argv { + return Name(yargs) + .option("local", { + describe: + "Execute commands/files against a local DB for use with wrangler dev", + type: "boolean", + }) + .option("file", { + describe: "A .sql file to injest", + type: "string", + }) + .option("command", { + describe: "A single SQL statement to execute", + type: "string", + }) + .option("persist-to", { + describe: "Specify directory to use for local persistence (for --local)", + type: "string", + requiresArg: true, + }); +} + +function shorten(query: string | undefined, length: number) { + return query && query.length > length + ? query.slice(0, length) + "..." + : query; +} + +export const Handler = withConfig( + async ({ config, name, file, command, local, persistTo }): Promise => { + logger.log(d1BetaWarning); + if (file && command) + return console.error(`Error: can't provide both --command and --file.`); + const { parser, splitter } = await loadSqlUtils(); + + const sql = file + ? parser.file(file) + : command + ? parser.__dangerous__rawValue(command) + : null; + + if (!sql) throw new Error(`Error: must provide --command or --file.`); + if (persistTo && !local) + throw new Error(`Error: can't use --persist-to without --local`); + + const isInteractive = process.stdout.isTTY; + const response: QueryResult[] | null = local + ? await executeLocally( + config, + name, + isInteractive, + splitSql(splitter, sql), + persistTo + ) + : await executeRemotely( + config, + name, + isInteractive, + batchSplit(splitter, sql) + ); + + // Early exit if prompt rejected + if (!response) return; + + if (isInteractive) { + render( + + {(result) => { + const { results, duration, query } = result; + + if (Array.isArray(results) && results.length > 0) { + const shortQuery = shorten(query, 48); + return ( + <> + {shortQuery ? {shortQuery} : null} +
+ + ); + } else { + const shortQuery = shorten(query, 24); + return ( + + Executed{" "} + {shortQuery ? {shortQuery} : "command"}{" "} + in {duration}ms. + + ); + } + }} +
+ ); + } else { + console.log(JSON.stringify(response, null, 2)); + } + } +); + +async function executeLocally( + config: Config, + name: string, + isInteractive: boolean, + queries: string[], + persistTo: string | undefined +) { + const localDB = getDatabaseInfoFromConfig(config, name); + if (!localDB) { + throw new Error( + `Can't find a DB with name/binding '${name}' in local config. Check info in wrangler.toml...` + ); + } + + const persistencePath = getLocalPersistencePath( + persistTo, + true, + config.configPath + ); + + const dbDir = path.join(persistencePath, "d1"); + const dbPath = path.join(dbDir, `${localDB.binding}.sqlite3`); + const [{ Statement }, { createSQLiteDB }] = + await npxImport( + ["@miniflare/d1", "@miniflare/shared"], + logDim + ); + + if (!existsSync(dbDir) && isInteractive) { + const ok = await confirm( + `About to create ${readableRelative(dbPath)}, ok?` + ); + if (!ok) return null; + await mkdir(dbDir, { recursive: true }); + } + + console.log(`Loading DB at ${readableRelative(dbPath)}`); + const db = await createSQLiteDB(dbPath); + + const results: QueryResult[] = []; + for (const sql of queries) { + const statement = new Statement(db, sql); + results.push((await statement.all()) as QueryResult); + } + + return results; +} + +async function executeRemotely( + config: Config, + name: string, + isInteractive: boolean, + batches: string[] +) { + if (batches.length > 1) { + const warning = + chalk.red(`WARNING! `) + + `Too much SQL to send at once, this execution will be sent as ${batches.length} batches.`; + + if (isInteractive) { + const ok = await confirm( + `${warning}\nNOTE: each batch is sent individually and may leave your DB in an unexpected state if a later batch fails.\n${chalk.green( + `Make sure you have a recent backup.` + )}\nOk to proceed?` + ); + if (!ok) return null; + } else { + console.error(warning); + } + } + + const accountId = await requireAuth({}); + const db: Database = await getDatabaseByNameOrBinding( + config, + accountId, + name + ); + + if (isInteractive) { + console.log(`Executing on ${name} (${db.uuid}):`); + } else { + // Pipe to error so we don't break jq + console.error(`Executing on ${name} (${db.uuid}):`); + } + + const results: QueryResult[] = []; + for (const sql of batches) { + results.push( + ...(await fetchResult( + `/accounts/${accountId}/d1/database/${db.uuid}/query`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ sql }), + } + )) + ); + } + return results; +} + +function splitSql(splitter: (query: SQLQuery) => SQLQuery[], sql: SQLQuery) { + // We have no interpolations, so convert everything to text + return splitter(sql).map( + (q) => + q.format({ + escapeIdentifier: (_) => "", + formatValue: (_, __) => ({ placeholder: "", value: "" }), + }).text + ); +} + +function batchSplit(splitter: typeof splitSqlQuery, sql: SQLQuery) { + const queries = splitSql(splitter, sql); + + const batches: string[] = []; + for (const query of queries) { + const last = batches.at(-1); + if (!last || last.length + query.length > QUERY_LIMIT) { + batches.push(query); + } else { + batches.splice(-1, 1, [last, query].join("; ")); + } + } + return batches; +} + +async function loadSqlUtils() { + const [ + { default: parser }, + { + // No idea why this is doubly-nested, see https://github.com/ForbesLindesay/atdatabases/issues/255 + default: { default: splitter }, + }, + ] = await npxImport< + [{ default: SQL }, { default: { default: typeof splitSqlQuery } }] + >(["@databases/sql@3.2.0", "@databases/split-sql-query@1.0.3"], logDim); + return { parser, splitter }; +} diff --git a/packages/wrangler/src/d1/formatTimeAgo.ts b/packages/wrangler/src/d1/formatTimeAgo.ts new file mode 100644 index 00000000000..753222e907f --- /dev/null +++ b/packages/wrangler/src/d1/formatTimeAgo.ts @@ -0,0 +1,14 @@ +import TimeAgo from "javascript-time-ago"; +import en from "javascript-time-ago/locale/en"; +import prettyBytes from "pretty-bytes"; +TimeAgo.addDefaultLocale(en); +const timeAgo = new TimeAgo("en-US"); + +export const formatTimeAgo = (date: Date): string => { + const result = timeAgo.format(date); + return Array.isArray(result) ? result[0] : result; +}; + +export const formatBytes = (bytes: number): string => { + return prettyBytes(bytes); +}; diff --git a/packages/wrangler/src/d1/index.ts b/packages/wrangler/src/d1/index.ts new file mode 100644 index 00000000000..5c4efbb16fd --- /dev/null +++ b/packages/wrangler/src/d1/index.ts @@ -0,0 +1,75 @@ +import * as Backups from "./backups"; +import * as Create from "./create"; +import * as Delete from "./delete"; +import * as Execute from "./execute"; +import * as List from "./list"; +import { d1BetaWarning } from "./utils"; +import type { Argv } from "yargs"; + +export const d1api = (yargs: Argv) => { + return ( + yargs + .command("list", "List D1 databases", List.Options, List.Handler) + .command( + "create ", + "Create D1 database", + Create.Options, + Create.Handler + ) + .command( + "delete ", + "Delete D1 database", + Delete.Options, + Delete.Handler + ) + .command("backup", "Interact with D1 Backups", (yargs2) => + yargs2 + .command( + "list ", + "List your D1 backups", + Backups.ListOptions, + Backups.ListHandler + ) + .command( + "create ", + "Create a new D1 backup", + Backups.CreateOptions, + Backups.CreateHandler + ) + .command( + "restore ", + "Restore a DB backup", + Backups.RestoreOptions, + Backups.RestoreHandler + ) + .command( + "download ", + "Download a DB backup", + Backups.DownloadOptions, + Backups.DownloadHandler + ) + .epilogue(d1BetaWarning) + ) + // .command( + // "console ", + // "Open a Console on a D1 database", + // (d1CreateYargs) => { + // return d1CreateYargs.positional("name", { + // describe: "The name of the DB", + // type: "string", + // demandOption: true, + // }); + // }, + // async (_) => { + // // TODO + // } + // ) + .command( + "execute ", + "Executed command or SQL file", + Execute.Options, + Execute.Handler + ) + .epilogue(d1BetaWarning) + ); +}; diff --git a/packages/wrangler/src/d1/list.tsx b/packages/wrangler/src/d1/list.tsx new file mode 100644 index 00000000000..9a2d89426e1 --- /dev/null +++ b/packages/wrangler/src/d1/list.tsx @@ -0,0 +1,48 @@ +import { render } from "ink"; +import Table from "ink-table"; +import React from "react"; +import { fetchResult } from "../cfetch"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { d1BetaWarning } from "./utils"; +import type { Database } from "./types"; +import type { ArgumentsCamelCase, Argv } from "yargs"; + +type ListArgs = Record; + +export function Options(d1ListYargs: Argv): Argv { + return d1ListYargs.epilogue(d1BetaWarning); +} + +export async function Handler(_: ArgumentsCamelCase): Promise { + const accountId = await requireAuth({}); + logger.log(d1BetaWarning); + + const dbs: Array = await listDatabases(accountId); + + render(
); +} + +export const listDatabases = async ( + accountId: string +): Promise> => { + const pageSize = 10; + let page = 1; + const results = []; + while (results.length % pageSize === 0) { + const json: Array = await fetchResult( + `/accounts/${accountId}/d1/database`, + {}, + new URLSearchParams({ + per_page: pageSize.toString(), + page: page.toString(), + }) + ); + page++; + results.push(...json); + if (json.length < pageSize) { + break; + } + } + return results; +}; diff --git a/packages/wrangler/src/d1/options.ts b/packages/wrangler/src/d1/options.ts new file mode 100644 index 00000000000..b8923ea4312 --- /dev/null +++ b/packages/wrangler/src/d1/options.ts @@ -0,0 +1,12 @@ +import { d1BetaWarning } from "./utils"; +import type { Argv } from "yargs"; + +export function Name(yargs: Argv) { + return yargs + .positional("name", { + describe: "The name or binding of the DB", + type: "string", + demandOption: true, + }) + .epilogue(d1BetaWarning); +} diff --git a/packages/wrangler/src/d1/types.tsx b/packages/wrangler/src/d1/types.tsx new file mode 100644 index 00000000000..fff0e701803 --- /dev/null +++ b/packages/wrangler/src/d1/types.tsx @@ -0,0 +1,14 @@ +export type Database = { + uuid: string; + name: string; +}; + +export type Backup = { + id: string; + database_id: string; + created_at: string; + state: "progress" | "done"; + num_tables: number; + file_size: number; + size?: string; +}; diff --git a/packages/wrangler/src/d1/utils.ts b/packages/wrangler/src/d1/utils.ts new file mode 100644 index 00000000000..75fafe3c786 --- /dev/null +++ b/packages/wrangler/src/d1/utils.ts @@ -0,0 +1,39 @@ +import { listDatabases } from "./list"; +import type { Config } from "../config"; +import type { Database } from "./types"; + +export function getDatabaseInfoFromConfig(config: Config, name: string) { + for (const d1Database of config.d1_databases) { + if ( + d1Database.database_id && + (name === d1Database.database_name || name === d1Database.binding) + ) { + return { + uuid: d1Database.database_id, + binding: d1Database.binding, + name: d1Database.database_name, + }; + } + } + return null; +} + +export const getDatabaseByNameOrBinding = async ( + config: Config, + accountId: string, + name: string +): Promise => { + const dbFromConfig = getDatabaseInfoFromConfig(config, name); + if (dbFromConfig) return dbFromConfig; + + const allDBs = await listDatabases(accountId); + const matchingDB = allDBs.find((db) => db.name === name); + if (!matchingDB) { + throw new Error(`Couldn't find DB with name '${name}'`); + } + return matchingDB; +}; + +export const d1BetaWarning = process.env.NO_D1_WARNING + ? "" + : "🚧 'wrangler d1 ' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"; diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 203890c4148..17931b0e2fb 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -6,6 +6,7 @@ import React from "react"; import { findWranglerToml, printBindings, readConfig } from "./config"; import Dev from "./dev/dev"; import { getVarsForDev } from "./dev/dev-vars"; +import { getLocalPersistencePath } from "./dev/get-local-persistence-path"; import { startDevServer } from "./dev/start-server"; import { getEntry } from "./entry"; @@ -13,18 +14,19 @@ import { logger } from "./logger"; import * as metrics from "./metrics"; import { getAssetPaths, getSiteAssetPaths } from "./sites"; import { getAccountFromCache } from "./user"; -import { getZoneIdFromHost, getZoneForRoute, getHostFromRoute } from "./zones"; +import { identifyD1BindingsAsBeta } from "./worker"; +import { getHostFromRoute, getZoneForRoute, getZoneIdFromHost } from "./zones"; import { - printWranglerBanner, - DEFAULT_LOCAL_PORT, type ConfigPath, - getScriptName, + DEFAULT_INSPECTOR_PORT, + DEFAULT_LOCAL_PORT, getDevCompatibilityDate, getRules, + getScriptName, isLegacyEnv, - DEFAULT_INSPECTOR_PORT, + printWranglerBanner, } from "./index"; -import type { Config } from "./config"; +import type { Config, Environment } from "./config"; import type { Route } from "./config/environment"; import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli"; import type { CfWorkerInit } from "./worker"; @@ -319,6 +321,7 @@ export type AdditionalDevProps = { bucket_name: string; preview_bucket_name?: string; }[]; + d1Databases?: Environment["d1_databases"]; }; type StartDevOptions = ArgumentsCamelCase & @@ -522,7 +525,7 @@ export async function startApiDev(args: StartDevOptions) { upstreamProtocol: upstreamProtocol, localProtocol: args.localProtocol || configParam.dev.local_protocol, localUpstream: args["local-upstream"] || host, - localPersistencePath: localPersistencePath, + localPersistencePath, liveReload: args.liveReload || false, accountId: configParam.account_id || getAccountFromCache()?.id, assetPaths: assetPaths, @@ -682,7 +685,6 @@ async function validateDevServerSettings( "The --assets argument is experimental and may change or break at any time" ); } - const upstreamProtocol = args["upstream-protocol"] || config.dev.upstream_protocol; if (upstreamProtocol === "http") { @@ -707,17 +709,11 @@ async function validateDevServerSettings( ); } - const localPersistencePath = args.persistTo - ? // If path specified, always treat it as relative to cwd() - path.resolve(process.cwd(), args.persistTo) - : args.persist - ? // If just flagged on, treat it as relative to wrangler.toml, - // if one can be found, otherwise cwd() - path.resolve( - config.configPath ? path.dirname(config.configPath) : process.cwd(), - ".wrangler/state" - ) - : null; + const localPersistencePath = getLocalPersistencePath( + args.persistTo, + Boolean(args.persist), + config.configPath + ); const cliDefines = args.define?.reduce>((collectDefines, d) => { @@ -757,6 +753,7 @@ async function getBindingsAndAssetPaths( vars: { ...args.vars, ...cliVars }, durableObjects: args.durableObjects, r2: args.r2, + d1Databases: args.d1Databases, }); const maskedVars = maskVars(bindings, configParam); @@ -843,6 +840,10 @@ async function getBindings( services: configParam.services, unsafe: configParam.unsafe?.bindings, logfwdr: configParam.logfwdr, + d1_databases: identifyD1BindingsAsBeta([ + ...configParam.d1_databases, + ...(args.d1Databases || []), + ]), }; return bindings; diff --git a/packages/wrangler/src/dev/dev.tsx b/packages/wrangler/src/dev/dev.tsx index 6ca3522f415..616479e5731 100644 --- a/packages/wrangler/src/dev/dev.tsx +++ b/packages/wrangler/src/dev/dev.tsx @@ -5,7 +5,7 @@ import { watch } from "chokidar"; import clipboardy from "clipboardy"; import commandExists from "command-exists"; import { Box, Text, useApp, useInput, useStdin } from "ink"; -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useMemo } from "react"; import { withErrorBoundary, useErrorHandler } from "react-error-boundary"; import onExit from "signal-exit"; import tmp from "tmp-promise"; @@ -219,6 +219,29 @@ function DevSession(props: DevSessionProps) { useCustomBuild(props.entry, props.build); const directory = useTmpDir(); + const handleError = useErrorHandler(); + + // Note: when D1 is out of beta, this (and all instances of `betaD1Shims`) can be removed. + // Additionally, useMemo is used so that new arrays aren't created on every render + // cause re-rendering further down. + const betaD1Shims = useMemo( + () => props.bindings.d1_databases?.map((db) => db.binding), + [props.bindings.d1_databases] + ); + const everyD1BindingHasPreview = props.bindings.d1_databases?.every( + (binding) => binding.preview_database_id + ); + if ( + betaD1Shims && + betaD1Shims.length > 0 && + !(props.local || everyD1BindingHasPreview) + ) { + handleError( + new Error( + "D1 bindings require dev --local or preview_database_id for now" + ) + ); + } const workerDefinitions = useDevRegistry( props.name, @@ -239,6 +262,7 @@ function DevSession(props: DevSessionProps) { tsconfig: props.tsconfig, minify: props.minify, nodeCompat: props.nodeCompat, + betaD1Shims, define: props.define, noBundle: props.noBundle, assets: props.assetsConfig, @@ -246,6 +270,7 @@ function DevSession(props: DevSessionProps) { services: props.bindings.services, durableObjects: props.bindings.durable_objects || { bindings: [] }, firstPartyWorkerDevFacade: props.firstPartyWorker, + local: props.local, // Enable the bundling to know whether we are using dev or publish targetConsumer: "dev", testScheduled: props.testScheduled ?? false, diff --git a/packages/wrangler/src/dev/get-local-persistence-path.tsx b/packages/wrangler/src/dev/get-local-persistence-path.tsx new file mode 100644 index 00000000000..36bd792bf0a --- /dev/null +++ b/packages/wrangler/src/dev/get-local-persistence-path.tsx @@ -0,0 +1,31 @@ +import path from "node:path"; + +export function getLocalPersistencePath( + persistTo: string | undefined, + doPersist: true, + configPath: string | undefined +): string; + +export function getLocalPersistencePath( + persistTo: string | undefined, + doPersist: boolean, + configPath: string | undefined +): string | null; + +export function getLocalPersistencePath( + persistTo: string | undefined, + doPersist: boolean, + configPath: string | undefined +) { + return persistTo + ? // If path specified, always treat it as relative to cwd() + path.resolve(process.cwd(), persistTo) + : doPersist + ? // If just flagged on, treat it as relative to wrangler.toml, + // if one can be found, otherwise cwd() + path.resolve( + configPath ? path.dirname(configPath) : process.cwd(), + ".wrangler/state" + ) + : null; +} diff --git a/packages/wrangler/src/dev/local.tsx b/packages/wrangler/src/dev/local.tsx index a89de851371..6cd11c60f4b 100644 --- a/packages/wrangler/src/dev/local.tsx +++ b/packages/wrangler/src/dev/local.tsx @@ -24,6 +24,7 @@ import type { CfKvNamespace, CfR2Bucket, CfVars, + CfD1Database, } from "../worker"; import type { EsbuildBundle } from "./use-esbuild"; import type { MiniflareOptions } from "miniflare"; @@ -169,6 +170,7 @@ function useLocalWorker({ usageModel, kv_namespaces: bindings?.kv_namespaces, r2_buckets: bindings?.r2_buckets, + d1_databases: bindings?.d1_databases, internalDurableObjects, externalDurableObjects, localPersistencePath, @@ -291,6 +293,7 @@ function useLocalWorker({ bindings.durable_objects, bindings.kv_namespaces, bindings.r2_buckets, + bindings.d1_databases, bindings.vars, bindings.services, workerDefinitions, @@ -410,6 +413,7 @@ interface SetupMiniflareOptionsProps { usageModel: "bundled" | "unbound" | undefined; kv_namespaces: CfKvNamespace[] | undefined; r2_buckets: CfR2Bucket[] | undefined; + d1_databases: CfD1Database[] | undefined; internalDurableObjects: CfDurableObject[]; externalDurableObjects: CfDurableObject[]; localPersistencePath: string | null; @@ -439,6 +443,7 @@ export function setupMiniflareOptions({ usageModel, kv_namespaces, r2_buckets, + d1_databases, internalDurableObjects, externalDurableObjects, localPersistencePath, @@ -504,12 +509,14 @@ export function setupMiniflareOptions({ }) .filter(([_, details]) => !!details) ), + d1Databases: d1_databases?.map((db) => db.binding), ...(localPersistencePath ? { cachePersist: path.join(localPersistencePath, "cache"), durableObjectsPersist: path.join(localPersistencePath, "do"), kvPersist: path.join(localPersistencePath, "kv"), r2Persist: path.join(localPersistencePath, "r2"), + d1Persist: path.join(localPersistencePath, "d1"), } : { // We mark these as true, so that they'll diff --git a/packages/wrangler/src/dev/start-server.ts b/packages/wrangler/src/dev/start-server.ts index 35561deb737..27e4cea45cc 100644 --- a/packages/wrangler/src/dev/start-server.ts +++ b/packages/wrangler/src/dev/start-server.ts @@ -211,6 +211,7 @@ async function runEsbuild({ services, firstPartyWorkerDevFacade, targetConsumer: "dev", // We are starting a dev server + local: false, testScheduled, }); @@ -319,6 +320,7 @@ export async function startLocalServer({ usageModel, kv_namespaces: bindings?.kv_namespaces, r2_buckets: bindings?.r2_buckets, + d1_databases: bindings?.d1_databases, internalDurableObjects, externalDurableObjects, localPersistencePath, diff --git a/packages/wrangler/src/dev/use-esbuild.ts b/packages/wrangler/src/dev/use-esbuild.ts index 6006a734d14..b7d096d1ff9 100644 --- a/packages/wrangler/src/dev/use-esbuild.ts +++ b/packages/wrangler/src/dev/use-esbuild.ts @@ -30,12 +30,14 @@ export function useEsbuild({ tsconfig, minify, nodeCompat, + betaD1Shims, define, noBundle, workerDefinitions, services, durableObjects, firstPartyWorkerDevFacade, + local, targetConsumer, testScheduled, }: { @@ -51,10 +53,12 @@ export function useEsbuild({ tsconfig: string | undefined; minify: boolean | undefined; nodeCompat: boolean | undefined; + betaD1Shims?: string[]; noBundle: boolean; workerDefinitions: WorkerRegistry; durableObjects: Config["durable_objects"]; firstPartyWorkerDevFacade: boolean | undefined; + local: boolean; targetConsumer: "dev" | "publish"; testScheduled: boolean; }): EsbuildBundle | undefined { @@ -110,6 +114,7 @@ export function useEsbuild({ tsconfig, minify, nodeCompat, + betaD1Shims, define, checkFetch: true, assets: assets && { @@ -120,6 +125,7 @@ export function useEsbuild({ workerDefinitions, services, firstPartyWorkerDevFacade, + local, targetConsumer, testScheduled, }); @@ -140,7 +146,6 @@ export function useEsbuild({ watcher.close(); }; } - setBundle({ id: 0, entry, @@ -179,6 +184,8 @@ export function useEsbuild({ durableObjects, workerDefinitions, firstPartyWorkerDevFacade, + betaD1Shims, + local, targetConsumer, testScheduled, ]); diff --git a/packages/wrangler/src/dialogs.tsx b/packages/wrangler/src/dialogs.tsx index f242a3c5db5..aae18152826 100644 --- a/packages/wrangler/src/dialogs.tsx +++ b/packages/wrangler/src/dialogs.tsx @@ -138,6 +138,10 @@ export function select( }); } +export function logDim(msg: string) { + console.log(chalk.gray(msg)); +} + export async function fromDashMessagePrompt( deploySource: "dash" | "wrangler" | "api" ): Promise { diff --git a/packages/wrangler/src/index.tsx b/packages/wrangler/src/index.tsx index 8050fb07f3b..9f5e009640a 100644 --- a/packages/wrangler/src/index.tsx +++ b/packages/wrangler/src/index.tsx @@ -7,33 +7,34 @@ import TOML from "@iarna/toml"; import chalk from "chalk"; import onExit from "signal-exit"; import supportsColor from "supports-color"; -import { setGlobalDispatcher, ProxyAgent } from "undici"; +import { ProxyAgent, setGlobalDispatcher } from "undici"; import makeCLI from "yargs"; import { version as wranglerVersion } from "../package.json"; import { fetchResult } from "./cfetch"; import { findWranglerToml, readConfig } from "./config"; import { createWorkerUploadForm } from "./create-worker-upload-form"; +import { d1api } from "./d1"; import { devHandler, devOptions } from "./dev"; import { confirm, prompt } from "./dialogs"; import { workerNamespaceCommands } from "./dispatch-namespace"; import { getEntry } from "./entry"; import { DeprecationError } from "./errors"; import { generateHandler, generateOptions } from "./generate"; -import { initOptions, initHandler } from "./init"; +import { initHandler, initOptions } from "./init"; import { - getKVNamespaceId, - listKVNamespaces, - listKVNamespaceKeys, - putKVKeyValue, - putKVBulkKeyValue, - deleteKVBulkKeyValue, createKVNamespace, - isValidKVNamespaceBinding, + deleteKVBulkKeyValue, + deleteKVKeyValue, + deleteKVNamespace, getKVKeyValue, + getKVNamespaceId, isKVKeyValue, + isValidKVNamespaceBinding, + listKVNamespaceKeys, + listKVNamespaces, + putKVBulkKeyValue, + putKVKeyValue, unexpectedKVKeyValueProps, - deleteKVNamespace, - deleteKVKeyValue, } from "./kv"; import { logger } from "./logger"; import * as metrics from "./metrics"; @@ -66,11 +67,11 @@ import { } from "./tail"; import { updateCheck } from "./update-check"; import { + listScopes, login, logout, - listScopes, - validateScopeKeys, requireAuth, + validateScopeKeys, } from "./user"; import { whoami } from "./whoami"; @@ -80,7 +81,6 @@ import type { KeyValue } from "./kv"; import type { TailCLIFilters } from "./tail"; import type { Readable } from "node:stream"; import type { RawData } from "ws"; -import type { CommandModule } from "yargs"; import type Yargs from "yargs"; export type ConfigPath = string | undefined; @@ -262,7 +262,7 @@ function createCLIParser(argv: string[]) { .wrap(null); // Default help command that supports the subcommands - const subHelp: CommandModule = { + const subHelp: Yargs.CommandModule = { command: ["*"], handler: async (args) => { setImmediate(() => @@ -578,7 +578,6 @@ function createCLIParser(argv: string[]) { "The --assets argument is experimental and may change or break at any time" ); } - if (args.latest) { logger.warn( "Using the latest version of the Workers runtime. To silence this warning, please choose a specific version of the runtime with --compatibility-date, or add a compatibility_date to your wrangler.toml.\n" @@ -1015,6 +1014,7 @@ function createCLIParser(argv: string[]) { vars: {}, durable_objects: { bindings: [] }, r2_buckets: [], + d1_databases: [], services: [], wasm_modules: {}, text_blobs: {}, @@ -2182,6 +2182,8 @@ function createCLIParser(argv: string[]) { } ); + wrangler.command("d1", "🗄 Interact with a D1 database", d1api); + wrangler.command( "pubsub", "📮 Interact and manage Pub/Sub Brokers", diff --git a/packages/wrangler/src/pages/dev.tsx b/packages/wrangler/src/pages/dev.tsx index e340e7a44b2..855aa29c266 100644 --- a/packages/wrangler/src/pages/dev.tsx +++ b/packages/wrangler/src/pages/dev.tsx @@ -99,6 +99,10 @@ export function Options(yargs: Argv) { description: "KV namespace to bind (--kv KV_BINDING)", alias: "k", }, + d1: { + type: "array", + description: "D1 database to bind", + }, do: { type: "array", description: "Durable Object to bind (--do NAME=CLASS)", @@ -167,6 +171,7 @@ export const Handler = async ({ binding: bindings = [], kv: kvs = [], do: durableObjects = [], + d1: d1s = [], r2: r2s = [], "live-reload": liveReload, "local-protocol": localProtocol, @@ -492,6 +497,12 @@ export const Handler = async ({ return { binding: binding.toString(), bucket_name: "" }; }), + d1Databases: d1s.map((binding) => ({ + binding: binding.toString(), + database_id: "", // Required for types, but unused by dev + database_name: `local-${binding}`, + })), + enablePagesAssetsServiceBinding: { proxyPort, directory, diff --git a/packages/wrangler/src/paths.ts b/packages/wrangler/src/paths.ts index 0bd3027ed4e..d6ae6229c55 100644 --- a/packages/wrangler/src/paths.ts +++ b/packages/wrangler/src/paths.ts @@ -1,5 +1,5 @@ import { assert } from "node:console"; -import { resolve } from "node:path"; +import { relative, basename, resolve } from "node:path"; type DiscriminatedPath = string & { _discriminator: Discriminator; @@ -18,12 +18,38 @@ export type UrlPath = DiscriminatedPath<"UrlPath">; * Use this helper to convert a `string` to a `UrlPath` when it is not clear whether the string needs normalizing. * Replaces all back-slashes with forward-slashes, and throws an error if the path contains a drive letter (e.g. `C:`). */ -export function toUrlPath(path: string): UrlPath { +export function toUrlPath(filePath: string): UrlPath { assert( - !/^[a-z]:/i.test(path), + !/^[a-z]:/i.test(filePath), "Tried to convert a Windows file path with a drive to a URL path." ); - return path.replace(/\\/g, "/") as UrlPath; + return filePath.replace(/\\/g, "/") as UrlPath; +} + +/** + * Get a human-readable path, relative to process.cwd(), prefixed with ./ if + * in a nested subdirectory, to aid with readability. + * Only used for logging e.g. `Loading DB at ${readableRelative(dbPath)}`: + * + * E.g. (assuming process.cwd() is /pwd) + * + * readableRelative('/pwd/wrangler.toml') => 'wrangler.toml' + * readableRelative('/wrangler.toml') => '../wrangler.toml' + * readableRelative('/pwd/subdir/wrangler.toml') => './subdir/wrangler.toml' + * + * */ +export function readableRelative(to: string) { + const relativePath = relative(process.cwd(), to); + if ( + // No directory nesting, return as-is + basename(relativePath) === relativePath || + // Outside current directory + relativePath.startsWith(".") + ) { + return relativePath; + } else { + return "./" + relativePath; + } } /** diff --git a/packages/wrangler/src/publish.ts b/packages/wrangler/src/publish.ts index f853ef888d5..83c283c8ff8 100644 --- a/packages/wrangler/src/publish.ts +++ b/packages/wrangler/src/publish.ts @@ -14,6 +14,7 @@ import { logger } from "./logger"; import { getMetricsUsageHeaders } from "./metrics"; import { ParseError } from "./parse"; import { syncAssets } from "./sites"; +import { identifyD1BindingsAsBeta } from "./worker"; import { getZoneForRoute } from "./zones"; import type { Config } from "./config"; import type { @@ -407,6 +408,9 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m { serveAssetsFromWorker: !props.isWorkersSite && Boolean(props.assetPaths), + betaD1Shims: identifyD1BindingsAsBeta(config.d1_databases)?.map( + (db) => db.binding + ), jsxFactory, jsxFragment, rules: props.rules, @@ -429,6 +433,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m // We want to know if the build is for development or publishing // This could potentially cause issues as we no longer have identical behaviour between dev and publish? targetConsumer: "publish", + local: false, } ); @@ -476,6 +481,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m data_blobs: config.data_blobs, durable_objects: config.durable_objects, r2_buckets: config.r2_buckets, + d1_databases: identifyD1BindingsAsBeta(config.d1_databases), services: config.services, dispatch_namespaces: config.dispatch_namespaces, logfwdr: config.logfwdr, diff --git a/packages/wrangler/src/user/user.tsx b/packages/wrangler/src/user/user.tsx index 714d1b707cc..9ca30e446b7 100644 --- a/packages/wrangler/src/user/user.tsx +++ b/packages/wrangler/src/user/user.tsx @@ -309,6 +309,7 @@ const Scopes = { "workers_scripts:write": "See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.", "workers_tail:read": "See Cloudflare Workers tail and script data.", + "d1:write": "See and change D1 Databases.", "pages:write": "See and change Cloudflare Pages projects, settings and deployments.", "zone:read": "Grants read level access to account zone.", diff --git a/packages/wrangler/src/worker.ts b/packages/wrangler/src/worker.ts index d8db6a0ff50..86dbefe2e98 100644 --- a/packages/wrangler/src/worker.ts +++ b/packages/wrangler/src/worker.ts @@ -1,9 +1,11 @@ +import type { Environment } from "./config"; import type { Route } from "./config/environment"; import type { ApiCredentials } from "./user"; /** * A Cloudflare account. */ + export interface CfAccount { /** * An API token. @@ -116,6 +118,17 @@ export interface CfR2Bucket { bucket_name: string; } +export const D1_BETA_PREFIX = `__D1_BETA__` as const; +export type D1PrefixedBinding = `${typeof D1_BETA_PREFIX}${string}`; + +export interface CfD1Database { + // For now, all D1 bindings are beta + binding: D1PrefixedBinding; + database_id: string; + database_name?: string; + preview_database_id?: string; +} + interface CfService { binding: string; service: string; @@ -182,6 +195,7 @@ export interface CfWorkerInit { data_blobs: CfDataBlobBindings | undefined; durable_objects: { bindings: CfDurableObject[] } | undefined; r2_buckets: CfR2Bucket[] | undefined; + d1_databases: CfD1Database[] | undefined; services: CfService[] | undefined; dispatch_namespaces: CfDispatchNamespace[] | undefined; logfwdr: CfLogfwdr | undefined; @@ -202,3 +216,19 @@ export interface CfWorkerContext { routes: Route[] | undefined; sendMetrics: boolean | undefined; } + +// Prefix binding with identifier which will then get picked up by the D1 shim. +// Once the D1 Api is out of beta, this function can be removed. +export function identifyD1BindingsAsBeta( + dbs: Environment["d1_databases"] +): CfD1Database[] | undefined { + return dbs?.map((db) => ({ + ...db, + binding: `${D1_BETA_PREFIX}${db.binding}`, + })); +} + +// Remove beta prefix +export function removeD1BetaPrefix(binding: D1PrefixedBinding): string { + return binding.slice(D1_BETA_PREFIX.length); +} diff --git a/packages/wrangler/templates/d1-beta-facade.js b/packages/wrangler/templates/d1-beta-facade.js new file mode 100644 index 00000000000..5ea73910319 --- /dev/null +++ b/packages/wrangler/templates/d1-beta-facade.js @@ -0,0 +1,174 @@ +// src/shim.ts +import worker from "__ENTRY_POINT__"; +export * from "__ENTRY_POINT__"; + +// src/index.ts +var D1Database = class { + constructor(binding) { + this.binding = binding; + } + prepare(query) { + return new D1PreparedStatement(this, query); + } + async dump() { + const response = await this.binding.fetch("/dump", { + method: "POST", + headers: { + "content-type": "application/json", + }, + }); + if (response.status !== 200) { + const err = await response.json(); + throw new Error("D1_DUMP_ERROR", { + cause: new Error(err.error), + }); + } + return await response.arrayBuffer(); + } + async batch(statements) { + const exec = await this._send( + "/query", + statements.map((s) => s.statement), + statements.map((s) => s.params) + ); + return exec; + } + async exec(query) { + const lines = query.trim().split("\n"); + const _exec = await this._send("/query", lines, []); + const exec = Array.isArray(_exec) ? _exec : [_exec]; + const error = exec + .map((r) => { + return r.error ? 1 : 0; + }) + .indexOf(1); + if (error !== -1) { + throw new Error("D1_EXEC_ERROR", { + cause: new Error( + `Error in line ${error + 1}: ${lines[error]}: ${exec[error].error}` + ), + }); + } else { + return { + count: exec.length, + duration: exec.reduce((p, c) => { + return p + c.duration; + }, 0), + }; + } + } + async _send(endpoint, query, params) { + const body = JSON.stringify( + typeof query == "object" + ? query.map((s, index) => { + return { sql: s, params: params[index] }; + }) + : { + sql: query, + params, + } + ); + const response = await this.binding.fetch(endpoint, { + method: "POST", + headers: { + "content-type": "application/json", + }, + body, + }); + if (response.status !== 200) { + const err = await response.json(); + throw new Error("D1_ERROR", { cause: new Error(err.error) }); + } + const answer = await response.json(); + return Array.isArray(answer) ? answer : answer; + } +}; +var D1PreparedStatement = class { + constructor(database, statement, values) { + this.database = database; + this.statement = statement; + this.params = values || []; + } + bind(...values) { + return new D1PreparedStatement(this.database, this.statement, values); + } + async first(colName) { + const info = firstIfArray( + await this.database._send("/query", this.statement, this.params) + ); + const results = info.results; + if (results.length < 1) { + throw new Error("D1_NORESULTS", { cause: new Error("No results") }); + } + const result = results[0]; + if (colName !== void 0) { + if (result[colName] === void 0) { + throw new Error("D1_COLUMN_NOTFOUND", { + cause: new Error(`Column not found`), + }); + } + return result[colName]; + } else { + return result; + } + } + async run() { + return firstIfArray( + await this.database._send("/execute", this.statement, this.params) + ); + } + async all() { + return firstIfArray( + await this.database._send("/query", this.statement, this.params) + ); + } + async raw() { + const s = firstIfArray( + await this.database._send("/query", this.statement, this.params) + ); + const raw = []; + for (var r in s.results) { + const entry = Object.keys(s.results[r]).map((k) => { + return s.results[r][k]; + }); + raw.push(entry); + } + return raw; + } +}; +function firstIfArray(results) { + return Array.isArray(results) ? results[0] : results; +} + +// src/shim.ts +var D1_IMPORTS = __D1_IMPORTS__; +var LOCAL_MODE = __LOCAL_MODE__; +var D1_BETA_PREFIX = `__D1_BETA__`; +var envMap = /* @__PURE__ */ new Map(); +function getMaskedEnv(env) { + if (envMap.has(env)) return envMap.get(env); + const newEnv = new Map(Object.entries(env)); + D1_IMPORTS.filter((bindingName) => + bindingName.startsWith(D1_BETA_PREFIX) + ).forEach((bindingName) => { + newEnv.delete(bindingName); + const newName = bindingName.slice(D1_BETA_PREFIX.length); + const newBinding = !LOCAL_MODE + ? new D1Database(env[bindingName]) + : env[bindingName]; + newEnv.set(newName, newBinding); + }); + const newEnvObj = Object.fromEntries(newEnv.entries()); + envMap.set(env, newEnvObj); + return newEnvObj; +} +var shim_default = { + async fetch(request, env, ctx) { + return worker.fetch(request, getMaskedEnv(env), ctx); + }, + + async scheduled(controller, env, ctx) { + return worker.scheduled(controller, getMaskedEnv(env), ctx); + }, +}; +export { shim_default as default };