From 245efbe4def6a1c0647cfc6c06c299968ad0eec9 Mon Sep 17 00:00:00 2001 From: Dirk Luijk Date: Sat, 28 Sep 2024 21:18:34 +0200 Subject: [PATCH] docs: add Vitepress site with Github Pages (#18) Release-As: 0.5.0 --- .github/workflows/{main.yml => ci.yml} | 3 - .github/workflows/deploy.yml | 57 + .../{release-please.yml => release.yml} | 6 +- .gitignore | 3 +- README.md | 505 +---- docs/.vitepress/config.ts | 66 + docs/.vitepress/theme/index.ts | 17 + docs/.vitepress/theme/style.css | 139 ++ docs/advanced/async-injection.md | 71 + docs/advanced/inheritance.md | 106 ++ docs/advanced/injection-tokens.md | 0 docs/advanced/multi-injection.md | 46 + docs/advanced/optional-injection.md | 34 + docs/advanced/providers.md | 0 docs/advanced/tree-shaking.md | 78 + docs/concepts/binding.md | 68 + docs/concepts/containers.md | 69 + docs/concepts/injection.md | 74 + docs/concepts/providers.md | 124 ++ docs/concepts/tokens.md | 98 + docs/getting-started.md | 48 + docs/index.md | 31 + docs/what-is-needle-di.md | 61 + package-lock.json | 1620 ++++++++++++++++- package.json | 9 +- src/examples.test.ts | 30 +- 26 files changed, 2840 insertions(+), 523 deletions(-) rename .github/workflows/{main.yml => ci.yml} (56%) create mode 100644 .github/workflows/deploy.yml rename .github/workflows/{release-please.yml => release.yml} (92%) create mode 100644 docs/.vitepress/config.ts create mode 100644 docs/.vitepress/theme/index.ts create mode 100644 docs/.vitepress/theme/style.css create mode 100644 docs/advanced/async-injection.md create mode 100644 docs/advanced/inheritance.md create mode 100644 docs/advanced/injection-tokens.md create mode 100644 docs/advanced/multi-injection.md create mode 100644 docs/advanced/optional-injection.md create mode 100644 docs/advanced/providers.md create mode 100644 docs/advanced/tree-shaking.md create mode 100644 docs/concepts/binding.md create mode 100644 docs/concepts/containers.md create mode 100644 docs/concepts/injection.md create mode 100644 docs/concepts/providers.md create mode 100644 docs/concepts/tokens.md create mode 100644 docs/getting-started.md create mode 100644 docs/index.md create mode 100644 docs/what-is-needle-di.md diff --git a/.github/workflows/main.yml b/.github/workflows/ci.yml similarity index 56% rename from .github/workflows/main.yml rename to .github/workflows/ci.yml index 85ed22e..5de403f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,3 @@ -# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - name: Node.js CI on: [push] diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..8b2c6f2 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,57 @@ +name: Deploy VitePress site to Pages + +on: + push: + branches: [main] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + name: Build docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm # or pnpm / yarn + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Install dependencies + run: npm ci + - name: Build with VitePress + run: npm run docs:build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + name: Deploy docs + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release.yml similarity index 92% rename from .github/workflows/release-please.yml rename to .github/workflows/release.yml index c204dc0..185241f 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,5 @@ +name: Release + on: push: branches: @@ -7,8 +9,6 @@ permissions: contents: write pull-requests: write -name: release-please - jobs: release-please: runs-on: ubuntu-latest @@ -34,4 +34,4 @@ jobs: - run: npm publish env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - if: ${{ steps.release.outputs.release_created }} \ No newline at end of file + if: ${{ steps.release.outputs.release_created }} diff --git a/.gitignore b/.gitignore index 905a47d..2f18e1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ dist node_modules .idea -coverage \ No newline at end of file +coverage +cache diff --git a/README.md b/README.md index 0693d4d..b7ff191 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![NPM version](http://img.shields.io/npm/v/needle-di.svg?style=flat-square)](https://www.npmjs.com/package/needle-di) [![NPM downloads](http://img.shields.io/npm/dm/needle-di.svg?style=flat-square)](https://www.npmjs.com/package/needle-di) -[![Build status](https://github.com/dirkluijk/needle-di/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/dirkluijk/needle-di/actions/workflows/main.yml) +[![Build status](https://github.com/dirkluijk/needle-di/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/dirkluijk/needle-di/actions/workflows/main.yml) Needle DI is a lightweight, TypeScript-first library for dependency injection (DI). It is designed to be both easy to use and highly efficient. @@ -12,7 +12,7 @@ Needle DI is a lightweight, TypeScript-first library for dependency injection (D - Stand-alone: No additional dependencies required - Intended for both JavaScript-only and TypeScript projects -- Supports [tree-shakeable injection tokens](#tree-shakeable-injection-tokens): optimize your builds for production. +- Supports [tree-shakeable injection tokens](https://needle-di.io/advanced/tree-shaking.html): optimize your builds for production. - Inspired by [Angular](https://angular.dev/) and [InversifyJS](https://github.com/inversify/InversifyJS), familiar to developers coming from these frameworks. - Uses native [ECMAScript TC39 decorators](https://github.com/tc39/proposal-decorators) (currently stage 3) - No need for `experimentalDecorators` and `emitDecoratorMetadata` @@ -46,27 +46,7 @@ const barService = container.get(BarService); // ^? Type will be inferred as `BarService` ``` -If you don't need to interact with the DI container at all, you can also use the `bootstrap()` shorthand function -instead. -This will internally create a new container and return the constructed service: - -```typescript -import { bootstrap } from "needle-di"; - -const barService = bootstrap(BarService); -``` - -Check out the [usage](#usage) section below to learn more! - -## Not (yet) supported - -- Transient scope (although you could provide a factory function instead) -- Nested DI containers -- Unbinding providers - -Please refer to the [usage](#usage) section below to see what's included! - -If you want to request a missing feature, feel free to submit an issue to explain your use case. +Check out the [docs](https://needle-di.io/concepts/binding.html) to learn more! ## Installation @@ -74,484 +54,9 @@ If you want to request a missing feature, feel free to submit an issue to explai npm install needle-di ``` -## Usage - -### Auto-binding (using decorators) - -The easiest way to use Needle DI is by using the `@injectable()` decorator. - -```typescript -import { injectable } from "needle-di"; - -@injectable() -class FooService { - // ... -} -``` - -This will automatically bind `FooService` as a singleton service. - -Its construction is lazy: it will only be created when you request it from the container. The first time a `FooService` -is injected, a -new instance is constructed, and it will reuse this instance whenever it needs to be injected again. - -> Note: since Needle DI uses native [ECMAScript TC39 decorators](https://github.com/tc39/proposal-decorators) -> (which is currently in Stage 3), you will need to transpile your code in order to use it in a browser or in Node.JS. -> -> All modern transpilers (including TypeScript, Esbuild, Webpack, Babel) do have support for decorators. If you don't -> want to depend on transpilation, you can bind your services [manually](#manual-binding) instead, without using -> decorators. - -### Creating the DI container - -The dependency injection container will keep track of all bindings and hold the actual instances of your services. To -create it, simply construct one: - -```typescript -import { Container } from "needle-di"; - -const container = new Container(); -``` - -### Injection - -To obtain something from the container, you can use `container.get(token)`: - -```typescript -const fooService = container.get(FooService); -// ^? Type will be inferred as `FooService` -``` - -This is useful when you need something outside of a class, but will require a reference to your container. - -> If you don't know what a **token** is: consider it the unique reference for your binding. In this case, its just the -> class reference, but there are many more tokens possible. Read more about [injection tokens](#injection-tokens) below. - -In most cases however, you probably want to inject your dependencies inside a class constructor. -Instead of using `container.get(token)`, you can use the `inject()` function here: - -```typescript -import { inject, injectable } from "needle-di"; - -@injectable() -class MyService { - constructor( - private fooService = inject(FooService), - // ^? Type will be inferred as `FooService` - private barService = inject(BarService), - // ^? Type will be inferred as `BarService` - ) {} - - // ... -} -``` - -Note that the `inject()` function is only available in the "injection context": - -- During construction of a class being instantiated by the DI container; -- In the initializer for fields of such classes; -- In a factory function specified for `useFactory` of a provider; -- In the `factory` function specified for an `InjectionToken`; - -> Needle DI uses **default parameter values** for constructor injection. This maximizes type-safety and removes the need -> for parameter decorators, which aren't yet standardized in ECMAScript. - -### Manual binding - -To manually bind (register) something to your DI container, you will need to understand the concepts: - -- A **service** is whatever you want the DI container to create; -- A **token** is the unique reference for that service; -- A **provider** states how the service should be created. - -For example: - -```typescript -import { Container } from "needle-di"; - -const container = new Container(); - -container.bind({ - provide: MyService, - useValue: new MyService(), -}); -``` - -In this case, `MyService` is the token, and `useValue: new MyService()` is the provider and states how the value will be -created. - -### Types of providers - -There are different types of providers: - -#### 1. Class provider - -A class provider refers to a class constructor, which will be used construct a new instance. - -```typescript -container.bind({ - provide: Logger, - useClass: Logger, -}); -``` - -This example can also be written with the shorthand: - -```typescript -container.bind(Logger); -``` - -This will register a singleton for `Logger` that gets lazily constructed. - -However, `useClass` may also refer to a child class of `Logger`: - -```typescript -container.bind({ - provide: Logger, - useClass: FileLogger, -}); -``` - -Check out [inheritance support](#inheritance-support) for more information. - -#### 2. Value provider - -A value provider refers to a static value. - -```typescript -container.bind({ - provide: MyService, - useValue: new MyService(), -}); -``` - -This will bind the provided value to the token. This value will act as a singleton and will be reused. -Note that this value is created, regardless of whether it is used. - -#### 3. Factory provider - -A factory provider refers to a factory function, which will only be invoked when this token gets injected for its first -time. -This makes it ideal for lazy evaluation. - -```typescript -container.bind({ - provide: MyService, - useFactory: () => new MyService(), -}); -``` - -The value returned by the function will act as a singleton and will be reused. - -Note that you can use the `inject()` function inside this factory function, allowing you to inject other dependencies: - -```typescript -container.bind({ - provide: MyService, - useFactory: () => new MyService(inject(FooService), inject(BarService)), -}); -``` - -#### 4. Existing provider - -An existing provider is a special provider that refers to another provider, by specifying its token. -This basically works like an alias. This can be useful for inheritance or [injection tokens](#injection-tokens). - -```typescript -container.bind({ - provide: MyValidator, - useClass: MyValidator, -}); - -container.bind({ - provide: VALIDATOR, - useExisting: MyValidator, -}); -``` - -In this case, both `inject(MyValidator)` and `inject(VALIDATOR)` would inject the same instance. - -### Injection tokens - -In most of the examples above, we used a class reference as token. -However, this is not always suitable, for example, when you want to provide a primitive value or object literal. - -> Note that TypeScript interfaces only exist compile-time and **cannot** be used as a token. - -In such cases, you could use a `string` (or a `symbol`): - -```typescript -container.bind({ - provide: "my-magic-number", - useValue: 42, -}); - -container.bind({ - provide: "my-config", - useFactory: { - foo: "bar", - }, -}); - -const myNumber = container.get("my-magic-number"); -const myConfig = container.get("my-config"); -``` - -However, in this case it will not infer the types, unless you provide the generic type yourself. This can easily -lead to inconsistency and mistakes. - -A better alternative is to use an `InjectionToken`. This is basically a unique token object, holding the generic -type. - -```typescript -import { InjectionToken } from "needle-di"; - -const MY_NUMBER = new InjectionToken("MY_NUMBER"); -const MY_CONFIG = new InjectionToken("MY_CONFIG"); - -container.bind({ - provide: MY_NUMBER, - useValue: 42, - // ^? should be `number` -}); - -container.bind({ - provide: MY_CONFIG, - useValue: { foo: "bar" }, - // ^? should be `MyConfig` -}); - -const myNumber = container.get(MY_NUMBER); -// ^? Type will be inferred as `number` -const myConfig = container.get(MY_CONFIG); -// ^? Type will be inferred as `MyConfig` -``` - -This maximizes type-safety since both `container.bind()`, `container.get()` and `inject()` will check and infer the -types associated with the injection token. - -This is not the only benefit: it also enables tree-shakable tokens. - -### Tree-shakeable injection tokens - -There is also the option to provide a `factory` function in your `InjectionToken`: - -```typescript -import { InjectionToken } from "needle-di"; - -const MY_NUMBER = new InjectionToken("MY_NUMBER", { - factory: () => 42, -}); - -const MY_CONFIG = new InjectionToken("MY_CONFIG", { - factory: () => ({ - foo: "bar", - }), -}); - -const myNumber = container.get(MY_NUMBER); -const myConfig = container.get(MY_CONFIG); -``` - -First, this will enable auto-binding: there is no need anymore to manually register these tokens in your container. -Since it holds a factory function, the container can automatically bind and construct them when you request them -for the first time. - -Furthermore, it will also make your tokens **tree-shakeable**: if you use a build tool that supports tree-shaking, -and there are no references to your injection token, everything associated with your token will be removed from your -transpiled code. This is useful if your factory function creates something with a heavy bundle size, e.g. -something from a big library. - -### Optional injection - -By default, when you try to inject something that isn't provided, it will throw an error. - -Alternatively, you can use optional injection, by passing `{ optional: true }`. Instead of throwing an error, it will -now return `undefined`: - -```typescript -import { inject } from "needle-di"; - -class MyService { - constructor( - private fooService = inject(FooService), - // ^? Type will be inferred as `FooService` - private barService = inject(BarService, { optional: true }), - // ^? Type will be inferred as `BarService | undefined` - ) {} -} -``` - -When you construct an instance of `MyService` manually outside the injection context, and you don't pass any argument -for -`barService`, the `inject()` function will automatically return `undefined` since it is optional: - -```typescript -const myService = new MyService(new FooService()); // will cause no issues -``` - -### Multi-injection - -By default, when you reuse an existing token in a binding, it will overwrite any previous binding. - -However, it is also possible to register multiple values for the same token: - -```typescript -import { Container } from "needle-di"; - -const container = new Container(); - -container.bind({ - provide: FooService, - multi: true, - factory: () => new FooService(123, "abc"), -}); - -container.bind({ - provide: FooService, - multi: true, - factory: () => new FooService(456, "def"), -}); -``` - -To inject both instances, you can pass `{ multi: true }` to the `inject()` function: - -```typescript -class MyService { - constructor( - private fooServices = inject(FooService, { multi: true }), - // ^? Type will be inferred as `FooService[]` - ) {} -} -``` - -There are some rules associated with multi-providers: - -- It is not possible to intermix providers with `multi: false` and `multi: true` for the same token. -- When you specify only a single provider with `multi: true`, you can still inject it as a single instance. -- When you specify multiple providers with `multi: true`, it will throw an error when you try to inject a single - instance. -- When you try to inject with `multi: true` and `optional: true`, and there are no providers, it will still - return `undefined` instead of an empty array. - -### Inheritance support - -Given the following class structure: - -```typescript -abstract class ExampleService { - /* ... */ -} - -@injectable() -class FooService extends ExampleService { - /* ... */ -} - -@injectable() -class BarService extends ExampleService { - /* ... */ -} -``` - -This will automatically bind `FooService` and `BarService`, but it will also automatically -bind two multi-providers for the token `ExampleService`: - -```typescript -const fooService = container.get(FooService); -const barService = container.get(BarService); - -const myServices = container.get(ExampleService, { multi: true }); -// ^? Type will be inferred as `ExampleService[]` -// and will be the same instances as "fooService" and "barService' -``` - -Under the hood, it is the same as: - -```typescript -container.bindAll( - { - provide: FooService, - useClass: FooService, - multi: true, - }, - { - provide: BarService, - useClass: BarService, - multi: true, - }, - { - provide: MyAbstractService, - useExisting: FooService, - multi: true, - }, - { - provide: MyAbstractService, - useExisting: BarService, - multi: true, - }, -); -``` - -This even works with multiple levels of inheritance! - -> Note: this only works with parent **classes**. If you're using TypeScript interfaces instead, you should manually bind -> multi-providers and use injection tokens. - -### Async factory providers (experimental) - -> Note: this feature is currently experimental, and has some limitations. - -It is also possible to use a provider with an async factory function, by passing `async: true`. -You can create a provider that returns a promise, such as an async factory function: - -```typescript -container.bind({ - provide: FooService, - async: true, - useFactory: async () => { - // ... returning some `FooService` here - }, -}); -``` - -Keep in mind, when you want to obtain this from the DI container, you will have to use `container.getAsync(token)` or -`injectAsync(token)`. - -```typescript -const fooService = await container.getAsync(FooService); -``` - -If you try to use `container.get(token)` or `inject(token)` for an async provider, an error will be thrown, -as these methods only support synchronous injection. This restriction also applies if any indirect dependencies are async. - -To handle this, inject a promise and resolve it when needed: - -```typescript -class MyService { - constructor(private getFooService: Promise = injectAsync(FooService)) {} - - async someMethod() { - const fooService: FooService = await this.getFooService; - // ... - } -} -``` - -### Workaround for async injection limitations - -A possible workaround for this restriction is to construct your service eagerly and bind a static value instead: - -```typescript -const fooService = await getFooService(); - -container.bind({ - provide: FooService, - useValue: fooService, -}); -``` +## Docs -By eagerly constructing the service and binding it as a static value, you can avoid the need to work with promises in your constructors. +Check out our docs on our website. ## License diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 0000000..8ae9f26 --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,66 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "Needle DI", + description: "A lightweight, type-safe Dependency Injection (DI) library", + themeConfig: { + siteTitle: 'Needle DI', + search: { + provider: 'local', + }, + + editLink: { + pattern: 'https://github.com/dirkluijk/needle-di/edit/main/docs/:path' + }, + + lastUpdated: { + text: 'Last updated' + }, + + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'Documentation', link: '/concepts/binding' } + ], + + sidebar: [ + { + text: 'Introduction', + items: [ + { text: 'What is Needle DI?', link: '/what-is-needle-di' }, + { text: 'Getting started', link: '/getting-started' }, + ] + }, + { + text: 'Concepts', + items: [ + { text: 'Binding', link: '/concepts/binding' }, + { text: 'Containers', link: '/concepts/containers' }, + { text: 'Injection', link: '/concepts/injection' }, + { text: 'Providers', link: '/concepts/providers' }, + { text: 'Tokens', link: '/concepts/tokens' }, + ] + }, + { + text: 'Advanced', + items: [ + { text: 'Optional injection', link: '/advanced/optional-injection' }, + { text: 'Multi-injection', link: '/advanced/multi-injection' }, + { text: 'Inheritance', link: '/advanced/inheritance' }, + { text: 'Tree-shaking', link: '/advanced/tree-shaking' }, + { text: 'Async injection', link: '/advanced/async-injection' }, + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/dirkluijk/needle-di' } + ], + + footer: { + message: 'Released under the MIT License', + copyright: 'Copyright © 2024 Dirk Luijk' + } + } +}) diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..def4cfc --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,17 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import './style.css' + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }) + }, + enhanceApp({ app, router, siteData }) { + // ... + } +} satisfies Theme diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..cbd263f --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,139 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + + :root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-yellow-1); + --vp-c-brand-2: var(--vp-c-yellow-2); + --vp-c-brand-3: var(--vp-c-yellow-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 140deg, + #f87202 50%, + #e1f15c + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #f87202 50%, + #e1f15c 50% + ); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + diff --git a/docs/advanced/async-injection.md b/docs/advanced/async-injection.md new file mode 100644 index 0000000..961b375 --- /dev/null +++ b/docs/advanced/async-injection.md @@ -0,0 +1,71 @@ +# Async injection (experimental) + +It is also possible to use a provider with an async factory function. + +> [!CAUTION] +> This feature is currently **experimental** and has some limitations. + +## Async factory providers + +All you have to do, is passing `async: true`. This will require you to return a `Promise` in your factory function. +This allows you to also use an `async` function: + +```typescript +container.bind({ + provide: FooService, + async: true, + useFactory: async () => { + // ... returning some `FooService` here + }, +}); +``` + +## `getAsync()` and `injectAsync()` + +When you want to obtain this from the DI container, you will have to use `container.getAsync(token)` or +`injectAsync(token)`. Since this returns a `Promise`, you can use `await` here. + +```typescript +const fooService = await container.getAsync(FooService); +``` + +If you try to use `container.get(token)` or `inject(token)` for an async provider, an error will be thrown, +as these methods only support synchronous injection. This restriction also applies if any indirect dependencies are async. + +## Constructor injection + +Just inject it using `injectAsync()` and resolve the promise when needed: + +```typescript +class MyService { + constructor(private getFooService: Promise = injectAsync(FooService)) {} + + async someMethod() { + const fooService: FooService = await this.getFooService; + // ... + } +} +``` + +## Synchronous constructor injection + +Since Needle DI uses default parameters, and performs no static analysis (e.g. through reflection), it is not possible +to use `inject()` in constructor injection, **even when the class is constructed within an asynchronous `.getAsync()` process**. + +This is because most providers are lazy, and those services will only be constructed on demand. + +A possible workaround for this restriction is to construct your service eagerly and bind a static value instead: + +```typescript +const fooService = await getFooService(); + +container.bind({ + provide: FooService, + useValue: fooService, +}); +``` + +By eagerly constructing the service and binding it as a static value, you can avoid the need to work with promises in your constructors. + +> [!NOTE] +> We know this is suboptimal, and we do have plans to remove this constraint in a future version. diff --git a/docs/advanced/inheritance.md b/docs/advanced/inheritance.md new file mode 100644 index 0000000..0466caa --- /dev/null +++ b/docs/advanced/inheritance.md @@ -0,0 +1,106 @@ + +# Inheritance support + +Needle DI offers extensive support for inheritance, allowing +for abstractions and interfaces. + +## Example + +Given the following class structure: + +```typescript +abstract class ExampleService { + /* ... */ +} + +@injectable() +class FooService extends ExampleService { + /* ... */ +} + +@injectable() +class BarService extends ExampleService { + /* ... */ +} +``` + +This will automatically bind `FooService` and `BarService`, but it will also automatically +bind two multi-providers for the token `ExampleService`: + +```typescript +const fooService = container.get(FooService); +const barService = container.get(BarService); + +const myServices = container.get(ExampleService, { multi: true }); +// ^? Type will be inferred as `ExampleService[]` +// and will be the same instances as "fooService" and "barService' +``` + +## Manual binding + +Under the hood, the example above would be the same as: + +```typescript +container.bindAll( + { + provide: FooService, + useClass: FooService, + multi: true, + }, + { + provide: BarService, + useClass: BarService, + multi: true, + }, + { + provide: MyAbstractService, + useExisting: FooService, + multi: true, + }, + { + provide: MyAbstractService, + useExisting: BarService, + multi: true, + }, +); +``` + +This even works with multiple levels of inheritance. + +## What about interfaces? + +If you're using TypeScript interfaces instead, you should use injection tokens instead. +This is because TypeScript interfaces don't exist at runtime and therefore cannot be used as tokens. + +```typescript +interface Logger { + info(): void; +} + +class FileLogger implements Logger { + info(): void { + // + } +} + +class ConsoleLogger implements Logger { + info(): void { + // + } +} + +const LOGGER = new InjectionToken('LOGGER'); + +container.bindAll( + { + provide: LOGGER, + multi: true, + useClass: FileLogger, + }, + { + provide: LOGGER, + multi: true, + useClass: ConsoleLogger, + }, +); +``` diff --git a/docs/advanced/injection-tokens.md b/docs/advanced/injection-tokens.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/advanced/multi-injection.md b/docs/advanced/multi-injection.md new file mode 100644 index 0000000..9942637 --- /dev/null +++ b/docs/advanced/multi-injection.md @@ -0,0 +1,46 @@ + +# Multi-injection + +By default, when you bind an existing token again, it will overwrite any previous binding. + +However, it is also possible to register multiple values for the same token: + +```typescript +import { Container } from "needle-di"; + +const container = new Container(); + +container.bind({ + provide: FooService, + multi: true, + factory: () => new FooService(123, "abc"), +}); + +container.bind({ + provide: FooService, + multi: true, + factory: () => new FooService(456, "def"), +}); +``` + +To inject both instances, you can pass `{ multi: true }` to the `inject()` function: + +```typescript +class MyService { + constructor( + private fooServices = inject(FooService, { multi: true }), + // ^? Type will be inferred as `FooService[]` + ) {} +} +``` + +## Behaviour & limitations + +There are some rules associated with multi-providers: + +- It is not allowed to combine multiple providers for the same token with both `multi: false` and `multi: true`. +- When you specify only one provider with `multi: true`, it is still allowed to inject it as a single instance. +- When you specify multiple providers with `multi: true`, it will throw an error when you try to inject a single + instance. +- When you try to inject with `multi: true` and `optional: true`, and there are no providers, it will still + return `undefined` instead of an empty array. diff --git a/docs/advanced/optional-injection.md b/docs/advanced/optional-injection.md new file mode 100644 index 0000000..7e0ec63 --- /dev/null +++ b/docs/advanced/optional-injection.md @@ -0,0 +1,34 @@ + +# Optional injection + +By default, when you try to inject something that isn't provided, Needle DI will throw an error. + +Alternatively, you can use optional injection, by passing `{ optional: true }`. Instead of throwing an error, it will +now return `undefined`: + +```typescript +import { inject } from "needle-di"; + +class MyService { + constructor( + private fooService = inject(FooService), + // ^? Type will be inferred as `FooService` + private barService = inject(BarService, { optional: true }), + // ^? Type will be inferred as `BarService | undefined` + ) {} +} +``` + +## Outside the injection context + +When you construct an instance of `MyService` manually outside the injection context, and you don't pass any argument +for an optional dependency, the `inject()` function will not throw an error, but gracefully return `undefined` instead: + +```typescript +class MyService { + constructor( + private barService = inject(BarService, { optional: true }), + ) {} +} +const myService = new MyService(); // will cause no issues +``` diff --git a/docs/advanced/providers.md b/docs/advanced/providers.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/advanced/tree-shaking.md b/docs/advanced/tree-shaking.md new file mode 100644 index 0000000..bef38ef --- /dev/null +++ b/docs/advanced/tree-shaking.md @@ -0,0 +1,78 @@ +# Tree-shaking + +Tree-shaking is an optimization technique used in JavaScript bundlers (like [esbuild], [Webpack] or [Rollup]) to remove +unused or dead code from the final bundle. Needle DI has been designed with this in mind. + +[esbuild]: https://esbuild.github.io/api/#tree-shaking + +[Webpack]: https://webpack.js.org/guides/tree-shaking/ + +[Rollup]: https://rollupjs.org/introduction/#tree-shaking + +## Tree-shaking: how does it work? + +It works by analyzing the `import` and `export` statements of modules and ensuring that +only the parts of code actually used in the application are included. By "shaking off" unused code, tree-shaking reduces +the +bundle size, improves load times, and enhances overall performance of the application. + +## Tree-shakeable injection tokens + +Let's imagine a library with a class `SomeHeavyClass` that depends on a lot of code, resulting in a larger bundle size. + +Since we cannot decorate this class, the alternative is to bind it using +an [injection token](/concepts/tokens#injectiontoken-t): + +```typescript +import { InjectionToken } from "needle-di"; + +const MY_TOKEN = new InjectionToken("MY_TOKEN"); + +const container = new Container(); + +container.bind({ + provide: MY_TOKEN, + useFactory: () => new SomeHeavyClass() +}); +``` + +However, if there is an entry point in which there are no other references to `MY_TOKEN` and `SomeHeavyClass`, it will +NOT be tree-shaken. + +This is because it is still referred by the container itself. + +However, there also an option to provide a `factory` function in your `InjectionToken`, +which will remove the need to bind it to your container: + +```typescript +import { InjectionToken } from "needle-di"; + +const MY_TOKEN = new InjectionToken( + "MY_TOKEN", + { // [!code ++] + factory: () => new SomeHeavyClass(), // [!code ++] + } // [!code ++] +); + +const container = new Container(); // [!code --] +// [!code --] +container.bind({ // [!code --] + provide: MY_TOKEN, // [!code --] + useFactory: () => new SomeHeavyClass() // [!code --] +}); // [!code --] +``` + +This basically enables [auto-binding](/concepts/binding#auto-binding): there is no need anymore to manually register +this token in your container. +Since it holds a factory function, the container can automatically construct it when you obtain it for the first time. + +But more importantly, this will make your token **tree-shakeable**: when there are no references to your injection +token, everything associated with your token will be removed from your bundle. + +## When to use? + +This is mainly relevant if you have multiple [entry points](https://esbuild.github.io/api/#entry-points) in the +same codebase, and you create separate bundles for each of them. Or, if you use code splitting (e.g. lazy loaded +modules/chunks) for dynamic imports. + +However, using factory functions in injection tokens can also help you to organize your code in a more modular way. diff --git a/docs/concepts/binding.md b/docs/concepts/binding.md new file mode 100644 index 0000000..35f3906 --- /dev/null +++ b/docs/concepts/binding.md @@ -0,0 +1,68 @@ + +# Binding + +**Binding** is the registration of your services into your dependency injection (DI) container. + +## Auto-binding + +The easiest way to register your class for automatic dependency injection, is by +applying the `@injectable()` decorator to your class: + +```typescript +import { injectable } from "needle-di"; + +@injectable() +class FooService { + // ... +} +``` + +This will automatically bind `FooService` as a singleton service. To request it from your service, +you can use the `.get()` method on the [container](./containers): + +```typescript +import { Container } from "needle-di"; + +const container = new Container(); +const fooService = container.get(FooService); +// ^? Type will be inferred as `FooService` +``` + +* Its construction is **lazy**: it will only be created when you request it from the container. +* It is also a **singleton**: the first time a `FooService` is injected, a new instance is constructed, but it will reuse this instance whenever it needs to be injected again. + +> [!NOTE] +> Since Needle DI uses native [ECMAScript TC39 decorators](https://github.com/tc39/proposal-decorators) +> (which is currently in [stage 3](https://github.com/tc39/proposals#stage-3)), you will need to transpile your code in order to use it in a browser or in Node.JS. +> +> All modern transpilers (including [TypeScript], [esbuild], [Webpack], [Babel]) do have support for stage 3 decorators. If you +> don't want to depend on transpilation, you can bind your services [manually](#manual-binding) instead, without using +> decorators. + +[TypeScript]: https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators +[esbuild]: https://github.com/evanw/esbuild/releases/v0.21.0 +[Webpack]: https://stackoverflow.com/a/37616418/1116452 +[Babel]: https://stackoverflow.com/a/37616418/1116452 + +## Manual binding + +If you don't want to use the `@injectable()` decorator, for example if you don't want to use decorators or you want to bind a +class that you cannot decorate (from another library), you can manually register your service with the `.bind()` method: + +```typescript +import { Container } from "needle-di"; + +const container = new Container(); + +container.bind(FooService); + +const fooService = container.get(FooService); +// ^? Type will be inferred as `FooService` +``` + +This is the same as applying a decorator to `FooService`. + +*** + +There are many different ways to bind services, +check out the section about [providers](./providers) to learn more. diff --git a/docs/concepts/containers.md b/docs/concepts/containers.md new file mode 100644 index 0000000..47a7dbe --- /dev/null +++ b/docs/concepts/containers.md @@ -0,0 +1,69 @@ + +# Containers + +## Creating a container + +The dependency injection (DI) container will keep track of all bindings and hold the actual instances of your services. To +create it, simply construct one: + +```typescript +import { Container } from "needle-di"; + +const container = new Container(); +``` + +Every DI container keeps track of its own service instances separately. + +## Binding services + +You can bind services using the `.bind()` or `.bindAll()` methods: + +```typescript +container + .bind(FooService) + .bind({ + provide: BarService, + useFactory: () => new BarService(), + }); + +container.bindAll( + { + provide: Logger, + useFactory: () => new FileLogger(), + }, + { + provide: AppConfig, + useValue: someConfig, + }, +); +``` + +Learn more about the different kind of [providers](./providers) you can use for binding. + +## Constructing & retrieving services + +To obtain something from the container, you can use `container.get(token)`: + +```typescript +const fooService = container.get(FooService); +// ^? Type will be inferred as `FooService` +``` + +This will create a new `FooService`, or return the existing one if it was requested before. + +> [!NOTE] +> To inject dependencies into classes, use [constructor injection](./injection#constructor-injection) instead. + +## Bootstrap + +If you don't need to interact with the DI container at all, you can also use the `bootstrap()` shorthand function +instead. This will internally create a new container and return the requested service directly: + +```typescript +import { bootstrap } from "needle-di"; + +const barService = bootstrap(BarService); +``` + +This is useful if you solely depend on [auto-binding](/concepts/binding#auto-binding) and/or [three-shakeable injection tokens](/advanced/tree-shaking) +and therefore don't need to register anything manually into your container. diff --git a/docs/concepts/injection.md b/docs/concepts/injection.md new file mode 100644 index 0000000..4f57532 --- /dev/null +++ b/docs/concepts/injection.md @@ -0,0 +1,74 @@ + +# Injection + +In most cases you may want to inject your dependencies inside a class. +There are several ways to do this. + +## Constructor injection + +Needle DI strongly recommends **constructor injection**, since it makes the dependencies +of your class explicit, making it more type-safe, and allows for easier unit testing. + +Instead of using `container.get(token)`, you can use the `inject(token)` function here, +so no reference to an actual container is needed. + +```typescript +import { inject, injectable } from "needle-di"; + +@injectable() +class MyService { + constructor( + private fooService = inject(FooService), + private barService = inject(BarService), + // ^? Type will be inferred as `BarService` + ) {} + + // ... +} +``` + +> [!TIP] +> If you don't know what a **token** is: consider it the unique reference for your binding. In this case, its just the +> class reference, but there are many more tokens possible. +> +> Learn more about [tokens](./tokens). + +> [!NOTE] +> Needle DI uses **default parameter values** for constructor injection. This maximizes type-safety and removes the need +> for parameter decorators, which [aren't yet standardized][parameter decorators] in ECMAScript. +> +> Although experimental parameter decorators allow for static analysis, +> this design was chosen on purpose to reduce complexity and bundle size. + +## Initializer injection + +Alternatively, you can also initialize your dependencies as (private) fields: + +```typescript +import { inject, injectable } from "needle-di"; + +@injectable() +class MyService { + private fooService = inject(FooService); + private barService = inject(BarService); + // ^? Type will be inferred as `BarService` + + // ... +} +``` +Although this is less verbose, this will not allow you to pass in those dependencies when you construct the cass manually, e.g. in unit tests. + +## About the `inject()` function + +Note that the `inject()` function is only available in the "injection context": + +- During construction of a class being instantiated by the DI container; +- In the initializer for fields of such classes; +- In a factory function specified for `useFactory` of a provider; +- In the `factory` function specified for an `InjectionToken`. + +If you try to use this function outside this context, it will throw +an error. This is because Needle DI needs a reference to a DI container when +constructing services globally. + +[parameter decorators]: https://github.com/tc39/proposal-class-method-parameter-decorators diff --git a/docs/concepts/providers.md b/docs/concepts/providers.md new file mode 100644 index 0000000..0f692df --- /dev/null +++ b/docs/concepts/providers.md @@ -0,0 +1,124 @@ +--- +outline: deep +--- + +# Providers + +There are many ways to register your services for dependency injection. + +## Terminology + +In the following section, we will use the following terms: + +- A **service** is whatever you want the DI container to create; +- A **token** is the unique reference for that service; +- A **provider** states how the service should be created. + +::: details Check an example +```typescript +import { Container } from "needle-di"; + +const container = new Container(); + +container.bind({ + provide: MyService, + useValue: new MyService(), +}); +``` + +In this case, `MyService` is the **token**, and `useValue: new MyService()` is the **provider** and states how the +**service** will be created. +::: + +## Types of providers + +There are different types of providers. + +### Class providers + +A class provider refers to a class constructor, which will be used construct a new instance. + +```typescript +container.bind({ + provide: Logger, + useClass: Logger, +}); +``` + +This example can also be written with the shorthand: + +```typescript +container.bind(Logger); +``` + +This will register a singleton for `Logger` that gets lazily constructed. + +However, `useClass` may also refer to a child class of `Logger`: + +```typescript +container.bind({ + provide: Logger, + useClass: FileLogger, +}); +``` + +Check out [inheritance support](/advanced/inheritance) for more information. + +### Value providers + +A value provider refers to a static value. + +```typescript +container.bind({ + provide: MyService, + useValue: new MyService(), +}); +``` + +This will bind the provided value to the token. This value will act as a singleton and will be reused. +Note that this value is created, regardless whether it is used. + +### Factory providers + +A factory provider refers to a factory function, which will only be invoked when this token gets injected for its first +time. +This makes it ideal for lazy evaluation. + +```typescript +container.bind({ + provide: MyService, + useFactory: () => new MyService(), +}); +``` + +The value returned by the function will act as a singleton and will be reused. + +Note that you can use the `inject()` function inside this factory function, allowing you to inject other dependencies: + +```typescript +container.bind({ + provide: MyService, + useFactory: () => new MyService(inject(FooService), inject(BarService)), +}); +``` + +### Existing providers + +An existing provider is a special provider that refers to another provider, by specifying its token. +This basically works like an alias. This can be useful for inheritance or [injection tokens](/concepts/tokens). + +```typescript +container.bind({ + provide: MyValidator, + useClass: MyValidator, +}); + +container.bind({ + provide: VALIDATOR, + useExisting: MyValidator, +}); +``` + +In this case, both `inject(MyValidator)` and `inject(VALIDATOR)` would inject the same instance. + + diff --git a/docs/concepts/tokens.md b/docs/concepts/tokens.md new file mode 100644 index 0000000..37c6414 --- /dev/null +++ b/docs/concepts/tokens.md @@ -0,0 +1,98 @@ +# Tokens + +An injection token is a reference for the dependency injection (DI) container +that refers to a service. When you need to obtain a service from the container, you should use this token. + +Needle DI allows you to use many different types of tokens. + +## Class reference + +When the service that you provide is a class, you can use its class reference as a token. + +```typescript +container.bind({ + provide: FooService, + useValue: new FooService(), +}); +``` + +However, this is not always a viable option. For example, if you want to provide a primitive value or an object literal, +using a class reference as token is not allowed. + +Therefore, Needle DI offers some alternatives. + +> [!NOTE] +> Note that TypeScript interfaces only exist compile-time, and therefore **cannot** be used as an injection token. + +## `string` and `symbol` + +You can also use any `string` or `symbol` as injection token: + +```typescript +// create some tokens +const MY_CONFIG = "my-config"; +const MY_MAGIC_NUMBER = Symbol("my-magic-number"); + +// bind some values using providers +container.bind({ + provide: MY_CONFIG, + useValue: { + foo: "bar", + }, +}); + +container.bind({ + provide: MY_MAGIC_NUMBER, + useValue: 42, +}); + +// retrieve the values by their tokens +const myConfig = container.get(MY_CONFIG); +const myNumber = container.get(MY_MAGIC_NUMBER); +``` + +> [!WARNING] +> When using a `string` or `symbol` as token, Needle DI will not be able to infer its associated type, unless you +> provide the generic type yourself (as shown in the example above). +> +> Note that this can easily lead to inconsistency and mistakes. + +## `InjectionToken` + +Instead of `string` or `symbol`, a better alternative is to construct an instance of `InjectionToken`. This is basically a unique token object, +that is referred by reference. + +> [!TIP] +> When using TypeScript, this token can also hold a generic type. This enables better type-checking. + +```typescript +import { InjectionToken } from "needle-di"; + +// create some injection tokens +const MY_NUMBER = new InjectionToken("MY_NUMBER"); +const MY_CONFIG = new InjectionToken("MY_CONFIG"); + +// bind some values using providers +container.bind({ + provide: MY_NUMBER, + useValue: 42, + // ^? should be `number` +}); + +container.bind({ + provide: MY_CONFIG, + useValue: { foo: "bar" }, + // ^? should be `MyConfig` +}); + +// retrieve the values by their tokens +const myNumber = container.get(MY_NUMBER); +// ^? Type will be inferred as `number` +const myConfig = container.get(MY_CONFIG); +// ^? Type will be inferred as `MyConfig` +``` + +This maximizes type-safety since both `container.bind()`, `container.get()` and `inject()` will check and infer the +types associated with the injection token. + +This is not the only benefit: it also enables [tree-shakable injection tokens](/advanced/tree-shaking). diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..38a9219 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,48 @@ +--- +outline: deep +--- + +# Getting started + +Just install it using your favorite package manager. + +```bash +npm install --save needle-di +``` + +## Basic example + +Here’s a simple example using constructor injection to inject one service into another. + +```typescript +import { injectable, inject } from "needle-di"; + +@injectable() +class FooService { + // ... +} + +@injectable() +class BarService { + constructor(private fooService = inject(FooService)) {} + // ^? Type will be inferred as `FooService` +} +``` +As you can see, Needle DI uses default parameter values for constructor injection. + +The `@injectable` decorator eliminates the need to manually register services. To construct the `BarService`, you have +to create a dependency injection container, and use the `container.get()` method: + +```typescript +import { Container } from "needle-di"; + +const container = new Container(); +const barService = container.get(BarService); +// ^? Type will be inferred as `BarService` +``` + +That's it! + +## What's next? + +Check out the [concepts](/concepts/binding) to learn more and see more examples. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..186e532 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,31 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "Needle DI 💉" + text: | + Dependency injection made easy + tagline: | + A lightweight, type-safe + Dependency Injection library + for JavaScript and TypeScript projects + actions: + - theme: brand + text: What is Needle DI? + link: /what-is-needle-di? + - theme: alt + text: Getting started + link: /getting-started + +features: + - title: Standalone + details: No need to install additional dependencies, such as reflection libraries + - title: Lightweight + details: Small bundle-size, with support for tree-shakeable injection tokens + - title: Modern + details: Written in ESM, suitable for both web and Node.js projects, uses ECMAScript stage 3 decorators + - title: Type-safe + details: TypeScript type definitions included, with smart type inference everywhere +--- + diff --git a/docs/what-is-needle-di.md b/docs/what-is-needle-di.md new file mode 100644 index 0000000..b37a00f --- /dev/null +++ b/docs/what-is-needle-di.md @@ -0,0 +1,61 @@ +--- +outline: deep +--- + +# What is Needle DI? + +Needle DI is a small, lightweight JavaScript library for dependency injection. + +## Why dependency injection? + +Dependency Injection (DI) is a design pattern in (object-oriented) programming where an object's or function's +dependencies are provided to it externally rather than the object creating them itself. In +simpler terms, instead of an object creating its own resources, they are "injected" into the object, usually through its +constructor. + +Using dependency injection will lead to: + +* **Loose coupling**: By injecting dependencies, classes depend on abstractions (like interfaces) instead of specific + implementations. This makes code more flexible and easier to maintain or extend. + +* **Easier testing**: Since dependencies can be injected, you can easily swap real dependencies with mock or fake + objects when + unit testing, allowing for isolated testing. + +* **Better code organization**: DI promotes separation of concerns, meaning each class has a specific role and does not + have + to manage its own dependencies, leading to cleaner and more organized code. + +* **Reusability**: When dependencies are provided externally, classes become more modular and can be reused in different + contexts with different dependencies. + +* **Improved maintainability**: As your codebase grows, the ability to swap dependencies without needing to change core + logic + becomes critical. DI allows you to update, change, or replace dependencies with minimal impact. + +In essence, DI helps in managing the complexity of large systems, improves code quality, and makes it easier to adapt to +changing requirements. + +## Design principles + +### Lightweight + +Needle DI is specifically designed for apps with a small footprint (e.g. serverless functions like AWS lambdas), +by minimizing bundle size by enabling tree-shaking and by not depending on any reflection metadata. + +### Type-safe + +Needle DI is written in TypeScript. You don't have to use TypeScript to use Needle DI, but when you do, +all type definitions are included and will make sure your code is consistent. + +### Modern + +Needle DI is an ESM-only package. This makes it suitable for modern Node.js and web projects. It also +uses stage 3 decorators, to push for ECMAScript standards. + +## When to use Needle DI? + +In most application frameworks for web or Node.js (e.g. [Angular](https://angular.dev/guide/di), [NestJS](https://docs.nestjs.com/providers#dependency-injection)), a dependency injection solution is +already included. Therefore, Needle DI is mainly intended for smaller "vanilla" JavaScript/TypeScript projects. + +In fact, Needle DI was originally created for TypeScript AWS Lambda functions, but you can also use this for small web projects. diff --git a/package-lock.json b/package-lock.json index c618f04..940f6dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,349 @@ "prettier": "^3.3.3", "typescript": "~5.5.0", "typescript-eslint": "^8.6.0", + "vitepress": "^1.3.4", "vitest": "^2.1.1" } }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.24.0" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.24.0" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.6.1.tgz", + "integrity": "sha512-4MGqXqiAyqsUJw+KamKWZO2Gxn9iMpc05vC0vy8+iQRjKRZEDB1a+3Da6CnkWzXa162pJb7a/chDAAKA9rye8A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.6.1.tgz", + "integrity": "sha512-HloeR0Ef29vf2yJc1lhjw1OYial3YgB0f3TQaqqMlSnM/IkAw9TnX1IOYLurnI91apMKggFpA9t8lRp7TGEKEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.6.1", + "@algolia/requester-browser-xhr": "5.6.1", + "@algolia/requester-fetch": "5.6.1", + "@algolia/requester-node-http": "5.6.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/logger-console": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/logger-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.6.1.tgz", + "integrity": "sha512-tY1RW60sGF9sMpxbd8j53IqLLwnkNhrAarVhFfNZzDZNvI8WyzG78W5ZD/SFvtkgNPPSav3T/3LpBT8xBpzbGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.6.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.6.1.tgz", + "integrity": "sha512-4TvR5IodrH+o+ji4ka+VBufWY0GfHr43nFqnDTStabtjspfo4rlcV16x534vvnbfp694oBxrz0SO/Ny8VemvXg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.6.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.6.1.tgz", + "integrity": "sha512-K7tlss87aq6UnWnU8+fPIe+Is9Mvyqwzysp6Ty/HpQ7YNKUU7opgkMOVKxzTwt3fm40NfNX4ENvVKHoYABL6vw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.6.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -90,6 +430,57 @@ "dev": true, "license": "MIT" }, + "node_modules/@docsearch/css": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", + "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.1.tgz", + "integrity": "sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.6.1", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", + "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.6.1", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1003,6 +1394,72 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.19.0.tgz", + "integrity": "sha512-314J5MPdS1wzfjuD856MXvbAI2wN03ofMnUGkZ5ZDBOza/d38paLwd+YVyuKrrjxJ4hfPMjc4tRmPkXd6UDMPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.19.0", + "@shikijs/engine-oniguruma": "1.19.0", + "@shikijs/types": "1.19.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.19.0.tgz", + "integrity": "sha512-D1sioU61n7fLWfDzTC9JNS19zEYZMr7qxkSVzv6ziEWDxnwzy2PvYoKPedJV4qUf+2VnrYPSaArDz2W0XgGB7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.19.0", + "@shikijs/vscode-textmate": "^9.2.2", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.19.0.tgz", + "integrity": "sha512-/JxwIefNVLGB4EmpB8i6P4JB/oVYRuzSixbqvx7m6iPW0lQ1T97c/0wmA+JlKbngEiExckSuPwa48fajlShB7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.19.0", + "@shikijs/vscode-textmate": "^9.2.2" + } + }, + "node_modules/@shikijs/transformers": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.19.0.tgz", + "integrity": "sha512-5h/eQ0jpFCdla9+SSzI6KyppyvCHRCSs5bkKfCgbK79s3rj5zo2bxN9fd4MsX+1ZjDCKkush0Ynrh1iWsps+kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "shiki": "1.19.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.19.0.tgz", + "integrity": "sha512-NZvVp3k1bP4MTRUbmnkGhYzPdoNMjNLSAwczMRUbtUl4oj2LlNRNbwERyeIyJt56Ac9fvPVZ2nn13OXk86E5UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz", + "integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==", + "dev": true, + "license": "MIT" + }, "node_modules/@total-typescript/tsconfig": { "version": "1.0.4", "resolved": "git+ssh://git@github.com/total-typescript/tsconfig.git#a42aef4f2350ac88b0b5d6ce88325481c964188d", @@ -1016,6 +1473,65 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", @@ -1245,6 +1761,27 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", + "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, "node_modules/@vitest/coverage-v8": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.1.tgz", @@ -1392,16 +1929,362 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "node_modules/@vue/compiler-core": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.9.tgz", + "integrity": "sha512-KE1sCdwqSKq0CQ/ltg3XnlMTKeinjegIkuFsuq9DKvNPmqLGdmI51ChZdGBBRXIvEYTLm8X/JxOuBQ1HqF/+PA==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.9", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.9.tgz", + "integrity": "sha512-gEAURwPo902AsJF50vl59VaWR+Cx6cX9SoqLYHu1jq9hDbmQlXvpZyYNIIbxa2JTJ+FD/oBQweVUwuTQv79KTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.9", + "@vue/shared": "3.5.9" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.9.tgz", + "integrity": "sha512-kp9qawcTXakYm0TN6YAwH24IurSywoXh4fWhRbLu0at4UVyo994bhEzJlQn82eiyqtut4GjkQodSfn8drFbpZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.9", + "@vue/compiler-dom": "3.5.9", + "@vue/compiler-ssr": "3.5.9", + "@vue/shared": "3.5.9", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.47", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.9.tgz", + "integrity": "sha512-fb1g2mQv32QzIei76rlXRTz08Grw+ZzBXSQfHo4StGFutm/flyebw3dGJkexKwcU3GjX9s5fIGjEv/cjO8j8Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.9", + "@vue/shared": "3.5.9" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.4.6.tgz", + "integrity": "sha512-XipBV5k0/IfTr0sNBDTg7OBUCp51cYMMXyPxLXJZ4K/wmUeMqt8cVdr2ZZGOFq+si/jTyCYnNxeKoyev5DOUUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.4.6" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.4.6.tgz", + "integrity": "sha512-NbYBwPWgEic1AOd9bWExz9weBzFdjiIfov0yRn4DrRfR+EQJCI9dn4I0XS7IxYGdkmUJi8mFW42LLk18WsGqew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.4.6", + "birpc": "^0.2.17", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.4.6.tgz", + "integrity": "sha512-rPeSBzElnHYMB05Cc056BQiJpgocQjY8XVulgni+O9a9Gr9tNXgPteSzFFD+fT/iWMxNuUgGKs9CuW5DZewfIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.9.tgz", + "integrity": "sha512-88ApgNZ6yPYpyYkTfXzcbWk6O8+LrPRIpa/U4AdeTzpfRUO+EUt5jemnTBVSlAUNmlYY96xa5feUNEq+BouLog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.9" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.9.tgz", + "integrity": "sha512-YAeP0zNkjSl5mEc1NxOg9qoAhLNbREElHAhfYbMXT57oF0ixehEEJWBhg2uvVxslCGh23JhpEAyMvJrJHW9WGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.9", + "@vue/shared": "3.5.9" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.9.tgz", + "integrity": "sha512-5Oq/5oenpB9lw94moKvOHqBDEaMSyDmcu2HS8AtAT6/pwdo/t9fR9aVtLh6FzYGGqZR9yRfoHAN6P7goblq1aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.9", + "@vue/runtime-core": "3.5.9", + "@vue/shared": "3.5.9", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.9.tgz", + "integrity": "sha512-tbuUsZfMWGazR9LXLNiiDSTwkO8K9sLyR70diY+FbQmKmh7236PPz4jkTxymelV8D89IJUGtbfe4VdmpHkmuxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.9", + "@vue/shared": "3.5.9" + }, + "peerDependencies": { + "vue": "3.5.9" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.9.tgz", + "integrity": "sha512-8wiT/m0mnsLhTME0mPgc57jv+4TipRBSAAmheUdYgiOaO6AobZPNOmm87ub4np65VVDgLcWxc+Edc++5Wyz1uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.1.0.tgz", + "integrity": "sha512-P6dk79QYA6sKQnghrUz/1tHi0n9mrb/iO1WTMk/ElLmTyNqgDeSZ3wcDf6fRBGzRJbeG1dxzEOvLENMjr+E3fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "11.1.0", + "@vueuse/shared": "11.1.0", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.1.0.tgz", + "integrity": "sha512-O2ZgrAGPy0qAjpoI2YR3egNgyEqwG85fxfwmA9BshRIGjV4G6yu6CfOPpMHAOoCD+UfsIl7Vb1bXJ6ifrHYDDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "11.1.0", + "@vueuse/shared": "11.1.0", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.1.0.tgz", + "integrity": "sha512-l9Q502TBTaPYGanl1G+hPgd3QX5s4CGnpXriVBR5fEZ/goI6fvDaVmIl3Td8oKFurOxTmbXvBPSsgrd6eu6HYg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.1.0.tgz", + "integrity": "sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { "node": ">=0.4.0" } }, @@ -1432,6 +2315,73 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/algoliasearch": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-account": "4.24.0", + "@algolia/client-analytics": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-personalization": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/recommend": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1482,6 +2432,16 @@ "dev": true, "license": "MIT" }, + "node_modules/birpc": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.17.tgz", + "integrity": "sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1526,6 +2486,17 @@ "node": ">=6" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", @@ -1560,6 +2531,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -1590,6 +2583,17 @@ "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1597,6 +2601,22 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1612,6 +2632,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -1647,6 +2674,30 @@ "dev": true, "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1661,6 +2712,19 @@ "dev": true, "license": "MIT" }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -2002,6 +3066,16 @@ "dev": true, "license": "ISC" }, + "node_modules/focus-trap": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.0.tgz", + "integrity": "sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.2.0" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -2134,12 +3208,68 @@ "node": ">=8" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, - "license": "MIT" + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, "node_modules/ignore": { "version": "5.3.2", @@ -2231,6 +3361,19 @@ "node": ">=8" } }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2444,6 +3587,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2454,6 +3626,100 @@ "node": ">= 8" } }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2491,6 +3757,20 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minisearch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz", + "integrity": "sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2524,6 +3804,19 @@ "dev": true, "license": "MIT" }, + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2648,6 +3941,13 @@ "node": ">= 14.16" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -2697,6 +3997,17 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.24.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.1.tgz", + "integrity": "sha512-PnBAwFI3Yjxxcxw75n6VId/5TFxNW/81zexzWD9jn1+eSrOP84NdsS38H5IkF/UH3frqRPT+MvuCoVHjTDTnDw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2723,6 +4034,17 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2754,6 +4076,13 @@ ], "license": "MIT" }, + "node_modules/regex": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz", + "integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2775,6 +4104,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.22.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", @@ -2835,6 +4171,14 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/search-insights": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.2.tgz", + "integrity": "sha512-zFNpOpUO+tY2D85KrxJ+aqwnIfdEGi06UH2+xEb+Bp9Mwznmauqc9djbnBibJO5mpfUPPa8st6Sx65+vbeO45g==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -2871,6 +4215,21 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.19.0.tgz", + "integrity": "sha512-Ng7Gd6XgWFLsv4Z3so65hOyXjV78qz1M117MuZHwdPQD6fgb5wR2IoLMvSlM/Ml14EXH7n+/YxIpTD74i7kDdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.19.0", + "@shikijs/engine-javascript": "1.19.0", + "@shikijs/engine-oniguruma": "1.19.0", + "@shikijs/types": "1.19.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -2901,6 +4260,27 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -2985,6 +4365,21 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3025,6 +4420,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superjson": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", + "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3038,6 +4446,13 @@ "node": ">=8" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true, + "license": "MIT" + }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -3153,6 +4568,17 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -3217,6 +4643,79 @@ } } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3227,6 +4726,36 @@ "punycode": "^2.1.0" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", @@ -3309,6 +4838,46 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vitepress": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.3.4.tgz", + "integrity": "sha512-I1/F6OW1xl3kW4PaIMC6snxjWgf3qfziq2aqsDoFc/Gt41WbcRv++z8zjw8qGRIJ+I4bUW7ZcKFDHHN/jkH9DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "^3.6.1", + "@docsearch/js": "^3.6.1", + "@shikijs/core": "^1.13.0", + "@shikijs/transformers": "^1.13.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.1.2", + "@vue/devtools-api": "^7.3.8", + "@vue/shared": "^3.4.38", + "@vueuse/core": "^11.0.0", + "@vueuse/integrations": "^11.0.0", + "focus-trap": "^7.5.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.0", + "shiki": "^1.13.0", + "vite": "^5.4.1", + "vue": "^3.4.38" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, "node_modules/vitest": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz", @@ -3374,6 +4943,28 @@ } } }, + "node_modules/vue": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.9.tgz", + "integrity": "sha512-nHzQhZ5cjFKynAY2beAm7XtJ5C13VKAFTLTgRYXy+Id1KEKBeiK6hO2RcW1hUjdbHMadz1YzxyHgQigOC54wug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.9", + "@vue/compiler-sfc": "3.5.9", + "@vue/runtime-dom": "3.5.9", + "@vue/server-renderer": "3.5.9", + "@vue/shared": "3.5.9" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3530,6 +5121,17 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 869798e..3dd213e 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "needle-di", "version": "0.4.0", "description": "A simple TypeScript library for dependency injection", + "license": "MIT", "keywords": [ "dependency injection", "di", @@ -10,7 +11,7 @@ "typescript", "type-safe" ], - "homepage": "https://github.com/dirkluijk/needle-di", + "homepage": "https://needle-di.io", "author": { "name": "Dirk Luijk", "email": "mail@dirkluijk.nl" @@ -39,7 +40,10 @@ "test:ci": "vitest run --silent --coverage", "lint": "eslint src", "lint:fix": "eslint src --fix", - "reformat": "prettier src/**/*.ts README.md --write" + "reformat": "prettier src/**/*.ts README.md --write", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" }, "devDependencies": { "@eslint/js": "^9.10.0", @@ -49,6 +53,7 @@ "prettier": "^3.3.3", "typescript": "~5.5.0", "typescript-eslint": "^8.6.0", + "vitepress": "^1.3.4", "vitest": "^2.1.1" } } diff --git a/src/examples.test.ts b/src/examples.test.ts index 6ad05b4..c161064 100644 --- a/src/examples.test.ts +++ b/src/examples.test.ts @@ -248,26 +248,26 @@ describe("Container", () => { } class BarService extends AbstractService { - constructor() { + constructor(public barProp: string) { super("Bar"); } } class SpecialBarService extends BarService { constructor(public age = 6) { - super(); + super("SpecialBar"); } } class BazService extends AbstractService { - constructor() { + constructor(public bazProp: string) { super("Bar"); } } class SpecialBazService extends BazService { - constructor(public age = 8) { - super(); + constructor(public specialBazProp: string) { + super("SpecialBaz"); } } @@ -710,4 +710,24 @@ describe("Container", () => { const bar = bootstrap(Bar); expect(bar.letFooSayHi()).toBe("Hi!"); }); + + it("should support symbols", () => { + const container = new Container(); + const OTHER_TOKEN = Symbol("other-token"); + + container.bindAll( + { + provide: Symbol.for("my-token"), + useValue: 42, + }, + { + provide: OTHER_TOKEN, + useValue: 2, + }, + ); + + expect(container.get(Symbol.for("my-token"))).toBe(42); + expect(() => container.get(Symbol.for("other-token"))).toThrowError("No provider(s) found for other-token"); + expect(container.get(OTHER_TOKEN)).toBe(2); + }); });