@dune2/tools 1.1.5 → 1.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/package.json +13 -13
- package/readme.md +16 -16
- package/src/factory/createStateContext.tsx +3 -3
- package/src/factory/fieldsMap.tsx +1 -1
- package/src/factory/mapProps.tsx +7 -7
- package/src/i18n/enums.ts +8 -8
- package/src/numbro/index.ts +20 -20
- package/src/numbro/shared.ts +2 -2
- package/src/rq/RequestBuilder.react-server.ts +16 -18
- package/src/rq/RequestBuilder.ts +21 -23
- package/src/rq/createApi.ts +7 -5
- package/src/rq/defaultQueryClient.ts +3 -3
- package/src/rq/index.react-server.ts +1 -1
- package/src/rq/index.ts +3 -3
- package/src/rq/options.ts +12 -16
- package/src/shared/index.ts +2 -2
- package/src/storage/cookie.ts +2 -2
- package/src/storage/index.ts +8 -9
- package/src/store/index.ts +5 -5
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dune2/tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n"
|
|
7
7
|
],
|
|
8
|
+
"license": "ISC",
|
|
9
|
+
"author": "",
|
|
8
10
|
"repository": {
|
|
9
11
|
"type": "git",
|
|
10
12
|
"url": "https://github.com/liaoyinglong/next-tools.git",
|
|
11
13
|
"directory": "packages/tools"
|
|
12
14
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
+
"files": [
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
15
18
|
"exports": {
|
|
16
19
|
"./factory/*": "./src/factory/*.tsx",
|
|
17
20
|
"./numbro": "./src/numbro/index.ts",
|
|
@@ -29,23 +32,20 @@
|
|
|
29
32
|
"./package.json": "./package.json",
|
|
30
33
|
".": "./src/index.ts"
|
|
31
34
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
35
38
|
"dependencies": {
|
|
36
39
|
"@tanstack/react-query": "^5",
|
|
37
|
-
"bignumber.js": "^9.1
|
|
38
|
-
"es-toolkit": "^1",
|
|
40
|
+
"bignumber.js": "^9.3.1",
|
|
41
|
+
"es-toolkit": "^1.44.0",
|
|
39
42
|
"js-cookie": "^3.0.5",
|
|
40
43
|
"store2": "^2.14.3",
|
|
41
|
-
"valtio": "^2"
|
|
44
|
+
"valtio": "^2.3.0"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"@types/js-cookie": "3.0.3",
|
|
45
|
-
"react": "^19"
|
|
46
|
-
},
|
|
47
|
-
"publishConfig": {
|
|
48
|
-
"access": "public"
|
|
48
|
+
"react": "^19.2.4"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "tsc --diagnostics",
|
package/readme.md
CHANGED
|
@@ -15,10 +15,10 @@ npm install @dune2/tools
|
|
|
15
15
|
React state management utilities for creating context providers.
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
import { createStateContext } from
|
|
18
|
+
import { createStateContext } from '@dune2/tools/factory/createStateContext';
|
|
19
19
|
|
|
20
20
|
const { Provider, useContextValue } = createStateContext({
|
|
21
|
-
name:
|
|
21
|
+
name: 'Counter',
|
|
22
22
|
useValueHooks: ({ initialCount = 0 }) => {
|
|
23
23
|
const [count, setCount] = useState(initialCount);
|
|
24
24
|
return { count, setCount };
|
|
@@ -31,17 +31,17 @@ const { Provider, useContextValue } = createStateContext({
|
|
|
31
31
|
Browser storage wrapper with React hooks integration.
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
|
-
import { createStorage } from
|
|
34
|
+
import { createStorage } from '@dune2/tools/storage';
|
|
35
35
|
|
|
36
36
|
class DataMap {
|
|
37
|
-
token =
|
|
37
|
+
token = '';
|
|
38
38
|
user = null;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const storage = createStorage({
|
|
42
42
|
DataMap,
|
|
43
|
-
namespace:
|
|
44
|
-
storageType:
|
|
43
|
+
namespace: 'app',
|
|
44
|
+
storageType: 'local', // or 'session'
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// Use in React
|
|
@@ -53,16 +53,16 @@ const token = storage.token.useValue();
|
|
|
53
53
|
Enhanced React Query utilities with built-in request building.
|
|
54
54
|
|
|
55
55
|
```typescript
|
|
56
|
-
import { RequestBuilder } from
|
|
56
|
+
import { RequestBuilder } from '@dune2/tools/rq';
|
|
57
57
|
|
|
58
58
|
const userApi = new RequestBuilder({
|
|
59
|
-
url:
|
|
60
|
-
method:
|
|
61
|
-
urlPathParams: [
|
|
59
|
+
url: '/api/users/{id}',
|
|
60
|
+
method: 'get',
|
|
61
|
+
urlPathParams: ['id'],
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
// Use in components
|
|
65
|
-
const { data, isLoading } = userApi.useQuery({ id:
|
|
65
|
+
const { data, isLoading } = userApi.useQuery({ id: '123' });
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
### Store
|
|
@@ -70,10 +70,10 @@ const { data, isLoading } = userApi.useQuery({ id: "123" });
|
|
|
70
70
|
Valtio-based state management with TypeScript support.
|
|
71
71
|
|
|
72
72
|
```typescript
|
|
73
|
-
import { createStore } from
|
|
73
|
+
import { createStore } from '@dune2/tools/store';
|
|
74
74
|
|
|
75
75
|
const counterStore = createStore({
|
|
76
|
-
name:
|
|
76
|
+
name: 'counter',
|
|
77
77
|
state: { count: 0 },
|
|
78
78
|
actionsCreator: (state) => ({
|
|
79
79
|
increment: () => state.count++,
|
|
@@ -90,11 +90,11 @@ const { count } = counterStore.useSnapshot();
|
|
|
90
90
|
BigNumber.js wrapper for precise number formatting and calculations.
|
|
91
91
|
|
|
92
92
|
```typescript
|
|
93
|
-
import { numbro } from
|
|
93
|
+
import { numbro } from '@dune2/tools/numbro';
|
|
94
94
|
|
|
95
95
|
const price = numbro(123.456);
|
|
96
96
|
price.format({ mantissa: 2 }); // "123.46"
|
|
97
|
-
price.formatCurrency({ symbol:
|
|
97
|
+
price.formatCurrency({ symbol: '$' }); // "$123.46"
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
### Shared
|
|
@@ -102,5 +102,5 @@ price.formatCurrency({ symbol: "$" }); // "$123.46"
|
|
|
102
102
|
TypeScript utility types for better type safety.
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
|
-
import type { OptionalKeys, Overwrite, Print } from
|
|
105
|
+
import type { OptionalKeys, Overwrite, Print } from '@dune2/tools/shared';
|
|
106
106
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import type { ComponentType, FC, PropsWithChildren } from
|
|
3
|
-
import React, { use, useContext } from
|
|
1
|
+
'use client';
|
|
2
|
+
import type { ComponentType, FC, PropsWithChildren } from 'react';
|
|
3
|
+
import React, { use, useContext } from 'react';
|
|
4
4
|
|
|
5
5
|
interface Params<P, T> {
|
|
6
6
|
/**
|
package/src/factory/mapProps.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ComponentProps, ComponentType, FC, JSX } from
|
|
2
|
-
import { forwardRef } from
|
|
3
|
-
import type { OptionalKeys } from
|
|
1
|
+
import type { ComponentProps, ComponentType, FC, JSX } from 'react';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import type { OptionalKeys } from '../shared/OptionalKeys';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ```js
|
|
@@ -48,7 +48,7 @@ export function mapProps<C, P extends Record<any, any>>(
|
|
|
48
48
|
) {
|
|
49
49
|
const MappedComponent = (props: P, ref: any) => {
|
|
50
50
|
let newProps =
|
|
51
|
-
typeof mapper ===
|
|
51
|
+
typeof mapper === 'function'
|
|
52
52
|
? mapper(props)
|
|
53
53
|
: {
|
|
54
54
|
...mapper,
|
|
@@ -57,16 +57,16 @@ export function mapProps<C, P extends Record<any, any>>(
|
|
|
57
57
|
|
|
58
58
|
return <BaseComponent {...newProps} ref={ref}></BaseComponent>;
|
|
59
59
|
};
|
|
60
|
-
if (process.env.NODE_ENV !==
|
|
60
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
61
61
|
MappedComponent.displayName = `mapProps(${getDisplayName(BaseComponent)})`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
return forwardRef<any, P>(MappedComponent);
|
|
65
65
|
}
|
|
66
66
|
function getDisplayName(Component: ComponentType<any>): string {
|
|
67
|
-
return typeof Component ===
|
|
67
|
+
return typeof Component === 'string'
|
|
68
68
|
? Component
|
|
69
|
-
: Component.displayName || Component.name ||
|
|
69
|
+
: Component.displayName || Component.name || 'Unknown';
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// type helper
|
package/src/i18n/enums.ts
CHANGED
|
@@ -6,21 +6,21 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export enum LocalesEnum {
|
|
8
8
|
// 中文 Chinese
|
|
9
|
-
zh =
|
|
9
|
+
zh = 'zh',
|
|
10
10
|
// 印尼 Indonesian
|
|
11
|
-
id =
|
|
11
|
+
id = 'id',
|
|
12
12
|
// 英文 English
|
|
13
|
-
en =
|
|
13
|
+
en = 'en',
|
|
14
14
|
// 立陶宛 Lithuanian
|
|
15
|
-
lt =
|
|
15
|
+
lt = 'lt',
|
|
16
16
|
// 俄罗斯 Russian
|
|
17
|
-
ru =
|
|
17
|
+
ru = 'ru',
|
|
18
18
|
// 日本 Japanese
|
|
19
|
-
ja =
|
|
19
|
+
ja = 'ja',
|
|
20
20
|
// 土耳其 Turkish
|
|
21
|
-
tr =
|
|
21
|
+
tr = 'tr',
|
|
22
22
|
// 韩国 Korean
|
|
23
|
-
ko =
|
|
23
|
+
ko = 'ko',
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// 让 vite 能识别成 es-module
|
package/src/numbro/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import BigNumber from
|
|
2
|
-
import type { Format } from
|
|
3
|
-
import { RoundingMode } from
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import type { Format } from './shared';
|
|
3
|
+
import { RoundingMode } from './shared';
|
|
4
4
|
|
|
5
|
-
export * from
|
|
5
|
+
export * from './shared';
|
|
6
6
|
|
|
7
7
|
// 可以参与运算的参数
|
|
8
8
|
type OperationParams =
|
|
@@ -32,9 +32,9 @@ export class Numbro {
|
|
|
32
32
|
if (other instanceof Numbro) {
|
|
33
33
|
other = other.bigNumber;
|
|
34
34
|
}
|
|
35
|
-
if (typeof other ===
|
|
35
|
+
if (typeof other === 'string') {
|
|
36
36
|
// case: "1,000" => "1000"
|
|
37
|
-
other = other.trim().replace(/,/g,
|
|
37
|
+
other = other.trim().replace(/,/g, '');
|
|
38
38
|
}
|
|
39
39
|
const res = new Numbro.BN(other as never);
|
|
40
40
|
return res;
|
|
@@ -90,8 +90,8 @@ export class Numbro {
|
|
|
90
90
|
const combinedFormat: BigNumber.Format = {
|
|
91
91
|
suffix: postfix,
|
|
92
92
|
groupSize: thousandSeparated ? 3 : 0,
|
|
93
|
-
groupSeparator: thousandSeparated ?
|
|
94
|
-
decimalSeparator:
|
|
93
|
+
groupSeparator: thousandSeparated ? ',' : '',
|
|
94
|
+
decimalSeparator: '.',
|
|
95
95
|
...rest,
|
|
96
96
|
};
|
|
97
97
|
|
|
@@ -103,9 +103,9 @@ export class Numbro {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// 百分比
|
|
106
|
-
if (output ===
|
|
106
|
+
if (output === 'percent') {
|
|
107
107
|
num = num.multipliedBy(100);
|
|
108
|
-
combinedFormat.suffix =
|
|
108
|
+
combinedFormat.suffix = '%';
|
|
109
109
|
}
|
|
110
110
|
// 格式化成 1k, 1m, 1b, 1t
|
|
111
111
|
if (average) {
|
|
@@ -148,14 +148,14 @@ export class Numbro {
|
|
|
148
148
|
private getPrefixSign(forceSign: boolean | undefined) {
|
|
149
149
|
// 如果是 NaN 或者 0,那么不显示正负号
|
|
150
150
|
if (forceSign === false || this.bigNumber.isNaN() || this.bigNumber.eq(0)) {
|
|
151
|
-
return
|
|
151
|
+
return '';
|
|
152
152
|
}
|
|
153
153
|
// 强制显示正负号
|
|
154
154
|
if (forceSign) {
|
|
155
|
-
return this.bigNumber.isPositive() ?
|
|
155
|
+
return this.bigNumber.isPositive() ? '+' : '-';
|
|
156
156
|
}
|
|
157
157
|
// 如果没有指定 forceSign,那么需要判断是否小于 0
|
|
158
|
-
return this.bigNumber.isPositive() ?
|
|
158
|
+
return this.bigNumber.isPositive() ? '' : '-';
|
|
159
159
|
}
|
|
160
160
|
//#endregion
|
|
161
161
|
|
|
@@ -171,10 +171,10 @@ export class Numbro {
|
|
|
171
171
|
thousand: Math.pow(10, 3),
|
|
172
172
|
};
|
|
173
173
|
const config = [
|
|
174
|
-
[powers.trillion,
|
|
175
|
-
[powers.billion,
|
|
176
|
-
[powers.million,
|
|
177
|
-
[powers.thousand,
|
|
174
|
+
[powers.trillion, 'T'],
|
|
175
|
+
[powers.billion, 'B'],
|
|
176
|
+
[powers.million, 'M'],
|
|
177
|
+
[powers.thousand, 'K'],
|
|
178
178
|
] as const;
|
|
179
179
|
|
|
180
180
|
for (let i = 0; i < config.length; i++) {
|
|
@@ -193,7 +193,7 @@ export class Numbro {
|
|
|
193
193
|
deleteInvalidZero: boolean | undefined,
|
|
194
194
|
mantissa: number | undefined | null | string,
|
|
195
195
|
outputFormat: string,
|
|
196
|
-
suffix =
|
|
196
|
+
suffix = '',
|
|
197
197
|
) {
|
|
198
198
|
const num = this.bigNumber;
|
|
199
199
|
let shouldDeleteEndZero = false;
|
|
@@ -203,13 +203,13 @@ export class Numbro {
|
|
|
203
203
|
// 先转成数字 移除无效0
|
|
204
204
|
// case: 1.00100 => 1.001
|
|
205
205
|
let cloned = num.toString();
|
|
206
|
-
let clonedDecimal = cloned.split(
|
|
206
|
+
let clonedDecimal = cloned.split('.')[1]?.length ?? 0;
|
|
207
207
|
// 移除无效 0 后,小数位数还比指定的小数位数多,那么就不用删除尾数 0
|
|
208
208
|
shouldDeleteEndZero = !(clonedDecimal > (mantissa as number));
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
if (shouldDeleteEndZero) {
|
|
212
|
-
const isDecimal = outputFormat.includes(
|
|
212
|
+
const isDecimal = outputFormat.includes('.');
|
|
213
213
|
if (isDecimal) {
|
|
214
214
|
// 移除尾数 0
|
|
215
215
|
// outputFormat = outputFormat.replace(/\.?0+$/, "");
|
package/src/numbro/shared.ts
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
import type { QueryClient } from
|
|
2
|
-
|
|
3
|
-
import type { Basic, RequestBuilderOptions, RequestConfig } from "./options";
|
|
1
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import type { Basic, RequestBuilderOptions, RequestConfig } from './options';
|
|
4
3
|
|
|
5
4
|
export class RequestBuilder<Req = any, Res = any> {
|
|
6
5
|
constructor(public options: RequestBuilderOptions<Req, Res>) {
|
|
7
6
|
this.defaultQueryFn = this.defaultQueryFn.bind(this);
|
|
8
7
|
this.request = this.request.bind(this);
|
|
9
8
|
this.requestWithConfig = this.requestWithConfig.bind(this);
|
|
10
|
-
this.options.method ??=
|
|
9
|
+
this.options.method ??= 'get';
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
//#region default requestFn
|
|
14
|
-
static requestFn: Basic[
|
|
15
|
-
static setRequestFn(requestFn: Basic[
|
|
13
|
+
static requestFn: Basic['requestFn'] | null = null;
|
|
14
|
+
static setRequestFn(requestFn: Basic['requestFn'] | null) {
|
|
16
15
|
RequestBuilder.requestFn = requestFn;
|
|
17
16
|
}
|
|
18
17
|
//#endregion
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
static
|
|
22
|
-
|
|
23
|
-
RequestBuilder.queryClient = queryClient;
|
|
19
|
+
static queryClientFactory: (() => QueryClient) | null = null;
|
|
20
|
+
static setQueryClientFactory(factory: (() => QueryClient) | null) {
|
|
21
|
+
RequestBuilder.queryClientFactory = factory;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
/**
|
|
@@ -28,15 +26,15 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
28
26
|
* 会依次从以下地方获取
|
|
29
27
|
* - options
|
|
30
28
|
* - 当前实例 options
|
|
31
|
-
* - RequestBuilder.
|
|
29
|
+
* - RequestBuilder.queryClientFactory()
|
|
32
30
|
*/
|
|
33
31
|
ensureQueryClient(options?: { queryClient?: QueryClient }) {
|
|
34
32
|
const queryClient =
|
|
35
33
|
options?.queryClient ??
|
|
36
34
|
this.options.queryClient ??
|
|
37
|
-
RequestBuilder.
|
|
35
|
+
RequestBuilder.queryClientFactory?.();
|
|
38
36
|
if (!queryClient) {
|
|
39
|
-
throw new Error(
|
|
37
|
+
throw new Error('queryClient is not defined');
|
|
40
38
|
}
|
|
41
39
|
return queryClient;
|
|
42
40
|
}
|
|
@@ -52,7 +50,7 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
52
50
|
const method = this.options.method!;
|
|
53
51
|
let data;
|
|
54
52
|
// 根据请求方法来放到 url 上或者 body 里
|
|
55
|
-
if (![
|
|
53
|
+
if (!['get', 'head', 'options'].includes(method)) {
|
|
56
54
|
data = params;
|
|
57
55
|
params = undefined;
|
|
58
56
|
}
|
|
@@ -76,10 +74,10 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
76
74
|
let requestFn =
|
|
77
75
|
config.requestFn ?? this.options.requestFn ?? RequestBuilder.requestFn;
|
|
78
76
|
if (!requestFn) {
|
|
79
|
-
throw new Error(
|
|
77
|
+
throw new Error('request function is not defined');
|
|
80
78
|
}
|
|
81
79
|
this.options.urlPathParams?.forEach((param) => {
|
|
82
|
-
let t =
|
|
80
|
+
let t = '';
|
|
83
81
|
//#region config.params || config.data 在 queryHash 之后不变的话,会保持同一个引用,这里需要做个浅拷贝,将引用打破
|
|
84
82
|
if (config.params?.[param]) {
|
|
85
83
|
config.params = { ...config.params };
|
|
@@ -107,7 +105,7 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
107
105
|
* 通常配置 react-query 的 queryKey
|
|
108
106
|
*/
|
|
109
107
|
getQueryKey(params?: Req) {
|
|
110
|
-
if (typeof params ===
|
|
108
|
+
if (typeof params === 'undefined') {
|
|
111
109
|
return [this.options.url, this.options.method];
|
|
112
110
|
}
|
|
113
111
|
return [this.options.url, this.options.method!, params] as const;
|
|
@@ -152,5 +150,5 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
const throwErrorInRSC = () => {
|
|
155
|
-
throw new Error(
|
|
153
|
+
throw new Error('This method is not supported in RSC');
|
|
156
154
|
};
|
package/src/rq/RequestBuilder.ts
CHANGED
|
@@ -7,12 +7,11 @@ import type {
|
|
|
7
7
|
RefetchOptions,
|
|
8
8
|
UseInfiniteQueryOptions,
|
|
9
9
|
UseMutationOptions,
|
|
10
|
-
} from
|
|
11
|
-
import { useInfiniteQuery, useMutation, useQuery } from
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { queryClient } from "./defaultQueryClient";
|
|
10
|
+
} from '@tanstack/react-query';
|
|
11
|
+
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
|
12
|
+
import { useDebugValue, useMemo } from 'react';
|
|
13
|
+
import { fieldsMap, type FieldsMap } from '../factory/fieldsMap';
|
|
14
|
+
import { queryClient } from './defaultQueryClient';
|
|
16
15
|
import type {
|
|
17
16
|
Basic,
|
|
18
17
|
FetchQueryOptions,
|
|
@@ -21,27 +20,26 @@ import type {
|
|
|
21
20
|
RequestBuilderOptions,
|
|
22
21
|
RequestConfig,
|
|
23
22
|
UseQueryOptions,
|
|
24
|
-
} from
|
|
23
|
+
} from './options';
|
|
25
24
|
|
|
26
25
|
export class RequestBuilder<Req = any, Res = any> {
|
|
27
26
|
constructor(public options: RequestBuilderOptions<Req, Res>) {
|
|
28
27
|
this.defaultQueryFn = this.defaultQueryFn.bind(this);
|
|
29
28
|
this.request = this.request.bind(this);
|
|
30
29
|
this.requestWithConfig = this.requestWithConfig.bind(this);
|
|
31
|
-
this.options.method ??=
|
|
30
|
+
this.options.method ??= 'get';
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
//#region default requestFn
|
|
35
|
-
static requestFn: Basic[
|
|
36
|
-
static setRequestFn(requestFn: Basic[
|
|
34
|
+
static requestFn: Basic['requestFn'] | null = null;
|
|
35
|
+
static setRequestFn(requestFn: Basic['requestFn'] | null) {
|
|
37
36
|
RequestBuilder.requestFn = requestFn;
|
|
38
37
|
}
|
|
39
38
|
//#endregion
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
static
|
|
43
|
-
|
|
44
|
-
RequestBuilder.queryClient = queryClient;
|
|
40
|
+
static queryClientFactory: (() => QueryClient) | null = () => queryClient;
|
|
41
|
+
static setQueryClientFactory(factory: (() => QueryClient) | null) {
|
|
42
|
+
RequestBuilder.queryClientFactory = factory;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
/**
|
|
@@ -49,15 +47,15 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
49
47
|
* 会依次从以下地方获取
|
|
50
48
|
* - options
|
|
51
49
|
* - 当前实例 options
|
|
52
|
-
* - RequestBuilder.
|
|
50
|
+
* - RequestBuilder.queryClientFactory()
|
|
53
51
|
*/
|
|
54
52
|
ensureQueryClient(options?: { queryClient?: QueryClient }) {
|
|
55
53
|
const queryClient =
|
|
56
54
|
options?.queryClient ??
|
|
57
55
|
this.options.queryClient ??
|
|
58
|
-
RequestBuilder.
|
|
56
|
+
RequestBuilder.queryClientFactory?.();
|
|
59
57
|
if (!queryClient) {
|
|
60
|
-
throw new Error(
|
|
58
|
+
throw new Error('queryClient is not defined');
|
|
61
59
|
}
|
|
62
60
|
return queryClient;
|
|
63
61
|
}
|
|
@@ -84,7 +82,7 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
84
82
|
const method = this.options.method!;
|
|
85
83
|
let data;
|
|
86
84
|
// 根据请求方法来放到 url 上或者 body 里
|
|
87
|
-
if (![
|
|
85
|
+
if (!['get', 'head', 'options'].includes(method)) {
|
|
88
86
|
data = params;
|
|
89
87
|
params = undefined;
|
|
90
88
|
}
|
|
@@ -108,10 +106,10 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
108
106
|
let requestFn =
|
|
109
107
|
config.requestFn ?? this.options.requestFn ?? RequestBuilder.requestFn;
|
|
110
108
|
if (!requestFn) {
|
|
111
|
-
throw new Error(
|
|
109
|
+
throw new Error('request function is not defined');
|
|
112
110
|
}
|
|
113
111
|
this.options.urlPathParams?.forEach((param) => {
|
|
114
|
-
let t =
|
|
112
|
+
let t = '';
|
|
115
113
|
//#region config.params || config.data 在 queryHash 之后不变的话,会保持同一个引用,这里需要做个浅拷贝,将引用打破
|
|
116
114
|
if (config.params?.[param]) {
|
|
117
115
|
config.params = { ...config.params };
|
|
@@ -139,7 +137,7 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
139
137
|
* 通常配置 react-query 的 queryKey
|
|
140
138
|
*/
|
|
141
139
|
getQueryKey(params?: Req) {
|
|
142
|
-
if (typeof params ===
|
|
140
|
+
if (typeof params === 'undefined') {
|
|
143
141
|
return [this.options.url, this.options.method];
|
|
144
142
|
}
|
|
145
143
|
return [this.options.url, this.options.method!, params] as const;
|
|
@@ -150,7 +148,7 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
150
148
|
return this.request(queryKey[2], {
|
|
151
149
|
signal: ctx.signal,
|
|
152
150
|
meta: ctx.meta,
|
|
153
|
-
requestFn: ctx.meta?.[
|
|
151
|
+
requestFn: ctx.meta?.['requestFn'] as never,
|
|
154
152
|
});
|
|
155
153
|
}
|
|
156
154
|
|
|
@@ -295,7 +293,7 @@ export class RequestBuilder<Req = any, Res = any> {
|
|
|
295
293
|
);
|
|
296
294
|
}, [rawData]);
|
|
297
295
|
|
|
298
|
-
type Data<T> = T extends PageData ? T[
|
|
296
|
+
type Data<T> = T extends PageData ? T['result'] : T;
|
|
299
297
|
|
|
300
298
|
const result = { ...res, data: data as Data<Res>, rawData };
|
|
301
299
|
useDebugValue(result);
|
package/src/rq/createApi.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { RequestBuilderOptions } from
|
|
2
|
-
import { RequestBuilder } from
|
|
1
|
+
import type { RequestBuilderOptions } from './options';
|
|
2
|
+
import { RequestBuilder } from './RequestBuilder';
|
|
3
3
|
|
|
4
|
-
interface Options<Req, Res>
|
|
5
|
-
|
|
4
|
+
interface Options<Req, Res> extends Omit<
|
|
5
|
+
Partial<RequestBuilderOptions<Req, Res>>,
|
|
6
|
+
'requestFn'
|
|
7
|
+
> {
|
|
6
8
|
/**
|
|
7
9
|
* 相当于 url
|
|
8
10
|
*/
|
|
@@ -30,7 +32,7 @@ export function createApi<Req, Res>(opts: Options<Req, Res>) {
|
|
|
30
32
|
const api = new RequestBuilder<Req, Res>({
|
|
31
33
|
url: opts.queryKey,
|
|
32
34
|
// 给定 get 则 在 requestFn 中可以通过 params 获取到参数,否则是 data 字段
|
|
33
|
-
method:
|
|
35
|
+
method: 'get',
|
|
34
36
|
requestFn: (config) => {
|
|
35
37
|
return requestFn(config.params) as never;
|
|
36
38
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { QueryClient } from
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
2
2
|
|
|
3
3
|
export const queryClient = new QueryClient({
|
|
4
4
|
defaultOptions: {
|
|
@@ -9,11 +9,11 @@ export const queryClient = new QueryClient({
|
|
|
9
9
|
// 在mac上,chrome 浏览器可能会出现没有请求的现象
|
|
10
10
|
// 主要是 navigator.onLine 的值为 false
|
|
11
11
|
// 但是实际上是没有断网的
|
|
12
|
-
networkMode:
|
|
12
|
+
networkMode: 'always',
|
|
13
13
|
},
|
|
14
14
|
mutations: {
|
|
15
15
|
retry: false,
|
|
16
|
-
networkMode:
|
|
16
|
+
networkMode: 'always',
|
|
17
17
|
},
|
|
18
18
|
},
|
|
19
19
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './RequestBuilder.react-server';
|
package/src/rq/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './RequestBuilder';
|
|
2
|
+
export * from './defaultQueryClient';
|
|
3
|
+
export * from './options';
|
package/src/rq/options.ts
CHANGED
|
@@ -3,17 +3,17 @@ import type {
|
|
|
3
3
|
FetchQueryOptions as RQFetchQueryOptions,
|
|
4
4
|
UseMutationOptions,
|
|
5
5
|
useQuery,
|
|
6
|
-
} from
|
|
6
|
+
} from '@tanstack/react-query';
|
|
7
7
|
|
|
8
8
|
// 定义 HTTP 方法类型
|
|
9
9
|
export type HttpMethod =
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
10
|
+
| 'get'
|
|
11
|
+
| 'post'
|
|
12
|
+
| 'put'
|
|
13
|
+
| 'delete'
|
|
14
|
+
| 'patch'
|
|
15
|
+
| 'head'
|
|
16
|
+
| 'options';
|
|
17
17
|
// 外部可以重写这个类型
|
|
18
18
|
export interface RequestBuilderMeta {}
|
|
19
19
|
|
|
@@ -48,23 +48,19 @@ export interface QueryClientBasic {
|
|
|
48
48
|
queryClient?: QueryClient;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
type OmitMetaAndPartial<T> = Partial<Omit<T,
|
|
51
|
+
type OmitMetaAndPartial<T> = Partial<Omit<T, 'meta'>>;
|
|
52
52
|
|
|
53
53
|
type RawUseQueryOptions<T> = Parameters<typeof useQuery<T>>[0];
|
|
54
54
|
// 透传给 useQuery
|
|
55
55
|
export interface UseQueryOptions<T>
|
|
56
|
-
extends OmitMetaAndPartial<RawUseQueryOptions<T>>,
|
|
57
|
-
Basic {}
|
|
56
|
+
extends OmitMetaAndPartial<RawUseQueryOptions<T>>, Basic {}
|
|
58
57
|
|
|
59
58
|
// 透传给 ensureQueryData / fetchQuery / prefetchQuery 等
|
|
60
59
|
export interface FetchQueryOptions<T>
|
|
61
|
-
extends OmitMetaAndPartial<RQFetchQueryOptions<T>>,
|
|
62
|
-
Basic,
|
|
63
|
-
QueryClientBasic {}
|
|
60
|
+
extends OmitMetaAndPartial<RQFetchQueryOptions<T>>, Basic, QueryClientBasic {}
|
|
64
61
|
|
|
65
62
|
export interface RequestBuilderOptions<Req, Res>
|
|
66
|
-
extends Basic,
|
|
67
|
-
QueryClientBasic {
|
|
63
|
+
extends Basic, QueryClientBasic {
|
|
68
64
|
/**
|
|
69
65
|
* 请求方法
|
|
70
66
|
* @default "get"
|
package/src/shared/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useLayoutEffect } from
|
|
1
|
+
import { useEffect, useLayoutEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
export const useIsomorphicLayoutEffect =
|
|
4
|
-
typeof window !==
|
|
4
|
+
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
package/src/storage/cookie.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Cookies from
|
|
1
|
+
import Cookies from 'js-cookie';
|
|
2
2
|
|
|
3
3
|
type CookieAttributes = Cookies.CookieAttributes;
|
|
4
4
|
type CookiesStatic = Cookies.CookiesStatic;
|
|
@@ -78,7 +78,7 @@ export function createCookieStorage<T extends Record<any, any>>(
|
|
|
78
78
|
namespace,
|
|
79
79
|
String(key),
|
|
80
80
|
// cookie 只能存 string
|
|
81
|
-
storageMap[key] +
|
|
81
|
+
storageMap[key] + '',
|
|
82
82
|
);
|
|
83
83
|
});
|
|
84
84
|
|
package/src/storage/index.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { isEqual } from
|
|
1
|
+
import { isEqual } from 'es-toolkit';
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
import type { StoreType } from 'store2';
|
|
4
|
+
import baseStore from 'store2';
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
import type { StoreType } from "store2";
|
|
5
|
-
import baseStore from "store2";
|
|
6
|
-
|
|
7
|
-
type StorageType = "local" | "session";
|
|
6
|
+
type StorageType = 'local' | 'session';
|
|
8
7
|
|
|
9
8
|
interface CreateStorageConfig<T> {
|
|
10
9
|
/**
|
|
@@ -90,11 +89,11 @@ class StorageHelper<V = any> {
|
|
|
90
89
|
// for current window
|
|
91
90
|
this.listeners.add(listener);
|
|
92
91
|
// for other windows
|
|
93
|
-
window.addEventListener(
|
|
92
|
+
window.addEventListener('storage', listener);
|
|
94
93
|
|
|
95
94
|
return () => {
|
|
96
95
|
this.listeners.delete(listener);
|
|
97
|
-
window.removeEventListener(
|
|
96
|
+
window.removeEventListener('storage', listener);
|
|
98
97
|
};
|
|
99
98
|
};
|
|
100
99
|
|
|
@@ -120,7 +119,7 @@ class StorageHelper<V = any> {
|
|
|
120
119
|
export function createStorage<T extends Record<string, any>>(
|
|
121
120
|
config: CreateStorageConfig<T>,
|
|
122
121
|
) {
|
|
123
|
-
const { DataMap, namespace, storageType =
|
|
122
|
+
const { DataMap, namespace, storageType = 'local' } = config;
|
|
124
123
|
|
|
125
124
|
const store = baseStore[storageType].namespace(namespace);
|
|
126
125
|
|
package/src/store/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { isEqual } from
|
|
2
|
-
import { useDebugValue, useRef, useSyncExternalStore } from
|
|
3
|
-
import { proxy, snapshot, subscribe, useSnapshot } from
|
|
1
|
+
import { isEqual } from 'es-toolkit';
|
|
2
|
+
import { useDebugValue, useRef, useSyncExternalStore } from 'react';
|
|
3
|
+
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio';
|
|
4
4
|
|
|
5
5
|
const stores: any = {};
|
|
6
6
|
|
|
7
|
-
if (typeof window !==
|
|
8
|
-
Object.defineProperty(window,
|
|
7
|
+
if (typeof window !== 'undefined') {
|
|
8
|
+
Object.defineProperty(window, '__stores2', {
|
|
9
9
|
get() {
|
|
10
10
|
let s: any = {};
|
|
11
11
|
Object.keys(stores).forEach((key) => {
|