@docker-harpoon/core 0.1.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.
Files changed (61) hide show
  1. package/dist/api/index.d.ts +7 -0
  2. package/dist/api/index.d.ts.map +1 -0
  3. package/dist/api/index.js +10 -0
  4. package/dist/api/promise.d.ts +179 -0
  5. package/dist/api/promise.d.ts.map +1 -0
  6. package/dist/api/promise.js +282 -0
  7. package/dist/bindings/index.d.ts +8 -0
  8. package/dist/bindings/index.d.ts.map +1 -0
  9. package/dist/bindings/index.js +6 -0
  10. package/dist/bindings/types.d.ts +116 -0
  11. package/dist/bindings/types.d.ts.map +1 -0
  12. package/dist/bindings/types.js +46 -0
  13. package/dist/build-strategies/index.d.ts +30 -0
  14. package/dist/build-strategies/index.d.ts.map +1 -0
  15. package/dist/build-strategies/index.js +47 -0
  16. package/dist/build-strategies/standard.d.ts +16 -0
  17. package/dist/build-strategies/standard.d.ts.map +1 -0
  18. package/dist/build-strategies/standard.js +39 -0
  19. package/dist/build-strategies/types.d.ts +81 -0
  20. package/dist/build-strategies/types.d.ts.map +1 -0
  21. package/dist/build-strategies/types.js +25 -0
  22. package/dist/config-patchers/index.d.ts +33 -0
  23. package/dist/config-patchers/index.d.ts.map +1 -0
  24. package/dist/config-patchers/index.js +75 -0
  25. package/dist/config-patchers/types.d.ts +89 -0
  26. package/dist/config-patchers/types.d.ts.map +1 -0
  27. package/dist/config-patchers/types.js +25 -0
  28. package/dist/dockerfile-transformers/core.d.ts +59 -0
  29. package/dist/dockerfile-transformers/core.d.ts.map +1 -0
  30. package/dist/dockerfile-transformers/core.js +271 -0
  31. package/dist/dockerfile-transformers/index.d.ts +42 -0
  32. package/dist/dockerfile-transformers/index.d.ts.map +1 -0
  33. package/dist/dockerfile-transformers/index.js +67 -0
  34. package/dist/dockerfile-transformers/types.d.ts +116 -0
  35. package/dist/dockerfile-transformers/types.d.ts.map +1 -0
  36. package/dist/dockerfile-transformers/types.js +29 -0
  37. package/dist/errors.d.ts +75 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +119 -0
  40. package/dist/helpers/database.d.ts +54 -0
  41. package/dist/helpers/database.d.ts.map +1 -0
  42. package/dist/helpers/database.js +108 -0
  43. package/dist/helpers/index.d.ts +8 -0
  44. package/dist/helpers/index.d.ts.map +1 -0
  45. package/dist/helpers/index.js +6 -0
  46. package/dist/index.d.ts +41 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +44 -0
  49. package/dist/resources/container.d.ts +42 -0
  50. package/dist/resources/container.d.ts.map +1 -0
  51. package/dist/resources/container.js +256 -0
  52. package/dist/resources/image.d.ts +37 -0
  53. package/dist/resources/image.d.ts.map +1 -0
  54. package/dist/resources/image.js +113 -0
  55. package/dist/resources/index.d.ts +12 -0
  56. package/dist/resources/index.d.ts.map +1 -0
  57. package/dist/resources/index.js +8 -0
  58. package/dist/resources/network.d.ts +21 -0
  59. package/dist/resources/network.d.ts.map +1 -0
  60. package/dist/resources/network.js +86 -0
  61. package/package.json +28 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Public API Module
