wrangler 2.1.6 → 2.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/miniflare-dist/index.mjs +5 -20
- package/package.json +14 -3
- package/src/__tests__/api-dev.test.ts +20 -0
- package/src/__tests__/configuration.test.ts +125 -22
- package/src/__tests__/dev.test.tsx +0 -2
- package/src/__tests__/helpers/mock-oauth-flow.ts +4 -2
- package/src/__tests__/index.test.ts +2 -0
- package/src/__tests__/paths.test.ts +23 -1
- package/src/__tests__/publish.test.ts +8 -10
- package/src/__tests__/user.test.ts +4 -4
- package/src/__tests__/whoami.test.tsx +0 -1
- package/src/__tests__/worker-namespace.test.ts +102 -112
- package/src/api/dev.ts +12 -12
- package/src/bundle.ts +59 -1
- package/src/cfetch/internal.ts +37 -21
- package/src/config/environment.ts +20 -0
- package/src/config/index.ts +32 -0
- package/src/config/validation.ts +59 -0
- package/src/config-cache.ts +1 -1
- package/src/create-worker-upload-form.ts +9 -0
- package/src/d1/backups.tsx +212 -0
- package/src/d1/create.tsx +54 -0
- package/src/d1/delete.tsx +56 -0
- package/src/d1/execute.tsx +294 -0
- package/src/d1/formatTimeAgo.ts +14 -0
- package/src/d1/index.ts +75 -0
- package/src/d1/list.tsx +48 -0
- package/src/d1/options.ts +12 -0
- package/src/d1/types.tsx +14 -0
- package/src/d1/utils.ts +39 -0
- package/src/dev/dev.tsx +30 -3
- package/src/dev/get-local-persistence-path.tsx +31 -0
- package/src/dev/local.tsx +73 -11
- package/src/dev/start-server.ts +6 -3
- package/src/dev/use-esbuild.ts +12 -1
- package/src/dev.tsx +48 -29
- package/src/dialogs.tsx +4 -0
- package/src/environment-variables.ts +17 -2
- package/src/index.tsx +18 -16
- package/src/logger.ts +11 -4
- package/src/miniflare-cli/index.ts +11 -16
- package/src/pages/dev.tsx +13 -9
- package/src/paths.ts +30 -4
- package/src/proxy.ts +21 -1
- package/src/publish.ts +7 -0
- package/src/user/user.tsx +1 -0
- package/src/worker.ts +30 -0
- package/templates/d1-beta-facade.js +174 -0
- package/templates/experimental-local-cache-stubs.js +27 -0
- package/wrangler-dist/cli.d.ts +438 -7
- package/wrangler-dist/cli.js +11679 -3911
- package/src/miniflare-cli/enum-keys.ts +0 -17
package/src/index.tsx
CHANGED
|
@@ -7,33 +7,34 @@ import TOML from "@iarna/toml";
|
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import onExit from "signal-exit";
|
|
9
9
|
import supportsColor from "supports-color";
|
|
10
|
-
import {
|
|
10
|
+
import { ProxyAgent, setGlobalDispatcher } from "undici";
|
|
11
11
|
import makeCLI from "yargs";
|
|
12
12
|
import { version as wranglerVersion } from "../package.json";
|
|
13
13
|
import { fetchResult } from "./cfetch";
|
|
14
14
|
import { findWranglerToml, readConfig } from "./config";
|
|
15
15
|
import { createWorkerUploadForm } from "./create-worker-upload-form";
|
|
16
|
+
import { d1api } from "./d1";
|
|
16
17
|
import { devHandler, devOptions } from "./dev";
|
|
17
18
|
import { confirm, prompt } from "./dialogs";
|
|
18
19
|
import { workerNamespaceCommands } from "./dispatch-namespace";
|
|
19
20
|
import { getEntry } from "./entry";
|
|
20
21
|
import { DeprecationError } from "./errors";
|
|
21
22
|
import { generateHandler, generateOptions } from "./generate";
|
|
22
|
-
import {
|
|
23
|
+
import { initHandler, initOptions } from "./init";
|
|
23
24
|
import {
|
|
24
|
-
getKVNamespaceId,
|
|
25
|
-
listKVNamespaces,
|
|
26
|
-
listKVNamespaceKeys,
|
|
27
|
-
putKVKeyValue,
|
|
28
|
-
putKVBulkKeyValue,
|
|
29
|
-
deleteKVBulkKeyValue,
|
|
30
25
|
createKVNamespace,
|
|
31
|
-
|
|
26
|
+
deleteKVBulkKeyValue,
|
|
27
|
+
deleteKVKeyValue,
|
|
28
|
+
deleteKVNamespace,
|
|
32
29
|
getKVKeyValue,
|
|
30
|
+
getKVNamespaceId,
|
|
33
31
|
isKVKeyValue,
|
|
32
|
+
isValidKVNamespaceBinding,
|
|
33
|
+
listKVNamespaceKeys,
|
|
34
|
+
listKVNamespaces,
|
|
35
|
+
putKVBulkKeyValue,
|
|
36
|
+
putKVKeyValue,
|
|
34
37
|
unexpectedKVKeyValueProps,
|
|
35
|
-
deleteKVNamespace,
|
|
36
|
-
deleteKVKeyValue,
|
|
37
38
|
} from "./kv";
|
|
38
39
|
import { logger } from "./logger";
|
|
39
40
|
import * as metrics from "./metrics";
|
|
@@ -66,11 +67,11 @@ import {
|
|
|
66
67
|
} from "./tail";
|
|
67
68
|
import { updateCheck } from "./update-check";
|
|
68
69
|
import {
|
|
70
|
+
listScopes,
|
|
69
71
|
login,
|
|
70
72
|
logout,
|
|
71
|
-
listScopes,
|
|
72
|
-
validateScopeKeys,
|
|
73
73
|
requireAuth,
|
|
74
|
+
validateScopeKeys,
|
|
74
75
|
} from "./user";
|
|
75
76
|
import { whoami } from "./whoami";
|
|
76
77
|
|
|
@@ -80,7 +81,6 @@ import type { KeyValue } from "./kv";
|
|
|
80
81
|
import type { TailCLIFilters } from "./tail";
|
|
81
82
|
import type { Readable } from "node:stream";
|
|
82
83
|
import type { RawData } from "ws";
|
|
83
|
-
import type { CommandModule } from "yargs";
|
|
84
84
|
import type Yargs from "yargs";
|
|
85
85
|
|
|
86
86
|
export type ConfigPath = string | undefined;
|
|
@@ -262,7 +262,7 @@ function createCLIParser(argv: string[]) {
|
|
|
262
262
|
.wrap(null);
|
|
263
263
|
|
|
264
264
|
// Default help command that supports the subcommands
|
|
265
|
-
const subHelp: CommandModule = {
|
|
265
|
+
const subHelp: Yargs.CommandModule = {
|
|
266
266
|
command: ["*"],
|
|
267
267
|
handler: async (args) => {
|
|
268
268
|
setImmediate(() =>
|
|
@@ -578,7 +578,6 @@ function createCLIParser(argv: string[]) {
|
|
|
578
578
|
"The --assets argument is experimental and may change or break at any time"
|
|
579
579
|
);
|
|
580
580
|
}
|
|
581
|
-
|
|
582
581
|
if (args.latest) {
|
|
583
582
|
logger.warn(
|
|
584
583
|
"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[]) {
|
|
|
1015
1014
|
vars: {},
|
|
1016
1015
|
durable_objects: { bindings: [] },
|
|
1017
1016
|
r2_buckets: [],
|
|
1017
|
+
d1_databases: [],
|
|
1018
1018
|
services: [],
|
|
1019
1019
|
wasm_modules: {},
|
|
1020
1020
|
text_blobs: {},
|
|
@@ -2182,6 +2182,8 @@ function createCLIParser(argv: string[]) {
|
|
|
2182
2182
|
}
|
|
2183
2183
|
);
|
|
2184
2184
|
|
|
2185
|
+
wrangler.command("d1", "🗄 Interact with a D1 database", d1api);
|
|
2186
|
+
|
|
2185
2187
|
wrangler.command(
|
|
2186
2188
|
"pubsub",
|
|
2187
2189
|
"📮 Interact and manage Pub/Sub Brokers",
|
package/src/logger.ts
CHANGED
|
@@ -3,10 +3,12 @@ import { formatMessagesSync } from "esbuild";
|
|
|
3
3
|
import { getEnvironmentVariableFactory } from "./environment-variables";
|
|
4
4
|
|
|
5
5
|
const LOGGER_LEVELS = {
|
|
6
|
+
none: -1,
|
|
6
7
|
error: 0,
|
|
7
8
|
warn: 1,
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
info: 2,
|
|
10
|
+
log: 3,
|
|
11
|
+
debug: 4,
|
|
10
12
|
} as const;
|
|
11
13
|
|
|
12
14
|
type LoggerLevel = keyof typeof LOGGER_LEVELS;
|
|
@@ -15,6 +17,7 @@ type LoggerLevel = keyof typeof LOGGER_LEVELS;
|
|
|
15
17
|
const LOGGER_LEVEL_FORMAT_TYPE_MAP = {
|
|
16
18
|
error: "error",
|
|
17
19
|
warn: "warning",
|
|
20
|
+
info: undefined,
|
|
18
21
|
log: undefined,
|
|
19
22
|
debug: undefined,
|
|
20
23
|
} as const;
|
|
@@ -31,17 +34,21 @@ class Logger {
|
|
|
31
34
|
columns = process.stdout.columns;
|
|
32
35
|
|
|
33
36
|
debug = (...args: unknown[]) => this.doLog("debug", args);
|
|
37
|
+
info = (...args: unknown[]) => this.doLog("info", args);
|
|
34
38
|
log = (...args: unknown[]) => this.doLog("log", args);
|
|
35
39
|
warn = (...args: unknown[]) => this.doLog("warn", args);
|
|
36
40
|
error = (...args: unknown[]) => this.doLog("error", args);
|
|
37
41
|
|
|
38
|
-
private doLog(messageLevel: LoggerLevel, args: unknown[]) {
|
|
42
|
+
private doLog(messageLevel: Exclude<LoggerLevel, "none">, args: unknown[]) {
|
|
39
43
|
if (LOGGER_LEVELS[this.loggerLevel] >= LOGGER_LEVELS[messageLevel]) {
|
|
40
44
|
console[messageLevel](this.formatMessage(messageLevel, format(...args)));
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
private formatMessage(
|
|
48
|
+
private formatMessage(
|
|
49
|
+
level: Exclude<LoggerLevel, "none">,
|
|
50
|
+
message: string
|
|
51
|
+
): string {
|
|
45
52
|
const kind = LOGGER_LEVEL_FORMAT_TYPE_MAP[level];
|
|
46
53
|
if (kind) {
|
|
47
54
|
// Format the message using the esbuild formatter.
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
} from "@miniflare/durable-objects";
|
|
6
6
|
import {
|
|
7
7
|
Log,
|
|
8
|
-
LogLevel,
|
|
9
8
|
Miniflare,
|
|
10
9
|
Response as MiniflareResponse,
|
|
11
10
|
Request as MiniflareRequest,
|
|
@@ -14,7 +13,6 @@ import yargs from "yargs";
|
|
|
14
13
|
import { hideBin } from "yargs/helpers";
|
|
15
14
|
import { FatalError } from "../errors";
|
|
16
15
|
import generateASSETSBinding from "./assets";
|
|
17
|
-
import { enumKeys } from "./enum-keys";
|
|
18
16
|
import { getRequestContextCheckOptions } from "./request-context";
|
|
19
17
|
import type { Options } from "./assets";
|
|
20
18
|
import type { AddressInfo } from "net";
|
|
@@ -35,26 +33,23 @@ class NoOpLog extends Log {
|
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
async function main() {
|
|
38
|
-
const args = await yargs(hideBin(process.argv))
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
.option("log", {
|
|
42
|
-
choices: enumKeys(LogLevel),
|
|
43
|
-
}).argv;
|
|
44
|
-
|
|
45
|
-
const logLevel = LogLevel[args.log ?? "INFO"];
|
|
36
|
+
const args = await yargs(hideBin(process.argv)).help(false).version(false)
|
|
37
|
+
.argv;
|
|
38
|
+
|
|
46
39
|
const requestContextCheckOptions = await getRequestContextCheckOptions();
|
|
47
40
|
const config = {
|
|
48
41
|
...JSON.parse((args._[0] as string) ?? "{}"),
|
|
49
42
|
...requestContextCheckOptions,
|
|
50
43
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
const logLevel = config.logLevel.toUpperCase();
|
|
45
|
+
|
|
46
|
+
config.log =
|
|
47
|
+
config.logLevel === "none"
|
|
48
|
+
? new NoOpLog()
|
|
49
|
+
: new Log(logLevel, config.logOptions);
|
|
55
50
|
|
|
56
|
-
if (logLevel
|
|
57
|
-
console.log("OPTIONS:\n", JSON.stringify(config, null, 2));
|
|
51
|
+
if (logLevel === "DEBUG" || logLevel === "VERBOSE") {
|
|
52
|
+
console.log("MINIFLARE OPTIONS:\n", JSON.stringify(config, null, 2));
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
config.bindings = {
|
package/src/pages/dev.tsx
CHANGED
|
@@ -99,6 +99,10 @@ export function Options(yargs: Argv) {
|
|
|
99
99
|
description: "KV namespace to bind (--kv KV_BINDING)",
|
|
100
100
|
alias: "k",
|
|
101
101
|
},
|
|
102
|
+
d1: {
|
|
103
|
+
type: "array",
|
|
104
|
+
description: "D1 database to bind",
|
|
105
|
+
},
|
|
102
106
|
do: {
|
|
103
107
|
type: "array",
|
|
104
108
|
description: "Durable Object to bind (--do NAME=CLASS)",
|
|
@@ -146,12 +150,9 @@ export function Options(yargs: Argv) {
|
|
|
146
150
|
type: "string",
|
|
147
151
|
hidden: true,
|
|
148
152
|
},
|
|
149
|
-
|
|
150
153
|
"log-level": {
|
|
151
|
-
// "none" will currently default to "error" for Wrangler Logger
|
|
152
154
|
choices: ["debug", "info", "log", "warn", "error", "none"] as const,
|
|
153
155
|
describe: "Specify logging level",
|
|
154
|
-
default: "log",
|
|
155
156
|
},
|
|
156
157
|
})
|
|
157
158
|
.epilogue(pagesBetaWarning);
|
|
@@ -170,6 +171,7 @@ export const Handler = async ({
|
|
|
170
171
|
binding: bindings = [],
|
|
171
172
|
kv: kvs = [],
|
|
172
173
|
do: durableObjects = [],
|
|
174
|
+
d1: d1s = [],
|
|
173
175
|
r2: r2s = [],
|
|
174
176
|
"live-reload": liveReload,
|
|
175
177
|
"local-protocol": localProtocol,
|
|
@@ -186,12 +188,8 @@ export const Handler = async ({
|
|
|
186
188
|
|
|
187
189
|
type LogLevelArg = "debug" | "info" | "log" | "warn" | "error" | "none";
|
|
188
190
|
if (logLevel) {
|
|
189
|
-
// we don't define a "none" logLevel, so "error" will do for now.
|
|
190
191
|
// The YargsOptionsToInterface doesn't handle the passing in of Unions from choices in Yargs
|
|
191
|
-
logger.loggerLevel =
|
|
192
|
-
(logLevel as LogLevelArg) === "none"
|
|
193
|
-
? "error"
|
|
194
|
-
: (logLevel as Exclude<"none", LogLevelArg>);
|
|
192
|
+
logger.loggerLevel = logLevel as LogLevelArg;
|
|
195
193
|
}
|
|
196
194
|
|
|
197
195
|
if (!local) {
|
|
@@ -499,6 +497,12 @@ export const Handler = async ({
|
|
|
499
497
|
return { binding: binding.toString(), bucket_name: "" };
|
|
500
498
|
}),
|
|
501
499
|
|
|
500
|
+
d1Databases: d1s.map((binding) => ({
|
|
501
|
+
binding: binding.toString(),
|
|
502
|
+
database_id: "", // Required for types, but unused by dev
|
|
503
|
+
database_name: `local-${binding}`,
|
|
504
|
+
})),
|
|
505
|
+
|
|
502
506
|
enablePagesAssetsServiceBinding: {
|
|
503
507
|
proxyPort,
|
|
504
508
|
directory,
|
|
@@ -508,8 +512,8 @@ export const Handler = async ({
|
|
|
508
512
|
persistTo,
|
|
509
513
|
showInteractiveDevSession: undefined,
|
|
510
514
|
inspect: true,
|
|
511
|
-
logLevel: "warn",
|
|
512
515
|
logPrefix: "pages",
|
|
516
|
+
logLevel: logLevel ?? "warn",
|
|
513
517
|
},
|
|
514
518
|
{ testMode: false, disableExperimentalWarning: true }
|
|
515
519
|
);
|
package/src/paths.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert } from "node:console";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { relative, basename, resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
type DiscriminatedPath<Discriminator extends string> = string & {
|
|
5
5
|
_discriminator: Discriminator;
|
|
@@ -18,12 +18,38 @@ export type UrlPath = DiscriminatedPath<"UrlPath">;
|
|
|
18
18
|
* Use this helper to convert a `string` to a `UrlPath` when it is not clear whether the string needs normalizing.
|
|
19
19
|
* Replaces all back-slashes with forward-slashes, and throws an error if the path contains a drive letter (e.g. `C:`).
|
|
20
20
|
*/
|
|
21
|
-
export function toUrlPath(
|
|
21
|
+
export function toUrlPath(filePath: string): UrlPath {
|
|
22
22
|
assert(
|
|
23
|
-
!/^[a-z]:/i.test(
|
|
23
|
+
!/^[a-z]:/i.test(filePath),
|
|
24
24
|
"Tried to convert a Windows file path with a drive to a URL path."
|
|
25
25
|
);
|
|
26
|
-
return
|
|
26
|
+
return filePath.replace(/\\/g, "/") as UrlPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get a human-readable path, relative to process.cwd(), prefixed with ./ if
|
|
31
|
+
* in a nested subdirectory, to aid with readability.
|
|
32
|
+
* Only used for logging e.g. `Loading DB at ${readableRelative(dbPath)}`:
|
|
33
|
+
*
|
|
34
|
+
* E.g. (assuming process.cwd() is /pwd)
|
|
35
|
+
*
|
|
36
|
+
* readableRelative('/pwd/wrangler.toml') => 'wrangler.toml'
|
|
37
|
+
* readableRelative('/wrangler.toml') => '../wrangler.toml'
|
|
38
|
+
* readableRelative('/pwd/subdir/wrangler.toml') => './subdir/wrangler.toml'
|
|
39
|
+
*
|
|
40
|
+
* */
|
|
41
|
+
export function readableRelative(to: string) {
|
|
42
|
+
const relativePath = relative(process.cwd(), to);
|
|
43
|
+
if (
|
|
44
|
+
// No directory nesting, return as-is
|
|
45
|
+
basename(relativePath) === relativePath ||
|
|
46
|
+
// Outside current directory
|
|
47
|
+
relativePath.startsWith(".")
|
|
48
|
+
) {
|
|
49
|
+
return relativePath;
|
|
50
|
+
} else {
|
|
51
|
+
return "./" + relativePath;
|
|
52
|
+
}
|
|
27
53
|
}
|
|
28
54
|
|
|
29
55
|
/**
|
package/src/proxy.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createServer as createHttpServer } from "node:http";
|
|
2
2
|
import { connect } from "node:http2";
|
|
3
3
|
import { createServer as createHttpsServer } from "node:https";
|
|
4
|
+
import { networkInterfaces } from "node:os";
|
|
4
5
|
import WebSocket from "faye-websocket";
|
|
5
6
|
import { createHttpTerminator } from "http-terminator";
|
|
6
7
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -327,7 +328,15 @@ export function usePreviewServer({
|
|
|
327
328
|
})
|
|
328
329
|
.then(() => {
|
|
329
330
|
proxy.server.on("listening", () => {
|
|
330
|
-
|
|
331
|
+
const address = proxy.server.address();
|
|
332
|
+
const usedPort =
|
|
333
|
+
address && typeof address === "object" ? address.port : port;
|
|
334
|
+
logger.log(`⬣ Listening at ${localProtocol}://${ip}:${usedPort}`);
|
|
335
|
+
const accessibleHosts =
|
|
336
|
+
ip !== "0.0.0.0" ? [ip] : getAccessibleHosts();
|
|
337
|
+
for (const accessibleHost of accessibleHosts) {
|
|
338
|
+
logger.log(`- ${localProtocol}://${accessibleHost}:${usedPort}`);
|
|
339
|
+
}
|
|
331
340
|
});
|
|
332
341
|
proxy.server.listen(port, ip);
|
|
333
342
|
})
|
|
@@ -484,3 +493,14 @@ export async function waitForPortToBeAvailable(
|
|
|
484
493
|
}
|
|
485
494
|
});
|
|
486
495
|
}
|
|
496
|
+
|
|
497
|
+
function getAccessibleHosts(): string[] {
|
|
498
|
+
const hosts: string[] = [];
|
|
499
|
+
Object.values(networkInterfaces()).forEach((net) => {
|
|
500
|
+
net?.forEach(({ family, address }) => {
|
|
501
|
+
// @ts-expect-error the `family` property is numeric as of Node.js 18.0.0
|
|
502
|
+
if (family === "IPv4" || family === 4) hosts.push(address);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
return hosts;
|
|
506
|
+
}
|
package/src/publish.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { logger } from "./logger";
|
|
|
14
14
|
import { getMetricsUsageHeaders } from "./metrics";
|
|
15
15
|
import { ParseError } from "./parse";
|
|
16
16
|
import { syncAssets } from "./sites";
|
|
17
|
+
import { identifyD1BindingsAsBeta } from "./worker";
|
|
17
18
|
import { getZoneForRoute } from "./zones";
|
|
18
19
|
import type { Config } from "./config";
|
|
19
20
|
import type {
|
|
@@ -407,6 +408,9 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
407
408
|
{
|
|
408
409
|
serveAssetsFromWorker:
|
|
409
410
|
!props.isWorkersSite && Boolean(props.assetPaths),
|
|
411
|
+
betaD1Shims: identifyD1BindingsAsBeta(config.d1_databases)?.map(
|
|
412
|
+
(db) => db.binding
|
|
413
|
+
),
|
|
410
414
|
jsxFactory,
|
|
411
415
|
jsxFragment,
|
|
412
416
|
rules: props.rules,
|
|
@@ -429,6 +433,8 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
429
433
|
// We want to know if the build is for development or publishing
|
|
430
434
|
// This could potentially cause issues as we no longer have identical behaviour between dev and publish?
|
|
431
435
|
targetConsumer: "publish",
|
|
436
|
+
local: false,
|
|
437
|
+
experimentalLocalStubCache: false,
|
|
432
438
|
}
|
|
433
439
|
);
|
|
434
440
|
|
|
@@ -476,6 +482,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
476
482
|
data_blobs: config.data_blobs,
|
|
477
483
|
durable_objects: config.durable_objects,
|
|
478
484
|
r2_buckets: config.r2_buckets,
|
|
485
|
+
d1_databases: identifyD1BindingsAsBeta(config.d1_databases),
|
|
479
486
|
services: config.services,
|
|
480
487
|
dispatch_namespaces: config.dispatch_namespaces,
|
|
481
488
|
logfwdr: config.logfwdr,
|
package/src/user/user.tsx
CHANGED
|
@@ -309,6 +309,7 @@ const Scopes = {
|
|
|
309
309
|
"workers_scripts:write":
|
|
310
310
|
"See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
|
|
311
311
|
"workers_tail:read": "See Cloudflare Workers tail and script data.",
|
|
312
|
+
"d1:write": "See and change D1 Databases.",
|
|
312
313
|
"pages:write":
|
|
313
314
|
"See and change Cloudflare Pages projects, settings and deployments.",
|
|
314
315
|
"zone:read": "Grants read level access to account zone.",
|
package/src/worker.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Environment } from "./config";
|
|
1
2
|
import type { Route } from "./config/environment";
|
|
2
3
|
import type { ApiCredentials } from "./user";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A Cloudflare account.
|
|
6
7
|
*/
|
|
8
|
+
|
|
7
9
|
export interface CfAccount {
|
|
8
10
|
/**
|
|
9
11
|
* An API token.
|
|
@@ -116,6 +118,17 @@ export interface CfR2Bucket {
|
|
|
116
118
|
bucket_name: string;
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
export const D1_BETA_PREFIX = `__D1_BETA__` as const;
|
|
122
|
+
export type D1PrefixedBinding = `${typeof D1_BETA_PREFIX}${string}`;
|
|
123
|
+
|
|
124
|
+
export interface CfD1Database {
|
|
125
|
+
// For now, all D1 bindings are beta
|
|
126
|
+
binding: D1PrefixedBinding;
|
|
127
|
+
database_id: string;
|
|
128
|
+
database_name?: string;
|
|
129
|
+
preview_database_id?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
119
132
|
interface CfService {
|
|
120
133
|
binding: string;
|
|
121
134
|
service: string;
|
|
@@ -182,6 +195,7 @@ export interface CfWorkerInit {
|
|
|
182
195
|
data_blobs: CfDataBlobBindings | undefined;
|
|
183
196
|
durable_objects: { bindings: CfDurableObject[] } | undefined;
|
|
184
197
|
r2_buckets: CfR2Bucket[] | undefined;
|
|
198
|
+
d1_databases: CfD1Database[] | undefined;
|
|
185
199
|
services: CfService[] | undefined;
|
|
186
200
|
dispatch_namespaces: CfDispatchNamespace[] | undefined;
|
|
187
201
|
logfwdr: CfLogfwdr | undefined;
|
|
@@ -202,3 +216,19 @@ export interface CfWorkerContext {
|
|
|
202
216
|
routes: Route[] | undefined;
|
|
203
217
|
sendMetrics: boolean | undefined;
|
|
204
218
|
}
|
|
219
|
+
|
|
220
|
+
// Prefix binding with identifier which will then get picked up by the D1 shim.
|
|
221
|
+
// Once the D1 Api is out of beta, this function can be removed.
|
|
222
|
+
export function identifyD1BindingsAsBeta(
|
|
223
|
+
dbs: Environment["d1_databases"]
|
|
224
|
+
): CfD1Database[] | undefined {
|
|
225
|
+
return dbs?.map((db) => ({
|
|
226
|
+
...db,
|
|
227
|
+
binding: `${D1_BETA_PREFIX}${db.binding}`,
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Remove beta prefix
|
|
232
|
+
export function removeD1BetaPrefix(binding: D1PrefixedBinding): string {
|
|
233
|
+
return binding.slice(D1_BETA_PREFIX.length);
|
|
234
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/shim.ts
|
|
2
|
+
import worker from "__ENTRY_POINT__";
|
|
3
|
+
export * from "__ENTRY_POINT__";
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
var D1Database = class {
|
|
7
|
+
constructor(binding) {
|
|
8
|
+
this.binding = binding;
|
|
9
|
+
}
|
|
10
|
+
prepare(query) {
|
|
11
|
+
return new D1PreparedStatement(this, query);
|
|
12
|
+
}
|
|
13
|
+
async dump() {
|
|
14
|
+
const response = await this.binding.fetch("/dump", {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: {
|
|
17
|
+
"content-type": "application/json",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (response.status !== 200) {
|
|
21
|
+
const err = await response.json();
|
|
22
|
+
throw new Error("D1_DUMP_ERROR", {
|
|
23
|
+
cause: new Error(err.error),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return await response.arrayBuffer();
|
|
27
|
+
}
|
|
28
|
+
async batch(statements) {
|
|
29
|
+
const exec = await this._send(
|
|
30
|
+
"/query",
|
|
31
|
+
statements.map((s) => s.statement),
|
|
32
|
+
statements.map((s) => s.params)
|
|
33
|
+
);
|
|
34
|
+
return exec;
|
|
35
|
+
}
|
|
36
|
+
async exec(query) {
|
|
37
|
+
const lines = query.trim().split("\n");
|
|
38
|
+
const _exec = await this._send("/query", lines, []);
|
|
39
|
+
const exec = Array.isArray(_exec) ? _exec : [_exec];
|
|
40
|
+
const error = exec
|
|
41
|
+
.map((r) => {
|
|
42
|
+
return r.error ? 1 : 0;
|
|
43
|
+
})
|
|
44
|
+
.indexOf(1);
|
|
45
|
+
if (error !== -1) {
|
|
46
|
+
throw new Error("D1_EXEC_ERROR", {
|
|
47
|
+
cause: new Error(
|
|
48
|
+
`Error in line ${error + 1}: ${lines[error]}: ${exec[error].error}`
|
|
49
|
+
),
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
return {
|
|
53
|
+
count: exec.length,
|
|
54
|
+
duration: exec.reduce((p, c) => {
|
|
55
|
+
return p + c.duration;
|
|
56
|
+
}, 0),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async _send(endpoint, query, params) {
|
|
61
|
+
const body = JSON.stringify(
|
|
62
|
+
typeof query == "object"
|
|
63
|
+
? query.map((s, index) => {
|
|
64
|
+
return { sql: s, params: params[index] };
|
|
65
|
+
})
|
|
66
|
+
: {
|
|
67
|
+
sql: query,
|
|
68
|
+
params,
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
const response = await this.binding.fetch(endpoint, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: {
|
|
74
|
+
"content-type": "application/json",
|
|
75
|
+
},
|
|
76
|
+
body,
|
|
77
|
+
});
|
|
78
|
+
if (response.status !== 200) {
|
|
79
|
+
const err = await response.json();
|
|
80
|
+
throw new Error("D1_ERROR", { cause: new Error(err.error) });
|
|
81
|
+
}
|
|
82
|
+
const answer = await response.json();
|
|
83
|
+
return Array.isArray(answer) ? answer : answer;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var D1PreparedStatement = class {
|
|
87
|
+
constructor(database, statement, values) {
|
|
88
|
+
this.database = database;
|
|
89
|
+
this.statement = statement;
|
|
90
|
+
this.params = values || [];
|
|
91
|
+
}
|
|
92
|
+
bind(...values) {
|
|
93
|
+
return new D1PreparedStatement(this.database, this.statement, values);
|
|
94
|
+
}
|
|
95
|
+
async first(colName) {
|
|
96
|
+
const info = firstIfArray(
|
|
97
|
+
await this.database._send("/query", this.statement, this.params)
|
|
98
|
+
);
|
|
99
|
+
const results = info.results;
|
|
100
|
+
if (results.length < 1) {
|
|
101
|
+
throw new Error("D1_NORESULTS", { cause: new Error("No results") });
|
|
102
|
+
}
|
|
103
|
+
const result = results[0];
|
|
104
|
+
if (colName !== void 0) {
|
|
105
|
+
if (result[colName] === void 0) {
|
|
106
|
+
throw new Error("D1_COLUMN_NOTFOUND", {
|
|
107
|
+
cause: new Error(`Column not found`),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return result[colName];
|
|
111
|
+
} else {
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async run() {
|
|
116
|
+
return firstIfArray(
|
|
117
|
+
await this.database._send("/execute", this.statement, this.params)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
async all() {
|
|
121
|
+
return firstIfArray(
|
|
122
|
+
await this.database._send("/query", this.statement, this.params)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
async raw() {
|
|
126
|
+
const s = firstIfArray(
|
|
127
|
+
await this.database._send("/query", this.statement, this.params)
|
|
128
|
+
);
|
|
129
|
+
const raw = [];
|
|
130
|
+
for (var r in s.results) {
|
|
131
|
+
const entry = Object.keys(s.results[r]).map((k) => {
|
|
132
|
+
return s.results[r][k];
|
|
133
|
+
});
|
|
134
|
+
raw.push(entry);
|
|
135
|
+
}
|
|
136
|
+
return raw;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function firstIfArray(results) {
|
|
140
|
+
return Array.isArray(results) ? results[0] : results;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/shim.ts
|
|
144
|
+
var D1_IMPORTS = __D1_IMPORTS__;
|
|
145
|
+
var LOCAL_MODE = __LOCAL_MODE__;
|
|
146
|
+
var D1_BETA_PREFIX = `__D1_BETA__`;
|
|
147
|
+
var envMap = /* @__PURE__ */ new Map();
|
|
148
|
+
function getMaskedEnv(env) {
|
|
149
|
+
if (envMap.has(env)) return envMap.get(env);
|
|
150
|
+
const newEnv = new Map(Object.entries(env));
|
|
151
|
+
D1_IMPORTS.filter((bindingName) =>
|
|
152
|
+
bindingName.startsWith(D1_BETA_PREFIX)
|
|
153
|
+
).forEach((bindingName) => {
|
|
154
|
+
newEnv.delete(bindingName);
|
|
155
|
+
const newName = bindingName.slice(D1_BETA_PREFIX.length);
|
|
156
|
+
const newBinding = !LOCAL_MODE
|
|
157
|
+
? new D1Database(env[bindingName])
|
|
158
|
+
: env[bindingName];
|
|
159
|
+
newEnv.set(newName, newBinding);
|
|
160
|
+
});
|
|
161
|
+
const newEnvObj = Object.fromEntries(newEnv.entries());
|
|
162
|
+
envMap.set(env, newEnvObj);
|
|
163
|
+
return newEnvObj;
|
|
164
|
+
}
|
|
165
|
+
var shim_default = {
|
|
166
|
+
async fetch(request, env, ctx) {
|
|
167
|
+
return worker.fetch(request, getMaskedEnv(env), ctx);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async scheduled(controller, env, ctx) {
|
|
171
|
+
return worker.scheduled(controller, getMaskedEnv(env), ctx);
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
export { shim_default as default };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// `workerd` currently throws on any use of the Cache API. Workers Sites
|
|
2
|
+
// requires the Cache API to function though, so stub it out to no-ops, like in
|
|
3
|
+
// regular `wrangler dev`.
|
|
4
|
+
|
|
5
|
+
class Cache {
|
|
6
|
+
async put(req, res) {}
|
|
7
|
+
|
|
8
|
+
async match(req, options) {}
|
|
9
|
+
|
|
10
|
+
async delete(req, options) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class CacheStorage {
|
|
16
|
+
#cache = new Cache();
|
|
17
|
+
|
|
18
|
+
get default() {
|
|
19
|
+
return this.#cache;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async open(cacheName) {
|
|
23
|
+
return this.#cache;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
globalThis.caches = new CacheStorage();
|