alchemy 0.0.8 → 0.1.2

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.
Files changed (105) hide show
  1. package/README.md +355 -0
  2. package/package.json +24 -20
  3. package/src/alchemize.ts +151 -0
  4. package/src/apply.ts +117 -0
  5. package/src/components/agent/agent.ts +45 -0
  6. package/src/components/agent/anthropic.ts +27 -0
  7. package/src/components/agent/index.ts +4 -0
  8. package/src/components/agent/model.ts +22 -0
  9. package/src/components/agent/openai.ts +44 -0
  10. package/src/components/agent/package.ts +126 -0
  11. package/src/components/agent/requirements.ts +111 -0
  12. package/src/components/agent/scrape.ts +47 -0
  13. package/src/components/agent/tsconfig.ts +150 -0
  14. package/src/components/agent/typescript.ts +177 -0
  15. package/src/components/aws/bucket.ts +168 -0
  16. package/src/components/aws/function.ts +302 -0
  17. package/src/components/aws/index.ts +6 -0
  18. package/src/components/aws/policy.ts +245 -0
  19. package/src/components/aws/queue.ts +222 -0
  20. package/src/components/aws/role.ts +245 -0
  21. package/src/components/aws/table.ts +172 -0
  22. package/src/components/esbuild.ts +128 -0
  23. package/src/components/fs.ts +45 -0
  24. package/src/destroy.ts +63 -0
  25. package/src/error.ts +16 -0
  26. package/src/global.ts +72 -0
  27. package/src/index.ts +6 -0
  28. package/src/input.ts +16 -0
  29. package/src/main.ts +34 -0
  30. package/src/output.ts +49 -0
  31. package/src/resource.ts +313 -0
  32. package/src/state.ts +100 -0
  33. package/test/aws/function.test.ts +144 -0
  34. package/test/aws/queue.test.ts +85 -0
  35. package/test/aws/role.test.ts +153 -0
  36. package/test/aws/table.test.ts +73 -0
  37. package/test/esbuild.test.ts +41 -0
  38. package/test/handler.ts +9 -0
  39. package/tsconfig.json +7 -0
  40. package/Column.js +0 -1
  41. package/Create.js +0 -1
  42. package/Delete.js +0 -1
  43. package/Drop.js +0 -1
  44. package/Insert.js +0 -1
  45. package/Makefile +0 -8
  46. package/Select.js +0 -1
  47. package/Table.js +0 -1
  48. package/Transaction.js +0 -1
  49. package/Types.js +0 -1
  50. package/Update.js +0 -1
  51. package/alchemy.js +0 -57
  52. package/alchemy.sublime-project +0 -9
  53. package/alchemy.sublime-workspace +0 -2304
  54. package/config/app.js +0 -8
  55. package/config/db.js +0 -20
  56. package/copy.js +0 -1
  57. package/defaultEngine.js +0 -8
  58. package/engine.js +0 -282
  59. package/getInstantiatorFunction.js +0 -28
  60. package/index.js +0 -52
  61. package/jstest.js +0 -3
  62. package/loggerFactory.js +0 -27
  63. package/logs/alchemy.log +0 -46
  64. package/logs/sql.log +0 -41
  65. package/sql/Column.js +0 -158
  66. package/sql/Columnable.js +0 -21
  67. package/sql/Create.js +0 -15
  68. package/sql/Delete.js +0 -34
  69. package/sql/Drop.js +0 -36
  70. package/sql/Insert.js +0 -96
  71. package/sql/SchemaElement.js +0 -11
  72. package/sql/Select.js +0 -192
  73. package/sql/SqlBase.js +0 -134
  74. package/sql/SqlStatement.js +0 -80
  75. package/sql/Table.js +0 -96
  76. package/sql/Transaction.js +0 -45
  77. package/sql/Update.js +0 -105
  78. package/sql/defineElement.js +0 -26
  79. package/sql/defineStatement.js +0 -26
  80. package/sql/types/BaseType.js +0 -43
  81. package/sql/types/BigSerialType.js +0 -5
  82. package/sql/types/IntegerType.js +0 -5
  83. package/sql/types/JsonType.js +0 -5
  84. package/sql/types/SerialType.js +0 -5
  85. package/sql/types/StringType.js +0 -31
  86. package/sql/types/TimestampType.js +0 -5
  87. package/sql/types/UUIDType.js +0 -5
  88. package/sql/types/defineType.js +0 -25
  89. package/sql/types/getType.js +0 -12
  90. package/sql/types/index.js +0 -11
  91. package/sql/where.js +0 -31
  92. package/sql/whereToString.js +0 -29
  93. package/test/ColumnTests.js +0 -80
  94. package/test/CreateTable.js +0 -54
  95. package/test/DeleteTests.js +0 -28
  96. package/test/DropTests.js +0 -23
  97. package/test/GetTypeTests.js +0 -56
  98. package/test/InsertTests.js +0 -86
  99. package/test/SelectExistsTests.js +0 -26
  100. package/test/SelectTests.js +0 -130
  101. package/test/TableTests.js +0 -46
  102. package/test.js +0 -8
  103. package/test.ls +0 -4
  104. package/validate.js +0 -26
  105. package/x.js +0 -5
