@wxn0brp/db 0.6.0 → 0.7.1
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/dist/relation.d.ts +2 -2
- package/dist/relation.js +95 -59
- package/dist/types/relation.d.ts +6 -0
- package/package.json +1 -1
package/dist/relation.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { RelationTypes } from "./types/relation.js";
|
|
|
4
4
|
declare class Relation {
|
|
5
5
|
dbs: RelationTypes.DBS;
|
|
6
6
|
constructor(dbs: RelationTypes.DBS);
|
|
7
|
-
findOne(path: RelationTypes.Path, search: Search, relations: RelationTypes.Relation, select?:
|
|
8
|
-
find(path: RelationTypes.Path, search: Search, relations: RelationTypes.Relation, select?:
|
|
7
|
+
findOne(path: RelationTypes.Path, search: Search, relations: RelationTypes.Relation, select?: string[][] | Record<string, any>): Promise<any>;
|
|
8
|
+
find(path: RelationTypes.Path, search: Search, relations: RelationTypes.Relation, select?: string[][] | Record<string, any>, findOpts?: DbFindOpts): Promise<any[]>;
|
|
9
9
|
}
|
|
10
10
|
export default Relation;
|
package/dist/relation.js
CHANGED
|
@@ -1,85 +1,121 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
function pickByPath(obj, paths) {
|
|
2
|
+
const result = {};
|
|
3
|
+
for (const path of paths) {
|
|
4
|
+
let src = obj;
|
|
5
|
+
let dst = result;
|
|
6
|
+
for (let i = 0; i < path.length; i++) {
|
|
7
|
+
const k = path[i];
|
|
8
|
+
if (src == null)
|
|
9
|
+
break;
|
|
10
|
+
if (i === path.length - 1) {
|
|
11
|
+
dst[k] = src[k];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
dst[k] ||= {};
|
|
15
|
+
dst = dst[k];
|
|
16
|
+
src = src[k];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function convertSearchObjToSearchArray(obj, parentKeys = []) {
|
|
23
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
24
|
+
const currentPath = [...parentKeys, key];
|
|
25
|
+
if (!value) {
|
|
26
|
+
return acc;
|
|
27
|
+
}
|
|
28
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
29
|
+
return [...acc, ...convertSearchObjToSearchArray(value, currentPath)];
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return [...acc, currentPath];
|
|
33
|
+
}
|
|
34
|
+
}, []);
|
|
35
|
+
}
|
|
36
|
+
async function processRelations(dbs, cfg, data, parentList = null) {
|
|
37
|
+
if (!data && !parentList)
|
|
3
38
|
return;
|
|
4
|
-
|
|
5
|
-
|
|
39
|
+
const batchMode = Array.isArray(parentList);
|
|
40
|
+
const targets = batchMode ? parentList : [data];
|
|
41
|
+
for (const key in cfg) {
|
|
42
|
+
if (!cfg.hasOwnProperty(key))
|
|
43
|
+
continue;
|
|
44
|
+
const rel = cfg[key];
|
|
45
|
+
const { pk = "_id", fk = "_id", type = "1", path, as = key, select, findOpts, through } = rel;
|
|
46
|
+
const [dbKey, coll] = path;
|
|
47
|
+
const db = dbs[dbKey];
|
|
6
48
|
if (type === "1") {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
if (relation.relations) {
|
|
16
|
-
await processRelations(dbs, relation.relations, item);
|
|
49
|
+
for (const item of targets) {
|
|
50
|
+
const result = await db.findOne(coll, { [fk]: item[pk] }, { projection: select });
|
|
51
|
+
if (result && rel.relations) {
|
|
52
|
+
await processRelations(dbs, rel.relations, result);
|
|
53
|
+
}
|
|
54
|
+
item[as] = result || null;
|
|
17
55
|
}
|
|
18
|
-
data[field] = item;
|
|
19
56
|
}
|
|
20
57
|
else if (type === "1n") {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
58
|
+
const ids = targets.map(i => i[pk]);
|
|
59
|
+
const results = await db.find(coll, { $in: { [fk]: ids } }, { ...findOpts, projection: select });
|
|
60
|
+
const grouped = results.reduce((acc, row) => {
|
|
61
|
+
const id = row[fk];
|
|
62
|
+
(acc[id] ||= []).push(row);
|
|
63
|
+
return acc;
|
|
64
|
+
}, {});
|
|
65
|
+
for (const item of targets) {
|
|
66
|
+
item[as] = grouped[item[pk]] || [];
|
|
67
|
+
}
|
|
68
|
+
if (rel.relations) {
|
|
69
|
+
await Promise.all(results.map(row => processRelations(dbs, rel.relations, row)));
|
|
27
70
|
}
|
|
28
|
-
data[field] = items;
|
|
29
71
|
}
|
|
30
72
|
else if (type === "nm") {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
await
|
|
73
|
+
if (!through || !through.table || !through.pk || !through.fk) {
|
|
74
|
+
throw new Error(`Relation type "nm" requires a defined 'through' in '${key}'`);
|
|
75
|
+
}
|
|
76
|
+
for (const item of targets) {
|
|
77
|
+
const pivotDb = dbs[through.db || dbKey];
|
|
78
|
+
const pivots = await pivotDb.find(through.table, { [through.pk]: item[pk] });
|
|
79
|
+
const ids = pivots.map(p => p[through.fk]);
|
|
80
|
+
const related = await db.find(coll, { [fk]: { $in: ids } }, { projection: select });
|
|
81
|
+
item[as] = related;
|
|
82
|
+
if (rel.relations) {
|
|
83
|
+
await Promise.all(related.map(row => processRelations(dbs, rel.relations, row)));
|
|
84
|
+
}
|
|
37
85
|
}
|
|
38
|
-
data[field] = items;
|
|
39
86
|
}
|
|
40
87
|
else {
|
|
41
|
-
throw new Error(`Unknown relation type: ${
|
|
88
|
+
throw new Error(`Unknown relation type: ${type}`);
|
|
42
89
|
}
|
|
43
90
|
}
|
|
44
91
|
}
|
|
45
|
-
function selectDataSelf(data, select) {
|
|
46
|
-
if (!data)
|
|
47
|
-
return null;
|
|
48
|
-
if (!select || select.length === 0)
|
|
49
|
-
return data;
|
|
50
|
-
if (Array.isArray(data))
|
|
51
|
-
return data.map(item => selectDataSelf(item, select));
|
|
52
|
-
return selectDataSelf(data[select[0]], select.slice(1));
|
|
53
|
-
}
|
|
54
|
-
function selectData(data, select) {
|
|
55
|
-
if (!select)
|
|
56
|
-
return data;
|
|
57
|
-
if (!data && select.length === 0)
|
|
58
|
-
return null;
|
|
59
|
-
const newData = {};
|
|
60
|
-
for (const field of select) {
|
|
61
|
-
const key = field.map(f => f.replaceAll(".", "\\.")).join(".");
|
|
62
|
-
newData[key] = selectDataSelf(data, field);
|
|
63
|
-
}
|
|
64
|
-
return newData;
|
|
65
|
-
}
|
|
66
92
|
class Relation {
|
|
67
93
|
dbs;
|
|
68
94
|
constructor(dbs) {
|
|
69
95
|
this.dbs = dbs;
|
|
70
96
|
}
|
|
71
97
|
async findOne(path, search, relations, select) {
|
|
72
|
-
const
|
|
73
|
-
const
|
|
98
|
+
const [dbKey, coll] = path;
|
|
99
|
+
const db = this.dbs[dbKey];
|
|
100
|
+
const data = await db.findOne(coll, search);
|
|
101
|
+
if (!data)
|
|
102
|
+
return null;
|
|
103
|
+
if (typeof select === "object" && !Array.isArray(select)) {
|
|
104
|
+
select = convertSearchObjToSearchArray(select);
|
|
105
|
+
}
|
|
74
106
|
await processRelations(this.dbs, relations, data);
|
|
75
|
-
|
|
76
|
-
return Object.keys(result).length === 0 ? null : result;
|
|
107
|
+
return select ? pickByPath(data, select) : data;
|
|
77
108
|
}
|
|
78
109
|
async find(path, search, relations, select, findOpts = {}) {
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
110
|
+
const [dbKey, coll] = path;
|
|
111
|
+
const db = this.dbs[dbKey];
|
|
112
|
+
const data = await db.find(coll, search, findOpts);
|
|
113
|
+
if (relations)
|
|
114
|
+
await processRelations(this.dbs, relations, null, data);
|
|
115
|
+
if (typeof select === "object" && !Array.isArray(select)) {
|
|
116
|
+
select = convertSearchObjToSearchArray(select);
|
|
117
|
+
}
|
|
118
|
+
return select ? data.map(d => pickByPath(d, select)) : data;
|
|
83
119
|
}
|
|
84
120
|
}
|
|
85
121
|
export default Relation;
|
package/dist/types/relation.d.ts
CHANGED
package/package.json
CHANGED