@saltcorn/server 0.9.4-beta.1 → 0.9.4-beta.11

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.
@@ -1,351 +0,0 @@
1
- var relationHelpers = (() => {
2
- // internal helper to build an object, structured for the picker
3
- const buildLayers = (path, pathArr, result) => {
4
- let currentLevel = result;
5
- for (const relation of pathArr) {
6
- if (relation.type === "Inbound") {
7
- const existing = currentLevel.inboundKeys.find(
8
- (key) => key.name === relation.key && key.table === relation.table
9
- );
10
- if (existing) {
11
- currentLevel = existing;
12
- } else {
13
- const nextLevel = {
14
- name: relation.key,
15
- table: relation.table,
16
- inboundKeys: [],
17
- fkeys: [],
18
- };
19
- currentLevel.inboundKeys.push(nextLevel);
20
- currentLevel = nextLevel;
21
- }
22
- } else if (relation.type === "Foreign") {
23
- const existing = currentLevel.fkeys.find(
24
- (key) => key.name === relation.key
25
- );
26
- if (existing) {
27
- currentLevel = existing;
28
- } else {
29
- const nextLevel = {
30
- name: relation.key,
31
- table: relation.table,
32
- inboundKeys: [],
33
- fkeys: [],
34
- };
35
- currentLevel.fkeys.push(nextLevel);
36
- currentLevel = nextLevel;
37
- }
38
- } else if (relation.type === "Independent") {
39
- currentLevel.fkeys.push({
40
- name: "None (no relation)",
41
- table: relation.table,
42
- inboundKeys: [],
43
- fkeys: [],
44
- relPath: path,
45
- });
46
- } else if (relation.type === "Own") {
47
- currentLevel.fkeys.push({
48
- name: "Same table",
49
- table: "",
50
- inboundKeys: [],
51
- fkeys: [],
52
- relPath: path,
53
- });
54
- }
55
- }
56
- currentLevel.relPath = path;
57
- };
58
-
59
- /**
60
- * build an array of relation objects from a path string
61
- * '.' stands for no relation
62
- * '.table' stands for same table
63
- * @param {*} path relation path string separated by '.', the first token is the source table
64
- * @param {*} tableNameCache an object with table name as key and table object as value
65
- * @returns
66
- */
67
- const parseRelationPath = (path, tableNameCache) => {
68
- if (path === ".")
69
- return [{ type: "Independent", table: "None (no relation)" }];
70
- const tokens = path.split(".");
71
- if (tokens.length === 2)
72
- return [{ type: "Own", table: `${tokens[1]} (same table)` }];
73
- else if (tokens.length >= 3) {
74
- const result = [];
75
- let currentTbl = tokens[1];
76
- for (const relation of tokens.slice(2)) {
77
- if (relation.indexOf("$") > 0) {
78
- const [inboundTbl, inboundKey] = relation.split("$");
79
- result.push({ type: "Inbound", table: inboundTbl, key: inboundKey });
80
- currentTbl = inboundTbl;
81
- } else {
82
- const srcTbl = tableNameCache[currentTbl];
83
- const fk = srcTbl.foreign_keys.find((fk) => fk.name === relation);
84
- if (fk) {
85
- const targetTbl = tableNameCache[fk.reftable_name];
86
- result.push({
87
- type: "Foreign",
88
- table: targetTbl.name,
89
- key: relation,
90
- });
91
- currentTbl = targetTbl.name;
92
- }
93
- }
94
- }
95
- return result;
96
- }
97
- };
98
-
99
- /**
100
- * build an array of relation objects from a legacy relation
101
- * @param {string} type relation type (ChildList, Independent, Own, OneToOneShow, ParentShow)
102
- * @param {string} rest rest of the legaccy relation
103
- * @param {string} parentTbl source table
104
- * @returns
105
- */
106
- const parseLegacyRelation = (type, rest, parentTbl) => {
107
- switch (type) {
108
- case "ChildList": {
109
- const path = rest ? rest.split(".") : [];
110
- if (path.length === 3) {
111
- const [viewName, table, key] = path;
112
- return [
113
- {
114
- type: "Inbound",
115
- table,
116
- key,
117
- },
118
- ];
119
- } else if (path.length === 5) {
120
- const [viewName, thrTbl, thrTblFkey, fromTbl, fromTblFkey] = path;
121
- return [
122
- {
123
- type: "Inbound",
124
- table: thrTbl,
125
- key: thrTblFkey,
126
- },
127
- {
128
- type: "Inbound",
129
- table: fromTbl,
130
- key: fromTblFkey,
131
- },
132
- ];
133
- }
134
- break;
135
- }
136
- case "Independent": {
137
- return [{ type: "Independent", table: "None (no relation)" }];
138
- }
139
- case "Own": {
140
- return [{ type: "Own", table: `${parentTbl} (same table)` }];
141
- }
142
- case "OneToOneShow": {
143
- const tokens = rest ? rest.split(".") : [];
144
- if (tokens.length !== 3) break;
145
- const [viewname, relatedTbl, fkey] = tokens;
146
- return [{ type: "Inbound", table: relatedTbl, key: fkey }];
147
- }
148
- case "ParentShow": {
149
- const tokens = rest ? rest.split(".") : [];
150
- if (tokens.length !== 3) break;
151
- const [viewname, parentTbl, fkey] = tokens;
152
- return [{ type: "Foreign", table: parentTbl, key: fkey }];
153
- }
154
- }
155
- return [];
156
- };
157
-
158
- const ViewDisplayType = {
159
- ROW_REQUIRED: "ROW_REQUIRED",
160
- NO_ROW_LIMIT: "NO_ROW_LIMIT",
161
- INVALID: "INVALID",
162
- };
163
-
164
- /**
165
- * prepare the relations finder
166
- * @param {object} tablesCache
167
- * @param {object} allViews
168
- * @param {number} maxDepth
169
- */
170
- const RelationsFinder = function (tablesCache, allViews, maxDepth) {
171
- this.maxDepth = +maxDepth;
172
- if (isNaN(this.maxDepth)) {
173
- console.log(`maxDepth '${maxDepth}' is not a number, set to 6`);
174
- this.maxDepth = 6;
175
- }
176
- this.allViews = allViews;
177
- const { tableIdCache, tableNameCache, fieldCache } = tablesCache;
178
- this.tableIdCache = tableIdCache;
179
- this.tableNameCache = tableNameCache;
180
- this.fieldCache = fieldCache;
181
- };
182
-
183
- /**
184
- * find relations between a source table and a subview
185
- * @param {string} sourceTblName
186
- * @param {string} subView
187
- * @param {string[]} excluded
188
- * @returns {object} {paths: string[], layers: object}
189
- */
190
- RelationsFinder.prototype.findRelations = function (
191
- sourceTblName,
192
- subView,
193
- excluded
194
- ) {
195
- let paths = [];
196
- const layers = { table: sourceTblName, inboundKeys: [], fkeys: [] };
197
- try {
198
- const view = this.allViews.find((v) => v.name === subView);
199
- if (!view) throw new Error(`The view ${subView} does not exist`);
200
- if (excluded?.find((e) => e === view.viewtemplate)) {
201
- console.log(`view ${subView} is excluded`);
202
- return { paths, layers };
203
- }
204
- switch (view.display_type) {
205
- case ViewDisplayType.ROW_REQUIRED:
206
- paths = this.singleRelationPaths(sourceTblName, subView, excluded);
207
- break;
208
- case ViewDisplayType.NO_ROW_LIMIT:
209
- paths = this.multiRelationPaths(sourceTblName, subView, excluded);
210
- break;
211
- default:
212
- throw new Error(
213
- `view ${subView}: The displayType (${view.display_type}) is not valid`
214
- );
215
- }
216
- for (const path of paths)
217
- buildLayers(path, parseRelationPath(path, this.tableNameCache), layers);
218
- } catch (error) {
219
- console.log(error);
220
- } finally {
221
- return { paths, layers };
222
- }
223
- };
224
-
225
- /**
226
- * find relations between a source table and a subview with single row display (e.g. show)
227
- * @param {string} sourceTblName
228
- * @param {string} subView
229
- * @param {string[]} excluded
230
- * @returns
231
- */
232
- RelationsFinder.prototype.singleRelationPaths = function (
233
- sourceTblName,
234
- subView,
235
- excluded
236
- ) {
237
- const result = [];
238
- const subViewObj = this.allViews.find((v) => v.name === subView);
239
- if (!subViewObj) throw new Error(`The view ${subView} does not exist`);
240
- if (excluded?.find((e) => e === subViewObj.viewtemplate)) {
241
- console.log(`view ${subView} is excluded`);
242
- return result;
243
- }
244
- const sourceTbl = this.tableNameCache[sourceTblName];
245
- if (!sourceTbl)
246
- throw new Error(`The table ${sourceTblName} does not exist`);
247
- // 1. parent relations
248
- const parentRelations = sourceTbl.foreign_keys;
249
- if (sourceTbl.id === subViewObj.table_id) result.push(`.${sourceTblName}`);
250
- for (const relation of parentRelations) {
251
- const targetTbl = this.tableNameCache[relation.reftable_name];
252
- if (!targetTbl)
253
- throw new Error(`The table ${relation.reftable_name} does not exist`);
254
- if (targetTbl.id === subViewObj.table_id)
255
- result.push(`.${sourceTblName}.${relation.name}`);
256
- }
257
- // 2. OneToOneShow
258
- const uniqueFksToSrc = (this.fieldCache[sourceTblName] || []).filter(
259
- (f) => f.is_unique
260
- );
261
- for (const relation of uniqueFksToSrc) {
262
- const targetTbl = this.tableIdCache[relation.table_id];
263
- if (!targetTbl)
264
- throw new Error(`The table ${relation.table_id} does not exist`);
265
- if (targetTbl.id === subViewObj.table_id)
266
- result.push(`.${sourceTblName}.${targetTbl.name}$${relation.name}`);
267
- }
268
- // 3. inbound_self_relations
269
- const srcFks = sourceTbl.foreign_keys;
270
- for (const fkToSrc of uniqueFksToSrc) {
271
- const refTable = this.tableIdCache[fkToSrc.table_id];
272
- if (!refTable)
273
- throw new Error(`The table ${fkToSrc.table_id} does not exist`);
274
- const fromSrcToRef = srcFks.filter(
275
- (field) => field.reftable_name === refTable.name
276
- );
277
- for (const toRef of fromSrcToRef) {
278
- if (fkToSrc.reftable_name === sourceTblName)
279
- result.push(`.${sourceTblName}.${toRef.name}.${fkToSrc.name}`);
280
- }
281
- }
282
- return result;
283
- };
284
-
285
- /**
286
- * find relations between a source table and a subview with multiple rows display (e.g. list)
287
- * @param {string} sourceTblName
288
- * @param {string} subView
289
- * @param {string[]} excluded
290
- * @returns
291
- */
292
- RelationsFinder.prototype.multiRelationPaths = function (
293
- sourceTblName,
294
- subView,
295
- excluded
296
- ) {
297
- const result = ["."]; // none no relation
298
- const subViewObj = this.allViews.find((v) => v.name === subView);
299
- if (!subViewObj) throw new Error(`The view ${subView} does not exist`);
300
- if (excluded?.find((e) => e === subViewObj.viewtemplate)) {
301
- console.log(`view ${subView} is excluded`);
302
- return result;
303
- }
304
- const sourceTbl = this.tableNameCache[sourceTblName];
305
- if (!sourceTbl)
306
- throw new Error(`The table ${sourceTblName} does not exist`);
307
- if (sourceTbl.id === subViewObj.table_id) result.push(`.${sourceTblName}`);
308
- const searcher = (current, path, level, visited) => {
309
- if (level > this.maxDepth) return;
310
- const visitedFkCopy = new Set(visited);
311
- for (const fk of current.foreign_keys) {
312
- if (visitedFkCopy.has(fk.id)) continue;
313
- visitedFkCopy.add(fk.id);
314
- const target = this.tableNameCache[fk.reftable_name];
315
- if (!target)
316
- throw new Error(`The table ${fk.reftable_name} does not exist`);
317
- const newPath = `${path}.${fk.name}`;
318
- if (target.id === subViewObj.table_id) result.push(newPath);
319
- searcher(target, newPath, level + 1, visitedFkCopy);
320
- }
321
-
322
- const visitedInboundCopy = new Set(visited);
323
- for (const inbound of this.fieldCache[current.name] || []) {
324
- if (visitedInboundCopy.has(inbound.id)) continue;
325
- visitedInboundCopy.add(inbound.id);
326
- const target = this.tableIdCache[inbound.table_id];
327
- if (!target)
328
- throw new Error(`The table ${inbound.table_id} does not exist`);
329
- const newPath = `${path}.${target.name}$${inbound.name}`;
330
- if (target.id === subViewObj.table_id) result.push(newPath);
331
- searcher(target, newPath, level + 1, visitedInboundCopy);
332
- }
333
- };
334
- const path = `.${sourceTblName}`;
335
- const visited = new Set();
336
- searcher(sourceTbl, path, 0, visited);
337
- return result;
338
- };
339
-
340
- return {
341
- RelationsFinder: RelationsFinder,
342
- ViewDisplayType: ViewDisplayType,
343
- parseRelationPath: parseRelationPath,
344
- parseLegacyRelation: parseLegacyRelation,
345
- };
346
- })();
347
-
348
- // make the module available for jest with react
349
- if (typeof process !== "undefined" && process.env?.NODE_ENV === "test") {
350
- module.exports = relationHelpers;
351
- }