@@ -0,0 +1,172 @@
1
+ import {
2
+ CreateTableCommand,
3
+ DeleteTableCommand,
4
+ DescribeTableCommand,
5
+ DynamoDBClient,
6
+ type KeySchemaElement,
7
+ ResourceInUseException,
8
+ ResourceNotFoundException,
9
+ } from "@aws-sdk/client-dynamodb";
10
+ import { ignore } from "../../error";
11
+ import { type Context, Resource } from "../../resource";
12
+
13
+ export interface TableProps {
14
+ tableName: string;
15
+ partitionKey: {
16
+ name: string;
17
+ type: "S" | "N" | "B";
18
+ };
19
+ sortKey?: {
20
+ name: string;
21
+ type: "S" | "N" | "B";
22
+ };
23
+ billingMode?: "PROVISIONED" | "PAY_PER_REQUEST";
24
+ readCapacity?: number;
25
+ writeCapacity?: number;
26
+ tags?: Record<string, string>;
27
+ }
28
+
29
+ export interface TableOutput extends TableProps {
30
+ id: string; // Same as tableName
31
+ arn: string;
32
+ streamArn?: string;
33
+ tableId: string;
34
+ }
35
+
36
+ export class Table extends Resource(
37
+ "dynamo::Table",
38
+ async (
39
+ ctx: Context<TableOutput>,
40
+ props: TableProps,
41
+ ): Promise<TableOutput> => {
42
+ const client = new DynamoDBClient({});
43
+
44
+ if (ctx.event === "delete") {
45
+ await ignore(ResourceNotFoundException.name, () =>
46
+ client.send(
47
+ new DeleteTableCommand({
48
+ TableName: props.tableName,
49
+ }),
50
+ ),
51
+ );
52
+
53
+ // Wait for table to be deleted
54
+ let tableDeleted = false;
55
+ while (!tableDeleted) {
56
+ try {
57
+ await client.send(
58
+ new DescribeTableCommand({
59
+ TableName: props.tableName,
60
+ }),
61
+ );
62
+ // If we get here, table still exists
63
+ await new Promise((resolve) => setTimeout(resolve, 1000));
64
+ } catch (error) {
65
+ if (error instanceof ResourceNotFoundException) {
66
+ tableDeleted = true;
67
+ } else {
68
+ throw error;
69
+ }
70
+ }
71
+ }
72
+
73
+ return {
74
+ ...props,
75
+ id: props.tableName,
76
+ arn: `arn:aws:dynamodb:${client.config.region}:${process.env.AWS_ACCOUNT_ID}:table/${props.tableName}`,
77
+ tableId: "",
78
+ };
79
+ } else {
80
+ try {
81
+ const attributeDefinitions = [
82
+ {
83
+ AttributeName: props.partitionKey.name,
84
+ AttributeType: props.partitionKey.type,
85
+ },
86
+ ];
87
+
88
+ const keySchema: KeySchemaElement[] = [
89
+ {
90
+ AttributeName: props.partitionKey.name,
91
+ KeyType: "HASH",
92
+ },
93
+ ];
94
+
95
+ if (props.sortKey) {
96
+ attributeDefinitions.push({
97
+ AttributeName: props.sortKey.name,
98
+ AttributeType: props.sortKey.type,
99
+ });
100
+ keySchema.push({
101
+ AttributeName: props.sortKey.name,
102
+ KeyType: "RANGE",
103
+ });
104
+ }
105
+
106
+ await client.send(
107
+ new CreateTableCommand({
108
+ TableName: props.tableName,
109
+ AttributeDefinitions: attributeDefinitions,
110
+ KeySchema: keySchema,
111
+ BillingMode: props.billingMode || "PAY_PER_REQUEST",
112
+ ProvisionedThroughput:
113
+ props.billingMode === "PROVISIONED"
114
+ ? {
115
+ ReadCapacityUnits: props.readCapacity || 5,
116
+ WriteCapacityUnits: props.writeCapacity || 5,
117
+ }
118
+ : undefined,
119
+ Tags: props.tags
120
+ ? Object.entries(props.tags).map(([Key, Value]) => ({
121
+ Key,
122
+ Value,
123
+ }))
124
+ : undefined,
125
+ }),
126
+ );
127
+
128
+ // Wait for table to be active
129
+ let tableActive = false;
130
+ let tableDescription;
131
+ while (!tableActive) {
132
+ const response = await client.send(
133
+ new DescribeTableCommand({
134
+ TableName: props.tableName,
135
+ }),
136
+ );
137
+ tableActive = response.Table?.TableStatus === "ACTIVE";
138
+ if (tableActive) {
139
+ tableDescription = response.Table;
140
+ } else {
141
+ await new Promise((resolve) => setTimeout(resolve, 1000));
142
+ }
143
+ }
144
+
145
+ return {
146
+ ...props,
147
+ id: props.tableName,
148
+ arn: tableDescription!.TableArn!,
149
+ streamArn: tableDescription!.LatestStreamArn,
150
+ tableId: tableDescription!.TableId!,
151
+ };
152
+ } catch (error) {
153
+ if (error instanceof ResourceInUseException) {
154
+ // Table already exists, get its details
155
+ const response = await client.send(
156
+ new DescribeTableCommand({
157
+ TableName: props.tableName,
158
+ }),
159
+ );
160
+ return {
161
+ ...props,
162
+ id: props.tableName,
163
+ arn: response.Table!.TableArn!,
164
+ streamArn: response.Table!.LatestStreamArn,
165
+ tableId: response.Table!.TableId!,
166
+ };
167
+ }
168
+ throw error;
169
+ }
170
+ }
171
+ },
172
+ ) {}
@@ -0,0 +1,128 @@
1
+ import * as esbuild from "esbuild";
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { Resource } from "../resource";
6
+
7
+ export interface BundleProps {
8
+ /**
9
+ * Entry point for the bundle
10
+ */
11
+ entryPoint: string;
12
+ /**
13
+ * Output directory for the bundle
14
+ */
15
+ outdir?: string;
16
+ /**
17
+ * Output filename for the bundle
18
+ */
19
+ outfile?: string;
20
+ /**
21
+ * Bundle format (iife, cjs, esm)
22
+ */
23
+ format?: "iife" | "cjs" | "esm";
24
+ /**
25
+ * Target environment (e.g., 'node16', 'es2020')
26
+ */
27
+ target?: string | string[];
28
+ /**
29
+ * Whether to minify the output
30
+ */
31
+ minify?: boolean;
32
+ /**
33
+ * Whether to generate sourcemaps
34
+ */
35
+ sourcemap?: boolean | "inline" | "external" | "both";
36
+ /**
37
+ * External packages to exclude from bundle
38
+ */
39
+ external?: string[];
40
+ /**
41
+ * Platform to target (browser, node, neutral)
42
+ */
43
+ platform?: "browser" | "node" | "neutral";
44
+ /**
45
+ * Additional esbuild options
46
+ */
47
+ options?: Partial<esbuild.BuildOptions>;
48
+ }
49
+
50
+ export interface BundleOutput {
51
+ /**
52
+ * Path to the bundled file
53
+ */
54
+ path: string;
55
+ /**
56
+ * SHA-256 hash of the bundle contents
57
+ */
58
+ hash: string;
59
+ }
60
+
61
+ export class Bundle extends Resource(
62
+ "esbuild::Bundle",
63
+ async (ctx, props: BundleProps): Promise<BundleOutput> => {
64
+ // Determine output path
65
+ const outputPath =
66
+ props.outfile ||
67
+ path.join(
68
+ props.outdir || "dist",
69
+ path.basename(props.entryPoint, path.extname(props.entryPoint)) + ".js",
70
+ );
71
+
72
+ // Ensure output directory exists
73
+ await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
74
+
75
+ if (ctx.event === "delete") {
76
+ await fs.promises.unlink(outputPath).catch(() => {});
77
+ // Also clean up sourcemap if it exists
78
+ await fs.promises.unlink(outputPath + ".map").catch(() => {});
79
+ return { path: outputPath, hash: "" };
80
+ }
81
+
82
+ // Build the bundle
83
+ const result = await esbuild.build({
84
+ entryPoints: [props.entryPoint],
85
+ outfile: outputPath,
86
+ bundle: true,
87
+ format: props.format,
88
+ target: props.target,
89
+ minify: props.minify,
90
+ sourcemap: props.sourcemap,
91
+ external: props.external,
92
+ platform: props.platform,
93
+ metafile: true,
94
+ write: true,
95
+ ...props.options,
96
+ });
97
+
98
+ // Calculate hash of the output
99
+ const contents = await fs.promises.readFile(outputPath);
100
+ const hash = crypto.createHash("sha256").update(contents).digest("hex");
101
+
102
+ // Store metadata in context
103
+ await ctx.set("metafile", result.metafile);
104
+ await ctx.set("hash", hash);
105
+
106
+ // Return output info
107
+ return {
108
+ path: outputPath,
109
+ hash,
110
+ };
111
+ },
112
+ ) {
113
+ /**
114
+ * Get the metafile from the last build
115
+ */
116
+ public async getMetafile(ctx: {
117
+ get<T>(key: string): Promise<T | undefined>;
118
+ }) {
119
+ return ctx.get<esbuild.Metafile>("metafile");
120
+ }
121
+
122
+ /**
123
+ * Get the hash from the last build
124
+ */
125
+ public async getHash(ctx: { get<T>(key: string): Promise<T | undefined> }) {
126
+ return ctx.get<string>("hash");
127
+ }
128
+ }
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { ignore } from "../error";
4
+ import { Resource } from "../resource";
5
+
6
+ export class File extends Resource(
7
+ "fs::File",
8
+ async (ctx, filePath: string, content: string) => {
9
+ if (ctx.event === "delete") {
10
+ await ignore("ENOENT", () => fs.promises.unlink(filePath));
11
+ } else {
12
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
13
+ await fs.promises.writeFile(filePath, content);
14
+ }
15
+ return filePath;
16
+ },
17
+ ) {}
18
+
19
+ export class Folder extends Resource(
20
+ "fs::Folder",
21
+ async (ctx, dirPath: string) => {
22
+ if (ctx.event === "delete") {
23
+ if (await ctx.get("isOwned")) {
24
+ // only delete the folder if we own it (we created it)
25
+ await ignore("ENOENT", () => fs.promises.rmdir(dirPath));
26
+ }
27
+ } else {
28
+ try {
29
+ await fs.promises.mkdir(dirPath, { recursive: true });
30
+ } catch (error: any) {
31
+ if (error.code === "EEXIST") {
32
+ // record that we own the directory
33
+ await ctx.set("isOwned", true);
34
+ } else {
35
+ throw error;
36
+ }
37
+ }
38
+ }
39
+ return dirPath;
40
+ },
41
+ ) {
42
+ public file(filePath: string, content: string) {
43
+ return new File(path.basename(filePath), filePath, content);
44
+ }
45
+ }
package/src/destroy.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { defaultStage, stateStore } from "./global";
2
+ import type { Output } from "./output";
3
+ import { Provider, ResourceID, isResource } from "./resource";
4
+ import type { State } from "./state";
5
+
6
+ interface DestroyOptions {
7
+ stage?: string;
8
+ }
9
+
10
+ /**
11
+ * Prune all resources from an Output and "down", i.e. that branches from it.
12
+ */
13
+ export async function destroy<T>(
14
+ output: Output<T>,
15
+ options?: DestroyOptions,
16
+ ): Promise<void>;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ export async function destroy<T>(
22
+ stage: string,
23
+ resourceID: ResourceID,
24
+ resourceState: State,
25
+ resourceProvider: Provider,
26
+ ): Promise<void>;
27
+
28
+ export async function destroy<T>(
29
+ ...args: [Output<T>, DestroyOptions?] | [string, ResourceID, State, Provider]
30
+ ): Promise<void> {
31
+ let resourceID: ResourceID;
32
+ let resourceState: State;
33
+ let resourceProvider: Provider;
34
+ let stage: string;
35
+
36
+ if (args.length === 4) {
37
+ stage = args[0];
38
+ resourceID = args[1];
39
+ resourceState = args[2];
40
+ resourceProvider = args[3];
41
+ } else if (isResource(args[0])) {
42
+ const resource = args[0];
43
+ stage = args[1]?.stage ?? defaultStage;
44
+ resourceID = resource[ResourceID];
45
+ // First destroy all dependencies
46
+ const _resourceState = await stateStore.get(stage, resourceID);
47
+ if (_resourceState === undefined) {
48
+ // we have no record of this resource, we must assume it's already deleted
49
+ return;
50
+ }
51
+ resourceState = _resourceState;
52
+ resourceProvider = resource[Provider];
53
+ } else {
54
+ throw new Error("Not implemented: must handle destroy a Output chain");
55
+ }
56
+ await resourceProvider.delete(
57
+ stage,
58
+ resourceID,
59
+ resourceState,
60
+ resourceState.inputs as [],
61
+ );
62
+ await stateStore.delete(stage, resourceID);
63
+ }
package/src/error.ts ADDED
@@ -0,0 +1,16 @@
1
+ export async function ignore<T>(
2
+ codes: string | string[],
3
+ fn: () => Promise<T>,
4
+ ): Promise<T | undefined> {
5
+ try {
6
+ return await fn();
7
+ } catch (error: any) {
8
+ const errorCode = error.code || error.name;
9
+ if (
10
+ Array.isArray(codes) ? codes.includes(errorCode) : errorCode === codes
11
+ ) {
12
+ return undefined;
13
+ }
14
+ throw error;
15
+ }
16
+ }
package/src/global.ts ADDED
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { Provider, Resource, ResourceID, ResourceType } from "./resource";
4
+ import { defaultStore, type StateStore } from "./state";
5
+
6
+ export interface Config {
7
+ defaultStage?: string;
8
+ stateStore?: StateStore;
9
+ }
10
+
11
+ const alchemy = path.resolve(process.cwd(), "alchemy.ts");
12
+
13
+ let _config: Config | undefined;
14
+
15
+ const exists =
16
+ fs.promises.exists ??
17
+ async function exists(path: string): Promise<boolean> {
18
+ try {
19
+ await fs.promises.access(path);
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ };
25
+
26
+ if (await exists(alchemy)) {
27
+ const mod = await import(alchemy);
28
+ if (mod) {
29
+ const config = mod.default as Config | undefined;
30
+ if (config) {
31
+ if (typeof config !== "object") {
32
+ throw new Error("Config is not an object");
33
+ }
34
+ if (typeof config.defaultStage !== "string") {
35
+ throw new Error("Config.stage is not a string");
36
+ }
37
+ _config = config;
38
+ }
39
+ }
40
+ }
41
+
42
+ const _stage = process.env.ALCHEMY_STAGE ?? process.env.USER ?? "dev";
43
+
44
+ export const config: Config = _config ?? {
45
+ defaultStage: _stage,
46
+ stateStore: defaultStore,
47
+ };
48
+
49
+ export const defaultStage = config.defaultStage ?? _stage;
50
+
51
+ export const stateStore = config.stateStore ?? defaultStore;
52
+
53
+ export const nodes = new Map<
54
+ ResourceID,
55
+ {
56
+ provider: Provider<any, any[], any>;
57
+ resource: Resource<any, any>;
58
+ }
59
+ >();
60
+
61
+ export const providers = new Map<
62
+ ResourceType,
63
+ Provider<ResourceType, any[], any>
64
+ >();
65
+
66
+ export const deletions: {
67
+ id: string;
68
+ data: Record<string, any>;
69
+ inputs: any[];
70
+ }[] = [];
71
+
72
+ await stateStore.init?.();
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./alchemize";
2
+ export * from "./global";
3
+ export * from "./output";
4
+ export * from "./resource";
5
+
6
+ export type { StateStore } from "./state";
package/src/input.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { Output } from "./output";
2
+
3
+ export type Inputs<P extends any[]> = P extends [infer First, ...infer Rest]
4
+ ? [Input<First>, ...Inputs<Rest>]
5
+ : [];
6
+
7
+ export type Input<T> =
8
+ | T
9
+ | Output<T>
10
+ | (T extends any[] ? Inputs<T> : never)
11
+ | (T extends string | number | boolean ? Output<T> : never)
12
+ | (T extends object
13
+ ? {
14
+ [k in keyof T]: Input<T[k]>;
15
+ }
16
+ : never);
package/src/main.ts ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bun
2
+ import { parseArgs } from "util";
3
+
4
+ const { values, positionals } = parseArgs({
5
+ args: Bun.argv.slice(2),
6
+ options: {
7
+ help: {
8
+ type: "boolean",
9
+ short: "h",
10
+ },
11
+ },
12
+ strict: true,
13
+ allowPositionals: true,
14
+ });
15
+
16
+ if (values.help || positionals.length === 0) {
17
+ console.error("Usage: iac <command> [args]");
18
+ console.error("\nCommands:");
19
+ console.error(" destroy [stack names] Delete the specified stacks");
20
+ process.exit(1);
21
+ }
22
+
23
+ const [command, ...restArgs] = positionals;
24
+
25
+ if (command.toLowerCase() === "destroy") {
26
+ if (restArgs.length === 0) {
27
+ console.error("Error: Please specify at least one stack name to destroy");
28
+ process.exit(1);
29
+ }
30
+ console.log("Deleting stacks:", restArgs);
31
+ } else {
32
+ console.error(`Unknown command: ${command}`);
33
+ process.exit(1);
34
+ }
package/src/output.ts ADDED
@@ -0,0 +1,49 @@
1
+ export type Eval<U> = U extends Output<infer V> ? Eval<V> : U;
2
+
3
+ export type Outputs<P extends readonly any[]> = P extends [
4
+ infer First,
5
+ ...infer Rest,
6
+ ]
7
+ ? [Output<First>, ...Outputs<Rest>]
8
+ : [];
9
+
10
+ export type Output<T> = {
11
+ apply<U>(fn: (value: T) => U): Output<Eval<U>>;
12
+ } & (T extends string
13
+ ? {
14
+ [k in keyof typeof String.prototype]: Output<
15
+ (typeof String.prototype)[k]
16
+ >;
17
+ }
18
+ : T extends number
19
+ ? {
20
+ [K in keyof typeof Number.prototype]: Output<
21
+ (typeof Number.prototype)[K]
22
+ >;
23
+ }
24
+ : T extends boolean
25
+ ? {
26
+ [K in keyof typeof Boolean.prototype]: Output<
27
+ (typeof Boolean.prototype)[K]
28
+ >;
29
+ }
30
+ : T extends any[]
31
+ ? Outputs<T>
32
+ : {
33
+ [k in keyof T]: Output<T[k]>;
34
+ });
35
+
36
+ export const Output = class<T, U> {
37
+ constructor(
38
+ public readonly parent: Output<T>,
39
+ public readonly fn: (value: T) => U,
40
+ ) {}
41
+
42
+ public apply<V>(fn: (value: U) => V): Output<V> {
43
+ // @ts-expect-error - we know we are an "Output"
44
+ return new Output(this, fn);
45
+ }
46
+ } as new <T, U>(
47
+ parent: Output<T>,
48
+ fn: (value: T) => U,
49
+ ) => Output<U>;