Repository: igorkamyshev/farfetched
Pull request #402 (2023-11-22 12:17:17)

Support enabled for keepFresh
Sometimes we need to control the execution of the query via keepFresh, so I added the `enabled` parameter and tests for it


Patch:
diff --git a/.changeset/light-chefs-applaud.md b/.changeset/light-chefs-applaud.md
new file mode 100644
index 0000000..acf6422
--- /dev/null
+++ b/.changeset/light-chefs-applaud.md
@@ -0,0 +1,5 @@
+---
+'@farfetched/core': patch
+---
+
+Support `enabled` in `keepFresh`
diff --git a/apps/website/docs/api/operators/keep_fresh.md b/apps/website/docs/api/operators/keep_fresh.md
index fae79fc..615daa7 100644
--- a/apps/website/docs/api/operators/keep_fresh.md
+++ b/apps/website/docs/api/operators/keep_fresh.md
@@ -14,6 +14,7 @@ Config fields:
 
 - `automatically?`: _true_ to refresh the data in a [_Query_](/api/primitives/query) automatically if any [_Store_](https://effector.dev/docs/api/effector/store) that is used in the [_Query_](/api/primitives/query) creation is changed.
 - `triggers?`: _Array_ of [_Events_](https://effector.dev/docs/api/effector/event) after which operator starts refreshing the data in the [_Query_](/api/primitives/query).
+- `enabled?`: <Badge type="tip" text="since v0.11" /> [_Store_](https://effector.dev/docs/api/effector/store) with the current enabled state. Disabled _keepFresh_ will not execute queries, instead, they will be treated as skipped. Can be `true` or `false`.
 
 ```ts
 import { keepFresh } from '@farfetched/core';
diff --git a/packages/core/src/trigger_api/keep_fresh.ts b/packages/core/src/trigger_api/keep_fresh.ts
index 7b80cbb..bb7f5ed 100644
--- a/packages/core/src/trigger_api/keep_fresh.ts
+++ b/packages/core/src/trigger_api/keep_fresh.ts
@@ -5,6 +5,7 @@ import {
   sample,
   is,
   createStore,
+  Store,
 } from 'effector';
 
 import { type Query } from '../query/type';
@@ -15,6 +16,7 @@ import {
   syncBatch,
   normalizeSourced,
   extractSource,
+  every,
 } from '../libs/patronus';
 import { type TriggerProtocol } from './trigger_protocol';
 
@@ -22,6 +24,7 @@ export function keepFresh<Params>(
   query: Query<Params, any, any, any>,
   config: {
     automatically: true;
+    enabled?: Store<boolean>;
   }
 ): void;
 
@@ -29,6 +32,7 @@ export function keepFresh<Params>(
   query: Query<Params, any, any, any>,
   config: {
     triggers: Array<Event<any> | TriggerProtocol>;
+    enabled?: Store<boolean>;
   }
 ): void;
 
@@ -37,6 +41,7 @@ export function keepFresh<Params>(
   config: {
     automatically: true;
     triggers: Array<Event<any> | TriggerProtocol>;
+    enabled?: Store<boolean>;
   }
 ): void;
 
@@ -45,6 +50,7 @@ export function keepFresh<Params>(
   config: {
     automatically?: true;
     triggers?: Array<Event<any> | TriggerProtocol>;
+    enabled?: Store<boolean>;
   }
 ): void {
   const triggers: Array<Event<any>> = [];
@@ -56,6 +62,16 @@ export function keepFresh<Params>(
 
   triggers.push(...triggerEvents);
 
+  const enabledParamStores = [query.$enabled];
+  if (config.enabled !== undefined) {
+    enabledParamStores.push(config.enabled);
+  }
+
+  const $enabled = every({
+    predicate: Boolean,
+    stores: enabledParamStores,
+  });
+
   if (protocolCompatibleObjects.length > 0) {
     const triggersByProtocol = protocolCompatibleObjects.map((trigger) =>
       trigger['@@trigger']()
@@ -71,15 +87,15 @@ export function keepFresh<Params>(
     sample({
       clock: [
         query.finished.success,
-        sample({ clock: query.$enabled.updates, filter: query.$enabled }),
+        sample({ clock: $enabled.updates, filter: $enabled }),
       ],
       filter: not($alreadySetup),
       target: [...triggersByProtocol.map(get('setup')), setup],
     });
 
     sample({
-      clock: query.$enabled.updates,
-      filter: and($alreadySetup, not(query.$enabled)),
+      clock: $enabled.updates,
+      filter: and($alreadySetup, not($enabled)),
       target: [...triggersByProtocol.map(get('teardown')), teardown],
     });
 
@@ -103,7 +119,7 @@ export function keepFresh<Params>(
       source: $partialSources,
       fn: (partialSources, clock) =>
         partialSources.map((partialSource) => partialSource(clock)),
-      filter: query.$enabled,
+      filter: $enabled,
       target: $previousSources,
     });
 
@@ -124,17 +140,14 @@ export function keepFresh<Params>(
 
     triggers.push(
       sample({
-        clock: [
-          $nextSources.updates,
-          query.$enabled.updates.filter({ fn: Boolean }),
-        ],
+        clock: [$nextSources.updates, $enabled.updates.filter({ fn: Boolean })],
         source: [$nextSources, $previousSources] as const,
         filter: ([next, prev]) => !isEqual(next, prev),
       })
     );
   }
 
-  const forceFresh = sample({ clock: triggers, filter: query.$enabled });
+  const forceFresh = sample({ clock: triggers, filter: $enabled });
 
   sample({
     clock: forceFresh,


Test patch:
diff --git a/packages/core/src/trigger_api/__test__/keep_fresh.test-d.ts b/packages/core/src/trigger_api/__test__/keep_fresh.test-d.ts
index a4f8959..fc9ead6 100644
--- a/packages/core/src/trigger_api/__test__/keep_fresh.test-d.ts
+++ b/packages/core/src/trigger_api/__test__/keep_fresh.test-d.ts
@@ -1,4 +1,4 @@
-import { Event } from 'effector';
+import { createStore, Event } from 'effector';
 import { describe, test } from 'vitest';
 
 import { Query } from '../../query/type';
@@ -11,6 +11,13 @@ describe('keepFresh', () => {
     keepFresh(q, { automatically: true });
     keepFresh(q, { triggers: [] });
     keepFresh(q, { automatically: true, triggers: [] });
+    keepFresh(q, { automatically: true, enabled: createStore(true) });
+    keepFresh(q, { triggers: [], enabled: createStore(true) });
+    keepFresh(q, {
+      automatically: true,
+      triggers: [],
+      enabled: createStore(true),
+    });
   });
 
   test('supports any Event as trigger', () => {
diff --git a/packages/core/src/trigger_api/__test__/keep_fresh.triggers.test.ts b/packages/core/src/trigger_api/__test__/keep_fresh.triggers.test.ts
index 32ca777..efe8368 100644
--- a/packages/core/src/trigger_api/__test__/keep_fresh.triggers.test.ts
+++ b/packages/core/src/trigger_api/__test__/keep_fresh.triggers.test.ts
@@ -214,4 +214,56 @@ describe('keepFresh, triggers as TriggerProtocol', () => {
     // 1 initial + 1 after enabling
     expect(setupListener).toBeCalledTimes(2);
   });
+
+  test('call teardown after config disabling and call start again after enabling', async () => {
+    const trigger = {
+      setup: createEvent(),
+      teardown: createEvent(),
+      fired: createEvent(),
+    };
+
+    const scope = fork();
+
+    const $enabled = createStore(true);
+
+    const teardownListener = vi.fn();
+
+    createWatch({
+      unit: trigger.teardown,
+      fn: teardownListener,
+      scope,
+    });
+
+    const setupListener = vi.fn();
+
+    createWatch({
+      unit: trigger.setup,
+      fn: setupListener,
+      scope,
+    });
+
+    const query = createQuery({
+      handler: vi.fn(),
+    });
+
+    keepFresh(query, {
+      enabled: $enabled,
+      triggers: [
+        {
+          '@@trigger': () => trigger,
+        },
+      ],
+    });
+
+    await allSettled(query.refresh, { scope });
+
+    await allSettled($enabled, { scope, params: false });
+
+    expect(teardownListener).toBeCalled();
+
+    await allSettled($enabled, { scope, params: true });
+
+    // 1 initial + 1 after enabling
+    expect(setupListener).toBeCalledTimes(2);
+  });
 });