@aiao/rxdb-react 0.0.7
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/hooks.d.ts +163 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/rxdb-react.d.ts +19 -0
- package/dist/rxdb-react.d.ts.map +1 -0
- package/eslint.config.mjs +12 -0
- package/package.json +27 -0
- package/src/hooks.spec.ts +127 -0
- package/src/hooks.ts +343 -0
- package/src/index.ts +2 -0
- package/src/rxdb-react.spec.tsx +41 -0
- package/src/rxdb-react.tsx +38 -0
- package/tsconfig.json +13 -0
- package/tsconfig.lib.json +48 -0
- package/tsconfig.spec.json +30 -0
- package/vite.config.ts +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aiao Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# rxdb-react
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { EntityStaticType, EntityType } from '../../rxdb/src/index.ts';
|
|
2
|
+
type UseOptions<T> = T | (() => T);
|
|
3
|
+
export interface RxDBResource<T> {
|
|
4
|
+
/**
|
|
5
|
+
* 资源的数值
|
|
6
|
+
*/
|
|
7
|
+
readonly value: T;
|
|
8
|
+
/**
|
|
9
|
+
* 资源的错误
|
|
10
|
+
*/
|
|
11
|
+
readonly error: Error | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* 资源的加载状态
|
|
14
|
+
*/
|
|
15
|
+
readonly isLoading: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 资源的空状态
|
|
18
|
+
*/
|
|
19
|
+
readonly isEmpty: boolean | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* 资源是否具有数值
|
|
22
|
+
*/
|
|
23
|
+
readonly hasValue: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 通过 ID 获取单个实体
|
|
27
|
+
*
|
|
28
|
+
* @param EntityType 实体类
|
|
29
|
+
* @param options 实体的 ID 或选项对象
|
|
30
|
+
* @returns 包含实体、加载状态和错误的资源对象
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const { value: user, isLoading } = useGet(User, 'user-1');
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare const useGet: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "getOptions">>) => RxDBResource<InstanceType<T> | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Find one entity matching the criteria
|
|
40
|
+
*
|
|
41
|
+
* @param EntityType The entity class
|
|
42
|
+
* @param options Query options (where clause, sort, etc.)
|
|
43
|
+
* @returns A resource object containing the entity
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const { value: user } = useFindOne(User, { where: { name: 'Alice' } });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare const useFindOne: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findOneOptions">>) => RxDBResource<InstanceType<T> | undefined>;
|
|
51
|
+
/**
|
|
52
|
+
* Find one entity or throw an error if not found
|
|
53
|
+
*
|
|
54
|
+
* @param EntityType The entity class
|
|
55
|
+
* @param options Query options
|
|
56
|
+
* @returns A resource object containing the entity
|
|
57
|
+
*/
|
|
58
|
+
export declare const useFindOneOrFail: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findOneOrFailOptions">>) => RxDBResource<InstanceType<T> | undefined>;
|
|
59
|
+
/**
|
|
60
|
+
* Find multiple entities matching the criteria
|
|
61
|
+
*
|
|
62
|
+
* @param EntityType The entity class
|
|
63
|
+
* @param options Query options
|
|
64
|
+
* @returns A resource object containing an array of entities
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const { value: users } = useFind(User, { where: { age: { $gt: 18 } } });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare const useFind: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findOptions">>) => RxDBResource<InstanceType<T>[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Find all entities
|
|
74
|
+
*
|
|
75
|
+
* @param EntityType The entity class
|
|
76
|
+
* @param options Query options
|
|
77
|
+
* @returns A resource object containing all entities
|
|
78
|
+
*/
|
|
79
|
+
export declare const useFindAll: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findAllOptions">>) => RxDBResource<InstanceType<T>[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Count entities matching the criteria
|
|
82
|
+
*
|
|
83
|
+
* @param EntityType The entity class
|
|
84
|
+
* @param options Query options
|
|
85
|
+
* @returns A resource object containing the count
|
|
86
|
+
*/
|
|
87
|
+
export declare const useCount: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "countOptions">>) => RxDBResource<number>;
|
|
88
|
+
/**
|
|
89
|
+
* Find all descendant entities in a tree structure
|
|
90
|
+
*
|
|
91
|
+
* @param EntityType The entity class
|
|
92
|
+
* @param options Tree query options (entityId, depth, etc.)
|
|
93
|
+
* @returns A resource object containing descendant entities
|
|
94
|
+
*/
|
|
95
|
+
export declare const useFindDescendants: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findTreeOptions">>) => RxDBResource<InstanceType<T>[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Count descendant entities in a tree structure
|
|
98
|
+
*
|
|
99
|
+
* @param EntityType The entity class
|
|
100
|
+
* @param options Tree query options
|
|
101
|
+
* @returns A resource object containing the count
|
|
102
|
+
*/
|
|
103
|
+
export declare const useCountDescendants: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findTreeOptions">>) => RxDBResource<number>;
|
|
104
|
+
/**
|
|
105
|
+
* Find all ancestor entities in a tree structure
|
|
106
|
+
*
|
|
107
|
+
* @param EntityType The entity class
|
|
108
|
+
* @param options Tree query options
|
|
109
|
+
* @returns A resource object containing ancestor entities
|
|
110
|
+
*/
|
|
111
|
+
export declare const useFindAncestors: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findTreeOptions">>) => RxDBResource<InstanceType<T>[]>;
|
|
112
|
+
/**
|
|
113
|
+
* Count ancestor entities in a tree structure
|
|
114
|
+
*
|
|
115
|
+
* @param EntityType The entity class
|
|
116
|
+
* @param options Tree query options
|
|
117
|
+
* @returns A resource object containing the count
|
|
118
|
+
*/
|
|
119
|
+
export declare const useCountAncestors: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findTreeOptions">>) => RxDBResource<number>;
|
|
120
|
+
/**
|
|
121
|
+
* Find neighbor entities in a graph structure
|
|
122
|
+
*
|
|
123
|
+
* @param EntityType The entity class
|
|
124
|
+
* @param options Graph query options (entityId, direction, level, etc.)
|
|
125
|
+
* @returns A resource object containing neighbor entities
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const { value: friends } = useGraphNeighbors(User, {
|
|
130
|
+
* entityId: 'user-1',
|
|
131
|
+
* direction: 'out',
|
|
132
|
+
* level: 1
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare const useGraphNeighbors: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findNeighborsOptions">>) => RxDBResource<InstanceType<T>[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Count neighbor entities in a graph structure
|
|
139
|
+
*
|
|
140
|
+
* @param EntityType The entity class
|
|
141
|
+
* @param options Graph query options
|
|
142
|
+
* @returns A resource object containing the count
|
|
143
|
+
*/
|
|
144
|
+
export declare const useCountNeighbors: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findNeighborsOptions">>) => RxDBResource<number>;
|
|
145
|
+
/**
|
|
146
|
+
* Find paths between two entities in a graph
|
|
147
|
+
*
|
|
148
|
+
* @param EntityType The entity class
|
|
149
|
+
* @param options Path query options (fromId, toId, maxDepth, etc.)
|
|
150
|
+
* @returns A resource object containing paths
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const { value: paths } = useGraphPaths(User, {
|
|
155
|
+
* fromId: 'user-1',
|
|
156
|
+
* toId: 'user-2',
|
|
157
|
+
* maxDepth: 5
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare const useGraphPaths: <T extends EntityType>(EntityType: T, options: UseOptions<EntityStaticType<T, "findPathsOptions">>) => RxDBResource<any[]>;
|
|
162
|
+
export {};
|
|
163
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isFunction as m } from "@aiao/utils";
|
|
2
|
+
import { useState as i, useRef as h, useMemo as F, useEffect as A, createContext as B, useContext as E } from "react";
|
|
3
|
+
import { jsx as O } from "react/jsx-runtime";
|
|
4
|
+
const s = (r, e, o, c) => {
|
|
5
|
+
const [D, R] = i(o), [b, d] = i(void 0), [g, f] = i(!0), [p, l] = i(void 0), [y, v] = i(!1), t = h(void 0), u = h(!0), x = F(() => m(c) ? c() : c, [c]);
|
|
6
|
+
return A(() => {
|
|
7
|
+
u.current = !0, t.current && (t.current.unsubscribe(), t.current = void 0);
|
|
8
|
+
const a = r[e];
|
|
9
|
+
if (!a || typeof a != "function") {
|
|
10
|
+
const n = new Error(`Method "${String(e)}" not found on EntityType`);
|
|
11
|
+
Promise.resolve().then(() => {
|
|
12
|
+
u.current && (d(n), f(!1));
|
|
13
|
+
});
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
t.current = a(x).subscribe({
|
|
18
|
+
next: (n) => {
|
|
19
|
+
u.current && (f(!1), v(!0), d(void 0), Array.isArray(n) ? l(n.length === 0) : l(n == null), R(n));
|
|
20
|
+
},
|
|
21
|
+
error: (n) => {
|
|
22
|
+
u.current && (f(!1), v(!1), d(n), console.error(`RxDB query error in ${String(e)}:`, n));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
} catch (n) {
|
|
26
|
+
const P = n instanceof Error ? n : new Error(String(n));
|
|
27
|
+
Promise.resolve().then(() => {
|
|
28
|
+
u.current && (f(!1), d(P));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return () => {
|
|
32
|
+
u.current = !1, t.current && (t.current.unsubscribe(), t.current = void 0);
|
|
33
|
+
};
|
|
34
|
+
}, [r, e, x]), {
|
|
35
|
+
value: D,
|
|
36
|
+
error: b,
|
|
37
|
+
isLoading: g,
|
|
38
|
+
isEmpty: p,
|
|
39
|
+
hasValue: y
|
|
40
|
+
};
|
|
41
|
+
}, S = (r, e) => s(r, "get", void 0, e), G = (r, e) => s(r, "findOne", void 0, e), V = (r, e) => s(r, "findOneOrFail", void 0, e), q = (r, e) => s(r, "find", [], e), I = (r, e) => s(r, "findAll", [], e), L = (r, e) => s(r, "count", 0, e), $ = (r, e) => s(r, "findDescendants", [], e), j = (r, e) => s(r, "countDescendants", 0, e), k = (r, e) => s(r, "findAncestors", [], e), H = (r, e) => s(r, "countAncestors", 0, e), Q = (r, e) => s(r, "findNeighbors", [], e), z = (r, e) => s(r, "countNeighbors", 0, e), J = (r, e) => s(r, "findPaths", [], e);
|
|
42
|
+
function C() {
|
|
43
|
+
const r = B(void 0);
|
|
44
|
+
return {
|
|
45
|
+
useRxDB: ((e) => {
|
|
46
|
+
const o = E(r);
|
|
47
|
+
if (e !== void 0) return e;
|
|
48
|
+
if (!o) throw new Error("No RxDB instance found, use RxDBProvider to provide one");
|
|
49
|
+
return o;
|
|
50
|
+
}),
|
|
51
|
+
RxDBProvider: ({ children: e, db: o }) => /* @__PURE__ */ O(r.Provider, { value: o, children: e })
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const { RxDBProvider: K, useRxDB: T } = C();
|
|
55
|
+
export {
|
|
56
|
+
K as RxDBProvider,
|
|
57
|
+
C as makeRxDBProvider,
|
|
58
|
+
L as useCount,
|
|
59
|
+
H as useCountAncestors,
|
|
60
|
+
j as useCountDescendants,
|
|
61
|
+
z as useCountNeighbors,
|
|
62
|
+
q as useFind,
|
|
63
|
+
I as useFindAll,
|
|
64
|
+
k as useFindAncestors,
|
|
65
|
+
$ as useFindDescendants,
|
|
66
|
+
G as useFindOne,
|
|
67
|
+
V as useFindOneOrFail,
|
|
68
|
+
S as useGet,
|
|
69
|
+
Q as useGraphNeighbors,
|
|
70
|
+
J as useGraphPaths,
|
|
71
|
+
T as useRxDB
|
|
72
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RxDB } from '../../rxdb/src/index.ts';
|
|
2
|
+
import { default as React } from 'react';
|
|
3
|
+
interface Props<T extends RxDB> {
|
|
4
|
+
children?: React.ReactNode;
|
|
5
|
+
db?: T;
|
|
6
|
+
}
|
|
7
|
+
type RxDBProviderType<T extends RxDB> = (props: Props<T>) => React.JSX.Element;
|
|
8
|
+
type UseRxDB<T extends RxDB> = (db?: T) => T;
|
|
9
|
+
interface RxDBProviderSet<T extends RxDB> {
|
|
10
|
+
RxDBProvider: RxDBProviderType<T>;
|
|
11
|
+
useRxDB: UseRxDB<T>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* RxDB 依赖注入
|
|
15
|
+
*/
|
|
16
|
+
declare function makeRxDBProvider<T extends RxDB>(): RxDBProviderSet<T>;
|
|
17
|
+
declare const RxDBProvider: RxDBProviderType<RxDB>, useRxDB: UseRxDB<RxDB>;
|
|
18
|
+
export { makeRxDBProvider, RxDBProvider, useRxDB };
|
|
19
|
+
//# sourceMappingURL=rxdb-react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rxdb-react.d.ts","sourceRoot":"","sources":["../src/rxdb-react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,KAAoC,MAAM,OAAO,CAAC;AAEzD,UAAU,KAAK,CAAC,CAAC,SAAS,IAAI;IAC5B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,EAAE,CAAC,EAAE,CAAC,CAAC;CACR;AAED,KAAK,gBAAgB,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;AAC/E,KAAK,OAAO,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;AAE7C,UAAU,eAAe,CAAC,CAAC,SAAS,IAAI;IACtC,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACrB;AAED;;GAEG;AACH,iBAAS,gBAAgB,CAAC,CAAC,SAAS,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,CAc9D;AAED,QAAA,MAAQ,YAAY,0BAAE,OAAO,eAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import nx from '@nx/eslint-plugin';
|
|
2
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
...baseConfig,
|
|
6
|
+
...nx.configs['flat/react'],
|
|
7
|
+
{
|
|
8
|
+
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
|
9
|
+
// Override or add rules here
|
|
10
|
+
rules: {}
|
|
11
|
+
}
|
|
12
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aiao/rxdb-react",
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"@aiao/source": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@aiao/rxdb": "0.0.7",
|
|
19
|
+
"@aiao/utils": "0.0.7"
|
|
20
|
+
},
|
|
21
|
+
"nx": {
|
|
22
|
+
"name": "rxdb-react",
|
|
23
|
+
"tags": [
|
|
24
|
+
"react-lib"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { useFind, useFindOne, useGet } from './hooks';
|
|
4
|
+
|
|
5
|
+
// Mock React hooks
|
|
6
|
+
let mockState: any[] = [];
|
|
7
|
+
let mockStateIndex = 0;
|
|
8
|
+
let mockEffectCleanups: Array<() => void> = [];
|
|
9
|
+
|
|
10
|
+
vi.mock('react', () => ({
|
|
11
|
+
useState: (initial: any) => {
|
|
12
|
+
const index = mockStateIndex++;
|
|
13
|
+
if (mockState[index] === undefined) {
|
|
14
|
+
mockState[index] = initial;
|
|
15
|
+
}
|
|
16
|
+
const setState = (newValue: any) => {
|
|
17
|
+
mockState[index] = typeof newValue === 'function' ? newValue(mockState[index]) : newValue;
|
|
18
|
+
};
|
|
19
|
+
return [mockState[index], setState];
|
|
20
|
+
},
|
|
21
|
+
useEffect: (effect: () => any) => {
|
|
22
|
+
const cleanup = effect();
|
|
23
|
+
if (cleanup) mockEffectCleanups.push(cleanup);
|
|
24
|
+
},
|
|
25
|
+
useRef: (initial: any) => ({ current: initial }),
|
|
26
|
+
useMemo: (fn: () => any, deps: any[]) => fn()
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Mock entity type
|
|
30
|
+
class MockEntity {
|
|
31
|
+
id!: string;
|
|
32
|
+
name!: string;
|
|
33
|
+
static get = vi.fn();
|
|
34
|
+
static findOne = vi.fn();
|
|
35
|
+
static find = vi.fn();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('useGet', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
mockState = [];
|
|
42
|
+
mockStateIndex = 0;
|
|
43
|
+
mockEffectCleanups = [];
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should initialize hook with default state', () => {
|
|
47
|
+
const mockData = { id: '1', name: 'Test' };
|
|
48
|
+
MockEntity.get.mockReturnValue(of(mockData));
|
|
49
|
+
|
|
50
|
+
const result = useGet(MockEntity as any, '1');
|
|
51
|
+
|
|
52
|
+
expect(result).toBeDefined();
|
|
53
|
+
expect(typeof result.isLoading).toBe('boolean');
|
|
54
|
+
expect(typeof result.hasValue).toBe('boolean');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should call entity method with correct options', () => {
|
|
58
|
+
MockEntity.get.mockReturnValue(of({ id: '1', name: 'Test' }));
|
|
59
|
+
|
|
60
|
+
useGet(MockEntity as any, '1');
|
|
61
|
+
|
|
62
|
+
expect(MockEntity.get).toHaveBeenCalledWith('1');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should accept function as options', () => {
|
|
66
|
+
MockEntity.get.mockReturnValue(of({ id: '2', name: 'Dynamic' }));
|
|
67
|
+
|
|
68
|
+
useGet(MockEntity as any, () => '2');
|
|
69
|
+
|
|
70
|
+
expect(MockEntity.get).toHaveBeenCalledWith('2');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('useFindOne', () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
vi.clearAllMocks();
|
|
77
|
+
mockState = [];
|
|
78
|
+
mockStateIndex = 0;
|
|
79
|
+
mockEffectCleanups = [];
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should call findOne method', () => {
|
|
83
|
+
MockEntity.findOne.mockReturnValue(of({ id: '1', name: 'Found' }));
|
|
84
|
+
|
|
85
|
+
useFindOne(MockEntity as any, { where: { name: 'Found' } });
|
|
86
|
+
|
|
87
|
+
expect(MockEntity.findOne).toHaveBeenCalledWith({ where: { name: 'Found' } });
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('useFind', () => {
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
vi.clearAllMocks();
|
|
94
|
+
mockState = [];
|
|
95
|
+
mockStateIndex = 0;
|
|
96
|
+
mockEffectCleanups = [];
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should call find method', () => {
|
|
100
|
+
const mockData = [
|
|
101
|
+
{ id: '1', name: 'User1' },
|
|
102
|
+
{ id: '2', name: 'User2' }
|
|
103
|
+
];
|
|
104
|
+
MockEntity.find.mockReturnValue(of(mockData));
|
|
105
|
+
|
|
106
|
+
useFind(MockEntity as any, {});
|
|
107
|
+
|
|
108
|
+
expect(MockEntity.find).toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('Hook cleanup', () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
vi.clearAllMocks();
|
|
115
|
+
mockState = [];
|
|
116
|
+
mockStateIndex = 0;
|
|
117
|
+
mockEffectCleanups = [];
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should register cleanup function', () => {
|
|
121
|
+
MockEntity.get.mockReturnValue(of({ id: '1' }));
|
|
122
|
+
|
|
123
|
+
useGet(MockEntity as any, '1');
|
|
124
|
+
|
|
125
|
+
expect(mockEffectCleanups.length).toBeGreaterThan(0);
|
|
126
|
+
});
|
|
127
|
+
});
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { EntityStaticType, EntityType } from '@aiao/rxdb';
|
|
2
|
+
import { isFunction } from '@aiao/utils';
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Subscription } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
type UseOptions<T> = T | (() => T);
|
|
7
|
+
|
|
8
|
+
export interface RxDBResource<T> {
|
|
9
|
+
/**
|
|
10
|
+
* 资源的数值
|
|
11
|
+
*/
|
|
12
|
+
readonly value: T;
|
|
13
|
+
/**
|
|
14
|
+
* 资源的错误
|
|
15
|
+
*/
|
|
16
|
+
readonly error: Error | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* 资源的加载状态
|
|
19
|
+
*/
|
|
20
|
+
readonly isLoading: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 资源的空状态
|
|
23
|
+
*/
|
|
24
|
+
readonly isEmpty: boolean | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* 资源是否具有数值
|
|
27
|
+
*/
|
|
28
|
+
readonly hasValue: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* RxDB 仓库查询的核心钩子实现
|
|
33
|
+
* 管理响应式订阅和状态更新
|
|
34
|
+
*/
|
|
35
|
+
const useRepositoryQuery = <T extends EntityType, RT>(
|
|
36
|
+
EntityType: T,
|
|
37
|
+
method: string,
|
|
38
|
+
defaultValue: RT,
|
|
39
|
+
options: UseOptions<any>
|
|
40
|
+
): RxDBResource<RT> => {
|
|
41
|
+
// 状态管理
|
|
42
|
+
const [value, setValue] = useState<RT>(defaultValue);
|
|
43
|
+
const [error, setError] = useState<Error | undefined>(undefined);
|
|
44
|
+
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
45
|
+
const [isEmpty, setIsEmpty] = useState<boolean | undefined>(undefined);
|
|
46
|
+
const [hasValue, setHasValue] = useState<boolean>(false);
|
|
47
|
+
|
|
48
|
+
// 订阅管理
|
|
49
|
+
const subscriptionRef = useRef<Subscription | undefined>(undefined);
|
|
50
|
+
const isMountedRef = useRef(true);
|
|
51
|
+
|
|
52
|
+
const resolvedOptions = useMemo(() => {
|
|
53
|
+
return isFunction(options) ? options() : options;
|
|
54
|
+
}, [options]);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
isMountedRef.current = true;
|
|
58
|
+
|
|
59
|
+
// 清理之前的订阅
|
|
60
|
+
if (subscriptionRef.current) {
|
|
61
|
+
subscriptionRef.current.unsubscribe();
|
|
62
|
+
subscriptionRef.current = undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 从 EntityType 获取方法
|
|
66
|
+
// 优化:使用 Record<string, any> 替代 any,以提高类型安全性
|
|
67
|
+
const queryMethod = (EntityType as unknown as Record<string, any>)[method];
|
|
68
|
+
|
|
69
|
+
if (!queryMethod || typeof queryMethod !== 'function') {
|
|
70
|
+
const err = new Error(`Method "${String(method)}" not found on EntityType`);
|
|
71
|
+
Promise.resolve().then(() => {
|
|
72
|
+
if (isMountedRef.current) {
|
|
73
|
+
setError(err);
|
|
74
|
+
setIsLoading(false);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 执行查询并订阅结果
|
|
81
|
+
try {
|
|
82
|
+
subscriptionRef.current = queryMethod(resolvedOptions).subscribe({
|
|
83
|
+
next: (data: RT) => {
|
|
84
|
+
if (!isMountedRef.current) return;
|
|
85
|
+
|
|
86
|
+
setIsLoading(false);
|
|
87
|
+
setHasValue(true);
|
|
88
|
+
setError(undefined);
|
|
89
|
+
|
|
90
|
+
// 为数组更新空状态
|
|
91
|
+
if (Array.isArray(data)) {
|
|
92
|
+
setIsEmpty(data.length === 0);
|
|
93
|
+
} else {
|
|
94
|
+
setIsEmpty(data == null);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setValue(data);
|
|
98
|
+
},
|
|
99
|
+
error: (err: Error) => {
|
|
100
|
+
if (!isMountedRef.current) return;
|
|
101
|
+
|
|
102
|
+
setIsLoading(false);
|
|
103
|
+
setHasValue(false);
|
|
104
|
+
setError(err);
|
|
105
|
+
console.error(`RxDB query error in ${String(method)}:`, err);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// 延迟状态更新以避免在 effect 中同步 setState
|
|
110
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
111
|
+
Promise.resolve().then(() => {
|
|
112
|
+
if (isMountedRef.current) {
|
|
113
|
+
setIsLoading(false);
|
|
114
|
+
setError(error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 清理函数
|
|
120
|
+
return () => {
|
|
121
|
+
isMountedRef.current = false;
|
|
122
|
+
if (subscriptionRef.current) {
|
|
123
|
+
subscriptionRef.current.unsubscribe();
|
|
124
|
+
subscriptionRef.current = undefined;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}, [EntityType, method, resolvedOptions]);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
value,
|
|
131
|
+
error,
|
|
132
|
+
isLoading,
|
|
133
|
+
isEmpty,
|
|
134
|
+
hasValue
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* 仓库钩子
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 通过 ID 获取单个实体
|
|
144
|
+
*
|
|
145
|
+
* @param EntityType 实体类
|
|
146
|
+
* @param options 实体的 ID 或选项对象
|
|
147
|
+
* @returns 包含实体、加载状态和错误的资源对象
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const { value: user, isLoading } = useGet(User, 'user-1');
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export const useGet = <T extends EntityType>(
|
|
155
|
+
EntityType: T,
|
|
156
|
+
options: UseOptions<EntityStaticType<T, 'getOptions'>>
|
|
157
|
+
): RxDBResource<InstanceType<T> | undefined> =>
|
|
158
|
+
useRepositoryQuery<T, InstanceType<T> | undefined>(EntityType, 'get', undefined, options);
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Find one entity matching the criteria
|
|
162
|
+
*
|
|
163
|
+
* @param EntityType The entity class
|
|
164
|
+
* @param options Query options (where clause, sort, etc.)
|
|
165
|
+
* @returns A resource object containing the entity
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* const { value: user } = useFindOne(User, { where: { name: 'Alice' } });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export const useFindOne = <T extends EntityType>(
|
|
173
|
+
EntityType: T,
|
|
174
|
+
options: UseOptions<EntityStaticType<T, 'findOneOptions'>>
|
|
175
|
+
): RxDBResource<InstanceType<T> | undefined> =>
|
|
176
|
+
useRepositoryQuery<T, InstanceType<T> | undefined>(EntityType, 'findOne', undefined, options);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Find one entity or throw an error if not found
|
|
180
|
+
*
|
|
181
|
+
* @param EntityType The entity class
|
|
182
|
+
* @param options Query options
|
|
183
|
+
* @returns A resource object containing the entity
|
|
184
|
+
*/
|
|
185
|
+
export const useFindOneOrFail = <T extends EntityType>(
|
|
186
|
+
EntityType: T,
|
|
187
|
+
options: UseOptions<EntityStaticType<T, 'findOneOrFailOptions'>>
|
|
188
|
+
): RxDBResource<InstanceType<T> | undefined> =>
|
|
189
|
+
useRepositoryQuery<T, InstanceType<T> | undefined>(EntityType, 'findOneOrFail', undefined, options);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Find multiple entities matching the criteria
|
|
193
|
+
*
|
|
194
|
+
* @param EntityType The entity class
|
|
195
|
+
* @param options Query options
|
|
196
|
+
* @returns A resource object containing an array of entities
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* const { value: users } = useFind(User, { where: { age: { $gt: 18 } } });
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export const useFind = <T extends EntityType>(
|
|
204
|
+
EntityType: T,
|
|
205
|
+
options: UseOptions<EntityStaticType<T, 'findOptions'>>
|
|
206
|
+
): RxDBResource<InstanceType<T>[]> => useRepositoryQuery<T, InstanceType<T>[]>(EntityType, 'find', [], options);
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Find all entities
|
|
210
|
+
*
|
|
211
|
+
* @param EntityType The entity class
|
|
212
|
+
* @param options Query options
|
|
213
|
+
* @returns A resource object containing all entities
|
|
214
|
+
*/
|
|
215
|
+
export const useFindAll = <T extends EntityType>(
|
|
216
|
+
EntityType: T,
|
|
217
|
+
options: UseOptions<EntityStaticType<T, 'findAllOptions'>>
|
|
218
|
+
): RxDBResource<InstanceType<T>[]> => useRepositoryQuery<T, InstanceType<T>[]>(EntityType, 'findAll', [], options);
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Count entities matching the criteria
|
|
222
|
+
*
|
|
223
|
+
* @param EntityType The entity class
|
|
224
|
+
* @param options Query options
|
|
225
|
+
* @returns A resource object containing the count
|
|
226
|
+
*/
|
|
227
|
+
export const useCount = <T extends EntityType>(
|
|
228
|
+
EntityType: T,
|
|
229
|
+
options: UseOptions<EntityStaticType<T, 'countOptions'>>
|
|
230
|
+
): RxDBResource<number> => useRepositoryQuery<T, number>(EntityType, 'count', 0, options);
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
* Tree Repository Hooks
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Find all descendant entities in a tree structure
|
|
238
|
+
*
|
|
239
|
+
* @param EntityType The entity class
|
|
240
|
+
* @param options Tree query options (entityId, depth, etc.)
|
|
241
|
+
* @returns A resource object containing descendant entities
|
|
242
|
+
*/
|
|
243
|
+
export const useFindDescendants = <T extends EntityType>(
|
|
244
|
+
EntityType: T,
|
|
245
|
+
options: UseOptions<EntityStaticType<T, 'findTreeOptions'>>
|
|
246
|
+
): RxDBResource<InstanceType<T>[]> =>
|
|
247
|
+
useRepositoryQuery<T, InstanceType<T>[]>(EntityType, 'findDescendants', [], options);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Count descendant entities in a tree structure
|
|
251
|
+
*
|
|
252
|
+
* @param EntityType The entity class
|
|
253
|
+
* @param options Tree query options
|
|
254
|
+
* @returns A resource object containing the count
|
|
255
|
+
*/
|
|
256
|
+
export const useCountDescendants = <T extends EntityType>(
|
|
257
|
+
EntityType: T,
|
|
258
|
+
options: UseOptions<EntityStaticType<T, 'findTreeOptions'>>
|
|
259
|
+
): RxDBResource<number> => useRepositoryQuery<T, number>(EntityType, 'countDescendants', 0, options);
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Find all ancestor entities in a tree structure
|
|
263
|
+
*
|
|
264
|
+
* @param EntityType The entity class
|
|
265
|
+
* @param options Tree query options
|
|
266
|
+
* @returns A resource object containing ancestor entities
|
|
267
|
+
*/
|
|
268
|
+
export const useFindAncestors = <T extends EntityType>(
|
|
269
|
+
EntityType: T,
|
|
270
|
+
options: UseOptions<EntityStaticType<T, 'findTreeOptions'>>
|
|
271
|
+
): RxDBResource<InstanceType<T>[]> =>
|
|
272
|
+
useRepositoryQuery<T, InstanceType<T>[]>(EntityType, 'findAncestors', [], options);
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Count ancestor entities in a tree structure
|
|
276
|
+
*
|
|
277
|
+
* @param EntityType The entity class
|
|
278
|
+
* @param options Tree query options
|
|
279
|
+
* @returns A resource object containing the count
|
|
280
|
+
*/
|
|
281
|
+
export const useCountAncestors = <T extends EntityType>(
|
|
282
|
+
EntityType: T,
|
|
283
|
+
options: UseOptions<EntityStaticType<T, 'findTreeOptions'>>
|
|
284
|
+
): RxDBResource<number> => useRepositoryQuery<T, number>(EntityType, 'countAncestors', 0, options);
|
|
285
|
+
|
|
286
|
+
/*
|
|
287
|
+
* Graph Repository Hooks
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Find neighbor entities in a graph structure
|
|
292
|
+
*
|
|
293
|
+
* @param EntityType The entity class
|
|
294
|
+
* @param options Graph query options (entityId, direction, level, etc.)
|
|
295
|
+
* @returns A resource object containing neighbor entities
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```typescript
|
|
299
|
+
* const { value: friends } = useGraphNeighbors(User, {
|
|
300
|
+
* entityId: 'user-1',
|
|
301
|
+
* direction: 'out',
|
|
302
|
+
* level: 1
|
|
303
|
+
* });
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
export const useGraphNeighbors = <T extends EntityType>(
|
|
307
|
+
EntityType: T,
|
|
308
|
+
options: UseOptions<EntityStaticType<T, 'findNeighborsOptions'>>
|
|
309
|
+
): RxDBResource<InstanceType<T>[]> =>
|
|
310
|
+
useRepositoryQuery<T, InstanceType<T>[]>(EntityType, 'findNeighbors', [], options);
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Count neighbor entities in a graph structure
|
|
314
|
+
*
|
|
315
|
+
* @param EntityType The entity class
|
|
316
|
+
* @param options Graph query options
|
|
317
|
+
* @returns A resource object containing the count
|
|
318
|
+
*/
|
|
319
|
+
export const useCountNeighbors = <T extends EntityType>(
|
|
320
|
+
EntityType: T,
|
|
321
|
+
options: UseOptions<EntityStaticType<T, 'findNeighborsOptions'>>
|
|
322
|
+
): RxDBResource<number> => useRepositoryQuery<T, number>(EntityType, 'countNeighbors', 0, options);
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Find paths between two entities in a graph
|
|
326
|
+
*
|
|
327
|
+
* @param EntityType The entity class
|
|
328
|
+
* @param options Path query options (fromId, toId, maxDepth, etc.)
|
|
329
|
+
* @returns A resource object containing paths
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```typescript
|
|
333
|
+
* const { value: paths } = useGraphPaths(User, {
|
|
334
|
+
* fromId: 'user-1',
|
|
335
|
+
* toId: 'user-2',
|
|
336
|
+
* maxDepth: 5
|
|
337
|
+
* });
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
export const useGraphPaths = <T extends EntityType>(
|
|
341
|
+
EntityType: T,
|
|
342
|
+
options: UseOptions<EntityStaticType<T, 'findPathsOptions'>>
|
|
343
|
+
): RxDBResource<any[]> => useRepositoryQuery<T, any[]>(EntityType, 'findPaths', [], options);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { makeRxDBProvider, RxDBProvider, useRxDB } from './rxdb-react';
|
|
4
|
+
|
|
5
|
+
describe('makeRxDBProvider', () => {
|
|
6
|
+
it('should create RxDBProvider and useRxDB hook', () => {
|
|
7
|
+
const { RxDBProvider, useRxDB } = makeRxDBProvider();
|
|
8
|
+
|
|
9
|
+
expect(RxDBProvider).toBeDefined();
|
|
10
|
+
expect(useRxDB).toBeDefined();
|
|
11
|
+
expect(typeof RxDBProvider).toBe('function');
|
|
12
|
+
expect(typeof useRxDB).toBe('function');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should create context provider component', () => {
|
|
16
|
+
const mockDB = { name: 'test-db' } as any;
|
|
17
|
+
const { RxDBProvider } = makeRxDBProvider();
|
|
18
|
+
|
|
19
|
+
const element = React.createElement(RxDBProvider, { db: mockDB }, 'children');
|
|
20
|
+
|
|
21
|
+
expect(element).toBeDefined();
|
|
22
|
+
expect(element.type).toBe(RxDBProvider);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Default exports', () => {
|
|
27
|
+
it('should export default RxDBProvider', () => {
|
|
28
|
+
expect(RxDBProvider).toBeDefined();
|
|
29
|
+
expect(typeof RxDBProvider).toBe('function');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should export default useRxDB', () => {
|
|
33
|
+
expect(useRxDB).toBeDefined();
|
|
34
|
+
expect(typeof useRxDB).toBe('function');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should export makeRxDBProvider function', () => {
|
|
38
|
+
expect(makeRxDBProvider).toBeDefined();
|
|
39
|
+
expect(typeof makeRxDBProvider).toBe('function');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RxDB } from '@aiao/rxdb';
|
|
2
|
+
import React, { createContext, useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
interface Props<T extends RxDB> {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
db?: T;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type RxDBProviderType<T extends RxDB> = (props: Props<T>) => React.JSX.Element;
|
|
10
|
+
type UseRxDB<T extends RxDB> = (db?: T) => T;
|
|
11
|
+
|
|
12
|
+
interface RxDBProviderSet<T extends RxDB> {
|
|
13
|
+
RxDBProvider: RxDBProviderType<T>;
|
|
14
|
+
useRxDB: UseRxDB<T>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* RxDB 依赖注入
|
|
19
|
+
*/
|
|
20
|
+
function makeRxDBProvider<T extends RxDB>(): RxDBProviderSet<T> {
|
|
21
|
+
const ctx = createContext<T | undefined>(undefined);
|
|
22
|
+
return {
|
|
23
|
+
useRxDB: ((db?: T) => {
|
|
24
|
+
const dbProvided = useContext(ctx);
|
|
25
|
+
if (db !== undefined) return db;
|
|
26
|
+
if (!dbProvided) throw new Error('No RxDB instance found, use RxDBProvider to provide one');
|
|
27
|
+
|
|
28
|
+
return dbProvided;
|
|
29
|
+
}) as UseRxDB<T>,
|
|
30
|
+
RxDBProvider: ({ children, db }: Props<T>) => {
|
|
31
|
+
return <ctx.Provider value={db}>{children}</ctx.Provider>;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { RxDBProvider, useRxDB } = makeRxDBProvider<RxDB>();
|
|
37
|
+
|
|
38
|
+
export { makeRxDBProvider, RxDBProvider, useRxDB };
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "dist",
|
|
4
|
+
"types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts", "vite/client"],
|
|
5
|
+
"rootDir": "src",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"module": "esnext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
|
|
10
|
+
},
|
|
11
|
+
"exclude": [
|
|
12
|
+
"out-tsc",
|
|
13
|
+
"dist",
|
|
14
|
+
"**/*.spec.ts",
|
|
15
|
+
"**/*.test.ts",
|
|
16
|
+
"**/*.spec.tsx",
|
|
17
|
+
"**/*.test.tsx",
|
|
18
|
+
"**/*.spec.js",
|
|
19
|
+
"**/*.test.js",
|
|
20
|
+
"**/*.spec.jsx",
|
|
21
|
+
"**/*.test.jsx",
|
|
22
|
+
"vite.config.ts",
|
|
23
|
+
"vite.config.mts",
|
|
24
|
+
"vitest.config.ts",
|
|
25
|
+
"vitest.config.mts",
|
|
26
|
+
"src/**/*.test.ts",
|
|
27
|
+
"src/**/*.spec.ts",
|
|
28
|
+
"src/**/*.test.tsx",
|
|
29
|
+
"src/**/*.spec.tsx",
|
|
30
|
+
"src/**/*.test.js",
|
|
31
|
+
"src/**/*.spec.js",
|
|
32
|
+
"src/**/*.test.jsx",
|
|
33
|
+
"src/**/*.spec.jsx",
|
|
34
|
+
"eslint.config.js",
|
|
35
|
+
"eslint.config.cjs",
|
|
36
|
+
"eslint.config.mjs"
|
|
37
|
+
],
|
|
38
|
+
"extends": "../../tsconfig.base.json",
|
|
39
|
+
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"],
|
|
40
|
+
"references": [
|
|
41
|
+
{
|
|
42
|
+
"path": "../utils/tsconfig.lib.json"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "../rxdb/tsconfig.lib.json"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "./out-tsc/vitest",
|
|
4
|
+
"types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"],
|
|
5
|
+
"jsx": "react-jsx",
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"moduleResolution": "bundler"
|
|
8
|
+
},
|
|
9
|
+
"extends": "../../tsconfig.base.json",
|
|
10
|
+
"include": [
|
|
11
|
+
"vite.config.ts",
|
|
12
|
+
"vite.config.mts",
|
|
13
|
+
"vitest.config.ts",
|
|
14
|
+
"vitest.config.mts",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"src/**/*.spec.ts",
|
|
17
|
+
"src/**/*.test.tsx",
|
|
18
|
+
"src/**/*.spec.tsx",
|
|
19
|
+
"src/**/*.test.js",
|
|
20
|
+
"src/**/*.spec.js",
|
|
21
|
+
"src/**/*.test.jsx",
|
|
22
|
+
"src/**/*.spec.jsx",
|
|
23
|
+
"src/**/*.d.ts"
|
|
24
|
+
],
|
|
25
|
+
"references": [
|
|
26
|
+
{
|
|
27
|
+
"path": "./tsconfig.lib.json"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/// <reference types='vitest' />
|
|
2
|
+
import { codecovVitePlugin } from '@codecov/vite-plugin';
|
|
3
|
+
import react from '@vitejs/plugin-react';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import dts from 'vite-plugin-dts';
|
|
6
|
+
import { defineConfig } from 'vitest/config';
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
root: __dirname,
|
|
10
|
+
cacheDir: '../../node_modules/.vite/packages/rxdb-react',
|
|
11
|
+
plugins: [
|
|
12
|
+
react(),
|
|
13
|
+
dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') }),
|
|
14
|
+
// Codecov Bundle Analysis - 仅在 CI 环境中启用
|
|
15
|
+
// TECH DEBT: @codecov/vite-plugin@1.9.1 不支持 Vite 7.x (仅支持 4/5/6)
|
|
16
|
+
// 临时使用类型断言绕过编译错误,功能运行时正常
|
|
17
|
+
// TODO: 等待上游支持 Vite 7 后移除 as any
|
|
18
|
+
...(process.env.CI === 'true' && process.env.CODECOV_TOKEN ?
|
|
19
|
+
[
|
|
20
|
+
codecovVitePlugin({
|
|
21
|
+
enableBundleAnalysis: true,
|
|
22
|
+
telemetry: false,
|
|
23
|
+
bundleName: 'rxdb-react',
|
|
24
|
+
uploadToken: process.env.CODECOV_TOKEN
|
|
25
|
+
}) as any
|
|
26
|
+
]
|
|
27
|
+
: [])
|
|
28
|
+
],
|
|
29
|
+
// Uncomment this if you are using workers.
|
|
30
|
+
// worker: {
|
|
31
|
+
// plugins: [ nxViteTsPaths() ],
|
|
32
|
+
// },
|
|
33
|
+
// Configuration for building your library.
|
|
34
|
+
// See: https://vitejs.dev/guide/build.html#library-mode
|
|
35
|
+
build: {
|
|
36
|
+
outDir: './dist',
|
|
37
|
+
emptyOutDir: true,
|
|
38
|
+
reportCompressedSize: true,
|
|
39
|
+
commonjsOptions: {
|
|
40
|
+
transformMixedEsModules: true
|
|
41
|
+
},
|
|
42
|
+
lib: {
|
|
43
|
+
// Could also be a dictionary or array of multiple entry points.
|
|
44
|
+
entry: 'src/index.ts',
|
|
45
|
+
name: 'rxdb-react',
|
|
46
|
+
fileName: 'index',
|
|
47
|
+
// Change this to the formats you want to support.
|
|
48
|
+
// Don't forget to update your package.json as well.
|
|
49
|
+
formats: ['es' as const]
|
|
50
|
+
},
|
|
51
|
+
rollupOptions: {
|
|
52
|
+
// External packages that should not be bundled into your library.
|
|
53
|
+
external: ['react', 'react-dom', 'react/jsx-runtime', '@aiao/rxdb', '@aiao/utils', 'rxjs']
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
optimizeDeps: {
|
|
57
|
+
exclude: ['rxjs', 'ms', 'fastest-levenshtein', 'ts-xor', '@aiao/rxdb', '@aiao/utils']
|
|
58
|
+
},
|
|
59
|
+
test: {
|
|
60
|
+
name: 'rxdb-react',
|
|
61
|
+
watch: false,
|
|
62
|
+
globals: true,
|
|
63
|
+
environment: 'happy-dom',
|
|
64
|
+
passWithNoTests: true,
|
|
65
|
+
testTimeout: 2000,
|
|
66
|
+
hookTimeout: 2000,
|
|
67
|
+
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
68
|
+
reporters: ['default', 'junit'],
|
|
69
|
+
outputFile: {
|
|
70
|
+
junit: '../../coverage/packages/rxdb-react/junit.xml'
|
|
71
|
+
},
|
|
72
|
+
coverage: {
|
|
73
|
+
enabled: true,
|
|
74
|
+
reportsDirectory: '../../coverage/packages/rxdb-react',
|
|
75
|
+
provider: 'v8',
|
|
76
|
+
include: ['src/**/*'],
|
|
77
|
+
exclude: ['**/index.ts', '**/dist/**']
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|