alchemy 0.57.2 → 0.59.0
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/bin/alchemy.js +1 -1
- package/lib/apply.js +3 -3
- package/lib/apply.js.map +1 -1
- package/lib/cloudflare/api-error.js +1 -1
- package/lib/cloudflare/api-error.js.map +1 -1
- package/lib/cloudflare/api-response.d.ts +19 -0
- package/lib/cloudflare/api-response.d.ts.map +1 -1
- package/lib/cloudflare/api-response.js.map +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +10 -2
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/assets.d.ts +1 -1
- package/lib/cloudflare/assets.d.ts.map +1 -1
- package/lib/cloudflare/bucket.d.ts +21 -3
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +83 -53
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/container.d.ts +8 -0
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +7 -1
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/custom-domain.d.ts +8 -0
- package/lib/cloudflare/custom-domain.d.ts.map +1 -1
- package/lib/cloudflare/custom-domain.js +11 -0
- package/lib/cloudflare/custom-domain.js.map +1 -1
- package/lib/cloudflare/d1-database.d.ts +15 -1
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +24 -17
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/d1-import.d.ts.map +1 -1
- package/lib/cloudflare/d1-import.js.map +1 -1
- package/lib/cloudflare/hyperdrive.d.ts +37 -9
- package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
- package/lib/cloudflare/hyperdrive.js +91 -5
- package/lib/cloudflare/hyperdrive.js.map +1 -1
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +23 -4
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +70 -58
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts +2 -0
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +9 -7
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/delete.d.ts +21 -0
- package/lib/cloudflare/miniflare/delete.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/delete.js +91 -0
- package/lib/cloudflare/miniflare/delete.js.map +1 -0
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.js +26 -9
- package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +2 -0
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +3 -4
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/pipeline.d.ts +3 -0
- package/lib/cloudflare/pipeline.d.ts.map +1 -1
- package/lib/cloudflare/pipeline.js +14 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/queue-consumer.d.ts +8 -0
- package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
- package/lib/cloudflare/queue-consumer.js +15 -4
- package/lib/cloudflare/queue-consumer.js.map +1 -1
- package/lib/cloudflare/queue.d.ts +22 -4
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +32 -9
- package/lib/cloudflare/queue.js.map +1 -1
- package/lib/cloudflare/route.d.ts +8 -0
- package/lib/cloudflare/route.d.ts.map +1 -1
- package/lib/cloudflare/route.js +13 -0
- package/lib/cloudflare/route.js.map +1 -1
- package/lib/cloudflare/tunnel.d.ts +448 -0
- package/lib/cloudflare/tunnel.d.ts.map +1 -0
- package/lib/cloudflare/tunnel.js +467 -0
- package/lib/cloudflare/tunnel.js.map +1 -0
- package/lib/cloudflare/worker-subdomain.d.ts +8 -0
- package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +9 -1
- package/lib/cloudflare/worker-subdomain.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +7 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +405 -375
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +1 -1
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +24 -12
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +4 -8
- package/lib/destroy.js.map +1 -1
- package/lib/esbuild/bundle.d.ts +1 -1
- package/lib/secret.d.ts +18 -1
- package/lib/secret.d.ts.map +1 -1
- package/lib/secret.js +25 -0
- package/lib/secret.js.map +1 -1
- package/package.json +1 -1
- package/src/apply.ts +5 -6
- package/src/cloudflare/api-error.ts +2 -2
- package/src/cloudflare/api-response.ts +22 -0
- package/src/cloudflare/api.ts +13 -2
- package/src/cloudflare/bucket.ts +126 -68
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/container.ts +18 -1
- package/src/cloudflare/custom-domain.ts +22 -0
- package/src/cloudflare/d1-database.ts +44 -19
- package/src/cloudflare/d1-import.ts +1 -14
- package/src/cloudflare/hyperdrive.ts +170 -14
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/kv-namespace.ts +117 -73
- package/src/cloudflare/miniflare/build-worker-options.ts +12 -8
- package/src/cloudflare/miniflare/delete.ts +136 -0
- package/src/cloudflare/miniflare/miniflare-controller.ts +26 -9
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +5 -4
- package/src/cloudflare/pipeline.ts +17 -1
- package/src/cloudflare/queue-consumer.ts +27 -5
- package/src/cloudflare/queue.ts +62 -12
- package/src/cloudflare/route.ts +24 -0
- package/src/cloudflare/tunnel.ts +911 -0
- package/src/cloudflare/worker-subdomain.ts +29 -6
- package/src/cloudflare/worker.ts +484 -465
- package/src/cloudflare/wrangler.json.ts +34 -13
- package/src/destroy.ts +4 -8
- package/src/secret.ts +30 -1
- package/templates/astro/README.md +2 -0
- package/templates/nuxt/README.md +2 -0
- package/templates/react-router/README.md +2 -0
- package/templates/rwsdk/README.md +2 -0
- package/templates/sveltekit/README.md +2 -0
- package/templates/tanstack-start/README.md +2 -0
- package/templates/typescript/README.md +2 -0
- package/templates/vite/README.md +2 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { alchemy } from "../alchemy.ts";
|
|
1
2
|
import type { Context } from "../context.ts";
|
|
2
3
|
import { Resource } from "../resource.ts";
|
|
3
|
-
import
|
|
4
|
+
import { Scope } from "../scope.ts";
|
|
5
|
+
import { Secret } from "../secret.ts";
|
|
4
6
|
import { logger } from "../util/logger.ts";
|
|
5
7
|
import { handleApiError } from "./api-error.ts";
|
|
6
8
|
import { createCloudflareApi, type CloudflareApiOptions } from "./api.ts";
|
|
@@ -8,7 +10,7 @@ import { createCloudflareApi, type CloudflareApiOptions } from "./api.ts";
|
|
|
8
10
|
/**
|
|
9
11
|
* Origin configuration for a PostgreSQL or MySQL database connection
|
|
10
12
|
*/
|
|
11
|
-
export interface
|
|
13
|
+
export interface HyperdrivePublicOrigin {
|
|
12
14
|
/**
|
|
13
15
|
* Database name
|
|
14
16
|
*/
|
|
@@ -23,7 +25,7 @@ export interface HyperdriveOrigin {
|
|
|
23
25
|
* Database password
|
|
24
26
|
* Use alchemy.secret() to securely store this value
|
|
25
27
|
*/
|
|
26
|
-
password: Secret;
|
|
28
|
+
password: string | Secret;
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Database port
|
|
@@ -56,7 +58,7 @@ export interface HyperdriveOriginWithAccess {
|
|
|
56
58
|
* Access client secret
|
|
57
59
|
* Use alchemy.secret() to securely store this value
|
|
58
60
|
*/
|
|
59
|
-
access_client_secret: Secret;
|
|
61
|
+
access_client_secret: string | Secret;
|
|
60
62
|
|
|
61
63
|
/**
|
|
62
64
|
* Database host
|
|
@@ -118,6 +120,16 @@ export interface HyperdriveMtls {
|
|
|
118
120
|
sslmode?: "verify-ca" | "verify-full";
|
|
119
121
|
}
|
|
120
122
|
|
|
123
|
+
export type HyperdriveOriginInput =
|
|
124
|
+
| string
|
|
125
|
+
| Secret
|
|
126
|
+
| HyperdrivePublicOrigin
|
|
127
|
+
| HyperdriveOriginWithAccess;
|
|
128
|
+
|
|
129
|
+
export type HyperdriveOrigin =
|
|
130
|
+
| HyperdrivePublicOrigin
|
|
131
|
+
| HyperdriveOriginWithAccess;
|
|
132
|
+
|
|
121
133
|
/**
|
|
122
134
|
* Properties for creating or updating a Cloudflare Hyperdrive.
|
|
123
135
|
*/
|
|
@@ -130,7 +142,7 @@ export interface HyperdriveProps extends CloudflareApiOptions {
|
|
|
130
142
|
/**
|
|
131
143
|
* Database connection origin configuration
|
|
132
144
|
*/
|
|
133
|
-
origin:
|
|
145
|
+
origin: HyperdriveOriginInput;
|
|
134
146
|
|
|
135
147
|
/**
|
|
136
148
|
* Caching configuration
|
|
@@ -148,6 +160,14 @@ export interface HyperdriveProps extends CloudflareApiOptions {
|
|
|
148
160
|
* @internal
|
|
149
161
|
*/
|
|
150
162
|
hyperdriveId?: string;
|
|
163
|
+
|
|
164
|
+
dev?: {
|
|
165
|
+
/**
|
|
166
|
+
* The database connection origin configuration for local development
|
|
167
|
+
* @default origin
|
|
168
|
+
*/
|
|
169
|
+
origin?: HyperdriveOriginInput;
|
|
170
|
+
};
|
|
151
171
|
}
|
|
152
172
|
|
|
153
173
|
/**
|
|
@@ -156,7 +176,7 @@ export interface HyperdriveProps extends CloudflareApiOptions {
|
|
|
156
176
|
*/
|
|
157
177
|
export interface Hyperdrive
|
|
158
178
|
extends Resource<"cloudflare::Hyperdrive">,
|
|
159
|
-
Omit<HyperdriveProps, "origin"> {
|
|
179
|
+
Omit<HyperdriveProps, "origin" | "dev"> {
|
|
160
180
|
/**
|
|
161
181
|
* The ID of the resource
|
|
162
182
|
*/
|
|
@@ -170,7 +190,18 @@ export interface Hyperdrive
|
|
|
170
190
|
/**
|
|
171
191
|
* Database connection origin configuration
|
|
172
192
|
*/
|
|
173
|
-
origin:
|
|
193
|
+
origin: HyperdrivePublicOrigin | HyperdriveOriginWithAccess;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Local development configuration
|
|
197
|
+
* @internal
|
|
198
|
+
*/
|
|
199
|
+
dev: {
|
|
200
|
+
/**
|
|
201
|
+
* The connection string to use for local development
|
|
202
|
+
*/
|
|
203
|
+
origin: Secret;
|
|
204
|
+
};
|
|
174
205
|
|
|
175
206
|
/**
|
|
176
207
|
* Resource type identifier for binding.
|
|
@@ -257,19 +288,66 @@ export interface Hyperdrive
|
|
|
257
288
|
* }
|
|
258
289
|
* });
|
|
259
290
|
*/
|
|
260
|
-
export
|
|
291
|
+
export async function Hyperdrive(
|
|
292
|
+
id: string,
|
|
293
|
+
props: HyperdriveProps,
|
|
294
|
+
): Promise<Hyperdrive> {
|
|
295
|
+
const origin = normalizeHyperdriveOrigin(props.origin);
|
|
296
|
+
const dev = {
|
|
297
|
+
origin: toConnectionString(
|
|
298
|
+
normalizeHyperdriveOrigin(props.dev?.origin ?? origin),
|
|
299
|
+
),
|
|
300
|
+
force: Scope.current.local,
|
|
301
|
+
};
|
|
302
|
+
return await _Hyperdrive(id, {
|
|
303
|
+
...props,
|
|
304
|
+
origin,
|
|
305
|
+
dev,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Internal properties for creating or updating a Cloudflare Hyperdrive config.
|
|
311
|
+
* @internal
|
|
312
|
+
*/
|
|
313
|
+
interface InternalHyperdriveProps extends CloudflareApiOptions {
|
|
314
|
+
name: string;
|
|
315
|
+
hyperdriveId?: string;
|
|
316
|
+
origin: HyperdriveOrigin;
|
|
317
|
+
caching?: HyperdriveCaching;
|
|
318
|
+
mtls?: HyperdriveMtls;
|
|
319
|
+
dev: {
|
|
320
|
+
origin: Secret;
|
|
321
|
+
force?: boolean;
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const _Hyperdrive = Resource(
|
|
261
326
|
"cloudflare::Hyperdrive",
|
|
262
327
|
async function (
|
|
263
328
|
this: Context<Hyperdrive>,
|
|
264
329
|
id: string,
|
|
265
|
-
props:
|
|
330
|
+
props: InternalHyperdriveProps,
|
|
266
331
|
): Promise<Hyperdrive> {
|
|
332
|
+
const hyperdriveId = props.hyperdriveId || this.output?.hyperdriveId;
|
|
333
|
+
|
|
334
|
+
if (this.scope.local) {
|
|
335
|
+
return this({
|
|
336
|
+
id,
|
|
337
|
+
hyperdriveId: hyperdriveId || "",
|
|
338
|
+
name: props.name,
|
|
339
|
+
origin: props.origin,
|
|
340
|
+
caching: props.caching,
|
|
341
|
+
mtls: props.mtls,
|
|
342
|
+
dev: props.dev,
|
|
343
|
+
type: "hyperdrive",
|
|
344
|
+
});
|
|
345
|
+
}
|
|
267
346
|
const api = await createCloudflareApi(props);
|
|
268
347
|
const configsPath = `/accounts/${api.accountId}/hyperdrive/configs`;
|
|
269
348
|
|
|
270
349
|
// For create operations, we don't have a hyperdriveId yet
|
|
271
350
|
// For update/delete operations, we need to use the hyperdriveId from props or output
|
|
272
|
-
const hyperdriveId = props.hyperdriveId || this.output?.hyperdriveId;
|
|
273
351
|
const configPath = hyperdriveId
|
|
274
352
|
? `${configsPath}/${hyperdriveId}`
|
|
275
353
|
: `${configsPath}`;
|
|
@@ -353,9 +431,10 @@ export const Hyperdrive = Resource(
|
|
|
353
431
|
id,
|
|
354
432
|
hyperdriveId: apiResource.id, // Store the Cloudflare-assigned UUID
|
|
355
433
|
name: apiResource.name,
|
|
356
|
-
origin: props.origin,
|
|
434
|
+
origin: props.origin,
|
|
357
435
|
caching: apiResource.caching,
|
|
358
436
|
mtls: apiResource.mtls,
|
|
437
|
+
dev: props.dev,
|
|
359
438
|
type: "hyperdrive",
|
|
360
439
|
});
|
|
361
440
|
},
|
|
@@ -364,7 +443,7 @@ export const Hyperdrive = Resource(
|
|
|
364
443
|
/**
|
|
365
444
|
* Prepare the request body by unwrapping secret values
|
|
366
445
|
*/
|
|
367
|
-
function prepareRequestBody(props:
|
|
446
|
+
function prepareRequestBody(props: InternalHyperdriveProps): any {
|
|
368
447
|
const requestBody: any = { ...props };
|
|
369
448
|
|
|
370
449
|
// Remove internal props
|
|
@@ -375,16 +454,93 @@ function prepareRequestBody(props: HyperdriveProps): any {
|
|
|
375
454
|
// Regular origin with password
|
|
376
455
|
requestBody.origin = {
|
|
377
456
|
...props.origin,
|
|
378
|
-
password: props.origin.password
|
|
457
|
+
password: Secret.unwrap(props.origin.password),
|
|
379
458
|
scheme: props.origin.scheme ?? "postgres",
|
|
380
459
|
};
|
|
381
460
|
} else if ("access_client_secret" in props.origin) {
|
|
382
461
|
// Origin with access client secret
|
|
383
462
|
requestBody.origin = {
|
|
384
463
|
...props.origin,
|
|
385
|
-
access_client_secret: props.origin.access_client_secret
|
|
464
|
+
access_client_secret: Secret.unwrap(props.origin.access_client_secret),
|
|
386
465
|
};
|
|
387
466
|
}
|
|
388
467
|
|
|
389
468
|
return requestBody;
|
|
390
469
|
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Converts a HyperdriveOriginInput to a HyperdriveOrigin.
|
|
473
|
+
* This includes:
|
|
474
|
+
* - parsing the origin from a string
|
|
475
|
+
* - ensuring the scheme is "postgres" or "mysql"
|
|
476
|
+
* - normalizing the port to a number with default values
|
|
477
|
+
* - wrapping secrets in a Secret object
|
|
478
|
+
* @internal - Exported for testing
|
|
479
|
+
*/
|
|
480
|
+
export const normalizeHyperdriveOrigin = (
|
|
481
|
+
input: HyperdriveOriginInput,
|
|
482
|
+
): Required<HyperdrivePublicOrigin> | Required<HyperdriveOriginWithAccess> => {
|
|
483
|
+
const origin = Secret.unwrap(input);
|
|
484
|
+
if (typeof origin === "string") {
|
|
485
|
+
const url = new URL(origin);
|
|
486
|
+
const scheme = normalizeScheme(url.protocol.slice(0, -1));
|
|
487
|
+
return {
|
|
488
|
+
scheme,
|
|
489
|
+
user: url.username,
|
|
490
|
+
password: alchemy.secret(url.password),
|
|
491
|
+
host: url.hostname,
|
|
492
|
+
port: normalizePort(scheme, url.port),
|
|
493
|
+
database: url.pathname.slice(1),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const scheme = normalizeScheme(origin.scheme);
|
|
497
|
+
return {
|
|
498
|
+
...origin,
|
|
499
|
+
...("password" in origin && {
|
|
500
|
+
password: Secret.wrap(origin.password),
|
|
501
|
+
}),
|
|
502
|
+
...("access_client_secret" in origin && {
|
|
503
|
+
access_client_secret: Secret.wrap(origin.access_client_secret),
|
|
504
|
+
}),
|
|
505
|
+
scheme,
|
|
506
|
+
port: normalizePort(scheme, origin.port),
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const normalizeScheme = (scheme: string | undefined) => {
|
|
511
|
+
if (!scheme || scheme === "postgres" || scheme === "postgresql") {
|
|
512
|
+
return "postgres";
|
|
513
|
+
}
|
|
514
|
+
if (scheme === "mysql" || scheme === "mysql2") {
|
|
515
|
+
return "mysql";
|
|
516
|
+
}
|
|
517
|
+
throw new Error(
|
|
518
|
+
`Unsupported database connection scheme "${scheme}" for Hyperdrive (expected "postgres" or "mysql")`,
|
|
519
|
+
);
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const normalizePort = (
|
|
523
|
+
scheme: "postgres" | "mysql",
|
|
524
|
+
port: string | number | undefined,
|
|
525
|
+
) => {
|
|
526
|
+
if (typeof port === "number") {
|
|
527
|
+
return port;
|
|
528
|
+
}
|
|
529
|
+
if (port) {
|
|
530
|
+
return Number.parseInt(port);
|
|
531
|
+
}
|
|
532
|
+
return scheme === "postgres" ? 5432 : 3306;
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const toConnectionString = (
|
|
536
|
+
origin:
|
|
537
|
+
| Required<HyperdrivePublicOrigin>
|
|
538
|
+
| Required<HyperdriveOriginWithAccess>,
|
|
539
|
+
) => {
|
|
540
|
+
const password = Secret.unwrap(
|
|
541
|
+
"password" in origin ? origin.password : origin.access_client_secret,
|
|
542
|
+
);
|
|
543
|
+
return new Secret(
|
|
544
|
+
`${origin.scheme}://${origin.user}:${password}@${origin.host}:${origin.port}/${origin.database}`,
|
|
545
|
+
);
|
|
546
|
+
};
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -50,6 +50,7 @@ export * from "./secrets-store.ts";
|
|
|
50
50
|
export * from "./state.ts";
|
|
51
51
|
export * from "./sveltekit/sveltekit.ts";
|
|
52
52
|
export * from "./tanstack-start/tanstack-start.ts";
|
|
53
|
+
export * from "./tunnel.ts";
|
|
53
54
|
export * from "./vectorize-index.ts";
|
|
54
55
|
export * from "./vectorize-metadata-index.ts";
|
|
55
56
|
export * from "./version-metadata.ts";
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import { Resource, ResourceKind } from "../resource.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { Scope } from "../scope.ts";
|
|
4
4
|
import { withExponentialBackoff } from "../util/retry.ts";
|
|
5
|
-
import { handleApiError } from "./api-error.ts";
|
|
5
|
+
import { CloudflareApiError, handleApiError } from "./api-error.ts";
|
|
6
|
+
import {
|
|
7
|
+
extractCloudflareResult,
|
|
8
|
+
type CloudflareApiErrorPayload,
|
|
9
|
+
} from "./api-response.ts";
|
|
6
10
|
import {
|
|
7
11
|
createCloudflareApi,
|
|
8
12
|
type CloudflareApi,
|
|
9
13
|
type CloudflareApiOptions,
|
|
10
14
|
} from "./api.ts";
|
|
15
|
+
import { deleteMiniflareBinding } from "./miniflare/delete.ts";
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* Properties for creating or updating a KV Namespace
|
|
@@ -49,6 +54,12 @@ export interface KVNamespaceProps extends CloudflareApiOptions {
|
|
|
49
54
|
* @default false
|
|
50
55
|
*/
|
|
51
56
|
remote?: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether to force the KV namespace to be created or updated
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
force?: boolean;
|
|
52
63
|
};
|
|
53
64
|
}
|
|
54
65
|
|
|
@@ -91,7 +102,7 @@ export function isKVNamespace(resource: Resource): resource is KVNamespace {
|
|
|
91
102
|
*/
|
|
92
103
|
export interface KVNamespace
|
|
93
104
|
extends Resource<"cloudflare::KVNamespace">,
|
|
94
|
-
Omit<KVNamespaceProps, "delete"> {
|
|
105
|
+
Omit<KVNamespaceProps, "delete" | "dev"> {
|
|
95
106
|
type: "kv_namespace";
|
|
96
107
|
/**
|
|
97
108
|
* The ID of the namespace
|
|
@@ -107,6 +118,22 @@ export interface KVNamespace
|
|
|
107
118
|
* Time at which the namespace was last modified
|
|
108
119
|
*/
|
|
109
120
|
modifiedAt: number;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Development mode properties
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
dev: {
|
|
127
|
+
/**
|
|
128
|
+
* The ID of the KV namespace in development mode
|
|
129
|
+
*/
|
|
130
|
+
id: string;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether the KV namespace is running remotely
|
|
134
|
+
*/
|
|
135
|
+
remote: boolean;
|
|
136
|
+
};
|
|
110
137
|
}
|
|
111
138
|
|
|
112
139
|
/**
|
|
@@ -163,84 +190,76 @@ export interface KVNamespace
|
|
|
163
190
|
* delete: false
|
|
164
191
|
* });
|
|
165
192
|
*/
|
|
166
|
-
export
|
|
193
|
+
export async function KVNamespace(
|
|
194
|
+
id: string,
|
|
195
|
+
props: KVNamespaceProps = {},
|
|
196
|
+
): Promise<KVNamespace> {
|
|
197
|
+
return await _KVNamespace(id, {
|
|
198
|
+
...props,
|
|
199
|
+
dev: {
|
|
200
|
+
...(props.dev ?? {}),
|
|
201
|
+
force: Scope.current.local,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const _KVNamespace = Resource(
|
|
167
207
|
"cloudflare::KVNamespace",
|
|
168
208
|
async function (
|
|
169
209
|
this: Context<KVNamespace>,
|
|
170
210
|
id: string,
|
|
171
211
|
props: KVNamespaceProps,
|
|
172
212
|
): Promise<KVNamespace> {
|
|
173
|
-
// Create Cloudflare API client with automatic account discovery
|
|
174
|
-
const api = await createCloudflareApi(props);
|
|
175
|
-
|
|
176
213
|
const title = props.title ?? id;
|
|
214
|
+
const local = this.scope.local && !props.dev?.remote;
|
|
215
|
+
const dev = {
|
|
216
|
+
id: this.output?.dev?.id ?? this.output?.namespaceId ?? id,
|
|
217
|
+
remote: props.dev?.remote ?? false,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
if (local) {
|
|
221
|
+
return this({
|
|
222
|
+
type: "kv_namespace",
|
|
223
|
+
namespaceId: this.output?.namespaceId ?? "",
|
|
224
|
+
title,
|
|
225
|
+
values: props.values,
|
|
226
|
+
dev,
|
|
227
|
+
createdAt: this.output?.createdAt ?? Date.now(),
|
|
228
|
+
modifiedAt: Date.now(),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const api = await createCloudflareApi(props);
|
|
177
233
|
|
|
178
234
|
if (this.phase === "delete") {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
235
|
+
if (this.output.dev?.id) {
|
|
236
|
+
await deleteMiniflareBinding("kv", this.output.dev.id);
|
|
237
|
+
}
|
|
238
|
+
if (this.output.namespaceId && props.delete !== false) {
|
|
239
|
+
await deleteKVNamespace(api, this.output.namespaceId);
|
|
183
240
|
}
|
|
184
|
-
|
|
185
|
-
// Return minimal output for deleted state
|
|
186
241
|
return this.destroy();
|
|
187
242
|
}
|
|
188
|
-
// For create or update operations
|
|
189
|
-
// If this.phase is "update", we expect this.output to exist
|
|
190
|
-
let namespaceId =
|
|
191
|
-
this.phase === "update" ? this.output?.namespaceId || "" : "";
|
|
192
|
-
let createdAt =
|
|
193
|
-
this.phase === "update"
|
|
194
|
-
? this.output?.createdAt || Date.now()
|
|
195
|
-
: Date.now();
|
|
196
|
-
|
|
197
|
-
if (this.phase === "update" && namespaceId) {
|
|
198
|
-
// Can't update a KV namespace title directly, just work with existing ID
|
|
199
|
-
} else {
|
|
200
|
-
try {
|
|
201
|
-
// Try to create the KV namespace
|
|
202
|
-
const { id } = await createKVNamespace(api, {
|
|
203
|
-
...props,
|
|
204
|
-
title,
|
|
205
|
-
});
|
|
206
|
-
createdAt = Date.now();
|
|
207
|
-
namespaceId = id;
|
|
208
|
-
} catch (error) {
|
|
209
|
-
// Check if this is a "namespace already exists" error and adopt is enabled
|
|
210
|
-
if (
|
|
211
|
-
props.adopt &&
|
|
212
|
-
error instanceof Error &&
|
|
213
|
-
error.message.includes("already exists")
|
|
214
|
-
) {
|
|
215
|
-
logger.log(`Namespace '${title}' already exists, adopting it`);
|
|
216
|
-
// Find the existing namespace by title
|
|
217
|
-
const existingNamespace = await findKVNamespaceByTitle(api, title);
|
|
218
|
-
|
|
219
|
-
if (!existingNamespace) {
|
|
220
|
-
throw new Error(
|
|
221
|
-
`Failed to find existing namespace '${title}' for adoption`,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
243
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
let result: { namespaceId: string; createdAt: number };
|
|
245
|
+
if (this.phase === "create" || !this.output.namespaceId) {
|
|
246
|
+
result = await createKVNamespace(api, {
|
|
247
|
+
...props,
|
|
248
|
+
title,
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
result = this.output;
|
|
233
252
|
}
|
|
234
253
|
|
|
235
|
-
await insertKVRecords(api, namespaceId, props);
|
|
254
|
+
await insertKVRecords(api, result.namespaceId, props);
|
|
236
255
|
|
|
237
256
|
return this({
|
|
238
257
|
type: "kv_namespace",
|
|
239
|
-
namespaceId,
|
|
240
|
-
title
|
|
258
|
+
namespaceId: result.namespaceId,
|
|
259
|
+
title,
|
|
241
260
|
values: props.values,
|
|
242
|
-
dev
|
|
243
|
-
createdAt: createdAt,
|
|
261
|
+
dev,
|
|
262
|
+
createdAt: result.createdAt,
|
|
244
263
|
modifiedAt: Date.now(),
|
|
245
264
|
});
|
|
246
265
|
},
|
|
@@ -251,19 +270,44 @@ export async function createKVNamespace(
|
|
|
251
270
|
props: KVNamespaceProps & {
|
|
252
271
|
title: string;
|
|
253
272
|
},
|
|
254
|
-
): Promise<{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
title:
|
|
259
|
-
|
|
260
|
-
|
|
273
|
+
): Promise<{ namespaceId: string; createdAt: number }> {
|
|
274
|
+
try {
|
|
275
|
+
const { id } = await extractCloudflareResult<{
|
|
276
|
+
id: string;
|
|
277
|
+
title: string;
|
|
278
|
+
beta?: boolean;
|
|
279
|
+
supports_url_encoding?: boolean;
|
|
280
|
+
}>(
|
|
281
|
+
`create kv namespace "${props.title}"`,
|
|
282
|
+
api.post(`/accounts/${api.accountId}/storage/kv/namespaces`, {
|
|
283
|
+
title: props.title,
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
return { namespaceId: id, createdAt: Date.now() };
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (
|
|
289
|
+
error instanceof CloudflareApiError &&
|
|
290
|
+
(error.errorData as CloudflareApiErrorPayload[]).some(
|
|
291
|
+
(e) => e.code === 10014,
|
|
292
|
+
) &&
|
|
293
|
+
props.adopt
|
|
294
|
+
) {
|
|
295
|
+
const existingNamespace = await findKVNamespaceByTitle(api, props.title);
|
|
296
|
+
|
|
297
|
+
if (!existingNamespace) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`Failed to find existing namespace '${props.title}' for adoption`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
261
302
|
|
|
262
|
-
|
|
263
|
-
|
|
303
|
+
return {
|
|
304
|
+
namespaceId: existingNamespace.id,
|
|
305
|
+
createdAt: existingNamespace.createdAt ?? Date.now(),
|
|
306
|
+
};
|
|
307
|
+
} else {
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
264
310
|
}
|
|
265
|
-
|
|
266
|
-
return { id: ((await createResponse.json()) as any).result.id };
|
|
267
311
|
}
|
|
268
312
|
|
|
269
313
|
export async function deleteKVNamespace(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as miniflare from "miniflare";
|
|
2
2
|
import { assertNever } from "../../util/assert-never.ts";
|
|
3
3
|
import type { HTTPServer } from "../../util/http.ts";
|
|
4
|
+
import type { CloudflareApi } from "../api.ts";
|
|
4
5
|
import {
|
|
5
6
|
Self,
|
|
6
7
|
type Binding,
|
|
@@ -14,6 +15,7 @@ import type { AssetsConfig } from "../worker.ts";
|
|
|
14
15
|
import { createRemoteProxyWorker } from "./remote-binding-proxy.ts";
|
|
15
16
|
|
|
16
17
|
export interface MiniflareWorkerInput {
|
|
18
|
+
api: CloudflareApi;
|
|
17
19
|
id: string;
|
|
18
20
|
name: string;
|
|
19
21
|
compatibilityDate: string | undefined;
|
|
@@ -143,7 +145,7 @@ export const buildWorkerOptions = async (
|
|
|
143
145
|
raw: true,
|
|
144
146
|
});
|
|
145
147
|
} else {
|
|
146
|
-
(options.d1Databases ??= {})[key] = binding.id;
|
|
148
|
+
(options.d1Databases ??= {})[key] = binding.dev.id;
|
|
147
149
|
}
|
|
148
150
|
break;
|
|
149
151
|
}
|
|
@@ -165,7 +167,8 @@ export const buildWorkerOptions = async (
|
|
|
165
167
|
break;
|
|
166
168
|
}
|
|
167
169
|
case "hyperdrive": {
|
|
168
|
-
|
|
170
|
+
(options.hyperdrives ??= {})[key] = binding.dev.origin.unencrypted;
|
|
171
|
+
break;
|
|
169
172
|
}
|
|
170
173
|
case "images": {
|
|
171
174
|
if (isRemoteBinding(binding)) {
|
|
@@ -182,17 +185,17 @@ export const buildWorkerOptions = async (
|
|
|
182
185
|
break;
|
|
183
186
|
}
|
|
184
187
|
case "kv_namespace": {
|
|
185
|
-
const namespaceId =
|
|
186
|
-
"namespaceId" in binding ? binding.namespaceId : binding.id;
|
|
187
188
|
if (isRemoteBinding(binding)) {
|
|
188
189
|
remoteBindings.push({
|
|
189
190
|
type: "kv_namespace",
|
|
190
191
|
name: key,
|
|
191
|
-
namespace_id:
|
|
192
|
+
namespace_id:
|
|
193
|
+
"namespaceId" in binding ? binding.namespaceId : binding.id,
|
|
192
194
|
raw: true,
|
|
193
195
|
});
|
|
194
196
|
} else {
|
|
195
|
-
(options.kvNamespaces ??= {})[key] =
|
|
197
|
+
(options.kvNamespaces ??= {})[key] =
|
|
198
|
+
"dev" in binding ? binding.dev.id : binding.id;
|
|
196
199
|
}
|
|
197
200
|
break;
|
|
198
201
|
}
|
|
@@ -217,7 +220,7 @@ export const buildWorkerOptions = async (
|
|
|
217
220
|
break;
|
|
218
221
|
}
|
|
219
222
|
case "pipeline": {
|
|
220
|
-
(options.pipelines ??= {})[key] = binding.
|
|
223
|
+
(options.pipelines ??= {})[key] = binding.name;
|
|
221
224
|
break;
|
|
222
225
|
}
|
|
223
226
|
case "ratelimit": {
|
|
@@ -249,7 +252,7 @@ export const buildWorkerOptions = async (
|
|
|
249
252
|
raw: true,
|
|
250
253
|
});
|
|
251
254
|
} else {
|
|
252
|
-
(options.r2Buckets ??= {})[key] = binding.
|
|
255
|
+
(options.r2Buckets ??= {})[key] = binding.dev.id;
|
|
253
256
|
}
|
|
254
257
|
break;
|
|
255
258
|
}
|
|
@@ -329,6 +332,7 @@ export const buildWorkerOptions = async (
|
|
|
329
332
|
}
|
|
330
333
|
if (remoteBindings.length > 0) {
|
|
331
334
|
const remoteProxy = await createRemoteProxyWorker({
|
|
335
|
+
api: input.api,
|
|
332
336
|
name: input.name,
|
|
333
337
|
bindings: remoteBindings,
|
|
334
338
|
});
|