in src/AutoSubscriptions.ts [213:320]
function makeAutoSubscribeDecorator(shallow = false, autoSubscribeKeys?: string[]): MethodDecorator {
return <T>(target: InstanceTarget, methodName: string|symbol, descriptor: TypedPropertyDescriptor<T>) => {
const methodNameString = methodName.toString();
const targetWithMetadata = instanceTargetToInstanceTargetWithMetadata(target);
const metaForMethod = getMethodMetadata(targetWithMetadata, methodNameString);
// Record that the target is decorated.
metaForMethod.hasAutoSubscribeDecorator = true;
// Save the method being decorated. Note this might not be the original method if already decorated.
// Note: T might have other properties (e.g. T = { (): void; bar: number; }). We don't support that and need a cast/assert.
const existingMethod = descriptor.value as any as Function;
assert(isFunction(existingMethod), 'Can only use @autoSubscribe on methods');
// Note: we need to be given 'this', so cannot use '=>' syntax.
descriptor.value = function AutoSubscribe(this: any, ...args: any[]) {
assert(targetWithMetadata.__resubMetadata.__decorated, `Missing @AutoSubscribeStore class decorator: "${ methodNameString }"`);
if (Options.development) {
// This is a check to see if we're in a rendering function component function. If you are, then calling useState will
// noop. If you aren't, then useState will throw an exception. So, we want to make sure that either you're inside render
// and have the call going through a wrapped component, or that you're not inside render, and hence calling the getter
// from a store or service or other random non-lifecycled instance, so it's on you to figure out how to manage
// subscriptions in that instance.
let inRender = false;
try {
useState();
inRender = true;
} catch {
// I guess we weren't in render.
}
assert(!inRender || !!handlerWrapper, 'Autosubscribe method called from inside a render function ' +
'or function component without using withResubAutoSubscriptions');
}
// Just call the method if no handler is setup.
const scopedHandleWrapper = handlerWrapper;
if (!scopedHandleWrapper || scopedHandleWrapper.useAutoSubscriptions === AutoOptions.None) {
return existingMethod.apply(this, args);
}
// If this is forbidding auto-subscribe then do not go through the auto-subscribe path below.
if (scopedHandleWrapper.useAutoSubscriptions === AutoOptions.Forbid) {
assert(false, `Only Store methods WITHOUT the ` +
`@autoSubscribe decorator can be called right now (e.g. in render): "${ methodNameString }"`);
return existingMethod.apply(this, args);
}
// Try to find an @key parameter in the target's metadata and form initial Key(s) from it/them.
let keyParamValues: (string | number)[] = [];
if (metaForMethod.keyIndexes) {
keyParamValues = metaForMethod.keyIndexes.map(index => {
let keyArg: number | string = args[index];
if (isNumber(keyArg)) {
keyArg = keyArg.toString();
}
assert(keyArg, `@key parameter must be given a non-empty string or number: ` +
`"${ methodNameString }"@${ index } was given ${ JSON.stringify(keyArg) }`);
assert(isString(keyArg), `@key parameter must be given a string or number: ` +
`"${ methodNameString }"@${ index }`);
return keyArg;
});
}
// Form a list of keys to trigger.
// If we have @key values, put them first, then append the @autosubscribewithkey key to the end.
// If there are multiple keys in the @autosubscribewithkey list, go through each one and do the
// same thing (@key then value). If there's neither @key nor @autosubscribewithkey, it's Key_All.
const specificKeyValues: string[] = (autoSubscribeKeys && autoSubscribeKeys.length > 0) ?
autoSubscribeKeys.map(autoSubKey => formCompoundKey(...keyParamValues.concat(autoSubKey))) :
[(keyParamValues.length > 0) ? formCompoundKey(...keyParamValues) : StoreBase.Key_All];
// Let the handler know about this auto-subscriptions, then proceed to the existing method.
let wasInAutoSubscribe: boolean;
const result = _tryFinally(() => {
// Disable further auto-subscriptions if shallow.
scopedHandleWrapper.useAutoSubscriptions = shallow ? AutoOptions.None : AutoOptions.Enabled;
// Any further @warnIfAutoSubscribeEnabled methods are safe.
wasInAutoSubscribe = scopedHandleWrapper.inAutoSubscribe;
scopedHandleWrapper.inAutoSubscribe = true;
// Let the handler know about this auto-subscription.
for (const specificKeyValue of specificKeyValues) {
scopedHandleWrapper
.handler!!!
.handle
.apply(scopedHandleWrapper.instance, [scopedHandleWrapper.instance, this, specificKeyValue]);
}
return existingMethod.apply(this, args);
}, () => {
// Must have been previously enabled to reach here.
scopedHandleWrapper.useAutoSubscriptions = AutoOptions.Enabled;
scopedHandleWrapper.inAutoSubscribe = wasInAutoSubscribe;
});
return result;
} as any as T;
return descriptor;
};
}