Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extraDependencies for createQuery and createJsonQuery #372

154 changes: 153 additions & 1 deletion packages/core/src/cache/__test__/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { allSettled, createEffect, createEvent, fork } from 'effector';
import { allSettled, createEffect, createEvent, createStore, fork } from 'effector';
import { setTimeout } from 'timers/promises';
import { describe, vi, expect, test } from 'vitest';

Expand All @@ -13,6 +13,7 @@ import { createJsonQuery } from '../../query/create_json_query';
import { declareParams } from '../../remote_operation/params';
import { unknownContract } from '../../contract/unknown_contract';
import { fetchFx } from '../../fetch/fetch';
import { stableStringify } from '../lib/stable_stringify';

describe('cache', () => {
test('use value from cache on second call, revalidate', async () => {
Expand Down Expand Up @@ -398,4 +399,155 @@ describe('cache', () => {
expect(scope.getState(query.$data)).toEqual({ step: 2 });
expect(scope.getState(query.$stale)).toBeFalsy();
});

test('use createQuery#extraDependencies in cache-key', async () => {
const EXTRA_DEPENDENCY_VALUE_ONE = 42;
const EXTRA_DEPENDENCY_VALUE_TWO = 24;

const $extraDependency = createStore(EXTRA_DEPENDENCY_VALUE_ONE);

const MOCK_VALUE_ONE = 10;
const MOCK_VALUE_TWO = 20;

const query = withFactory({
fn: () =>
createQuery({
handler: (params: any) => Promise.resolve(MOCK_VALUE_ONE),
extraDependencies: [$extraDependency]
}),

sid: '1'
});

const adapter = inMemoryCache();
cache(query, { adapter });

const scope = fork({
handlers: [
[
query.__.executeFx,
vi
.fn()
.mockImplementationOnce(() => MOCK_VALUE_ONE)
.mockImplementationOnce(() => MOCK_VALUE_TWO)
]
]
});

// Do not await
// But wait for next tick becuase of async adapter's nature
allSettled(query.start, { scope });
await setTimeout(1);
await allSettled(scope);

await allSettled($extraDependency, { scope, params: EXTRA_DEPENDENCY_VALUE_TWO })

// Do not await
// But wait for next tick becuase of async adapter's nature
allSettled(query.start, { scope });
await setTimeout(1);
await allSettled(scope);

const key1 = sha1(
stableStringify({
params: null, // Using `null` and not `undefined` bcs of stableStringify default behavior
sources: [EXTRA_DEPENDENCY_VALUE_ONE],
sid: query.__.meta.sid // queryUniqId(query)
})!
);

const key2 = sha1(
stableStringify({
params: null, // Using `null` and not `undefined` bcs of stableStringify default behavior
sources: [EXTRA_DEPENDENCY_VALUE_TWO],
sid: query.__.meta.sid // queryUniqId(query)
})!
);

const cachedResult1 = await adapter.get({ key: key1 });
const cachedResult2 = await adapter.get({ key: key2 });

expect(cachedResult1?.value).toEqual(MOCK_VALUE_ONE);
expect(cachedResult2?.value).toEqual(MOCK_VALUE_TWO);
});

test('use createJsonQuery#extraDependencies in cache-key', async () => {
const REQUEST_URL = 'https://api.salo.com/';

const EXTRA_DEPENDENCY_VALUE_ONE = 42;
const EXTRA_DEPENDENCY_VALUE_TWO = 24;

const $extraDependency = createStore(EXTRA_DEPENDENCY_VALUE_ONE);

const MOCK_VALUE_ONE = 10;
const MOCK_VALUE_TWO = 20;

const query = withFactory({
fn: () =>
createJsonQuery({
params: declareParams<void>(),
request: {
method: 'GET',
url: REQUEST_URL,
},
response: {
contract: unknownContract,
},
extraDependencies: [$extraDependency]
}),

sid: '1'
});

const adapter = inMemoryCache();
cache(query, { adapter });

const scope = fork({
handlers: [
[
query.__.executeFx,
vi
.fn()
.mockImplementationOnce(() => MOCK_VALUE_ONE)
.mockImplementationOnce(() => MOCK_VALUE_TWO)
]
]
});

// Do not await
// But wait for next tick becuase of async adapter's nature
allSettled(query.start, { scope });
await setTimeout(1);
await allSettled(scope);

await allSettled($extraDependency, { scope, params: EXTRA_DEPENDENCY_VALUE_TWO })

// Do not await
// But wait for next tick becuase of async adapter's nature
allSettled(query.start, { scope });
await setTimeout(1);
await allSettled(scope);

const key1 = sha1(
stableStringify({
params: null, // Using `null` and not `undefined` bcs of stableStringify default behavior
sources: [REQUEST_URL, EXTRA_DEPENDENCY_VALUE_ONE],
sid: query.__.meta.sid // queryUniqId(query)
})!
);

const key2 = sha1(
stableStringify({
params: null, // Using `null` and not `undefined` bcs of stableStringify default behavior
sources: [REQUEST_URL, EXTRA_DEPENDENCY_VALUE_TWO],
sid: query.__.meta.sid // queryUniqId(query)
})!
);

const cachedResult1 = await adapter.get({ key: key1 });
const cachedResult2 = await adapter.get({ key: key2 });

expect(cachedResult1?.value).toEqual(MOCK_VALUE_ONE);
expect(cachedResult2?.value).toEqual(MOCK_VALUE_TWO);
});
});
30 changes: 30 additions & 0 deletions packages/core/src/query/__tests__/extra-dependencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createStore } from "effector";
import { describe, test, expect } from 'vitest';

import { normalizeExtraDependencies } from "../extra-dependencies";

describe('normalizeExtraDependencies', () => {
test('should return empty undefined if no extraDependencies', async () => {
const result = normalizeExtraDependencies();
expect(result).toEqual(undefined);
});

test('should return empty array if extraDependencies is empty array', async () => {
const result = normalizeExtraDependencies([]);
expect(result).toEqual([]);
});

test('should return array with one item if extraDependencies is store', async () => {
const $dependency = createStore(42);
const result = normalizeExtraDependencies($dependency);
expect(result).toEqual([$dependency]);
});

test('should return array if extraDependencies is array with stores', async () => {
const $dependency1 = createStore(42);
const $dependency2 = createStore(24);
const result = normalizeExtraDependencies([$dependency1, $dependency2]);

expect(result).toEqual([$dependency1, $dependency2]);
});
})
13 changes: 12 additions & 1 deletion packages/core/src/query/create_json_query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from './create_headless_query';
import { unknownContract } from '../contract/unknown_contract';
import { type Validator } from '../validation/type';
import { type ExtraDependencies, normalizeExtraDependencies } from "./extra-dependencies";

// -- Shared

Expand Down Expand Up @@ -110,6 +111,7 @@ export function createJsonQuery<
>;
validate?: Validator<TransformedData, Params, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<Params, TransformedData, JsonApiRequestError>;

Expand Down Expand Up @@ -142,6 +144,7 @@ export function createJsonQuery<
>;
validate?: Validator<TransformedData, Params, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<Params, TransformedData, JsonApiRequestError, TransformedData>;

Expand All @@ -167,6 +170,7 @@ export function createJsonQuery<
contract: Contract<unknown, Data>;
validate?: Validator<Data, Params, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<Params, Data, JsonApiRequestError>;

Expand All @@ -192,6 +196,7 @@ export function createJsonQuery<
contract: Contract<unknown, Data>;
validate?: Validator<Data, Params, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<Params, Data, JsonApiRequestError, Data>;

Expand Down Expand Up @@ -222,6 +227,7 @@ export function createJsonQuery<
>;
validate?: Validator<TransformedData, void, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<void, TransformedData, JsonApiRequestError>;

Expand Down Expand Up @@ -252,6 +258,7 @@ export function createJsonQuery<
>;
validate?: Validator<TransformedData, void, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<void, TransformedData, JsonApiRequestError, TransformedData>;

Expand All @@ -275,6 +282,7 @@ export function createJsonQuery<
contract: Contract<unknown, Data>;
validate?: Validator<Data, void, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<void, Data, JsonApiRequestError>;

Expand All @@ -298,6 +306,7 @@ export function createJsonQuery<
contract: Contract<unknown, Data>;
validate?: Validator<Data, void, ValidationSource>;
};
extraDependencies?: ExtraDependencies;
}
): Query<void, Data, JsonApiRequestError, Data>;

