alchemy 2.0.0-beta.64 → 2.0.0-beta.65
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/lib/AWS/EC2/InternetGateway.d.ts.map +1 -1
- package/lib/AWS/EC2/InternetGateway.js +8 -3
- package/lib/AWS/EC2/InternetGateway.js.map +1 -1
- package/lib/AWS/EC2/LingeringEnis.d.ts +26 -0
- package/lib/AWS/EC2/LingeringEnis.d.ts.map +1 -0
- package/lib/AWS/EC2/LingeringEnis.js +128 -0
- package/lib/AWS/EC2/LingeringEnis.js.map +1 -0
- package/lib/AWS/EC2/SecurityGroup.d.ts.map +1 -1
- package/lib/AWS/EC2/SecurityGroup.js +14 -14
- package/lib/AWS/EC2/SecurityGroup.js.map +1 -1
- package/lib/AWS/EC2/Subnet.d.ts.map +1 -1
- package/lib/AWS/EC2/Subnet.js +13 -21
- package/lib/AWS/EC2/Subnet.js.map +1 -1
- package/lib/AWS/ECR/Image.d.ts +1 -0
- package/lib/AWS/ECR/Image.d.ts.map +1 -1
- package/lib/AWS/EKS/Addon.d.ts.map +1 -1
- package/lib/AWS/EKS/Addon.js +15 -3
- package/lib/AWS/EKS/Addon.js.map +1 -1
- package/lib/AWS/EKS/Deployment.d.ts +10 -1
- package/lib/AWS/EKS/Deployment.d.ts.map +1 -1
- package/lib/AWS/EKS/Deployment.js +10 -4
- package/lib/AWS/EKS/Deployment.js.map +1 -1
- package/lib/AWS/EKS/HyperPod.d.ts +57 -0
- package/lib/AWS/EKS/HyperPod.d.ts.map +1 -0
- package/lib/AWS/EKS/HyperPod.js +46 -0
- package/lib/AWS/EKS/HyperPod.js.map +1 -0
- package/lib/AWS/EKS/Job.d.ts +10 -1
- package/lib/AWS/EKS/Job.d.ts.map +1 -1
- package/lib/AWS/EKS/Job.js +16 -6
- package/lib/AWS/EKS/Job.js.map +1 -1
- package/lib/AWS/EKS/internal/client.d.ts +4 -3
- package/lib/AWS/EKS/internal/client.d.ts.map +1 -1
- package/lib/AWS/EKS/internal/client.js +33 -2
- package/lib/AWS/EKS/internal/client.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +1 -1
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Providers.d.ts.map +1 -1
- package/lib/AWS/Providers.js +4 -1
- package/lib/AWS/Providers.js.map +1 -1
- package/lib/AWS/SageMaker/Cluster.d.ts +327 -0
- package/lib/AWS/SageMaker/Cluster.d.ts.map +1 -0
- package/lib/AWS/SageMaker/Cluster.js +469 -0
- package/lib/AWS/SageMaker/Cluster.js.map +1 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.d.ts +96 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.d.ts.map +1 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.js +270 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.js.map +1 -0
- package/lib/AWS/SageMaker/ComputeQuota.d.ts +106 -0
- package/lib/AWS/SageMaker/ComputeQuota.d.ts.map +1 -0
- package/lib/AWS/SageMaker/ComputeQuota.js +268 -0
- package/lib/AWS/SageMaker/ComputeQuota.js.map +1 -0
- package/lib/AWS/SageMaker/index.d.ts +3 -0
- package/lib/AWS/SageMaker/index.d.ts.map +1 -1
- package/lib/AWS/SageMaker/index.js +3 -0
- package/lib/AWS/SageMaker/index.js.map +1 -1
- package/lib/Bundle/PurePlugin.d.ts +29 -7
- package/lib/Bundle/PurePlugin.d.ts.map +1 -1
- package/lib/Bundle/PurePlugin.js +56 -33
- package/lib/Bundle/PurePlugin.js.map +1 -1
- package/lib/Cli/commands/nuke.d.ts.map +1 -1
- package/lib/Cli/commands/nuke.js +231 -37
- package/lib/Cli/commands/nuke.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +1 -1
- package/lib/Cloudflare/Workers/Assets.d.ts +26 -8
- package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Assets.js +46 -1
- package/lib/Cloudflare/Workers/Assets.js.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +2 -2
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +39 -6
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +7 -5
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +0 -1
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +2 -2
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts +20 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +11 -0
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.js +10 -4
- package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts +2 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.js +5 -5
- package/lib/Cloudflare/Workflows/WorkflowBridge.js.map +1 -1
- package/lib/Cloudflare/Workflows/index.d.ts +1 -1
- package/lib/Cloudflare/Workflows/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workflows/index.js +1 -1
- package/lib/Cloudflare/Workflows/index.js.map +1 -1
- package/lib/Docker/Container.d.ts +26 -0
- package/lib/Docker/Container.d.ts.map +1 -1
- package/lib/Docker/Container.js +20 -1
- package/lib/Docker/Container.js.map +1 -1
- package/lib/Docker/Docker.d.ts +2 -0
- package/lib/Docker/Docker.d.ts.map +1 -1
- package/lib/Docker/Docker.js.map +1 -1
- package/lib/Output.d.ts +4 -1
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js.map +1 -1
- package/lib/Provider.d.ts +20 -0
- package/lib/Provider.d.ts.map +1 -1
- package/lib/Provider.js.map +1 -1
- package/lib/Random.d.ts +1 -1
- package/lib/Random.d.ts.map +1 -1
- package/lib/Resource.d.ts +14 -1
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js.map +1 -1
- package/lib/SQL/D1.d.ts +1 -1
- package/lib/SQL/D1.js +1 -1
- package/lib/SQL/Postgres.d.ts +1 -1
- package/lib/SQL/Postgres.js +1 -1
- package/lib/State/HttpStateApi.d.ts +11 -11
- package/lib/Test/Alchemy.d.ts +1 -1
- package/lib/Test/Alchemy.js +1 -1
- package/lib/Test/Vitest.d.ts +58 -0
- package/lib/Test/Vitest.d.ts.map +1 -0
- package/lib/Test/Vitest.js +114 -0
- package/lib/Test/Vitest.js.map +1 -0
- package/lib/Util/proxy-chain.d.ts +5 -0
- package/lib/Util/proxy-chain.d.ts.map +1 -1
- package/lib/Util/proxy-chain.js +52 -4
- package/lib/Util/proxy-chain.js.map +1 -1
- package/package.json +23 -15
- package/src/AWS/EC2/InternetGateway.ts +8 -3
- package/src/AWS/EC2/LingeringEnis.ts +169 -0
- package/src/AWS/EC2/SecurityGroup.ts +24 -28
- package/src/AWS/EC2/Subnet.ts +23 -34
- package/src/AWS/EKS/Addon.ts +17 -4
- package/src/AWS/EKS/Deployment.ts +26 -5
- package/src/AWS/EKS/HyperPod.ts +97 -0
- package/src/AWS/EKS/Job.ts +32 -7
- package/src/AWS/EKS/internal/client.ts +40 -3
- package/src/AWS/Lambda/Function.ts +5 -3
- package/src/AWS/Providers.ts +6 -0
- package/src/AWS/SageMaker/Cluster.ts +775 -0
- package/src/AWS/SageMaker/ClusterSchedulerConfig.ts +430 -0
- package/src/AWS/SageMaker/ComputeQuota.ts +426 -0
- package/src/AWS/SageMaker/index.ts +3 -0
- package/src/Bundle/PurePlugin.ts +92 -39
- package/src/Cli/commands/nuke.ts +295 -56
- package/src/Cloudflare/Workers/Assets.ts +53 -1
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +46 -7
- package/src/Cloudflare/Workers/Worker.ts +7 -5
- package/src/Cloudflare/Workers/WorkerBundle.ts +31 -1
- package/src/Cloudflare/Workers/WorkerProvider.ts +18 -4
- package/src/Cloudflare/Workflows/WorkflowBridge.ts +5 -5
- package/src/Cloudflare/Workflows/index.ts +1 -1
- package/src/Docker/Container.ts +30 -1
- package/src/Docker/Docker.ts +2 -0
- package/src/Output.ts +19 -11
- package/src/Provider.ts +20 -0
- package/src/Resource.ts +18 -1
- package/src/SQL/D1.ts +1 -1
- package/src/SQL/Postgres.ts +1 -1
- package/src/Test/Alchemy.ts +1 -1
- package/src/Test/Vitest.ts +244 -0
- package/src/Util/proxy-chain.ts +73 -4
- package/lib/SQL/index.d.ts +0 -4
- package/lib/SQL/index.d.ts.map +0 -1
- package/lib/SQL/index.js +0 -4
- package/lib/SQL/index.js.map +0 -1
- package/src/SQL/index.ts +0 -3
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import * as sagemaker from "@distilled.cloud/aws/sagemaker";
|
|
2
|
+
import * as Data from "effect/Data";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schedule from "effect/Schedule";
|
|
5
|
+
import * as EffectStream from "effect/Stream";
|
|
6
|
+
import { Unowned } from "../../AdoptPolicy.ts";
|
|
7
|
+
import { isResolved } from "../../Diff.ts";
|
|
8
|
+
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
9
|
+
import * as Provider from "../../Provider.ts";
|
|
10
|
+
import { Resource } from "../../Resource.ts";
|
|
11
|
+
import {
|
|
12
|
+
createInternalTags,
|
|
13
|
+
diffTags,
|
|
14
|
+
hasAlchemyTags,
|
|
15
|
+
type Tags,
|
|
16
|
+
} from "../../Tags.ts";
|
|
17
|
+
import type { Providers } from "../Providers.ts";
|
|
18
|
+
|
|
19
|
+
export interface ComputeQuotaProps {
|
|
20
|
+
/**
|
|
21
|
+
* Name of the compute allocation. Maximum 63 characters, alphanumeric and
|
|
22
|
+
* hyphens.
|
|
23
|
+
* @default ${app}-${stage}-${id}
|
|
24
|
+
*/
|
|
25
|
+
name?: string;
|
|
26
|
+
/**
|
|
27
|
+
* ARN of the EKS-orchestrated HyperPod cluster the allocation applies to.
|
|
28
|
+
* Changing the cluster replaces the allocation.
|
|
29
|
+
*/
|
|
30
|
+
clusterArn: string;
|
|
31
|
+
/**
|
|
32
|
+
* The team the quota is allocated to (its name maps to a Kubernetes
|
|
33
|
+
* namespace `hyperpod-ns-<team-name>`) and its fair-share weight.
|
|
34
|
+
*/
|
|
35
|
+
computeQuotaTarget: sagemaker.ComputeQuotaTarget;
|
|
36
|
+
/**
|
|
37
|
+
* The allocation itself: instance-type quotas, the resource-sharing
|
|
38
|
+
* strategy for idle compute, and whether the team's own tasks can preempt
|
|
39
|
+
* each other.
|
|
40
|
+
*/
|
|
41
|
+
computeQuotaConfig?: sagemaker.ComputeQuotaConfig;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the quota is enforced.
|
|
44
|
+
* @default "Enabled"
|
|
45
|
+
*/
|
|
46
|
+
activationState?: sagemaker.ActivationState;
|
|
47
|
+
/**
|
|
48
|
+
* A description of the compute allocation.
|
|
49
|
+
*/
|
|
50
|
+
description?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Tags to associate with the compute allocation. Merged with internal
|
|
53
|
+
* Alchemy tags.
|
|
54
|
+
*/
|
|
55
|
+
tags?: Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ComputeQuota extends Resource<
|
|
59
|
+
"AWS.SageMaker.ComputeQuota",
|
|
60
|
+
ComputeQuotaProps,
|
|
61
|
+
{
|
|
62
|
+
/**
|
|
63
|
+
* The compute allocation's ID.
|
|
64
|
+
*/
|
|
65
|
+
computeQuotaId: string;
|
|
66
|
+
/**
|
|
67
|
+
* ARN of the compute allocation.
|
|
68
|
+
*/
|
|
69
|
+
computeQuotaArn: string;
|
|
70
|
+
/**
|
|
71
|
+
* The compute allocation's name.
|
|
72
|
+
*/
|
|
73
|
+
name: string;
|
|
74
|
+
/**
|
|
75
|
+
* ARN of the HyperPod cluster the allocation applies to.
|
|
76
|
+
*/
|
|
77
|
+
clusterArn: string;
|
|
78
|
+
/**
|
|
79
|
+
* The allocation's current version (incremented on every update).
|
|
80
|
+
*/
|
|
81
|
+
computeQuotaVersion: number;
|
|
82
|
+
/**
|
|
83
|
+
* The team the quota is allocated to. Task governance materializes the
|
|
84
|
+
* `hyperpod-ns-<teamName>` namespace and its Kueue LocalQueue from it.
|
|
85
|
+
*/
|
|
86
|
+
teamName: string;
|
|
87
|
+
},
|
|
88
|
+
never,
|
|
89
|
+
Providers
|
|
90
|
+
> {}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A SageMaker HyperPod compute allocation (task governance) — reserves
|
|
94
|
+
* instance capacity on an EKS-orchestrated HyperPod cluster for a team,
|
|
95
|
+
* with fair-share weights and borrow/lend rules for idle compute.
|
|
96
|
+
* @resource
|
|
97
|
+
* @section Creating Compute Allocations
|
|
98
|
+
* @example Team Quota with Borrowing
|
|
99
|
+
* ```typescript
|
|
100
|
+
* import * as AWS from "alchemy/AWS";
|
|
101
|
+
*
|
|
102
|
+
* const quota = yield* AWS.SageMaker.ComputeQuota("ResearchQuota", {
|
|
103
|
+
* clusterArn: hyperpod.clusterArn,
|
|
104
|
+
* computeQuotaTarget: { TeamName: "research", FairShareWeight: 10 },
|
|
105
|
+
* computeQuotaConfig: {
|
|
106
|
+
* ComputeQuotaResources: [{ InstanceType: "ml.g5.xlarge", Count: 2 }],
|
|
107
|
+
* ResourceSharingConfig: { Strategy: "Lend", BorrowLimit: 50 },
|
|
108
|
+
* },
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export const ComputeQuota = Resource<ComputeQuota>(
|
|
113
|
+
"AWS.SageMaker.ComputeQuota",
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const createQuotaName = (id: string, props: { name?: string | undefined }) =>
|
|
117
|
+
props.name
|
|
118
|
+
? Effect.succeed(props.name)
|
|
119
|
+
: createPhysicalName({ id, maxLength: 63 });
|
|
120
|
+
|
|
121
|
+
const describeQuotaOrUndefined = (quotaId: string) =>
|
|
122
|
+
sagemaker
|
|
123
|
+
.describeComputeQuota({ ComputeQuotaId: quotaId })
|
|
124
|
+
.pipe(Effect.catchTag("ResourceNotFound", () => Effect.succeed(undefined)));
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Look a compute allocation up by exact name — used when state was lost
|
|
128
|
+
* (read without output) or a create raced.
|
|
129
|
+
*/
|
|
130
|
+
const findQuotaByName = Effect.fn(function* (name: string) {
|
|
131
|
+
const summaries = yield* sagemaker.listComputeQuotas
|
|
132
|
+
.pages({ NameContains: name })
|
|
133
|
+
.pipe(
|
|
134
|
+
EffectStream.runCollect,
|
|
135
|
+
Effect.map((chunk) =>
|
|
136
|
+
Array.from(chunk).flatMap((page) => page.ComputeQuotaSummaries ?? []),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
return summaries.find((s) => s.Name === name && s.Status !== "Deleted");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const fetchQuotaTags = Effect.fn(function* (arn: string) {
|
|
143
|
+
const response = yield* sagemaker
|
|
144
|
+
.listTags({ ResourceArn: arn })
|
|
145
|
+
.pipe(
|
|
146
|
+
Effect.catchTag("AccessDeniedException", () => Effect.succeed(undefined)),
|
|
147
|
+
);
|
|
148
|
+
return Object.fromEntries(
|
|
149
|
+
(response?.Tags ?? []).flatMap((tag) =>
|
|
150
|
+
tag.Key !== undefined ? [[tag.Key, tag.Value ?? ""]] : [],
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const toAttrs = (
|
|
156
|
+
described: sagemaker.DescribeComputeQuotaResponse,
|
|
157
|
+
): ComputeQuota["Attributes"] => ({
|
|
158
|
+
computeQuotaId: described.ComputeQuotaId,
|
|
159
|
+
computeQuotaArn: described.ComputeQuotaArn,
|
|
160
|
+
name: described.Name,
|
|
161
|
+
clusterArn: described.ClusterArn ?? "",
|
|
162
|
+
computeQuotaVersion: described.ComputeQuotaVersion,
|
|
163
|
+
teamName: described.ComputeQuotaTarget?.TeamName ?? "",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The compute allocation is still transitioning toward the awaited state —
|
|
168
|
+
* retried by the bounded wait schedule.
|
|
169
|
+
*/
|
|
170
|
+
class ComputeQuotaNotReady extends Data.TaggedError("ComputeQuotaNotReady")<{
|
|
171
|
+
readonly quotaId: string;
|
|
172
|
+
readonly status: string | undefined;
|
|
173
|
+
}> {}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The compute allocation converged to a terminal failed status.
|
|
177
|
+
*/
|
|
178
|
+
export class ComputeQuotaFailed extends Data.TaggedError("ComputeQuotaFailed")<{
|
|
179
|
+
readonly quotaId: string;
|
|
180
|
+
readonly status: string | undefined;
|
|
181
|
+
readonly message: string | undefined;
|
|
182
|
+
}> {}
|
|
183
|
+
|
|
184
|
+
const FAILED_STATUSES: string[] = [
|
|
185
|
+
"CreateFailed",
|
|
186
|
+
"CreateRollbackFailed",
|
|
187
|
+
"UpdateFailed",
|
|
188
|
+
"UpdateRollbackFailed",
|
|
189
|
+
"DeleteFailed",
|
|
190
|
+
"DeleteRollbackFailed",
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
// Explicitly-typed retry wrapper — an inline `Effect.retry` in provider
|
|
194
|
+
// lifecycle code leaks `Retry.Return`'s conditional type into declaration
|
|
195
|
+
// emit and widens the provider layer to `unknown` for every consumer of
|
|
196
|
+
// `AWS.providers()`.
|
|
197
|
+
const retryWhileNotReady = <A, E extends { readonly _tag: string }, R>(
|
|
198
|
+
self: Effect.Effect<A, E, R>,
|
|
199
|
+
): Effect.Effect<A, E, R> =>
|
|
200
|
+
Effect.retry(self, {
|
|
201
|
+
while: (e) => e._tag === "ComputeQuotaNotReady",
|
|
202
|
+
schedule: Schedule.max([Schedule.spaced("5 seconds"), Schedule.recurs(60)]),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const waitForQuota = (quotaId: string, target: "Ready" | "Gone") =>
|
|
206
|
+
retryWhileNotReady(
|
|
207
|
+
Effect.gen(function* () {
|
|
208
|
+
const described = yield* describeQuotaOrUndefined(quotaId);
|
|
209
|
+
if (target === "Gone") {
|
|
210
|
+
if (described === undefined || described.Status === "Deleted") return;
|
|
211
|
+
if (FAILED_STATUSES.includes(described.Status)) {
|
|
212
|
+
return yield* Effect.fail(
|
|
213
|
+
new ComputeQuotaFailed({
|
|
214
|
+
quotaId,
|
|
215
|
+
status: described.Status,
|
|
216
|
+
message: described.FailureReason,
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return yield* Effect.fail(
|
|
221
|
+
new ComputeQuotaNotReady({ quotaId, status: described.Status }),
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
if (described?.Status === "Created" || described?.Status === "Updated") {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (
|
|
228
|
+
described !== undefined &&
|
|
229
|
+
FAILED_STATUSES.includes(described.Status)
|
|
230
|
+
) {
|
|
231
|
+
return yield* Effect.fail(
|
|
232
|
+
new ComputeQuotaFailed({
|
|
233
|
+
quotaId,
|
|
234
|
+
status: described.Status,
|
|
235
|
+
message: described.FailureReason,
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return yield* Effect.fail(
|
|
240
|
+
new ComputeQuotaNotReady({ quotaId, status: described?.Status }),
|
|
241
|
+
);
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
export const ComputeQuotaProvider = () =>
|
|
246
|
+
Provider.effect(
|
|
247
|
+
ComputeQuota,
|
|
248
|
+
Effect.gen(function* () {
|
|
249
|
+
return {
|
|
250
|
+
stables: ["computeQuotaId", "computeQuotaArn", "name", "clusterArn"],
|
|
251
|
+
list: () =>
|
|
252
|
+
Effect.gen(function* () {
|
|
253
|
+
const summaries = yield* sagemaker.listComputeQuotas.pages({}).pipe(
|
|
254
|
+
EffectStream.runCollect,
|
|
255
|
+
Effect.map((chunk) =>
|
|
256
|
+
Array.from(chunk).flatMap(
|
|
257
|
+
(page) => page.ComputeQuotaSummaries ?? [],
|
|
258
|
+
),
|
|
259
|
+
),
|
|
260
|
+
);
|
|
261
|
+
return summaries.flatMap((s) =>
|
|
262
|
+
s.ComputeQuotaId !== undefined && s.Status !== "Deleted"
|
|
263
|
+
? [
|
|
264
|
+
{
|
|
265
|
+
computeQuotaId: s.ComputeQuotaId,
|
|
266
|
+
computeQuotaArn: s.ComputeQuotaArn ?? "",
|
|
267
|
+
name: s.Name ?? "",
|
|
268
|
+
clusterArn: s.ClusterArn ?? "",
|
|
269
|
+
computeQuotaVersion: s.ComputeQuotaVersion ?? 1,
|
|
270
|
+
teamName: s.ComputeQuotaTarget?.TeamName ?? "",
|
|
271
|
+
},
|
|
272
|
+
]
|
|
273
|
+
: [],
|
|
274
|
+
);
|
|
275
|
+
}),
|
|
276
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
277
|
+
const quotaId =
|
|
278
|
+
output?.computeQuotaId ??
|
|
279
|
+
(yield* findQuotaByName(yield* createQuotaName(id, olds ?? {})))
|
|
280
|
+
?.ComputeQuotaId;
|
|
281
|
+
if (quotaId === undefined) return undefined;
|
|
282
|
+
const described = yield* describeQuotaOrUndefined(quotaId);
|
|
283
|
+
if (
|
|
284
|
+
!described ||
|
|
285
|
+
described.Status === "Deleting" ||
|
|
286
|
+
described.Status === "Deleted"
|
|
287
|
+
) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
const attrs = toAttrs(described);
|
|
291
|
+
const tags = yield* fetchQuotaTags(attrs.computeQuotaArn);
|
|
292
|
+
return (yield* hasAlchemyTags(id, tags as Tags))
|
|
293
|
+
? attrs
|
|
294
|
+
: Unowned(attrs);
|
|
295
|
+
}),
|
|
296
|
+
diff: Effect.fn(function* ({ id, news, olds }) {
|
|
297
|
+
if (!isResolved(news)) return;
|
|
298
|
+
if (olds === undefined) return;
|
|
299
|
+
const oldName = yield* createQuotaName(id, olds);
|
|
300
|
+
const newName = yield* createQuotaName(id, news);
|
|
301
|
+
// The name and target cluster are fixed at creation.
|
|
302
|
+
if (oldName !== newName || olds.clusterArn !== news.clusterArn) {
|
|
303
|
+
return { action: "replace" } as const;
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
reconcile: Effect.fn(function* ({ id, news, output, session }) {
|
|
307
|
+
if (!news) {
|
|
308
|
+
return yield* Effect.fail(
|
|
309
|
+
new Error("SageMaker ComputeQuota requires props"),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const name = yield* createQuotaName(id, news);
|
|
313
|
+
const internalTags = yield* createInternalTags(id);
|
|
314
|
+
const desiredTags = { ...internalTags, ...news.tags };
|
|
315
|
+
|
|
316
|
+
// Observe — by cached id first, then by name (lost state or race).
|
|
317
|
+
let quotaId = output?.computeQuotaId;
|
|
318
|
+
let described =
|
|
319
|
+
quotaId !== undefined
|
|
320
|
+
? yield* describeQuotaOrUndefined(quotaId)
|
|
321
|
+
: undefined;
|
|
322
|
+
if (described === undefined) {
|
|
323
|
+
const found = yield* findQuotaByName(name);
|
|
324
|
+
described =
|
|
325
|
+
found?.ComputeQuotaId !== undefined
|
|
326
|
+
? yield* describeQuotaOrUndefined(found.ComputeQuotaId)
|
|
327
|
+
: undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Ensure — create if missing; a Conflict means a concurrent
|
|
331
|
+
// create won the race, so re-observe by name.
|
|
332
|
+
if (described === undefined) {
|
|
333
|
+
const created = yield* sagemaker
|
|
334
|
+
.createComputeQuota({
|
|
335
|
+
Name: name,
|
|
336
|
+
ClusterArn: news.clusterArn,
|
|
337
|
+
ComputeQuotaConfig: news.computeQuotaConfig,
|
|
338
|
+
ComputeQuotaTarget: news.computeQuotaTarget,
|
|
339
|
+
ActivationState: news.activationState,
|
|
340
|
+
Description: news.description,
|
|
341
|
+
Tags: Object.entries(desiredTags).map(([Key, Value]) => ({
|
|
342
|
+
Key,
|
|
343
|
+
Value,
|
|
344
|
+
})),
|
|
345
|
+
})
|
|
346
|
+
.pipe(
|
|
347
|
+
Effect.catchTag("ConflictException", () =>
|
|
348
|
+
Effect.succeed(undefined),
|
|
349
|
+
),
|
|
350
|
+
);
|
|
351
|
+
quotaId =
|
|
352
|
+
created?.ComputeQuotaId ??
|
|
353
|
+
(yield* findQuotaByName(name))?.ComputeQuotaId;
|
|
354
|
+
if (quotaId === undefined) {
|
|
355
|
+
return yield* Effect.fail(
|
|
356
|
+
new Error(`failed to create compute quota ${name}`),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
yield* session.note(`Creating compute quota ${name}...`);
|
|
360
|
+
yield* waitForQuota(quotaId, "Ready");
|
|
361
|
+
} else {
|
|
362
|
+
quotaId = described.ComputeQuotaId;
|
|
363
|
+
// Wait out any in-flight transition before diffing.
|
|
364
|
+
yield* waitForQuota(quotaId, "Ready");
|
|
365
|
+
described = yield* describeQuotaOrUndefined(quotaId);
|
|
366
|
+
// Sync — diff observed allocation against desired.
|
|
367
|
+
if (
|
|
368
|
+
described !== undefined &&
|
|
369
|
+
(JSON.stringify(described.ComputeQuotaConfig) !==
|
|
370
|
+
JSON.stringify(news.computeQuotaConfig) ||
|
|
371
|
+
JSON.stringify(described.ComputeQuotaTarget) !==
|
|
372
|
+
JSON.stringify(news.computeQuotaTarget) ||
|
|
373
|
+
(news.activationState !== undefined &&
|
|
374
|
+
described.ActivationState !== news.activationState) ||
|
|
375
|
+
(described.Description ?? undefined) !==
|
|
376
|
+
(news.description ?? undefined))
|
|
377
|
+
) {
|
|
378
|
+
yield* sagemaker.updateComputeQuota({
|
|
379
|
+
ComputeQuotaId: quotaId,
|
|
380
|
+
TargetVersion: described.ComputeQuotaVersion,
|
|
381
|
+
ComputeQuotaConfig: news.computeQuotaConfig,
|
|
382
|
+
ComputeQuotaTarget: news.computeQuotaTarget,
|
|
383
|
+
ActivationState: news.activationState,
|
|
384
|
+
Description: news.description,
|
|
385
|
+
});
|
|
386
|
+
yield* session.note(`Updating compute quota ${name}...`);
|
|
387
|
+
yield* waitForQuota(quotaId, "Ready");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
described = yield* describeQuotaOrUndefined(quotaId);
|
|
392
|
+
if (described === undefined) {
|
|
393
|
+
return yield* Effect.fail(
|
|
394
|
+
new Error(`failed to read reconciled compute quota ${name}`),
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
const attrs = toAttrs(described);
|
|
398
|
+
|
|
399
|
+
// Sync tags — diff against OBSERVED cloud tags.
|
|
400
|
+
const currentTags = yield* fetchQuotaTags(attrs.computeQuotaArn);
|
|
401
|
+
const { removed, upsert } = diffTags(currentTags, desiredTags);
|
|
402
|
+
if (removed.length > 0) {
|
|
403
|
+
yield* sagemaker.deleteTags({
|
|
404
|
+
ResourceArn: attrs.computeQuotaArn,
|
|
405
|
+
TagKeys: removed,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (upsert.length > 0) {
|
|
409
|
+
yield* sagemaker.addTags({
|
|
410
|
+
ResourceArn: attrs.computeQuotaArn,
|
|
411
|
+
Tags: upsert.map(({ Key, Value }) => ({ Key, Value })),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
yield* session.note(attrs.computeQuotaArn);
|
|
416
|
+
return attrs;
|
|
417
|
+
}),
|
|
418
|
+
delete: Effect.fn(function* ({ output }) {
|
|
419
|
+
yield* sagemaker
|
|
420
|
+
.deleteComputeQuota({ ComputeQuotaId: output.computeQuotaId })
|
|
421
|
+
.pipe(Effect.catchTag("ResourceNotFound", () => Effect.void));
|
|
422
|
+
yield* waitForQuota(output.computeQuotaId, "Gone");
|
|
423
|
+
}),
|
|
424
|
+
};
|
|
425
|
+
}),
|
|
426
|
+
);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from "./BatchGetRecord.ts";
|
|
2
2
|
export * from "./BatchGetRecordHttp.ts";
|
|
3
|
+
export * from "./Cluster.ts";
|
|
4
|
+
export * from "./ClusterSchedulerConfig.ts";
|
|
5
|
+
export * from "./ComputeQuota.ts";
|
|
3
6
|
export * from "./BatchWriteRecord.ts";
|
|
4
7
|
export * from "./BatchWriteRecordHttp.ts";
|
|
5
8
|
export * from "./DeleteRecord.ts";
|
package/src/Bundle/PurePlugin.ts
CHANGED
|
@@ -62,10 +62,11 @@ export interface PurePluginOptions {
|
|
|
62
62
|
* too. This makes the user's own source tree-shakeable without any
|
|
63
63
|
* configuration.
|
|
64
64
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
65
|
+
* Only calls whose result is used (variable initializers, exports) are
|
|
66
|
+
* annotated in the auto-detected package. Top-level calls whose result
|
|
67
|
+
* is discarded (e.g. Hono/Express-style `app.get("/", handler)` route
|
|
68
|
+
* registrations) are preserved unless the package explicitly declares
|
|
69
|
+
* `"sideEffects": false` (or `[]`) in its `package.json`.
|
|
69
70
|
*
|
|
70
71
|
* @default true
|
|
71
72
|
*/
|
|
@@ -139,19 +140,32 @@ export const purePlugin = (
|
|
|
139
140
|
if (name === null || !isMatch(name)) return null;
|
|
140
141
|
|
|
141
142
|
// Only override `moduleSideEffects` when the owning package
|
|
142
|
-
// explicitly opts in via `sideEffects: false` / `[]`.
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
// explicitly opts in via `sideEffects: false` / `[]`. Marking
|
|
144
|
+
// arbitrary modules side-effect-free could erase intentional
|
|
145
|
+
// registrations / mutations the author made at the top level of
|
|
146
|
+
// files in packages that did not declare so.
|
|
147
|
+
const isEntry = entryPaths.has(cleanId);
|
|
148
|
+
const sideEffectFreePkg = isSideEffectFree(info?.sideEffects);
|
|
148
149
|
const markSideEffectFree =
|
|
149
|
-
markSideEffectFreeOpt &&
|
|
150
|
-
!entryPaths.has(cleanId) &&
|
|
151
|
-
isSideEffectFree(info?.sideEffects);
|
|
150
|
+
markSideEffectFreeOpt && !isEntry && sideEffectFreePkg;
|
|
152
151
|
|
|
153
152
|
const anchors = collectPureAnchorsCached(code, cleanId);
|
|
154
|
-
|
|
153
|
+
// Annotating a call whose result is BOUND (variable initializer,
|
|
154
|
+
// export) only lets the minifier drop it when the binding itself
|
|
155
|
+
// is unused — safe everywhere. Annotating a call whose result is
|
|
156
|
+
// DISCARDED (a bare expression statement like `app.get("/", h)`)
|
|
157
|
+
// is an instruction to delete the call under minification, so it
|
|
158
|
+
// is only applied when the owning package explicitly declared
|
|
159
|
+
// itself side-effect free, and never to entry modules, whose side
|
|
160
|
+
// effects we always preserve (#949).
|
|
161
|
+
const annotateDiscarded = !isEntry && sideEffectFreePkg;
|
|
162
|
+
const positions =
|
|
163
|
+
anchors === null
|
|
164
|
+
? []
|
|
165
|
+
: annotateDiscarded
|
|
166
|
+
? [...anchors.bound, ...anchors.discarded]
|
|
167
|
+
: anchors.bound;
|
|
168
|
+
if (positions.length === 0) {
|
|
155
169
|
// Metadata-only result: omitting `code` tells rolldown the
|
|
156
170
|
// source was NOT transformed, so no sourcemap is expected and
|
|
157
171
|
// no SOURCEMAP_BROKEN warning is emitted.
|
|
@@ -162,7 +176,7 @@ export const purePlugin = (
|
|
|
162
176
|
// rolldown's native side (computed on a background thread). The
|
|
163
177
|
// fallback covers direct hook invocations (unit tests).
|
|
164
178
|
const s = meta.magicString ?? new RolldownMagicString(code);
|
|
165
|
-
for (const anchor of
|
|
179
|
+
for (const anchor of positions) s.appendLeft(anchor, PURE_COMMENT);
|
|
166
180
|
return {
|
|
167
181
|
code: s,
|
|
168
182
|
moduleSideEffects: markSideEffectFree ? false : null,
|
|
@@ -324,14 +338,14 @@ export function packageNameFromId(id: string): string | null {
|
|
|
324
338
|
*/
|
|
325
339
|
const anchorsCache = new Map<
|
|
326
340
|
string,
|
|
327
|
-
{ readonly code: string; readonly anchors:
|
|
341
|
+
{ readonly code: string; readonly anchors: PureAnchors | null }
|
|
328
342
|
>();
|
|
329
343
|
const ANCHORS_CACHE_MAX = 10_000;
|
|
330
344
|
|
|
331
345
|
const collectPureAnchorsCached = (
|
|
332
346
|
code: string,
|
|
333
347
|
filename: string,
|
|
334
|
-
):
|
|
348
|
+
): PureAnchors | null => {
|
|
335
349
|
const cached = anchorsCache.get(filename);
|
|
336
350
|
if (cached !== undefined && cached.code === code) {
|
|
337
351
|
return cached.anchors;
|
|
@@ -344,16 +358,38 @@ const collectPureAnchorsCached = (
|
|
|
344
358
|
return anchors;
|
|
345
359
|
};
|
|
346
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Offsets at which `/*#__PURE__*\/` may be inserted, split by whether the
|
|
363
|
+
* call's result is consumed.
|
|
364
|
+
*/
|
|
365
|
+
export interface PureAnchors {
|
|
366
|
+
/**
|
|
367
|
+
* Calls whose result is bound — variable initializers, exports,
|
|
368
|
+
* assignment right-hand sides. Annotating these only lets the minifier
|
|
369
|
+
* drop the call when the binding itself is unused, so it is safe for
|
|
370
|
+
* any module.
|
|
371
|
+
*/
|
|
372
|
+
readonly bound: number[];
|
|
373
|
+
/**
|
|
374
|
+
* Top-level calls whose result is discarded (bare expression
|
|
375
|
+
* statements such as `app.get("/", handler)`). Annotating these tells
|
|
376
|
+
* the minifier the whole statement is deletable, so they are only safe
|
|
377
|
+
* in packages that explicitly declare `sideEffects: false` / `[]`.
|
|
378
|
+
*/
|
|
379
|
+
readonly discarded: number[];
|
|
380
|
+
}
|
|
381
|
+
|
|
347
382
|
/**
|
|
348
383
|
* Parses `code` and returns the offsets at which `/*#__PURE__*\/` must be
|
|
349
384
|
* inserted — before every top-level `CallExpression` callee / `new`
|
|
350
|
-
* keyword
|
|
351
|
-
*
|
|
385
|
+
* keyword — classified by whether the call result is bound or discarded.
|
|
386
|
+
* Returns `null` if the file does not need to be modified (parse failure
|
|
387
|
+
* or no annotations needed).
|
|
352
388
|
*/
|
|
353
389
|
export function collectPureAnchors(
|
|
354
390
|
code: string,
|
|
355
391
|
filename: string,
|
|
356
|
-
):
|
|
392
|
+
): PureAnchors | null {
|
|
357
393
|
let program: Program;
|
|
358
394
|
try {
|
|
359
395
|
// Use TS lang so the parser tolerates TS syntax (`as`, `satisfies`,
|
|
@@ -364,51 +400,66 @@ export function collectPureAnchors(
|
|
|
364
400
|
return null;
|
|
365
401
|
}
|
|
366
402
|
|
|
367
|
-
const
|
|
403
|
+
const bound: number[] = [];
|
|
404
|
+
const discarded: number[] = [];
|
|
368
405
|
|
|
369
|
-
const visitCall = (
|
|
406
|
+
const visitCall = (
|
|
407
|
+
call: CallExpression | NewExpression,
|
|
408
|
+
isDiscarded: boolean,
|
|
409
|
+
) => {
|
|
370
410
|
if (isIIFE(call)) return;
|
|
371
411
|
// For `new X()`, anchor BEFORE the `new` keyword so we get
|
|
372
412
|
// `/*#__PURE__*/ new X()` (matches babel-plugin-annotate-pure-calls).
|
|
373
413
|
const anchor =
|
|
374
414
|
call.type === "NewExpression" ? call.start : call.callee.start;
|
|
375
415
|
if (alreadyAnnotated(code, anchor)) return;
|
|
376
|
-
|
|
416
|
+
(isDiscarded ? discarded : bound).push(anchor);
|
|
377
417
|
};
|
|
378
418
|
|
|
379
|
-
const visitExpression = (
|
|
419
|
+
const visitExpression = (
|
|
420
|
+
expr: Expression | null | undefined,
|
|
421
|
+
isDiscarded: boolean,
|
|
422
|
+
) => {
|
|
380
423
|
if (!expr) return;
|
|
381
424
|
switch (expr.type) {
|
|
382
425
|
case "CallExpression":
|
|
383
426
|
case "NewExpression":
|
|
384
|
-
visitCall(expr);
|
|
427
|
+
visitCall(expr, isDiscarded);
|
|
385
428
|
return;
|
|
386
|
-
case "SequenceExpression":
|
|
387
|
-
|
|
429
|
+
case "SequenceExpression": {
|
|
430
|
+
// All but the last expression's results are always discarded;
|
|
431
|
+
// the last one takes the surrounding context.
|
|
432
|
+
const exprs = expr.expressions;
|
|
433
|
+
for (let i = 0; i < exprs.length; i++) {
|
|
434
|
+
visitExpression(exprs[i], i < exprs.length - 1 ? true : isDiscarded);
|
|
435
|
+
}
|
|
388
436
|
return;
|
|
437
|
+
}
|
|
389
438
|
case "ParenthesizedExpression":
|
|
390
|
-
visitExpression(expr.expression);
|
|
439
|
+
visitExpression(expr.expression, isDiscarded);
|
|
391
440
|
return;
|
|
392
441
|
case "LogicalExpression":
|
|
393
|
-
visitExpression(expr.left);
|
|
394
|
-
visitExpression(expr.right);
|
|
442
|
+
visitExpression(expr.left, isDiscarded);
|
|
443
|
+
visitExpression(expr.right, isDiscarded);
|
|
395
444
|
return;
|
|
396
445
|
case "ConditionalExpression":
|
|
397
|
-
visitExpression(expr.consequent);
|
|
398
|
-
visitExpression(expr.alternate);
|
|
446
|
+
visitExpression(expr.consequent, isDiscarded);
|
|
447
|
+
visitExpression(expr.alternate, isDiscarded);
|
|
399
448
|
return;
|
|
400
449
|
case "AssignmentExpression":
|
|
401
|
-
|
|
450
|
+
// The right-hand side's result is stored in the target, so it is
|
|
451
|
+
// bound even when the assignment appears in statement position.
|
|
452
|
+
visitExpression(expr.right, false);
|
|
402
453
|
return;
|
|
403
454
|
case "TSAsExpression":
|
|
404
455
|
case "TSSatisfiesExpression":
|
|
405
456
|
case "TSNonNullExpression":
|
|
406
457
|
case "TSTypeAssertion":
|
|
407
|
-
visitExpression(expr.expression);
|
|
458
|
+
visitExpression(expr.expression, isDiscarded);
|
|
408
459
|
return;
|
|
409
460
|
case "ChainExpression": {
|
|
410
461
|
const inner = expr.expression;
|
|
411
|
-
if (inner.type === "CallExpression") visitCall(inner);
|
|
462
|
+
if (inner.type === "CallExpression") visitCall(inner, isDiscarded);
|
|
412
463
|
return;
|
|
413
464
|
}
|
|
414
465
|
default:
|
|
@@ -419,11 +470,11 @@ export function collectPureAnchors(
|
|
|
419
470
|
const visitTopLevel = (node: Statement) => {
|
|
420
471
|
switch (node.type) {
|
|
421
472
|
case "ExpressionStatement":
|
|
422
|
-
visitExpression((node as ExpressionStatement).expression);
|
|
473
|
+
visitExpression((node as ExpressionStatement).expression, true);
|
|
423
474
|
return;
|
|
424
475
|
case "VariableDeclaration":
|
|
425
476
|
for (const decl of (node as VariableDeclaration).declarations) {
|
|
426
|
-
visitExpression(decl.init);
|
|
477
|
+
visitExpression(decl.init, false);
|
|
427
478
|
}
|
|
428
479
|
return;
|
|
429
480
|
case "ExportNamedDeclaration": {
|
|
@@ -439,7 +490,7 @@ export function collectPureAnchors(
|
|
|
439
490
|
decl.type !== "ClassDeclaration" &&
|
|
440
491
|
decl.type !== "TSInterfaceDeclaration"
|
|
441
492
|
) {
|
|
442
|
-
visitExpression(decl as Expression);
|
|
493
|
+
visitExpression(decl as Expression, false);
|
|
443
494
|
}
|
|
444
495
|
return;
|
|
445
496
|
}
|
|
@@ -455,7 +506,9 @@ export function collectPureAnchors(
|
|
|
455
506
|
visitTopLevel(node as Statement);
|
|
456
507
|
}
|
|
457
508
|
|
|
458
|
-
return
|
|
509
|
+
return bound.length === 0 && discarded.length === 0
|
|
510
|
+
? null
|
|
511
|
+
: { bound, discarded };
|
|
459
512
|
}
|
|
460
513
|
|
|
461
514
|
function isIIFE(node: CallExpression | NewExpression): boolean {
|