# Docker Module - Development Hints ## getContainerById() Bug Fix (2025-11-24 - v5.0.1) ### Problem The `getContainerById()` method had a critical bug where it would create a DockerContainer object from Docker API error responses when a container didn't exist. **Symptoms:** - Calling `docker.getContainerById('invalid-id')` returned a DockerContainer object with `{ message: "No such container: invalid-id" }` - Calling `.logs()` on this invalid container returned "[object Object]" instead of logs or throwing an error - No way to detect the error state without checking for a `.message` property **Root Cause:** The `DockerContainer._fromId()` method made a direct API call to `/containers/{id}/json` and blindly passed `response.body` to the constructor, even when the API returned a 404 error response. ### Solution Changed `DockerContainer._fromId()` to use the **list+filter pattern**, matching the behavior of all other resource getter methods (DockerImage, DockerNetwork, DockerService, DockerSecret): ```typescript // Before (buggy): public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise { const response = await dockerHostArg.request('GET', `/containers/${containerId}/json`); return new DockerContainer(dockerHostArg, response.body); // Creates invalid object from error! } // After (fixed): public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise { const containers = await this._list(dockerHostArg); return containers.find((container) => container.Id === containerId); // Returns undefined if not found } ``` **Benefits:** - 100% consistent with all other resource classes - Type-safe return signature: `Promise` - Cannot create invalid objects - `.find()` naturally returns undefined - Users can now properly check for non-existent containers **Usage:** ```typescript const container = await docker.getContainerById('abc123'); if (container) { const logs = await container.logs(); console.log(logs); } else { console.log('Container not found'); } ``` ## OOP Refactoring - Clean Architecture (2025-11-24) ### Architecture Changes The module has been restructured to follow a clean OOP Facade pattern: - **DockerHost** is now the single entry point for all Docker operations - All resource classes extend abstract `DockerResource` base class - Static methods are prefixed with `_` to indicate internal use - Public API is exclusively through DockerHost methods ### Key Changes **1. Factory Pattern** - All resource creation/retrieval goes through DockerHost: ```typescript // Old (deprecated): const container = await DockerContainer.getContainers(dockerHost); const network = await DockerNetwork.createNetwork(dockerHost, descriptor); // New (clean API): const containers = await dockerHost.listContainers(); const network = await dockerHost.createNetwork(descriptor); ``` **2. Container Management Methods Added** The DockerContainer class now has full CRUD and streaming operations: **Lifecycle:** - `container.start()` - Start container - `container.stop(options?)` - Stop container - `container.remove(options?)` - Remove container - `container.refresh()` - Reload state **Information:** - `container.inspect()` - Get detailed info - `container.logs(options)` - Get logs as string (one-shot) - `container.stats(options)` - Get stats **Streaming & Interactive:** - `container.streamLogs(options)` - Stream logs continuously (follow mode) - `container.attach(options)` - Attach to main process (PID 1) with bidirectional stream - `container.exec(command, options)` - Execute commands in container interactively **Example - Stream Logs:** ```typescript const container = await dockerHost.getContainerById('abc123'); const logStream = await container.streamLogs({ timestamps: true }); logStream.on('data', (chunk) => { console.log(chunk.toString()); }); ``` **Example - Attach to Container:** ```typescript const { stream, close } = await container.attach({ stdin: true, stdout: true, stderr: true }); // Pipe to/from process process.stdin.pipe(stream); stream.pipe(process.stdout); // Later: detach await close(); ``` **Example - Execute Command:** ```typescript const { stream, close } = await container.exec('ls -la /app', { tty: true }); stream.on('data', (chunk) => { console.log(chunk.toString()); }); stream.on('end', async () => { await close(); }); ``` **3. DockerResource Base Class** All resource classes now extend `DockerResource`: - Consistent `dockerHost` property (not `dockerHostRef`) - Required `refresh()` method - Standardized constructor pattern **4. ImageStore Encapsulation** - `dockerHost.imageStore` is now private - Use `dockerHost.storeImage(name, stream)` instead - Use `dockerHost.retrieveImage(name)` instead **5. Creation Descriptors Support Both Primitives and Instances** Interfaces now accept both strings and class instances: ```typescript // Both work: await dockerHost.createService({ image: 'nginx:latest', // String networks: ['my-network'], // String array secrets: ['my-secret'] // String array }); await dockerHost.createService({ image: imageInstance, // DockerImage instance networks: [networkInstance], // DockerNetwork array secrets: [secretInstance] // DockerSecret array }); ``` ### Migration Guide Replace all static method calls with dockerHost methods: - `DockerContainer.getContainers(host)` → `dockerHost.listContainers()` - `DockerImage.createFromRegistry(host, opts)` → `dockerHost.createImageFromRegistry(opts)` - `DockerService.createService(host, desc)` → `dockerHost.createService(desc)` - `dockerHost.imageStore.storeImage(...)` → `dockerHost.storeImage(...)` ## smartrequest v5+ Migration (2025-11-17) ### Breaking Change smartrequest v5.0.0+ returns web `ReadableStream` objects (Web Streams API) instead of Node.js streams. ### Solution Implemented All streaming methods now convert web ReadableStreams to Node.js streams using: ```typescript plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webStream) ``` ### Files Modified - `ts/classes.host.ts`: - `requestStreaming()` - Converts web stream to Node.js stream before returning - `getEventObservable()` - Works with converted Node.js stream - `ts/classes.image.ts`: - `createFromTarStream()` - Uses converted Node.js stream for event handling - `exportToTarStream()` - Uses converted Node.js stream for backpressure management ### Testing - Build:  All 11 type errors resolved - Tests:  Node.js tests pass (DockerHost, DockerContainer, DockerImage, DockerImageStore) ### Notes - The conversion maintains backward compatibility with existing code expecting Node.js stream methods (`.on()`, `.emit()`, `.pause()`, `.resume()`) - smartstream's `nodewebhelpers` module provides bidirectional conversion utilities between web and Node.js streams