Expand Down Expand Up @@ -338,7 +347,9 @@ export function createJsonQuery(config: any) {
config.request.body,
config.request.headers,
config.request.query,
],
]
.concat(normalizeExtraDependencies(config.extraDependencies))
.filter(Boolean),
paramsAreMeaningless: true,
});

Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/query/create_query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@ import { type DynamicallySourcedField } from '../libs/patronus';
import { InvalidDataError } from '../errors/type';
import { Validator } from '../validation/type';
import { resolveExecuteEffect } from '../remote_operation/resolve_execute_effect';
import { normalizeExtraDependencies, type ExtraDependencies } from "./extra-dependencies";

// Overload: Only handler
export function createQuery<Params, Response>(
config: {
handler: (p: Params) => Promise<Response>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<Response>
): Query<Params, Response, unknown>;

export function createQuery<Params, Response>(
config: {
initialData: Response;
handler: (p: Params) => Promise<Response>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<Response>
): Query<Params, Response, unknown, Response>;

Expand All @@ -43,20 +46,23 @@ export function createQuery<
MapDataSource
>;
validate?: Validator<MappedData, Params, ValidationSource>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<MappedData>
): Query<Params, MappedData, Error>;

// Overload: Only effect
export function createQuery<Params, Response, Error>(
config: {
effect: Effect<Params, Response, Error>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<Response>
): Query<Params, Response, Error>;

export function createQuery<Params, Response, Error>(
config: {
initialData: Response;
effect: Effect<Params, Response, Error>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<Response>
): Query<Params, Response, Error, Response>;

Expand All @@ -72,6 +78,7 @@ export function createQuery<
effect: Effect<Params, Response, Error>;
contract: Contract<Response, ContractData>;
validate?: Validator<ContractData, Params, ValidationSource>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<ContractData>
): Query<Params, ContractData, Error | InvalidDataError>;

Expand All @@ -87,6 +94,7 @@ export function createQuery<
effect: Effect<Params, Response, Error>;
contract: Contract<Response, ContractData>;
validate?: Validator<ContractData, Params, ValidationSource>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<ContractData>
): Query<Params, ContractData, Error | InvalidDataError, ContractData>;

Expand All @@ -107,6 +115,7 @@ export function createQuery<
MapDataSource
>;
validate?: Validator<MappedData, Params, ValidationSource>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<MappedData>
): Query<Params, MappedData, Error, MappedData>;

