|
1 | 1 | import { assert } from '@ember/debug'; |
2 | | -import { service as emberService } from '@ember/service'; |
3 | 2 |
|
| 3 | +import { getPromiseState } from 'reactiveweb/get-promise-state'; |
| 4 | + |
| 5 | +import { createStore } from './store.ts'; |
| 6 | +import { findOwner } from './utils.ts'; |
| 7 | + |
| 8 | +import type { Newable } from './type-utils.ts'; |
| 9 | + |
| 10 | +/* |
4 | 11 | import type { Newable } from './type-utils.ts'; |
5 | 12 | import type { Registry } from '@ember/service'; |
6 | 13 | import type Service from '@ember/service'; |
7 | 14 |
|
8 | 15 | type Decorator = ReturnType<typeof emberService>; |
9 | 16 |
|
10 | | -export function service<Key extends keyof Registry>( |
11 | | - context: object, |
12 | | - serviceName: Key |
13 | | -): Registry[Key] & Service; |
| 17 | +// export function service<Key extends keyof Registry>( |
| 18 | +// context: object, |
| 19 | +// serviceName: Key |
| 20 | +// ): Registry[Key] & Service; |
14 | 21 | export function service<Class extends object>( |
15 | 22 | context: object, |
16 | 23 | serviceDefinition: Newable<Class> |
17 | 24 | ): Class; |
18 | 25 | export function service<Class extends object>(serviceDefinition: Newable<Class>): Decorator; |
19 | | -export function service<Key extends keyof Registry>(serviceName: string): Decorator; |
20 | | -export function service(prototype: object, name: string | symbol, descriptor?: unknown): void; |
| 26 | +export function service<Key extends keyof Registry>(serviceName: Key): Decorator; |
| 27 | +export function service(prototype: object, name: string | symbol, descriptor: unknown): void; |
| 28 | +export function service<Value, Result>( |
| 29 | + context: object, |
| 30 | + fn: Parameters<typeof getPromiseState<Value, Result>>[0] |
| 31 | +): ReturnType<typeof getPromiseState<Value, Result>>; |
| 32 | +export function service<Value, Result>( |
| 33 | + fn: Parameters<typeof getPromiseState<Value, Result>>[0] |
| 34 | +): Decorator; |
| 35 | +*/ |
21 | 36 |
|
22 | 37 | /** |
23 | | - * Lazily instantiate a service |
| 38 | + * Instantiates a class once per application instance. |
| 39 | + * |
| 40 | + * |
| 41 | + */ |
| 42 | +export function createService<Instance extends object>( |
| 43 | + context: object, |
| 44 | + theClass: Newable<Instance> | (() => Instance) |
| 45 | +): Instance { |
| 46 | + const owner = findOwner(context); |
| 47 | + |
| 48 | + assert( |
| 49 | + `Could not find owner / application instance. Cannot create a instance tied to the application lifetime without the application`, |
| 50 | + owner |
| 51 | + ); |
| 52 | + |
| 53 | + return createStore(owner, theClass); |
| 54 | +} |
| 55 | + |
| 56 | +const promiseCache = new WeakMap<() => any, unknown>(); |
| 57 | + |
| 58 | +/** |
| 59 | + * Lazily instantiate a service. |
24 | 60 | * |
25 | 61 | * This is a replacement / alternative API for ember's `@service` decorator from `@ember/service`. |
26 | 62 | * |
27 | 63 | * For example |
28 | 64 | * ```js |
29 | 65 | * import { service } from 'ember-primitives/service'; |
30 | 66 | * |
31 | | - * class State { |
32 | | - * @service router; // also works with the traditional string usage |
33 | | - * } |
| 67 | + * const loader = () => import('./foo/file/with/class.js'); |
34 | 68 | * |
35 | 69 | * class Demo extends Component { |
36 | | - * @service(State) state; |
| 70 | + * state = createAsyncService(this, loader); |
37 | 71 | * } |
38 | 72 | * ``` |
39 | 73 | * |
40 | | - * Additionally for better TypeScript type-inference, this alternate invocation syntax is allowed: |
41 | | - * ```js |
42 | | - * class Demo extends Component { |
43 | | - * state = service(this, State); |
44 | | - * } |
| 74 | + * The important thing is for repeat usage of `createAsyncService` the second parameter, |
| 75 | + * (loader in this case), must be shared between all usages. |
| 76 | + * |
| 77 | + * This is an alternative to using `createStore` inside an await'd component, |
| 78 | + * or a component rendered with [`getPromiseState`](https://reactive.nullvoxpopuli.com/functions/get-promise-state.getPromiseState.html) |
45 | 79 | * ``` |
46 | 80 | */ |
47 | | -export function service(...args: any[]) { |
48 | | - /** |
49 | | - * Stage 1 / Legacy Decorators (original ember service) |
50 | | - */ |
51 | | - if (args.length === 3) { |
52 | | - return emberService(...(args as Parameters<typeof emberService>)); |
53 | | - } |
| 81 | +export function createAsyncService<Instance extends object>( |
| 82 | + context: object, |
| 83 | + theClass: () => Promise<Newable<Instance>> | (() => Promise<() => Instance | Newable<Instance>>) |
| 84 | +): ReturnType<typeof getPromiseState<unknown, Instance>> { |
| 85 | + let existing = promiseCache.get(theClass); |
54 | 86 |
|
55 | | - /** |
56 | | - * This is either: |
57 | | - * - spec decorator |
58 | | - * - or non-decorator usage (requiring a this parameter to be passed for the first) |
59 | | - */ |
60 | | - if (args.length === 2) { |
61 | | - if (typeof args[1] === 'object' && args[1]) { |
62 | | - if ('kind' in args[1]) { |
63 | | - assert( |
64 | | - `TC39 Spec decorators are not yet supported. If you see this error, please open an issue at https://github.com/universal-ember/ember-primitives/ -- also!, congratulations for being an early adopter of real decorators!` |
65 | | - ); |
66 | | - } |
67 | | - } |
| 87 | + if (!existing) { |
| 88 | + existing = async () => { |
| 89 | + const result = await theClass(); |
68 | 90 |
|
69 | | - return inlineSerice(...args); |
70 | | - } |
| 91 | + // Pay no attention to the lies, I don't know what the right type is here |
| 92 | + return createStore(context, result as Newable<Instance>); |
| 93 | + }; |
71 | 94 |
|
72 | | - if (args.length === 1) { |
73 | | - /** |
74 | | - * Traditional string-based service usage |
75 | | - */ |
76 | | - if (typeof args[0] === 'string') { |
77 | | - return emberService(...args); |
78 | | - } |
| 95 | + promiseCache.set(theClass, existing); |
79 | 96 | } |
80 | | -} |
81 | | - |
82 | | -function concreteService<Class>(definition: Newable<Class>): Decorator {} |
83 | 97 |
|
84 | | -function inlineService(context: object, service: string | object | Function) {} |
| 98 | + // Pay no attention to the TS inference crime here |
| 99 | + return getPromiseState<unknown, Instance>(existing); |
| 100 | +} |
0 commit comments