@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.
@@ -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?: RelationTypes.FieldPath[]): Promise<any>;
8
- find(path: RelationTypes.Path, search: Search, relations: RelationTypes.Relation, select?: RelationTypes.FieldPath[], findOpts?: DbFindOpts): Promise<any[]>;
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
- async function processRelations(dbs, cfg, data) {
2
- if (!data)
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
- for (const [key, relation] of Object.entries(cfg)) {
5
- const { pk = "_id", fk = "_id", type = "1" } = relation;
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 db = dbs[relation.path[0]];
8
- const collection = relation.path[1];
9
- const item = await db.findOne(collection, { [fk]: data[pk] }, {}, { select: relation.select || null });
10
- const field = relation.as || key;
11
- if (!item) {
12
- data[field] = null;
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 db = dbs[relation.path[0]];
22
- const collection = relation.path[1];
23
- const items = await db.find(collection, { [fk]: data[pk] }, {}, relation.findOpts || {}, { select: relation.select || null });
24
- const field = relation.as || key;
25
- if (relation.relations) {
26
- await Promise.all(items.map(item => processRelations(dbs, relation.relations, item)));
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
- const db = dbs[relation.path[0]];
32
- const collection = relation.path[1];
33
- const items = await db.find(collection, {}, {}, {}, { select: relation.select || null });
34
- const field = relation.as || key;
35
- if (relation.relations) {
36
- await Promise.all(items.map(item => processRelations(dbs, relation.relations, item)));
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: ${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 db = this.dbs[path[0]];
73
- const data = await db.findOne(path[1], search);
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
- const result = selectData(data, select);
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 db = this.dbs[path[0]];
80
- const data = await db.find(path[1], search, {}, findOpts);
81
- await Promise.all(data.map(item => processRelations(this.dbs, relations, item)));
82
- return data.map(item => selectData(item, select));
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;
@@ -18,5 +18,11 @@ export declare namespace RelationTypes {
18
18
  findOpts?: DbFindOpts;
19
19
  type?: "1" | "1n" | "nm";
20
20
  relations?: Relation;
21
+ through?: {
22
+ table: string;
23
+ db?: string;
24
+ pk: string;
25
+ fk: string;
26
+ };
21
27
  }
22
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/db",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "description": "A simple file-based database management system with support for CRUD operations, custom queries, and graph structures.",