Expand All @@ -129,6 +138,7 @@ export function createQuery<
MapDataSource
>;
validate?: Validator<ContractData, Params, ValidationSource>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<MappedData>
): Query<Params, MappedData, Error | InvalidDataError>;

Expand All @@ -151,6 +161,7 @@ export function createQuery<
MapDataSource
>;
validate?: Validator<ContractData, Params, ValidationSource>;
extraDependencies?: ExtraDependencies;
} & SharedQueryFactoryConfig<MappedData>
): Query<Params, MappedData, Error | InvalidDataError, MappedData>;

Expand Down Expand Up @@ -185,6 +196,7 @@ export function createQuery<
validate: config.validate,
name: config.name,
serialize: config.serialize,
sourced: normalizeExtraDependencies(config.extraDependencies)
});

query.__.executeFx.use(resolveExecuteEffect(config));
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/query/extra-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Store } from "effector";

/**
* It should be possible to use any store as a source for a field.
* Type of store value should be serializable (with stableStringify @see ../cache/lib/stable_stringify.ts)
* But there is no way to express this in typescript without using `any` (`unknown` causes type errors)
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type SerializableAnyStore = Store<any>;
export type ExtraDependencies = SerializableAnyStore | SerializableAnyStore[];

export function normalizeExtraDependencies(extraDependencies?: ExtraDependencies): SerializableAnyStore[] | undefined {
if(!extraDependencies) return undefined;

if(Array.isArray(extraDependencies)) {
return extraDependencies.filter(Boolean)
}

return (extraDependencies ? [extraDependencies] : []).filter(Boolean);
}
Loading
Loading