@mobx-query/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/README.md +98 -0
- package/eslint.config.js +29 -0
- package/index.html +13 -0
- package/package.json +60 -0
- package/public/vite.svg +1 -0
- package/src/App.css +0 -0
- package/src/App.tsx +5 -0
- package/src/api/constants.ts +1 -0
- package/src/api/fetch.ts +29 -0
- package/src/api/todos.ts +14 -0
- package/src/api/types.ts +30 -0
- package/src/api/users.ts +19 -0
- package/src/assets/react.svg +1 -0
- package/src/index.css +60 -0
- package/src/libs/mobx-query/client/MQClient.ts +75 -0
- package/src/libs/mobx-query/client/MQClientAccessor.ts +74 -0
- package/src/libs/mobx-query/client/index.ts +8 -0
- package/src/libs/mobx-query/client/types.ts +37 -0
- package/src/libs/mobx-query/entity/Entity.ts +232 -0
- package/src/libs/mobx-query/entity/EntityCollection.ts +285 -0
- package/src/libs/mobx-query/entity/constants.ts +7 -0
- package/src/libs/mobx-query/entity/index.ts +15 -0
- package/src/libs/mobx-query/entity/types.ts +11 -0
- package/src/libs/mobx-query/mutations/BatchMutationBase.ts +105 -0
- package/src/libs/mobx-query/mutations/BatchUpdateMutation.ts +48 -0
- package/src/libs/mobx-query/mutations/CreateMutation.ts +172 -0
- package/src/libs/mobx-query/mutations/DeleteMutation.ts +94 -0
- package/src/libs/mobx-query/mutations/EntityMutationBase.ts +110 -0
- package/src/libs/mobx-query/mutations/MutationBase.ts +40 -0
- package/src/libs/mobx-query/mutations/OptimisticMutationStrategy.ts +122 -0
- package/src/libs/mobx-query/mutations/UpdateMutation.ts +76 -0
- package/src/libs/mobx-query/mutations/constants.ts +16 -0
- package/src/libs/mobx-query/mutations/index.ts +44 -0
- package/src/libs/mobx-query/mutations/types.ts +205 -0
- package/src/libs/mobx-query/queries/QueryBase.ts +65 -0
- package/src/libs/mobx-query/queries/QueryFragmentMany.ts +31 -0
- package/src/libs/mobx-query/queries/QueryFragmentOne.ts +35 -0
- package/src/libs/mobx-query/queries/QueryMany.ts +80 -0
- package/src/libs/mobx-query/queries/QueryManyBase.ts +135 -0
- package/src/libs/mobx-query/queries/QueryOne.ts +84 -0
- package/src/libs/mobx-query/queries/QueryOneBase.ts +93 -0
- package/src/libs/mobx-query/queries/index.ts +33 -0
- package/src/libs/mobx-query/queries/types.ts +60 -0
- package/src/libs/mobx-query/react/createReactContext.tsx +23 -0
- package/src/libs/mobx-query/react/index.ts +3 -0
- package/src/libs/mobx-query/utils/generateEntityId.ts +12 -0
- package/src/libs/mobx-query/utils/index.ts +8 -0
- package/src/libs/mobx-query/utils/invalidateQueryByHash.ts +18 -0
- package/src/libs/mobx-query/utils/types.ts +18 -0
- package/src/libs/react-query.ts +11 -0
- package/src/main.tsx +16 -0
- package/src/utils.ts +3 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +52 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EntityConstructorAny,
|
|
3
|
+
EntityCollection,
|
|
4
|
+
EntityDataAny,
|
|
5
|
+
EntityId,
|
|
6
|
+
} from "../entity";
|
|
7
|
+
import { QueryBase } from "./QueryBase";
|
|
8
|
+
import type { UseQueryOneOptions } from "./types";
|
|
9
|
+
import { type InferEntityData, invalidateQueryByHash } from "../utils";
|
|
10
|
+
import type { DefaultError } from "@tanstack/react-query";
|
|
11
|
+
|
|
12
|
+
export class QueryOneBase<
|
|
13
|
+
TArguments,
|
|
14
|
+
TMeta = void,
|
|
15
|
+
TEntityConstructor extends EntityConstructorAny = EntityConstructorAny,
|
|
16
|
+
TError extends DefaultError = DefaultError,
|
|
17
|
+
> extends QueryBase<TArguments> {
|
|
18
|
+
protected readonly collection: EntityCollection<TEntityConstructor>;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
queryPrefix: string,
|
|
22
|
+
protected readonly options: UseQueryOneOptions<
|
|
23
|
+
TArguments,
|
|
24
|
+
TMeta,
|
|
25
|
+
TEntityConstructor,
|
|
26
|
+
TError
|
|
27
|
+
>,
|
|
28
|
+
) {
|
|
29
|
+
super([options.entity], () => {
|
|
30
|
+
return [queryPrefix, ...options.queryKey()];
|
|
31
|
+
});
|
|
32
|
+
this.collection = this.getEntityCollection(options.entity);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
prefetch(args: TArguments) {
|
|
36
|
+
const queryFn = this.queryFnWrapper(args);
|
|
37
|
+
return this.queryClient.prefetchQuery({
|
|
38
|
+
queryKey: queryFn.queryKey,
|
|
39
|
+
queryFn: () => queryFn.run(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ensureData(args: TArguments) {
|
|
44
|
+
const queryFn = this.queryFnWrapper(args);
|
|
45
|
+
return this.queryClient.ensureQueryData({
|
|
46
|
+
queryKey: queryFn.queryKey,
|
|
47
|
+
queryFn: () => queryFn.run(),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
invalidate(args: TArguments) {
|
|
52
|
+
invalidateQueryByHash(
|
|
53
|
+
this.createQueryHash(args),
|
|
54
|
+
this.queryClient.getQueryCache(),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setQueryData(data: InferEntityData<TEntityConstructor>, args: TArguments) {
|
|
59
|
+
const queryKey = this.createQueryKey(args);
|
|
60
|
+
// @ts-expect-error: data is not an entity
|
|
61
|
+
const entity = this.setOneEntity(data, queryKey, this.options.entity);
|
|
62
|
+
this.queryClient.setQueryData(queryKey, entity.id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected getEntity(id: EntityId) {
|
|
66
|
+
if (this.collection.deletedRecordIds.has(id)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const entity = this.collection.collection.get(id);
|
|
71
|
+
|
|
72
|
+
return entity as unknown as InstanceType<TEntityConstructor>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected queryFnWrapper(args: TArguments) {
|
|
76
|
+
const queryKey = this.createQueryKey(args);
|
|
77
|
+
return {
|
|
78
|
+
run: async () => {
|
|
79
|
+
if (!this.options.queryFn) {
|
|
80
|
+
throw new Error("Query function is not defined");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const data = (await this.options.queryFn(
|
|
84
|
+
args,
|
|
85
|
+
this.context,
|
|
86
|
+
)) as EntityDataAny;
|
|
87
|
+
const entity = this.setOneEntity(data, queryKey, this.options.entity);
|
|
88
|
+
return entity.id as EntityId;
|
|
89
|
+
},
|
|
90
|
+
queryKey,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
UseQueryManyFunction,
|
|
3
|
+
UseQueryOneOptions,
|
|
4
|
+
UseQueryCommonOptions,
|
|
5
|
+
UseQueryManyOptions,
|
|
6
|
+
UseQueryOneFunction,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
import { QueryBase } from "./QueryBase";
|
|
10
|
+
import { QueryFragmentMany } from "./QueryFragmentMany";
|
|
11
|
+
import { QueryFragmentOne } from "./QueryFragmentOne";
|
|
12
|
+
import { QueryMany } from "./QueryMany";
|
|
13
|
+
import { QueryManyBase } from "./QueryManyBase";
|
|
14
|
+
import { QueryOne } from "./QueryOne";
|
|
15
|
+
import { QueryOneBase } from "./QueryOneBase";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
QueryBase,
|
|
19
|
+
QueryFragmentMany,
|
|
20
|
+
QueryFragmentOne,
|
|
21
|
+
QueryMany,
|
|
22
|
+
QueryManyBase,
|
|
23
|
+
QueryOne,
|
|
24
|
+
QueryOneBase,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type {
|
|
28
|
+
UseQueryCommonOptions,
|
|
29
|
+
UseQueryManyFunction,
|
|
30
|
+
UseQueryManyOptions,
|
|
31
|
+
UseQueryOneFunction,
|
|
32
|
+
UseQueryOneOptions,
|
|
33
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DefaultError, NetworkMode } from "@tanstack/react-query";
|
|
2
|
+
import type { EntityConstructorAny } from "../entity";
|
|
3
|
+
import type { MQClientContextRegistered } from "../client/types";
|
|
4
|
+
import type { InferEntityData } from "../utils";
|
|
5
|
+
|
|
6
|
+
export type UseQueryManyFunction<
|
|
7
|
+
TArguments,
|
|
8
|
+
TEntityConstructor extends EntityConstructorAny,
|
|
9
|
+
> = (
|
|
10
|
+
args: TArguments,
|
|
11
|
+
context: MQClientContextRegistered,
|
|
12
|
+
) =>
|
|
13
|
+
| InferEntityData<TEntityConstructor>[]
|
|
14
|
+
| Promise<InferEntityData<TEntityConstructor>[]>;
|
|
15
|
+
|
|
16
|
+
export type UseQueryOneFunction<
|
|
17
|
+
TArguments,
|
|
18
|
+
TEntityConstructor extends EntityConstructorAny,
|
|
19
|
+
> = (
|
|
20
|
+
args: TArguments,
|
|
21
|
+
context: MQClientContextRegistered,
|
|
22
|
+
) =>
|
|
23
|
+
| InferEntityData<TEntityConstructor>
|
|
24
|
+
| Promise<InferEntityData<TEntityConstructor>>;
|
|
25
|
+
|
|
26
|
+
export interface UseQueryCommonOptions<
|
|
27
|
+
TArguments,
|
|
28
|
+
TMeta = void,
|
|
29
|
+
TEntityConstructor extends EntityConstructorAny = EntityConstructorAny,
|
|
30
|
+
TError extends DefaultError = DefaultError,
|
|
31
|
+
> {
|
|
32
|
+
entity: TEntityConstructor;
|
|
33
|
+
queryKey: () => ReadonlyArray<unknown>;
|
|
34
|
+
staleTime?: number | "static";
|
|
35
|
+
gcTime?: number;
|
|
36
|
+
enabled?: boolean | ((meta: TMeta, args: TArguments) => boolean);
|
|
37
|
+
// enabled?: boolean
|
|
38
|
+
networkMode?: NetworkMode;
|
|
39
|
+
retry?: boolean | number | ((failureCount: number, error: TError) => boolean);
|
|
40
|
+
retryOnMount?: boolean;
|
|
41
|
+
retryDelay?: number | ((failureCount: number, error: TError) => number);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface UseQueryOneOptions<
|
|
45
|
+
TArguments,
|
|
46
|
+
TMeta = void,
|
|
47
|
+
TEntityConstructor extends EntityConstructorAny = EntityConstructorAny,
|
|
48
|
+
TError extends DefaultError = DefaultError,
|
|
49
|
+
> extends UseQueryCommonOptions<TArguments, TMeta, TEntityConstructor, TError> {
|
|
50
|
+
queryFn?: UseQueryOneFunction<TArguments, TEntityConstructor>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface UseQueryManyOptions<
|
|
54
|
+
TArguments,
|
|
55
|
+
TMeta = void,
|
|
56
|
+
TEntityConstructor extends EntityConstructorAny = EntityConstructorAny,
|
|
57
|
+
TError extends DefaultError = DefaultError,
|
|
58
|
+
> extends UseQueryCommonOptions<TArguments, TMeta, TEntityConstructor, TError> {
|
|
59
|
+
queryFn?: UseQueryManyFunction<TArguments, TEntityConstructor>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { MQClient } from "../client";
|
|
3
|
+
|
|
4
|
+
export function createReactContext<TMQClient extends MQClient<any>>() {
|
|
5
|
+
const context = createContext<TMQClient | null>(null);
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
Provider: ({
|
|
9
|
+
children,
|
|
10
|
+
client,
|
|
11
|
+
}: {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
client: TMQClient;
|
|
14
|
+
}) => <context.Provider value={client}>{children}</context.Provider>,
|
|
15
|
+
useContext: () => {
|
|
16
|
+
const ctx = useContext(context);
|
|
17
|
+
if (!ctx) {
|
|
18
|
+
throw new Error("useMQ must be used within an MQProvider");
|
|
19
|
+
}
|
|
20
|
+
return ctx;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EntityConstructorAny } from "../entity";
|
|
2
|
+
|
|
3
|
+
const ENTITY_ID_INCREMENTORS = new Map<string, number>();
|
|
4
|
+
const CLIENT_ONLY_ENTITY_ID_PREFIX = "entityClientOnlyId";
|
|
5
|
+
|
|
6
|
+
export function generateEntityId(
|
|
7
|
+
entityConstructor: EntityConstructorAny,
|
|
8
|
+
): string {
|
|
9
|
+
const id = (ENTITY_ID_INCREMENTORS.get(entityConstructor.name) ?? 0) + 1;
|
|
10
|
+
ENTITY_ID_INCREMENTORS.set(entityConstructor.name, id);
|
|
11
|
+
return `${CLIENT_ONLY_ENTITY_ID_PREFIX}_${entityConstructor.name}_${id}`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { generateEntityId } from "./generateEntityId";
|
|
2
|
+
import { invalidateQueryByHash } from "./invalidateQueryByHash";
|
|
3
|
+
|
|
4
|
+
import type { InferEntity, InferEntityData } from "./types";
|
|
5
|
+
|
|
6
|
+
export { invalidateQueryByHash, generateEntityId };
|
|
7
|
+
|
|
8
|
+
export type { InferEntity, InferEntityData };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { QueryCache } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
export function invalidateQueryByHash(
|
|
4
|
+
hash: string,
|
|
5
|
+
cache: QueryCache,
|
|
6
|
+
onQueryNotFound?: (hash: string) => void,
|
|
7
|
+
) {
|
|
8
|
+
const query = cache.get(hash)
|
|
9
|
+
|
|
10
|
+
if (query) {
|
|
11
|
+
query.invalidate()
|
|
12
|
+
if (query.isActive()) {
|
|
13
|
+
query.fetch()
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
onQueryNotFound?.(hash)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EntityAny, EntityDataAny } from "../entity";
|
|
2
|
+
|
|
3
|
+
export type InferEntity<T> = T extends { new (...args: any[]): infer E }
|
|
4
|
+
? E extends EntityAny
|
|
5
|
+
? E
|
|
6
|
+
: never
|
|
7
|
+
: T extends EntityAny
|
|
8
|
+
? T
|
|
9
|
+
: never;
|
|
10
|
+
|
|
11
|
+
export type InferEntityData<T> =
|
|
12
|
+
InferEntity<T> extends {
|
|
13
|
+
hydrate(data: infer TData): void;
|
|
14
|
+
}
|
|
15
|
+
? TData
|
|
16
|
+
: T extends EntityDataAny
|
|
17
|
+
? T
|
|
18
|
+
: never;
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import "./index.css";
|
|
4
|
+
import App from "./App.tsx";
|
|
5
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
6
|
+
import { queryClient } from "./libs/react-query.ts";
|
|
7
|
+
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
8
|
+
|
|
9
|
+
createRoot(document.getElementById("root")!).render(
|
|
10
|
+
<StrictMode>
|
|
11
|
+
<QueryClientProvider client={queryClient}>
|
|
12
|
+
<App />
|
|
13
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
14
|
+
</QueryClientProvider>
|
|
15
|
+
</StrictMode>
|
|
16
|
+
);
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
|
|
18
|
+
/* Linting */
|
|
19
|
+
"strict": true,
|
|
20
|
+
"noUnusedLocals": true,
|
|
21
|
+
"noUnusedParameters": true,
|
|
22
|
+
"erasableSyntaxOnly": false,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
"noUncheckedSideEffectImports": true
|
|
25
|
+
},
|
|
26
|
+
"include": ["src"]
|
|
27
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"erasableSyntaxOnly": false,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedSideEffectImports": true
|
|
23
|
+
},
|
|
24
|
+
"include": ["vite.config.ts"]
|
|
25
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import react from "@vitejs/plugin-react";
|
|
5
|
+
import observerPlugin from "mobx-react-observer/babel-plugin";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
const mobxQueryDir = resolve(__dirname, "src/libs/mobx-query");
|
|
10
|
+
|
|
11
|
+
// https://vite.dev/config/
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
plugins: [
|
|
14
|
+
react({
|
|
15
|
+
babel: {
|
|
16
|
+
plugins: [
|
|
17
|
+
observerPlugin(),
|
|
18
|
+
[
|
|
19
|
+
"@babel/plugin-proposal-decorators",
|
|
20
|
+
{
|
|
21
|
+
version: "2023-05",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
build: {
|
|
29
|
+
lib: {
|
|
30
|
+
entry: {
|
|
31
|
+
client: resolve(mobxQueryDir, "./client/index.js"),
|
|
32
|
+
entity: resolve(mobxQueryDir, "./entity/index.js"),
|
|
33
|
+
mutations: resolve(mobxQueryDir, "./mutations/index.js"),
|
|
34
|
+
queries: resolve(mobxQueryDir, "./queries/index.js"),
|
|
35
|
+
react: resolve(mobxQueryDir, "./react/index.js"),
|
|
36
|
+
utils: resolve(mobxQueryDir, "./utils/index.js"),
|
|
37
|
+
},
|
|
38
|
+
name: "mobx-query",
|
|
39
|
+
},
|
|
40
|
+
rollupOptions: {
|
|
41
|
+
external: [
|
|
42
|
+
"react",
|
|
43
|
+
"react-dom",
|
|
44
|
+
"mobx",
|
|
45
|
+
"mobx-react-lite",
|
|
46
|
+
"mobx-react-observer",
|
|
47
|
+
"@tanstack/react-query",
|
|
48
|
+
"@tanstack/react-query-devtools",
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|