Skip to content

Commit

Permalink
feat(core): add runHooks, deprecate runHook
Browse files Browse the repository at this point in the history
DEPRECATED: `runHook` has been replaced with a more flexible `runHooks` function.
  • Loading branch information
paul-thebaud committed Jan 29, 2024
1 parent 22df8d6 commit 56b50ff
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 41 deletions.
6 changes: 3 additions & 3 deletions packages/core/src/actions/context/enhancers/crud/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import changeInstanceExistence
from '@foscia/core/actions/context/enhancers/hooks/changeInstanceExistence';
import onRunning from '@foscia/core/actions/context/enhancers/hooks/onRunning';
import onSuccess from '@foscia/core/actions/context/enhancers/hooks/onSuccess';
import runInstanceHooks from '@foscia/core/actions/context/enhancers/hooks/runInstanceHooks';
import makeEnhancersExtension from '@foscia/core/actions/extensions/makeEnhancersExtension';
import {
Action,
Expand All @@ -16,6 +15,7 @@ import {
ConsumeModel,
ConsumeSerializer,
} from '@foscia/core/actions/types';
import runHooks from '@foscia/core/hooks/runHooks';
import { Model, ModelClassInstance, ModelInstance } from '@foscia/core/model/types';

/**
Expand All @@ -39,8 +39,8 @@ export default function create<
.use(context({ action: 'create' }))
.use(instanceData(instance))
.use(changeInstanceExistence(true))
.use(onRunning(runInstanceHooks(instance, ['creating', 'saving'])))
.use(onSuccess(runInstanceHooks(instance, ['created', 'saved'])));
.use(onRunning(() => runHooks(instance.$model, ['creating', 'saving'], instance)))
.use(onSuccess(() => runHooks(instance.$model, ['created', 'saved'], instance)));
}

type EnhancerExtension = ActionParsedExtension<{
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/actions/context/enhancers/crud/destroy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import changeInstanceExistence
from '@foscia/core/actions/context/enhancers/hooks/changeInstanceExistence';
import onRunning from '@foscia/core/actions/context/enhancers/hooks/onRunning';
import onSuccess from '@foscia/core/actions/context/enhancers/hooks/onSuccess';
import runInstanceHooks from '@foscia/core/actions/context/enhancers/hooks/runInstanceHooks';
import makeEnhancersExtension from '@foscia/core/actions/extensions/makeEnhancersExtension';
import {
Action,
Expand All @@ -13,6 +12,7 @@ import {
ConsumeInstance,
ConsumeModel,
} from '@foscia/core/actions/types';
import runHooks from '@foscia/core/hooks/runHooks';
import { Model, ModelClassInstance, ModelInstance } from '@foscia/core/model/types';

/**
Expand All @@ -31,8 +31,8 @@ export default function destroy<
.use(forInstance<C, D, I>(instance))
.use(context({ action: 'destroy' }))
.use(changeInstanceExistence(false))
.use(onRunning(runInstanceHooks(instance, ['destroying'])))
.use(onSuccess(runInstanceHooks(instance, ['destroyed'])));
.use(onRunning(() => runHooks(instance.$model, ['destroying'], instance)))
.use(onSuccess(() => runHooks(instance.$model, ['destroyed'], instance)));
}

type EnhancerExtension = ActionParsedExtension<{
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/actions/context/enhancers/crud/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import changeInstanceExistence
from '@foscia/core/actions/context/enhancers/hooks/changeInstanceExistence';
import onRunning from '@foscia/core/actions/context/enhancers/hooks/onRunning';
import onSuccess from '@foscia/core/actions/context/enhancers/hooks/onSuccess';
import runInstanceHooks from '@foscia/core/actions/context/enhancers/hooks/runInstanceHooks';
import makeEnhancersExtension from '@foscia/core/actions/extensions/makeEnhancersExtension';
import {
Action,
Expand All @@ -15,6 +14,7 @@ import {
ConsumeModel,
ConsumeSerializer,
} from '@foscia/core/actions/types';
import runHooks from '@foscia/core/hooks/runHooks';
import { Model, ModelClassInstance, ModelInstance } from '@foscia/core/model/types';

/**
Expand All @@ -37,8 +37,8 @@ export default function update<
.use(context({ action: 'update' }))
.use(instanceData(instance))
.use(changeInstanceExistence(true))
.use(onRunning(runInstanceHooks(instance, ['updating', 'saving'])))
.use(onSuccess(runInstanceHooks(instance, ['updated', 'saved'])));
.use(onRunning(() => runHooks(instance.$model, ['updating', 'saving'], instance)))
.use(onSuccess(() => runHooks(instance.$model, ['updated', 'saved'], instance)));
}

type EnhancerExtension = ActionParsedExtension<{
Expand Down

This file was deleted.

10 changes: 5 additions & 5 deletions packages/core/src/actions/makeActionClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ContextRunner,
} from '@foscia/core/actions/types';
import registerHook from '@foscia/core/hooks/registerHook';
import runHook from '@foscia/core/hooks/runHook';
import runHooks from '@foscia/core/hooks/runHooks';
import { HooksRegistrar } from '@foscia/core/hooks/types';
import withoutHooks from '@foscia/core/hooks/withoutHooks';
import logger from '@foscia/core/logger/logger';
Expand Down Expand Up @@ -61,22 +61,22 @@ export default function makeActionClass<Extension extends {} = {}>(
public async run(runner: ContextRunner<any, any, any>) {
const context = await this.useContext();

await runHook(this, 'running', { context, runner });
await runHooks(this, 'running', { context, runner });

try {
// Context runner might use other context runners, so we must disable
// hooks at this point to avoid duplicated hooks runs.
const result = await withoutHooks(this, () => runner(this as any));

await runHook(this, 'success', { context, result });
await runHooks(this, 'success', { context, result });

return result;
} catch (error) {
await runHook(this, 'error', { context, error });
await runHooks(this, 'error', { context, error });

throw error;
} finally {
await runHook(this, 'finally', { context });
await runHooks(this, 'finally', { context });
}
}

Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/hooks/runHook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { Hookable, HookCallback, HooksDefinition } from '@foscia/core/hooks/types';
import { sequentialTransform } from '@foscia/shared';

/**
* Run one hook of a hookable object.
*
* @param hookable
* @param key
* @param event
*
* @deprecated Use {@link runHooks} instead.
*/
export default async function runHook<D extends HooksDefinition, K extends keyof D>(
hookable: Hookable<D>,
key: K,
Expand Down
23 changes: 23 additions & 0 deletions packages/core/src/hooks/runHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Hookable, HookCallback, HooksDefinition } from '@foscia/core/hooks/types';
import { Arrayable, sequentialTransform, wrap } from '@foscia/shared';

/**
* Sequentially run multiple hooks of a hookable object.
*
* @param hookable
* @param hooks
* @param event
*/
export default async function runHooks<D extends HooksDefinition, K extends keyof D>(
hookable: Hookable<D>,
hooks: Arrayable<K>,
event: D[K] extends HookCallback<infer E> ? E : never,
) {
await sequentialTransform(wrap(hooks).map((hook) => async () => {
const hookCallbacks = hookable.$hooks?.[hook] ?? [];

await sequentialTransform(hookCallbacks.map((callback) => async () => {
await callback(event);
}));
}));
}
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import FosciaError from '@foscia/core/errors/fosciaError';
import SerializerError from '@foscia/core/errors/serializerError';
import registerHook from '@foscia/core/hooks/registerHook';
import runHook from '@foscia/core/hooks/runHook';
import runHooks from '@foscia/core/hooks/runHooks';
import unregisterHook from '@foscia/core/hooks/unregisterHook';
import withoutHooks from '@foscia/core/hooks/withoutHooks';
import logger from '@foscia/core/logger/logger';
Expand Down Expand Up @@ -138,6 +139,7 @@ export {
restoreSnapshot,
takeSnapshot,
runHook,
runHooks,
registerHook,
unregisterHook,
withoutHooks,
Expand Down
12 changes: 6 additions & 6 deletions packages/core/tests/unit/hooks/hooks.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Hookable, registerHook, runHook, unregisterHook, withoutHooks } from '@foscia/core';
import { Hookable, registerHook, runHooks, unregisterHook, withoutHooks } from '@foscia/core';
import { Awaitable } from '@foscia/shared';
import { describe, expect, it, vi } from 'vitest';

Expand All @@ -16,7 +16,7 @@ describe.concurrent('unit: hooks', () => {
dummyValue = `${dummyValue}>${value}2`;
});

await runHook(dummyHookable, 'dummy', 'foo');
await runHooks(dummyHookable, 'dummy', 'foo');

expect(dummyValue).toStrictEqual('');
expect(firstHookMock).not.toHaveBeenCalled();
Expand All @@ -25,7 +25,7 @@ describe.concurrent('unit: hooks', () => {
const unregisterFirst = registerHook(dummyHookable, 'dummy', firstHookMock);
registerHook(dummyHookable, 'dummy', secondHookMock);

await runHook(dummyHookable, 'dummy', 'foo');
await runHooks(dummyHookable, 'dummy', 'foo');

expect(dummyValue).toStrictEqual('>foo1>foo2');
expect(firstHookMock).toHaveBeenCalledOnce();
Expand All @@ -36,19 +36,19 @@ describe.concurrent('unit: hooks', () => {
// Non-existing callback.
});

await withoutHooks(dummyHookable, () => {
withoutHooks(dummyHookable, () => {
registerHook(dummyHookable, 'dummy', firstHookMock);
unregisterHook(dummyHookable, 'dummy', secondHookMock);
});

await runHook(dummyHookable, 'dummy', 'foo');
await runHooks(dummyHookable, 'dummy', 'foo');

expect(dummyValue).toStrictEqual('>foo1>foo2>foo2');
expect(firstHookMock).toHaveBeenCalledOnce();
expect(secondHookMock).toHaveBeenCalledTimes(2);

await withoutHooks(dummyHookable, async () => {
await runHook(dummyHookable, 'dummy', 'foo');
await runHooks(dummyHookable, 'dummy', 'foo');
});

expect(dummyValue).toStrictEqual('>foo1>foo2>foo2');
Expand Down
4 changes: 2 additions & 2 deletions packages/serialization/src/makeDeserializerWith.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ModelInstance,
ModelRelation,
normalizeKey,
runHook,
runHooks,
shouldSync,
} from '@foscia/core';
import {
Expand Down Expand Up @@ -231,7 +231,7 @@ export default function makeDeserializerWith<
instance.$raw = record.raw;

markSynced(instance);
await runHook(instance.$model, 'retrieved', instance);
await runHooks(instance.$model, ['retrieved'], instance);

const cache = await consumeCache(context, null);
if (cache && !isNil(instance.id)) {
Expand Down
8 changes: 3 additions & 5 deletions website/docs/core-concepts/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,8 @@ parallelized).
:::

You can temporally disable hook execution for a given action by using the
`withoutHooks` function.

Be aware that `withoutHooks` will always return a promise, even when your
callback is a sync function.
`withoutHooks` function. `withoutHooks` can receive a sync or async
callback: if an async callback is passed, it will return a `Promise`.

```typescript
import { withoutHooks } from '@foscia/core';
Expand All @@ -333,7 +331,7 @@ const users = await withoutHooks(action(), async (a) => {

:::warning

Foscia may also register hooks internally when using some enhancers. Those
**Foscia may also register hooks internally** when using some enhancers. Those
provide some library features
([**models hooks**](/docs/core-concepts/models#hooks), etc.). Be careful running
actions without hooks, as those hooks will also be disable.
Expand Down

0 comments on commit 56b50ff

Please sign in to comment.