goby-database 1.0.9 → 2.0.10
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/index.d.ts +125 -0
- package/dist/index.js +969 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox.d.ts +1 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/unit-tests.d.ts +1 -0
- package/dist/unit-tests.js +82 -0
- package/dist/unit-tests.js.map +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +106 -0
- package/dist/utils.js.map +1 -0
- package/package.json +11 -3
- package/src/index.ts +1251 -0
- package/src/sandbox.ts +107 -0
- package/src/types.ts +218 -0
- package/src/unit-tests.ts +117 -0
- package/src/utils.ts +133 -0
- package/tsconfig.json +111 -0
- package/index.js +0 -840
package/index.js
DELETED
|
@@ -1,840 +0,0 @@
|
|
|
1
|
-
// import Database from 'better-sqlite3';
|
|
2
|
-
const Database = require('better-sqlite3');
|
|
3
|
-
|
|
4
|
-
class Project{
|
|
5
|
-
constructor(source){
|
|
6
|
-
this.db= new Database(source);
|
|
7
|
-
|
|
8
|
-
this.text_datatypes=['string','resource'];
|
|
9
|
-
this.real_datatypes=['number'];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//checks if goby has been initialized, initializes if not
|
|
14
|
-
const goby_init=this.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='system_root'`).get();
|
|
15
|
-
if(!goby_init){
|
|
16
|
-
console.log('initializing goby database');
|
|
17
|
-
this.init();
|
|
18
|
-
}else{
|
|
19
|
-
console.log('opened goby database');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
this.db.function('junction_obj', (side_a, side_b) => {
|
|
23
|
-
|
|
24
|
-
let split_1=side_a.split('.');
|
|
25
|
-
let split_2=side_b.split('.');
|
|
26
|
-
let c1=`"class_id":${split_1[0]}`;
|
|
27
|
-
let p1=split_1[1]?`,"prop_id":${split_1[1]}`:'';
|
|
28
|
-
let c2=`"class_id":${split_2[0]}`;
|
|
29
|
-
let p2=split_2[1]?`,"prop_id":${split_2[1]}`:'';
|
|
30
|
-
return `[ {${c1}${p1}}, {${c2}${p2}} ]`;
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
//prepared statements with arguments so my code isn't as verbose elsewhere
|
|
34
|
-
this.run={
|
|
35
|
-
begin:this.db.prepare('BEGIN IMMEDIATE'),
|
|
36
|
-
commit:this.db.prepare('COMMIT'),
|
|
37
|
-
rollback:this.db.prepare('ROLLBACK'),
|
|
38
|
-
create_item:this.db.prepare('INSERT INTO system_root(type,value) VALUES (@type, @value)'),
|
|
39
|
-
get_junctionlist:this.db.prepare('SELECT id, junction_obj(side_a, side_b) AS sides, metadata FROM system_junctionlist'),
|
|
40
|
-
get_class:this.db.prepare(`SELECT name, metadata FROM system_classlist WHERE id = ?`),
|
|
41
|
-
get_class_id:this.db.prepare(`SELECT id FROM system_classlist WHERE name = ?`),
|
|
42
|
-
get_all_classes:this.db.prepare(`SELECT id, name, metadata FROM system_classlist`),
|
|
43
|
-
save_class_meta:this.db.prepare(`UPDATE system_classlist set metadata = ? WHERE id = ?`),
|
|
44
|
-
update_window:this.db.prepare(`UPDATE system_windows set open = @open, type=@type, metadata = @meta WHERE id = @id`),
|
|
45
|
-
create_window: this.db.prepare(`INSERT INTO system_windows (type,open, metadata) VALUES (@type, @open, @meta)`),
|
|
46
|
-
get_windows:this.db.prepare(`SELECT id, type, open, metadata FROM system_windows`),
|
|
47
|
-
match_junction:this.db.prepare(`SELECT id, side_a, side_b, metadata FROM system_junctionlist WHERE (side_a = @input_1 AND side_b = @input_2 ) OR ( side_a = @input_2 AND side_b = @input_1 )`),
|
|
48
|
-
fuzzy_match_junction:this.db.prepare(`SELECT id, side_a, side_b, metadata FROM system_junctionlist WHERE (side_a LIKE @input_1 AND side_b LIKE @input_2 ) OR ( side_a LIKE @input_2 AND side_b LIKE @input_1 )`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.class_cache={};
|
|
55
|
-
this.refresh_class_cache();
|
|
56
|
-
this.junction_cache=[];
|
|
57
|
-
this.refresh_junction_cache();
|
|
58
|
-
// this.junction_cache;
|
|
59
|
-
|
|
60
|
-
//if I understand transactions correctly, a new one will begin with every user action while committing the one before, meaning I'll need to have the first begin here
|
|
61
|
-
//this enables a one-step undo.
|
|
62
|
-
// this.run.begin.run();
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get_latest_id(table_name){
|
|
67
|
-
let id=this.db.prepare(`SELECT last_insert_rowid() AS id FROM ${table_name}`).get().id;
|
|
68
|
-
return id;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
init(){
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//System table to contain all items in the project.
|
|
77
|
-
this.create_table('system','root',[
|
|
78
|
-
'id INTEGER NOT NULL PRIMARY KEY',
|
|
79
|
-
'type TEXT',
|
|
80
|
-
'value TEXT'
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
//System table to contain metadata for all classes created by user
|
|
85
|
-
this.create_table('system','classlist',['id INTEGER NOT NULL PRIMARY KEY','name TEXT','metadata TEXT']);
|
|
86
|
-
//System table to contain all the junction tables and aggregate info about relations
|
|
87
|
-
this.create_table('system','junctionlist',['id INTEGER NOT NULL PRIMARY KEY','side_a TEXT','side_b TEXT','metadata TEXT']);
|
|
88
|
-
//System table to contain generated image data
|
|
89
|
-
this.create_table('system','images',['file_path TEXT','img_type TEXT','img BLOB']);
|
|
90
|
-
|
|
91
|
-
this.create_table('system','windows',[
|
|
92
|
-
'id INTEGER NOT NULL PRIMARY KEY',
|
|
93
|
-
'type TEXT',
|
|
94
|
-
'open INTEGER',
|
|
95
|
-
'metadata TEXT'
|
|
96
|
-
]);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// let home_id=this.action_config_window('home',0,{pos:[null,null], size:[540,400]})
|
|
100
|
-
// let hopper_id=this.action_config_window('hopper',0,{pos:[null,null], size:[300,400]})
|
|
101
|
-
// console.log('home:',home_id,'hopper:',hopper_id)
|
|
102
|
-
this.db.prepare(`INSERT INTO system_windows
|
|
103
|
-
(type, open, metadata)
|
|
104
|
-
VALUES
|
|
105
|
-
('home',0,'${JSON.stringify({pos:[null,null], size:[540,400]})}'),
|
|
106
|
-
('hopper',0,'${JSON.stringify({pos:[null,null], size:[300,400]})}')`).run();
|
|
107
|
-
|
|
108
|
-
// [TO ADD: special junction table for root items to reference themselves in individual relation]
|
|
109
|
-
this.create_table('system','junction_root',[
|
|
110
|
-
'id_1 INTEGER',
|
|
111
|
-
'id_2 INTEGER',
|
|
112
|
-
'metadata TEXT'
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
refresh_class_cache(){
|
|
123
|
-
let class_array=this.run.get_all_classes.all();
|
|
124
|
-
let cache_obj={};
|
|
125
|
-
for(let cls of class_array){
|
|
126
|
-
cache_obj[cls.id]={
|
|
127
|
-
id:cls.id,
|
|
128
|
-
name:cls.name,
|
|
129
|
-
metadata:JSON.parse(cls.metadata)
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
this.class_cache=cache_obj;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
refresh_junction_cache(){
|
|
137
|
-
let junction_list=this.run.get_junctionlist.all();
|
|
138
|
-
junction_list.map(a=>a.sides=JSON.parse(a.sides));
|
|
139
|
-
this.junction_cache=junction_list;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
create_table(type,name,columns){
|
|
145
|
-
//type will pass in 'class', 'system', or 'junction' to use as a name prefix
|
|
146
|
-
//columns is an array of raw SQL column strings
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let columns_string=columns.join(',');
|
|
150
|
-
|
|
151
|
-
//name in brackets to allow special characters
|
|
152
|
-
// validation test: what happens if there are brackets in the name?
|
|
153
|
-
const sqlname=type=='class'?`[class_${name}]`:`${type}_${name}`;
|
|
154
|
-
let create_statement=`CREATE TABLE ${sqlname}(
|
|
155
|
-
${columns_string}
|
|
156
|
-
)`;
|
|
157
|
-
this.db.prepare(create_statement).run();
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
action_create_class(name,meta){
|
|
163
|
-
// if(this.db.inTransaction) this.run.commit.run();
|
|
164
|
-
// this.run.begin.run();
|
|
165
|
-
|
|
166
|
-
//a class starts with these basic columns
|
|
167
|
-
let columns=['system_id INTEGER UNIQUE',
|
|
168
|
-
'system_order INTEGER','user_name TEXT',`FOREIGN KEY(system_id) REFERENCES system_root(id)`];
|
|
169
|
-
|
|
170
|
-
this.create_table('class',name,columns);
|
|
171
|
-
|
|
172
|
-
const class_meta={
|
|
173
|
-
properties:[
|
|
174
|
-
{
|
|
175
|
-
name:'name',
|
|
176
|
-
type:'data',
|
|
177
|
-
id:1,
|
|
178
|
-
datatype:'string',
|
|
179
|
-
conditions:{
|
|
180
|
-
max:1
|
|
181
|
-
},
|
|
182
|
-
style:{
|
|
183
|
-
display:true,
|
|
184
|
-
colwidth:4
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
],
|
|
188
|
-
used_prop_ids:[1],
|
|
189
|
-
style:{
|
|
190
|
-
color:'#b5ffd5',
|
|
191
|
-
display:meta?.display || true,
|
|
192
|
-
position:meta?.position || [0,0]
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
this.db.prepare(`INSERT INTO system_classlist (name, metadata) VALUES ('${name}','${JSON.stringify(class_meta)}')`).run();
|
|
197
|
-
//get the id of newest value from system_classlist and return
|
|
198
|
-
const class_id=this.db.prepare('SELECT id FROM system_classlist ORDER BY id DESC').get().id;
|
|
199
|
-
|
|
200
|
-
this.refresh_class_cache();
|
|
201
|
-
this.refresh_junction_cache();
|
|
202
|
-
|
|
203
|
-
return class_id;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
action_add_data_property(class_id,name,conditions,datatype,style){
|
|
210
|
-
|
|
211
|
-
let class_data=this.class_cache[class_id];
|
|
212
|
-
let class_name=class_data.name;
|
|
213
|
-
let class_meta=class_data.metadata;
|
|
214
|
-
|
|
215
|
-
let id=Math.max(...class_meta.used_prop_ids)+1;
|
|
216
|
-
class_meta.used_prop_ids.push(id);
|
|
217
|
-
//create JSON for storage in system_classlist
|
|
218
|
-
const prop_meta={ type:'data', name,conditions,style,datatype,id};
|
|
219
|
-
|
|
220
|
-
//add property to property list
|
|
221
|
-
class_meta.properties.push(prop_meta);
|
|
222
|
-
|
|
223
|
-
let sql_datatype='';
|
|
224
|
-
|
|
225
|
-
if(conditions.max>1||this.text_datatypes.includes(datatype)){
|
|
226
|
-
//multiple for data means a stringified array no matter what it is
|
|
227
|
-
sql_datatype='TEXT';
|
|
228
|
-
}else if(this.real_datatypes.includes(datatype)){
|
|
229
|
-
sql_datatype='REAL';
|
|
230
|
-
}
|
|
231
|
-
//create property in table
|
|
232
|
-
let command_string=`ALTER TABLE [class_${class_name}] ADD COLUMN [user_${name}] ${sql_datatype};`;
|
|
233
|
-
this.db.prepare(command_string).run();
|
|
234
|
-
|
|
235
|
-
//update metadata json for table with new property
|
|
236
|
-
this.run.save_class_meta.run(JSON.stringify(class_meta),class_id);
|
|
237
|
-
this.refresh_class_cache();
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
action_add_relation_property(class_id,name,conditions,style){
|
|
243
|
-
// basic property construction------------------
|
|
244
|
-
let class_meta=this.class_cache[class_id].metadata;
|
|
245
|
-
|
|
246
|
-
let id=Math.max(...class_meta.used_prop_ids)+1;
|
|
247
|
-
class_meta.used_prop_ids.push(id);
|
|
248
|
-
//create JSON for storage in system_classlist
|
|
249
|
-
const prop_meta={
|
|
250
|
-
type:'relation',
|
|
251
|
-
name,
|
|
252
|
-
style,
|
|
253
|
-
id,
|
|
254
|
-
targets:[],
|
|
255
|
-
conditions
|
|
256
|
-
}
|
|
257
|
-
//add property to property list
|
|
258
|
-
class_meta.properties.push(prop_meta);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this.run.save_class_meta.run(JSON.stringify(class_meta),class_id);
|
|
262
|
-
this.refresh_class_cache();
|
|
263
|
-
|
|
264
|
-
return id;
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
delete_property(class_id,prop_id){
|
|
270
|
-
//this function is meant to be used within a flow where the relations that need to change as a result of this deletion are already kept track of
|
|
271
|
-
|
|
272
|
-
let class_meta=this.class_cache[class_id].metadata;
|
|
273
|
-
let i=class_meta.properties.findIndex(a=>a.id==prop_id);
|
|
274
|
-
class_meta.properties.splice(i,1);
|
|
275
|
-
this.run.save_class_meta.run(JSON.stringify(class_meta),class_id.id);
|
|
276
|
-
this.refresh_class_cache();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
get_junctions(){
|
|
280
|
-
let junction_list=this.run.get_junctionlist.all();
|
|
281
|
-
junction_list.map(a=>a.sides=JSON.parse(a.sides))
|
|
282
|
-
return junction_list;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
action_edit_class_schema(edits){
|
|
287
|
-
// class_changes=[],new_junction_list
|
|
288
|
-
let class_changes=edits.class_changes || [];
|
|
289
|
-
let junction_list=edits.junction_list;
|
|
290
|
-
|
|
291
|
-
for(let change of class_changes){
|
|
292
|
-
|
|
293
|
-
if(change.action=='create'){
|
|
294
|
-
if(change.class_id==undefined){
|
|
295
|
-
change.class_id=this.action_create_class(change.class_name);
|
|
296
|
-
if(junction_list) junction_list.map(junction=>{
|
|
297
|
-
let matching=junction.sides.find(
|
|
298
|
-
side=>side.class_name==change.class_name);
|
|
299
|
-
if(matching) matching.class_id=change.class_id;
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if(change.prop_name!==undefined){
|
|
304
|
-
//if a prop name is specified, that means
|
|
305
|
-
// this change involves a property
|
|
306
|
-
// that property needs to be created
|
|
307
|
-
if(change.type=='relation'){
|
|
308
|
-
change.prop_id=this.action_add_relation_property(change.class_id,change.prop_name,change.conditions,change.style);
|
|
309
|
-
junction_list.map(junction=>{
|
|
310
|
-
let matching=junction.sides.find(
|
|
311
|
-
side=>side.class_id==change.class_id&&side.prop_name==change.prop_name);
|
|
312
|
-
if(matching) matching.prop_id=change.prop_id;
|
|
313
|
-
})
|
|
314
|
-
}else if(change.type=='data'){
|
|
315
|
-
this.action_add_data_property(change.class_id,change.prop_name,change.conditions,change.datatype,change.style);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
}else if(change.action=='delete'){
|
|
320
|
-
if(change.prop_id!==undefined){
|
|
321
|
-
this.delete_property(change.class_id,change.prop_id);
|
|
322
|
-
}else{
|
|
323
|
-
// delete class
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
//maybe it's worthwhile and safe to just check the junction list for any relations that include this property, and remove them/convert them if necessary
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if(junction_list!==undefined) this.action_update_relations(junction_list)
|
|
333
|
-
//in the middle of adding update_relations to this generalized funciton
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
action_update_relations(junction_list){
|
|
339
|
-
|
|
340
|
-
let classes_meta=this.class_cache;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// STEP 1 ============================
|
|
344
|
-
// find all the old junctions which don't appear in the new list
|
|
345
|
-
// add them to a "delete_queue" to schedule them for deletion
|
|
346
|
-
// remove the corresponding targets from the properties they reference
|
|
347
|
-
|
|
348
|
-
let delete_queue=[];
|
|
349
|
-
let old_junction_list=this.get_junctions();
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
for(let junction of old_junction_list){
|
|
354
|
-
|
|
355
|
-
let s1=junction.sides[0]
|
|
356
|
-
let s2=junction.sides[1];
|
|
357
|
-
|
|
358
|
-
let matching=junction_list.find(a=>junction_match(a.sides,junction.sides));
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if(matching==undefined){
|
|
362
|
-
if(s1.prop_id) remove_target(s1,s2);
|
|
363
|
-
if(s2.prop_id) remove_target(s2,s1);
|
|
364
|
-
delete_queue.push(junction);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
//delete queue junctions will need to have the targets removed (or left be, if the prop target doesn't match) from their respective props
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
// STEP 2 ============================
|
|
373
|
-
// a) find all the junctions in the new list that don't appear in the old one
|
|
374
|
-
// b) this means they need to be newly created
|
|
375
|
-
// c) the corresponding targets need to be registered in any referenced properties
|
|
376
|
-
// d) we check if there are any former tables which share at least one of the same properties, and we transfer their properties
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
for(let junction of junction_list){
|
|
380
|
-
let s1=junction.sides[0];
|
|
381
|
-
let s2=junction.sides[1];
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// a)
|
|
385
|
-
let matching=old_junction_list.find(a=>junction_match(a.sides,junction.sides));
|
|
386
|
-
|
|
387
|
-
if(matching==undefined){
|
|
388
|
-
|
|
389
|
-
// b)
|
|
390
|
-
// create the new table
|
|
391
|
-
let j_sides={
|
|
392
|
-
input_1: `${s1.class_id}.${s1.prop_id || ''}`,
|
|
393
|
-
input_2:`${s2.class_id}.${s2.prop_id || ''}`
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
let junction_id=this.create_junction_table(j_sides);
|
|
397
|
-
|
|
398
|
-
// c)
|
|
399
|
-
|
|
400
|
-
if(s1.prop_id) add_target(s1,s2,junction_id);
|
|
401
|
-
if(s2.prop_id) add_target(s2,s1,junction_id);
|
|
402
|
-
|
|
403
|
-
let j_object={
|
|
404
|
-
side_a:j_sides.input_1,
|
|
405
|
-
side_b:j_sides.input_2,
|
|
406
|
-
id:junction_id
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
// d)
|
|
410
|
-
// look for any tables in the delete pile that pair the same classes and have the same property on one side. If any exist, transfer the connections from the old tables
|
|
411
|
-
let partial_matches=delete_queue.filter(a=>partial_junction_match(a.sides,junction.sides));
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
for(let partial of partial_matches){
|
|
415
|
-
let p_object={
|
|
416
|
-
id:partial.id,
|
|
417
|
-
side_a:`${partial.sides[0].class_id}.${partial.sides[0].prop_id || ''}`,
|
|
418
|
-
side_b:`${partial.sides[1].class_id}.${partial.sides[1].prop_id || ''}`
|
|
419
|
-
}
|
|
420
|
-
this.transfer_connections(p_object,j_object);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// STEP 3 ============================
|
|
427
|
-
// delete the tables in the delete pile
|
|
428
|
-
for(let junction of delete_queue){
|
|
429
|
-
this.delete_junction_table(junction.id);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
// STEP 4 ============================
|
|
434
|
-
// submit all prop updates to class_meta
|
|
435
|
-
let modified_classes=Object.entries(classes_meta).filter(a=>a[1].modified).map(a=>a[1]);
|
|
436
|
-
|
|
437
|
-
for(let modified of modified_classes) this.run.save_class_meta.run(JSON.stringify(modified.metadata),modified.id);
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
this.refresh_class_cache();
|
|
441
|
-
|
|
442
|
-
// STEP 5 (TBD) ===================
|
|
443
|
-
// check if the connections in the new tables follow the conditons of their corresponding properties, and remove any that don't pass muster
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// ======================== utility functions ============================
|
|
449
|
-
function side_match(x,y){
|
|
450
|
-
return x.class_id==y.class_id&&x.prop_id==y.prop_id;
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
function junction_match(a,b){
|
|
455
|
-
// match sides completely, order doesn't matter
|
|
456
|
-
return (side_match(a[0],b[0])&&side_match(a[1],b[1])) ||
|
|
457
|
-
(side_match(a[0],b[1])&&side_match(a[1],b[0]));
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function partial_junction_match(a,b){
|
|
462
|
-
|
|
463
|
-
// match both classes
|
|
464
|
-
// match at least one prop
|
|
465
|
-
let a0_match_i=b.findIndex(side=>a[0].class_id==side.class_id);
|
|
466
|
-
let a1_match_i=b.findIndex(side=>a[1].class_id==side.class_id);
|
|
467
|
-
if(a0_match_i>=0&&a1_match_i>=0&&a0_match_i!==a1_match_i){
|
|
468
|
-
return b[a0_match_i].prop_id==a[0].prop_id||
|
|
469
|
-
b[a1_match_i].prop_id==a[1].prop_id
|
|
470
|
-
}else{
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
function remove_target(prop,target){
|
|
478
|
-
let prop_class=classes_meta[prop.class_id];
|
|
479
|
-
|
|
480
|
-
let class_meta=prop_class.metadata;
|
|
481
|
-
|
|
482
|
-
let prop_meta=class_meta.properties.find(a=>a.id==prop.prop_id);
|
|
483
|
-
|
|
484
|
-
if(prop_meta){
|
|
485
|
-
let target_index=prop_meta.targets.findIndex(a=>side_match(a,target));
|
|
486
|
-
if(target_index>=0) prop_meta.targets.splice(target_index,1);
|
|
487
|
-
prop_class.modified=true;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function add_target(prop,target,junction_id){
|
|
492
|
-
let prop_class=classes_meta[prop.class_id]
|
|
493
|
-
let class_meta=prop_class.metadata
|
|
494
|
-
|
|
495
|
-
let prop_meta=class_meta.properties.find(a=>a.id==prop.prop_id);
|
|
496
|
-
|
|
497
|
-
if(prop_meta){
|
|
498
|
-
let obj={
|
|
499
|
-
class_id:target.class_id,
|
|
500
|
-
junction_id:junction_id
|
|
501
|
-
}
|
|
502
|
-
if(target.prop_id!==undefined) obj.prop_id=target.prop_id;
|
|
503
|
-
prop_meta.targets.push(obj)
|
|
504
|
-
|
|
505
|
-
prop_class.modified=true;
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
create_junction_table(sides){
|
|
515
|
-
// adds new record to junction table
|
|
516
|
-
this.db.prepare(`INSERT INTO system_junctionlist (side_a, side_b) VALUES ('${sides.input_1}','${sides.input_2}')`).run();
|
|
517
|
-
|
|
518
|
-
//gets id of new record
|
|
519
|
-
let id=this.db.prepare('SELECT id FROM system_junctionlist ORDER BY id DESC').get().id;
|
|
520
|
-
|
|
521
|
-
// creates table
|
|
522
|
-
this.create_table('junction',id,[
|
|
523
|
-
`"${sides.input_1}" INTEGER`,
|
|
524
|
-
`"${sides.input_2}" INTEGER`
|
|
525
|
-
]);
|
|
526
|
-
|
|
527
|
-
return id;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
transfer_connections(source,target){
|
|
532
|
-
|
|
533
|
-
let source_relations=this.db.prepare(`SELECT * FROM junction_${source.id}`).all();
|
|
534
|
-
|
|
535
|
-
//have to match source sides to target sides in order to determine junction order
|
|
536
|
-
let source_sides=source.side_a.split('.')[0] == target.side_a.split('.')[0]?
|
|
537
|
-
[source.side_a,source.side_b]:[source.side_b,source.side_a];
|
|
538
|
-
|
|
539
|
-
for(let relation of source_relations){
|
|
540
|
-
|
|
541
|
-
this.db.prepare(`INSERT INTO junction_${target.id}("${target.side_a}","${target.side_b}")
|
|
542
|
-
VALUES(${ relation[source_sides[0]]},${ relation[source_sides[1]]})`).run();
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
delete_junction_table(id){
|
|
547
|
-
this.db.prepare(`DELETE FROM system_junctionlist WHERE id = ${id}`).run();
|
|
548
|
-
this.db.prepare(`DROP TABLE junction_${id}`).run();
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
check_conditions(class_id,prop_id,targets,conditions){
|
|
552
|
-
/* conditions={
|
|
553
|
-
max: integer or undefined,
|
|
554
|
-
filters:[
|
|
555
|
-
|
|
556
|
-
],
|
|
557
|
-
rules:[
|
|
558
|
-
|
|
559
|
-
]
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
for now not gonna deal with filters or rules, just going to check max
|
|
563
|
-
*/
|
|
564
|
-
|
|
565
|
-
let cls=this.retrieve_class(class_id);
|
|
566
|
-
let prop_name='user_'+cls.metadata.properties.find(a=>a.id==prop_id).name;
|
|
567
|
-
for(let item of cls.items){
|
|
568
|
-
let prop_values=item[prop_name];
|
|
569
|
-
console.log(prop_name,prop_values);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// this is going to require writing the new retrieval function.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
action_save(){
|
|
578
|
-
if(this.db.inTransaction) this.run.commit.run();
|
|
579
|
-
this.db.close();
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
action_create_item_in_root({type=null,value=''}){
|
|
583
|
-
// this.db.prepare('INSERT INTO system_root VALUES (null)').run();
|
|
584
|
-
this.run.create_item.run({type,value});
|
|
585
|
-
let id=this.db.prepare('SELECT id FROM system_root ORDER BY id DESC').get().id;
|
|
586
|
-
return id;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
action_delete_item_from_root(id){
|
|
590
|
-
this.db.prepare(`DELETE FROM system_root WHERE id = ${id}`).run();
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
action_set_root_item_value(id,value){
|
|
594
|
-
this.db.prepare(`UPDATE system_root set value = ? WHERE id = ?`).run(value,id);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
action_add_row(class_id,class_name){
|
|
598
|
-
//first add new row to root and get id
|
|
599
|
-
if(class_name==undefined) class_name=this.class_cache[class_id].name;
|
|
600
|
-
|
|
601
|
-
// note for future: instead of letting class_id be undefined, locate it
|
|
602
|
-
const root_id=this.action_create_item_in_root({type:class_id!==undefined?'class_'+class_id:null});
|
|
603
|
-
|
|
604
|
-
//get the last item in class table order and use it to get the order for the new item
|
|
605
|
-
const last_order=this.db.prepare(`SELECT system_order FROM [class_${class_name}] ORDER BY system_order DESC`).get();
|
|
606
|
-
const new_order=last_order?last_order.system_order+1:1;
|
|
607
|
-
|
|
608
|
-
this.db.prepare(`INSERT INTO [class_${class_name}] (system_id, system_order) VALUES (${root_id},${new_order})`).run();
|
|
609
|
-
|
|
610
|
-
return root_id;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
action_make_relation(input_1,input_2){
|
|
614
|
-
/* input = {
|
|
615
|
-
class_id: INT,
|
|
616
|
-
prop_id: INT,
|
|
617
|
-
item_id: INT
|
|
618
|
-
}
|
|
619
|
-
*/
|
|
620
|
-
|
|
621
|
-
let sides={
|
|
622
|
-
input_1:`${input_1.class_id}.${input_1.prop_id || ''}`,
|
|
623
|
-
input_2:`${input_2.class_id}.${input_2.prop_id || ''}`
|
|
624
|
-
}
|
|
625
|
-
let junction_id=this.run.match_junction.get(sides)?.id;
|
|
626
|
-
this.db.prepare(`INSERT INTO junction_${junction_id} ("${sides.input_1}", "${sides.input_2}") VALUES (${input_1.item_id},${input_2.item_id})`).run();
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
retrieve_class(class_id,class_name,class_meta){
|
|
632
|
-
if(class_name==undefined){
|
|
633
|
-
let class_data=this.class_cache[class_id];
|
|
634
|
-
// console.log(class_data)
|
|
635
|
-
class_name=class_data.name;
|
|
636
|
-
class_meta=class_data.metadata;
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const class_string=`[class_${class_name}]`;
|
|
640
|
-
|
|
641
|
-
// //joined+added at beginning of the query, built from relations
|
|
642
|
-
const cte_strings=[];
|
|
643
|
-
|
|
644
|
-
// //joined+added near the end of the query, built from relations
|
|
645
|
-
const cte_joins=[];
|
|
646
|
-
|
|
647
|
-
// //joined+added between SELECT and FROM, built from relations
|
|
648
|
-
const relation_selections=[];
|
|
649
|
-
|
|
650
|
-
let relation_properties=class_meta.properties.filter(a=>a.type=='relation');
|
|
651
|
-
|
|
652
|
-
for (let prop of relation_properties){
|
|
653
|
-
const target_selects=[];
|
|
654
|
-
const target_joins=[];
|
|
655
|
-
let p_side=`${class_id}.${prop.id}`;
|
|
656
|
-
let first=prop.targets[0];
|
|
657
|
-
|
|
658
|
-
for(let i = 0; i < prop.targets.length; i++){
|
|
659
|
-
let target=prop.targets[i];
|
|
660
|
-
let t_side=`${target.class_id}.${target.prop_id || ''}`;
|
|
661
|
-
|
|
662
|
-
let target_select=`
|
|
663
|
-
SELECT "${p_side}", json_object('target_id','${target.class_id}','id',"${t_side}") AS json_object
|
|
664
|
-
FROM junction_${target.junction_id}`
|
|
665
|
-
target_selects.push(target_select);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const cte=`[${prop.id}_cte] AS (
|
|
669
|
-
SELECT "${p_side}", ('[' || GROUP_CONCAT(json_object,',') || ']') AS [user_${prop.name}]
|
|
670
|
-
FROM (${target_selects.join(' UNION ')})
|
|
671
|
-
GROUP BY "${p_side}"
|
|
672
|
-
)`
|
|
673
|
-
|
|
674
|
-
cte_strings.push(cte);
|
|
675
|
-
relation_selections.push(`[${prop.id}_cte].[user_${prop.name}]`);
|
|
676
|
-
cte_joins.push(`LEFT JOIN [${prop.id}_cte] ON [${prop.id}_cte]."${p_side}" = ${class_string}.system_id`)
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
let orderby=`ORDER BY ${class_string}.system_order`;
|
|
681
|
-
|
|
682
|
-
let query=`
|
|
683
|
-
${cte_strings.length>0?"WITH "+cte_strings.join(','):''}
|
|
684
|
-
SELECT [class_${class_name}].* ${cte_strings.length>0?', '+relation_selections.join(`, `):''}
|
|
685
|
-
FROM [class_${class_name}]
|
|
686
|
-
${cte_joins.join(' ')}
|
|
687
|
-
${orderby}`;
|
|
688
|
-
|
|
689
|
-
let items=this.db.prepare(query).all();
|
|
690
|
-
|
|
691
|
-
let stringified_properties=class_meta.properties.filter(a=>a.type=='relation'||a.conditions?.max>1);
|
|
692
|
-
items.map(row=>{
|
|
693
|
-
for (let prop of stringified_properties){
|
|
694
|
-
row['user_'+prop.name]=JSON.parse(row['user_'+prop.name]);
|
|
695
|
-
}
|
|
696
|
-
})
|
|
697
|
-
return {
|
|
698
|
-
items,
|
|
699
|
-
metadata:class_meta,
|
|
700
|
-
name:class_name
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
retrieve_all_classes(){
|
|
705
|
-
const classes_data=this.run.get_all_classes.all();
|
|
706
|
-
// console.log(classes)
|
|
707
|
-
let classes=[];
|
|
708
|
-
for(let cls of classes_data){
|
|
709
|
-
classes.push(this.retrieve_class(cls.id,cls.name,JSON.parse(cls.metadata)));
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
return classes;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
retrieve_windows(){
|
|
716
|
-
let windows=this.run.get_windows.all();
|
|
717
|
-
windows.map(a=>a.metadata=JSON.parse(a.metadata));
|
|
718
|
-
return windows;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
retrieve_workspace_contents(id){
|
|
722
|
-
// get the workspace table
|
|
723
|
-
let blocks=this.db.prepare(`SELECT * FROM workspace_${id}`).all();
|
|
724
|
-
for(let block of blocks) block.properties=JSON.parse(block.properties);
|
|
725
|
-
// get any relevant root items
|
|
726
|
-
let items=this.db.prepare(`SELECT system_root.*
|
|
727
|
-
FROM system_root
|
|
728
|
-
LEFT JOIN workspace_${id}
|
|
729
|
-
ON system_root.id = workspace_${id}.concept_id
|
|
730
|
-
WHERE workspace_${id}.type = 'item';
|
|
731
|
-
`).all();
|
|
732
|
-
// get any relevant classes (going to hold off from this for now)
|
|
733
|
-
|
|
734
|
-
return {
|
|
735
|
-
blocks,
|
|
736
|
-
items
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
action_config_window(type,open,meta={pos:[null,null], size:[1000,700]},id){
|
|
741
|
-
if(id!==undefined){
|
|
742
|
-
this.run.update_window.run({
|
|
743
|
-
id,
|
|
744
|
-
open,
|
|
745
|
-
type,
|
|
746
|
-
meta:JSON.stringify(meta)
|
|
747
|
-
})
|
|
748
|
-
}else{
|
|
749
|
-
|
|
750
|
-
let id=this.create_workspace(open,meta)
|
|
751
|
-
|
|
752
|
-
return id;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
create_workspace(open,meta){
|
|
759
|
-
|
|
760
|
-
this.run.create_window.run({
|
|
761
|
-
type:'workspace',
|
|
762
|
-
open,
|
|
763
|
-
meta:JSON.stringify(meta)
|
|
764
|
-
})
|
|
765
|
-
|
|
766
|
-
let id=this.get_latest_id('system_windows')
|
|
767
|
-
|
|
768
|
-
this.create_table('workspace',id,[
|
|
769
|
-
'block_id INTEGER NOT NULL PRIMARY KEY',
|
|
770
|
-
'type TEXT',
|
|
771
|
-
'properties TEXT',
|
|
772
|
-
'concept_id INTEGER'
|
|
773
|
-
]);
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
return id;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
action_create_workspace_block({workspace_id,type,properties,concept_id}){
|
|
781
|
-
// should return block id
|
|
782
|
-
this.db.prepare(`INSERT INTO workspace_${workspace_id}(type,properties,concept_id) VALUES (@type,@properties,@concept_id)`).run({
|
|
783
|
-
type,
|
|
784
|
-
properties:JSON.stringify(properties),
|
|
785
|
-
concept_id
|
|
786
|
-
});
|
|
787
|
-
let block_id=this.db.prepare(`SELECT block_id FROM workspace_${workspace_id} ORDER BY block_id DESC`).get().block_id;
|
|
788
|
-
return block_id;
|
|
789
|
-
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
action_remove_workspace_block({workspace_id,block_id}){
|
|
793
|
-
this.db.prepare(`DELETE FROM workspace_${workspace_id} WHERE block_id = ${block_id}`).run();
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
action_create_and_add_to_workspace(workspace_id,blocktype,block_properties,concept_data){
|
|
797
|
-
let concept_id;
|
|
798
|
-
// concept creation
|
|
799
|
-
switch(blocktype){
|
|
800
|
-
case 'item':
|
|
801
|
-
let {
|
|
802
|
-
value:item_value,
|
|
803
|
-
type:item_type
|
|
804
|
-
} = concept_data;
|
|
805
|
-
concept_id=this.action_create_item_in_root({type:item_type,value:item_value});
|
|
806
|
-
break;
|
|
807
|
-
// add cases for class and anything else in the future
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
let block_id=this.action_create_workspace_block({
|
|
811
|
-
workspace_id,
|
|
812
|
-
type:blocktype,
|
|
813
|
-
properties:block_properties,
|
|
814
|
-
concept_id
|
|
815
|
-
})
|
|
816
|
-
|
|
817
|
-
return {
|
|
818
|
-
concept_id,
|
|
819
|
-
block_id
|
|
820
|
-
}
|
|
821
|
-
// should return the block id and item id
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
action_remove_from_workspace_and_delete(workspace_id,block_id,blocktype,concept_id){
|
|
825
|
-
this.action_remove_workspace_block({workspace_id,block_id});
|
|
826
|
-
switch(blocktype){
|
|
827
|
-
case 'item':
|
|
828
|
-
this.action_delete_item_from_root(concept_id);
|
|
829
|
-
break;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
// export default Project;
|
|
838
|
-
module.exports=Project;
|
|
839
|
-
|
|
840
|
-
// export default Project;
|