beanbagdb 0.5.54 → 0.5.61
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/doc-src/1_getting-started.md +64 -1
- package/package.json +1 -1
- package/src/index.js +144 -3
- package/src/system_schema.js +117 -45
- package/test/edges.test.js +313 -0
- package/test/operations.test.js +2 -2
- package/test/plugin.test.js +4 -7
|
@@ -1 +1,64 @@
|
|
|
1
|
-
# Getting started
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Nodes and edges
|
|
6
|
+
|
|
7
|
+
The records stored in BeanBagDB can be organized into a simple directed graph. In in simple directed graph, edges have a direction and there can only be one edge between 2 nodes. Edges can have label based on the context of your data. You can also define rules for these edges based on the schema of the nodes.
|
|
8
|
+
|
|
9
|
+
Example : Consider you have 3 types of nodes : "player", "team", "match" to simulate a game where a teams has multiple players and a player plays a match as part of a team.
|
|
10
|
+
Here are the rules:
|
|
11
|
+
- A player is part of a team (and not vice versa)
|
|
12
|
+
- A team plays a match (and not vice versa)
|
|
13
|
+
- A player cannot be in 2 teams
|
|
14
|
+
- A match can only be between 2 teams (not more than 2)
|
|
15
|
+
- Player does not play a match (but a team does, assuming this is a team sport)
|
|
16
|
+
- A team cannot be part of a team
|
|
17
|
+
- A team cannot have more than 11 players
|
|
18
|
+
|
|
19
|
+
In terms of nodes and edges, the rules translate to:
|
|
20
|
+
|
|
21
|
+
| rule | node 1 | edge label | node 2 | constraint |
|
|
22
|
+
|-------------------------------------------------------|--------|-------------|--------|---------------------------------|
|
|
23
|
+
| 1 A player is part of a team (and not vice versa) | player | is part of | team | player-->team |
|
|
24
|
+
| 2 A team plays a match (and not vice versa) | team | plays | match | |
|
|
25
|
+
| 3 A player cannot be in 2 teams | player | is part of | team | only one such relation |
|
|
26
|
+
| 4 A match can only be between 2 teams (not more than 2) | team | plays | match | only 2 such relation per match |
|
|
27
|
+
| 5 Player does not play a match | player | plays | match | not valid |
|
|
28
|
+
| 6 A team cannot be part of a team | team | is part of | team | not valid |
|
|
29
|
+
| 7 A team cannot have more than 11 players | player | is part of | team | only 11 such relations per team |
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
These rules can be enforced in the database as "edge_constraints" and are automatically checked when new edges are added to the graph.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
- Constraint 1 :
|
|
36
|
+
- node 1 : player
|
|
37
|
+
- edge type : part_of
|
|
38
|
+
- edge label : is part of a
|
|
39
|
+
- node 2 : team
|
|
40
|
+
- max_from_node1 : 1
|
|
41
|
+
- max_to_node2 : 11
|
|
42
|
+
- Constraint 2 :
|
|
43
|
+
- node 1 : team
|
|
44
|
+
- edge type : plays
|
|
45
|
+
- edge label : plays
|
|
46
|
+
- node 2 : match
|
|
47
|
+
- max_from_node1 : None
|
|
48
|
+
- max_to_node2 : 2
|
|
49
|
+
```
|
|
50
|
+
- `max_from_node1` defines the maximum number of edges of a certain type that can exist from node 1 to node 2. For example, player1 is part of team1; now, player1 cannot be part of any other team.
|
|
51
|
+
- `max_to_node2` defines the maximum number of edges of a certain type that can be directed towards a particular node. For example, team1 and team2 can only play match1.
|
|
52
|
+
- Violations of these results in an error and the edge is not created
|
|
53
|
+
- for both node1 and node2, the general approach is to whitelist a set of schemas for the specified type of edge. It is still valid to add other types of edges from these nodes to others.
|
|
54
|
+
- The schema names in node1 and node2 fixes the schema types for this particular edge type. This means creating a relations "player1--(part_of)-->team1" or "team1--(part_of)-->player1" will result in the same relation "player1--(part_of)-->team1".
|
|
55
|
+
- You can also specify nodes with different schema types as : "player,coach".
|
|
56
|
+
- If you want to allow nodes of all schema types, use "*."
|
|
57
|
+
- Eg :
|
|
58
|
+
- Constraint:
|
|
59
|
+
- node 1: *
|
|
60
|
+
- edge label: interacts with
|
|
61
|
+
- node 2: *
|
|
62
|
+
- If you want to include all except for a few types of nodes, use : "* - schema1, schema2"
|
|
63
|
+
- Eg : "*-tag tagged_as tag" means that nodes of all types (except for tag nodes themselves) can be tagged with a tag.
|
|
64
|
+
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -344,6 +344,7 @@ export class BeanBagDB {
|
|
|
344
344
|
async create(schema, data, meta = {}, settings = {}) {
|
|
345
345
|
this._check_ready_to_use();
|
|
346
346
|
if(!schema){throw new DocCreationError(`No schema provided`)}
|
|
347
|
+
if(schema=="setting_edge"){throw new DocCreationError("This type of record can only be created through the create_edge api")}
|
|
347
348
|
if(Object.keys(data).length==0){throw new DocCreationError(`No data provided`)}
|
|
348
349
|
try {
|
|
349
350
|
let doc_obj = await this._insert_pre_checks(schema, data,meta, settings);
|
|
@@ -440,7 +441,6 @@ export class BeanBagDB {
|
|
|
440
441
|
* - Retrieves the document based on the provided search criteria.
|
|
441
442
|
* - Checks the revision ID to detect potential conflicts. (To be implemented: behavior when the `rev_id` does not match).
|
|
442
443
|
* - Validates editable fields against `schema.settings.editable_fields` (or allows editing of all fields if not specified).
|
|
443
|
-
* - Performs primary key conflict checks if multiple records are allowed (`single_record == false`).
|
|
444
444
|
* - Encrypts fields if encryption is required by the schema settings.
|
|
445
445
|
* - Updates the `meta` fields (such as `updated_on` and `updated_by`) and saves the updated document to the database.
|
|
446
446
|
*
|
|
@@ -448,7 +448,7 @@ export class BeanBagDB {
|
|
|
448
448
|
* @returns {Object} The result of the document update operation.
|
|
449
449
|
*
|
|
450
450
|
* **Errors**:
|
|
451
|
-
* - Throws an error if a document with the same primary keys already exists
|
|
451
|
+
* - Throws an error if a document with the same primary keys already exists .
|
|
452
452
|
* - Throws a `DocUpdateError` if a primary key conflict is detected during the update.
|
|
453
453
|
*
|
|
454
454
|
* @throws {DocUpdateError} - If a document with conflicting primary keys already exists.
|
|
@@ -655,6 +655,119 @@ export class BeanBagDB {
|
|
|
655
655
|
}
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
+
///////////////////////////////////////////////////////////
|
|
659
|
+
//////////////// simple directed graph ////////////////////////
|
|
660
|
+
//////////////////////////////////////////////////////////
|
|
661
|
+
|
|
662
|
+
async create_edge(node1,node2,edge_name,edge_label=""){
|
|
663
|
+
this._check_ready_to_use();
|
|
664
|
+
if(!edge_name){throw new ValidationError("edge_name required")}
|
|
665
|
+
if(Object.keys(node1)==0){throw new ValidationError("node1 required")}
|
|
666
|
+
if(Object.keys(node2)==0){throw new ValidationError("node2 required")}
|
|
667
|
+
|
|
668
|
+
let n1 = await this.read(node1)
|
|
669
|
+
let n2 = await this.read(node2)
|
|
670
|
+
let edges_constraint
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
let d = await this.read({schema:"setting_edge_constraint",data:{name:edge_name}})
|
|
674
|
+
edges_constraint = d["doc"]["data"]
|
|
675
|
+
let errors = []
|
|
676
|
+
let node1id = n1.doc._id
|
|
677
|
+
let node2id = n2.doc._id
|
|
678
|
+
let val_check = this._check_nodes_edge(edges_constraint.node1,edges_constraint.node2,n1.doc.schema,n2.doc.schema)
|
|
679
|
+
|
|
680
|
+
if (val_check.valid){
|
|
681
|
+
if(val_check.swapped){
|
|
682
|
+
// swapping required
|
|
683
|
+
node1id = n2.doc._id
|
|
684
|
+
node2id = n1.doc._id
|
|
685
|
+
}
|
|
686
|
+
}else{
|
|
687
|
+
this.errors.push("Invalid nodes.This config of nodes not allowed")
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
let records = await this.search({selector:{schema:"system_edge","data.edge_name":edge_name}})
|
|
691
|
+
|
|
692
|
+
if(edges_constraint.max_from_node1!=-1){
|
|
693
|
+
let filter1 = records.docs.filter((itm)=>itm.data.node1==node1id)
|
|
694
|
+
if(filter1.length>=edges_constraint.max_from_node1){
|
|
695
|
+
errors.push("max limit reached")
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if(edges_constraint.max_to_node2!=-1){
|
|
700
|
+
let filter2 = records.docs.filter((itm)=>itm.data.node2==node2id)
|
|
701
|
+
if(filter1.length>=edges_constraint.max_from_node1){
|
|
702
|
+
errors.push("max limit reached")
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if(errors.length==0){
|
|
707
|
+
let edge = await this.create("system_edge",{node1: node1id , node2: node1id ,edge_name:edge_name })
|
|
708
|
+
return edge
|
|
709
|
+
}else{
|
|
710
|
+
throw new RelationError(errors)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if(error instanceof DocNotFoundError){
|
|
715
|
+
let doc = {node1:"*",node2:"*",name:edge_name,label:edge_label}
|
|
716
|
+
let new_doc = await this.create("system_edge_constraint",doc)
|
|
717
|
+
let edge = await this.create("system_edge",{node1: n1.doc._id,node2: n2.doc._id,edge_name:edge_name })
|
|
718
|
+
return edge
|
|
719
|
+
}else{
|
|
720
|
+
throw error
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
_check_node_schema_match(rule, schema) {
|
|
726
|
+
/**
|
|
727
|
+
* Check if the schema matches the rule. The rule can be:
|
|
728
|
+
* - "*" for any schema
|
|
729
|
+
* - "*-n1,n2" for all schemas except n1 and n2
|
|
730
|
+
* - "specific_schema" or "schema1,schema2" for specific schema matches
|
|
731
|
+
*/
|
|
732
|
+
if (rule === "*") {
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (rule.startsWith("*-")) {
|
|
737
|
+
// Exclude the schemas listed after "*-"
|
|
738
|
+
const exclusions = rule.slice(2).split(",");
|
|
739
|
+
return !exclusions.includes(schema);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Otherwise, check if schema matches the specific rule (comma-separated for multiple allowed schemas)
|
|
743
|
+
const allowedSchemas = rule.split(",");
|
|
744
|
+
return allowedSchemas.includes(schema);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
_check_nodes_edge(node1Rule, node2Rule, schema1, schema2) {
|
|
748
|
+
/**
|
|
749
|
+
* Check if the edge between schema1 (node1) and schema2 (node2) is valid based on the rules
|
|
750
|
+
* node1Rule and node2Rule. Also checks if the nodes should be swapped.
|
|
751
|
+
*
|
|
752
|
+
*/
|
|
753
|
+
// Check if schema1 matches node1Rule and if schema2 matches node2Rule
|
|
754
|
+
const matchesNode1 = this._check_node_schema_match(node1Rule, schema1);
|
|
755
|
+
const matchesNode2 = this._check_node_schema_match(node2Rule, schema2);
|
|
756
|
+
|
|
757
|
+
// Check if schema1 matches node2Rule and schema2 matches node1Rule (for swapping condition)
|
|
758
|
+
const matchesSwappedNode1 = this._check_node_schema_match(node2Rule, schema1);
|
|
759
|
+
const matchesSwappedNode2 = this._check_node_schema_match(node1Rule, schema2);
|
|
760
|
+
|
|
761
|
+
// If the schemas match their respective rules (node1 and node2), the edge is valid
|
|
762
|
+
if (matchesNode1 && matchesNode2) { return { valid: true, swapped: false }}
|
|
763
|
+
|
|
764
|
+
// If swapping makes it valid, indicate that the nodes should be swapped
|
|
765
|
+
if (matchesSwappedNode1 && matchesSwappedNode2) { return { valid: true, swapped: true }}
|
|
766
|
+
// Otherwise, the edge is invalid
|
|
767
|
+
return { valid: false, swapped: false };
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
|
|
658
771
|
///////////////////////////////////////////////////////////
|
|
659
772
|
//////////////// Internal methods ////////////////////////
|
|
660
773
|
//////////////////////////////////////////////////////////
|
|
@@ -803,7 +916,10 @@ export class BeanBagDB {
|
|
|
803
916
|
this.util_validate_data(schemaDoc.schema, data);
|
|
804
917
|
|
|
805
918
|
// validate meta
|
|
806
|
-
|
|
919
|
+
if(Object.keys.length>0){
|
|
920
|
+
this.util_validate_data(sys_sch.editable_metadata_schema, meta)
|
|
921
|
+
}
|
|
922
|
+
|
|
807
923
|
|
|
808
924
|
// duplicate meta.link check
|
|
809
925
|
if (meta.link) {
|
|
@@ -1170,4 +1286,29 @@ constructor(errors=[]){
|
|
|
1170
1286
|
this.name = "EncryptionError";
|
|
1171
1287
|
this.errors = errors
|
|
1172
1288
|
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Custom error class for relation error.
|
|
1293
|
+
*
|
|
1294
|
+
* @extends {Error}
|
|
1295
|
+
*/
|
|
1296
|
+
export class RelationError extends Error {
|
|
1297
|
+
/**
|
|
1298
|
+
*
|
|
1299
|
+
* @extends {Error}
|
|
1300
|
+
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1301
|
+
*/
|
|
1302
|
+
constructor(errors=[]){
|
|
1303
|
+
let error_messages
|
|
1304
|
+
if(Array.isArray(errors)){
|
|
1305
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1306
|
+
}else {
|
|
1307
|
+
error_messages = [errors]
|
|
1308
|
+
}
|
|
1309
|
+
let message = `Error in relation of the simple digraph : ${error_messages.join(",")}`
|
|
1310
|
+
super(message)
|
|
1311
|
+
this.name = "RelationError";
|
|
1312
|
+
this.errors = errors
|
|
1313
|
+
}
|
|
1173
1314
|
}
|
package/src/system_schema.js
CHANGED
|
@@ -2,7 +2,7 @@ export const schema_schema = {
|
|
|
2
2
|
name: "schema",
|
|
3
3
|
description:"Meta-schema or the schema for defining other schemas",
|
|
4
4
|
system_generated:true,
|
|
5
|
-
version:0.
|
|
5
|
+
version:0.56,
|
|
6
6
|
schema: {
|
|
7
7
|
type: "object",
|
|
8
8
|
additionalProperties: false,
|
|
@@ -68,12 +68,6 @@ export const schema_schema = {
|
|
|
68
68
|
},
|
|
69
69
|
maxItems: 50,
|
|
70
70
|
description:"Once set, all the data in this field will be encrypted before storing it in the database. Encryption key must be provided during the time of BeanBagDB initialization and must be managed by the user as it is NOT stored in the database"
|
|
71
|
-
},
|
|
72
|
-
single_record: {
|
|
73
|
-
type: "boolean",
|
|
74
|
-
default: false,
|
|
75
|
-
description:
|
|
76
|
-
"If set, only a single records with this schema will be allowed to insert in the database",
|
|
77
71
|
}
|
|
78
72
|
},
|
|
79
73
|
required :["primary_keys","non_editable_fields","encrypted_fields"]
|
|
@@ -83,7 +77,7 @@ export const schema_schema = {
|
|
|
83
77
|
},
|
|
84
78
|
settings: {
|
|
85
79
|
primary_keys: ["name"],
|
|
86
|
-
|
|
80
|
+
non_editable_fields:[],
|
|
87
81
|
encrypted_fields:[]
|
|
88
82
|
},
|
|
89
83
|
};
|
|
@@ -91,9 +85,9 @@ export const schema_schema = {
|
|
|
91
85
|
export const system_schemas = {
|
|
92
86
|
keys: {
|
|
93
87
|
system_generated:true,
|
|
94
|
-
version:0.
|
|
88
|
+
version:0.53,
|
|
95
89
|
description:"To store user defined key. this can include anything like API tokens etc. There is a special method to fetch this. The values are encrypted",
|
|
96
|
-
name: "
|
|
90
|
+
name: "system_key",
|
|
97
91
|
schema: {
|
|
98
92
|
type: "object",
|
|
99
93
|
additionalProperties: true,
|
|
@@ -116,22 +110,20 @@ export const system_schemas = {
|
|
|
116
110
|
type: "string",
|
|
117
111
|
minLength: 1,
|
|
118
112
|
maxLength: 5000,
|
|
119
|
-
pattern: "^[a-zA-Z][a-zA-Z0-9_]*$",
|
|
120
113
|
},
|
|
121
114
|
},
|
|
122
115
|
},
|
|
123
116
|
settings: {
|
|
124
117
|
primary_keys: ["name"],
|
|
125
118
|
encrypted_fields:["value"],
|
|
126
|
-
non_editable_fields:[]
|
|
127
|
-
single_record: false
|
|
119
|
+
non_editable_fields:[]
|
|
128
120
|
},
|
|
129
121
|
},
|
|
130
122
|
settings: {
|
|
131
123
|
version:0.5,
|
|
132
124
|
system_generated:true,
|
|
133
125
|
description:"The system relies on these settings for proper functioning or enabling optional features.",
|
|
134
|
-
name: "
|
|
126
|
+
name: "system_setting",
|
|
135
127
|
schema: {
|
|
136
128
|
required:["name","value"],
|
|
137
129
|
type: "object",
|
|
@@ -157,44 +149,124 @@ export const system_schemas = {
|
|
|
157
149
|
settings: {
|
|
158
150
|
primary_keys: ["name"],
|
|
159
151
|
non_editable_fields: ["name"],
|
|
160
|
-
encrypted_fields:[]
|
|
161
|
-
|
|
152
|
+
encrypted_fields:[]
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
edges_constraints:{
|
|
156
|
+
name:"system_edge_constraint",
|
|
157
|
+
system_generated:true,
|
|
158
|
+
version:0.5,
|
|
159
|
+
description: "To define edge constraints for simple directed graph of records.",
|
|
160
|
+
schema:{
|
|
161
|
+
type: "object",
|
|
162
|
+
additionalProperties: true,
|
|
163
|
+
required:["node1","node2","edge_type"],
|
|
164
|
+
properties: {
|
|
165
|
+
node1: {
|
|
166
|
+
type: "string",
|
|
167
|
+
minLength: 1,
|
|
168
|
+
maxLength: 500,
|
|
169
|
+
pattern: "^(\\*|(\\*-[a-zA-Z0-9_-]+)(,[a-zA-Z0-9_-]+)*|[a-zA-Z0-9_-]+(,[a-zA-Z0-9_-]+)*)$"
|
|
170
|
+
},
|
|
171
|
+
node2: {
|
|
172
|
+
type: "string",
|
|
173
|
+
minLength: 1,
|
|
174
|
+
maxLength: 500,
|
|
175
|
+
pattern: "^(\\*|(\\*-[a-zA-Z0-9_-]+)(,[a-zA-Z0-9_-]+)*|[a-zA-Z0-9_-]+(,[a-zA-Z0-9_-]+)*)$"
|
|
176
|
+
},
|
|
177
|
+
name:{
|
|
178
|
+
type: "string",
|
|
179
|
+
minLength: 1,
|
|
180
|
+
maxLength: 500,
|
|
181
|
+
pattern: "^[a-zA-Z][a-zA-Z0-9_]*$",
|
|
182
|
+
},
|
|
183
|
+
label:{
|
|
184
|
+
type: "string",
|
|
185
|
+
maxLength: 500,
|
|
186
|
+
},
|
|
187
|
+
note:{
|
|
188
|
+
type: "string",
|
|
189
|
+
maxLength: 5000,
|
|
190
|
+
},
|
|
191
|
+
max_from_node1:{
|
|
192
|
+
type:"number",
|
|
193
|
+
default: -1,
|
|
194
|
+
},
|
|
195
|
+
max_to_node2:{
|
|
196
|
+
type:"number",
|
|
197
|
+
default: -1,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
settings: {
|
|
202
|
+
primary_keys: ["name"],
|
|
203
|
+
non_editable_fields:["name"],
|
|
204
|
+
encrypted_fields:[]
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
edges:{
|
|
208
|
+
name:"system_edge",
|
|
209
|
+
system_generated:true,
|
|
210
|
+
version:0.5,
|
|
211
|
+
description: "To define edges in the simple directed graph of records.",
|
|
212
|
+
schema:{
|
|
213
|
+
type: "object",
|
|
214
|
+
additionalProperties: true,
|
|
215
|
+
required:["node1","node2","edge_type"],
|
|
216
|
+
properties: {
|
|
217
|
+
node1: {
|
|
218
|
+
type: "string",
|
|
219
|
+
},
|
|
220
|
+
node2: {
|
|
221
|
+
type: "string",
|
|
222
|
+
},
|
|
223
|
+
edge_name:{
|
|
224
|
+
type: "string",
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
settings: {
|
|
229
|
+
primary_keys: ["node1","node2","edge_type"],
|
|
230
|
+
non_editable_fields:["edge_type"],
|
|
231
|
+
encrypted_fields:[]
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
media:{
|
|
235
|
+
name:"system_media",
|
|
236
|
+
system_generated:true,
|
|
237
|
+
version:0.5,
|
|
238
|
+
description: "To store images as Base64",
|
|
239
|
+
schema:{
|
|
240
|
+
type: "object",
|
|
241
|
+
additionalProperties: true,
|
|
242
|
+
required:["imageBase64","caption","source"],
|
|
243
|
+
properties: {
|
|
244
|
+
imageBase64: {
|
|
245
|
+
type: "string",
|
|
246
|
+
"media": {
|
|
247
|
+
"binaryEncoding": "base64"
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
caption: {
|
|
251
|
+
type: "string",
|
|
252
|
+
},
|
|
253
|
+
source:{
|
|
254
|
+
type: "string",
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
settings: {
|
|
259
|
+
primary_keys: ["caption"],
|
|
260
|
+
non_editable_fields:[],
|
|
261
|
+
encrypted_fields:[]
|
|
162
262
|
},
|
|
163
263
|
}
|
|
164
|
-
// ,
|
|
165
|
-
// edges:{
|
|
166
|
-
// name:"edges",
|
|
167
|
-
// description:"To relate one document to another.Labels can be configured using the db_network setting document.This stores edges of a simple directed graph",
|
|
168
|
-
// schema:{
|
|
169
|
-
// required:["graph","node1","node2","label"],
|
|
170
|
-
// type:"object",
|
|
171
|
-
// additionalProperties: false,
|
|
172
|
-
// properties : {
|
|
173
|
-
// graph:{
|
|
174
|
-
// type:"string",
|
|
175
|
-
// minLength:3,
|
|
176
|
-
// default:"default",
|
|
177
|
-
// maxLength:100,
|
|
178
|
-
// description:"Name of the graph in which this edge is being added. This is managed by the db_network setting "
|
|
179
|
-
// },
|
|
180
|
-
// node1:{
|
|
181
|
-
// type:"string",
|
|
182
|
-
// description:"the source of this directed edge. this must be a valid document id."
|
|
183
|
-
// }
|
|
184
|
-
// }
|
|
185
|
-
// },
|
|
186
|
-
// settings :{
|
|
187
|
-
// primary_key:["graph","node1","node2","label"],
|
|
188
|
-
// non_editable_fields:["node1","node2","graph"],
|
|
189
|
-
// encrypted_fields:[],
|
|
190
|
-
// single_record:false
|
|
191
|
-
// }
|
|
192
|
-
// }
|
|
193
264
|
};
|
|
194
265
|
|
|
195
266
|
// this is not stored in the DB. only for validating the metadata during doc update
|
|
196
267
|
export const editable_metadata_schema = {
|
|
197
268
|
additionalProperties: false,
|
|
269
|
+
type:"object",
|
|
198
270
|
properties:{
|
|
199
271
|
tags:{
|
|
200
272
|
type:"array",
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// to test database operations. assuming the class is initialized successfully
|
|
2
|
+
// to test initialization of the BeanBagDB class
|
|
3
|
+
import { get_pdb_doc } from "./pouchdb.js";
|
|
4
|
+
import assert, { throws, strictEqual, rejects } from "assert";
|
|
5
|
+
import {
|
|
6
|
+
BeanBagDB,
|
|
7
|
+
DocCreationError,
|
|
8
|
+
EncryptionError,
|
|
9
|
+
ValidationError,
|
|
10
|
+
DocNotFoundError,
|
|
11
|
+
DocUpdateError,
|
|
12
|
+
} from "../src/index.js";
|
|
13
|
+
|
|
14
|
+
import * as chai from "chai";
|
|
15
|
+
import chaiAsPromised from "chai-as-promised";
|
|
16
|
+
|
|
17
|
+
chai.use(chaiAsPromised);
|
|
18
|
+
|
|
19
|
+
// Then either:
|
|
20
|
+
const expect = chai.expect;
|
|
21
|
+
|
|
22
|
+
let database; // this is the global db object
|
|
23
|
+
|
|
24
|
+
describe("Edge constraint insertion tests", async () => {
|
|
25
|
+
const constraint_docs_invalid = [
|
|
26
|
+
[
|
|
27
|
+
"error when node1 empty",
|
|
28
|
+
{
|
|
29
|
+
node1: "",
|
|
30
|
+
node2: "*",
|
|
31
|
+
name: "sample",
|
|
32
|
+
label: "Sample",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"error when node2 empty ",
|
|
37
|
+
{
|
|
38
|
+
node1: "*",
|
|
39
|
+
node2: "",
|
|
40
|
+
name: "sample",
|
|
41
|
+
label: "Sample",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
"error when edge name empty",
|
|
46
|
+
{
|
|
47
|
+
node1: "*",
|
|
48
|
+
node2: "*",
|
|
49
|
+
name: "",
|
|
50
|
+
label: "Sample",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"error when node 1 invalid 1",
|
|
55
|
+
{
|
|
56
|
+
node1: "*3434",
|
|
57
|
+
node2: "*",
|
|
58
|
+
name: "sample",
|
|
59
|
+
label: "Sample",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
"error when node 1 invalid 2",
|
|
64
|
+
{
|
|
65
|
+
node1: "1212121 ssd sdsd",
|
|
66
|
+
node2: "*",
|
|
67
|
+
name: "sample",
|
|
68
|
+
label: "Sample",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
"error when node 1 invalid 3",
|
|
73
|
+
{
|
|
74
|
+
node1: "sample1-sample2",
|
|
75
|
+
node2: "*",
|
|
76
|
+
name: "sample",
|
|
77
|
+
label: "Sample",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
"error when node 1 invalid 4",
|
|
82
|
+
{
|
|
83
|
+
node1: "*+sample1-sample2",
|
|
84
|
+
node2: "*",
|
|
85
|
+
name: "sample",
|
|
86
|
+
label: "Sample",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"error when edge name is invalid",
|
|
91
|
+
{
|
|
92
|
+
node1: "*",
|
|
93
|
+
node2: "*",
|
|
94
|
+
name: "Sample one",
|
|
95
|
+
label: "Sample",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
before(async () => {
|
|
101
|
+
// adding a schema
|
|
102
|
+
let doc_obj = get_pdb_doc("test_database_41", "qwe33rtyuiopaqwsde1254");
|
|
103
|
+
database = new BeanBagDB(doc_obj);
|
|
104
|
+
await database.ready(); // Ensure the database is ready before running tests
|
|
105
|
+
try {
|
|
106
|
+
//console.log(test_schema)
|
|
107
|
+
//let a = await database.create("schema",test_schema)
|
|
108
|
+
//console.log("Ready for more tests...");
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.log("error in before");
|
|
111
|
+
console.log(error);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
constraint_docs_invalid.forEach((element, index) => {
|
|
116
|
+
it(`${element[0]}`, async () => {
|
|
117
|
+
await rejects(async () => {
|
|
118
|
+
await database.create("setting_edge_constraint", element[1]);
|
|
119
|
+
}, DocCreationError);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("Edge insertion test", async () => {
|
|
125
|
+
// it('read docs', async () => {
|
|
126
|
+
// try {
|
|
127
|
+
// let udata = await database3.search({selector:{"schema":"book"}})
|
|
128
|
+
// assert(udata.docs.length==2)
|
|
129
|
+
// } catch (error) {
|
|
130
|
+
// //console.log(error)
|
|
131
|
+
// throw error
|
|
132
|
+
// }
|
|
133
|
+
// })
|
|
134
|
+
|
|
135
|
+
const edge_docs_invalid = [
|
|
136
|
+
[
|
|
137
|
+
"error when invalid node 1",
|
|
138
|
+
{
|
|
139
|
+
node1: "sample",
|
|
140
|
+
node2: "*",
|
|
141
|
+
name: "does not matter",
|
|
142
|
+
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
"error when node 2 invalid ",
|
|
147
|
+
{
|
|
148
|
+
node1: { link: "does_not_exists" },
|
|
149
|
+
node2: "invalid,must be a criteria obj",
|
|
150
|
+
name: "sample",
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
|
|
154
|
+
[
|
|
155
|
+
"error when edge name invalid",
|
|
156
|
+
{
|
|
157
|
+
node1: { link: "sample1" },
|
|
158
|
+
node2: { link: "sample2" },
|
|
159
|
+
name: "sample",
|
|
160
|
+
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
"error when both nodes are same",
|
|
165
|
+
{
|
|
166
|
+
node1: {},
|
|
167
|
+
node2: "*",
|
|
168
|
+
name: "Sample one"
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
before(async () => {
|
|
174
|
+
// adding a schema
|
|
175
|
+
let doc_obj = get_pdb_doc("test_database_41", "qwe33rtyuiopaqwsde1254");
|
|
176
|
+
database = new BeanBagDB(doc_obj);
|
|
177
|
+
await database.ready(); // Ensure the database is ready before running tests
|
|
178
|
+
try {
|
|
179
|
+
let schemas = [
|
|
180
|
+
{
|
|
181
|
+
name: "player",
|
|
182
|
+
description:"Player",
|
|
183
|
+
schema: {
|
|
184
|
+
additionalProperties:true,
|
|
185
|
+
type:"object",
|
|
186
|
+
properties: {
|
|
187
|
+
name: {
|
|
188
|
+
type: "string",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
settings: {
|
|
193
|
+
primary_keys: ["name"],
|
|
194
|
+
encrypted_fields: [],
|
|
195
|
+
non_editable_fields: ["name"],
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "team",
|
|
200
|
+
description:"Team",
|
|
201
|
+
schema: {
|
|
202
|
+
additionalProperties:true,
|
|
203
|
+
type:"object",
|
|
204
|
+
properties: {
|
|
205
|
+
name: {
|
|
206
|
+
type: "string",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
settings: {
|
|
211
|
+
primary_keys: ["name"],
|
|
212
|
+
encrypted_fields: [],
|
|
213
|
+
non_editable_fields: ["name"],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "match",
|
|
218
|
+
description:"Match",
|
|
219
|
+
schema: {
|
|
220
|
+
type:"object",
|
|
221
|
+
additionalProperties:true,
|
|
222
|
+
properties: {
|
|
223
|
+
name: {
|
|
224
|
+
type: "string",
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
settings: {
|
|
229
|
+
primary_keys: ["name"],
|
|
230
|
+
encrypted_fields: [],
|
|
231
|
+
non_editable_fields: ["name"],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
for (let index = 0; index < schemas.length; index++) {
|
|
236
|
+
try {
|
|
237
|
+
const element = schemas[index];
|
|
238
|
+
await database.create("schema", element);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.log("not heeee")
|
|
241
|
+
console.log(error)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
}
|
|
245
|
+
const records = [
|
|
246
|
+
["player", { name: "player1" }],
|
|
247
|
+
["player", { name: "player2" }],
|
|
248
|
+
["player", { name: "player3" }],
|
|
249
|
+
["player", { name: "player4" }],
|
|
250
|
+
["player", { name: "player5" }],
|
|
251
|
+
["player", { name: "player6" }],
|
|
252
|
+
["player", { name: "player7" }],
|
|
253
|
+
["player", { name: "player8" }],
|
|
254
|
+
["player", { name: "player9" }],
|
|
255
|
+
["player", { name: "player10" }],
|
|
256
|
+
["player", { name: "player11" }],
|
|
257
|
+
["player", { name: "player12" }],
|
|
258
|
+
["player", { name: "player13" }],
|
|
259
|
+
["player", { name: "player14" }],
|
|
260
|
+
["player", { name: "player15" }],
|
|
261
|
+
["team", { name: "team1" }],
|
|
262
|
+
["team", { name: "team2" }],
|
|
263
|
+
["match", { name: "match1" }],
|
|
264
|
+
["match", { name: "match2" }],
|
|
265
|
+
["system_edge_constraint",{ name:"part_of",node1:"player",node2:"team", max_from_node1:1, max_to_node2:5 , label:"is part of "}],
|
|
266
|
+
["system_edge_constraint",{name: "plays",node1:"team",node2:"match",max_from_node1 : 1, max_to_node2: 2 , label:"plays in" }]
|
|
267
|
+
];
|
|
268
|
+
for (let index = 0; index < records.length; index++) {
|
|
269
|
+
const element = records[index];
|
|
270
|
+
let data1 = element[1];
|
|
271
|
+
await database.create(element[0], data1, { link: data1.name });
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.log("error in before....");
|
|
275
|
+
console.log(error);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
edge_docs_invalid.forEach((element, index) => {
|
|
280
|
+
it(`${element[0]}`, async () => {
|
|
281
|
+
await rejects(async () => {
|
|
282
|
+
await database.create("setting_edge", element[1]);
|
|
283
|
+
}, DocCreationError);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
let valid_edges = [
|
|
289
|
+
{node1:{link:"player1"},node2:{link:"team1"},edge_name:"part_of"},
|
|
290
|
+
{node1:{link:"player2"},node2:{link:"team1"},edge_name:"part_of"},
|
|
291
|
+
{node1:{link:"player3"},node2:{link:"team1"},edge_name:"part_of"},
|
|
292
|
+
{node1:{link:"player4"},node2:{link:"team1"},edge_name:"part_of"},
|
|
293
|
+
{node1:{link:"player5"},node2:{link:"team1"},edge_name:"part_of"},
|
|
294
|
+
{node1:{link:"player6"},node2:{link:"team2"},edge_name:"part_of"},
|
|
295
|
+
{node1:{link:"player7"},node2:{link:"team2"},edge_name:"part_of"}
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
valid_edges.forEach((element, index) => {
|
|
299
|
+
it(`adds an edge`, async () => {
|
|
300
|
+
try {
|
|
301
|
+
let edge = await database.create_edge(element.node1,element.node2,element.name)
|
|
302
|
+
console.log(edge)
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.log(error)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
});
|
package/test/operations.test.js
CHANGED
|
@@ -1656,7 +1656,7 @@ describe("Doc search tests", async () => {
|
|
|
1656
1656
|
it('all docs', async () => {
|
|
1657
1657
|
try {
|
|
1658
1658
|
let udata = await database3.search({selector:{}})
|
|
1659
|
-
assert(udata.docs.length==
|
|
1659
|
+
assert(udata.docs.length==11)
|
|
1660
1660
|
} catch (error) {
|
|
1661
1661
|
//console.log(error)
|
|
1662
1662
|
throw error
|
|
@@ -1676,7 +1676,7 @@ describe("Doc search tests", async () => {
|
|
|
1676
1676
|
it('read docs 2', async () => {
|
|
1677
1677
|
try {
|
|
1678
1678
|
let udata = await database3.search({selector:{"schema":"schema"}})
|
|
1679
|
-
assert(udata.docs.length==
|
|
1679
|
+
assert(udata.docs.length==7) // schema,book,setting,key,edge,edge_constraints
|
|
1680
1680
|
} catch (error) {
|
|
1681
1681
|
//console.log(error)
|
|
1682
1682
|
throw error
|
package/test/plugin.test.js
CHANGED
|
@@ -19,15 +19,12 @@ let database; // this is the global db object
|
|
|
19
19
|
|
|
20
20
|
describe("Testing plugin load", async () => {
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
before(async () => {
|
|
24
23
|
let doc_obj = get_pdb_doc("test_database_30", "qwertyuiopaqwsde1254");
|
|
25
24
|
database = new BeanBagDB(doc_obj);
|
|
26
25
|
await database.ready(); // Ensure the database is ready before running tests
|
|
27
26
|
console.log("Ready for more tests...");
|
|
28
27
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
28
|
|
|
32
29
|
it('successfully loads the plugin', async () => {
|
|
33
30
|
try {
|
|
@@ -41,10 +38,10 @@ describe("Testing plugin load", async () => {
|
|
|
41
38
|
|
|
42
39
|
it('successfully runs the loaded the plugin method', async () => {
|
|
43
40
|
try {
|
|
44
|
-
|
|
45
|
-
let command = await database["txtcmd"].parse("new/system_keys")
|
|
46
|
-
console.log(command)
|
|
47
|
-
assert (
|
|
41
|
+
//await database.load_plugin("txtcmd",text_command)
|
|
42
|
+
let command = await database.plugins["txtcmd"].parse("new/system_keys")
|
|
43
|
+
//console.log(command)
|
|
44
|
+
assert (command.valid ==true)
|
|
48
45
|
} catch (error) {
|
|
49
46
|
console.log(error)
|
|
50
47
|
throw error
|