@docker-harpoon/core 0.1.3 → 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 (98) hide show
  1. package/dist/__tests__/bindings.test.js +9 -5
  2. package/dist/__tests__/container.test.js +30 -9
  3. package/dist/__tests__/database.test.js +0 -14
  4. package/dist/__tests__/docker-infra.template.d.ts +2 -0
  5. package/dist/__tests__/docker-infra.template.d.ts.map +1 -0
  6. package/dist/__tests__/docker-infra.template.js +174 -0
  7. package/dist/__tests__/test-setup.d.ts +9 -0
  8. package/dist/__tests__/test-setup.d.ts.map +1 -0
  9. package/dist/__tests__/test-setup.js +27 -0
  10. package/dist/api/index.d.ts +1 -1
  11. package/dist/api/index.d.ts.map +1 -1
  12. package/dist/api/promise.d.ts +13 -3
  13. package/dist/api/promise.d.ts.map +1 -1
  14. package/dist/api/promise.js +33 -18
  15. package/dist/bindings/index.d.ts +2 -2
  16. package/dist/bindings/index.d.ts.map +1 -1
  17. package/dist/bindings/index.js +1 -1
  18. package/dist/bindings/types.d.ts.map +1 -1
  19. package/dist/bindings/types.js +1 -3
  20. package/dist/build-strategies/types.d.ts.map +1 -1
  21. package/dist/config-patchers/index.d.ts.map +1 -1
  22. package/dist/config-patchers/types.d.ts.map +1 -1
  23. package/dist/dockerfile-transformers/core.d.ts.map +1 -1
  24. package/dist/dockerfile-transformers/core.js +2 -5
  25. package/dist/dockerfile-transformers/index.d.ts.map +1 -1
  26. package/dist/dockerfile-transformers/types.d.ts.map +1 -1
  27. package/dist/errors.d.ts +6 -1
  28. package/dist/errors.d.ts.map +1 -1
  29. package/dist/errors.js +3 -3
  30. package/dist/helpers/database.d.ts.map +1 -1
  31. package/dist/helpers/database.js +1 -3
  32. package/dist/index.d.ts +3 -3
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/resources/container.d.ts +25 -2
  36. package/dist/resources/container.d.ts.map +1 -1
  37. package/dist/resources/container.js +301 -77
  38. package/dist/resources/image.d.ts.map +1 -1
  39. package/dist/resources/image.js +14 -35
  40. package/dist/resources/index.d.ts +1 -1
  41. package/dist/resources/index.d.ts.map +1 -1
  42. package/dist/resources/network.d.ts.map +1 -1
  43. package/dist/resources/network.js +23 -9
  44. package/dist/resources/schemas.d.ts +178 -18
  45. package/dist/resources/schemas.d.ts.map +1 -1
  46. package/dist/resources/schemas.js +2 -1
  47. package/dist/services/CircuitBreaker.d.ts +83 -0
  48. package/dist/services/CircuitBreaker.d.ts.map +1 -0
  49. package/dist/services/CircuitBreaker.js +164 -0
  50. package/dist/services/ContainerPool.d.ts +82 -0
  51. package/dist/services/ContainerPool.d.ts.map +1 -0
  52. package/dist/services/ContainerPool.js +186 -0
  53. package/dist/services/DockerBatcher.d.ts +74 -0
  54. package/dist/services/DockerBatcher.d.ts.map +1 -0
  55. package/dist/services/DockerBatcher.js +107 -0
  56. package/dist/services/DockerClient.d.ts +125 -0
  57. package/dist/services/DockerClient.d.ts.map +1 -0
  58. package/dist/services/DockerClient.js +220 -0
  59. package/dist/services/DockerErrors.d.ts +145 -0
  60. package/dist/services/DockerErrors.d.ts.map +1 -0
  61. package/dist/services/DockerErrors.js +224 -0
  62. package/dist/services/DockerRateLimiter.d.ts +80 -0
  63. package/dist/services/DockerRateLimiter.d.ts.map +1 -0
  64. package/dist/services/DockerRateLimiter.js +93 -0
  65. package/dist/services/EventBus.d.ts +126 -0
  66. package/dist/services/EventBus.d.ts.map +1 -0
  67. package/dist/services/EventBus.js +111 -0
  68. package/dist/services/Harpoon.d.ts +151 -0
  69. package/dist/services/Harpoon.d.ts.map +1 -0
  70. package/dist/services/Harpoon.js +148 -0
  71. package/dist/services/HarpoonConfig.d.ts +60 -0
  72. package/dist/services/HarpoonConfig.d.ts.map +1 -0
  73. package/dist/services/HarpoonConfig.js +67 -0
  74. package/dist/services/HarpoonLogger.d.ts +36 -0
  75. package/dist/services/HarpoonLogger.d.ts.map +1 -0
  76. package/dist/services/HarpoonLogger.js +94 -0
  77. package/dist/services/ReadinessCoordinator.d.ts +128 -0
  78. package/dist/services/ReadinessCoordinator.d.ts.map +1 -0
  79. package/dist/services/ReadinessCoordinator.js +170 -0
  80. package/dist/services/ResourceTracker.d.ts +74 -0
  81. package/dist/services/ResourceTracker.d.ts.map +1 -0
  82. package/dist/services/ResourceTracker.js +145 -0
  83. package/dist/services/index.d.ts +29 -0
  84. package/dist/services/index.d.ts.map +1 -0
  85. package/dist/services/index.js +47 -0
  86. package/dist/testing/helpers.d.ts +114 -0
  87. package/dist/testing/helpers.d.ts.map +1 -0
  88. package/dist/testing/helpers.js +140 -0
  89. package/dist/testing/index.d.ts +29 -0
  90. package/dist/testing/index.d.ts.map +1 -0
  91. package/dist/testing/index.js +47 -0
  92. package/dist/testing/mocks.d.ts +66 -0
  93. package/dist/testing/mocks.d.ts.map +1 -0
  94. package/dist/testing/mocks.js +224 -0
  95. package/dist/utils/process.d.ts +24 -0
  96. package/dist/utils/process.d.ts.map +1 -0
  97. package/dist/utils/process.js +49 -0
  98. package/package.json +12 -8
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Mock Implementations for Testing
3
+ *
4
+ * Provides mock implementations of Harpoon services for unit testing.
5
+ */
6
+ import { Effect, Layer, Ref, PubSub, Stream, Duration, Option } from 'effect';
7
+ import { DockerClient, } from '../services/DockerClient';
8
+ import { DockerRateLimiter } from '../services/DockerRateLimiter';
9
+ import { CircuitBreaker } from '../services/CircuitBreaker';
10
+ import { ResourceTracker, } from '../services/ResourceTracker';
11
+ import '../services/ReadinessCoordinator';
12
+ import { HarpoonConfig } from '../services/HarpoonConfig';
13
+ /**
14
+ * Create a mock Docker container.
15
+ */
16
+ export const createMockContainer = (config) => ({
17
+ id: config.id,
18
+ start: () => Promise.resolve(),
19
+ stop: () => Promise.resolve(),
20
+ kill: () => Promise.resolve(),
21
+ remove: () => Promise.resolve(),
22
+ inspect: () => Promise.resolve({
23
+ Id: config.id,
24
+ Name: `/${config.name}`,
25
+ State: { Running: config.running ?? true },
26
+ NetworkSettings: { Networks: {} },
27
+ }),
28
+ logs: () => Promise.resolve(Buffer.from(config.logs ?? '')),
29
+ stats: () => Promise.resolve(config.stats ?? {
30
+ cpu_stats: {
31
+ cpu_usage: { total_usage: 1000000 },
32
+ system_cpu_usage: 100000000,
33
+ online_cpus: 4,
34
+ },
35
+ precpu_stats: {
36
+ cpu_usage: { total_usage: 900000 },
37
+ system_cpu_usage: 99000000,
38
+ },
39
+ memory_stats: {
40
+ usage: 1024 * 1024 * 100,
41
+ limit: 1024 * 1024 * 1024,
42
+ },
43
+ networks: {},
44
+ }),
45
+ exec: () => Promise.resolve({
46
+ start: () => Promise.resolve({}),
47
+ inspect: () => Promise.resolve({ ExitCode: 0 }),
48
+ }),
49
+ });
50
+ /**
51
+ * Create a mock Docker network.
52
+ */
53
+ export const createMockNetwork = (id, name = 'mock-network') => ({
54
+ id,
55
+ inspect: () => Promise.resolve({ Id: id, Name: name }),
56
+ remove: () => Promise.resolve(),
57
+ connect: () => Promise.resolve(),
58
+ disconnect: () => Promise.resolve(),
59
+ });
60
+ /**
61
+ * Create a mock DockerClient layer with customizable behavior.
62
+ */
63
+ export const createMockDockerClient = (overrides = {}) => Layer.succeed(DockerClient, {
64
+ _tag: 'DockerClient',
65
+ client: {},
66
+ ping: () => Effect.void,
67
+ version: () => Effect.succeed({
68
+ Version: '24.0.0',
69
+ ApiVersion: '1.43',
70
+ Os: 'linux',
71
+ Arch: 'amd64',
72
+ }),
73
+ listContainers: () => Effect.succeed([]),
74
+ getContainer: (id) => createMockContainer({ id, name: id }),
75
+ createContainer: (options) => Effect.succeed(createMockContainer({ id: `mock-${options.name}`, name: options.name })),
76
+ listNetworks: () => Effect.succeed([]),
77
+ getNetwork: (id) => createMockNetwork(id),
78
+ createNetwork: (options) => Effect.succeed(createMockNetwork(`mock-${options.Name}`, options.Name)),
79
+ getImage: (name) => ({
80
+ inspect: () => Promise.resolve({ Id: name }),
81
+ remove: () => Promise.resolve(),
82
+ }),
83
+ listImages: () => Effect.succeed([]),
84
+ pullImage: () => Effect.void,
85
+ ...overrides,
86
+ });
87
+ // ============ Mock RateLimiter ============
88
+ /**
89
+ * Create a mock RateLimiter that doesn't limit.
90
+ */
91
+ export const MockDockerRateLimiter = Layer.succeed(DockerRateLimiter, {
92
+ _tag: 'DockerRateLimiter',
93
+ withPermit: (effect) => effect,
94
+ withPermits: () => (effect) => effect,
95
+ available: () => Effect.succeed(Infinity),
96
+ });
97
+ // ============ Mock CircuitBreaker ============
98
+ /**
99
+ * Create a mock CircuitBreaker that never opens.
100
+ */
101
+ export const MockCircuitBreaker = Layer.succeed(CircuitBreaker, {
102
+ _tag: 'CircuitBreaker',
103
+ protect: (effect) => effect,
104
+ getState: () => Effect.succeed('Closed'),
105
+ reset: () => Effect.void,
106
+ getFailures: () => Effect.succeed(0),
107
+ });
108
+ // ============ Mock ResourceTracker ============
109
+ /**
110
+ * Create a mock ResourceTracker that tracks in memory.
111
+ */
112
+ export const createMockResourceTracker = () => Effect.gen(function* () {
113
+ const resources = yield* Ref.make(new Map());
114
+ return {
115
+ _tag: 'ResourceTracker',
116
+ register: (resource) => Ref.update(resources, (map) => {
117
+ const newMap = new Map(map);
118
+ newMap.set(resource.id, resource);
119
+ return newMap;
120
+ }),
121
+ unregister: (id) => Ref.update(resources, (map) => {
122
+ const newMap = new Map(map);
123
+ newMap.delete(id);
124
+ return newMap;
125
+ }),
126
+ get: (id) => Ref.get(resources).pipe(Effect.map((map) => Option.fromNullable(map.get(id)))),
127
+ getAll: () => Ref.get(resources).pipe(Effect.map((map) => [...map.values()])),
128
+ getByType: (type) => Ref.get(resources).pipe(Effect.map((map) => [...map.values()].filter((r) => r.type === type))),
129
+ count: () => Ref.get(resources).pipe(Effect.map((map) => map.size)),
130
+ destroyAll: () => Ref.set(resources, new Map()).pipe(Effect.asVoid),
131
+ destroyByType: (_type) => Effect.void,
132
+ clear: () => Ref.set(resources, new Map()),
133
+ };
134
+ });
135
+ export const MockResourceTracker = Layer.effect(ResourceTracker, createMockResourceTracker());
136
+ // ============ Mock EventBus ============
137
+ /**
138
+ * Create a mock EventBus that collects events.
139
+ */
140
+ export const createMockEventBus = () => Effect.gen(function* () {
141
+ const events = yield* Ref.make([]);
142
+ const pubsub = yield* PubSub.unbounded();
143
+ const service = {
144
+ _tag: 'EventBus',
145
+ publish: (event) => Effect.all([
146
+ Ref.update(events, (arr) => [...arr, event]),
147
+ PubSub.publish(pubsub, event),
148
+ ]).pipe(Effect.map(() => true)),
149
+ subscribe: () => Effect.gen(function* () {
150
+ const queue = yield* PubSub.subscribe(pubsub);
151
+ return Stream.fromQueue(queue);
152
+ }),
153
+ subscribeContainers: () => Effect.gen(function* () {
154
+ const queue = yield* PubSub.subscribe(pubsub);
155
+ return Stream.fromQueue(queue).pipe(Stream.filter((e) => e._tag === 'ContainerEvent'));
156
+ }),
157
+ subscribeNetworks: () => Effect.gen(function* () {
158
+ const queue = yield* PubSub.subscribe(pubsub);
159
+ return Stream.fromQueue(queue).pipe(Stream.filter((e) => e._tag === 'NetworkEvent'));
160
+ }),
161
+ subscribeDatabases: () => Effect.gen(function* () {
162
+ const queue = yield* PubSub.subscribe(pubsub);
163
+ return Stream.fromQueue(queue).pipe(Stream.filter((e) => e._tag === 'DatabaseEvent'));
164
+ }),
165
+ subscriberCount: () => Effect.succeed(0),
166
+ };
167
+ return {
168
+ service,
169
+ getEvents: () => Ref.get(events),
170
+ };
171
+ });
172
+ // ============ Mock ReadinessCoordinator ============
173
+ /**
174
+ * Create a mock ReadinessCoordinator.
175
+ */
176
+ export const createMockReadinessCoordinator = (preReady = []) => Effect.gen(function* () {
177
+ const state = yield* Ref.make(new Map(preReady.map((id) => [id, true])));
178
+ return {
179
+ _tag: 'ReadinessCoordinator',
180
+ waitForReady: (containerId, _healthCheck, _checkFn, _options) => Ref.update(state, (map) => {
181
+ const newMap = new Map(map);
182
+ newMap.set(containerId, true);
183
+ return newMap;
184
+ }).pipe(Effect.asVoid),
185
+ isReady: (containerId) => Ref.get(state).pipe(Effect.map((map) => map.get(containerId) ?? false)),
186
+ markReady: (containerId) => Ref.update(state, (map) => {
187
+ const newMap = new Map(map);
188
+ newMap.set(containerId, true);
189
+ return newMap;
190
+ }),
191
+ markNotReady: (containerId) => Ref.update(state, (map) => {
192
+ const newMap = new Map(map);
193
+ newMap.set(containerId, false);
194
+ return newMap;
195
+ }),
196
+ untrack: (containerId) => Ref.update(state, (map) => {
197
+ const newMap = new Map(map);
198
+ newMap.delete(containerId);
199
+ return newMap;
200
+ }),
201
+ getAll: () => Ref.get(state).pipe(Effect.map((map) => [...map.entries()].map(([containerId, ready]) => ({
202
+ containerId,
203
+ ready,
204
+ timestamp: new Date(),
205
+ })))),
206
+ };
207
+ });
208
+ // ============ Mock HarpoonConfig ============
209
+ /**
210
+ * Create a test configuration with fast timeouts.
211
+ */
212
+ export const createMockHarpoonConfig = (overrides = {}) => Layer.succeed(HarpoonConfig, {
213
+ _tag: 'HarpoonConfig',
214
+ dockerSocket: undefined,
215
+ defaultTimeout: Duration.millis(5000),
216
+ cleanupTimeout: Duration.millis(1000),
217
+ logLevel: 'debug',
218
+ parallelLimit: 2,
219
+ retryAttempts: 1,
220
+ retryDelay: Duration.millis(100),
221
+ dockerStartupTimeout: Duration.millis(5000),
222
+ ...overrides,
223
+ });
224
+ export const MockHarpoonConfig = createMockHarpoonConfig();
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Cross-Runtime Process Utilities
3
+ *
4
+ * Provides process execution utilities that work with Node.js, Bun, and Deno.
5
+ * Uses Node.js child_process module which is compatible across all major runtimes.
6
+ */
7
+ export interface SpawnResult {
8
+ exitCode: number;
9
+ stdout: string;
10
+ stderr: string;
11
+ }
12
+ /**
13
+ * Convert a readable stream to a string.
14
+ */
15
+ export declare function streamToString(stream: NodeJS.ReadableStream): Promise<string>;
16
+ /**
17
+ * Execute a command and capture output.
18
+ * Works with Node.js, Bun, and Deno (via Node compat).
19
+ */
20
+ export declare function execCommand(command: string, args: string[], options?: {
21
+ captureStdout?: boolean;
22
+ captureStderr?: boolean;
23
+ }): Promise<SpawnResult>;
24
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE;IACP,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CACpB,GACL,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Cross-Runtime Process Utilities
3
+ *
4
+ * Provides process execution utilities that work with Node.js, Bun, and Deno.
5
+ * Uses Node.js child_process module which is compatible across all major runtimes.
6
+ */
7
+ import { spawn } from 'child_process';
8
+ /**
9
+ * Convert a readable stream to a string.
10
+ */
11
+ export function streamToString(stream) {
12
+ return new Promise((resolve, reject) => {
13
+ const chunks = [];
14
+ stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
15
+ stream.on('error', reject);
16
+ stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
17
+ });
18
+ }
19
+ /**
20
+ * Execute a command and capture output.
21
+ * Works with Node.js, Bun, and Deno (via Node compat).
22
+ */
23
+ export function execCommand(command, args, options = {}) {
24
+ return new Promise((resolve, reject) => {
25
+ const proc = spawn(command, args, {
26
+ stdio: [
27
+ 'ignore',
28
+ options.captureStdout ? 'pipe' : 'ignore',
29
+ options.captureStderr ? 'pipe' : 'ignore',
30
+ ],
31
+ });
32
+ let stdout = '';
33
+ let stderr = '';
34
+ if (proc.stdout) {
35
+ proc.stdout.on('data', (data) => {
36
+ stdout += data.toString();
37
+ });
38
+ }
39
+ if (proc.stderr) {
40
+ proc.stderr.on('data', (data) => {
41
+ stderr += data.toString();
42
+ });
43
+ }
44
+ proc.on('error', reject);
45
+ proc.on('close', (exitCode) => {
46
+ resolve({ exitCode: exitCode ?? 1, stdout, stderr });
47
+ });
48
+ });
49
+ }
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@docker-harpoon/core",
3
- "version": "0.1.3",
4
- "license": "MIT",
3
+ "version": "0.1.5",
5
4
  "description": "Core Docker resource primitives and binding interface",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
6
9
  "type": "module",
7
10
  "main": "./dist/index.js",
8
11
  "types": "./dist/index.d.ts",
@@ -10,11 +13,12 @@
10
13
  ".": {
11
14
  "types": "./dist/index.d.ts",
12
15
  "import": "./dist/index.js"
16
+ },
17
+ "./testing": {
18
+ "types": "./dist/testing/index.d.ts",
19
+ "import": "./dist/testing/index.js"
13
20
  }
14
21
  },
15
- "files": [
16
- "dist"
17
- ],
18
22
  "scripts": {
19
23
  "build": "tsgo",
20
24
  "typecheck": "tsgo --noEmit",
@@ -23,9 +27,9 @@
23
27
  "dependencies": {
24
28
  "dockerode": "^4.0.9",
25
29
  "effect": "^3.19.14",
26
- "zod": "^4.3.5"
30
+ "zod": "^3.23.8"
27
31
  },
28
- "peerDependencies": {
29
- "bun": ">=1.0.0"
32
+ "devDependencies": {
33
+ "@effect/vitest": "^0.10.0"
30
34
  }
31
35
  }