Dependency Injection Architecture

March 10, 2026 · View on GitHub

Technical overview of the Dependency Injection system implemented in Debian Time Capsule.

System Design

The Dependency Injection architecture addresses legacy coupling issues by introducing interface-based abstractions and a centralized service container. This enables testability, flexibility, and adherence to SOLID principles.

Core Components

Directory Structure

src/scripts/core/
├── interfaces/              # Segregated interfaces (ISP)
│   ├── window-manager.interface.ts
│   ├── settings-manager.interface.ts
│   ├── filesystem.interface.ts
│   ├── audio-manager.interface.ts
│   └── index.ts
├── adapters/               # Legacy code adapters
│   ├── vfs.adapter.ts
│   ├── audiomanager.adapter.ts
│   ├── settingsmanager.adapter.ts
│   └── index.ts
├── container.ts            # DI Container
├── container.init.ts       # Initialization
├── cde-namespace.ts        # Consolidated namespace
└── README.md

Service Container

The ServiceContainer manages service registration and resolution:

class ServiceContainer {
  private services = new Map<string, any>();
  private factories = new Map<string, () => any>();
  private singletons = new Set<string>();

  register<T>(key: string, factory: () => T, singleton: boolean = true): void;
  registerInstance<T>(key: string, instance: T): void;
  get<T>(key: string): T;
  has(key: string): boolean;
  clear(): void;
}

Interface Segregation

Monolithic interfaces have been split following the Interface Segregation Principle.

Filesystem Interfaces:

interface IFileReader {
  getNode(path: string): VFSNode | null;
  getChildren(path: string): Record<string, VFSNode> | null;
  exists(path: string): boolean;
  getSize(path: string): number;
}

interface IFileWriter {
  touch(path: string, name: string): Promise<void>;
  mkdir(path: string, name: string): Promise<void>;
  writeFile(path: string, content: string): Promise<void>;
}

interface IFileOperations {
  rm(path: string, name: string): Promise<boolean>;
  rename(path: string, oldName: string, newName: string): Promise<void>;
  move(oldPath: string, newPath: string): Promise<void>;
  copy(sourcePath: string, destPath: string): Promise<void>;
}

interface ITrashManager {
  moveToTrash(path: string): Promise<void>;
  restoreFromTrash(name: string): Promise<void>;
}

interface IFileSearch {
  search(basePath: string, query: string, recursive?: boolean): Promise<string[]>;
}

Audio Interfaces:

interface IBasicAudio {
  beep(frequency?: number, duration?: number): void;
  click(): void;
}

interface ISystemSounds {
  error(): void;
  success(): void;
  notification(): void;
}

interface IWindowSounds {
  windowOpen(): void;
  windowClose(): void;
  windowMinimize(): void;
  windowMaximize(): void;
  windowShade(): void;
}

interface IMenuSounds {
  menuOpen(): void;
  menuClose(): void;
}

interface IMusicPlayer {
  playMelody(notes: Note[]): Promise<void>;
  playStartupChime(): void;
  playThemeMelody(): void;
}

Adapters

Adapters wrap legacy implementations to conform to new interfaces:

class VFSAdapter implements IFileReader, IFileWriter, IFileOperations, ITrashManager, IFileSearch {
  getNode(path: string): VFSNode | null {
    return VFS.getNode(path);
  }
  // Delegates to legacy VFS
}

Consolidated Namespace

window.CDE = {
  core: {
    fs: IFileSystem,
    audio: IAudioManager,
    settings: ISettingsManager
  },
  ui: {
    modal: CDEModal
  },
  apps: {
    fileManager: () => void,
    emacs: () => void,
  }
}

Usage

Accessing Services

import { container } from '@/scripts/core/container';
import type { IFileReader } from '@/scripts/core/interfaces';

// Get service with specific type
const fs = container.get<IFileReader>('fileReader');
const node = fs.getNode('/home/user/file.txt');

Global Namespace

// Access from anywhere
const node = window.CDE.core.fs.getNode('/home/user/file.txt');
window.CDE.core.audio.beep();
window.CDE.ui.modal.open('Info', 'Hello World');

Available Services

KeyTypeDescription
fsIFileReader & IFileWriter & ...Complete filesystem
fileReaderIFileReaderRead-only operations
fileWriterIFileWriterWrite operations
fileOperationsIFileOperationsMove, copy, delete operations
trashManagerITrashManagerTrash management
fileSearchIFileSearchFile search
audioIBasicAudio & ISystemSounds & ...Complete audio system
basicAudioIBasicAudioBasic sounds
systemSoundsISystemSoundsSystem sounds
windowSoundsIWindowSoundsWindow sounds
settingsISettingsManagerConfiguration
sessionStorageISessionStorageSession management

Migration Guide

Before

import { VFS } from '@/scripts/core/vfs';
import { AudioManager } from '@/scripts/core/audiomanager';

class MyComponent {
  render() {
    const node = VFS.getNode('/path');
    AudioManager.beep();
  }
}

After

import { container } from '@/scripts/core/container';
import type { IFileReader, IBasicAudio } from '@/scripts/core/interfaces';

class MyComponent {
  private fs: IFileReader;
  private audio: IBasicAudio;

  constructor() {
    this.fs = container.get<IFileReader>('fileReader');
    this.audio = container.get<IBasicAudio>('basicAudio');
  }

  render() {
    const node = this.fs.getNode('/path');
    this.audio.beep();
  }
}

Testing with Mocks

import { describe, it, expect, beforeEach } from 'vitest';
import { container } from '@/scripts/core/container';
import type { IFileReader } from '@/scripts/core/interfaces';

class MockFileReader implements IFileReader {
  getNode(path: string) {
    return { type: 'file', content: 'mock' };
  }
  getChildren() {
    return {};
  }
  exists() {
    return true;
  }
  getSize() {
    return 100;
  }
}

describe('MyComponent', () => {
  beforeEach(() => {
    container.registerInstance('fileReader', new MockFileReader());
  });

  it('should render', () => {
    const component = new MyComponent();
    component.render();
  });
});

Backward Compatibility

Legacy APIs remain available for compatibility:

// Legacy (deprecated)
window.VirtualFS.getNode('/path');
window.AudioManager.beep();

// New (recommended)
window.CDE.core.fs.getNode('/path');
window.CDE.core.audio.beep();

Legacy APIs will be removed in future versions.

Further Reading