3
+ *
4
+ * Re-exports the Promise-based API for cleaner imports.
5
+ */
6
+ export { Network, Container, database, Image, setDocker, resetDocker, destroyAll, type NetworkConfig, type NetworkResource, type ContainerConfig, type ContainerResource, type PortMapping, type ShutdownMetadata, type DatabaseConfig, type DatabaseResource, type ImageConfig, type ImageResource, } from './promise';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAEL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,KAAK,EAGL,SAAS,EACT,WAAW,EACX,UAAU,EAGV,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public API Module
3
+ *
4
+ * Re-exports the Promise-based API for cleaner imports.
5
+ */
6
+ export {
7
+ // Resource constructors
8
+ Network, Container, database, Image,
9
+ // Docker client management
10
+ setDocker, resetDocker, destroyAll, } from './promise';
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Promise-based API Layer
3
+ *
4
+ * This module wraps the Effect-based resources with a simple Promise API.
5
+ * Effect is completely hidden from users - they just use async/await.
6
+ *
7
+ * Inspired by Alchemy.run and Pulumi's IAC patterns.
8
+ */
9
+ import Docker from 'dockerode';
10
+ import type { Binding } from '../bindings/types';
11
+ export interface NetworkConfig {
12
+ /** Network driver (default: 'bridge') */
13
+ driver?: string;
14
+ }
15
+ export interface NetworkResource {
16
+ readonly id: string;
17
+ readonly name: string;
18
+ /** Remove this network */
19
+ destroy(): Promise<void>;
20
+ }
21
+ export interface PortMapping {
22
+ internal: number;
23
+ external: number;
24
+ }
25
+ export interface ShutdownMetadata {
26
+ signalCount: number;
27
+ graceful: boolean;
28
+ timeTakenMs: number;
29
+ signal: string;
30
+ timeoutMs: number;
31
+ }
32
+ export interface ContainerConfig {
33
+ image: string;
34
+ networks?: Array<NetworkResource | {
35
+ name: string;
36
+ }>;
37
+ ports?: PortMapping[];
38
+ env?: Record<string, string>;
39
+ cmd?: string[];
40
+ /** Bindings that provide env vars and lifecycle hooks */
41
+ bindings?: Record<string, Binding>;
42
+ }
43
+ export interface ContainerResource {
44
+ readonly id: string;
45
+ readonly name: string;
46
+ /** Wait for a log pattern to appear */
47
+ waitForLog(pattern: string | RegExp, timeoutMs?: number): Promise<void>;
48
+ /** Stop container gracefully */
49
+ stop(signal?: string, timeoutMs?: number): Promise<ShutdownMetadata>;
50
+ /** Get container IP in a network */
51
+ getIp(networkName: string): Promise<string>;
52
+ /** Remove this container */
53
+ destroy(): Promise<void>;
54
+ }
55
+ export interface DatabaseConfig {
56
+ /** Docker image (e.g., "postgres:15", "mysql:8", "redis:7") */
57
+ image: string;
58
+ /** Override default port for this database type */
59
+ port?: number;
60
+ /** Host port to expose (defaults to same as internal port) */
61
+ hostPort?: number;
62
+ /** Environment variables (e.g., { POSTGRES_PASSWORD: 'secret' }) */
63
+ env?: Record<string, string>;
64
+ /** Networks to connect to */
65
+ networks?: Array<NetworkResource | {
66
+ name: string;
67
+ }>;
68
+ /** Bindings for this database */
69
+ bindings?: Record<string, Binding>;
70
+ }
71
+ export interface DatabaseResource extends ContainerResource {
72
+ /** Connection string for this database */
73
+ readonly connectionString: string;
74
+ /** Internal port the database is listening on */
75
+ readonly port: number;
76
+ /** Host port mapped to the database */
77
+ readonly hostPort: number;
78
+ }
79
+ export interface ImageConfig {
80
+ /** Path to the build context directory */
81
+ context: string;
82
+ /** Path to Dockerfile relative to context */
83
+ dockerfile?: string;
84
+ /** Docker build arguments */
85
+ buildArgs?: Record<string, string>;
86
+ /** Build strategy to use (default: 'standard') */
87
+ buildStrategy?: string;
88
+ /** App name for monorepo strategies */
89
+ monorepoAppName?: string;
90
+ /** Source directory for shared monorepo folders */
91
+ monorepoSource?: string;
92
+ }
93
+ export interface ImageResource {
94
+ readonly tag: string;
95
+ readonly ref: string;
96
+ }
97
+ /**
98
+ * Set a custom Docker client.
99
+ * Useful for testing or custom configurations.
100
+ */
101
+ export declare function setDocker(docker: Docker): void;
102
+ /**
103
+ * Reset Docker client (mainly for testing).
104
+ */
105
+ export declare function resetDocker(): void;
106
+ /**
107
+ * Create a Docker network.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const network = await Network("my-app-net");
112
+ * // ... use network
113
+ * await network.destroy();
114
+ * ```
115
+ */
116
+ export declare function Network(name: string, config?: NetworkConfig): Promise<NetworkResource>;
117
+ /**
118
+ * Create a Docker container.
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const container = await Container("my-app", {
123
+ * image: "node:20-alpine",
124
+ * cmd: ["node", "server.js"],
125
+ * ports: [{ internal: 3000, external: 3000 }],
126
+ * });
127
+ *
128
+ * await container.waitForLog(/listening on/);
129
+ * // ... use container
130
+ * await container.destroy();
131
+ * ```
132
+ */
133
+ export declare function Container(name: string, config: ContainerConfig): Promise<ContainerResource>;
134
+ /**
135
+ * Create a database container with sensible defaults.
136
+ *
137
+ * Automatically infers database type from image name and provides:
138
+ * - Default port mappings
139
+ * - Connection string generation
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const db = await database("my-postgres", {
144
+ * image: "postgres:16-alpine",
145
+ * env: { POSTGRES_PASSWORD: "secret", POSTGRES_DB: "mydb" },
146
+ * });
147
+ *
148
+ * console.log(db.connectionString);
149
+ * // postgresql://postgres:secret@localhost:5432/mydb
150
+ *
151
+ * await db.waitForLog(/ready to accept connections/);
152
+ * // ... use database
153
+ * await db.destroy();
154
+ * ```
155
+ */
156
+ export declare function database(name: string, config: DatabaseConfig): Promise<DatabaseResource>;
157
+ /**
158
+ * Build a Docker image.
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * const image = await Image("my-app:latest", {
163
+ * context: "./",
164
+ * dockerfile: "Dockerfile",
165
+ * });
166
+ *
167
+ * const container = await Container("app", {
168
+ * image: image.tag,
169
+ * // ...
170
+ * });
171
+ * ```
172
+ */
173
+ export declare function Image(tag: string, config: ImageConfig): Promise<ImageResource>;
174
+ /**
175
+ * Clean up all tracked resources.
176
+ * Called automatically on process exit, but can be called manually.
177
+ */
178
+ export declare function destroyAll(): Promise<void>;
179
+ //# sourceMappingURL=promise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Promise-based API Layer
3
+ *
4
+ * This module wraps the Effect-based resources with a simple Promise API.
5
+ * Effect is completely hidden from users - they just use async/await.
6
+ *
7
+ * Inspired by Alchemy.run and Pulumi's IAC patterns.
8
+ */
9
+ import { Effect, Scope, Exit } from 'effect';
10
+ import { homedir } from 'os';
11
+ import { existsSync } from 'fs';
12
+ import Docker from 'dockerode';
13
+ // Import Effect-based resources (internal only)
14
+ import { Network as EffectNetwork, Container as EffectContainer, Image as EffectImage, } from '../resources';
15
+ import { database as effectDatabase, } from '../helpers';
16
+ // Binding type is used as-is from internal types
17
+ // Pre-built bindings like PrismaBinding handle Effect internally
18
+ // Users creating simple bindings can use createEnvBinding()
19
+ // ============ Docker Client Management ============
20
+ let dockerClient = null;
21
+ /**
22
+ * Detect Docker socket path for the current platform.
23
+ */
24
+ function detectDockerSocket() {
25
+ const candidates = [
26
+ '/var/run/docker.sock',
27
+ `${homedir()}/.docker/run/docker.sock`,
28
+ ];
29
+ for (const path of candidates) {
30
+ if (existsSync(path)) {
31
+ return path;
32
+ }
33
+ }
34
+ // Windows uses named pipes, let dockerode handle it
35
+ return '/var/run/docker.sock';
36
+ }
37
+ /**
38
+ * Get the Docker client, auto-detecting the socket if needed.
39
+ */
40
+ function getDocker() {
41
+ if (!dockerClient) {
42
+ const socketPath = detectDockerSocket();
43
+ dockerClient = new Docker({ socketPath });
44
+ }
45
+ return dockerClient;
46
+ }
47
+ /**
48
+ * Set a custom Docker client.
49
+ * Useful for testing or custom configurations.
50
+ */
51
+ export function setDocker(docker) {
52
+ dockerClient = docker;
53
+ }
54
+ /**
55
+ * Reset Docker client (mainly for testing).
56
+ */
57
+ export function resetDocker() {
58
+ dockerClient = null;
59
+ }
60
+ const resources = new Map();
61
+ /**
62
+ * Register cleanup on process exit.
63
+ */
64
+ let cleanupRegistered = false;
65
+ function registerCleanup() {
66
+ if (cleanupRegistered)
67
+ return;
68
+ cleanupRegistered = true;
69
+ const cleanup = async () => {
70
+ for (const [id, resource] of resources) {
71
+ try {
72
+ await resource.cleanup();
73
+ }
74
+ catch (e) {
75
+ console.error(`[Cleanup] Error cleaning up ${id}:`, e);
76
+ }
77
+ }
78
+ resources.clear();
79
+ };
80
+ process.on('beforeExit', cleanup);
81
+ process.on('SIGINT', async () => {
82
+ await cleanup();
83
+ process.exit(0);
84
+ });
85
+ process.on('SIGTERM', async () => {
86
+ await cleanup();
87
+ process.exit(0);
88
+ });
89
+ }
90
+ // ============ Promise-based Resource Constructors ============
91
+ /**
92
+ * Create a Docker network.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const network = await Network("my-app-net");
97
+ * // ... use network
98
+ * await network.destroy();
99
+ * ```
100
+ */
101
+ export async function Network(name, config = {}) {
102
+ registerCleanup();
103
+ const docker = getDocker();
104
+ const scope = Effect.runSync(Scope.make());
105
+ const effectConfig = {
106
+ name,
107
+ ...(config.driver !== undefined && { driver: config.driver }),
108
+ };
109
+ const effectResource = await Effect.runPromise(EffectNetwork(name, effectConfig, docker).pipe(Scope.extend(scope)));
110
+ const resourceId = `network:${effectResource.id}`;
111
+ const cleanup = async () => {
112
+ await Effect.runPromise(Scope.close(scope, Exit.void));
113
+ };
114
+ resources.set(resourceId, { scope, cleanup });
115
+ return {
116
+ id: effectResource.id,
117
+ name: effectResource.name,
118
+ async destroy() {
119
+ await cleanup();
120
+ resources.delete(resourceId);
121
+ },
122
+ };
123
+ }
124
+ /**
125
+ * Create a Docker container.
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const container = await Container("my-app", {
130
+ * image: "node:20-alpine",
131
+ * cmd: ["node", "server.js"],
132
+ * ports: [{ internal: 3000, external: 3000 }],
133
+ * });
134
+ *
135
+ * await container.waitForLog(/listening on/);
136
+ * // ... use container
137
+ * await container.destroy();
138
+ * ```
139
+ */
140
+ export async function Container(name, config) {
141
+ registerCleanup();
142
+ const docker = getDocker();
143
+ const scope = Effect.runSync(Scope.make());
144
+ // Convert NetworkResource to { name: string }
145
+ const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
146
+ const effectConfig = {
147
+ image: config.image,
148
+ ...(networks !== undefined && { networks }),
149
+ ...(config.ports !== undefined && { ports: config.ports }),
150
+ ...(config.env !== undefined && { env: config.env }),
151
+ ...(config.cmd !== undefined && { cmd: config.cmd }),
152
+ ...(config.bindings !== undefined && { bindings: config.bindings }),
153
+ };
154
+ const effectResource = await Effect.runPromise(EffectContainer(name, effectConfig, docker).pipe(Scope.extend(scope)));
155
+ const resourceId = `container:${effectResource.id}`;
156
+ const cleanup = async () => {
157
+ await Effect.runPromise(Scope.close(scope, Exit.void));
158
+ };
159
+ resources.set(resourceId, { scope, cleanup });
160
+ return {
161
+ id: effectResource.id,
162
+ name: effectResource.name,
163
+ async waitForLog(pattern, timeoutMs = 30000) {
164
+ await Effect.runPromise(effectResource.waitForLog(pattern, timeoutMs));
165
+ },
166
+ async stop(signal = 'SIGTERM', timeoutMs = 10000) {
167
+ return Effect.runPromise(effectResource.stopGracefully(signal, timeoutMs));
168
+ },
169
+ async getIp(networkName) {
170
+ return Effect.runPromise(effectResource.getIp(networkName));
171
+ },
172
+ async destroy() {
173
+ await cleanup();
174
+ resources.delete(resourceId);
175
+ },
176
+ };
177
+ }
178
+ /**
179
+ * Create a database container with sensible defaults.
180
+ *
181
+ * Automatically infers database type from image name and provides:
182
+ * - Default port mappings
183
+ * - Connection string generation
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * const db = await database("my-postgres", {
188
+ * image: "postgres:16-alpine",
189
+ * env: { POSTGRES_PASSWORD: "secret", POSTGRES_DB: "mydb" },
190
+ * });
191
+ *
192
+ * console.log(db.connectionString);
193
+ * // postgresql://postgres:secret@localhost:5432/mydb
194
+ *
195
+ * await db.waitForLog(/ready to accept connections/);
196
+ * // ... use database
197
+ * await db.destroy();
198
+ * ```
199
+ */
200
+ export async function database(name, config) {
201
+ registerCleanup();
202
+ const docker = getDocker();
203
+ const scope = Effect.runSync(Scope.make());
204
+ // Convert NetworkResource to { name: string }
205
+ const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
206
+ const effectConfig = {
207
+ image: config.image,
208
+ ...(config.port !== undefined && { port: config.port }),
209
+ ...(config.hostPort !== undefined && { hostPort: config.hostPort }),
210
+ ...(config.env !== undefined && { env: config.env }),
211
+ ...(networks !== undefined && { networks }),
212
+ ...(config.bindings !== undefined && { bindings: config.bindings }),
213
+ };
214
+ const effectResource = await Effect.runPromise(effectDatabase(name, effectConfig, docker).pipe(Scope.extend(scope)));
215
+ const resourceId = `database:${effectResource.id}`;
216
+ const cleanup = async () => {
217
+ await Effect.runPromise(Scope.close(scope, Exit.void));
218
+ };
219
+ resources.set(resourceId, { scope, cleanup });
220
+ return {
221
+ id: effectResource.id,
222
+ name: effectResource.name,
223
+ connectionString: effectResource.connectionString,
224
+ port: effectResource.port,
225
+ hostPort: effectResource.hostPort,
226
+ async waitForLog(pattern, timeoutMs = 30000) {
227
+ await Effect.runPromise(effectResource.waitForLog(pattern, timeoutMs));
228
+ },
229
+ async stop(signal = 'SIGTERM', timeoutMs = 10000) {
230
+ return Effect.runPromise(effectResource.stopGracefully(signal, timeoutMs));
231
+ },
232
+ async getIp(networkName) {
233
+ return Effect.runPromise(effectResource.getIp(networkName));
234
+ },
235
+ async destroy() {
236
+ await cleanup();
237
+ resources.delete(resourceId);
238
+ },
239
+ };
240
+ }
241
+ /**
242
+ * Build a Docker image.
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * const image = await Image("my-app:latest", {
247
+ * context: "./",
248
+ * dockerfile: "Dockerfile",
249
+ * });
250
+ *
251
+ * const container = await Container("app", {
252
+ * image: image.tag,
253
+ * // ...
254
+ * });
255
+ * ```
256
+ */
257
+ export async function Image(tag, config) {
258
+ const effectConfig = {
259
+ context: config.context,
260
+ ...(config.dockerfile !== undefined && { dockerfile: config.dockerfile }),
261
+ ...(config.buildArgs !== undefined && { buildArgs: config.buildArgs }),
262
+ ...(config.buildStrategy !== undefined && { buildStrategy: config.buildStrategy }),
263
+ ...(config.monorepoAppName !== undefined && { monorepoAppName: config.monorepoAppName }),
264
+ ...(config.monorepoSource !== undefined && { monorepoSource: config.monorepoSource }),
265
+ };
266
+ return Effect.runPromise(EffectImage(tag, effectConfig));
267
+ }
268
+ /**
269
+ * Clean up all tracked resources.
270
+ * Called automatically on process exit, but can be called manually.
271
+ */
272
+ export async function destroyAll() {
273
+ for (const [id, resource] of resources) {
274
+ try {
275
+ await resource.cleanup();
276
+ }
277
+ catch (e) {
278
+ console.error(`[Cleanup] Error cleaning up ${id}:`, e);
279
+ }
280
+ }
281
+ resources.clear();
282
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Bindings Module
3
+ *
4
+ * Exports binding types and utilities for creating resource bindings.
5
+ */
6
+ export type { Binding, BuildBinding, BindingsEnv, FlatBindingsEnv, } from './types';
7
+ export { isBuildBinding, mergeBindingsEnv, createEnvBinding, } from './types';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,OAAO,EACP,YAAY,EACZ,WAAW,EACX,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Bindings Module
3
+ *
4
+ * Exports binding types and utilities for creating resource bindings.
5
+ */
6
+ export { isBuildBinding, mergeBindingsEnv, createEnvBinding, } from './types';
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Binding Types
3
+ *
4
+ * Bindings connect resources to containers, providing:
5
+ * - Environment variables
6
+ * - Lifecycle hooks
7
+ * - Build-time transformations
8
+ *
9
+ * Inspired by CloudFlare Workers bindings pattern.
10
+ */
11
+ import { Effect } from 'effect';
12
+ import type { DockerfileInstruction, TransformContext } from '../dockerfile-transformers/types';
13
+ import type { PatchContext, PatchResult } from '../config-patchers/types';
14
+ import type { BuildContext, BuildStrategyInput } from '../build-strategies/types';
15
+ /**
16
+ * Base binding interface.
17
+ *
18
+ * Bindings attach to resources and provide environment variables
19
+ * and lifecycle hooks to containers.
20
+ */
21
+ export interface Binding<TResource = unknown, TEnv extends Record<string, string> = Record<string, string>> {
22
+ /** Unique binding type identifier */
23
+ readonly type: string;
24
+ /** The resource this binding is attached to (optional) */
25
+ readonly resource?: TResource;
26
+ /**
27
+ * Get environment variables this binding provides.
28
+ * These are merged into the container's environment.
29
+ */
30
+ getEnv(): TEnv;
31
+ /**
32
+ * Called when the binding is attached to a container config.
33
+ * Can modify the container config before creation.
34
+ */
35
+ onAttach?<TConfig>(config: TConfig): TConfig | Effect.Effect<TConfig, Error>;
36
+ /**
37
+ * Called after the container starts.
38
+ * Use for initialization tasks like running migrations.
39
+ */
40
+ onStart?<TContainer>(container: TContainer): Effect.Effect<void, Error>;
41
+ /**
42
+ * Called before the container stops.
43
+ * Use for cleanup tasks.
44
+ */
45
+ onStop?<TContainer>(container: TContainer): Effect.Effect<void, Error>;
46
+ }
47
+ /**
48
+ * Build-time binding interface.
49
+ *
50
+ * Extends Binding with build-time transformation capabilities
51
+ * for Dockerfile modifications and config patching.
52
+ */
53
+ export interface BuildBinding<TEnv extends Record<string, string> = Record<string, string>> extends Binding<undefined, TEnv> {
54
+ /**
55
+ * Transform Dockerfile instructions during image build.
56
+ * Return modified instructions or the same instructions unchanged.
57
+ */
58
+ transformDockerfile?(instructions: readonly DockerfileInstruction[], ctx: TransformContext): readonly DockerfileInstruction[];
59
+ /**
60
+ * Patch project configuration files before build.
61
+ */
62
+ patchConfig?(ctx: PatchContext): Effect.Effect<PatchResult, Error>;
63
+ /**
64
+ * Prepare or modify the build context.
65
+ * Called during image build strategy execution.
66
+ */
67
+ prepareBuildContext?(input: BuildStrategyInput, ctx: BuildContext): Effect.Effect<BuildContext, Error>;
68
+ }
69
+ /**
70
+ * Utility type to extract the combined env type from a bindings record.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const bindings = {
75
+ * DB: PrismaBinding(db), // { DATABASE_URL: string }
76
+ * CACHE: RedisBinding(redis), // { REDIS_URL: string }
77
+ * };
78
+ *
79
+ * type Env = BindingsEnv<typeof bindings>;
80
+ * // { DB: { DATABASE_URL: string }, CACHE: { REDIS_URL: string } }
81
+ * ```
82
+ */
83
+ export type BindingsEnv<B extends Record<string, Binding<unknown, Record<string, string>>>> = {
84
+ [K in keyof B]: B[K] extends Binding<unknown, infer E> ? E : never;
85
+ };
86
+ /**
87
+ * Flatten bindings env into a single record of all env vars.
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * type FlatEnv = FlatBindingsEnv<typeof bindings>;
92
+ * // { DATABASE_URL: string, REDIS_URL: string }
93
+ * ```
94
+ */
95
+ export type FlatBindingsEnv<B extends Record<string, Binding<unknown, Record<string, string>>>> = B[keyof B] extends Binding<unknown, infer E> ? E : never;
96
+ /**
97
+ * Check if a binding is a build-time binding.
98
+ */
99
+ export declare function isBuildBinding(binding: Binding): binding is BuildBinding;
100
+ /**
101
+ * Merge environment variables from multiple bindings.
102
+ */
103
+ export declare function mergeBindingsEnv(bindings: Record<string, Binding>): Record<string, string>;
104
+ /**
105
+ * Create a simple binding from environment variables.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const configBinding = createEnvBinding('config', {
110
+ * NODE_ENV: 'production',
111
+ * LOG_LEVEL: 'info',
112
+ * });
113
+ * ```
114
+ */
115
+ export declare function createEnvBinding<TEnv extends Record<string, string>>(type: string, env: TEnv): Binding<undefined, TEnv>;
116
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bindings/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Binding Types
3
+ *
4
+ * Bindings connect resources to containers, providing:
5
+ * - Environment variables
6
+ * - Lifecycle hooks
7
+ * - Build-time transformations
8
+ *
9
+ * Inspired by CloudFlare Workers bindings pattern.
10
+ */
11
+ /**
12
+ * Check if a binding is a build-time binding.
13
+ */
14
+ export function isBuildBinding(binding) {
15
+ return ('transformDockerfile' in binding ||
16
+ 'patchConfig' in binding ||
17
+ 'prepareBuildContext' in binding);
18
+ }
19
+ /**
20
+ * Merge environment variables from multiple bindings.
21
+ */
22
+ export function mergeBindingsEnv(bindings) {
23
+ const env = {};
24
+ for (const binding of Object.values(bindings)) {
25
+ const bindingEnv = binding.getEnv();
26
+ Object.assign(env, bindingEnv);
27
+ }
28
+ return env;
29
+ }
30
+ /**
31
+ * Create a simple binding from environment variables.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const configBinding = createEnvBinding('config', {
36
+ * NODE_ENV: 'production',
37
+ * LOG_LEVEL: 'info',
38
+ * });
39
+ * ```
40
+ */
41
+ export function createEnvBinding(type, env) {
42
+ return {
43
+ type,
44
+ getEnv: () => env,
45
+ };
46
+ }