Skip to content

Commit

Permalink
Merge pull request #1091 from NullVoxPopuli/add-support-for-custom-us…
Browse files Browse the repository at this point in the history
…ables

Implement custom usables, `regusterUsable`
  • Loading branch information
NullVoxPopuli authored Jan 9, 2024
2 parents 837ee4f + 2030928 commit 8818a0e
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 422 deletions.
10 changes: 0 additions & 10 deletions dev/estimate-bytes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,7 @@ const config = {
bundles: {
"index.js": {
alias: ".",
nest: [
"core/class-based/index.js",
"core/function-based/index.js",
"core/cell.js",
"core/use.js",
],
},
"core/use.js": {},
"core/class-based/index.js": {},
"core/function-based/index.js": {},
"link.js": {},
},
};
/**
Expand Down
11 changes: 9 additions & 2 deletions ember-resources/src/function-based/resource.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { assert } from '@ember/debug';
// @ts-ignore
import { setHelperManager } from '@ember/helper';
import { invokeHelper, setHelperManager } from '@ember/helper';

import { registerUsable } from '../use';
import { ResourceManagerFactory } from './manager';
import { INTERNAL } from './types';
import { wrapForPlainUsage } from './utils';

import type { InternalFunctionResourceConfig, ResourceFn, ResourceFunction } from './types';

const TYPE = 'function-based';

registerUsable(TYPE, (context: object, config: InternalFunctionResourceConfig) => {
return invokeHelper(context, config);
});

/**
* `resource` provides a single reactive read-only value with lifetime and may have cleanup.
*
Expand Down Expand Up @@ -214,7 +221,7 @@ export function resource<Value>(

let internalConfig: InternalFunctionResourceConfig<Value> = {
definition: setup as ResourceFunction<Value>,
type: 'function-based',
type: TYPE,
[INTERNAL]: true,
};

Expand Down
9 changes: 3 additions & 6 deletions ember-resources/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// Public API -- base classes
export { resource, resourceFactory } from './function-based';
export { use } from './use';

// Public API -- Utilities
// Public API
export { cell } from './cell';
export { resource, resourceFactory } from './function-based';
export { registerUsable, use } from './use';

// Public Type Utilities
export type { ResourceAPI } from './function-based';
export type { Reactive } from './function-based/types';
export type { ArgsWrapper, ExpandArgs, Thunk } from './types';
17 changes: 0 additions & 17 deletions ember-resources/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import type { INTERNAL } from './function-based/types';
import type { Thunk } from './types/thunk';

export * from './types/base';
export * from './types/signature-args';
export * from './types/thunk';

// typed-ember should provide this from
// @glimmer/tracking/primitives/cache
Expand All @@ -13,12 +9,6 @@ export interface Cache<T = unknown> {
_: T;
}

// typed-ember should provide this from @ember/helper
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Helper {
/* no clue what's in here */
}

export interface Stage1DecoratorDescriptor {
initializer: () => unknown;
}
Expand All @@ -28,10 +18,3 @@ export type Stage1Decorator = (
key: string | symbol,
descriptor?: Stage1DecoratorDescriptor,
) => any;

export interface ClassResourceConfig {
thunk: Thunk;
definition: unknown;
type: 'class-based';
[INTERNAL]: true;
}
8 changes: 0 additions & 8 deletions ember-resources/src/types/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ export interface Class<Instance> {
new (...args: unknown[]): Instance;
}

/**
* @private utility type
*/
export type NoArgs = {
named: EmptyObject;
positional: [];
};

/**
* This is a utility interface that represents the resulting args structure after
* the thunk is normalized.
Expand Down
150 changes: 0 additions & 150 deletions ember-resources/src/types/thunk.ts

This file was deleted.

74 changes: 56 additions & 18 deletions ember-resources/src/use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import { associateDestroyableChild } from '@ember/destroyable';
import { invokeHelper } from '@ember/helper';

import { ReadonlyCell } from './cell';
import { INTERNAL } from './function-based/types';
import { normalizeThunk } from './utils';

import type { INTERNAL } from './function-based/types';
import type { InternalFunctionResourceConfig, Reactive } from './function-based/types';
import type { ClassResourceConfig, Stage1DecoratorDescriptor } from '[core-types]';
import type { Stage1DecoratorDescriptor } from '[core-types]';

type Config = ClassResourceConfig | InternalFunctionResourceConfig;
type Config =
| { [INTERNAL]: true; type: string; definition: unknown }
| InternalFunctionResourceConfig;

type NonInstanceType<K> = K extends InstanceType<any> ? object : K;
type DecoratorKey<K> = K extends string | symbol ? K : never;
type NonDecoratorKey<K> = K extends string | symbol ? never : ThisType<K>;

/**
* The `@use(...)` decorator can be used to use a Resource in javascript classes
Expand Down Expand Up @@ -157,6 +157,47 @@ function argumentToDecorator<Value>(definition: Value | (() => Value)): Property
};
}

interface UsableConfig {
type: string;
definition: unknown;
}

export type UsableFn<Usable extends UsableConfig> = (
context: object,
config: Usable,
) => ReturnType<typeof invokeHelper>;

const USABLES = new Map<string, UsableFn<any>>();

/**
* Register with the usable system.
* This is only needed for for the `@use` decorator, as use(this, Helper) is a concise wrapper
* around the helper-manager system.
*
* The return type must be a "Cache" returned from `invokeHelper` so that `@use`'s usage of `getValue` gets the value (as determined by the helper manager you wrote for your usable).
*/
export function registerUsable<Usable extends UsableConfig>(
/**
* The key to register the usable under.
*
* All usables must have a `type`.
*
* Any usable matching the registered type will used the passed function to
* create its Cache -- this is typically the return result of `invokeHelper`,
*
* Any usables must have a `type` property matching this string
*/
type: string,
/**
* Receives the the parent context and object passed to the `@use` decorator.
*/
useFn: UsableFn<Usable>,
) {
assert(`type may not overlap with an existing usable`, !USABLES.has(type));

USABLES.set(type, useFn);
}

function descriptorGetter(initializer: unknown | (() => unknown)) {
let caches = new WeakMap<object, any>();

Expand All @@ -169,24 +210,21 @@ function descriptorGetter(initializer: unknown | (() => unknown)) {
typeof initializer === 'function' ? initializer.call(this) : initializer
) as Config;

let usable = USABLES.get(config.type);

assert(
`Expected initialized value under @use to have used either the \`resource\` wrapper function, or a \`Resource.from\` call`,
INTERNAL in config,
`Expected the initialized value with @use to have been a registerd "usable". Available usables are: ${[
...USABLES.keys(),
]}`,
usable,
);

if (config.type === 'function-based') {
cache = invokeHelper(this, config);
caches.set(this as object, cache);
associateDestroyableChild(this, cache);
} else if (config.type === 'class-based') {
let { definition, thunk } = config;
cache = usable(this, config);

cache = invokeHelper(this, definition, () => normalizeThunk(thunk));
caches.set(this as object, cache);
associateDestroyableChild(this, cache);
}
assert(`Failed to create cache for usable: ${config.type}`, cache);

assert(`Failed to create cache for internal resource configuration object`, cache);
caches.set(this as object, cache);
associateDestroyableChild(this, cache);
}

let value = getValue(cache);
Expand Down
29 changes: 0 additions & 29 deletions ember-resources/src/utils.ts

This file was deleted.

Loading

0 comments on commit 8818a0e

Please sign in to comment.