Просмотр исходного кода

chore: move dioc out of monorepo

Andrew Bastin 1 год назад
Родитель
Сommit
957641fb0f

+ 0 - 24
packages/dioc/.gitignore

@@ -1,24 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?

+ 0 - 141
packages/dioc/README.md

@@ -1,141 +0,0 @@
-# dioc
-
-A small and lightweight dependency injection / inversion of control system.
-
-### About
-
-`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.
-
-### Demo
-
-```ts
-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 values
-export class TodoService extends Service<TodoServiceEvent> {
-  public static ID = "TODO_SERVICE"
-
-  // Inject persistence service into this service
-  private readonly persistence = this.bind(PersistenceService)
-
-  public todos = []
-
-  // Service constructors cannot have arguments
-  constructor() {
-    super()
-
-    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 in
-const 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
-```
-
-### Demo (Unit Test)
-
-`dioc/testing` contains `TestContainer` which lets you bind mocked services to the container.
-
-```ts
-import { TestContainer } from "dioc/testing"
-import { TodoService, PersistenceService } from "./demo.ts" // The above demo code snippet
-import { describe, it, expect, vi } from "vitest"
-
-describe("TodoService", () => {
-  it("addTodo writes to persistence", () => {
-    const container = new TestContainer()
-
-    const writeFn = vi.fn()
-
-    // The first parameter is the service to mock and the second parameter
-    // is the mocked service fields and functions
-    container.bindMock(PersistenceService, {
-      read: () => undefined, // Not really important for this test
-      write: writeFn,
-    })
-
-    // the peristence service bind in TodoService will now use the
-    // above defined mocked implementation
-    const todoService = container.bind(TodoService)
-
-    todoService.addTodo("sup")
-
-    expect(writeFn).toHaveBeenCalledOnce()
-    expect(writeFn).toHaveBeenCalledWith("todos", JSON.stringify(["sup"]))
-  })
-})
-```
-
-### Demo (Vue)
-
-`dioc/vue` contains a Vue Plugin and a `useService` composable that allows Vue components to use the defined services.
-
-In the app entry point:
-
-```ts
-import { createApp } from "vue"
-import { diocPlugin } from "dioc/vue"
-
-const app = createApp()
-
-app.use(diocPlugin, {
-  container: new Container(), // You can pass in the container you want to provide to the components here
-})
-```
-
-In your Vue components:
-
-```vue
-<script setup>
-import { TodoService } from "./demo.ts" // The above demo
-import { useService } from "dioc/vue"
-
-const todoService = useService(TodoService) // Returns an instance of the TodoService class
-</script>
-```

+ 0 - 2
packages/dioc/index.d.ts

@@ -1,2 +0,0 @@
-export { default } from "./dist/main.d.ts"
-export * from "./dist/main.d.ts"

+ 0 - 147
packages/dioc/lib/container.ts

@@ -1,147 +0,0 @@
-import { Service } from "./service"
-import { Observable, Subject } from 'rxjs'
-
-/**
- * Stores the current container instance in the current operating context.
- *
- * NOTE: This should not be used outside of dioc library code
- */
-export let currentContainer: Container | null = null
-
-/**
- * The events emitted by the container
- *
- * `SERVICE_BIND` - emitted when a service is bound to the container directly or as a dependency to another service
- * `SERVICE_INIT` - emitted when a service is initialized
- */
-export type ContainerEvent =
-  | {
-    type: 'SERVICE_BIND';
-
-    /** The Service ID of the service being bounded (the dependency) */
-    boundeeID: string;
-
-    /**
-     * The Service ID of the bounder that is binding the boundee (the dependent)
-     *
-     * NOTE: This will be undefined if the service is bound directly to the container
-     */
-    bounderID: string | undefined
-  }
-  | {
-    type: 'SERVICE_INIT';
-
-    /** The Service ID of the service being initialized */
-    serviceID: string
-  }
-
-/**
- * The dependency injection container, allows for services to be initialized and maintains the dependency trees.
- */
-export class Container {
-  /** Used during the `bind` operation to detect circular dependencies */
-  private bindStack: string[] = []
-
-  /** The map of bound services to their IDs */
-  protected boundMap = new Map<string, Service<unknown>>()
-
-  /** The RxJS observable representing the event stream */
-  protected event$ = new Subject<ContainerEvent>()
-
-  /**
-   * Returns whether a container has the given service bound
-   * @param service The service to check for
-   */
-  public hasBound<
-    T extends typeof Service<any> & { ID: string }
-  >(service: T): boolean {
-    return this.boundMap.has(service.ID)
-  }
-
-  /**
-   * Returns the service bound to the container with the given ID or if not found, undefined.
-   *
-   * NOTE: This is an advanced method and should not be used as much as possible.
-   *
-   * @param serviceID The ID of the service to get
-   */
-  public getBoundServiceWithID(serviceID: string): Service<unknown> | undefined {
-    return this.boundMap.get(serviceID)
-  }
-
-  /**
-   * Binds a service to the container. This is equivalent to marking a service as a dependency.
-   * @param service The class reference of a service to bind
-   * @param bounder The class reference of the service that is binding the service (if bound directly to the container, this should be undefined)
-   */
-  public bind<T extends typeof Service<any> & { ID: string }>(
-    service: T,
-    bounder: ((typeof Service<T>) & { ID: string }) | undefined = undefined
-  ): InstanceType<T> {
-    // We need to store the current container in a variable so that we can restore it after the bind operation
-    const oldCurrentContainer = currentContainer;
-    currentContainer = this;
-
-    // If the service is already bound, return the existing instance
-    if (this.hasBound(service)) {
-      this.event$.next({
-        type: 'SERVICE_BIND',
-        boundeeID: service.ID,
-        bounderID: bounder?.ID // Return the bounder ID if it is defined, else assume its the container
-      })
-
-      return this.boundMap.get(service.ID) as InstanceType<T> // Casted as InstanceType<T> because service IDs and types are expected to match
-    }
-
-    // Detect circular dependency and throw error
-    if (this.bindStack.findIndex((serviceID) => serviceID === service.ID) !== -1) {
-      const circularServices = `${this.bindStack.join(' -> ')} -> ${service.ID}`
-
-      throw new Error(`Circular dependency detected.\nChain: ${circularServices}`)
-    }
-
-    // Push the service ID onto the bind stack to detect circular dependencies
-    this.bindStack.push(service.ID)
-
-    // Initialize the service and emit events
-
-    // NOTE: We need to cast the service to any as TypeScript thinks that the service is abstract
-    const instance: Service<any> = new (service as any)()
-
-    this.boundMap.set(service.ID, instance)
-
-    this.bindStack.pop()
-
-    this.event$.next({
-      type: 'SERVICE_INIT',
-      serviceID: service.ID,
-    })
-
-    this.event$.next({
-      type: 'SERVICE_BIND',
-      boundeeID: service.ID,
-      bounderID: bounder?.ID
-    })
-
-
-    // Restore the current container
-    currentContainer = oldCurrentContainer;
-
-    // We expect the return type to match the service definition
-    return instance as InstanceType<T>
-  }
-
-  /**
-   * Returns an iterator of the currently bound service IDs and their instances
-   */
-  public getBoundServices(): IterableIterator<[string, Service<any>]> {
-    return this.boundMap.entries()
-  }
-
-  /**
-   * Returns the public container event stream
-   */
-  public getEventStream(): Observable<ContainerEvent> {
-    return this.event$.asObservable()
-  }
-}

+ 0 - 2
packages/dioc/lib/main.ts

@@ -1,2 +0,0 @@
-export * from "./container"
-export * from "./service"

+ 0 - 65
packages/dioc/lib/service.ts

@@ -1,65 +0,0 @@
-import { Observable, Subject } from 'rxjs'
-import { Container, currentContainer } from './container'
-
-/**
- * A Dioc service that can bound to a container and can bind dependency services.
- *
- * NOTE: Services cannot have a constructor that takes arguments.
- *
- * @template EventDef The type of events that can be emitted by the service. These will be accessible by event streams
- */
-export abstract class Service<EventDef = {}> {
-
-  /**
-   * The internal event stream of the service
-   */
-  private event$ = new Subject<EventDef>()
-
-  /** The container the service is bound to */
-  #container: Container
-
-  constructor() {
-    if (!currentContainer) {
-      throw new Error(
-        `Tried to initialize service with no container (ID: ${ (this.constructor as any).ID })`
-      )
-    }
-
-    this.#container = currentContainer
-  }
-
-  /**
-   * Binds a dependency service into this service.
-   * @param service The class reference of the service to bind
-   */
-  protected bind<T extends typeof Service<any> & { ID: string }>(service: T): InstanceType<T> {
-    if (!currentContainer) {
-      throw new Error('No currentContainer defined.')
-    }
-
-    return currentContainer.bind(service, this.constructor as typeof Service<any> & { ID: string })
-  }
-
-  /**
-   * Returns the container the service is bound to
-   */
-  protected getContainer(): Container {
-    return this.#container
-  }
-
-  /**
-   * Emits an event on the service's event stream
-   * @param event The event to emit
-   */
-  protected emit(event: EventDef) {
-    this.event$.next(event)
-  }
-
-  /**
-   * Returns the event stream of the service
-   */
-  public getEventStream(): Observable<EventDef> {
-
-    return this.event$.asObservable()
-  }
-}

+ 0 - 33
packages/dioc/lib/testing.ts

@@ -1,33 +0,0 @@
-import { Container, Service } from "./main";
-
-/**
- * A container that can be used for writing tests, contains additional methods
- * for binding suitable for writing tests. (see `bindMock`).
- */
-export class TestContainer extends Container {
-
-  /**
-   * Binds a mock service to the container.
-   *
-   * @param service
-   * @param mock
-   */
-  public bindMock<
-    T extends typeof Service<any> & { ID: string },
-    U extends Partial<InstanceType<T>>
-  >(service: T, mock: U): U {
-    if (this.boundMap.has(service.ID)) {
-      throw new Error(`Service '${service.ID}' already bound to container. Did you already call bindMock on this ?`)
-    }
-
-    this.boundMap.set(service.ID, mock as any)
-
-    this.event$.next({
-      type: "SERVICE_BIND",
-      boundeeID: service.ID,
-      bounderID: undefined,
-    })
-
-    return mock
-  }
-}

+ 0 - 34
packages/dioc/lib/vue.ts

@@ -1,34 +0,0 @@
-import { Plugin, inject } from "vue"
-import { Container } from "./container"
-import { Service } from "./service"
-
-const VUE_CONTAINER_KEY = Symbol()
-
-// TODO: Some Vue version issue with plugin generics is breaking type checking
-/**
- * The Vue Dioc Plugin, this allows the composables to work and access the container
- *
- * NOTE: Make sure you add `vue` as dependency to be able to use this plugin (duh)
- */
-export const diocPlugin: Plugin = {
-  install(app, { container }) {
-    app.provide(VUE_CONTAINER_KEY, container)
-  }
-}
-
-/**
- * A composable that binds a service to a Vue Component
- *
- * @param service The class reference of the service to bind
- */
-export function useService<
-  T extends typeof Service<any> & { ID: string }
->(service: T): InstanceType<T> {
-  const container = inject(VUE_CONTAINER_KEY) as Container | undefined | null
-
-  if (!container) {
-    throw new Error("Container not found, did you forget to install the dioc plugin?")
-  }
-
-  return container.bind(service)
-}

+ 0 - 54
packages/dioc/package.json

@@ -1,54 +0,0 @@
-{
-  "name": "dioc",
-  "private": true,
-  "version": "0.1.0",
-  "type": "module",
-  "files": [
-    "dist",
-    "index.d.ts"
-  ],
-  "main": "./dist/counter.umd.cjs",
-  "module": "./dist/counter.js",
-  "types": "./index.d.ts",
-  "exports": {
-    ".": {
-      "types": "./dist/main.d.ts",
-      "require": "./dist/index.cjs",
-      "import": "./dist/index.js"
-    },
-    "./vue": {
-      "types": "./dist/vue.d.ts",
-      "require": "./dist/vue.cjs",
-      "import": "./dist/vue.js"
-    },
-    "./testing": {
-      "types": "./dist/testing.d.ts",
-      "require": "./dist/testing.cjs",
-      "import": "./dist/testing.js"
-    }
-  },
-  "scripts": {
-    "dev": "vite",
-    "build": "vite build && tsc --emitDeclarationOnly",
-    "prepare": "pnpm run build",
-    "test": "vitest run",
-    "do-test": "pnpm run test",
-    "test:watch": "vitest"
-  },
-  "devDependencies": {
-    "typescript": "^4.9.4",
-    "vite": "^4.0.4",
-    "vitest": "^0.29.3"
-  },
-  "dependencies": {
-    "rxjs": "^7.8.1"
-  },
-  "peerDependencies": {
-    "vue": "^3.2.25"
-  },
-  "peerDependenciesMeta": {
-    "vue": {
-      "optional": true
-    }
-  }
-}

+ 0 - 262
packages/dioc/test/container.spec.ts

@@ -1,262 +0,0 @@
-import { it, expect, describe, vi } from "vitest"
-import { Service } from "../lib/service"
-import { Container, currentContainer, ContainerEvent } from "../lib/container"
-
-class TestServiceA extends Service {
-  public static ID = "TestServiceA"
-}
-
-class TestServiceB extends Service {
-  public static ID = "TestServiceB"
-
-  // Marked public to allow for testing
-  public readonly serviceA = this.bind(TestServiceA)
-}
-
-describe("Container", () => {
-  describe("getBoundServiceWithID", () => {
-    it("returns the service instance if it is bound to the container", () => {
-      const container = new Container()
-
-      const service = container.bind(TestServiceA)
-
-      expect(container.getBoundServiceWithID(TestServiceA.ID)).toBe(service)
-    })
-
-    it("returns undefined if the service is not bound to the container", () => {
-      const container = new Container()
-
-      expect(container.getBoundServiceWithID(TestServiceA.ID)).toBeUndefined()
-    })
-  })
-
-  describe("bind", () => {
-    it("correctly binds the service to it", () => {
-      const container = new Container()
-
-      const service = container.bind(TestServiceA)
-
-      // @ts-expect-error getContainer is defined as a protected property, but we are leveraging it here to check
-      expect(service.getContainer()).toBe(container)
-    })
-
-    it("after bind, the current container is set back to its previous value", () => {
-      const originalValue = currentContainer
-
-      const container = new Container()
-      container.bind(TestServiceA)
-
-      expect(currentContainer).toBe(originalValue)
-    })
-
-    it("dependent services are registered in the same container", () => {
-      const container = new Container()
-
-      const serviceB = container.bind(TestServiceB)
-
-      // @ts-expect-error getContainer is defined as a protected property, but we are leveraging it here to check
-      expect(serviceB.serviceA.getContainer()).toBe(container)
-    })
-
-    it("binding an already initialized service returns the initialized instance (services are singletons)", () => {
-      const container = new Container()
-
-      const serviceA = container.bind(TestServiceA)
-      const serviceA2 = container.bind(TestServiceA)
-
-      expect(serviceA).toBe(serviceA2)
-    })
-
-    it("binding a service which is a dependency of another service returns the same instance created from the dependency resolution (services are singletons)", () => {
-      const container = new Container()
-
-      const serviceB = container.bind(TestServiceB)
-      const serviceA = container.bind(TestServiceA)
-
-      expect(serviceB.serviceA).toBe(serviceA)
-    })
-
-    it("binding an initialized service as a dependency returns the same instance", () => {
-      const container = new Container()
-
-      const serviceA = container.bind(TestServiceA)
-      const serviceB = container.bind(TestServiceB)
-
-      expect(serviceB.serviceA).toBe(serviceA)
-    })
-
-    it("container emits an init event when an uninitialized service is initialized via bind and event only called once", () => {
-      const container = new Container()
-
-      const serviceFunc = vi.fn<
-        [ContainerEvent & { type: "SERVICE_INIT" }],
-        void
-      >()
-
-      container.getEventStream().subscribe((ev) => {
-        if (ev.type === "SERVICE_INIT") {
-          serviceFunc(ev)
-        }
-      })
-
-      const instance = container.bind(TestServiceA)
-
-      expect(serviceFunc).toHaveBeenCalledOnce()
-      expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
-        type: "SERVICE_INIT",
-        serviceID: TestServiceA.ID,
-      })
-    })
-
-    it("the bind event emitted has an undefined bounderID when the service is bound directly to the container", () => {
-      const container = new Container()
-
-      const serviceFunc = vi.fn<
-        [ContainerEvent & { type: "SERVICE_BIND" }],
-        void
-      >()
-
-      container.getEventStream().subscribe((ev) => {
-        if (ev.type === "SERVICE_BIND") {
-          serviceFunc(ev)
-        }
-      })
-
-      container.bind(TestServiceA)
-
-      expect(serviceFunc).toHaveBeenCalledOnce()
-      expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
-        type: "SERVICE_BIND",
-        boundeeID: TestServiceA.ID,
-        bounderID: undefined,
-      })
-    })
-
-    it("the bind event emitted has the correct bounderID when the service is bound to another service", () => {
-      const container = new Container()
-
-      const serviceFunc = vi.fn<
-        [ContainerEvent & { type: "SERVICE_BIND" }],
-        void
-      >()
-
-      container.getEventStream().subscribe((ev) => {
-        // We only care about the bind event of TestServiceA
-        if (ev.type === "SERVICE_BIND" && ev.boundeeID === TestServiceA.ID) {
-          serviceFunc(ev)
-        }
-      })
-
-      container.bind(TestServiceB)
-
-      expect(serviceFunc).toHaveBeenCalledOnce()
-      expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
-        type: "SERVICE_BIND",
-        boundeeID: TestServiceA.ID,
-        bounderID: TestServiceB.ID,
-      })
-    })
-  })
-
-  describe("hasBound", () => {
-    it("returns true if the given service is bound to the container", () => {
-      const container = new Container()
-
-      container.bind(TestServiceA)
-
-      expect(container.hasBound(TestServiceA)).toEqual(true)
-    })
-
-    it("returns false if the given service is not bound to the container", () => {
-      const container = new Container()
-
-      expect(container.hasBound(TestServiceA)).toEqual(false)
-    })
-
-    it("returns true when the service is bound because it is a dependency of another service", () => {
-      const container = new Container()
-
-      container.bind(TestServiceB)
-
-      expect(container.hasBound(TestServiceA)).toEqual(true)
-    })
-  })
-
-  describe("getEventStream", () => {
-    it("returns an observable which emits events correctly when services are initialized", () => {
-      const container = new Container()
-
-      const serviceFunc = vi.fn<
-        [ContainerEvent & { type: "SERVICE_INIT" }],
-        void
-      >()
-
-      container.getEventStream().subscribe((ev) => {
-        if (ev.type === "SERVICE_INIT") {
-          serviceFunc(ev)
-        }
-      })
-
-      container.bind(TestServiceB)
-
-      expect(serviceFunc).toHaveBeenCalledTimes(2)
-      expect(serviceFunc).toHaveBeenNthCalledWith(1, <ContainerEvent>{
-        type: "SERVICE_INIT",
-        serviceID: TestServiceA.ID,
-      })
-      expect(serviceFunc).toHaveBeenNthCalledWith(2, <ContainerEvent>{
-        type: "SERVICE_INIT",
-        serviceID: TestServiceB.ID,
-      })
-    })
-
-    it("returns an observable which emits events correctly when services are bound", () => {
-      const container = new Container()
-
-      const serviceFunc = vi.fn<
-        [ContainerEvent & { type: "SERVICE_BIND" }],
-        void
-      >()
-
-      container.getEventStream().subscribe((ev) => {
-        if (ev.type === "SERVICE_BIND") {
-          serviceFunc(ev)
-        }
-      })
-
-      container.bind(TestServiceB)
-
-      expect(serviceFunc).toHaveBeenCalledTimes(2)
-      expect(serviceFunc).toHaveBeenNthCalledWith(1, <ContainerEvent>{
-        type: "SERVICE_BIND",
-        boundeeID: TestServiceA.ID,
-        bounderID: TestServiceB.ID,
-      })
-      expect(serviceFunc).toHaveBeenNthCalledWith(2, <ContainerEvent>{
-        type: "SERVICE_BIND",
-        boundeeID: TestServiceB.ID,
-        bounderID: undefined,
-      })
-    })
-  })
-
-  describe("getBoundServices", () => {
-    it("returns an iterator over all services bound to the container in the format [service id, service instance]", () => {
-      const container = new Container()
-
-      const instanceB = container.bind(TestServiceB)
-      const instanceA = instanceB.serviceA
-
-      expect(Array.from(container.getBoundServices())).toEqual([
-        [TestServiceA.ID, instanceA],
-        [TestServiceB.ID, instanceB],
-      ])
-    })
-
-    it("returns an empty iterator if no services are bound", () => {
-      const container = new Container()
-
-      expect(Array.from(container.getBoundServices())).toEqual([])
-    })
-  })
-})

Некоторые файлы не были показаны из-за большого количества измененных файлов