alchemy 0.86.0 → 0.89.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 +483 -541
- package/lib/cloudflare/compatibility-date.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.d.ts.map +1 -1
- package/lib/cloudflare/dns-records.d.ts +5 -1
- package/lib/cloudflare/dns-records.d.ts.map +1 -1
- package/lib/cloudflare/dns-records.js +54 -31
- package/lib/cloudflare/dns-records.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/r2-bucket-notification.d.ts +312 -0
- package/lib/cloudflare/r2-bucket-notification.d.ts.map +1 -0
- package/lib/cloudflare/r2-bucket-notification.js +387 -0
- package/lib/cloudflare/r2-bucket-notification.js.map +1 -0
- package/lib/cloudflare/workflow.d.ts +17 -0
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js +1 -0
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/cloudflare/wrangler.json.js +1 -0
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/docker/api.d.ts +51 -0
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +30 -0
- package/lib/docker/api.js.map +1 -1
- package/lib/docker/container.d.ts +7 -1
- package/lib/docker/container.d.ts.map +1 -1
- package/lib/docker/container.js +51 -3
- package/lib/docker/container.js.map +1 -1
- package/lib/docker/network.d.ts.map +1 -1
- package/lib/docker/network.js +24 -7
- package/lib/docker/network.js.map +1 -1
- package/lib/planetscale/api.d.ts +1 -0
- package/lib/planetscale/api.d.ts.map +1 -1
- package/lib/planetscale/api.js +1 -1
- package/lib/planetscale/api.js.map +1 -1
- package/lib/planetscale/branch.d.ts +28 -0
- package/lib/planetscale/branch.d.ts.map +1 -1
- package/lib/planetscale/branch.js +12 -0
- package/lib/planetscale/branch.js.map +1 -1
- package/lib/planetscale/database-extensions.d.ts +515 -0
- package/lib/planetscale/database-extensions.d.ts.map +1 -0
- package/lib/planetscale/database-extensions.js +389 -0
- package/lib/planetscale/database-extensions.js.map +1 -0
- package/lib/planetscale/database.d.ts +40 -2
- package/lib/planetscale/database.d.ts.map +1 -1
- package/lib/planetscale/database.js +76 -1
- package/lib/planetscale/database.js.map +1 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +1 -2
- package/lib/scope.js.map +1 -1
- package/lib/util/telemetry.d.ts +1 -6
- package/lib/util/telemetry.d.ts.map +1 -1
- package/lib/util/telemetry.js.map +1 -1
- package/package.json +4 -4
- package/src/cloudflare/dns-records.ts +71 -51
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/r2-bucket-notification.ts +809 -0
- package/src/cloudflare/workflow.ts +18 -0
- package/src/cloudflare/wrangler.json.ts +1 -0
- package/src/docker/api.ts +73 -0
- package/src/docker/container.ts +78 -4
- package/src/docker/network.ts +27 -7
- package/src/planetscale/api.ts +1 -1
- package/src/planetscale/branch.ts +45 -0
- package/src/planetscale/database-extensions.ts +947 -0
- package/src/planetscale/database.ts +141 -2
- package/src/scope.ts +1 -2
- package/src/util/telemetry.ts +1 -20
- package/workers/tunnel-proxy.js +1 -1
- package/lib/state/instrumented-state-store.d.ts +0 -19
- package/lib/state/instrumented-state-store.d.ts.map +0 -1
- package/lib/state/instrumented-state-store.js +0 -68
- package/lib/state/instrumented-state-store.js.map +0 -1
- package/src/state/instrumented-state-store.ts +0 -108
|
@@ -25,6 +25,20 @@ export interface WorkflowProps {
|
|
|
25
25
|
* @default - bound worker script
|
|
26
26
|
*/
|
|
27
27
|
scriptName?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Limits for the workflow instance
|
|
30
|
+
*/
|
|
31
|
+
limits?: {
|
|
32
|
+
/**
|
|
33
|
+
* Maximum number of steps per workflow instance.
|
|
34
|
+
*
|
|
35
|
+
* Workers Free: 1,024 (not configurable)
|
|
36
|
+
* Workers Paid: 10,000 default, configurable up to 25,000
|
|
37
|
+
*
|
|
38
|
+
* @see https://developers.cloudflare.com/workflows/reference/limits/
|
|
39
|
+
*/
|
|
40
|
+
steps?: number;
|
|
41
|
+
};
|
|
28
42
|
dev?: {
|
|
29
43
|
/**
|
|
30
44
|
* Whether to run the workflow remotely instead of locally
|
|
@@ -45,6 +59,9 @@ export type Workflow<PARAMS = unknown> = {
|
|
|
45
59
|
workflowName: string;
|
|
46
60
|
className: string;
|
|
47
61
|
scriptName?: string;
|
|
62
|
+
limits?: {
|
|
63
|
+
steps?: number;
|
|
64
|
+
};
|
|
48
65
|
};
|
|
49
66
|
|
|
50
67
|
export function isWorkflow(binding: Binding): binding is Workflow {
|
|
@@ -77,6 +94,7 @@ export function Workflow<PARAMS = unknown>(
|
|
|
77
94
|
workflowName,
|
|
78
95
|
className,
|
|
79
96
|
scriptName: props.scriptName,
|
|
97
|
+
limits: props.limits,
|
|
80
98
|
};
|
|
81
99
|
}
|
|
82
100
|
|
package/src/docker/api.ts
CHANGED
|
@@ -106,9 +106,48 @@ export type ContainerInfo = {
|
|
|
106
106
|
Aliases: string[] | null;
|
|
107
107
|
}
|
|
108
108
|
> | null;
|
|
109
|
+
Ports?: Record<
|
|
110
|
+
string,
|
|
111
|
+
Array<{ HostIp: string; HostPort: string }> | null
|
|
112
|
+
> | null;
|
|
109
113
|
};
|
|
110
114
|
};
|
|
111
115
|
|
|
116
|
+
export type ContainerRuntimeInfo = {
|
|
117
|
+
/**
|
|
118
|
+
* Map of internal container ports to their bound host ports.
|
|
119
|
+
* Format: "internalPort/protocol" -> hostPort (number)
|
|
120
|
+
*/
|
|
121
|
+
ports: Record<string, number>;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type NetworkInfo = {
|
|
125
|
+
Name: string;
|
|
126
|
+
Id: string;
|
|
127
|
+
Created: string;
|
|
128
|
+
Scope: string;
|
|
129
|
+
Driver: string;
|
|
130
|
+
EnableIPv6: boolean;
|
|
131
|
+
IPAM: {
|
|
132
|
+
Driver: string;
|
|
133
|
+
Options: Record<string, string> | null;
|
|
134
|
+
Config: Array<{
|
|
135
|
+
Subnet: string;
|
|
136
|
+
Gateway: string;
|
|
137
|
+
}> | null;
|
|
138
|
+
};
|
|
139
|
+
Internal: boolean;
|
|
140
|
+
Attachable: boolean;
|
|
141
|
+
Ingress: boolean;
|
|
142
|
+
ConfigFrom: {
|
|
143
|
+
Network: string;
|
|
144
|
+
};
|
|
145
|
+
ConfigOnly: boolean;
|
|
146
|
+
Containers: Record<string, any>;
|
|
147
|
+
Options: Record<string, string>;
|
|
148
|
+
Labels: Record<string, string>;
|
|
149
|
+
};
|
|
150
|
+
|
|
112
151
|
/**
|
|
113
152
|
* Docker API client that wraps Docker CLI commands
|
|
114
153
|
*/
|
|
@@ -457,6 +496,40 @@ export class DockerApi {
|
|
|
457
496
|
await this.exec(["network", "rm", networkId]);
|
|
458
497
|
}
|
|
459
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Get Docker network information
|
|
501
|
+
*
|
|
502
|
+
* @param networkId Network ID or name
|
|
503
|
+
* @returns Network details in JSON format
|
|
504
|
+
*/
|
|
505
|
+
async inspectNetwork(networkId: string): Promise<NetworkInfo[]> {
|
|
506
|
+
const { stdout } = await this.exec(
|
|
507
|
+
["network", "inspect", networkId],
|
|
508
|
+
undefined,
|
|
509
|
+
true,
|
|
510
|
+
);
|
|
511
|
+
try {
|
|
512
|
+
return JSON.parse(stdout.trim()) as NetworkInfo[];
|
|
513
|
+
} catch (_error) {
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Check if a network exists
|
|
520
|
+
*
|
|
521
|
+
* @param networkId Network ID or name
|
|
522
|
+
* @returns True if network exists
|
|
523
|
+
*/
|
|
524
|
+
async networkExists(networkId: string): Promise<boolean> {
|
|
525
|
+
try {
|
|
526
|
+
const networks = await this.inspectNetwork(networkId);
|
|
527
|
+
return networks.length > 0;
|
|
528
|
+
} catch (_error) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
460
533
|
/**
|
|
461
534
|
* Connect container to network
|
|
462
535
|
*
|
package/src/docker/container.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import { Resource } from "../resource.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { Secret } from "../secret.ts";
|
|
4
|
+
import {
|
|
5
|
+
DockerApi,
|
|
6
|
+
normalizeDuration,
|
|
7
|
+
type ContainerInfo,
|
|
8
|
+
type ContainerRuntimeInfo,
|
|
9
|
+
} from "./api.ts";
|
|
4
10
|
import type { Image } from "./image.ts";
|
|
5
11
|
import type { RemoteImage } from "./remote-image.ts";
|
|
6
12
|
|
|
@@ -140,7 +146,7 @@ export interface ContainerProps {
|
|
|
140
146
|
/**
|
|
141
147
|
* Environment variables
|
|
142
148
|
*/
|
|
143
|
-
environment?: Record<string, string>;
|
|
149
|
+
environment?: Record<string, string | Secret>;
|
|
144
150
|
|
|
145
151
|
/**
|
|
146
152
|
* Port mappings
|
|
@@ -207,6 +213,11 @@ export interface Container extends ContainerProps {
|
|
|
207
213
|
* Time when the container was created
|
|
208
214
|
*/
|
|
209
215
|
createdAt: number;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Inspect the container to get detailed information
|
|
219
|
+
*/
|
|
220
|
+
inspect(): Promise<ContainerRuntimeInfo>;
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
/**
|
|
@@ -370,6 +381,13 @@ export const Container = Resource(
|
|
|
370
381
|
name: containerName,
|
|
371
382
|
state: containerState,
|
|
372
383
|
createdAt: new Date(containerInfo.Created).getTime(),
|
|
384
|
+
inspect: async () => {
|
|
385
|
+
const [info] = await api.inspectContainer(containerName);
|
|
386
|
+
if (!info) {
|
|
387
|
+
throw new Error(`Container ${containerName} not found`);
|
|
388
|
+
}
|
|
389
|
+
return toRuntimeInfo(info);
|
|
390
|
+
},
|
|
373
391
|
};
|
|
374
392
|
}
|
|
375
393
|
}
|
|
@@ -396,7 +414,7 @@ export const Container = Resource(
|
|
|
396
414
|
// Create new container
|
|
397
415
|
const containerId = await api.createContainer(imageRef, containerName, {
|
|
398
416
|
ports: portMappings,
|
|
399
|
-
env: props.environment,
|
|
417
|
+
env: normalizeEnvironment(props.environment),
|
|
400
418
|
volumes: volumeMappings,
|
|
401
419
|
cmd: props.command,
|
|
402
420
|
healthcheck: props.healthcheck,
|
|
@@ -424,10 +442,46 @@ export const Container = Resource(
|
|
|
424
442
|
name: containerName,
|
|
425
443
|
state: containerState,
|
|
426
444
|
createdAt: Date.now(),
|
|
445
|
+
inspect: async () => {
|
|
446
|
+
const [info] = await api.inspectContainer(containerName);
|
|
447
|
+
if (!info) {
|
|
448
|
+
throw new Error(`Container ${containerName} not found`);
|
|
449
|
+
}
|
|
450
|
+
return toRuntimeInfo(info);
|
|
451
|
+
},
|
|
427
452
|
};
|
|
428
453
|
},
|
|
429
454
|
);
|
|
430
455
|
|
|
456
|
+
function toRuntimeInfo(info: ContainerInfo): ContainerRuntimeInfo {
|
|
457
|
+
const ports: Record<string, number> = {};
|
|
458
|
+
const networkSettings = info.NetworkSettings;
|
|
459
|
+
|
|
460
|
+
if (networkSettings?.Ports) {
|
|
461
|
+
for (const [internal, bindings] of Object.entries(networkSettings.Ports)) {
|
|
462
|
+
if (bindings && bindings.length > 0) {
|
|
463
|
+
ports[internal] = parseInt(bindings[0].HostPort, 10);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Also check HostConfig.PortBindings as a fallback or additional source
|
|
469
|
+
// though NetworkSettings.Ports is usually the source of truth for running containers
|
|
470
|
+
if (info.HostConfig.PortBindings) {
|
|
471
|
+
for (const [internal, bindings] of Object.entries(
|
|
472
|
+
info.HostConfig.PortBindings,
|
|
473
|
+
)) {
|
|
474
|
+
if (bindings && bindings.length > 0 && !(internal in ports)) {
|
|
475
|
+
ports[internal] = parseInt(bindings[0].HostPort, 10);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
ports,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
431
485
|
function getNetworkChanges(
|
|
432
486
|
props: ContainerProps,
|
|
433
487
|
containerInfo: ContainerInfo,
|
|
@@ -477,7 +531,12 @@ function shouldReplace(
|
|
|
477
531
|
}
|
|
478
532
|
|
|
479
533
|
// Environment variables
|
|
480
|
-
if (
|
|
534
|
+
if (
|
|
535
|
+
!compareEnv(
|
|
536
|
+
normalizeEnvironment(props.environment),
|
|
537
|
+
containerInfo.Config.Env,
|
|
538
|
+
)
|
|
539
|
+
) {
|
|
481
540
|
return true;
|
|
482
541
|
}
|
|
483
542
|
|
|
@@ -513,6 +572,21 @@ function shouldReplace(
|
|
|
513
572
|
return false;
|
|
514
573
|
}
|
|
515
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Unwrap secrets in given environment variables
|
|
577
|
+
* @internal
|
|
578
|
+
*/
|
|
579
|
+
function normalizeEnvironment(
|
|
580
|
+
environment: Record<string, string | Secret> | undefined,
|
|
581
|
+
): Record<string, string> {
|
|
582
|
+
return Object.fromEntries(
|
|
583
|
+
Object.entries(environment ?? {}).map(([key, value]) => [
|
|
584
|
+
key,
|
|
585
|
+
Secret.unwrap(value),
|
|
586
|
+
]),
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
516
590
|
/**
|
|
517
591
|
* Normalize port mappings to a comparable format
|
|
518
592
|
* @internal
|
package/src/docker/network.ts
CHANGED
|
@@ -102,14 +102,34 @@ export const Network = Resource(
|
|
|
102
102
|
} else {
|
|
103
103
|
// Create the network
|
|
104
104
|
props.driver = props.driver || "bridge";
|
|
105
|
-
|
|
105
|
+
try {
|
|
106
|
+
const networkId = await api.createNetwork(networkName, props.driver);
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
return {
|
|
109
|
+
...props,
|
|
110
|
+
id: networkId,
|
|
111
|
+
name: networkName,
|
|
112
|
+
createdAt: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
const errorMessage = error.message || String(error);
|
|
116
|
+
if (
|
|
117
|
+
errorMessage.includes(
|
|
118
|
+
`network with name ${networkName} already exists`,
|
|
119
|
+
)
|
|
120
|
+
) {
|
|
121
|
+
const [details] = await api.inspectNetwork(networkName);
|
|
122
|
+
if (details) {
|
|
123
|
+
return {
|
|
124
|
+
...props,
|
|
125
|
+
id: details.Id,
|
|
126
|
+
name: networkName,
|
|
127
|
+
createdAt: Date.parse(details.Created) || Date.now(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
113
133
|
}
|
|
114
134
|
},
|
|
115
135
|
);
|
package/src/planetscale/api.ts
CHANGED
|
@@ -78,7 +78,7 @@ export function createPlanetScaleClient(
|
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const extractToken = (props: PlanetScaleProps) => {
|
|
81
|
+
export const extractToken = (props: PlanetScaleProps) => {
|
|
82
82
|
if (props.apiKey) {
|
|
83
83
|
return props.apiKey.unencrypted;
|
|
84
84
|
} else if (props.serviceTokenId && props.serviceToken) {
|
|
@@ -83,6 +83,22 @@ export interface BranchProps extends PlanetScaleProps {
|
|
|
83
83
|
* Enable or disable safe migrations on this branch
|
|
84
84
|
*/
|
|
85
85
|
safeMigrations?: boolean;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The region to create the branch in.
|
|
89
|
+
* If not provided, the branch will be created in the default region for its database.
|
|
90
|
+
* On adopt/update, if specified, the actual branch region is validated against this value.
|
|
91
|
+
*
|
|
92
|
+
* @see https://planetscale.com/docs/concepts/regions
|
|
93
|
+
*/
|
|
94
|
+
region?: {
|
|
95
|
+
/**
|
|
96
|
+
* The slug identifier of the region (e.g. "us-east", "eu-west", "gcp-us-central1")
|
|
97
|
+
*
|
|
98
|
+
* @see https://planetscale.com/docs/concepts/regions#available-regions
|
|
99
|
+
*/
|
|
100
|
+
slug: string;
|
|
101
|
+
};
|
|
86
102
|
}
|
|
87
103
|
|
|
88
104
|
/**
|
|
@@ -113,6 +129,20 @@ export interface Branch extends BranchProps {
|
|
|
113
129
|
* HTML URL to access the branch
|
|
114
130
|
*/
|
|
115
131
|
htmlUrl: string;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The region of the branch as reported by PlanetScale.
|
|
135
|
+
*
|
|
136
|
+
* @see https://planetscale.com/docs/concepts/regions
|
|
137
|
+
*/
|
|
138
|
+
region: {
|
|
139
|
+
/**
|
|
140
|
+
* The slug identifier of the region (e.g. "us-east", "eu-west", "gcp-us-central1")
|
|
141
|
+
*
|
|
142
|
+
* @see https://planetscale.com/docs/concepts/regions#available-regions
|
|
143
|
+
*/
|
|
144
|
+
slug: string;
|
|
145
|
+
};
|
|
116
146
|
}
|
|
117
147
|
|
|
118
148
|
/**
|
|
@@ -259,6 +289,18 @@ export const Branch = Resource(
|
|
|
259
289
|
const data = getResponse.data;
|
|
260
290
|
const currentParentBranch = data.parent_branch || "main";
|
|
261
291
|
|
|
292
|
+
// Validate region matches if specified
|
|
293
|
+
if (props.region) {
|
|
294
|
+
const actualSlug = data.region.slug;
|
|
295
|
+
if (actualSlug !== props.region.slug) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
`Branch "${branchName}" is in region "${actualSlug}" but expected "${props.region.slug}". ` +
|
|
298
|
+
`PlanetScale branch regions cannot be changed after creation. ` +
|
|
299
|
+
`Either update the region in your configuration to match, or create a new branch in the correct region.`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
262
304
|
// Check immutable properties
|
|
263
305
|
if (props.parentBranch && parentBranchName !== currentParentBranch) {
|
|
264
306
|
throw new Error(
|
|
@@ -323,6 +365,7 @@ export const Branch = Resource(
|
|
|
323
365
|
createdAt: data.created_at,
|
|
324
366
|
updatedAt: data.updated_at,
|
|
325
367
|
htmlUrl: data.html_url,
|
|
368
|
+
region: { slug: data.region.slug },
|
|
326
369
|
};
|
|
327
370
|
}
|
|
328
371
|
let clusterSize: string | undefined;
|
|
@@ -352,6 +395,7 @@ export const Branch = Resource(
|
|
|
352
395
|
parent_branch: parentBranchName,
|
|
353
396
|
backup_id: props.backupId,
|
|
354
397
|
seed_data: props.seedData,
|
|
398
|
+
region: props.region?.slug,
|
|
355
399
|
// This is ignored unless props.backupId is provided
|
|
356
400
|
cluster_size: clusterSize,
|
|
357
401
|
},
|
|
@@ -391,6 +435,7 @@ export const Branch = Resource(
|
|
|
391
435
|
createdAt: data.created_at,
|
|
392
436
|
updatedAt: data.updated_at,
|
|
393
437
|
htmlUrl: data.html_url,
|
|
438
|
+
region: { slug: data.region.slug },
|
|
394
439
|
};
|
|
395
440
|
},
|
|
396
441
|
);
|