@docker-harpoon/core 0.1.4 → 0.1.5

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 (87) hide show
  1. package/dist/__tests__/test-setup.d.ts.map +1 -1
  2. package/dist/__tests__/test-setup.js +8 -1
  3. package/dist/api/promise.d.ts +1 -1
  4. package/dist/api/promise.d.ts.map +1 -1
  5. package/dist/api/promise.js +28 -18
  6. package/dist/bindings/index.d.ts +2 -2
  7. package/dist/bindings/index.d.ts.map +1 -1
  8. package/dist/bindings/index.js +1 -1
  9. package/dist/bindings/types.d.ts.map +1 -1
  10. package/dist/bindings/types.js +1 -3
  11. package/dist/build-strategies/types.d.ts.map +1 -1
  12. package/dist/config-patchers/index.d.ts.map +1 -1
  13. package/dist/config-patchers/types.d.ts.map +1 -1
  14. package/dist/dockerfile-transformers/core.d.ts.map +1 -1
  15. package/dist/dockerfile-transformers/core.js +2 -5
  16. package/dist/dockerfile-transformers/index.d.ts.map +1 -1
  17. package/dist/dockerfile-transformers/types.d.ts.map +1 -1
  18. package/dist/errors.d.ts +6 -1
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/errors.js +3 -3
  21. package/dist/helpers/database.d.ts.map +1 -1
  22. package/dist/helpers/database.js +1 -3
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/resources/container.d.ts +7 -2
  27. package/dist/resources/container.d.ts.map +1 -1
  28. package/dist/resources/container.js +222 -31
  29. package/dist/resources/image.d.ts.map +1 -1
  30. package/dist/resources/image.js +14 -35
  31. package/dist/resources/network.d.ts.map +1 -1
  32. package/dist/resources/network.js +23 -9
  33. package/dist/resources/schemas.d.ts +177 -18
  34. package/dist/resources/schemas.d.ts.map +1 -1
  35. package/dist/resources/schemas.js +1 -1
  36. package/dist/services/CircuitBreaker.d.ts +83 -0
  37. package/dist/services/CircuitBreaker.d.ts.map +1 -0
  38. package/dist/services/CircuitBreaker.js +164 -0
  39. package/dist/services/ContainerPool.d.ts +82 -0
  40. package/dist/services/ContainerPool.d.ts.map +1 -0
  41. package/dist/services/ContainerPool.js +186 -0
  42. package/dist/services/DockerBatcher.d.ts +74 -0
  43. package/dist/services/DockerBatcher.d.ts.map +1 -0
  44. package/dist/services/DockerBatcher.js +107 -0
  45. package/dist/services/DockerClient.d.ts +125 -0
  46. package/dist/services/DockerClient.d.ts.map +1 -0
  47. package/dist/services/DockerClient.js +220 -0
  48. package/dist/services/DockerErrors.d.ts +145 -0
  49. package/dist/services/DockerErrors.d.ts.map +1 -0
  50. package/dist/services/DockerErrors.js +224 -0
  51. package/dist/services/DockerRateLimiter.d.ts +80 -0
  52. package/dist/services/DockerRateLimiter.d.ts.map +1 -0
  53. package/dist/services/DockerRateLimiter.js +93 -0
  54. package/dist/services/EventBus.d.ts +126 -0
  55. package/dist/services/EventBus.d.ts.map +1 -0
  56. package/dist/services/EventBus.js +111 -0
  57. package/dist/services/Harpoon.d.ts +151 -0
  58. package/dist/services/Harpoon.d.ts.map +1 -0
  59. package/dist/services/Harpoon.js +148 -0
  60. package/dist/services/HarpoonConfig.d.ts +60 -0
  61. package/dist/services/HarpoonConfig.d.ts.map +1 -0
  62. package/dist/services/HarpoonConfig.js +67 -0
  63. package/dist/services/HarpoonLogger.d.ts +36 -0
  64. package/dist/services/HarpoonLogger.d.ts.map +1 -0
  65. package/dist/services/HarpoonLogger.js +94 -0
  66. package/dist/services/ReadinessCoordinator.d.ts +128 -0
  67. package/dist/services/ReadinessCoordinator.d.ts.map +1 -0
  68. package/dist/services/ReadinessCoordinator.js +170 -0
  69. package/dist/services/ResourceTracker.d.ts +74 -0
  70. package/dist/services/ResourceTracker.d.ts.map +1 -0
  71. package/dist/services/ResourceTracker.js +145 -0
  72. package/dist/services/index.d.ts +29 -0
  73. package/dist/services/index.d.ts.map +1 -0
  74. package/dist/services/index.js +47 -0
  75. package/dist/testing/helpers.d.ts +114 -0
  76. package/dist/testing/helpers.d.ts.map +1 -0
  77. package/dist/testing/helpers.js +140 -0
  78. package/dist/testing/index.d.ts +29 -0
  79. package/dist/testing/index.d.ts.map +1 -0
  80. package/dist/testing/index.js +47 -0
  81. package/dist/testing/mocks.d.ts +66 -0
  82. package/dist/testing/mocks.d.ts.map +1 -0
  83. package/dist/testing/mocks.js +224 -0
  84. package/dist/utils/process.d.ts +24 -0
  85. package/dist/utils/process.d.ts.map +1 -0
  86. package/dist/utils/process.js +49 -0
  87. package/package.json +12 -8
