In this article, we review Dioc lbrary in Hoppscotch codebase. We will look at:
What is Hoppscotch?
What is Dioc library?
Dioc usage in Hoppscotch
I study patterns used in an open source project found on Github Trending. For this week, I reviewed some parts of Hoppscotch codebase and wrote this article.
dioc is a really simple DI/IOC system where you write services (which are singletons per container) that can depend on each other and emit events that can be listened upon.
import { Service, Container } from "dioc"// Here is a simple service, which you can define by extending the Service class// and providing an ID static field (of type string)export class PersistenceService extends Service { // This should be unique for each container public static ID = "PERSISTENCE_SERVICE" public read(key: string): string | undefined { // ... } public write(key: string, value: string) { // ... }}type TodoServiceEvent = | { type: "TODO_CREATED"; index: number } | { type: "TODO_DELETED"; index: number }// Services have a built in event system// Define the generic argument to say what are the possible emitted valuesexport class TodoService extends Service<TodoServiceEvent> { public static ID = "TODO_SERVICE" // Inject persistence service into this service private readonly persistence = this.bind(PersistenceService) public todos = [] // Services cannot(*) have constructors, but init logic can be mentioned here override onServiceInit() { this.todos = JSON.parse(this.persistence.read("todos") ?? "[]") } public addTodo(text: string) { // ... // You can access services via the bound fields this.persistence.write("todos", JSON.stringify(this.todos)) // This is how you emit an event this.emit({ type: "TODO_CREATED", index, }) } public removeTodo(index: number) { // ... this.emit({ type: "TODO_DELETED", index, }) }}// Services need a container to run inconst container = new Container()// You can initialize and get services using Container#bind// It will automatically initialize the service (and its dependencies)const todoService = container.bind(TodoService) // Returns an instance of TodoService
/** * Service to manage the downloadable links in the app header. */export class HeaderDownloadableLinksService extends Service implements AdditionalLinkSet{ public static readonly ID = "HEADER_DOWNLOADABLE_LINKS_SERVICE" public readonly linkSetID = "HEADER_DOWNLOADABLE_LINKS" private readonly additionalLinkSet = this.bind(AdditionalLinksService) /** * List of downloadable links to be shown in the header * This includes showing the link to the desktop app, PWA, CLI. */ private headerDownloadableLinks = ref<Link[]>([ macOS, windows, linux, pwa, cli, ]) override onServiceInit() { this.additionalLinkSet.registerAdditionalSet(this) } getLinks(): Ref<Link[]> { // @ts-expect-error show type not recognizing ComputedRef return this.headerDownloadableLinks }}
onServiceInit function calls registerAdditionalSet function and this additionalLinkSet is imported as shown below, at the top:
import { AdditionalLinkSet, AdditionalLinksService, Link,} from "@hoppscotch/common/services/additionalLinks.service"
I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com