Skip to content

Commit

Permalink
Add lazy provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Gnuk committed Oct 11, 2024
1 parent 8bf53d0 commit 6045f05
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 18 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ Classic usage:
```typescript
import { key, piqure } from './Piqure';

const { provide, inject } = piqure();
const { provide, provideLazy, inject } = piqure();

// Create an injection key with a type and a description
const KEY_TO_INJECT = key<string>('Key to inject');

// Register a text using provide
provide(KEY_TO_INJECT, 'Injected text');

// Register a text using provideLazy
provideLazy(KEY_TO_INJECT, () => 'Injected text');

// Use your text
const injected = inject(KEY_TO_INJECT);

Expand All @@ -40,7 +43,7 @@ import { piqure } from './Piqure';

const memory = new Map();

const { provide, inject } = piqure(memory); // Now, the injected values will be inside "memory"
const { provide, provideLazy, inject } = piqure(memory); // Now, the injected values will be inside "memory"

// […]
```
Expand All @@ -50,7 +53,7 @@ Attach piqure to a wrapper like `window`:
```typescript
import { piqureWrapper } from './Piqure';

const { provide, inject } = piqureWrapper(window, 'piqure'); // This will reuse or create a "piqure" field
const { provide, provideLazy, inject } = piqureWrapper(window, 'piqure'); // This will reuse or create a "piqure" field

// […]
```
Expand All @@ -60,9 +63,9 @@ To expose `provide` and `inject` globally with the `window` (if you're in a brow
```typescript
import { piqureWrapper } from './Piqure';

const { provide, inject } = piqureWrapper(window, 'piqure');
const { provide, provideLazy, inject } = piqureWrapper(window, 'piqure');

export { provide, inject };
export { provide, provideLazy, inject };
```

## Contribute
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "piqure",
"version": "2.0.0",
"version": "2.1.0",
"description": "A library to provide and inject for JavaScript and TypeScript",
"main": "dist/Piqure.js",
"typings": "src/Piqure.ts",
Expand Down
26 changes: 26 additions & 0 deletions src/Piqure.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,30 @@ describe('Piqure', () => {
expect(wrapper.memory.size).toBe(5);
});
});

describe('Lazy Provider', () => {
it('should get lazy value', () => {
const { provideLazy, inject } = piqure();
const LAZY_KEY = key('Lazy key');
provideLazy(LAZY_KEY, () => 'Lazy value');

expect(inject(LAZY_KEY)).toBe('Lazy value');
});

it('should get value once', () => {
const { provideLazy, inject } = piqure();
let count = 0;
const LAZY_KEY = key('Lazy key');
const lazyProvider = (): number => {
count++;
return 42;
};
provideLazy(LAZY_KEY, lazyProvider);

inject(LAZY_KEY);
inject(LAZY_KEY);

expect(count).toBe(1);
});
});
});
22 changes: 12 additions & 10 deletions src/Piqure.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
// Used to infer type on a symbol
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export interface InjectionKey<T> extends Symbol {} // eslint-disable-line @typescript-eslint/no-empty-interface,@typescript-eslint/no-unused-vars,@typescript-eslint/ban-types
import { InjectionKey, Provide, ProvideLazy } from './Providing';
import { LazyValue, StaticValue, Value } from './Value';

export type ProvidePair<T> = [InjectionKey<T>, T];

type Provide = <T>(key: InjectionKey<T>, injected: T) => void;

interface Piqure {
provide: Provide;
provideLazy: ProvideLazy;
inject: <T>(key: InjectionKey<T>) => T;
provides: (list: ProvidePair<unknown>[]) => void;
}

export const piqureWrapper = (wrapper: object, field: string): Piqure => {
const wrapperTyped = wrapper as Record<string, Map<unknown, unknown>>;
const wrapperTyped = wrapper as Record<string, Map<unknown, Value<unknown>>>;

wrapperTyped[field] = wrapperTyped[field] ?? new Map();

return piqure(wrapperTyped[field]);
};

export const piqure = (memory: Map<unknown, unknown> = new Map()): Piqure => {
export const piqure = (memory: Map<unknown, Value<unknown>> = new Map()): Piqure => {
const provide: Provide = (key, injected) => {
memory.set(key, injected);
memory.set(key, new StaticValue(injected));
};

const provideLazy: ProvideLazy = (key, provider) => {
memory.set(key, new LazyValue(provider));
};

return {
provide,
provideLazy,
inject<T>(key: InjectionKey<T>): T {
if (!memory.has(key)) {
throw new Error(`The key ${key.toString()} is not provided`);
}
return memory.get(key) as T;
return memory.get(key)?.get() as T;
},
provides(list) {
list.forEach(([key, value]) => provide(key, value));
Expand Down
7 changes: 7 additions & 0 deletions src/Providing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Used to infer type on a symbol
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export interface InjectionKey<T> extends Symbol {} // eslint-disable-line @typescript-eslint/no-empty-interface,@typescript-eslint/no-unused-vars,@typescript-eslint/ban-types
export type Provider<T> = () => T;
export type Provide = <T>(key: InjectionKey<T>, injected: T) => void;
export type ProvideLazy = <T>(key: InjectionKey<T>, injected: Provider<T>) => void;
35 changes: 35 additions & 0 deletions src/Value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Provider } from './Providing';

const ABSENT = Symbol('Absent');

export interface Value<T> {
get(): T;
}

export class StaticValue<T> implements Value<T> {
readonly #value: T;

constructor(value: T) {
this.#value = value;
}

get(): T {
return this.#value;
}
}

export class LazyValue<T> implements Value<T> {
readonly #provider: Provider<T>;
#memory: T | typeof ABSENT = ABSENT;

constructor(provider: Provider<T>) {
this.#provider = provider;
}

get(): T {
if (this.#memory === ABSENT) {
this.#memory = this.#provider();
}
return this.#memory;
}
}

0 comments on commit 6045f05

Please sign in to comment.