@@ -0,0 +1,186 @@
1
+ /**
2
+ * ContainerPool Service
3
+ *
4
+ * Provides a pool of pre-warmed containers for faster test execution.
5
+ * Uses Effect's Pool for efficient resource management.
6
+ */
7
+ import { Context, Effect, Layer, Pool, Scope, Duration, Ref } from 'effect';
8
+ import { DockerClient } from './DockerClient';
9
+ import { HarpoonConfig } from './HarpoonConfig';
10
+ import { Container } from '../resources/container';
11
+ /**
12
+ * ContainerPool service tag for dependency injection.
13
+ */
14
+ export class ContainerPool extends Context.Tag('@harpoon/ContainerPool')() {
15
+ }
16
+ /**
17
+ * Default pool configuration.
18
+ */
19
+ const defaultPoolConfig = {
20
+ containerConfig: {
21
+ image: 'alpine:latest',
22
+ cmd: ['tail', '-f', '/dev/null'], // Keep container running
23
+ },
24
+ minSize: 2,
25
+ maxSize: 10,
26
+ acquireTimeout: Duration.seconds(30),
27
+ idleTimeout: Duration.minutes(5),
28
+ namePrefix: 'harpoon-pool',
29
+ };
30
+ /**
31
+ * Create a ContainerPool with specific configuration.
32
+ */
33
+ export const makeContainerPool = (config = {}) => Effect.gen(function* () {
34
+ const dockerClient = yield* DockerClient;
35
+ const docker = dockerClient.client;
36
+ const harpoonConfig = yield* HarpoonConfig;
37
+ const poolConfig = {
38
+ ...defaultPoolConfig,
39
+ ...config,
40
+ };
41
+ // Counter for unique container names
42
+ const counterRef = yield* Ref.make(0);
43
+ // Track in-use containers
44
+ const inUseRef = yield* Ref.make(0);
45
+ // Track total created
46
+ const totalRef = yield* Ref.make(0);
47
+ // Generate unique pool ID
48
+ const poolId = `${poolConfig.namePrefix}-${Date.now()}`;
49
+ // Create a container for the pool
50
+ const createContainer = Effect.gen(function* () {
51
+ const count = yield* Ref.getAndUpdate(counterRef, (n) => n + 1);
52
+ const name = `${poolId}-${count}`;
53
+ yield* Effect.logDebug('Creating pooled container').pipe(Effect.annotateLogs({ poolId, containerName: name }));
54
+ const scope = yield* Scope.make();
55
+ const resource = yield* Container(name, {
56
+ ...poolConfig.containerConfig,
57
+ }, docker).pipe(Scope.extend(scope));
58
+ yield* Ref.update(totalRef, (n) => n + 1);
59
+ return {
60
+ resource,
61
+ scope,
62
+ name,
63
+ };
64
+ });
65
+ // Create the pool
66
+ const pool = yield* Pool.make({
67
+ acquire: createContainer.pipe(Effect.map(({ resource, scope, name }) => ({
68
+ resource,
69
+ scope,
70
+ name,
71
+ acquiredAt: new Date(),
72
+ })), Effect.timeoutFail({
73
+ duration: poolConfig.acquireTimeout,
74
+ onTimeout: () => new Error('Container acquisition timed out'),
75
+ })),
76
+ size: poolConfig.maxSize,
77
+ });
78
+ const service = {
79
+ _tag: 'ContainerPool',
80
+ acquire: () => Effect.gen(function* () {
81
+ yield* Ref.update(inUseRef, (n) => n + 1);
82
+ const item = yield* pool.get;
83
+ yield* Effect.addFinalizer(() => Ref.update(inUseRef, (n) => Math.max(0, n - 1)));
84
+ return {
85
+ resource: item.resource,
86
+ acquiredAt: item.acquiredAt,
87
+ poolId,
88
+ };
89
+ }),
90
+ size: () => Ref.get(totalRef),
91
+ available: () => Effect.gen(function* () {
92
+ const total = yield* Ref.get(totalRef);
93
+ const used = yield* Ref.get(inUseRef);
94
+ return Math.max(0, total - used);
95
+ }),
96
+ inUse: () => Ref.get(inUseRef),
97
+ warmup: () => Effect.gen(function* () {
98
+ yield* Effect.logInfo('Warming up container pool').pipe(Effect.annotateLogs({
99
+ poolId,
100
+ targetSize: poolConfig.minSize,
101
+ }));
102
+ // Create containers up to minSize
103
+ const current = yield* Ref.get(totalRef);
104
+ const needed = Math.max(0, poolConfig.minSize - current);
105
+ if (needed > 0) {
106
+ yield* Effect.all(Array.from({ length: needed }, () => Effect.scoped(Effect.gen(function* () {
107
+ const item = yield* pool.get;
108
+ // Immediately release back to pool
109
+ yield* Effect.yieldNow();
110
+ }))), { concurrency: harpoonConfig.parallelLimit });
111
+ }
112
+ yield* Effect.logInfo('Container pool warmed up').pipe(Effect.annotateLogs({
113
+ poolId,
114
+ size: yield* Ref.get(totalRef),
115
+ }));
116
+ }),
117
+ };
118
+ return service;
119
+ });
120
+ /**
121
+ * Live implementation layer.
122
+ */
123
+ export const ContainerPoolLive = (config = {}) => Layer.scoped(ContainerPool, makeContainerPool(config));
124
+ /**
125
+ * Test implementation that creates containers on-demand.
126
+ */
127
+ export const ContainerPoolTest = Layer.scoped(ContainerPool, Effect.gen(function* () {
128
+ const counterRef = yield* Ref.make(0);
129
+ const inUseRef = yield* Ref.make(0);
130
+ return {
131
+ _tag: 'ContainerPool',
132
+ acquire: () => Effect.gen(function* () {
133
+ const count = yield* Ref.getAndUpdate(counterRef, (n) => n + 1);
134
+ yield* Ref.update(inUseRef, (n) => n + 1);
135
+ yield* Effect.addFinalizer(() => Ref.update(inUseRef, (n) => Math.max(0, n - 1)));
136
+ // Return a mock pooled container
137
+ return {
138
+ resource: {
139
+ id: `mock-pool-${count}`,
140
+ name: `mock-pool-container-${count}`,
141
+ waitForLog: () => Effect.void,
142
+ stopGracefully: () => Effect.succeed({
143
+ signalCount: 1,
144
+ graceful: true,
145
+ timeTakenMs: 100,
146
+ signal: 'SIGTERM',
147
+ timeoutMs: 10000,
148
+ }),
149
+ getIp: () => Effect.succeed('127.0.0.1'),
150
+ stats: () => Effect.succeed({
151
+ cpu: { usage: 0, system: 0, percent: 0, cores: 1 },
152
+ memory: { usage: 0, limit: 0, percent: 0, usageMB: 0, limitMB: 0 },
153
+ network: { rxBytes: 0, txBytes: 0, rxPackets: 0, txPackets: 0 },
154
+ timestamp: new Date().toISOString(),
155
+ containerId: `mock-pool-${count}`,
156
+ }),
157
+ statsStream: () => {
158
+ const { Stream } = require('effect');
159
+ return Stream.empty;
160
+ },
161
+ exec: () => Effect.succeed({
162
+ exitCode: 0,
163
+ stdout: '',
164
+ stderr: '',
165
+ durationMs: 0,
166
+ }),
167
+ streamLogs: () => Effect.succeed((async function* () { })()),
168
+ logsStream: () => {
169
+ const { Stream } = require('effect');
170
+ return Stream.empty;
171
+ },
172
+ },
173
+ acquiredAt: new Date(),
174
+ poolId: 'test-pool',
175
+ };
176
+ }),
177
+ size: () => Ref.get(counterRef),
178
+ available: () => Effect.gen(function* () {
179
+ const total = yield* Ref.get(counterRef);
180
+ const used = yield* Ref.get(inUseRef);
181
+ return Math.max(0, total - used);
182
+ }),
183
+ inUse: () => Ref.get(inUseRef),
184
+ warmup: () => Effect.void,
185
+ };
186
+ }));
@@ -0,0 +1,74 @@
1
+ /**
2
+ * DockerBatcher Service
3
+ *
4
+ * Provides request batching for Docker API operations to reduce API calls.
5
+ * Uses Effect's RequestResolver for efficient batched resolution.
6
+ */
7
+ import { Context, Effect, Layer, Request } from 'effect';
8
+ import Docker from 'dockerode';
9
+ import { DockerClient } from './DockerClient';
10
+ import { DockerApiError } from './DockerErrors';
11
+ /**
12
+ * Request to get container info by ID.
13
+ */
14
+ export interface GetContainerInfoRequest extends Request.Request<Docker.ContainerInspectInfo, DockerApiError> {
15
+ readonly _tag: 'GetContainerInfoRequest';
16
+ readonly containerId: string;
17
+ }
18
+ /**
19
+ * Request to get network info by ID.
20
+ */
21
+ export interface GetNetworkInfoRequest extends Request.Request<Docker.NetworkInspectInfo, DockerApiError> {
22
+ readonly _tag: 'GetNetworkInfoRequest';
23
+ readonly networkId: string;
24
+ }
25
+ /**
26
+ * All batchable request types.
27
+ */
28
+ export type DockerBatchRequest = GetContainerInfoRequest | GetNetworkInfoRequest;
29
+ /**
30
+ * Create a container info request.
31
+ */
32
+ export declare const GetContainerInfo: Request.Request.Constructor<GetContainerInfoRequest, "_tag">;
33
+ /**
34
+ * Create a network info request.
35
+ */
36
+ export declare const GetNetworkInfo: Request.Request.Constructor<GetNetworkInfoRequest, "_tag">;
37
+ /**
38
+ * DockerBatcher service interface.
39
+ */
40
+ export interface DockerBatcherService {
41
+ readonly _tag: 'DockerBatcher';
42
+ /**
43
+ * Get container info, batched with other container info requests.
44
+ */
45
+ readonly getContainerInfo: (containerId: string) => Effect.Effect<Docker.ContainerInspectInfo, DockerApiError>;
46
+ /**
47
+ * Get network info, batched with other network info requests.
48
+ */
49
+ readonly getNetworkInfo: (networkId: string) => Effect.Effect<Docker.NetworkInspectInfo, DockerApiError>;
50
+ /**
51
+ * Get multiple container infos in a single batch.
52
+ */
53
+ readonly getContainerInfos: (containerIds: readonly string[]) => Effect.Effect<readonly Docker.ContainerInspectInfo[], DockerApiError>;
54
+ /**
55
+ * Get multiple network infos in a single batch.
56
+ */
57
+ readonly getNetworkInfos: (networkIds: readonly string[]) => Effect.Effect<readonly Docker.NetworkInspectInfo[], DockerApiError>;
58
+ }
59
+ declare const DockerBatcher_base: Context.TagClass<DockerBatcher, "@harpoon/DockerBatcher", DockerBatcherService>;
60
+ /**
61
+ * DockerBatcher service tag for dependency injection.
62
+ */
63
+ export declare class DockerBatcher extends DockerBatcher_base {
64
+ }
65
+ /**
66
+ * Live implementation of DockerBatcher.
67
+ */
68
+ export declare const DockerBatcherLive: Layer.Layer<DockerBatcher, never, DockerClient>;
69
+ /**
70
+ * Test implementation that doesn't batch.
71
+ */
72
+ export declare const DockerBatcherTest: Layer.Layer<DockerBatcher, never, DockerClient>;
73
+ export {};
74
+ //# sourceMappingURL=DockerBatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DockerBatcher.d.ts","sourceRoot":"","sources":["../../src/services/DockerBatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAyC,MAAM,QAAQ,CAAC;AAChG,OAAO,MAAM,MAAM,WAAW,CAAC;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAqB,MAAM,gBAAgB,CAAC;AAInE;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,OAAO,CAAC,OAAO,CAC9D,MAAM,CAAC,oBAAoB,EAC3B,cAAc,CACf;IACC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,OAAO,CAAC,OAAO,CAC5D,MAAM,CAAC,kBAAkB,EACzB,cAAc,CACf;IACC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,uBAAuB,GAAG,qBAAqB,CAAC;AAIjF;;GAEG;AACH,eAAO,MAAM,gBAAgB,8DAAqE,CAAC;AAEnG;;GAEG;AACH,eAAO,MAAM,cAAc,4DAAiE,CAAC;AAI7F;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAE/B;;OAEG;IACH,QAAQ,CAAC,gBAAgB,EAAE,CACzB,WAAW,EAAE,MAAM,KAChB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,cAAc,CAAC,CAAC;IAEhE;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,CACvB,SAAS,EAAE,MAAM,KACd,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;IAE9D;;OAEG;IACH,QAAQ,CAAC,iBAAiB,EAAE,CAC1B,YAAY,EAAE,SAAS,MAAM,EAAE,KAC5B,MAAM,CAAC,MAAM,CAAC,SAAS,MAAM,CAAC,oBAAoB,EAAE,EAAE,cAAc,CAAC,CAAC;IAE3E;;OAEG;IACH,QAAQ,CAAC,eAAe,EAAE,CACxB,UAAU,EAAE,SAAS,MAAM,EAAE,KAC1B,MAAM,CAAC,MAAM,CAAC,SAAS,MAAM,CAAC,kBAAkB,EAAE,EAAE,cAAc,CAAC,CAAC;CAC1E;;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,kBAGhC;CAAG;AAoEN;;GAEG;AACH,eAAO,MAAM,iBAAiB,iDAsC7B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,iDA0C7B,CAAC"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * DockerBatcher Service
3
+ *
4
+ * Provides request batching for Docker API operations to reduce API calls.
5
+ * Uses Effect's RequestResolver for efficient batched resolution.
6
+ */
7
+ import { Context, Effect, Layer, Request, RequestResolver } from 'effect';
8
+ import { DockerClient } from './DockerClient';
9
+ import { wrapAsDockerError } from './DockerErrors';
10
+ // ============ Request Constructors ============
11
+ /**
12
+ * Create a container info request.
13
+ */
14
+ export const GetContainerInfo = Request.tagged('GetContainerInfoRequest');
15
+ /**
16
+ * Create a network info request.
17
+ */
18
+ export const GetNetworkInfo = Request.tagged('GetNetworkInfoRequest');
19
+ /**
20
+ * DockerBatcher service tag for dependency injection.
21
+ */
22
+ export class DockerBatcher extends Context.Tag('@harpoon/DockerBatcher')() {
23
+ }
24
+ /**
25
+ * Create the batched resolver for container info requests.
26
+ */
27
+ const makeContainerInfoResolver = (docker) => RequestResolver.makeBatched((requests) => Effect.gen(function* () {
28
+ yield* Effect.logDebug(`Batching ${requests.length} container info requests`);
29
+ // Process all requests in parallel
30
+ const results = yield* Effect.all(requests.map((req) => Effect.tryPromise({
31
+ try: () => docker.getContainer(req.containerId).inspect(),
32
+ catch: (e) => wrapAsDockerError('inspectContainer', e),
33
+ }).pipe(Effect.map((info) => ({ req, info })), Effect.catchAll((error) => Effect.succeed({ req, error })))), { concurrency: 'unbounded' });
34
+ // Complete each request with its result
35
+ for (const result of results) {
36
+ if ('info' in result) {
37
+ yield* Request.completeEffect(result.req, Effect.succeed(result.info));
38
+ }
39
+ else {
40
+ yield* Request.completeEffect(result.req, Effect.fail(result.error));
41
+ }
42
+ }
43
+ }));
44
+ /**
45
+ * Create the batched resolver for network info requests.
46
+ */
47
+ const makeNetworkInfoResolver = (docker) => RequestResolver.makeBatched((requests) => Effect.gen(function* () {
48
+ yield* Effect.logDebug(`Batching ${requests.length} network info requests`);
49
+ // Process all requests in parallel
50
+ const results = yield* Effect.all(requests.map((req) => Effect.tryPromise({
51
+ try: () => docker.getNetwork(req.networkId).inspect(),
52
+ catch: (e) => wrapAsDockerError('inspectNetwork', e),
53
+ }).pipe(Effect.map((info) => ({ req, info })), Effect.catchAll((error) => Effect.succeed({ req, error })))), { concurrency: 'unbounded' });
54
+ // Complete each request with its result
55
+ for (const result of results) {
56
+ if ('info' in result) {
57
+ yield* Request.completeEffect(result.req, Effect.succeed(result.info));
58
+ }
59
+ else {
60
+ yield* Request.completeEffect(result.req, Effect.fail(result.error));
61
+ }
62
+ }
63
+ }));
64
+ /**
65
+ * Live implementation of DockerBatcher.
66
+ */
67
+ export const DockerBatcherLive = Layer.effect(DockerBatcher, Effect.gen(function* () {
68
+ const dockerClient = yield* DockerClient;
69
+ const docker = dockerClient.client;
70
+ const containerResolver = makeContainerInfoResolver(docker);
71
+ const networkResolver = makeNetworkInfoResolver(docker);
72
+ yield* Effect.logDebug('DockerBatcher initialized');
73
+ const service = {
74
+ _tag: 'DockerBatcher',
75
+ getContainerInfo: (containerId) => Effect.request(GetContainerInfo({ containerId }), containerResolver),
76
+ getNetworkInfo: (networkId) => Effect.request(GetNetworkInfo({ networkId }), networkResolver),
77
+ getContainerInfos: (containerIds) => Effect.all(containerIds.map((containerId) => Effect.request(GetContainerInfo({ containerId }), containerResolver)), { batching: true }),
78
+ getNetworkInfos: (networkIds) => Effect.all(networkIds.map((networkId) => Effect.request(GetNetworkInfo({ networkId }), networkResolver)), { batching: true }),
79
+ };
80
+ return service;
81
+ }));
82
+ /**
83
+ * Test implementation that doesn't batch.
84
+ */
85
+ export const DockerBatcherTest = Layer.effect(DockerBatcher, Effect.gen(function* () {
86
+ const dockerClient = yield* DockerClient;
87
+ const docker = dockerClient.client;
88
+ return {
89
+ _tag: 'DockerBatcher',
90
+ getContainerInfo: (containerId) => Effect.tryPromise({
91
+ try: () => docker.getContainer(containerId).inspect(),
92
+ catch: (e) => wrapAsDockerError('inspectContainer', e),
93
+ }),
94
+ getNetworkInfo: (networkId) => Effect.tryPromise({
95
+ try: () => docker.getNetwork(networkId).inspect(),
96
+ catch: (e) => wrapAsDockerError('inspectNetwork', e),
97
+ }),
98
+ getContainerInfos: (containerIds) => Effect.all(containerIds.map((containerId) => Effect.tryPromise({
99
+ try: () => docker.getContainer(containerId).inspect(),
100
+ catch: (e) => wrapAsDockerError('inspectContainer', e),
101
+ }))),
102
+ getNetworkInfos: (networkIds) => Effect.all(networkIds.map((networkId) => Effect.tryPromise({
103
+ try: () => docker.getNetwork(networkId).inspect(),
104
+ catch: (e) => wrapAsDockerError('inspectNetwork', e),
105
+ }))),
106
+ };
107
+ }));
@@ -0,0 +1,125 @@
1
+ /**
2
+ * DockerClient Service
3
+ *
4
+ * Provides a typed interface to Docker operations using Effect.
5
+ * This service wraps dockerode and converts all operations to Effect-based APIs.
6
+ */
7
+ import { Context, Effect, Layer } from 'effect';
8
+ import Docker from 'dockerode';
9
+ import { HarpoonConfig } from './HarpoonConfig';
10
+ import { DockerConnectionError, DockerApiError } from './DockerErrors';
11
+ /**
12
+ * Container creation options (subset of dockerode options).
13
+ */
14
+ export interface CreateContainerOptions {
15
+ name: string;
16
+ Image: string;
17
+ Cmd?: string[];
18
+ Env?: string[];
19
+ ExposedPorts?: Record<string, object>;
20
+ HostConfig?: {
21
+ PortBindings?: Record<string, Array<{
22
+ HostPort: string;
23
+ }>>;
24
+ NetworkMode?: string;
25
+ Binds?: string[];
26
+ CapAdd?: string[];
27
+ CapDrop?: string[];
28
+ Privileged?: boolean;
29
+ User?: string;
30
+ };
31
+ NetworkingConfig?: {
32
+ EndpointsConfig?: Record<string, object>;
33
+ };
34
+ }
35
+ /**
36
+ * Network creation options.
37
+ */
38
+ export interface CreateNetworkOptions {
39
+ Name: string;
40
+ Driver?: string;
41
+ CheckDuplicate?: boolean;
42
+ }
43
+ /**
44
+ * Container list options.
45
+ */
46
+ export interface ListContainersOptions {
47
+ all?: boolean;
48
+ filters?: {
49
+ name?: string[];
50
+ status?: string[];
51
+ };
52
+ }
53
+ /**
54
+ * Network list options.
55
+ */
56
+ export interface ListNetworksOptions {
57
+ filters?: {
58
+ name?: string[];
59
+ };
60
+ }
61
+ /**
62
+ * DockerClient service interface.
63
+ *
64
+ * All methods return Effects with typed errors, enabling proper error handling
65
+ * and composition with other Effect-based operations.
66
+ */
67
+ export interface DockerClientService {
68
+ readonly _tag: 'DockerClient';
69
+ /** The underlying dockerode client (for advanced usage) */
70
+ readonly client: Docker;
71
+ /** Check if Docker daemon is reachable */
72
+ readonly ping: () => Effect.Effect<void, DockerConnectionError>;
73
+ /** Get Docker version information */
74
+ readonly version: () => Effect.Effect<Docker.DockerVersion, DockerApiError>;
75
+ /** List containers */
76
+ readonly listContainers: (options?: ListContainersOptions) => Effect.Effect<Docker.ContainerInfo[], DockerApiError>;
77
+ /** Get a container by ID */
78
+ readonly getContainer: (id: string) => Docker.Container;
79
+ /** Create a new container */
80
+ readonly createContainer: (options: CreateContainerOptions) => Effect.Effect<Docker.Container, DockerApiError>;
81
+ /** List networks */
82
+ readonly listNetworks: (options?: ListNetworksOptions) => Effect.Effect<Docker.NetworkInspectInfo[], DockerApiError>;
83
+ /** Get a network by ID */
84
+ readonly getNetwork: (id: string) => Docker.Network;
85
+ /** Create a new network */
86
+ readonly createNetwork: (options: CreateNetworkOptions) => Effect.Effect<Docker.Network, DockerApiError>;
87
+ /** Get an image by name */
88
+ readonly getImage: (name: string) => Docker.Image;
89
+ /** List images */
90
+ readonly listImages: () => Effect.Effect<Docker.ImageInfo[], DockerApiError>;
91
+ /** Pull an image from registry */
92
+ readonly pullImage: (name: string, options?: {
93
+ authconfig?: object;
94
+ }) => Effect.Effect<void, DockerApiError>;
95
+ }
96
+ declare const DockerClient_base: Context.TagClass<DockerClient, "@harpoon/DockerClient", DockerClientService>;
97
+ /**
98
+ * DockerClient service tag for dependency injection.
99
+ */
100
+ export declare class DockerClient extends DockerClient_base {
101
+ }
102
+ /**
103
+ * Live implementation of DockerClient.
104
+ *
105
+ * Creates a connection to Docker and verifies it on startup.
106
+ * Requires HarpoonConfig and DockerRateLimiter to be provided.
107
+ *
108
+ * All Docker API operations are:
109
+ * - Rate limited via semaphore
110
+ * - Retried on transient errors with exponential backoff
111
+ */
112
+ export declare const DockerClientLive: Layer.Layer<DockerClient, DockerConnectionError, HarpoonConfig>;
113
+ /**
114
+ * Test implementation of DockerClient.
115
+ *
116
+ * Returns mock responses for all operations.
117
+ * Useful for unit tests that don't need real Docker.
118
+ */
119
+ export declare const DockerClientTest: Layer.Layer<DockerClient, never, never>;
120
+ /**
121
+ * Create a custom test client with specific mock behaviors.
122
+ */
123
+ export declare const makeDockerClientTest: (overrides: Partial<DockerClientService>) => Layer.Layer<DockerClient, never, never>;
124
+ export {};
125
+ //# sourceMappingURL=DockerClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DockerClient.d.ts","sourceRoot":"","sources":["../../src/services/DockerClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAS,MAAM,QAAQ,CAAC;AACvD,OAAO,MAAM,MAAM,WAAW,CAAC;AAI/B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,cAAc,EAGf,MAAM,gBAAgB,CAAC;AAGxB;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE;QACX,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC,CAAC;QAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,gBAAgB,CAAC,EAAE;QACjB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC1C,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAE9B,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAEhE,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAE5E,sBAAsB;IACtB,QAAQ,CAAC,cAAc,EAAE,CACvB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,cAAc,CAAC,CAAC;IAE3D,4BAA4B;IAC5B,QAAQ,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,SAAS,CAAC;IAExD,6BAA6B;IAC7B,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,sBAAsB,KAC5B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAErD,oBAAoB;IACpB,QAAQ,CAAC,YAAY,EAAE,CACrB,OAAO,CAAC,EAAE,mBAAmB,KAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,cAAc,CAAC,CAAC;IAEhE,0BAA0B;IAC1B,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,OAAO,CAAC;IAEpD,2BAA2B;IAC3B,QAAQ,CAAC,aAAa,EAAE,CACtB,OAAO,EAAE,oBAAoB,KAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAEnD,2BAA2B;IAC3B,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,KAAK,CAAC;IAElD,kBAAkB;IAClB,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,cAAc,CAAC,CAAC;IAE7E,kCAAkC;IAClC,QAAQ,CAAC,SAAS,EAAE,CAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;CAC1C;;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,iBAG/B;CAAG;AAuCN;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,iEAqIe,CAAC;AAoC7C;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,yCAmCG,CAAC;AAEjC;;GAEG;AACH,eAAO,MAAM,oBAAoB,sFAyB7B,CAAC"}