@magda/org-tree 1.2.1 → 2.0.0-alpha.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.
@@ -92,7 +92,7 @@ return /******/ (function(modules) { // webpackBootstrap
92
92
  /******/
93
93
  /******/
94
94
  /******/ // Load entry module and return exports
95
- /******/ return __webpack_require__(__webpack_require__.s = 9);
95
+ /******/ return __webpack_require__(__webpack_require__.s = 13);
96
96
  /******/ })
97
97
  /************************************************************************/
98
98
  /******/ ([
@@ -102,977 +102,509 @@ return /******/ (function(modules) { // webpackBootstrap
102
102
  /* 3 */,
103
103
  /* 4 */,
104
104
  /* 5 */,
105
- /* 6 */,
106
- /* 7 */,
107
- /* 8 */,
108
- /* 9 */
109
- /***/ (function(module, exports, __webpack_require__) {
105
+ /* 6 */
106
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
110
107
 
111
108
  "use strict";
112
-
113
- var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) {
114
- function adopt(value) {return value instanceof P ? value : new P(function (resolve) {resolve(value);});}
115
- return new (P || (P = Promise))(function (resolve, reject) {
116
- function fulfilled(value) {try {step(generator.next(value));} catch (e) {reject(e);}}
117
- function rejected(value) {try {step(generator["throw"](value));} catch (e) {reject(e);}}
118
- function step(result) {result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);}
119
- step((generator = generator.apply(thisArg, _arguments || [])).next());
120
- });
121
- };
122
- var __importDefault = this && this.__importDefault || function (mod) {
123
- return mod && mod.__esModule ? mod : { "default": mod };
124
- };
125
- Object.defineProperty(exports, "__esModule", { value: true });
126
- exports.NodeNotFoundError = void 0;
127
- const lodash_1 = __importDefault(__webpack_require__(10));
128
- const tsmonad_1 = __webpack_require__(12);
129
- const textTree = __webpack_require__(13);
130
- class NodeNotFoundError extends Error {}
131
-
132
- exports.NodeNotFoundError = NodeNotFoundError;
133
- function isNonEmptyArray(v) {
134
- if (!v || !lodash_1.default.isArray(v) || !v.length)
135
- return false;
136
- return true;
137
- }
138
- const INVALID_CHAR_REGEX = /[^a-z_\d]/i;
139
- function isValidSqlIdentifier(id) {
140
- if (INVALID_CHAR_REGEX.test(id))
141
- return false;
142
- return true;
143
- }
144
- class NestedSetModelQueryer {
145
- /**
146
- * Creates an instance of NestedSetModelQueryer.
147
- * @param {pg.Pool} dbPool
148
- * @param {string} tableName
149
- * @param {string[]} [defaultSelectFieldList=null] default select fields; If null, all fields (i.e. `SELECT "id", "name"`) will be returned
150
- * @memberof NestedSetModelQueryer
151
- */
152
- constructor(dbPool, tableName, defaultSelectFieldList = null, defaultInsertFieldList = null) {
153
- /**
154
- * default select fields if [], all fields (i.e. `SELECT "id", "name"`) will be returned
155
- *
156
- * @type {string[]}
157
- * @memberof NestedSetModelQueryer
158
- */
159
- this.defaultSelectFieldList = ["id", "name"];
160
- /**
161
- * Default field list that will be used when insert nodes into tree.
162
- * By default, only `name` field will be saved to database
163
- * e.g. If your tree nodes have three properties (besides `id`, `left`, `right` --- they auto generated):
164
- * - name
165
- * - description
166
- * - fullName
167
- *
168
- * Then you should set `defaultInsertFieldList` to ["name", "description", "fullName"]
169
- *
170
- * @type {string[]}
171
- * @memberof NestedSetModelQueryer
172
- */
173
- this.defaultInsertFieldList = ["name"];
174
- if (!dbPool)
175
- throw new Error("dbPool cannot be empty!");
176
- if (!tableName)
177
- throw new Error("tableName cannot be empty!");
178
- if (!isValidSqlIdentifier(tableName)) {
179
- throw new Error(`tableName: ${tableName} contains invalid characters!`);
109
+ __webpack_require__.r(__webpack_exports__);
110
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SQLSyntax", function() { return SQLSyntax; });
111
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return SQLSyntax; });
112
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "escapeIdentifier", function() { return escapeIdentifier; });
113
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getTableColumnName", function() { return getTableColumnName; });
114
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sqls", function() { return sqls; });
115
+ /**
116
+ * An class represents a SQL query piece / fragment.
117
+ * The provided `sqls` tagged template literals will output an instance of SQLSyntax class.
118
+ * All properties of SQLSyntax are readonly. Thus is immutable.
119
+ * An instance of SQLSyntax class will record
120
+ *
121
+ * @class SQLSyntax
122
+ */
123
+ class SQLSyntax {
124
+ constructor(isEmpty, rawSqlParts, rawValues, sqlParts, values) {
125
+ this.values = [];
126
+ this.sqlParts = [];
127
+ this.isEmpty = isEmpty ? true : false;
128
+ if (this.isEmpty) {
129
+ this.isEmpty = true;
130
+ return;
131
+ }
132
+ if (!(rawSqlParts === null || rawSqlParts === void 0 ? void 0 : rawSqlParts.length) && !(sqlParts === null || sqlParts === void 0 ? void 0 : sqlParts.length)) {
133
+ throw new Error("SQLSyntax: `rawSqlParts` & `sqlParts` can't be both empty for a non-empty SQLSyntax.");
134
+ }
135
+ if (!(rawSqlParts === null || rawSqlParts === void 0 ? void 0 : rawSqlParts.length)) {
136
+ // allow to set this.sqlParts & this.values directly.
137
+ // an interface for SQLSyntax.createUnsafely
138
+ this.sqlParts.push(...sqlParts);
139
+ if (values === null || values === void 0 ? void 0 : values.length) {
140
+ this.values.push(...values);
141
+ }
142
+ return;
143
+ }
144
+ this.rawSqlParts = rawSqlParts;
145
+ this.rawValues = rawValues;
146
+ // in case it was called with empty value (e.g. undefined ) `rawValues` parameter
147
+ rawValues = (rawValues === null || rawValues === void 0 ? void 0 : rawValues.length) ? rawValues : [];
148
+ if (rawSqlParts.length !== rawValues.length + 1) {
149
+ throw new Error("SQLSyntax: `rawSqlParts.length` should be equal to `rawValues.length` + 1.");
150
+ }
151
+ // if (rawSqlParts.length === 1) {
152
+ // this.sqlParts.push(rawSqlParts[0]);
153
+ // return;
154
+ // }
155
+ rawSqlParts.forEach((part, idx) => {
156
+ if (idx === 0) {
157
+ this.sqlParts.push(part);
158
+ }
159
+ else {
160
+ const previousValue = rawValues === null || rawValues === void 0 ? void 0 : rawValues[idx - 1];
161
+ if (previousValue instanceof SQLSyntax) {
162
+ this.sqlParts[this.sqlParts.length - 1] += part;
163
+ }
164
+ else {
165
+ this.sqlParts.push(part);
166
+ }
167
+ }
168
+ if (idx < rawValues.length) {
169
+ const currentVal = rawValues[idx];
170
+ if (currentVal instanceof SQLSyntax) {
171
+ if (!currentVal.isEmpty) {
172
+ this.sqlParts[this.sqlParts.length - 1] +=
173
+ currentVal.sqlParts[0];
174
+ this.values.push(...currentVal.values);
175
+ this.sqlParts.push(...currentVal.sqlParts.slice(1));
176
+ }
177
+ }
178
+ else {
179
+ this.values.push(currentVal);
180
+ }
181
+ }
182
+ });
180
183
  }
181
- this.pool = dbPool;
182
- this.tableName = tableName;
183
- if (defaultSelectFieldList) {
184
- if (!lodash_1.default.isArray(defaultSelectFieldList))
185
- throw new Error("defaultSelectFieldList should be an array");
186
- this.defaultSelectFieldList = defaultSelectFieldList;
184
+ toQuery() {
185
+ let query;
186
+ if (SQLSyntax.customToQueryFunc) {
187
+ query = SQLSyntax.customToQueryFunc(this);
188
+ }
189
+ else {
190
+ query = SQLSyntax.defaultToQueryFunc(this);
191
+ }
192
+ if (SQLSyntax.isDebugMode) {
193
+ const [sqlQuery, params] = query;
194
+ console.log("SQL Query: ", sqlQuery);
195
+ console.log("SQL Query params: ", params);
196
+ }
197
+ return query;
187
198
  }
188
- if (defaultSelectFieldList) {
189
- if (!lodash_1.default.isArray(defaultSelectFieldList))
190
- throw new Error("defaultSelectFieldList should be an array");
191
- this.defaultSelectFieldList = defaultSelectFieldList;
199
+ static defaultToQueryFunc(sql) {
200
+ if (sql.isEmpty) {
201
+ return ["", []];
202
+ }
203
+ const finalParts = [];
204
+ sql.values.forEach((value, idx) => {
205
+ finalParts.push(sql.sqlParts[idx], `$${idx + 1}`);
206
+ });
207
+ if (sql.sqlParts.length > sql.values.length) {
208
+ finalParts.push(...sql.sqlParts.slice(sql.values.length, sql.sqlParts.length));
209
+ }
210
+ return [finalParts.join(""), sql.values];
192
211
  }
193
- if (defaultInsertFieldList) {
194
- if (!lodash_1.default.isArray(defaultInsertFieldList))
195
- throw new Error("defaultInsertFieldList should be an array");
196
- this.defaultInsertFieldList = defaultInsertFieldList;
212
+ append(syntax) {
213
+ return SQLSyntax.sqls `${this} ${syntax}`;
197
214
  }
198
- }
199
- selectFields(tableAliasOrName = "", fields = null) {
200
- const fieldList = isNonEmptyArray(fields) ?
201
- fields :
202
- this.defaultSelectFieldList;
203
- if (!isNonEmptyArray(fieldList)) {
204
- return "*";
215
+ groupBy(...columns) {
216
+ columns = SQLSyntax.filterEmpty(columns);
217
+ if (!(columns === null || columns === void 0 ? void 0 : columns.length)) {
218
+ return this;
219
+ }
220
+ else {
221
+ return SQLSyntax.sqls `${this} GROUP BY ${SQLSyntax.csv(...columns)}`;
222
+ }
205
223
  }
206
- if (!isValidSqlIdentifier(tableAliasOrName)) {
207
- throw new Error(`'tableAliasOrName' ${tableAliasOrName} contains invalid characters.`);
224
+ having(condition) {
225
+ return SQLSyntax.sqls `${this} HAVING ${condition}`;
208
226
  }
209
- // --- do not double quote `tableAliasOrName`
210
- // --- or you will get missing FROM-clause entry for table error
211
- return fieldList.
212
- map(f => {
213
- if (!isValidSqlIdentifier(f)) {
214
- throw new Error(`Field name ${f} contains invalid characters.`);
215
- }
216
- return tableAliasOrName === "" ?
217
- `"${f}"` :
218
- `${tableAliasOrName}."${f}"`;
219
- }).
220
- join(", ");
221
- }
222
- /**
223
- * Get nodes by name
224
- * You hardly need this one --- only for write test case (you can get a id from name)
225
- *
226
- * @param {string} name
227
- * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
228
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
229
- * @returns {Promise<NodeRecord[]>}
230
- * @memberof NestedSetModelQueryer
231
- */
232
- getNodes(nodesQuery = {}, fields = null, client = null) {
233
- return __awaiter(this, void 0, void 0, function* () {
234
- const getParamPlaceholder = (() => {
235
- let currentPlaceholder = 1;
236
- return () => currentPlaceholder++;
237
- })();
238
- const clauses = [
239
- nodesQuery.name && {
240
- sql: `"name" = $${getParamPlaceholder()}`,
241
- values: [nodesQuery.name] },
242
-
243
- nodesQuery.leafNodesOnly && {
244
- sql: `"left" = ( "right" - 1 )` }].
245
-
246
- filter(x => !!x);
247
- const whereClause = clauses.length > 0 ?
248
- `WHERE ${clauses.map(({ sql }) => sql).join(" AND ")}` :
249
- "";
250
- const query = `SELECT ${this.selectFields("", fields)} FROM "${this.tableName}" ${whereClause}`;
251
- const result = yield (client ? client : this.pool).query(query, lodash_1.default.flatMap(clauses, ({ values }) => values || []));
252
- if (!result || !result.rows || !result.rows.length)
253
- return [];
254
- return result.rows;
255
- });
256
- }
257
- /**
258
- *
259
- * Get a node by its id
260
- * @param {string} id
261
- * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
262
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
263
- * @returns {Promise<NodeRecord>}
264
- * @memberof NestedSetModelQueryer
265
- */
266
- getNodeById(id, fields = null, client = null) {
267
- return __awaiter(this, void 0, void 0, function* () {
268
- const result = yield (client ?
269
- client :
270
- this.pool).query(`SELECT ${this.selectFields("", fields)} FROM "${this.tableName}" WHERE "id" = $1`, [id]);
271
- if (!result || !result.rows || !result.rows.length)
272
- return tsmonad_1.Maybe.nothing();
273
- return tsmonad_1.Maybe.just(result.rows[0]);
274
- });
275
- }
276
- /**
277
- * Get the root node of the tree
278
- * Return null if empty tree
279
- *
280
- * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
281
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
282
- * @returns {Promise<NodeRecord>}
283
- * @memberof NestedSetModelQueryer
284
- */
285
- getRootNode(fields = null, client = null) {
286
- return __awaiter(this, void 0, void 0, function* () {
287
- const result = yield (client ? client : this.pool).query(`SELECT ${this.selectFields("", fields)} FROM "${this.tableName}" WHERE "left" = 1`);
288
- if (!result || !result.rows || !result.rows.length)
289
- return tsmonad_1.Maybe.nothing();
290
- return tsmonad_1.Maybe.just(result.rows[0]);
291
- });
292
- }
293
- /**
294
- * Get All children of a given node
295
- * (including immediate children and children of immediate children etc.)
296
- * If the node has no child (i.e. a leaf node), an empty array will be returned
297
- *
298
- * @param {string} parentNodeId
299
- * @param {boolean} [includeMyself=false]
300
- * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
301
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
302
- * @returns {Promise<NodeRecord[]>}
303
- * @memberof NestedSetModelQueryer
304
- */
305
- getAllChildren(parentNodeId, includeMyself = false, fields = null, client = null) {
306
- return __awaiter(this, void 0, void 0, function* () {
307
- const tbl = this.tableName;
308
- const result = yield (client ? client : this.pool).query(`SELECT ${this.selectFields("Children", fields)}
309
- FROM "${tbl}" AS Parents, "${tbl}" AS Children
310
- WHERE Children."left" ${includeMyself ? ">=" : ">"} Parents."left" AND Children."left" ${includeMyself ? "<=" : "<"} Parents."right" AND Parents."id" = $1`, [parentNodeId]);
311
- if (!result || !result.rows || !result.rows.length)
312
- return [];
313
- return result.rows;
314
- });
315
- }
316
- /**
317
- * Get All parents of a given node
318
- * (including immediate parent and parents of immediate parent etc.)
319
- * If the node has no parent (i.e. a root node), an empty array will be returned (unless `includeMyself` = true)
227
+ orderBy(...columns) {
228
+ columns = SQLSyntax.filterEmpty(columns);
229
+ if (!(columns === null || columns === void 0 ? void 0 : columns.length)) {
230
+ return this;
231
+ }
232
+ else {
233
+ return SQLSyntax.sqls `${this} ORDER BY ${SQLSyntax.csv(...columns)}`;
234
+ }
235
+ }
236
+ asc() {
237
+ return SQLSyntax.sqls `${this} ASC`;
238
+ }
239
+ desc() {
240
+ return SQLSyntax.sqls `${this} DESC`;
241
+ }
242
+ limit(n) {
243
+ return SQLSyntax.sqls `${this} LIMIT ${n}`;
244
+ }
245
+ offset(n) {
246
+ return SQLSyntax.sqls `${this} OFFSET ${n}`;
247
+ }
248
+ where(conditions) {
249
+ if (!conditions || conditions.isEmpty) {
250
+ return this;
251
+ }
252
+ return SQLSyntax.sqls `${this} WHERE ${conditions}`;
253
+ }
254
+ and(condition) {
255
+ if (!condition || condition.isEmpty) {
256
+ return this;
257
+ }
258
+ return SQLSyntax.sqls `${this} AND ${condition}`;
259
+ }
260
+ or(condition) {
261
+ if (!condition || condition.isEmpty) {
262
+ return this;
263
+ }
264
+ return SQLSyntax.sqls `${this} OR ${condition}`;
265
+ }
266
+ roundBracket() {
267
+ if (this.isEmpty) {
268
+ return this;
269
+ }
270
+ return SQLSyntax.sqls `(${this})`;
271
+ }
272
+ eq(value) {
273
+ if (typeof value === "undefined" || value == null) {
274
+ return SQLSyntax.sqls `${this} IS NULL`;
275
+ }
276
+ else {
277
+ return SQLSyntax.sqls `${this} = ${value}`;
278
+ }
279
+ }
280
+ gt(value) {
281
+ return SQLSyntax.sqls `${this} > ${value}`;
282
+ }
283
+ ge(value) {
284
+ return SQLSyntax.sqls `${this} >= ${value}`;
285
+ }
286
+ lt(value) {
287
+ return SQLSyntax.sqls `${this} < ${value}`;
288
+ }
289
+ le(value) {
290
+ return SQLSyntax.sqls `${this} <= ${value}`;
291
+ }
292
+ isNull() {
293
+ return SQLSyntax.sqls `${this} IS NULL`;
294
+ }
295
+ isNotNull() {
296
+ return SQLSyntax.sqls `${this} IS NOT NULL`;
297
+ }
298
+ in(valueOrSubQuery) {
299
+ if (valueOrSubQuery instanceof SQLSyntax) {
300
+ if (valueOrSubQuery.isEmpty) {
301
+ throw new Error("empty SQLSyntax is not allowed for `in`.");
302
+ }
303
+ else {
304
+ return SQLSyntax.sqls `${this} IN (${valueOrSubQuery})`;
305
+ }
306
+ }
307
+ else {
308
+ if (!(valueOrSubQuery === null || valueOrSubQuery === void 0 ? void 0 : valueOrSubQuery.length)) {
309
+ throw new Error("empty value list is not allowed for `in`.");
310
+ }
311
+ else {
312
+ return SQLSyntax.sqls `${this} IN ${SQLSyntax.csv(...valueOrSubQuery.map((v) => SQLSyntax.sqls `${v}`)).roundBracket()}`;
313
+ }
314
+ }
315
+ }
316
+ notIn(valueOrSubQuery) {
317
+ if (valueOrSubQuery instanceof SQLSyntax) {
318
+ if (valueOrSubQuery.isEmpty) {
319
+ throw new Error("empty SQLSyntax is not allowed for `notIn`.");
320
+ }
321
+ else {
322
+ return SQLSyntax.sqls `${this} NOT IN (${valueOrSubQuery})`;
323
+ }
324
+ }
325
+ else {
326
+ if (!(valueOrSubQuery === null || valueOrSubQuery === void 0 ? void 0 : valueOrSubQuery.length)) {
327
+ throw new Error("empty SQLSyntax is not allowed for `notIn`.");
328
+ }
329
+ else {
330
+ return SQLSyntax.sqls `${this} NOT IN ${SQLSyntax.csv(...valueOrSubQuery.map((v) => SQLSyntax.sqls `${v}`)).roundBracket()}`;
331
+ }
332
+ }
333
+ }
334
+ like(value) {
335
+ return SQLSyntax.sqls `${this} LIKE ${value}`;
336
+ }
337
+ notLike(value) {
338
+ return SQLSyntax.sqls `${this} NOT LIKE ${value}`;
339
+ }
340
+ exists(part) {
341
+ if (!part || !part.isEmpty) {
342
+ return SQLSyntax.empty;
343
+ }
344
+ return SQLSyntax.sqls `${this} EXISTS ${part}`;
345
+ }
346
+ notExists(part) {
347
+ if (!part || !part.isEmpty) {
348
+ return SQLSyntax.empty;
349
+ }
350
+ return SQLSyntax.sqls `${this} NOT EXISTS ${part}`;
351
+ }
352
+ static sqls(sqlParts, ...values) {
353
+ if (sqlParts.length === 1 && sqlParts[0] === "") {
354
+ return SQLSyntax.empty;
355
+ }
356
+ return new SQLSyntax(false, sqlParts, values);
357
+ }
358
+ /**
359
+ * Allow create an instance of SQLSyntax from a string variable `sqlStr`.
360
+ * The content of the string variable `sqlStr` will become the SQL query string without any escaping.
361
+ * You might need this method to create indentifier (e.g. tbale or column names) as SQLSyntax.
362
+ * Make sure you process the string input well to avoid SQL injection vulnerability.
320
363
  *
321
- * @param {string} childNodeId
322
- * @param {boolean} [includeMyself=false]
323
- * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
324
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
325
- * @returns {Promise<NodeRecord[]>}
326
- * @memberof NestedSetModelQueryer
364
+ * @static
365
+ * @param {string} sqlStr
366
+ * @return {SQLSyntax}
367
+ * @memberof SQLSyntax
327
368
  */
328
- getAllParents(childNodeId, includeMyself = false, fields = null, client = null) {
329
- return __awaiter(this, void 0, void 0, function* () {
330
- const tbl = this.tableName;
331
- const result = yield (client ? client : this.pool).query(`SELECT ${this.selectFields("Parents", fields)}
332
- FROM "${tbl}" AS Parents, "${tbl}" AS Children
333
- WHERE Children."left" ${includeMyself ? ">=" : ">"} Parents."left" AND Children."left" ${includeMyself ? "<=" : "<"} Parents."right" AND Children."id" = $1`, [childNodeId]);
334
- if (!result || !result.rows || !result.rows.length)
335
- return [];
336
- return result.rows;
337
- });
338
- }
339
- /**
340
- * Get Immediate Children of a Node
341
- * If the node has no child (i.e. a leaf node), an empty array will be returned
342
- *
343
- * @param {string} parentNodeId
344
- * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
345
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
346
- * @returns {Promise<NodeRecord[]>}
347
- * @memberof NestedSetModelQueryer
348
- */
349
- getImmediateChildren(parentNodeId, fields = null, client = null) {
350
- return __awaiter(this, void 0, void 0, function* () {
351
- const tbl = this.tableName;
352
- const result = yield (client ? client : this.pool).query(`SELECT ${this.selectFields("Children", fields)}
353
- FROM "${tbl}" AS Parents, "${tbl}" AS Children
354
- WHERE Children."left" BETWEEN Parents."left" AND Parents."right"
355
- AND Parents."left" = (
356
- SELECT MAX(S."left") FROM "${tbl}" AS S
357
- WHERE S."left" < Children."left" AND S."right" > Children."right"
358
- )
359
- AND Parents."id" = $1
360
- ORDER BY Children."left" ASC`, [parentNodeId]);
361
- if (!result || !result.rows || !result.rows.length)
362
- return [];
363
- return result.rows;
364
- });
365
- }
366
- /**
367
- * Get Immediate Parent of a Node
368
- * If the node has no parent (i.e. a root node), null will be returned
369
- *
370
- * @param {string} childNodeId
371
- * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
372
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
373
- * @returns {Promise<NodeRecord>}
374
- * @memberof NestedSetModelQueryer
375
- */
376
- getImmediateParent(childNodeId, fields = null, client = null) {
377
- return __awaiter(this, void 0, void 0, function* () {
378
- const tbl = this.tableName;
379
- const result = yield (client ? client : this.pool).query(`SELECT ${this.selectFields("Parents", fields)}
380
- FROM "${tbl}" AS Parents, "${tbl}" AS Children
381
- WHERE Children.left BETWEEN Parents.left AND Parents.right
382
- AND Parents.left = (
383
- SELECT MAX(S.left) FROM "${tbl}" AS S
384
- WHERE S.left < Children.left AND S.right > Children.right
385
- )
386
- AND Children.id = $1`, [childNodeId]);
387
- if (!result || !result.rows || !result.rows.length)
388
- return tsmonad_1.Maybe.nothing();
389
- return tsmonad_1.Maybe.just(result.rows[0]);
390
- });
391
- }
392
- /**
393
- * Get all nodes at level n from top
394
- * e.g. get all nodes at level 3:
395
- * this.getAllNodesAtLevel(3)
396
- * Root node is at level 1
397
- *
398
- * @param {number} level
399
- * @returns {Promise<NodeRecord[]>}
400
- * @memberof NestedSetModelQueryer
401
- */
402
- getAllNodesAtLevel(level) {
403
- return __awaiter(this, void 0, void 0, function* () {
404
- const tbl = this.tableName;
405
- const result = yield this.pool.query(`SELECT ${this.selectFields("t2")}
406
- FROM "${tbl}" AS t1, "${tbl}" AS t2
407
- WHERE t2.left BETWEEN t1.left AND t1.right
408
- GROUP BY t2.id
409
- HAVING COUNT(t1.id) = $1`, [level]);
410
- if (!result || !result.rows || !result.rows.length)
411
- return [];
412
- return result.rows;
413
- });
414
- }
415
- /**
416
- * Get level no. of a given node
417
- * Starts from 1. i.e. The root node is 1
418
- *
419
- * @param {string} nodeId
420
- * @returns {Promise<number>}
421
- * @throws NodeNotFoundError If the node can't be found in the tree
422
- * @memberof NestedSetModelQueryer
423
- */
424
- getLevelOfNode(nodeId) {
425
- return __awaiter(this, void 0, void 0, function* () {
426
- const tbl = this.tableName;
427
- const result = yield this.pool.query(`SELECT COUNT(Parents.id) AS level
428
- FROM "${tbl}" AS Parents, "${tbl}" AS Children
429
- WHERE Children.left BETWEEN Parents.left AND Parents.right AND Children.id = $1`, [nodeId]);
430
- if (!result || !result.rows || !result.rows.length)
431
- throw new NodeNotFoundError();
432
- const level = parseInt(result.rows[0]["level"]);
433
- if (!lodash_1.default.isNumber(level) || lodash_1.default.isNaN(level) || level < 1)
434
- throw new Error(`Could find a valid level for node ${nodeId}: ${level}`);
435
- return level;
436
- });
437
- }
438
- /**
439
- * Get total height (no. of the levels) of the tree
440
- * Starts with 1 level.
441
- *
442
- * @returns {Promise<number>}
443
- * @throws NodeNotFoundError If the root node can't be found in the tree
444
- * @memberof NestedSetModelQueryer
445
- */
446
- getTreeHeight() {
447
- return __awaiter(this, void 0, void 0, function* () {
448
- const tbl = this.tableName;
449
- const result = yield this.pool.query(`SELECT MAX(level) AS height
450
- FROM(
451
- SELECT COUNT(t1.id)
452
- FROM "${tbl}" AS t1, "${tbl}" AS t2
453
- WHERE t2.left BETWEEN t1.left AND t1.right
454
- GROUP BY t2.id
455
- ) AS L(level)`);
456
- if (!result || !result.rows || !result.rows.length)
457
- throw new NodeNotFoundError();
458
- const height = parseInt(result.rows[0]["height"]);
459
- if (!lodash_1.default.isNumber(height) || lodash_1.default.isNaN(height) || height < 0)
460
- throw new Error(`Invalid height for tree: ${height}`);
461
- return height;
462
- });
463
- }
464
- /**
465
- * Get left most immediate child of a node
466
- *
467
- * @param {string} parentNodeId
468
- * @returns {Promise<Maybe<NodeRecord>>}
469
- * @memberof NestedSetModelQueryer
470
- */
471
- getLeftMostImmediateChild(parentNodeId) {
472
- return __awaiter(this, void 0, void 0, function* () {
473
- const tbl = this.tableName;
474
- const result = yield this.pool.query(`SELECT ${this.selectFields("Children")}
475
- FROM "${tbl}" AS Parents, "${tbl}" AS Children
476
- WHERE Children.left = Parents.left + 1 AND Parents.id = $1`, [parentNodeId]);
477
- if (!result || !result.rows || !result.rows.length)
478
- return tsmonad_1.Maybe.nothing();
479
- return tsmonad_1.Maybe.just(result.rows[0]);
480
- });
481
- }
482
- /**
483
- * Get right most immediate child of a node
484
- *
485
- * @param {string} parentNodeId
486
- * @returns {Promise<Maybe<NodeRecord>>}
487
- * @memberof NestedSetModelQueryer
488
- */
489
- getRightMostImmediateChild(parentNodeId) {
490
- return __awaiter(this, void 0, void 0, function* () {
491
- const tbl = this.tableName;
492
- const result = yield this.pool.query(`SELECT ${this.selectFields("Children")}
493
- FROM "${tbl}" AS Parents, "${tbl}" AS Children
494
- WHERE Children.right = Parents.right - 1 AND Parents.id = $1`, [parentNodeId]);
495
- if (!result || !result.rows || !result.rows.length)
496
- return tsmonad_1.Maybe.nothing();
497
- return tsmonad_1.Maybe.just(result.rows[0]);
498
- });
499
- }
500
- /**
501
- * Get all nodes on the top to down path between the `higherNode` to the `lowerNode`
502
- * Sort from higher level nodes to lower level node
503
- * If a path doesn't exist, null will be returned
504
- * If you pass a lower node to the `higherNodeId` and a higher node to `lowerNodeId`, null will be returned
505
- *
506
- * @param {string} higherNodeId
507
- * @param {string} lowerNodeId
508
- * @returns {Promise<Maybe<NodeRecord[]>}
509
- * @memberof NestedSetModelQueryer
510
- */
511
- getTopDownPathBetween(higherNodeId, lowerNodeId) {
512
- return __awaiter(this, void 0, void 0, function* () {
513
- const tbl = this.tableName;
514
- const result = yield this.pool.query(`SELECT ${this.selectFields("t2")}
515
- FROM "${tbl}" AS t1, "${tbl}" AS t2, "${tbl}" AS t3
516
- WHERE t1.id = $1 AND t3.id = $2
517
- AND t2.left BETWEEN t1.left AND t1.right
518
- AND t3.left BETWEEN t2.left AND t2.right
519
- ORDER BY (t2.right-t2.left) DESC`, [higherNodeId, lowerNodeId]);
520
- if (!result || !result.rows || !result.rows.length)
521
- return tsmonad_1.Maybe.nothing();
522
- return tsmonad_1.Maybe.just(result.rows);
523
- });
524
- }
525
- /**
526
- * Compare the relative position of the two nodes
527
- * If node1 is superior to node2, return "ancestor"
528
- * if node1 is the subordinate of node2, return "descendant"
529
- * If node1 = node2 return "equal"
530
- * If there is no path can be found between Node1 and Node2 return "unrelated"
531
- *
532
- * @param {string} node1Id
533
- * @param {string} node2Id
534
- * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
535
- * @returns {Promise<CompareNodeResult>}
536
- * @memberof NestedSetModelQueryer
537
- */
538
- compareNodes(node1Id, node2Id, client = null) {
539
- return __awaiter(this, void 0, void 0, function* () {
540
- const tbl = this.tableName;
541
- const result = yield (client ? client : this.pool).query(`SELECT (
542
- CASE
543
- WHEN CAST($1 AS varchar) = CAST($2 AS varchar)
544
- THEN 0
545
- WHEN t1.left BETWEEN t2.left AND t2.right
546
- THEN -1
547
- WHEN t2.left BETWEEN t1.left AND t1.right
548
- THEN 1
549
- ELSE null
550
- END
551
- ) AS "result"
552
- FROM "${tbl}" AS t1, "${tbl}" AS t2
553
- WHERE t1.id = CAST($1 AS uuid) AND t2.id = CAST($2 AS uuid)`, [node1Id, node2Id]);
554
- if (!result || !result.rows || !result.rows.length)
555
- return "unrelated";
556
- const comparisonResult = result.rows[0]["result"];
557
- if (typeof comparisonResult === "number") {
558
- switch (comparisonResult) {
559
- case 1:
560
- return "ancestor";
561
- case -1:
562
- return "descendant";
563
- case 0:
564
- return "equal";}
565
-
566
- }
567
- return "unrelated";
568
- });
569
- }
570
- getInsertFields(insertFieldList = null) {
571
- const fieldList = isNonEmptyArray(insertFieldList) ?
572
- insertFieldList :
573
- this.defaultInsertFieldList;
574
- if (!isNonEmptyArray(fieldList)) {
575
- throw new Error("Insert fields must be an non-empty array!");
369
+ static createUnsafely(sqlStr) {
370
+ return new SQLSyntax(false, undefined, undefined, [sqlStr]);
576
371
  }
577
- return fieldList;
578
- }
579
- getNodesInsertSql(nodes, sqlValues, insertFieldList = null, tableAliasOrName = "") {
580
- if (!isNonEmptyArray(nodes)) {
581
- throw new Error("`sqlValues` parameter should be an non-empty array!");
372
+ static filterEmpty(parts) {
373
+ if (!(parts === null || parts === void 0 ? void 0 : parts.length)) {
374
+ return [];
375
+ }
376
+ return parts.filter((item) => item && !item.isEmpty);
582
377
  }
583
- if (!lodash_1.default.isArray(sqlValues)) {
584
- throw new Error("`sqlValues` parameter should be an array!");
378
+ static join(parts, delimiter, spaceBeforeDelimier = true) {
379
+ parts = SQLSyntax.filterEmpty(parts);
380
+ if (!(parts === null || parts === void 0 ? void 0 : parts.length)) {
381
+ return SQLSyntax.empty;
382
+ }
383
+ const sep = spaceBeforeDelimier
384
+ ? SQLSyntax.sqls ` ${delimiter}`
385
+ : delimiter;
386
+ let result = parts[0];
387
+ if (parts.length > 1) {
388
+ for (let i = 1; i < parts.length; i++) {
389
+ result = SQLSyntax.sqls `${result}${sep}${parts[i]}`;
390
+ }
391
+ }
392
+ return result;
585
393
  }
586
- if (!isValidSqlIdentifier(tableAliasOrName)) {
587
- throw new Error(`tableAliasOrName: ${tableAliasOrName} contains invalid characters!`);
394
+ static csv(...parts) {
395
+ parts = SQLSyntax.filterEmpty(parts);
396
+ if (!(parts === null || parts === void 0 ? void 0 : parts.length)) {
397
+ return SQLSyntax.empty;
398
+ }
399
+ return SQLSyntax.join(parts, SQLSyntax.sqls `,`, false);
588
400
  }
589
- const tbl = this.tableName;
590
- const fieldList = this.getInsertFields(insertFieldList);
591
- const columnsList = fieldList.
592
- map(f => {
593
- if (!isValidSqlIdentifier(f)) {
594
- throw new Error(`column name: ${f} contains invalid characters!`);
595
- }
596
- return tableAliasOrName == "" ?
597
- `"${f}"` :
598
- `${tableAliasOrName}."${f}"`;
599
- }).
600
- join(", ");
601
- const valuesList = nodes.
602
- map(node => "(" +
603
- fieldList.
604
- map(f => {
605
- sqlValues.push(node[f]);
606
- return `$${sqlValues.length}`;
607
- }).
608
- join(", ") +
609
- ")").
610
- join(", ");
611
- return `INSERT INTO "${tbl}" (${columnsList}) VALUES ${valuesList}`;
612
- }
613
- /**
614
- * Create the root node of the tree.
615
- * If a root node already exists, an error will be thrown.
616
- *
617
- * @param {NodeRecord} node
618
- * @param {pg.Client} [existingClient=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
619
- * @returns {Promise<string>} newly created node ID
620
- * @memberof NestedSetModelQueryer
621
- */
622
- createRootNode(node, existingClient = null) {
623
- return __awaiter(this, void 0, void 0, function* () {
624
- const tbl = this.tableName;
625
- const client = existingClient ?
626
- existingClient :
627
- yield this.pool.connect();
628
- const fields = Object.keys(node);
629
- if (!fields.length) {
630
- throw new Error("`node` parameter cannot be an empty object with no key.");
631
- }
632
- let nodeId;
633
- try {
634
- yield client.query("BEGIN");
635
- let result = yield client.query(`SELECT "id" FROM "${tbl}" WHERE "left" = 1 LIMIT 1`);
636
- if (result && isNonEmptyArray(result.rows)) {
637
- throw new Error(`A root node with id: ${result.rows[0]["id"]} already exists`);
401
+ static hasAndOr(s) {
402
+ let [sqlStr] = s.toQuery();
403
+ sqlStr = sqlStr.toLowerCase();
404
+ if (sqlStr.indexOf(" and ") == -1 && sqlStr.indexOf(" or ") == -1) {
405
+ return false;
638
406
  }
639
- const countResult = yield client.query(`SELECT COUNT("id") AS "num" FROM "${tbl}" WHERE "left" != 1`);
640
- let countNum = countResult && countResult.rows && countResult.rows.length ?
641
- parseInt(countResult.rows[0].num) :
642
- 0;
643
- countNum = isNaN(countNum) ? 0 : countNum;
644
- const right = countNum ? (countNum + 1) * 2 : 2;
645
- const sqlValues = [];
646
- result = yield client.query(this.getNodesInsertSql([
647
- Object.assign(Object.assign({}, node), { left: 1, right })],
648
- sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
649
- if (!result || !isNonEmptyArray(result.rows)) {
650
- throw new Error("Cannot locate create root node ID!");
407
+ else {
408
+ return true;
651
409
  }
652
- yield client.query("COMMIT");
653
- nodeId = result.rows[0]["id"];
654
- }
655
- catch (e) {
656
- yield client.query("ROLLBACK");
657
- throw e;
658
- } finally
659
- {
660
- if (!existingClient) {
661
- client.release();
410
+ }
411
+ static joinWithAnd(conditions) {
412
+ conditions = SQLSyntax.filterEmpty(conditions);
413
+ if (!(conditions === null || conditions === void 0 ? void 0 : conditions.length)) {
414
+ return SQLSyntax.empty;
662
415
  }
663
- }
664
- return nodeId;
665
- });
666
- }
667
- getNodeDataWithinTx(client, nodeId, fields) {
668
- return __awaiter(this, void 0, void 0, function* () {
669
- const node = yield this.getNodeById(nodeId, fields, client);
670
- return node.caseOf({
671
- just: node => node,
672
- nothing: () => {
673
- throw new NodeNotFoundError(`Cannot locate tree node record with id: ${nodeId}`);
674
- } });
675
-
676
- });
677
- }
678
- /**
679
- * Insert a node to the tree under a parent node
680
- *
681
- * @param {string} parentNodeId
682
- * @param {NodeRecord} node
683
- * @returns {Promise<string>}
684
- * @throws NodeNotFoundError if parent node not found
685
- * @memberof NestedSetModelQueryer
686
- */
687
- insertNode(parentNodeId, node) {
688
- return __awaiter(this, void 0, void 0, function* () {
689
- if (!parentNodeId) {
690
- throw new Error("`parentNodeId` cannot be empty!");
691
- }
692
- const fields = Object.keys(node);
693
- if (!fields.length) {
694
- throw new Error("`node` parameter cannot be an empty object with no key.");
695
- }
696
- const tbl = this.tableName;
697
- const client = yield this.pool.connect();
698
- let nodeId;
699
- try {
700
- yield client.query("BEGIN");
701
- const { right: parentRight } = yield this.getNodeDataWithinTx(client, parentNodeId, ["right"]);
702
- yield client.query(`UPDATE "${tbl}"
703
- SET
704
- "left" = CASE WHEN "left" > $1 THEN "left" + 2 ELSE "left" END,
705
- "right" = CASE WHEN "right" >= $1 THEN "right" + 2 ELSE "right" END
706
- WHERE "right" >= $1`, [parentRight]);
707
- const sqlValues = [];
708
- const result = yield client.query(this.getNodesInsertSql([
709
- Object.assign(Object.assign({}, node), { left: parentRight, right: parentRight + 1 })],
710
- sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
711
- if (!result || !isNonEmptyArray(result.rows)) {
712
- throw new Error("Cannot locate created node ID!");
416
+ return SQLSyntax.join(conditions
417
+ .filter((s) => s && !s.isEmpty)
418
+ .map((s) => (SQLSyntax.hasAndOr(s) ? s.roundBracket() : s)), SQLSyntax.sqls ` AND `, false);
419
+ }
420
+ static joinWithOr(conditions) {
421
+ conditions = SQLSyntax.filterEmpty(conditions);
422
+ if (!(conditions === null || conditions === void 0 ? void 0 : conditions.length)) {
423
+ return SQLSyntax.empty;
713
424
  }
714
- yield client.query("COMMIT");
715
- nodeId = result.rows[0]["id"];
716
- }
717
- catch (e) {
718
- yield client.query("ROLLBACK");
719
- throw e;
720
- } finally
721
- {
722
- client.release();
723
- }
724
- return nodeId;
725
- });
726
- }
727
- /**
728
- * Insert a node to the right of its sibling
729
- * If `siblingNodeId` belongs to a root node, an error will be thrown
730
- *
731
- * @param {string} siblingNodeId
732
- * @param {NodeRecord} node
733
- * @returns {Promise<string>}
734
- * @throws NodeNotFoundError If the node can't be found in the tree
735
- * @memberof NestedSetModelQueryer
736
- */
737
- insertNodeToRightOfSibling(siblingNodeId, node) {
738
- return __awaiter(this, void 0, void 0, function* () {
739
- if (!siblingNodeId) {
740
- throw new Error("`siblingNodeId` cannot be empty!");
741
- }
742
- const fields = Object.keys(node);
743
- if (!fields.length) {
744
- throw new Error("`node` parameter cannot be an empty object with no key.");
745
- }
746
- const tbl = this.tableName;
747
- const client = yield this.pool.connect();
748
- let nodeId;
749
- try {
750
- yield client.query("BEGIN");
751
- const { left: siblingLeft, right: siblingRight } = yield this.getNodeDataWithinTx(client, siblingNodeId, [
752
- "left",
753
- "right"]);
754
-
755
- if (siblingLeft === 1) {
756
- throw new Error("Cannot add sibling to the Root node!");
425
+ return SQLSyntax.join(conditions
426
+ .filter((s) => s && !s.isEmpty)
427
+ .map((s) => (SQLSyntax.hasAndOr(s) ? s.roundBracket() : s)), SQLSyntax.sqls ` OR `, false);
428
+ }
429
+ static groupBy(...columns) {
430
+ return SQLSyntax.empty.groupBy(...columns);
431
+ }
432
+ static having(condition) {
433
+ return SQLSyntax.empty.having(condition);
434
+ }
435
+ static orderBy(...columns) {
436
+ return SQLSyntax.empty.orderBy(...columns);
437
+ }
438
+ static limit(n) {
439
+ return SQLSyntax.empty.limit(n);
440
+ }
441
+ static offset(n) {
442
+ return SQLSyntax.empty.offset(n);
443
+ }
444
+ static where(condition) {
445
+ return SQLSyntax.empty.where(condition);
446
+ }
447
+ static eq(column, value) {
448
+ if (typeof value === "undefined" || value == null) {
449
+ return SQLSyntax.sqls `${column} IS NULL`;
757
450
  }
758
- yield client.query(`UPDATE "${tbl}"
759
- SET
760
- "left" = CASE WHEN "left" < $1 THEN "left" ELSE "left" + 2 END,
761
- "right" = CASE WHEN "right" < $1 THEN "right" ELSE "right" + 2 END
762
- WHERE "right" > $1`, [siblingRight]);
763
- const sqlValues = [];
764
- const result = yield client.query(this.getNodesInsertSql([
765
- Object.assign(Object.assign({}, node), { left: siblingRight + 1, right: siblingRight + 2 })],
766
- sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
767
- if (!result || !isNonEmptyArray(result.rows)) {
768
- throw new Error("Cannot locate created node ID!");
451
+ else {
452
+ return SQLSyntax.sqls `${column} = ${value}`;
769
453
  }
770
- yield client.query("COMMIT");
771
- nodeId = result.rows[0]["id"];
772
- }
773
- catch (e) {
774
- yield client.query("ROLLBACK");
775
- throw e;
776
- } finally
777
- {
778
- client.release();
779
- }
780
- return nodeId;
781
- });
782
- }
783
- /**
784
- * Move a subtree (the specified root node and all its subordinates)
785
- * to under a new parent.
786
- *
787
- * If the specifed sub tree root node is a child of the new parent node,
788
- * an error will be be thrown
789
- *
790
- * @param {string} subTreeRootNodeId
791
- * @param {string} newParentId
792
- * @returns {Promise<void>}
793
- * @throws NodeNotFoundError If the node can't be found in the tree
794
- * @memberof NestedSetModelQueryer
795
- */
796
- moveSubTreeTo(subTreeRootNodeId, newParentId) {
797
- return __awaiter(this, void 0, void 0, function* () {
798
- if (!subTreeRootNodeId) {
799
- throw new Error("`subTreeRootNodeId` cannot be empty!");
800
- }
801
- if (!newParentId) {
802
- throw new Error("`newParentId` cannot be empty!");
803
- }
804
- const tbl = this.tableName;
805
- const client = yield this.pool.connect();
806
- try {
807
- yield client.query("BEGIN");
808
- const comparisonResult = yield this.compareNodes(subTreeRootNodeId, newParentId, client);
809
- if (comparisonResult === "ancestor") {
810
- throw new Error(`Cannot move a higher level node (id: ${subTreeRootNodeId})to its subordinate (id: ${newParentId})`);
454
+ }
455
+ static gt(column, value) {
456
+ return SQLSyntax.sqls `${column} > ${value}`;
457
+ }
458
+ static ge(column, value) {
459
+ return SQLSyntax.sqls `${column} >= ${value}`;
460
+ }
461
+ static lt(column, value) {
462
+ return SQLSyntax.sqls `${column} < ${value}`;
463
+ }
464
+ static le(column, value) {
465
+ return SQLSyntax.sqls `${column} <= ${value}`;
466
+ }
467
+ static isNull(column) {
468
+ return SQLSyntax.sqls `${column} IS NULL`;
469
+ }
470
+ static isNotNull(column) {
471
+ return SQLSyntax.sqls `${column} IS NOT NULL`;
472
+ }
473
+ static in(column, valueOrSubQuery) {
474
+ if (valueOrSubQuery instanceof SQLSyntax) {
475
+ if (valueOrSubQuery.isEmpty) {
476
+ return SQLSyntax.sqls `FALSE`;
477
+ }
478
+ else {
479
+ return SQLSyntax.sqls `${column} IN (${valueOrSubQuery})`;
480
+ }
811
481
  }
812
- const { left: originRootLeft, right: originRootRight } = yield this.getNodeDataWithinTx(client, subTreeRootNodeId, [
813
- "left",
814
- "right"]);
815
-
816
- if (originRootLeft === "1") {
817
- throw new Error("Cannot move Tree root node as substree.");
482
+ else {
483
+ if (!(valueOrSubQuery === null || valueOrSubQuery === void 0 ? void 0 : valueOrSubQuery.length)) {
484
+ return SQLSyntax.sqls `FALSE`;
485
+ }
486
+ else {
487
+ return SQLSyntax.sqls `${column} IN ${SQLSyntax.csv(...valueOrSubQuery.map((v) => SQLSyntax.sqls `${v}`)).roundBracket()}`;
488
+ }
818
489
  }
819
- const { right: newParentRight } = yield this.getNodeDataWithinTx(client, newParentId, ["right"]);
820
- yield client.query(`
821
- UPDATE "${tbl}"
822
- SET
823
- "left" = "left" + CASE
824
- WHEN $3::int4 < $1::int4
825
- THEN CASE
826
- WHEN "left" BETWEEN $1 AND $2
827
- THEN $3 - $1
828
- WHEN "left" BETWEEN $3 AND ($1 - 1)
829
- THEN $2 - $1 + 1
830
- ELSE 0 END
831
- WHEN $3::int4 > $2::int4
832
- THEN CASE
833
- WHEN "left" BETWEEN $1 AND $2
834
- THEN $3 - $2 - 1
835
- WHEN "left" BETWEEN ($2 + 1) AND ($3 - 1)
836
- THEN $1 - $2 - 1
837
- ELSE 0 END
838
- ELSE 0 END,
839
- "right" = "right" + CASE
840
- WHEN $3::int4 < $1::int4
841
- THEN CASE
842
- WHEN "right" BETWEEN $1 AND $2
843
- THEN $3 - $1
844
- WHEN "right" BETWEEN $3 AND ($1 - 1)
845
- THEN $2 - $1 + 1
846
- ELSE 0 END
847
- WHEN $3::int4 > $2::int4
848
- THEN CASE
849
- WHEN "right" BETWEEN $1 AND $2
850
- THEN $3 - $2 - 1
851
- WHEN "right" BETWEEN ($2 + 1) AND ($3 - 1)
852
- THEN $1 - $2 - 1
853
- ELSE 0 END
854
- ELSE 0 END
855
- `, [originRootLeft, originRootRight, newParentRight]);
856
- yield client.query("COMMIT");
857
- }
858
- catch (e) {
859
- yield client.query("ROLLBACK");
860
- throw e;
861
- } finally
862
- {
863
- client.release();
864
- }
865
- });
866
- }
867
- /**
868
- * Delete a subtree (and all its dependents)
869
- * If you sent in a root node id (and `allowRootNodeId` is true), the whole tree will be removed
870
- * When `allowRootNodeId` is false and you passed a root node id, an error will be thrown
871
- *
872
- * @param {string} subTreeRootNodeId
873
- * @param {boolean} [allowRootNodeId=false]
874
- * @returns {Promise<void>}
875
- * @throws NodeNotFoundError If the node can't be found in the tree
876
- * @memberof NestedSetModelQueryer
877
- */
878
- deleteSubTree(subTreeRootNodeId, allowRootNodeId = false) {
879
- return __awaiter(this, void 0, void 0, function* () {
880
- if (!subTreeRootNodeId) {
881
- throw new Error("`subTreeRootNodeId` cannot be empty!");
882
- }
883
- const tbl = this.tableName;
884
- const client = yield this.pool.connect();
885
- try {
886
- yield client.query("BEGIN");
887
- const { left: subTreeRootLeft, right: subTreeRootRight } = yield this.getNodeDataWithinTx(client, subTreeRootNodeId, [
888
- "left",
889
- "right"]);
890
-
891
- if (subTreeRootLeft === 1 && !allowRootNodeId) {
892
- throw new Error("Root node id is not allowed!");
893
- }
894
- // --- delete the sub tree nodes
895
- yield client.query(`DELETE FROM "${tbl}" WHERE "left" BETWEEN $1 AND $2`, [subTreeRootLeft, subTreeRootRight]);
896
- // --- closing the gap after deletion
897
- yield client.query(`
898
- UPDATE "${tbl}"
899
- SET "left" = CASE
900
- WHEN "left" > $1
901
- THEN "left" - ($2 - $1 + 1)
902
- ELSE "left" END,
903
- "right" = CASE
904
- WHEN "right" > $1
905
- THEN "right" - ($2 - $1 + 1)
906
- ELSE "right" END
907
- WHERE "left" > $1 OR "right" > $1
908
- `, [subTreeRootLeft, subTreeRootRight]);
909
- yield client.query("COMMIT");
910
- }
911
- catch (e) {
912
- yield client.query("ROLLBACK");
913
- throw e;
914
- } finally
915
- {
916
- client.release();
917
- }
918
- });
919
- }
920
- /**
921
- * Delete a single node from the tree
922
- * Its childrens will become its parent's children
923
- * Deleting a root node is not allowed.
924
- * You can, however, delete the whole sub tree from root node or update the root node, instead.
925
- *
926
- * @param {string} nodeId
927
- * @returns {Promise<void>}
928
- * @throws NodeNotFoundError If the node can't be found in the tree
929
- * @memberof NestedSetModelQueryer
930
- */
931
- deleteNode(nodeId) {
932
- return __awaiter(this, void 0, void 0, function* () {
933
- if (!nodeId) {
934
- throw new Error("`nodeId` cannot be empty!");
935
- }
936
- const tbl = this.tableName;
937
- const client = yield this.pool.connect();
938
- try {
939
- yield client.query("BEGIN");
940
- const { left: subTreeRootLeft, right: subTreeRootRight } = yield this.getNodeDataWithinTx(client, nodeId, [
941
- "left",
942
- "right"]);
943
-
944
- if (subTreeRootLeft === 1) {
945
- throw new Error("Delete a root node is not allowed!");
490
+ }
491
+ static notIn(column, valueOrSubQuery) {
492
+ if (valueOrSubQuery instanceof SQLSyntax) {
493
+ if (valueOrSubQuery.isEmpty) {
494
+ return SQLSyntax.sqls `TRUE`;
495
+ }
496
+ else {
497
+ return SQLSyntax.sqls `${column} NOT IN (${valueOrSubQuery})`;
498
+ }
946
499
  }
947
- // --- delete the node
948
- // --- In nested set model, children are still bind to the deleted node's parent after deletion
949
- yield client.query(`DELETE FROM "${tbl}" WHERE "id" = $1`, [
950
- nodeId]);
951
-
952
- // --- closing the gap after deletion
953
- yield client.query(`
954
- UPDATE "${tbl}"
955
- SET "left" = CASE
956
- WHEN "left" > $1 AND "right" < $2
957
- THEN "left" - 1
958
- WHEN "left" > $1 AND "right" > $2
959
- THEN "left" - 2
960
- ELSE "left" END,
961
- "right" = CASE
962
- WHEN "left" > $1 AND "right" < $2
963
- THEN "right" - 1
964
- ELSE ("right" - 2) END
965
- WHERE "left" > $1 OR "right" > $1
966
- `, [subTreeRootLeft, subTreeRootRight]);
967
- yield client.query("COMMIT");
968
- }
969
- catch (e) {
970
- yield client.query("ROLLBACK");
971
- throw e;
972
- } finally
973
- {
974
- client.release();
975
- }
976
- });
977
- }
978
- /**
979
- * Update node data of the node specified by the nodeId
980
- * The followings fields will be ignored (as they should be generated by program):
981
- * - `left`
982
- * - `right`
983
- * - `id`
984
- *
985
- * @param {string} nodeId
986
- * @param {NodeRecord} nodeData
987
- * @param {pg.Client} [client=null]
988
- * @returns {Promise<void>}
989
- * @memberof NestedSetModelQueryer
990
- */
991
- updateNode(nodeId, nodeData, client = null) {
992
- return __awaiter(this, void 0, void 0, function* () {
993
- if (nodeId.trim() === "") {
994
- throw new Error("nodeId can't be empty!");
995
- }
996
- const sqlValues = [nodeId];
997
- const updateFields = Object.keys(nodeData).filter(k => k !== "left" && k !== "right" && k !== "id");
998
- if (!updateFields.length) {
999
- throw new Error("No valid node data passed for updating.");
1000
- }
1001
- const setFieldList = updateFields.
1002
- map(f => {
1003
- if (!isValidSqlIdentifier(f)) {
1004
- throw new Error(`field name: ${f} contains invalid characters!`);
500
+ else {
501
+ if (!(valueOrSubQuery === null || valueOrSubQuery === void 0 ? void 0 : valueOrSubQuery.length)) {
502
+ return SQLSyntax.sqls `TRUE`;
503
+ }
504
+ else {
505
+ return SQLSyntax.sqls `${column} NOT IN ${SQLSyntax.csv(...valueOrSubQuery.map((v) => SQLSyntax.sqls `${v}`)).roundBracket()}`;
506
+ }
1005
507
  }
1006
- sqlValues.push(nodeData[f]);
1007
- return `"${f}" = $${sqlValues.length}`;
1008
- }).
1009
- join(", ");
1010
- yield (client ? client : this.pool).query(`UPDATE "${this.tableName}" SET ${setFieldList} WHERE "id" = $1`, sqlValues);
1011
- });
1012
- }
1013
- getChildTextTreeNodes(parentId) {
1014
- return __awaiter(this, void 0, void 0, function* () {
1015
- const nodes = yield this.getImmediateChildren(parentId);
1016
- if (!nodes || !nodes.length)
1017
- return [];
1018
- const textNodeList = [];
1019
- for (let i = 0; i < nodes.length; i++) {
1020
- const nodeChildren = yield this.getChildTextTreeNodes(nodes[i].id);
1021
- if (nodeChildren.length) {
1022
- textNodeList.push({
1023
- text: nodes[i].name,
1024
- children: nodeChildren });
1025
-
1026
- } else
1027
- {
1028
- textNodeList.push(nodes[i].name);
508
+ }
509
+ static like(column, value) {
510
+ return SQLSyntax.sqls `${column} LIKE ${value}`;
511
+ }
512
+ static notLike(column, value) {
513
+ return SQLSyntax.sqls `${column} NOT LIKE ${value}`;
514
+ }
515
+ static exists(part) {
516
+ return SQLSyntax.empty.exists(part);
517
+ }
518
+ static notExists(part) {
519
+ return SQLSyntax.empty.notExists(part);
520
+ }
521
+ static distinct(...columns) {
522
+ return SQLSyntax.sqls `DISTINCT ${SQLSyntax.csv(...columns)}`;
523
+ }
524
+ static count(column) {
525
+ return SQLSyntax.sqls `COUNT(${column})`;
526
+ }
527
+ static min(column) {
528
+ return SQLSyntax.sqls `MIN(${column})`;
529
+ }
530
+ static max(column) {
531
+ return SQLSyntax.sqls `MAX(${column})`;
532
+ }
533
+ static avg(column) {
534
+ return SQLSyntax.sqls `AVG(${column})`;
535
+ }
536
+ static sum(column) {
537
+ return SQLSyntax.sqls `SUM(${column})`;
538
+ }
539
+ static roundBracket(inner) {
540
+ if (!inner || !(inner === null || inner === void 0 ? void 0 : inner.isEmpty)) {
541
+ return SQLSyntax.empty;
1029
542
  }
1030
- }
1031
- return textNodeList;
1032
- });
1033
- }
1034
- /**
1035
- * Generate the Text View of the tree
1036
- * Provided as Dev tool only
1037
- *
1038
- * E.g. output could be:
1039
- * └─ Albert
1040
- * ├─ Chuck
1041
- * │ ├─ Fred
1042
- * │ ├─ Eddie
1043
- * │ └─ Donna
1044
- * └─ Bert
1045
- *
1046
- * @returns {Promise<string>}
1047
- * @memberof NestedSetModelQueryer
1048
- */
1049
- getTreeTextView() {
1050
- return __awaiter(this, void 0, void 0, function* () {
1051
- const rootNodeMaybe = yield this.getRootNode();
1052
- return rootNodeMaybe.caseOf({
1053
- just: rootNode => __awaiter(this, void 0, void 0, function* () {
1054
- const tree = [];
1055
- const children = yield this.getChildTextTreeNodes(rootNode.id);
1056
- if (children.length) {
1057
- tree.push({
1058
- text: rootNode.name,
1059
- children });
543
+ return inner.roundBracket();
544
+ }
545
+ }
546
+ SQLSyntax.isDebugMode = false;
547
+ SQLSyntax.empty = new SQLSyntax(true);
548
+ SQLSyntax.asc = SQLSyntax.empty.asc();
549
+ SQLSyntax.desc = SQLSyntax.empty.desc();
550
+ function sqls(sqlParts, ...values) {
551
+ return SQLSyntax.sqls(sqlParts, ...values);
552
+ }
553
+ /**
554
+ * Escape postgreSQL SQL identifier string
555
+ * Although postgreSQL does allow non-ASCII characters in identifiers, to make it simple, we will remove any non-ASCII characters.
556
+ *
557
+ * @export
558
+ * @param {string} idStr
559
+ * @return {*} {string}
560
+ */
561
+ function escapeIdentifierStr(idStr) {
562
+ return '"' + idStr.replace(/[^\x20-\x7e]/g, "").replace(/"/g, "\"'") + '"';
563
+ }
564
+ /**
565
+ * Escape postgreSQL SQL identifier (e.g. column names, or table names).
566
+ * `xxx."ss.dd` will be escaped as `"xxx"."""ss"."dd"`
567
+ * Although postgreSQL does allow non-ASCII characters in identifiers, to make it simple, we will remove any non-ASCII characters.
568
+ *
569
+ * @export
570
+ * @param {string} id
571
+ * @return {*} {SQLSyntax}
572
+ */
573
+ function escapeIdentifier(id) {
574
+ const sanitisedIdStr = id.replace(/[^\x20-\x7e]/g, "");
575
+ const parts = sanitisedIdStr.split(".");
576
+ const escapedIdStr = parts.length > 1
577
+ ? parts.map((item) => escapeIdentifierStr(item)).join(".")
578
+ : escapeIdentifierStr(sanitisedIdStr);
579
+ return SQLSyntax.createUnsafely(escapedIdStr);
580
+ }
581
+ /**
582
+ * Make a postgreSQL identifier in SQLSyntax from tableRef (optional) & column name.
583
+ *
584
+ * @export
585
+ * @param {String} columnName
586
+ * @param {String} [tableRef=""]
587
+ * @param {Boolean} [useLowerCaseColumnName=true]
588
+ * @return {*} {SQLSyntax}
589
+ */
590
+ function getTableColumnName(columnName, tableRef = "", useLowerCaseColumnName = false) {
591
+ const id = [
592
+ tableRef,
593
+ useLowerCaseColumnName ? columnName.toLowerCase : useLowerCaseColumnName
594
+ ]
595
+ .filter((item) => item)
596
+ .join(".");
597
+ return escapeIdentifier(id);
598
+ }
1060
599
 
1061
- } else
1062
- {
1063
- tree.push(rootNode.name);
1064
- }
1065
- return textTree(tree);
1066
- }),
1067
- nothing: () => __awaiter(this, void 0, void 0, function* () {return "Empty Tree";}) });
1068
600
 
1069
- });
1070
- }}
601
+ //# sourceMappingURL=index.js.map
1071
602
 
1072
- exports.default = NestedSetModelQueryer;
1073
603
 
1074
604
  /***/ }),
1075
- /* 10 */
605
+ /* 7 */,
606
+ /* 8 */,
607
+ /* 9 */
1076
608
  /***/ (function(module, exports, __webpack_require__) {
1077
609
 
1078
610
  /* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_RESULT__;/**
@@ -18008,279 +17540,1874 @@ exports.default = NestedSetModelQueryer;
18008
17540
  lodash.upperCase = upperCase;
18009
17541
  lodash.upperFirst = upperFirst;
18010
17542
 
18011
- // Add aliases.
18012
- lodash.each = forEach;
18013
- lodash.eachRight = forEachRight;
18014
- lodash.first = head;
17543
+ // Add aliases.
17544
+ lodash.each = forEach;
17545
+ lodash.eachRight = forEachRight;
17546
+ lodash.first = head;
17547
+
17548
+ mixin(lodash, (function() {
17549
+ var source = {};
17550
+ baseForOwn(lodash, function(func, methodName) {
17551
+ if (!hasOwnProperty.call(lodash.prototype, methodName)) {
17552
+ source[methodName] = func;
17553
+ }
17554
+ });
17555
+ return source;
17556
+ }()), { 'chain': false });
17557
+
17558
+ /*------------------------------------------------------------------------*/
17559
+
17560
+ /**
17561
+ * The semantic version number.
17562
+ *
17563
+ * @static
17564
+ * @memberOf _
17565
+ * @type {string}
17566
+ */
17567
+ lodash.VERSION = VERSION;
17568
+
17569
+ // Assign default placeholders.
17570
+ arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
17571
+ lodash[methodName].placeholder = lodash;
17572
+ });
17573
+
17574
+ // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
17575
+ arrayEach(['drop', 'take'], function(methodName, index) {
17576
+ LazyWrapper.prototype[methodName] = function(n) {
17577
+ n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
17578
+
17579
+ var result = (this.__filtered__ && !index)
17580
+ ? new LazyWrapper(this)
17581
+ : this.clone();
17582
+
17583
+ if (result.__filtered__) {
17584
+ result.__takeCount__ = nativeMin(n, result.__takeCount__);
17585
+ } else {
17586
+ result.__views__.push({
17587
+ 'size': nativeMin(n, MAX_ARRAY_LENGTH),
17588
+ 'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
17589
+ });
17590
+ }
17591
+ return result;
17592
+ };
17593
+
17594
+ LazyWrapper.prototype[methodName + 'Right'] = function(n) {
17595
+ return this.reverse()[methodName](n).reverse();
17596
+ };
17597
+ });
17598
+
17599
+ // Add `LazyWrapper` methods that accept an `iteratee` value.
17600
+ arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
17601
+ var type = index + 1,
17602
+ isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
17603
+
17604
+ LazyWrapper.prototype[methodName] = function(iteratee) {
17605
+ var result = this.clone();
17606
+ result.__iteratees__.push({
17607
+ 'iteratee': getIteratee(iteratee, 3),
17608
+ 'type': type
17609
+ });
17610
+ result.__filtered__ = result.__filtered__ || isFilter;
17611
+ return result;
17612
+ };
17613
+ });
17614
+
17615
+ // Add `LazyWrapper` methods for `_.head` and `_.last`.
17616
+ arrayEach(['head', 'last'], function(methodName, index) {
17617
+ var takeName = 'take' + (index ? 'Right' : '');
17618
+
17619
+ LazyWrapper.prototype[methodName] = function() {
17620
+ return this[takeName](1).value()[0];
17621
+ };
17622
+ });
17623
+
17624
+ // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
17625
+ arrayEach(['initial', 'tail'], function(methodName, index) {
17626
+ var dropName = 'drop' + (index ? '' : 'Right');
17627
+
17628
+ LazyWrapper.prototype[methodName] = function() {
17629
+ return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
17630
+ };
17631
+ });
17632
+
17633
+ LazyWrapper.prototype.compact = function() {
17634
+ return this.filter(identity);
17635
+ };
17636
+
17637
+ LazyWrapper.prototype.find = function(predicate) {
17638
+ return this.filter(predicate).head();
17639
+ };
17640
+
17641
+ LazyWrapper.prototype.findLast = function(predicate) {
17642
+ return this.reverse().find(predicate);
17643
+ };
17644
+
17645
+ LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
17646
+ if (typeof path == 'function') {
17647
+ return new LazyWrapper(this);
17648
+ }
17649
+ return this.map(function(value) {
17650
+ return baseInvoke(value, path, args);
17651
+ });
17652
+ });
17653
+
17654
+ LazyWrapper.prototype.reject = function(predicate) {
17655
+ return this.filter(negate(getIteratee(predicate)));
17656
+ };
17657
+
17658
+ LazyWrapper.prototype.slice = function(start, end) {
17659
+ start = toInteger(start);
17660
+
17661
+ var result = this;
17662
+ if (result.__filtered__ && (start > 0 || end < 0)) {
17663
+ return new LazyWrapper(result);
17664
+ }
17665
+ if (start < 0) {
17666
+ result = result.takeRight(-start);
17667
+ } else if (start) {
17668
+ result = result.drop(start);
17669
+ }
17670
+ if (end !== undefined) {
17671
+ end = toInteger(end);
17672
+ result = end < 0 ? result.dropRight(-end) : result.take(end - start);
17673
+ }
17674
+ return result;
17675
+ };
17676
+
17677
+ LazyWrapper.prototype.takeRightWhile = function(predicate) {
17678
+ return this.reverse().takeWhile(predicate).reverse();
17679
+ };
17680
+
17681
+ LazyWrapper.prototype.toArray = function() {
17682
+ return this.take(MAX_ARRAY_LENGTH);
17683
+ };
17684
+
17685
+ // Add `LazyWrapper` methods to `lodash.prototype`.
17686
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
17687
+ var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
17688
+ isTaker = /^(?:head|last)$/.test(methodName),
17689
+ lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
17690
+ retUnwrapped = isTaker || /^find/.test(methodName);
17691
+
17692
+ if (!lodashFunc) {
17693
+ return;
17694
+ }
17695
+ lodash.prototype[methodName] = function() {
17696
+ var value = this.__wrapped__,
17697
+ args = isTaker ? [1] : arguments,
17698
+ isLazy = value instanceof LazyWrapper,
17699
+ iteratee = args[0],
17700
+ useLazy = isLazy || isArray(value);
17701
+
17702
+ var interceptor = function(value) {
17703
+ var result = lodashFunc.apply(lodash, arrayPush([value], args));
17704
+ return (isTaker && chainAll) ? result[0] : result;
17705
+ };
17706
+
17707
+ if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
17708
+ // Avoid lazy use if the iteratee has a "length" value other than `1`.
17709
+ isLazy = useLazy = false;
17710
+ }
17711
+ var chainAll = this.__chain__,
17712
+ isHybrid = !!this.__actions__.length,
17713
+ isUnwrapped = retUnwrapped && !chainAll,
17714
+ onlyLazy = isLazy && !isHybrid;
17715
+
17716
+ if (!retUnwrapped && useLazy) {
17717
+ value = onlyLazy ? value : new LazyWrapper(this);
17718
+ var result = func.apply(value, args);
17719
+ result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
17720
+ return new LodashWrapper(result, chainAll);
17721
+ }
17722
+ if (isUnwrapped && onlyLazy) {
17723
+ return func.apply(this, args);
17724
+ }
17725
+ result = this.thru(interceptor);
17726
+ return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
17727
+ };
17728
+ });
17729
+
17730
+ // Add `Array` methods to `lodash.prototype`.
17731
+ arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
17732
+ var func = arrayProto[methodName],
17733
+ chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
17734
+ retUnwrapped = /^(?:pop|shift)$/.test(methodName);
17735
+
17736
+ lodash.prototype[methodName] = function() {
17737
+ var args = arguments;
17738
+ if (retUnwrapped && !this.__chain__) {
17739
+ var value = this.value();
17740
+ return func.apply(isArray(value) ? value : [], args);
17741
+ }
17742
+ return this[chainName](function(value) {
17743
+ return func.apply(isArray(value) ? value : [], args);
17744
+ });
17745
+ };
17746
+ });
17747
+
17748
+ // Map minified method names to their real names.
17749
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
17750
+ var lodashFunc = lodash[methodName];
17751
+ if (lodashFunc) {
17752
+ var key = lodashFunc.name + '';
17753
+ if (!hasOwnProperty.call(realNames, key)) {
17754
+ realNames[key] = [];
17755
+ }
17756
+ realNames[key].push({ 'name': methodName, 'func': lodashFunc });
17757
+ }
17758
+ });
17759
+
17760
+ realNames[createHybrid(undefined, WRAP_BIND_KEY_FLAG).name] = [{
17761
+ 'name': 'wrapper',
17762
+ 'func': undefined
17763
+ }];
17764
+
17765
+ // Add methods to `LazyWrapper`.
17766
+ LazyWrapper.prototype.clone = lazyClone;
17767
+ LazyWrapper.prototype.reverse = lazyReverse;
17768
+ LazyWrapper.prototype.value = lazyValue;
17769
+
17770
+ // Add chain sequence methods to the `lodash` wrapper.
17771
+ lodash.prototype.at = wrapperAt;
17772
+ lodash.prototype.chain = wrapperChain;
17773
+ lodash.prototype.commit = wrapperCommit;
17774
+ lodash.prototype.next = wrapperNext;
17775
+ lodash.prototype.plant = wrapperPlant;
17776
+ lodash.prototype.reverse = wrapperReverse;
17777
+ lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
17778
+
17779
+ // Add lazy aliases.
17780
+ lodash.prototype.first = lodash.prototype.head;
17781
+
17782
+ if (symIterator) {
17783
+ lodash.prototype[symIterator] = wrapperToIterator;
17784
+ }
17785
+ return lodash;
17786
+ });
17787
+
17788
+ /*--------------------------------------------------------------------------*/
17789
+
17790
+ // Export lodash.
17791
+ var _ = runInContext();
17792
+
17793
+ // Some AMD build optimizers, like r.js, check for condition patterns like:
17794
+ if (true) {
17795
+ // Expose Lodash on the global object to prevent errors when Lodash is
17796
+ // loaded by a script tag in the presence of an AMD loader.
17797
+ // See http://requirejs.org/docs/errors.html#mismatch for more details.
17798
+ // Use `_.noConflict` to remove Lodash from the global object.
17799
+ root._ = _;
17800
+
17801
+ // Define as an anonymous module so, through path mapping, it can be
17802
+ // referenced as the "underscore" module.
17803
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
17804
+ return _;
17805
+ }).call(exports, __webpack_require__, exports, module),
17806
+ __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
17807
+ }
17808
+ // Check for `exports` after `define` in case a build optimizer adds it.
17809
+ else {}
17810
+ }.call(this));
17811
+
17812
+ /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(14)(module)))
17813
+
17814
+ /***/ }),
17815
+ /* 10 */
17816
+ /***/ (function(module, exports, __webpack_require__) {
17817
+
17818
+ "use strict";
17819
+
17820
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17821
+ if (k2 === undefined) k2 = k;
17822
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
17823
+ }) : (function(o, m, k, k2) {
17824
+ if (k2 === undefined) k2 = k;
17825
+ o[k2] = m[k];
17826
+ }));
17827
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17828
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17829
+ }) : function(o, v) {
17830
+ o["default"] = v;
17831
+ });
17832
+ var __importStar = (this && this.__importStar) || function (mod) {
17833
+ if (mod && mod.__esModule) return mod;
17834
+ var result = {};
17835
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
17836
+ __setModuleDefault(result, mod);
17837
+ return result;
17838
+ };
17839
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17840
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
17841
+ return new (P || (P = Promise))(function (resolve, reject) {
17842
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17843
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17844
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
17845
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
17846
+ });
17847
+ };
17848
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17849
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17850
+ };
17851
+ Object.defineProperty(exports, "__esModule", { value: true });
17852
+ exports.countTableRecord = exports.getTableRecord = exports.searchTableRecord = exports.MAX_PAGE_RECORD_NUMBER = exports.parseIntParam = exports.deleteTableRecord = exports.updateTableRecord = exports.createTableRecord = exports.getTableColumnName = exports.escapeIdentifier = exports.escapeIdentifierStr = void 0;
17853
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
17854
+ const AuthDecision_1 = __webpack_require__(11);
17855
+ const lodash_1 = __webpack_require__(9);
17856
+ const ServerError_1 = __importDefault(__webpack_require__(17));
17857
+ /**
17858
+ * Escape SQL identifier string
17859
+ * Although postgreSQL does allow non-ASCII characters in identifiers, to make it simple, we will remove any non-ASCII characters.
17860
+ *
17861
+ * @export
17862
+ * @param {string} idStr
17863
+ * @return {*} {string}
17864
+ */
17865
+ function escapeIdentifierStr(idStr) {
17866
+ return '"' + idStr.replace(/[^\x20-\x7e]/g, "").replace(/"/g, "\"'") + '"';
17867
+ }
17868
+ exports.escapeIdentifierStr = escapeIdentifierStr;
17869
+ /**
17870
+ * Escape SQL identifier (e.g. column names, or table names).
17871
+ * `xxx."ss.dd` will be escaped as `"xxx"."""ss"."dd"`
17872
+ * Although postgreSQL does allow non-ASCII characters in identifiers, to make it simple, we will remove any non-ASCII characters.
17873
+ *
17874
+ * @export
17875
+ * @param {string} id
17876
+ * @return {*} {SQLSyntax}
17877
+ */
17878
+ function escapeIdentifier(id) {
17879
+ const sanitisedIdStr = id.replace(/[^\x20-\x7e]/g, "");
17880
+ const parts = sanitisedIdStr.split(".");
17881
+ const escapedIdStr = parts.length > 1
17882
+ ? parts.map((item) => escapeIdentifierStr(item)).join(".")
17883
+ : escapeIdentifierStr(sanitisedIdStr);
17884
+ return sql_syntax_1.default.createUnsafely(escapedIdStr);
17885
+ }
17886
+ exports.escapeIdentifier = escapeIdentifier;
17887
+ /**
17888
+ * Make a postgreSQL identifier in SQLSyntax from tableRef (optional) & column name.
17889
+ *
17890
+ * @export
17891
+ * @param {String} columnName
17892
+ * @param {String} [tableRef=""]
17893
+ * @param {Boolean} [useLowerCaseColumnName=true]
17894
+ * @return {*} {SQLSyntax}
17895
+ */
17896
+ function getTableColumnName(columnName, tableRef = "", useLowerCaseColumnName = false) {
17897
+ const id = [
17898
+ tableRef,
17899
+ useLowerCaseColumnName ? columnName.toLowerCase() : columnName
17900
+ ]
17901
+ .filter((item) => item)
17902
+ .join(".");
17903
+ return escapeIdentifier(id);
17904
+ }
17905
+ exports.getTableColumnName = getTableColumnName;
17906
+ /**
17907
+ * Create a record for given table with given data object.
17908
+ * This method will use the key / value pairs of the object as column name / value of the new record.
17909
+ * It will return the newly created record
17910
+ *
17911
+ * @export
17912
+ * @param {pg.Client | pg.Pool} poolOrClient
17913
+ * @param {string} table
17914
+ * @param {{ [key: string]: Value }} data
17915
+ * @return {*}
17916
+ */
17917
+ function createTableRecord(poolOrClient, table, data, allowFieldList, autoGenerateUuid = true) {
17918
+ return __awaiter(this, void 0, void 0, function* () {
17919
+ if (!table.trim()) {
17920
+ throw new Error("invalid empty table name is supplied.");
17921
+ }
17922
+ if (allowFieldList === null || allowFieldList === void 0 ? void 0 : allowFieldList.length) {
17923
+ const keys = Object.keys(data);
17924
+ const diff = lodash_1.difference(keys, allowFieldList);
17925
+ if (diff === null || diff === void 0 ? void 0 : diff.length) {
17926
+ throw new ServerError_1.default(`Failed to create record, the following fields are not allowed: ${diff.join(",")}`, 400);
17927
+ }
17928
+ }
17929
+ if (autoGenerateUuid) {
17930
+ data["id"] = sql_syntax_1.sqls `uuid_generate_v4()`;
17931
+ }
17932
+ const [fieldList, valueList] = Object.keys(data).reduce((result, currentKey) => {
17933
+ const currentValue = data[currentKey];
17934
+ result[0].push(escapeIdentifier(currentKey));
17935
+ result[1].push(sql_syntax_1.sqls `${currentValue}`);
17936
+ return result;
17937
+ }, [[], []]);
17938
+ const sqlSyntax = sql_syntax_1.sqls `INSERT INTO ${escapeIdentifier(table)}
17939
+ (${sql_syntax_1.default.csv(...fieldList)})
17940
+ VALUES
17941
+ (${sql_syntax_1.default.csv(...valueList)})
17942
+ RETURNING *`;
17943
+ const result = yield poolOrClient.query(...sqlSyntax.toQuery());
17944
+ return result.rows[0];
17945
+ });
17946
+ }
17947
+ exports.createTableRecord = createTableRecord;
17948
+ function updateTableRecord(poolOrClient, table, id, data, allowFieldList) {
17949
+ return __awaiter(this, void 0, void 0, function* () {
17950
+ if (!id.trim()) {
17951
+ throw new ServerError_1.default("Failed to delete the record: empty id was provided.", 400);
17952
+ }
17953
+ if (!table.trim()) {
17954
+ throw new ServerError_1.default("invalid empty table name is supplied.", 500);
17955
+ }
17956
+ if (allowFieldList === null || allowFieldList === void 0 ? void 0 : allowFieldList.length) {
17957
+ const keys = Object.keys(data);
17958
+ const diff = lodash_1.difference(keys, allowFieldList);
17959
+ if (diff === null || diff === void 0 ? void 0 : diff.length) {
17960
+ throw new ServerError_1.default(`Failed to update record, the following fields are not allowed: ${diff.join(",")}`, 400);
17961
+ }
17962
+ }
17963
+ const updates = Object.keys(data).reduce((result, currentKey) => {
17964
+ const currentValue = data[currentKey];
17965
+ result.push(sql_syntax_1.sqls `${escapeIdentifier(currentKey)} = ${sql_syntax_1.sqls `${currentValue}`}`);
17966
+ return result;
17967
+ }, []);
17968
+ const sqlSyntax = sql_syntax_1.sqls `UPDATE ${escapeIdentifier(table)}
17969
+ SET ${sql_syntax_1.default.csv(...updates)}
17970
+ WHERE id = ${id}
17971
+ RETURNING *`;
17972
+ const result = yield poolOrClient.query(...sqlSyntax.toQuery());
17973
+ return result.rows[0];
17974
+ });
17975
+ }
17976
+ exports.updateTableRecord = updateTableRecord;
17977
+ function deleteTableRecord(poolOrClient, table, id) {
17978
+ return __awaiter(this, void 0, void 0, function* () {
17979
+ if (!id.trim()) {
17980
+ throw new ServerError_1.default("Failed to delete the record: empty id was provided.", 400);
17981
+ }
17982
+ if (!table.trim()) {
17983
+ throw new ServerError_1.default("invalid empty table name is supplied.", 500);
17984
+ }
17985
+ const sqlSyntax = sql_syntax_1.sqls `DELETE FROM ${escapeIdentifier(table)} WHERE id = ${id}`;
17986
+ yield poolOrClient.query(...sqlSyntax.toQuery());
17987
+ });
17988
+ }
17989
+ exports.deleteTableRecord = deleteTableRecord;
17990
+ function parseIntParam(p) {
17991
+ if (!p) {
17992
+ return 0;
17993
+ }
17994
+ const result = parseInt(p === null || p === void 0 ? void 0 : p.toString());
17995
+ if (isNaN(result)) {
17996
+ return 0;
17997
+ }
17998
+ return result;
17999
+ }
18000
+ exports.parseIntParam = parseIntParam;
18001
+ exports.MAX_PAGE_RECORD_NUMBER = 500;
18002
+ function searchTableRecord(poolOrClient, table, contiditions = [], queryConfig) {
18003
+ var _a, _b, _c;
18004
+ return __awaiter(this, void 0, void 0, function* () {
18005
+ if (!table.trim()) {
18006
+ throw new ServerError_1.default("invalid empty table name is supplied.");
18007
+ }
18008
+ const objectKind = (queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.objectKind) ? queryConfig.objectKind
18009
+ : "authObject";
18010
+ const authDecision = (queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.authDecision) ? queryConfig.authDecision
18011
+ : AuthDecision_1.UnconditionalTrueDecision;
18012
+ let limit = parseIntParam(queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.limit);
18013
+ const offset = parseIntParam(queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.offset);
18014
+ if (limit > exports.MAX_PAGE_RECORD_NUMBER) {
18015
+ limit = exports.MAX_PAGE_RECORD_NUMBER;
18016
+ }
18017
+ const config = (queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.toSqlConfig) ? queryConfig.toSqlConfig
18018
+ : {
18019
+ prefixes: [
18020
+ `input.${objectKind}.${lodash_1.camelCase(table.replace(/s$/, ""))}`
18021
+ ]
18022
+ };
18023
+ const authConditions = authDecision.toSql(config);
18024
+ const where = sql_syntax_1.default.where(sql_syntax_1.default.joinWithAnd([...contiditions, authConditions]));
18025
+ const sqlSyntax = sql_syntax_1.sqls `SELECT ${(queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.selectedFields) ? sql_syntax_1.default.csv(...queryConfig.selectedFields)
18026
+ : sql_syntax_1.sqls `*`}
18027
+ FROM ${escapeIdentifier(table)}
18028
+ ${((_a = queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.leftJoins) === null || _a === void 0 ? void 0 : _a.length) ? sql_syntax_1.default.join(queryConfig.leftJoins.map((joinItem) => sql_syntax_1.sqls `LEFT JOIN ${escapeIdentifier(joinItem.table)} ON ${joinItem.joinCondition}`), sql_syntax_1.sqls `\n`)
18029
+ : sql_syntax_1.default.empty}
18030
+ ${where}
18031
+ ${(queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.groupBy) ? sql_syntax_1.sqls `GROUP BY ${typeof ((_b = queryConfig.groupBy) === null || _b === void 0 ? void 0 : _b.length) === "number"
18032
+ ? sql_syntax_1.default.csv(...queryConfig.groupBy)
18033
+ : queryConfig.groupBy}`
18034
+ : sql_syntax_1.default.empty}
18035
+ ${offset ? sql_syntax_1.sqls `OFFSET ${offset}` : sql_syntax_1.default.empty}
18036
+ ${limit ? sql_syntax_1.sqls `LIMIT ${limit}` : sql_syntax_1.default.empty}
18037
+ `;
18038
+ const result = yield poolOrClient.query(...sqlSyntax.toQuery());
18039
+ if (!((_c = result === null || result === void 0 ? void 0 : result.rows) === null || _c === void 0 ? void 0 : _c.length)) {
18040
+ return [];
18041
+ }
18042
+ else {
18043
+ return result.rows;
18044
+ }
18045
+ });
18046
+ }
18047
+ exports.searchTableRecord = searchTableRecord;
18048
+ function getTableRecord(poolOrClient, table, id, authDecision = AuthDecision_1.UnconditionalTrueDecision, objectKind = "authObject", toSqlConfig) {
18049
+ return __awaiter(this, void 0, void 0, function* () {
18050
+ const records = yield searchTableRecord(poolOrClient, table, [sql_syntax_1.sqls `id = ${id}`], {
18051
+ authDecision,
18052
+ objectKind,
18053
+ toSqlConfig
18054
+ });
18055
+ if (!records.length) {
18056
+ return null;
18057
+ }
18058
+ else {
18059
+ return records[0];
18060
+ }
18061
+ });
18062
+ }
18063
+ exports.getTableRecord = getTableRecord;
18064
+ function countTableRecord(poolOrClient, table, contiditions = [], authDecision, objectKind, toSqlConfig) {
18065
+ return __awaiter(this, void 0, void 0, function* () {
18066
+ const records = yield searchTableRecord(poolOrClient, table, contiditions, {
18067
+ authDecision,
18068
+ objectKind,
18069
+ toSqlConfig,
18070
+ selectedFields: [sql_syntax_1.sqls `COUNT(*) AS total`]
18071
+ });
18072
+ if (!records.length) {
18073
+ return 0;
18074
+ }
18075
+ else {
18076
+ return records[0]["total"];
18077
+ }
18078
+ });
18079
+ }
18080
+ exports.countTableRecord = countTableRecord;
18081
+ //# sourceMappingURL=SQLUtils.js.map
18082
+
18083
+ /***/ }),
18084
+ /* 11 */
18085
+ /***/ (function(module, exports, __webpack_require__) {
18086
+
18087
+ "use strict";
18088
+
18089
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18090
+ if (k2 === undefined) k2 = k;
18091
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
18092
+ }) : (function(o, m, k, k2) {
18093
+ if (k2 === undefined) k2 = k;
18094
+ o[k2] = m[k];
18095
+ }));
18096
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18097
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18098
+ }) : function(o, v) {
18099
+ o["default"] = v;
18100
+ });
18101
+ var __importStar = (this && this.__importStar) || function (mod) {
18102
+ if (mod && mod.__esModule) return mod;
18103
+ var result = {};
18104
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18105
+ __setModuleDefault(result, mod);
18106
+ return result;
18107
+ };
18108
+ Object.defineProperty(exports, "__esModule", { value: true });
18109
+ exports.ConciseOperand = exports.ConciseExpression = exports.ConciseRule = exports.UnconditionalFalseDecision = exports.UnconditionalTrueDecision = exports.isTrueEquivalent = void 0;
18110
+ const AspectQuery_1 = __webpack_require__(16);
18111
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
18112
+ class AuthDecision {
18113
+ constructor(hasResidualRules, residualRules, result, hasWarns = false, warns = [], unknowns) {
18114
+ if (typeof hasResidualRules !== "boolean") {
18115
+ throw new Error("Failed to create AuthDecision: invalid hasResidualRules type");
18116
+ }
18117
+ if (hasResidualRules && !(residualRules === null || residualRules === void 0 ? void 0 : residualRules.length)) {
18118
+ throw new Error("Failed to create AuthDecision: residualRules must have at least one item when hasResidualRules == true");
18119
+ }
18120
+ this.hasResidualRules = hasResidualRules;
18121
+ this.result = result;
18122
+ this.residualRules = residualRules;
18123
+ this.hasWarns = hasWarns;
18124
+ this.warns = warns;
18125
+ this.unknowns = unknowns;
18126
+ }
18127
+ static fromJson(data) {
18128
+ var _a;
18129
+ return new AuthDecision(data === null || data === void 0 ? void 0 : data.hasResidualRules, (_a = data === null || data === void 0 ? void 0 : data.residualRules) === null || _a === void 0 ? void 0 : _a.map((item) => ConciseRule.fromJson(item)), data === null || data === void 0 ? void 0 : data.result, (data === null || data === void 0 ? void 0 : data.hasWarns) ? true : false, data === null || data === void 0 ? void 0 : data.warns, data === null || data === void 0 ? void 0 : data.unknowns);
18130
+ }
18131
+ toAspectQueryGroups(prefixes) {
18132
+ if (this.hasResidualRules) {
18133
+ return this.residualRules.map((item) => item.toAspectQueryGroup(prefixes));
18134
+ }
18135
+ else {
18136
+ if (isTrueEquivalent(this.result)) {
18137
+ // unconditional true
18138
+ return [new AspectQuery_1.AspectQueryGroup([new AspectQuery_1.AspectQueryTrue()])];
18139
+ }
18140
+ else {
18141
+ return [new AspectQuery_1.AspectQueryGroup([new AspectQuery_1.AspectQueryFalse()])];
18142
+ }
18143
+ }
18144
+ }
18145
+ toSql(config) {
18146
+ return sql_syntax_1.default.joinWithOr(this.toAspectQueryGroups(config.prefixes).map((item) => item.toSql(config))).roundBracket();
18147
+ }
18148
+ }
18149
+ exports.default = AuthDecision;
18150
+ function isTrueEquivalent(value) {
18151
+ const typeStr = typeof value;
18152
+ if (typeStr === "boolean") {
18153
+ return value;
18154
+ }
18155
+ else if (typeStr === "undefined") {
18156
+ return false;
18157
+ }
18158
+ else if (typeof (value === null || value === void 0 ? void 0 : value.length) !== "undefined") {
18159
+ return !!value.length;
18160
+ }
18161
+ else {
18162
+ return !!value;
18163
+ }
18164
+ }
18165
+ exports.isTrueEquivalent = isTrueEquivalent;
18166
+ exports.UnconditionalTrueDecision = new AuthDecision(false, undefined, true);
18167
+ exports.UnconditionalFalseDecision = new AuthDecision(false, undefined, false);
18168
+ class ConciseRule {
18169
+ constructor(fullName, name, value, expressions, isDefault = false) {
18170
+ if (!isDefault && !(expressions === null || expressions === void 0 ? void 0 : expressions.length)) {
18171
+ throw new Error("Invalid ConciseRule data: it must contain at least one ConciseExpression item unless it's a default rule.");
18172
+ }
18173
+ this.default = isDefault ? true : false;
18174
+ this.fullName = fullName;
18175
+ this.name = name;
18176
+ this.value = value;
18177
+ this.expressions = (expressions === null || expressions === void 0 ? void 0 : expressions.length) ? expressions : [];
18178
+ }
18179
+ static fromJson(data) {
18180
+ var _a;
18181
+ return new ConciseRule(data === null || data === void 0 ? void 0 : data.fullName, data === null || data === void 0 ? void 0 : data.name, data === null || data === void 0 ? void 0 : data.value, (_a = data === null || data === void 0 ? void 0 : data.expressions) === null || _a === void 0 ? void 0 : _a.map((item) => ConciseExpression.fromJson(item)));
18182
+ }
18183
+ toAspectQueryGroup(prefixes) {
18184
+ var _a;
18185
+ return new AspectQuery_1.AspectQueryGroup((_a = this.expressions) === null || _a === void 0 ? void 0 : _a.map((item) => item.toAspectQuery(prefixes)), true, !isTrueEquivalent(this.value));
18186
+ }
18187
+ }
18188
+ exports.ConciseRule = ConciseRule;
18189
+ class ConciseExpression {
18190
+ constructor(operands, operator, negated = false) {
18191
+ var _a, _b;
18192
+ this.negated = negated ? true : false;
18193
+ this.operands = operands;
18194
+ this.operator = operator;
18195
+ if (!((_a = this.operands) === null || _a === void 0 ? void 0 : _a.length)) {
18196
+ throw new Error("invalid ConciseExpression data: it must have at least one operand.");
18197
+ }
18198
+ if (((_b = this.operands) === null || _b === void 0 ? void 0 : _b.length) > 1 && typeof operator !== "string") {
18199
+ throw new Error("invalid ConciseExpression data: when operands number > 1, operator must be a valid string value.");
18200
+ }
18201
+ }
18202
+ static fromJson(data) {
18203
+ var _a;
18204
+ return new ConciseExpression((_a = data === null || data === void 0 ? void 0 : data.operands) === null || _a === void 0 ? void 0 : _a.map((item) => ConciseOperand.fromJson(item)), data === null || data === void 0 ? void 0 : data.operator, data === null || data === void 0 ? void 0 : data.negated);
18205
+ }
18206
+ getSqlOperator() {
18207
+ switch (this.operator) {
18208
+ case "=":
18209
+ return sql_syntax_1.sqls `=`;
18210
+ case ">":
18211
+ return sql_syntax_1.sqls `>`;
18212
+ case "<":
18213
+ return sql_syntax_1.sqls `<`;
18214
+ case ">=":
18215
+ return sql_syntax_1.sqls `>=`;
18216
+ case "<=":
18217
+ return sql_syntax_1.sqls `<=`;
18218
+ default:
18219
+ throw new Error(`Failed to convert auth decision operator to SQL operator: unsupported operator: ${this.operator}`);
18220
+ }
18221
+ }
18222
+ toAspectQuery(prefixes) {
18223
+ var _a, _b;
18224
+ if (((_a = this.operands) === null || _a === void 0 ? void 0 : _a.length) == 1) {
18225
+ const [aspectId, path, isCollection] = this.operands[0].extractAspectIdAndPath(prefixes);
18226
+ if (isCollection) {
18227
+ return new AspectQuery_1.AspectQueryArrayNotEmpty(aspectId, path, this.negated);
18228
+ }
18229
+ else {
18230
+ return new AspectQuery_1.AspectQueryExists(aspectId, path, this.negated);
18231
+ }
18232
+ }
18233
+ else if (((_b = this.operands) === null || _b === void 0 ? void 0 : _b.length) == 2) {
18234
+ const refOperand = this.operands.find((item) => item.isRef);
18235
+ const valOperand = this.operands.find((item) => !item.isRef);
18236
+ if (!valOperand) {
18237
+ throw new Error("Failed to convert auth decision expression to AspectQuery: " +
18238
+ `expression with both terms are references is currently not supported. Expression: ${this}`);
18239
+ }
18240
+ if (!refOperand) {
18241
+ // it's unlikely both terms are values as our decision API has already done the evaluation for this case.
18242
+ throw new Error("Failed to convert auth decision expression to AspectQuery: " +
18243
+ `Terms shouldn't be both value. Expression: ${this}`);
18244
+ }
18245
+ const [aspectId, path, isCollection] = refOperand.extractAspectIdAndPath(prefixes);
18246
+ if (isCollection && this.operator != "=") {
18247
+ throw new Error("Failed to convert auth decision expression to AspectQuery: " +
18248
+ `Only \`=\` operator is supported for collection reference. Expression: ${this}`);
18249
+ }
18250
+ if (isCollection && this.operator == "=") {
18251
+ return new AspectQuery_1.AspectQueryValueInArray(valOperand.toAspectQueryValue(), aspectId, path, this.negated);
18252
+ }
18253
+ else {
18254
+ return new AspectQuery_1.AspectQueryWithValue(valOperand.toAspectQueryValue(), this.getSqlOperator(), this.operands[0].isRef, aspectId, path, this.negated);
18255
+ }
18256
+ }
18257
+ else {
18258
+ throw new Error(`Failed to convert auth decision expression to AspectQuery: more than 2 operands found. Expression: ${this}`);
18259
+ }
18260
+ }
18261
+ }
18262
+ exports.ConciseExpression = ConciseExpression;
18263
+ class ConciseOperand {
18264
+ constructor(isRef, value) {
18265
+ if (typeof isRef !== "boolean") {
18266
+ throw new Error("Invalid ConciseOperand data: isRef must be a boolean value.");
18267
+ }
18268
+ this.isRef = isRef;
18269
+ if (isRef && typeof value !== "string") {
18270
+ throw new Error("Invalid ConciseOperand data: when `isRef`== true, `value` must be a string value (ref string).");
18271
+ }
18272
+ this.value = value;
18273
+ }
18274
+ static fromJson(data) {
18275
+ return new ConciseOperand(data === null || data === void 0 ? void 0 : data.isRef, data === null || data === void 0 ? void 0 : data.value);
18276
+ }
18277
+ refString() {
18278
+ if (!this.isRef) {
18279
+ throw new Error("Cannot convert non-ref term to a ref string");
18280
+ }
18281
+ else {
18282
+ if (typeof this.value === "string") {
18283
+ return this.value;
18284
+ }
18285
+ else {
18286
+ throw new Error(`ref term has non-string type value: ${this.value}`);
18287
+ }
18288
+ }
18289
+ }
18290
+ isCollectionRef() {
18291
+ return this.refString().endsWith("[_]");
18292
+ }
18293
+ refStringWithoutPrefixes(prefixes) {
18294
+ const sortedPrefixes = prefixes.sort((a, b) => b.length - a.length);
18295
+ return sortedPrefixes.reduce((ref, prefix) => {
18296
+ if (ref.startsWith(prefix)) {
18297
+ return ref.substring(prefix.length);
18298
+ }
18299
+ else {
18300
+ return ref;
18301
+ }
18302
+ }, this.refString());
18303
+ }
18304
+ extractAspectIdAndPath(prefixes) {
18305
+ // make it work for both "input.object.record" & "input.object.record." prefixe input
18306
+ // we remove the first leading `.` char (if any)
18307
+ let ref = this.refStringWithoutPrefixes(prefixes).replace(/^\./, "");
18308
+ const isCollection = this.isCollectionRef();
18309
+ if (isCollection) {
18310
+ ref = ref.replace(/\[_\]$/, "");
18311
+ }
18312
+ const parts = ref.split(".").filter((item) => item);
18313
+ if (parts.length < 2) {
18314
+ return [ref, [], isCollection];
18315
+ }
18316
+ else {
18317
+ return [parts[0], parts.slice(1, parts.length), isCollection];
18318
+ }
18319
+ }
18320
+ toAspectQueryValue() {
18321
+ if (this.isRef) {
18322
+ throw new Error(`Attempt to covert reference \`Operand\` to \`AspectQueryValue\`: ${this}`);
18323
+ }
18324
+ return new AspectQuery_1.AspectQueryValue(this.value);
18325
+ }
18326
+ }
18327
+ exports.ConciseOperand = ConciseOperand;
18328
+ //# sourceMappingURL=AuthDecision.js.map
18329
+
18330
+ /***/ }),
18331
+ /* 12 */,
18332
+ /* 13 */
18333
+ /***/ (function(module, exports, __webpack_require__) {
18334
+
18335
+ "use strict";
18336
+
18337
+ var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) {
18338
+ if (k2 === undefined) k2 = k;
18339
+ Object.defineProperty(o, k2, { enumerable: true, get: function () {return m[k];} });
18340
+ } : function (o, m, k, k2) {
18341
+ if (k2 === undefined) k2 = k;
18342
+ o[k2] = m[k];
18343
+ });
18344
+ var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) {
18345
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18346
+ } : function (o, v) {
18347
+ o["default"] = v;
18348
+ });
18349
+ var __importStar = this && this.__importStar || function (mod) {
18350
+ if (mod && mod.__esModule) return mod;
18351
+ var result = {};
18352
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18353
+ __setModuleDefault(result, mod);
18354
+ return result;
18355
+ };
18356
+ var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) {
18357
+ function adopt(value) {return value instanceof P ? value : new P(function (resolve) {resolve(value);});}
18358
+ return new (P || (P = Promise))(function (resolve, reject) {
18359
+ function fulfilled(value) {try {step(generator.next(value));} catch (e) {reject(e);}}
18360
+ function rejected(value) {try {step(generator["throw"](value));} catch (e) {reject(e);}}
18361
+ function step(result) {result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);}
18362
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
18363
+ });
18364
+ };
18365
+ var __importDefault = this && this.__importDefault || function (mod) {
18366
+ return mod && mod.__esModule ? mod : { "default": mod };
18367
+ };
18368
+ Object.defineProperty(exports, "__esModule", { value: true });
18369
+ exports.NodeNotFoundError = void 0;
18370
+ const lodash_1 = __importDefault(__webpack_require__(9));
18371
+ const tsmonad_1 = __webpack_require__(15);
18372
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
18373
+ const SQLUtils_1 = __webpack_require__(10);
18374
+ const AuthDecision_1 = __webpack_require__(11);
18375
+ const textTree = __webpack_require__(18);
18376
+ class NodeNotFoundError extends Error {}
18377
+
18378
+ exports.NodeNotFoundError = NodeNotFoundError;
18379
+ function isNonEmptyArray(v) {
18380
+ if (!v || !lodash_1.default.isArray(v) || !v.length)
18381
+ return false;
18382
+ return true;
18383
+ }
18384
+ const INVALID_CHAR_REGEX = /[^a-z_\d]/i;
18385
+ function isValidSqlIdentifier(id) {
18386
+ if (INVALID_CHAR_REGEX.test(id))
18387
+ return false;
18388
+ return true;
18389
+ }
18390
+ class NestedSetModelQueryer {
18391
+ /**
18392
+ * Creates an instance of NestedSetModelQueryer.
18393
+ * @param {pg.Pool} dbPool
18394
+ * @param {string} tableName
18395
+ * @param {string[]} [defaultSelectFieldList=null] default select fields; If null, all fields (i.e. `SELECT "id", "name"`) will be returned
18396
+ * @memberof NestedSetModelQueryer
18397
+ */
18398
+ constructor(dbPool, tableName, defaultSelectFieldList = null, defaultInsertFieldList = null) {
18399
+ /**
18400
+ * default select fields if [], all fields (i.e. `SELECT "id", "name"`) will be returned
18401
+ *
18402
+ * @type {string[]}
18403
+ * @memberof NestedSetModelQueryer
18404
+ */
18405
+ this.defaultSelectFieldList = ["id", "name"];
18406
+ /**
18407
+ * Default field list that will be used when insert nodes into tree.
18408
+ * By default, only `name` field will be saved to database
18409
+ * e.g. If your tree nodes have three properties (besides `id`, `left`, `right` --- they auto generated):
18410
+ * - name
18411
+ * - description
18412
+ * - fullName
18413
+ *
18414
+ * Then you should set `defaultInsertFieldList` to ["name", "description", "fullName"]
18415
+ *
18416
+ * @type {string[]}
18417
+ * @memberof NestedSetModelQueryer
18418
+ */
18419
+ this.defaultInsertFieldList = ["name"];
18420
+ if (!dbPool)
18421
+ throw new Error("dbPool cannot be empty!");
18422
+ if (!tableName)
18423
+ throw new Error("tableName cannot be empty!");
18424
+ if (!isValidSqlIdentifier(tableName)) {
18425
+ throw new Error(`tableName: ${tableName} contains invalid characters!`);
18426
+ }
18427
+ this.pool = dbPool;
18428
+ this.tableName = tableName;
18429
+ if (defaultSelectFieldList) {
18430
+ if (!lodash_1.default.isArray(defaultSelectFieldList))
18431
+ throw new Error("defaultSelectFieldList should be an array");
18432
+ this.defaultSelectFieldList = defaultSelectFieldList;
18433
+ }
18434
+ if (defaultSelectFieldList) {
18435
+ if (!lodash_1.default.isArray(defaultSelectFieldList))
18436
+ throw new Error("defaultSelectFieldList should be an array");
18437
+ this.defaultSelectFieldList = defaultSelectFieldList;
18438
+ }
18439
+ if (defaultInsertFieldList) {
18440
+ if (!lodash_1.default.isArray(defaultInsertFieldList))
18441
+ throw new Error("defaultInsertFieldList should be an array");
18442
+ this.defaultInsertFieldList = defaultInsertFieldList;
18443
+ }
18444
+ }
18445
+ selectFields(tableAliasOrName = "", fields = null) {
18446
+ const fieldList = isNonEmptyArray(fields) ?
18447
+ fields :
18448
+ this.defaultSelectFieldList;
18449
+ if (!isNonEmptyArray(fieldList)) {
18450
+ return sql_syntax_1.sqls`*`;
18451
+ }
18452
+ if (!isValidSqlIdentifier(tableAliasOrName)) {
18453
+ throw new Error(`'tableAliasOrName' ${tableAliasOrName} contains invalid characters.`);
18454
+ }
18455
+ // --- do not double quote `tableAliasOrName`
18456
+ // --- or you will get missing FROM-clause entry for table error
18457
+ return sql_syntax_1.default.createUnsafely(fieldList.
18458
+ map(f => {
18459
+ if (!isValidSqlIdentifier(f)) {
18460
+ throw new Error(`Field name ${f} contains invalid characters.`);
18461
+ }
18462
+ return tableAliasOrName === "" ?
18463
+ `"${f}"` :
18464
+ `${tableAliasOrName}."${f}"`;
18465
+ }).
18466
+ join(", "));
18467
+ }
18468
+ /**
18469
+ * Get nodes by name
18470
+ * You hardly need this one --- only for write test case (you can get a id from name)
18471
+ *
18472
+ * @param {string} name
18473
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18474
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18475
+ * @returns {Promise<NodeRecord[]>}
18476
+ * @memberof NestedSetModelQueryer
18477
+ */
18478
+ getNodes(nodesQuery = {}, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18479
+ return __awaiter(this, void 0, void 0, function* () {
18480
+ const authConditions = authDecision.toSql({
18481
+ prefixes: ["input.authObject.orgUnit"] });
18482
+
18483
+ const clauses = [
18484
+ authConditions,
18485
+ nodesQuery.name ?
18486
+ sql_syntax_1.sqls`"name" = ${nodesQuery.name}` :
18487
+ sql_syntax_1.default.empty,
18488
+ nodesQuery.leafNodesOnly ?
18489
+ sql_syntax_1.sqls`"left" = ( "right" - 1 )` :
18490
+ sql_syntax_1.default.empty];
18491
+
18492
+ const whereClause = sql_syntax_1.default.where(sql_syntax_1.default.joinWithAnd(clauses));
18493
+ const query = sql_syntax_1.sqls`SELECT ${this.selectFields("", fields)} FROM ${SQLUtils_1.escapeIdentifier(this.tableName)} ${whereClause}`;
18494
+ const result = yield (client ? client : this.pool).query(...query.toQuery());
18495
+ if (!result || !result.rows || !result.rows.length)
18496
+ return [];
18497
+ return result.rows;
18498
+ });
18499
+ }
18500
+ /**
18501
+ *
18502
+ * Get a node by its id
18503
+ * @param {string} id
18504
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18505
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18506
+ * @returns {Promise<NodeRecord>}
18507
+ * @memberof NestedSetModelQueryer
18508
+ */
18509
+ getNodeById(id, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18510
+ return __awaiter(this, void 0, void 0, function* () {
18511
+ const authConditions = authDecision.toSql({
18512
+ prefixes: ["input.authObject.orgUnit"] });
18513
+
18514
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("", fields)} FROM ${SQLUtils_1.escapeIdentifier(this.tableName)} WHERE ${sql_syntax_1.default.joinWithAnd([
18515
+ sql_syntax_1.sqls`"id" = ${id}`,
18516
+ authConditions])
18517
+ }`.toQuery());
18518
+ if (!result || !result.rows || !result.rows.length)
18519
+ return tsmonad_1.Maybe.nothing();
18520
+ return tsmonad_1.Maybe.just(result.rows[0]);
18521
+ });
18522
+ }
18523
+ /**
18524
+ * Get the root node of the tree
18525
+ * Return null if empty tree or the user has no access to the root node
18526
+ *
18527
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18528
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18529
+ * @returns {Promise<NodeRecord>}
18530
+ * @memberof NestedSetModelQueryer
18531
+ */
18532
+ getRootNode(fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18533
+ return __awaiter(this, void 0, void 0, function* () {
18534
+ const authConditions = authDecision.toSql({
18535
+ prefixes: ["input.authObject.orgUnit"] });
18015
18536
 
18016
- mixin(lodash, (function() {
18017
- var source = {};
18018
- baseForOwn(lodash, function(func, methodName) {
18019
- if (!hasOwnProperty.call(lodash.prototype, methodName)) {
18020
- source[methodName] = func;
18021
- }
18022
- });
18023
- return source;
18024
- }()), { 'chain': false });
18537
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("", fields)} FROM ${SQLUtils_1.escapeIdentifier(this.tableName)} WHERE ${sql_syntax_1.default.joinWithAnd([
18538
+ sql_syntax_1.sqls`"left" = 1`,
18539
+ authConditions])
18540
+ }`.toQuery());
18541
+ if (!result || !result.rows || !result.rows.length)
18542
+ return tsmonad_1.Maybe.nothing();
18543
+ return tsmonad_1.Maybe.just(result.rows[0]);
18544
+ });
18545
+ }
18546
+ /**
18547
+ * Get All children of a given node
18548
+ * (including immediate children and children of immediate children etc.)
18549
+ * If the node has no child (i.e. a leaf node), an empty array will be returned
18550
+ *
18551
+ * @param {string} parentNodeId
18552
+ * @param {boolean} [includeMyself=false]
18553
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18554
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18555
+ * @returns {Promise<NodeRecord[]>}
18556
+ * @memberof NestedSetModelQueryer
18557
+ */
18558
+ getAllChildren(parentNodeId, includeMyself = false, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18559
+ return __awaiter(this, void 0, void 0, function* () {
18560
+ const authConditions = authDecision.toSql({
18561
+ prefixes: ["input.authObject.orgUnit"],
18562
+ tableRef: "children" });
18563
+
18564
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18565
+ const conditions = [
18566
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`>=` : sql_syntax_1.sqls`>`} Parents."left"`,
18567
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`<=` : sql_syntax_1.sqls`<`} Parents."right"`,
18568
+ sql_syntax_1.sqls`Parents."id" = ${parentNodeId}`,
18569
+ authConditions];
18570
+
18571
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children", fields)}
18572
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18573
+ WHERE ${sql_syntax_1.default.joinWithAnd(conditions)}`.toQuery());
18574
+ if (!result || !result.rows || !result.rows.length)
18575
+ return [];
18576
+ return result.rows;
18577
+ });
18578
+ }
18579
+ /**
18580
+ * Get All parents of a given node
18581
+ * (including immediate parent and parents of immediate parent etc.)
18582
+ * If the node has no parent (i.e. a root node), an empty array will be returned (unless `includeMyself` = true)
18583
+ *
18584
+ * @param {string} childNodeId
18585
+ * @param {boolean} [includeMyself=false]
18586
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18587
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18588
+ * @returns {Promise<NodeRecord[]>}
18589
+ * @memberof NestedSetModelQueryer
18590
+ */
18591
+ getAllParents(childNodeId, includeMyself = false, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18592
+ return __awaiter(this, void 0, void 0, function* () {
18593
+ const authConditions = authDecision.toSql({
18594
+ prefixes: ["input.authObject.orgUnit"],
18595
+ tableRef: "parents" });
18596
+
18597
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18598
+ const conditions = [
18599
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`>=` : sql_syntax_1.sqls`>`} Parents."left"`,
18600
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`<=` : sql_syntax_1.sqls`<`} Parents."right"`,
18601
+ sql_syntax_1.sqls`Children."id" = ${childNodeId}`,
18602
+ authConditions];
18603
+
18604
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Parents", fields)}
18605
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18606
+ WHERE ${sql_syntax_1.default.joinWithAnd(conditions)}`.toQuery());
18607
+ if (!result || !result.rows || !result.rows.length)
18608
+ return [];
18609
+ return result.rows;
18610
+ });
18611
+ }
18612
+ /**
18613
+ * Get Immediate Children of a Node
18614
+ * If the node has no child (i.e. a leaf node), an empty array will be returned
18615
+ *
18616
+ * @param {string} parentNodeId
18617
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18618
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18619
+ * @returns {Promise<NodeRecord[]>}
18620
+ * @memberof NestedSetModelQueryer
18621
+ */
18622
+ getImmediateChildren(parentNodeId, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18623
+ return __awaiter(this, void 0, void 0, function* () {
18624
+ const authConditions = authDecision.toSql({
18625
+ prefixes: ["input.authObject.orgUnit"],
18626
+ tableRef: "children" });
18025
18627
 
18026
- /*------------------------------------------------------------------------*/
18628
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18629
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children", fields)}
18630
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18631
+ WHERE Children."left" BETWEEN Parents."left" AND Parents."right"
18632
+ AND Parents."left" = (
18633
+ SELECT MAX(S."left") FROM ${tbl} AS S
18634
+ WHERE S."left" < Children."left" AND S."right" > Children."right"
18635
+ )
18636
+ AND Parents."id" = ${parentNodeId}
18637
+ ${authConditions.isEmpty ?
18638
+ sql_syntax_1.default.empty :
18639
+ sql_syntax_1.sqls` AND ${authConditions}`}
18640
+ ORDER BY Children."left" ASC`.toQuery());
18641
+ if (!result || !result.rows || !result.rows.length)
18642
+ return [];
18643
+ return result.rows;
18644
+ });
18645
+ }
18646
+ /**
18647
+ * Get Immediate Parent of a Node
18648
+ * If the node has no parent (i.e. a root node), null will be returned
18649
+ *
18650
+ * @param {string} childNodeId
18651
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18652
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18653
+ * @returns {Promise<NodeRecord>}
18654
+ * @memberof NestedSetModelQueryer
18655
+ */
18656
+ getImmediateParent(childNodeId, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18657
+ return __awaiter(this, void 0, void 0, function* () {
18658
+ const authConditions = authDecision.toSql({
18659
+ prefixes: ["input.authObject.orgUnit"],
18660
+ tableRef: "parents" });
18027
18661
 
18028
- /**
18029
- * The semantic version number.
18662
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18663
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Parents", fields)}
18664
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18665
+ WHERE Children.left BETWEEN Parents.left AND Parents.right
18666
+ AND Parents.left = (
18667
+ SELECT MAX(S.left) FROM ${tbl} AS S
18668
+ WHERE S.left < Children.left AND S.right > Children.right
18669
+ )
18670
+ AND Children.id = ${childNodeId}
18671
+ ${authConditions.isEmpty ?
18672
+ sql_syntax_1.default.empty :
18673
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18674
+ if (!result || !result.rows || !result.rows.length)
18675
+ return tsmonad_1.Maybe.nothing();
18676
+ return tsmonad_1.Maybe.just(result.rows[0]);
18677
+ });
18678
+ }
18679
+ /**
18680
+ * Get all nodes at level n from top
18681
+ * e.g. get all nodes at level 3:
18682
+ * this.getAllNodesAtLevel(3)
18683
+ * Root node is at level 1
18030
18684
  *
18031
- * @static
18032
- * @memberOf _
18033
- * @type {string}
18685
+ * @param {number} level
18686
+ * @returns {Promise<NodeRecord[]>}
18687
+ * @memberof NestedSetModelQueryer
18034
18688
  */
18035
- lodash.VERSION = VERSION;
18689
+ getAllNodesAtLevel(level, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18690
+ return __awaiter(this, void 0, void 0, function* () {
18691
+ const authConditions = authDecision.toSql({
18692
+ prefixes: ["input.authObject.orgUnit"],
18693
+ tableRef: "t2" });
18694
+
18695
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18696
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("t2")}
18697
+ FROM ${tbl} AS t1, ${tbl} AS t2
18698
+ WHERE t2.left BETWEEN t1.left AND t1.right
18699
+ ${authConditions.isEmpty ?
18700
+ sql_syntax_1.default.empty :
18701
+ sql_syntax_1.sqls` AND ${authConditions}`}
18702
+ GROUP BY t2.id
18703
+ HAVING COUNT(t1.id) = ${level}`.toQuery());
18704
+ if (!result || !result.rows || !result.rows.length)
18705
+ return [];
18706
+ return result.rows;
18707
+ });
18708
+ }
18709
+ /**
18710
+ * Get level no. of a given node
18711
+ * Starts from 1. i.e. The root node is 1
18712
+ *
18713
+ * @param {string} nodeId
18714
+ * @returns {Promise<number>}
18715
+ * @throws NodeNotFoundError If the node can't be found in the tree
18716
+ * @memberof NestedSetModelQueryer
18717
+ */
18718
+ getLevelOfNode(nodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18719
+ return __awaiter(this, void 0, void 0, function* () {
18720
+ const authConditions = authDecision.toSql({
18721
+ prefixes: ["input.authObject.orgUnit"],
18722
+ tableRef: "parents" });
18723
+
18724
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18725
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT COUNT(Parents.id) AS level
18726
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18727
+ WHERE Children.left BETWEEN Parents.left AND Parents.right AND Children.id = ${nodeId}
18728
+ ${authConditions.isEmpty ?
18729
+ sql_syntax_1.default.empty :
18730
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18731
+ if (!result || !result.rows || !result.rows.length)
18732
+ throw new NodeNotFoundError();
18733
+ const level = parseInt(result.rows[0]["level"]);
18734
+ if (!lodash_1.default.isNumber(level) || lodash_1.default.isNaN(level) || level < 1)
18735
+ throw new Error(`Could find a valid level for node ${nodeId}: ${level}`);
18736
+ return level;
18737
+ });
18738
+ }
18739
+ /**
18740
+ * Get total height (no. of the levels) of the tree
18741
+ * Starts with 1 level.
18742
+ *
18743
+ * @returns {Promise<number>}
18744
+ * @throws NodeNotFoundError If the root node can't be found in the tree
18745
+ * @memberof NestedSetModelQueryer
18746
+ */
18747
+ getTreeHeight(authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18748
+ return __awaiter(this, void 0, void 0, function* () {
18749
+ const authConditions = authDecision.toSql({
18750
+ prefixes: ["input.authObject.orgUnit"],
18751
+ tableRef: "t1" });
18752
+
18753
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18754
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT MAX(level) AS height
18755
+ FROM(
18756
+ SELECT COUNT(t1.id)
18757
+ FROM ${tbl} AS t1, ${tbl} AS t2
18758
+ WHERE t2.left BETWEEN t1.left AND t1.right
18759
+ ${authConditions.isEmpty ?
18760
+ sql_syntax_1.default.empty :
18761
+ sql_syntax_1.sqls` AND ${authConditions}`}
18762
+ GROUP BY t2.id
18763
+ ) AS L(level)`.toQuery());
18764
+ if (!result || !result.rows || !result.rows.length)
18765
+ throw new NodeNotFoundError();
18766
+ const height = parseInt(result.rows[0]["height"]);
18767
+ if (!lodash_1.default.isNumber(height) || lodash_1.default.isNaN(height) || height < 0)
18768
+ throw new Error(`Invalid height for tree: ${height}`);
18769
+ return height;
18770
+ });
18771
+ }
18772
+ /**
18773
+ * Get left most immediate child of a node
18774
+ *
18775
+ * @param {string} parentNodeId
18776
+ * @returns {Promise<Maybe<NodeRecord>>}
18777
+ * @memberof NestedSetModelQueryer
18778
+ */
18779
+ getLeftMostImmediateChild(parentNodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18780
+ return __awaiter(this, void 0, void 0, function* () {
18781
+ const authConditions = authDecision.toSql({
18782
+ prefixes: ["input.authObject.orgUnit"],
18783
+ tableRef: "children" });
18784
+
18785
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18786
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children")}
18787
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18788
+ WHERE Children.left = Parents.left + 1 AND Parents.id = ${parentNodeId}
18789
+ ${authConditions.isEmpty ?
18790
+ sql_syntax_1.default.empty :
18791
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18792
+ if (!result || !result.rows || !result.rows.length)
18793
+ return tsmonad_1.Maybe.nothing();
18794
+ return tsmonad_1.Maybe.just(result.rows[0]);
18795
+ });
18796
+ }
18797
+ /**
18798
+ * Get right most immediate child of a node
18799
+ *
18800
+ * @param {string} parentNodeId
18801
+ * @returns {Promise<Maybe<NodeRecord>>}
18802
+ * @memberof NestedSetModelQueryer
18803
+ */
18804
+ getRightMostImmediateChild(parentNodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18805
+ return __awaiter(this, void 0, void 0, function* () {
18806
+ const authConditions = authDecision.toSql({
18807
+ prefixes: ["input.authObject.orgUnit"],
18808
+ tableRef: "children" });
18809
+
18810
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18811
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children")}
18812
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18813
+ WHERE Children.right = Parents.right - 1 AND Parents.id = ${parentNodeId}
18814
+ ${authConditions.isEmpty ?
18815
+ sql_syntax_1.default.empty :
18816
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18817
+ if (!result || !result.rows || !result.rows.length)
18818
+ return tsmonad_1.Maybe.nothing();
18819
+ return tsmonad_1.Maybe.just(result.rows[0]);
18820
+ });
18821
+ }
18822
+ /**
18823
+ * Get all nodes on the top to down path between the `higherNode` to the `lowerNode`
18824
+ * Sort from higher level nodes to lower level node.
18825
+ * Result will include `higherNode` and the `lowerNode`.
18826
+ * If `higherNode` and the `lowerNode` is the same node, an array contains the single node will be return.
18827
+ * If a path doesn't exist, empty array (`[]`) will be returned
18828
+ * If you pass a lower node to the `higherNodeId` and a higher node to `lowerNodeId`, empty array will be returned
18829
+ *
18830
+ * @param {string} higherNodeId
18831
+ * @param {string} lowerNodeId
18832
+ * @returns {Promise<Maybe<NodeRecord[]>}
18833
+ * @memberof NestedSetModelQueryer
18834
+ */
18835
+ getTopDownPathBetween(higherNodeId, lowerNodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18836
+ var _a;
18837
+ return __awaiter(this, void 0, void 0, function* () {
18838
+ const authConditions = authDecision.toSql({
18839
+ prefixes: ["input.authObject.orgUnit"],
18840
+ tableRef: "t2" });
18841
+
18842
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18843
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("t2")}
18844
+ FROM ${tbl} AS t1, ${tbl} AS t2, ${tbl} AS t3
18845
+ WHERE t1.id = ${higherNodeId} AND t3.id = ${lowerNodeId}
18846
+ AND t2.left BETWEEN t1.left AND t1.right
18847
+ AND t3.left BETWEEN t2.left AND t2.right
18848
+ ${authConditions.isEmpty ?
18849
+ sql_syntax_1.default.empty :
18850
+ sql_syntax_1.sqls` AND ${authConditions}`}
18851
+ ORDER BY (t2.right-t2.left) DESC`.toQuery());
18852
+ if (!((_a = result === null || result === void 0 ? void 0 : result.rows) === null || _a === void 0 ? void 0 : _a.length)) {
18853
+ return [];
18854
+ } else
18855
+ {
18856
+ return result.rows;
18857
+ }
18858
+ });
18859
+ }
18860
+ /**
18861
+ * Compare the relative position of the two nodes
18862
+ * If node1 is superior to node2, return "ancestor"
18863
+ * if node1 is the subordinate of node2, return "descendant"
18864
+ * If node1 = node2 return "equal"
18865
+ * If there is no path can be found between Node1 and Node2 return "unrelated"
18866
+ *
18867
+ * @param {string} node1Id
18868
+ * @param {string} node2Id
18869
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18870
+ * @returns {Promise<CompareNodeResult>}
18871
+ * @memberof NestedSetModelQueryer
18872
+ */
18873
+ compareNodes(node1Id, node2Id, client = null) {
18874
+ return __awaiter(this, void 0, void 0, function* () {
18875
+ const tbl = this.tableName;
18876
+ const result = yield (client ? client : this.pool).query(`SELECT (
18877
+ CASE
18878
+ WHEN CAST($1 AS varchar) = CAST($2 AS varchar)
18879
+ THEN 0
18880
+ WHEN t1.left BETWEEN t2.left AND t2.right
18881
+ THEN -1
18882
+ WHEN t2.left BETWEEN t1.left AND t1.right
18883
+ THEN 1
18884
+ ELSE null
18885
+ END
18886
+ ) AS "result"
18887
+ FROM "${tbl}" AS t1, "${tbl}" AS t2
18888
+ WHERE t1.id = CAST($1 AS uuid) AND t2.id = CAST($2 AS uuid)`, [node1Id, node2Id]);
18889
+ if (!result || !result.rows || !result.rows.length)
18890
+ return "unrelated";
18891
+ const comparisonResult = result.rows[0]["result"];
18892
+ if (typeof comparisonResult === "number") {
18893
+ switch (comparisonResult) {
18894
+ case 1:
18895
+ return "ancestor";
18896
+ case -1:
18897
+ return "descendant";
18898
+ case 0:
18899
+ return "equal";}
18036
18900
 
18037
- // Assign default placeholders.
18038
- arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
18039
- lodash[methodName].placeholder = lodash;
18901
+ }
18902
+ return "unrelated";
18040
18903
  });
18041
-
18042
- // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
18043
- arrayEach(['drop', 'take'], function(methodName, index) {
18044
- LazyWrapper.prototype[methodName] = function(n) {
18045
- n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
18046
-
18047
- var result = (this.__filtered__ && !index)
18048
- ? new LazyWrapper(this)
18049
- : this.clone();
18050
-
18051
- if (result.__filtered__) {
18052
- result.__takeCount__ = nativeMin(n, result.__takeCount__);
18053
- } else {
18054
- result.__views__.push({
18055
- 'size': nativeMin(n, MAX_ARRAY_LENGTH),
18056
- 'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
18057
- });
18904
+ }
18905
+ getInsertFields(insertFieldList = null) {
18906
+ const fieldList = isNonEmptyArray(insertFieldList) ?
18907
+ insertFieldList :
18908
+ this.defaultInsertFieldList;
18909
+ if (!isNonEmptyArray(fieldList)) {
18910
+ throw new Error("Insert fields must be an non-empty array!");
18911
+ }
18912
+ return fieldList;
18913
+ }
18914
+ getNodesInsertSql(nodes, sqlValues, insertFieldList = null, tableAliasOrName = "") {
18915
+ if (!isNonEmptyArray(nodes)) {
18916
+ throw new Error("`sqlValues` parameter should be an non-empty array!");
18917
+ }
18918
+ if (!lodash_1.default.isArray(sqlValues)) {
18919
+ throw new Error("`sqlValues` parameter should be an array!");
18920
+ }
18921
+ if (!isValidSqlIdentifier(tableAliasOrName)) {
18922
+ throw new Error(`tableAliasOrName: ${tableAliasOrName} contains invalid characters!`);
18923
+ }
18924
+ const tbl = this.tableName;
18925
+ const fieldList = this.getInsertFields(insertFieldList);
18926
+ const columnsList = fieldList.
18927
+ map(f => {
18928
+ if (!isValidSqlIdentifier(f)) {
18929
+ throw new Error(`column name: ${f} contains invalid characters!`);
18930
+ }
18931
+ return tableAliasOrName == "" ?
18932
+ `"${f}"` :
18933
+ `${tableAliasOrName}."${f}"`;
18934
+ }).
18935
+ join(", ");
18936
+ const valuesList = nodes.
18937
+ map(node => "(" +
18938
+ fieldList.
18939
+ map(f => {
18940
+ sqlValues.push(node[f]);
18941
+ return `$${sqlValues.length}`;
18942
+ }).
18943
+ join(", ") +
18944
+ ")").
18945
+ join(", ");
18946
+ return `INSERT INTO "${tbl}" (${columnsList}) VALUES ${valuesList}`;
18947
+ }
18948
+ /**
18949
+ * Create the root node of the tree.
18950
+ * If a root node already exists, an error will be thrown.
18951
+ *
18952
+ * @param {NodeRecord} node
18953
+ * @param {pg.Client} [existingClient=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18954
+ * @returns {Promise<string>} newly created node ID
18955
+ * @memberof NestedSetModelQueryer
18956
+ */
18957
+ createRootNode(node, existingClient = null) {
18958
+ return __awaiter(this, void 0, void 0, function* () {
18959
+ const tbl = this.tableName;
18960
+ const client = existingClient ?
18961
+ existingClient :
18962
+ yield this.pool.connect();
18963
+ const fields = Object.keys(node);
18964
+ if (!fields.length) {
18965
+ throw new Error("`node` parameter cannot be an empty object with no key.");
18966
+ }
18967
+ let nodeId;
18968
+ try {
18969
+ yield client.query("BEGIN");
18970
+ let result = yield client.query(`SELECT "id" FROM "${tbl}" WHERE "left" = 1 LIMIT 1`);
18971
+ if (result && isNonEmptyArray(result.rows)) {
18972
+ throw new Error(`A root node with id: ${result.rows[0]["id"]} already exists`);
18058
18973
  }
18059
- return result;
18060
- };
18061
-
18062
- LazyWrapper.prototype[methodName + 'Right'] = function(n) {
18063
- return this.reverse()[methodName](n).reverse();
18064
- };
18065
- });
18066
-
18067
- // Add `LazyWrapper` methods that accept an `iteratee` value.
18068
- arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
18069
- var type = index + 1,
18070
- isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
18071
-
18072
- LazyWrapper.prototype[methodName] = function(iteratee) {
18073
- var result = this.clone();
18074
- result.__iteratees__.push({
18075
- 'iteratee': getIteratee(iteratee, 3),
18076
- 'type': type
18077
- });
18078
- result.__filtered__ = result.__filtered__ || isFilter;
18079
- return result;
18080
- };
18081
- });
18082
-
18083
- // Add `LazyWrapper` methods for `_.head` and `_.last`.
18084
- arrayEach(['head', 'last'], function(methodName, index) {
18085
- var takeName = 'take' + (index ? 'Right' : '');
18086
-
18087
- LazyWrapper.prototype[methodName] = function() {
18088
- return this[takeName](1).value()[0];
18089
- };
18974
+ const countResult = yield client.query(`SELECT COUNT("id") AS "num" FROM "${tbl}" WHERE "left" != 1`);
18975
+ let countNum = countResult && countResult.rows && countResult.rows.length ?
18976
+ parseInt(countResult.rows[0].num) :
18977
+ 0;
18978
+ countNum = isNaN(countNum) ? 0 : countNum;
18979
+ const right = countNum ? (countNum + 1) * 2 : 2;
18980
+ const sqlValues = [];
18981
+ result = yield client.query(this.getNodesInsertSql([
18982
+ Object.assign(Object.assign({}, node), { left: 1, right })],
18983
+ sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
18984
+ if (!result || !isNonEmptyArray(result.rows)) {
18985
+ throw new Error("Cannot locate create root node ID!");
18986
+ }
18987
+ yield client.query("COMMIT");
18988
+ nodeId = result.rows[0]["id"];
18989
+ }
18990
+ catch (e) {
18991
+ yield client.query("ROLLBACK");
18992
+ throw e;
18993
+ } finally
18994
+ {
18995
+ if (!existingClient) {
18996
+ client.release();
18997
+ }
18998
+ }
18999
+ return nodeId;
18090
19000
  });
19001
+ }
19002
+ getNodeDataWithinTx(client, nodeId, fields) {
19003
+ return __awaiter(this, void 0, void 0, function* () {
19004
+ const node = yield this.getNodeById(nodeId, fields, client);
19005
+ return node.caseOf({
19006
+ just: node => node,
19007
+ nothing: () => {
19008
+ throw new NodeNotFoundError(`Cannot locate tree node record with id: ${nodeId}`);
19009
+ } });
18091
19010
 
18092
- // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
18093
- arrayEach(['initial', 'tail'], function(methodName, index) {
18094
- var dropName = 'drop' + (index ? '' : 'Right');
18095
-
18096
- LazyWrapper.prototype[methodName] = function() {
18097
- return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
18098
- };
18099
19011
  });
18100
-
18101
- LazyWrapper.prototype.compact = function() {
18102
- return this.filter(identity);
18103
- };
18104
-
18105
- LazyWrapper.prototype.find = function(predicate) {
18106
- return this.filter(predicate).head();
18107
- };
18108
-
18109
- LazyWrapper.prototype.findLast = function(predicate) {
18110
- return this.reverse().find(predicate);
18111
- };
18112
-
18113
- LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
18114
- if (typeof path == 'function') {
18115
- return new LazyWrapper(this);
19012
+ }
19013
+ /**
19014
+ * Insert a node to the tree under a parent node
19015
+ *
19016
+ * @param {string} parentNodeId
19017
+ * @param {NodeRecord} node
19018
+ * @returns {Promise<string>}
19019
+ * @throws NodeNotFoundError if parent node not found
19020
+ * @memberof NestedSetModelQueryer
19021
+ */
19022
+ insertNode(parentNodeId, node) {
19023
+ return __awaiter(this, void 0, void 0, function* () {
19024
+ if (!parentNodeId) {
19025
+ throw new Error("`parentNodeId` cannot be empty!");
18116
19026
  }
18117
- return this.map(function(value) {
18118
- return baseInvoke(value, path, args);
18119
- });
19027
+ const fields = Object.keys(node);
19028
+ if (!fields.length) {
19029
+ throw new Error("`node` parameter cannot be an empty object with no key.");
19030
+ }
19031
+ const tbl = this.tableName;
19032
+ const client = yield this.pool.connect();
19033
+ let nodeId;
19034
+ try {
19035
+ yield client.query("BEGIN");
19036
+ const { right: parentRight } = yield this.getNodeDataWithinTx(client, parentNodeId, ["right"]);
19037
+ yield client.query(`UPDATE "${tbl}"
19038
+ SET
19039
+ "left" = CASE WHEN "left" > $1 THEN "left" + 2 ELSE "left" END,
19040
+ "right" = CASE WHEN "right" >= $1 THEN "right" + 2 ELSE "right" END
19041
+ WHERE "right" >= $1`, [parentRight]);
19042
+ const sqlValues = [];
19043
+ const result = yield client.query(this.getNodesInsertSql([
19044
+ Object.assign(Object.assign({}, node), { left: parentRight, right: parentRight + 1 })],
19045
+ sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
19046
+ if (!result || !isNonEmptyArray(result.rows)) {
19047
+ throw new Error("Cannot locate created node ID!");
19048
+ }
19049
+ yield client.query("COMMIT");
19050
+ nodeId = result.rows[0]["id"];
19051
+ }
19052
+ catch (e) {
19053
+ yield client.query("ROLLBACK");
19054
+ throw e;
19055
+ } finally
19056
+ {
19057
+ client.release();
19058
+ }
19059
+ return nodeId;
18120
19060
  });
19061
+ }
19062
+ /**
19063
+ * Insert a node to the right of its sibling
19064
+ * If `siblingNodeId` belongs to a root node, an error will be thrown
19065
+ *
19066
+ * @param {string} siblingNodeId
19067
+ * @param {NodeRecord} node
19068
+ * @returns {Promise<string>}
19069
+ * @throws NodeNotFoundError If the node can't be found in the tree
19070
+ * @memberof NestedSetModelQueryer
19071
+ */
19072
+ insertNodeToRightOfSibling(siblingNodeId, node) {
19073
+ return __awaiter(this, void 0, void 0, function* () {
19074
+ if (!siblingNodeId) {
19075
+ throw new Error("`siblingNodeId` cannot be empty!");
19076
+ }
19077
+ const fields = Object.keys(node);
19078
+ if (!fields.length) {
19079
+ throw new Error("`node` parameter cannot be an empty object with no key.");
19080
+ }
19081
+ const tbl = this.tableName;
19082
+ const client = yield this.pool.connect();
19083
+ let nodeId;
19084
+ try {
19085
+ yield client.query("BEGIN");
19086
+ const { left: siblingLeft, right: siblingRight } = yield this.getNodeDataWithinTx(client, siblingNodeId, [
19087
+ "left",
19088
+ "right"]);
18121
19089
 
18122
- LazyWrapper.prototype.reject = function(predicate) {
18123
- return this.filter(negate(getIteratee(predicate)));
18124
- };
18125
-
18126
- LazyWrapper.prototype.slice = function(start, end) {
18127
- start = toInteger(start);
18128
-
18129
- var result = this;
18130
- if (result.__filtered__ && (start > 0 || end < 0)) {
18131
- return new LazyWrapper(result);
19090
+ if (siblingLeft === 1) {
19091
+ throw new Error("Cannot add sibling to the Root node!");
19092
+ }
19093
+ yield client.query(`UPDATE "${tbl}"
19094
+ SET
19095
+ "left" = CASE WHEN "left" < $1 THEN "left" ELSE "left" + 2 END,
19096
+ "right" = CASE WHEN "right" < $1 THEN "right" ELSE "right" + 2 END
19097
+ WHERE "right" > $1`, [siblingRight]);
19098
+ const sqlValues = [];
19099
+ const result = yield client.query(this.getNodesInsertSql([
19100
+ Object.assign(Object.assign({}, node), { left: siblingRight + 1, right: siblingRight + 2 })],
19101
+ sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
19102
+ if (!result || !isNonEmptyArray(result.rows)) {
19103
+ throw new Error("Cannot locate created node ID!");
19104
+ }
19105
+ yield client.query("COMMIT");
19106
+ nodeId = result.rows[0]["id"];
18132
19107
  }
18133
- if (start < 0) {
18134
- result = result.takeRight(-start);
18135
- } else if (start) {
18136
- result = result.drop(start);
19108
+ catch (e) {
19109
+ yield client.query("ROLLBACK");
19110
+ throw e;
19111
+ } finally
19112
+ {
19113
+ client.release();
18137
19114
  }
18138
- if (end !== undefined) {
18139
- end = toInteger(end);
18140
- result = end < 0 ? result.dropRight(-end) : result.take(end - start);
19115
+ return nodeId;
19116
+ });
19117
+ }
19118
+ /**
19119
+ * Move a subtree (the specified root node and all its subordinates)
19120
+ * to under a new parent.
19121
+ *
19122
+ * If the specifed sub tree root node is a child of the new parent node,
19123
+ * an error will be be thrown
19124
+ *
19125
+ * @param {string} subTreeRootNodeId
19126
+ * @param {string} newParentId
19127
+ * @returns {Promise<void>}
19128
+ * @throws NodeNotFoundError If the node can't be found in the tree
19129
+ * @memberof NestedSetModelQueryer
19130
+ */
19131
+ moveSubTreeTo(subTreeRootNodeId, newParentId) {
19132
+ return __awaiter(this, void 0, void 0, function* () {
19133
+ if (!subTreeRootNodeId) {
19134
+ throw new Error("`subTreeRootNodeId` cannot be empty!");
18141
19135
  }
18142
- return result;
18143
- };
18144
-
18145
- LazyWrapper.prototype.takeRightWhile = function(predicate) {
18146
- return this.reverse().takeWhile(predicate).reverse();
18147
- };
18148
-
18149
- LazyWrapper.prototype.toArray = function() {
18150
- return this.take(MAX_ARRAY_LENGTH);
18151
- };
18152
-
18153
- // Add `LazyWrapper` methods to `lodash.prototype`.
18154
- baseForOwn(LazyWrapper.prototype, function(func, methodName) {
18155
- var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
18156
- isTaker = /^(?:head|last)$/.test(methodName),
18157
- lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
18158
- retUnwrapped = isTaker || /^find/.test(methodName);
18159
-
18160
- if (!lodashFunc) {
18161
- return;
19136
+ if (!newParentId) {
19137
+ throw new Error("`newParentId` cannot be empty!");
18162
19138
  }
18163
- lodash.prototype[methodName] = function() {
18164
- var value = this.__wrapped__,
18165
- args = isTaker ? [1] : arguments,
18166
- isLazy = value instanceof LazyWrapper,
18167
- iteratee = args[0],
18168
- useLazy = isLazy || isArray(value);
18169
-
18170
- var interceptor = function(value) {
18171
- var result = lodashFunc.apply(lodash, arrayPush([value], args));
18172
- return (isTaker && chainAll) ? result[0] : result;
18173
- };
18174
-
18175
- if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
18176
- // Avoid lazy use if the iteratee has a "length" value other than `1`.
18177
- isLazy = useLazy = false;
19139
+ const tbl = this.tableName;
19140
+ const client = yield this.pool.connect();
19141
+ try {
19142
+ yield client.query("BEGIN");
19143
+ const comparisonResult = yield this.compareNodes(subTreeRootNodeId, newParentId, client);
19144
+ if (comparisonResult === "ancestor") {
19145
+ throw new Error(`Cannot move a higher level node (id: ${subTreeRootNodeId})to its subordinate (id: ${newParentId})`);
18178
19146
  }
18179
- var chainAll = this.__chain__,
18180
- isHybrid = !!this.__actions__.length,
18181
- isUnwrapped = retUnwrapped && !chainAll,
18182
- onlyLazy = isLazy && !isHybrid;
19147
+ const { left: originRootLeft, right: originRootRight } = yield this.getNodeDataWithinTx(client, subTreeRootNodeId, [
19148
+ "left",
19149
+ "right"]);
18183
19150
 
18184
- if (!retUnwrapped && useLazy) {
18185
- value = onlyLazy ? value : new LazyWrapper(this);
18186
- var result = func.apply(value, args);
18187
- result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
18188
- return new LodashWrapper(result, chainAll);
18189
- }
18190
- if (isUnwrapped && onlyLazy) {
18191
- return func.apply(this, args);
19151
+ if (originRootLeft === "1") {
19152
+ throw new Error("Cannot move Tree root node as substree.");
18192
19153
  }
18193
- result = this.thru(interceptor);
18194
- return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
18195
- };
19154
+ const { right: newParentRight } = yield this.getNodeDataWithinTx(client, newParentId, ["right"]);
19155
+ yield client.query(`
19156
+ UPDATE "${tbl}"
19157
+ SET
19158
+ "left" = "left" + CASE
19159
+ WHEN $3::int4 < $1::int4
19160
+ THEN CASE
19161
+ WHEN "left" BETWEEN $1 AND $2
19162
+ THEN $3 - $1
19163
+ WHEN "left" BETWEEN $3 AND ($1 - 1)
19164
+ THEN $2 - $1 + 1
19165
+ ELSE 0 END
19166
+ WHEN $3::int4 > $2::int4
19167
+ THEN CASE
19168
+ WHEN "left" BETWEEN $1 AND $2
19169
+ THEN $3 - $2 - 1
19170
+ WHEN "left" BETWEEN ($2 + 1) AND ($3 - 1)
19171
+ THEN $1 - $2 - 1
19172
+ ELSE 0 END
19173
+ ELSE 0 END,
19174
+ "right" = "right" + CASE
19175
+ WHEN $3::int4 < $1::int4
19176
+ THEN CASE
19177
+ WHEN "right" BETWEEN $1 AND $2
19178
+ THEN $3 - $1
19179
+ WHEN "right" BETWEEN $3 AND ($1 - 1)
19180
+ THEN $2 - $1 + 1
19181
+ ELSE 0 END
19182
+ WHEN $3::int4 > $2::int4
19183
+ THEN CASE
19184
+ WHEN "right" BETWEEN $1 AND $2
19185
+ THEN $3 - $2 - 1
19186
+ WHEN "right" BETWEEN ($2 + 1) AND ($3 - 1)
19187
+ THEN $1 - $2 - 1
19188
+ ELSE 0 END
19189
+ ELSE 0 END
19190
+ `, [originRootLeft, originRootRight, newParentRight]);
19191
+ yield client.query("COMMIT");
19192
+ }
19193
+ catch (e) {
19194
+ yield client.query("ROLLBACK");
19195
+ throw e;
19196
+ } finally
19197
+ {
19198
+ client.release();
19199
+ }
18196
19200
  });
19201
+ }
19202
+ /**
19203
+ * Delete a subtree (and all its dependents)
19204
+ * If you sent in a root node id (and `allowRootNodeId` is true), the whole tree will be removed
19205
+ * When `allowRootNodeId` is false and you passed a root node id, an error will be thrown
19206
+ *
19207
+ * @param {string} subTreeRootNodeId
19208
+ * @param {boolean} [allowRootNodeId=false]
19209
+ * @returns {Promise<void>}
19210
+ * @throws NodeNotFoundError If the node can't be found in the tree
19211
+ * @memberof NestedSetModelQueryer
19212
+ */
19213
+ deleteSubTree(subTreeRootNodeId, allowRootNodeId = false) {
19214
+ return __awaiter(this, void 0, void 0, function* () {
19215
+ if (!subTreeRootNodeId) {
19216
+ throw new Error("`subTreeRootNodeId` cannot be empty!");
19217
+ }
19218
+ const tbl = this.tableName;
19219
+ const client = yield this.pool.connect();
19220
+ try {
19221
+ yield client.query("BEGIN");
19222
+ const { left: subTreeRootLeft, right: subTreeRootRight } = yield this.getNodeDataWithinTx(client, subTreeRootNodeId, [
19223
+ "left",
19224
+ "right"]);
18197
19225
 
18198
- // Add `Array` methods to `lodash.prototype`.
18199
- arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
18200
- var func = arrayProto[methodName],
18201
- chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
18202
- retUnwrapped = /^(?:pop|shift)$/.test(methodName);
19226
+ if (subTreeRootLeft === 1 && !allowRootNodeId) {
19227
+ throw new Error("Root node id is not allowed!");
19228
+ }
19229
+ // --- delete the sub tree nodes
19230
+ yield client.query(`DELETE FROM "${tbl}" WHERE "left" BETWEEN $1 AND $2`, [subTreeRootLeft, subTreeRootRight]);
19231
+ // --- closing the gap after deletion
19232
+ yield client.query(`
19233
+ UPDATE "${tbl}"
19234
+ SET "left" = CASE
19235
+ WHEN "left" > $1
19236
+ THEN "left" - ($2 - $1 + 1)
19237
+ ELSE "left" END,
19238
+ "right" = CASE
19239
+ WHEN "right" > $1
19240
+ THEN "right" - ($2 - $1 + 1)
19241
+ ELSE "right" END
19242
+ WHERE "left" > $1 OR "right" > $1
19243
+ `, [subTreeRootLeft, subTreeRootRight]);
19244
+ yield client.query("COMMIT");
19245
+ }
19246
+ catch (e) {
19247
+ yield client.query("ROLLBACK");
19248
+ throw e;
19249
+ } finally
19250
+ {
19251
+ client.release();
19252
+ }
19253
+ });
19254
+ }
19255
+ /**
19256
+ * Delete a single node from the tree
19257
+ * Its childrens will become its parent's children
19258
+ * Deleting a root node is not allowed.
19259
+ * You can, however, delete the whole sub tree from root node or update the root node, instead.
19260
+ *
19261
+ * @param {string} nodeId
19262
+ * @returns {Promise<void>}
19263
+ * @throws NodeNotFoundError If the node can't be found in the tree
19264
+ * @memberof NestedSetModelQueryer
19265
+ */
19266
+ deleteNode(nodeId) {
19267
+ return __awaiter(this, void 0, void 0, function* () {
19268
+ if (!nodeId) {
19269
+ throw new Error("`nodeId` cannot be empty!");
19270
+ }
19271
+ const tbl = this.tableName;
19272
+ const client = yield this.pool.connect();
19273
+ try {
19274
+ yield client.query("BEGIN");
19275
+ const { left: subTreeRootLeft, right: subTreeRootRight } = yield this.getNodeDataWithinTx(client, nodeId, [
19276
+ "left",
19277
+ "right"]);
18203
19278
 
18204
- lodash.prototype[methodName] = function() {
18205
- var args = arguments;
18206
- if (retUnwrapped && !this.__chain__) {
18207
- var value = this.value();
18208
- return func.apply(isArray(value) ? value : [], args);
19279
+ if (subTreeRootLeft === 1) {
19280
+ throw new Error("Delete a root node is not allowed!");
18209
19281
  }
18210
- return this[chainName](function(value) {
18211
- return func.apply(isArray(value) ? value : [], args);
18212
- });
18213
- };
19282
+ // --- delete the node
19283
+ // --- In nested set model, children are still bind to the deleted node's parent after deletion
19284
+ yield client.query(`DELETE FROM "${tbl}" WHERE "id" = $1`, [
19285
+ nodeId]);
19286
+
19287
+ // --- closing the gap after deletion
19288
+ yield client.query(`
19289
+ UPDATE "${tbl}"
19290
+ SET "left" = CASE
19291
+ WHEN "left" > $1 AND "right" < $2
19292
+ THEN "left" - 1
19293
+ WHEN "left" > $1 AND "right" > $2
19294
+ THEN "left" - 2
19295
+ ELSE "left" END,
19296
+ "right" = CASE
19297
+ WHEN "left" > $1 AND "right" < $2
19298
+ THEN "right" - 1
19299
+ ELSE ("right" - 2) END
19300
+ WHERE "left" > $1 OR "right" > $1
19301
+ `, [subTreeRootLeft, subTreeRootRight]);
19302
+ yield client.query("COMMIT");
19303
+ }
19304
+ catch (e) {
19305
+ yield client.query("ROLLBACK");
19306
+ throw e;
19307
+ } finally
19308
+ {
19309
+ client.release();
19310
+ }
19311
+ });
19312
+ }
19313
+ /**
19314
+ * Update node data of the node specified by the nodeId
19315
+ * The followings fields will be ignored (as they should be generated by program):
19316
+ * - `left`
19317
+ * - `right`
19318
+ * - `id`
19319
+ *
19320
+ * @param {string} nodeId
19321
+ * @param {NodeRecord} nodeData
19322
+ * @param {pg.Client} [client=null]
19323
+ * @returns {Promise<void>}
19324
+ * @memberof NestedSetModelQueryer
19325
+ */
19326
+ updateNode(nodeId, nodeData, client = null) {
19327
+ return __awaiter(this, void 0, void 0, function* () {
19328
+ if (nodeId.trim() === "") {
19329
+ throw new Error("nodeId can't be empty!");
19330
+ }
19331
+ const sqlValues = [nodeId];
19332
+ const updateFields = Object.keys(nodeData).filter(k => k !== "left" && k !== "right" && k !== "id");
19333
+ if (!updateFields.length) {
19334
+ throw new Error("No valid node data passed for updating.");
19335
+ }
19336
+ const setFieldList = updateFields.
19337
+ map(f => {
19338
+ if (!isValidSqlIdentifier(f)) {
19339
+ throw new Error(`field name: ${f} contains invalid characters!`);
19340
+ }
19341
+ sqlValues.push(nodeData[f]);
19342
+ return `"${f}" = $${sqlValues.length}`;
19343
+ }).
19344
+ join(", ");
19345
+ yield (client ? client : this.pool).query(`UPDATE "${this.tableName}" SET ${setFieldList} WHERE "id" = $1`, sqlValues);
18214
19346
  });
19347
+ }
19348
+ getChildTextTreeNodes(parentId) {
19349
+ return __awaiter(this, void 0, void 0, function* () {
19350
+ const nodes = yield this.getImmediateChildren(parentId);
19351
+ if (!nodes || !nodes.length)
19352
+ return [];
19353
+ const textNodeList = [];
19354
+ for (let i = 0; i < nodes.length; i++) {
19355
+ const nodeChildren = yield this.getChildTextTreeNodes(nodes[i].id);
19356
+ if (nodeChildren.length) {
19357
+ textNodeList.push({
19358
+ text: nodes[i].name,
19359
+ children: nodeChildren });
18215
19360
 
18216
- // Map minified method names to their real names.
18217
- baseForOwn(LazyWrapper.prototype, function(func, methodName) {
18218
- var lodashFunc = lodash[methodName];
18219
- if (lodashFunc) {
18220
- var key = lodashFunc.name + '';
18221
- if (!hasOwnProperty.call(realNames, key)) {
18222
- realNames[key] = [];
19361
+ } else
19362
+ {
19363
+ textNodeList.push(nodes[i].name);
18223
19364
  }
18224
- realNames[key].push({ 'name': methodName, 'func': lodashFunc });
18225
19365
  }
19366
+ return textNodeList;
18226
19367
  });
19368
+ }
19369
+ /**
19370
+ * Generate the Text View of the tree
19371
+ * Provided as Dev tool only
19372
+ *
19373
+ * E.g. output could be:
19374
+ * └─ Albert
19375
+ * ├─ Chuck
19376
+ * │ ├─ Fred
19377
+ * │ ├─ Eddie
19378
+ * │ └─ Donna
19379
+ * └─ Bert
19380
+ *
19381
+ * @returns {Promise<string>}
19382
+ * @memberof NestedSetModelQueryer
19383
+ */
19384
+ getTreeTextView() {
19385
+ return __awaiter(this, void 0, void 0, function* () {
19386
+ const rootNodeMaybe = yield this.getRootNode();
19387
+ return rootNodeMaybe.caseOf({
19388
+ just: rootNode => __awaiter(this, void 0, void 0, function* () {
19389
+ const tree = [];
19390
+ const children = yield this.getChildTextTreeNodes(rootNode.id);
19391
+ if (children.length) {
19392
+ tree.push({
19393
+ text: rootNode.name,
19394
+ children });
18227
19395
 
18228
- realNames[createHybrid(undefined, WRAP_BIND_KEY_FLAG).name] = [{
18229
- 'name': 'wrapper',
18230
- 'func': undefined
18231
- }];
18232
-
18233
- // Add methods to `LazyWrapper`.
18234
- LazyWrapper.prototype.clone = lazyClone;
18235
- LazyWrapper.prototype.reverse = lazyReverse;
18236
- LazyWrapper.prototype.value = lazyValue;
18237
-
18238
- // Add chain sequence methods to the `lodash` wrapper.
18239
- lodash.prototype.at = wrapperAt;
18240
- lodash.prototype.chain = wrapperChain;
18241
- lodash.prototype.commit = wrapperCommit;
18242
- lodash.prototype.next = wrapperNext;
18243
- lodash.prototype.plant = wrapperPlant;
18244
- lodash.prototype.reverse = wrapperReverse;
18245
- lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
18246
-
18247
- // Add lazy aliases.
18248
- lodash.prototype.first = lodash.prototype.head;
18249
-
18250
- if (symIterator) {
18251
- lodash.prototype[symIterator] = wrapperToIterator;
18252
- }
18253
- return lodash;
18254
- });
18255
-
18256
- /*--------------------------------------------------------------------------*/
18257
-
18258
- // Export lodash.
18259
- var _ = runInContext();
18260
-
18261
- // Some AMD build optimizers, like r.js, check for condition patterns like:
18262
- if (true) {
18263
- // Expose Lodash on the global object to prevent errors when Lodash is
18264
- // loaded by a script tag in the presence of an AMD loader.
18265
- // See http://requirejs.org/docs/errors.html#mismatch for more details.
18266
- // Use `_.noConflict` to remove Lodash from the global object.
18267
- root._ = _;
19396
+ } else
19397
+ {
19398
+ tree.push(rootNode.name);
19399
+ }
19400
+ return textTree(tree);
19401
+ }),
19402
+ nothing: () => __awaiter(this, void 0, void 0, function* () {return "Empty Tree";}) });
18268
19403
 
18269
- // Define as an anonymous module so, through path mapping, it can be
18270
- // referenced as the "underscore" module.
18271
- !(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
18272
- return _;
18273
- }).call(exports, __webpack_require__, exports, module),
18274
- __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
18275
- }
18276
- // Check for `exports` after `define` in case a build optimizer adds it.
18277
- else {}
18278
- }.call(this));
19404
+ });
19405
+ }}
18279
19406
 
18280
- /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(11)(module)))
19407
+ exports.default = NestedSetModelQueryer;
18281
19408
 
18282
19409
  /***/ }),
18283
- /* 11 */
19410
+ /* 14 */
18284
19411
  /***/ (function(module, exports) {
18285
19412
 
18286
19413
  module.exports = function(module) {
@@ -18308,14 +19435,262 @@ module.exports = function(module) {
18308
19435
 
18309
19436
 
18310
19437
  /***/ }),
18311
- /* 12 */
19438
+ /* 15 */
18312
19439
  /***/ (function(module, exports, __webpack_require__) {
18313
19440
 
18314
19441
  !function(t,n){if(true)module.exports=n();else { var i, e; }}(this,function(){return function(t){function n(i){if(e[i])return e[i].exports;var r=e[i]={exports:{},id:i,loaded:!1};return t[i].call(r.exports,r,r.exports,n),r.loaded=!0,r.exports}var e={};return n.m=t,n.c=e,n.p="",n(0)}([function(t,n,e){"use strict";function i(t){for(var e in t)n.hasOwnProperty(e)||(n[e]=t[e])}Object.defineProperty(n,"__esModule",{value:!0}),i(e(2)),i(e(3)),i(e(1)),i(e(4))},function(t,n){"use strict";function e(t,n){var i=0;if(t===n)return!0;if("function"==typeof t.equals)return t.equals(n);if(t.length>0&&t.length===n.length){for(;i<t.length;i+=1)if(!e(t[i],n[i]))return!1;return!0}return!1}Object.defineProperty(n,"__esModule",{value:!0}),n.eq=e},function(t,n,e){"use strict";function i(t){return null!==t&&void 0!==t}function r(t,n){if(i(t)&&i(n))throw new TypeError("Cannot construct an Either with both a left and a right");if(!i(t)&&!i(n))throw new TypeError("Cannot construct an Either with neither a left nor a right");return i(t)&&!i(n)?s.left(t):!i(t)&&i(n)?s.right(n):void 0}Object.defineProperty(n,"__esModule",{value:!0});var o,u=e(1);!function(t){t[t.Left=0]="Left",t[t.Right=1]="Right"}(o=n.EitherType||(n.EitherType={})),n.either=r;var s=function(){function t(t,n,e){this.type=t,this.l=n,this.r=e,this.of=this.unit,this.chain=this.bind,this.lift=this.fmap,this.map=this.fmap}return t.left=function(n){return new t(o.Left,n)},t.right=function(n){return new t(o.Right,null,n)},t.prototype.unit=function(n){return t.right(n)},t.prototype.bind=function(n){return this.type===o.Right?n(this.r):t.left(this.l)},t.prototype.fmap=function(t){var n=this;return this.bind(function(e){return n.unit(t(e))})},t.prototype.caseOf=function(t){return this.type===o.Right?t.right(this.r):t.left(this.l)},t.prototype.equals=function(t){return t.type===this.type&&(this.type===o.Left&&u.eq(t.l,this.l)||this.type===o.Right&&u.eq(t.r,this.r))},t.prototype["do"]=function(t){void 0===t&&(t={});var n={left:function(t){},right:function(t){}},e=Object.assign(n,t);return this.caseOf(e),this},t}();n.Either=s},function(t,n,e){"use strict";function i(t){return u.maybe(t)}Object.defineProperty(n,"__esModule",{value:!0});var r,o=e(1);!function(t){t[t.Nothing=0]="Nothing",t[t.Just=1]="Just"}(r=n.MaybeType||(n.MaybeType={})),n.maybe=i;var u=function(){function t(t,n){this.type=t,this.value=n,this.of=this.unit,this.chain=this.bind,this.lift=this.fmap,this.map=this.fmap}return t.sequence=function(n){if(Object.keys(n).filter(function(t){return n[t].type===r.Nothing}).length)return t.nothing();var e={};for(var i in n)n.hasOwnProperty(i)&&(e[i]=n[i].value);return t.just(e)},t.maybe=function(n){return null===n||void 0===n?new t(r.Nothing):new t(r.Just,n)},t.just=function(n){if(null===n||void 0===n)throw new TypeError("Cannot Maybe.just(null)");return new t(r.Just,n)},t.nothing=function(){return new t(r.Nothing)},t.prototype.unit=function(n){return t.maybe(n)},t.prototype.bind=function(n){return this.type===r.Just?n(this.value):t.nothing()},t.prototype.fmap=function(t){var n=this;return this.bind(function(e){return n.unit(t(e))})},t.prototype.caseOf=function(t){return this.type===r.Just?t.just(this.value):t.nothing()},t.prototype.defaulting=function(n){return t.just(this.valueOr(n))},t.prototype.equals=function(t){return t.type===this.type&&(this.type===r.Nothing||o.eq(t.value,this.value))},t.prototype.valueOr=function(t){return this.valueOrCompute(function(){return t})},t.prototype.valueOrCompute=function(t){return this.type===r.Just?this.value:t()},t.prototype.valueOrThrow=function(t){if(this.type===r.Just)return this.value;throw t||new Error("No value is available.")},t.prototype["do"]=function(t){void 0===t&&(t={});var n={just:function(t){},nothing:function(){}},e=Object.assign(n,t);return this.caseOf(e),this},t}();u.all=function(t){return u.sequence(t)},n.Maybe=u},function(t,n){"use strict";function e(t,n){return i.writer(t,n)}Object.defineProperty(n,"__esModule",{value:!0}),n.writer=e;var i=function(){function t(t,n){this.story=t,this.value=n,this.of=this.unit,this.chain=this.bind,this.lift=this.fmap,this.map=this.fmap}return t.writer=function(n,e){return new t(n,e)},t.tell=function(n){return new t([n],0)},t.prototype.unit=function(n){return new t([],n)},t.prototype.bind=function(n){var e=n(this.value),i=this.story.concat(e.story);return new t(i,e.value)},t.prototype.fmap=function(t){var n=this;return this.bind(function(e){return n.unit(t(e))})},t.prototype.caseOf=function(t){return t.writer(this.story,this.value)},t.prototype.equals=function(t){var n,e=!0;for(n=0;n<this.story.length;n+=1)e=e&&this.story[n]===t.story[n];return e&&this.value===t.value},t}();n.Writer=i}])});
18315
19442
  //# sourceMappingURL=tsmonad.js.map
18316
19443
 
18317
19444
  /***/ }),
18318
- /* 13 */
19445
+ /* 16 */
19446
+ /***/ (function(module, exports, __webpack_require__) {
19447
+
19448
+ "use strict";
19449
+
19450
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19451
+ if (k2 === undefined) k2 = k;
19452
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
19453
+ }) : (function(o, m, k, k2) {
19454
+ if (k2 === undefined) k2 = k;
19455
+ o[k2] = m[k];
19456
+ }));
19457
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19458
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19459
+ }) : function(o, v) {
19460
+ o["default"] = v;
19461
+ });
19462
+ var __importStar = (this && this.__importStar) || function (mod) {
19463
+ if (mod && mod.__esModule) return mod;
19464
+ var result = {};
19465
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
19466
+ __setModuleDefault(result, mod);
19467
+ return result;
19468
+ };
19469
+ Object.defineProperty(exports, "__esModule", { value: true });
19470
+ exports.AspectQueryGroup = exports.AspectQueryValueInArray = exports.AspectQueryArrayNotEmpty = exports.AspectQueryWithValue = exports.AspectQueryExists = exports.AspectQueryFalse = exports.AspectQueryTrue = exports.AspectQuery = exports.AspectQueryValue = void 0;
19471
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
19472
+ const SQLUtils_1 = __webpack_require__(10);
19473
+ class AspectQueryValue {
19474
+ constructor(value) {
19475
+ this.value = value;
19476
+ switch (typeof value) {
19477
+ case "number":
19478
+ this.postgresType = sql_syntax_1.sqls `NUMERIC`;
19479
+ break;
19480
+ case "boolean":
19481
+ this.postgresType = sql_syntax_1.sqls `BOOL`;
19482
+ break;
19483
+ case "string":
19484
+ this.postgresType = sql_syntax_1.sqls `TEXT`;
19485
+ break;
19486
+ default:
19487
+ throw new Error("getPostgresValueTypeCastStr: unsupported data type: `${" +
19488
+ typeof value +
19489
+ "}`");
19490
+ }
19491
+ }
19492
+ }
19493
+ exports.AspectQueryValue = AspectQueryValue;
19494
+ class AspectQuery {
19495
+ constructor(aspectId, path = [], negated = false) {
19496
+ this.aspectId = aspectId;
19497
+ this.path = path;
19498
+ this.negated = negated;
19499
+ }
19500
+ /**
19501
+ * Public interface of all types of AspectQuery. Call this method to covert AspectQuery to SQL statement.
19502
+ * Sub-class might choose to override this method to alter generic logic.
19503
+ *
19504
+ * @param {AspectQueryToSqlConfig} config
19505
+ * @return {SQLSyntax}
19506
+ * @memberof AspectQuery
19507
+ */
19508
+ toSql(config) {
19509
+ if (!this.aspectId) {
19510
+ throw new Error("Invalid AspectQuery: aspectId cannot be empty.");
19511
+ }
19512
+ const sqlQuery = this.sqlQueries(config);
19513
+ if (this.negated) {
19514
+ return sql_syntax_1.sqls `NOT ${sqlQuery}`;
19515
+ }
19516
+ else {
19517
+ return sqlQuery;
19518
+ }
19519
+ }
19520
+ }
19521
+ exports.AspectQuery = AspectQuery;
19522
+ class AspectQueryTrue extends AspectQuery {
19523
+ constructor() {
19524
+ super(undefined);
19525
+ }
19526
+ sqlQueries(config) {
19527
+ return sql_syntax_1.sqls `TRUE`;
19528
+ }
19529
+ // override default toSql as we don't need any validation logic
19530
+ toSql(config) {
19531
+ return this.sqlQueries(config);
19532
+ }
19533
+ }
19534
+ exports.AspectQueryTrue = AspectQueryTrue;
19535
+ class AspectQueryFalse extends AspectQuery {
19536
+ constructor() {
19537
+ super(undefined);
19538
+ }
19539
+ sqlQueries(config) {
19540
+ return sql_syntax_1.sqls `FALSE`;
19541
+ }
19542
+ // override default toSql as we don't need any validation logic
19543
+ toSql(config) {
19544
+ return this.sqlQueries(config);
19545
+ }
19546
+ }
19547
+ exports.AspectQueryFalse = AspectQueryFalse;
19548
+ class AspectQueryExists extends AspectQuery {
19549
+ sqlQueries(config) {
19550
+ var _a;
19551
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19552
+ if ((_a = this.path) === null || _a === void 0 ? void 0 : _a.length) {
19553
+ return sql_syntax_1.sqls `(${fieldRef} #> string_to_array(${this.path.join(",")}, ',')) IS NOT NULL`;
19554
+ }
19555
+ else {
19556
+ return fieldRef.isNotNull();
19557
+ }
19558
+ }
19559
+ }
19560
+ exports.AspectQueryExists = AspectQueryExists;
19561
+ class AspectQueryWithValue extends AspectQuery {
19562
+ constructor(value, operator, placeReferenceFirst, aspectId, path = [], negated = false) {
19563
+ super(aspectId, path, negated);
19564
+ this.value = value;
19565
+ this.operator = operator;
19566
+ this.placeReferenceFirst = placeReferenceFirst;
19567
+ }
19568
+ sqlQueries(config) {
19569
+ var _a;
19570
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19571
+ const tableDataRef = !((_a = this.path) === null || _a === void 0 ? void 0 : _a.length)
19572
+ ? sql_syntax_1.sqls `${fieldRef}::${this.value.postgresType}`
19573
+ : sql_syntax_1.sqls `(${fieldRef} #>> string_to_array(${this.path.join(",")}, ','))::${this.value.postgresType}`;
19574
+ return this.placeReferenceFirst
19575
+ ? sql_syntax_1.sqls `COALESCE(${tableDataRef} ${this.operator} ${this.value.value}::${this.value.postgresType}, false)`
19576
+ : sql_syntax_1.sqls `COALESCE(${this.value.value}::${this.value.postgresType} ${this.operator} ${tableDataRef}, false)`;
19577
+ }
19578
+ }
19579
+ exports.AspectQueryWithValue = AspectQueryWithValue;
19580
+ class AspectQueryArrayNotEmpty extends AspectQuery {
19581
+ sqlQueries(config) {
19582
+ var _a;
19583
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19584
+ const tableDataRef = !((_a = this.path) === null || _a === void 0 ? void 0 : _a.length)
19585
+ ? sql_syntax_1.sqls `(${fieldRef} #> string_to_array('0',','))`
19586
+ : sql_syntax_1.sqls `(${fieldRef} #>> string_to_array(${[...this.path, "0"].join(",")}, ','))`;
19587
+ return tableDataRef.isNotNull();
19588
+ }
19589
+ }
19590
+ exports.AspectQueryArrayNotEmpty = AspectQueryArrayNotEmpty;
19591
+ class AspectQueryValueInArray extends AspectQuery {
19592
+ constructor(value, aspectId, path = [], negated = false) {
19593
+ super(aspectId, path, negated);
19594
+ this.value = value;
19595
+ }
19596
+ sqlQueries(config) {
19597
+ var _a;
19598
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19599
+ const tableDataRef = !((_a = this.path) === null || _a === void 0 ? void 0 : _a.length)
19600
+ ? sql_syntax_1.sqls `COALESCE(
19601
+ (
19602
+ (${fieldRef}::JSONB #> string_to_array('0',','))::JSONB
19603
+ ) @> to_json(${this.value.value})::JSONB,
19604
+ FALSE
19605
+ )`
19606
+ : sql_syntax_1.sqls `COALESCE(
19607
+ (
19608
+ (${fieldRef}::JSONB #> string_to_array(${this.path.join(",")}, ','))::JSONB
19609
+ ) @> to_json(${this.value.value})::JSONB,
19610
+ FALSE
19611
+ )`;
19612
+ return tableDataRef;
19613
+ }
19614
+ }
19615
+ exports.AspectQueryValueInArray = AspectQueryValueInArray;
19616
+ class AspectQueryGroup {
19617
+ constructor(queries, joinWithAnd = true, negated = false) {
19618
+ this.queries = queries;
19619
+ this.joinWithAnd = joinWithAnd;
19620
+ this.negated = negated;
19621
+ }
19622
+ toSql(config) {
19623
+ var _a;
19624
+ if (!((_a = this.queries) === null || _a === void 0 ? void 0 : _a.length)) {
19625
+ return sql_syntax_1.default.empty;
19626
+ }
19627
+ let result;
19628
+ if (this.joinWithAnd) {
19629
+ if (this.queries.findIndex((item) => item instanceof AspectQueryFalse) !== -1) {
19630
+ result = sql_syntax_1.sqls `FALSE`;
19631
+ }
19632
+ else {
19633
+ result = sql_syntax_1.default.joinWithAnd(this.queries.map((q) => {
19634
+ if (q instanceof AspectQueryTrue) {
19635
+ return sql_syntax_1.default.empty;
19636
+ }
19637
+ else {
19638
+ return q.toSql(config);
19639
+ }
19640
+ }));
19641
+ }
19642
+ }
19643
+ else {
19644
+ if (this.queries.findIndex((item) => item instanceof AspectQueryTrue) !== -1) {
19645
+ result = sql_syntax_1.sqls `TRUE`;
19646
+ }
19647
+ else {
19648
+ result = sql_syntax_1.default.joinWithOr(this.queries.map((q) => {
19649
+ if (q instanceof AspectQueryFalse) {
19650
+ return sql_syntax_1.default.empty;
19651
+ }
19652
+ else {
19653
+ return q.toSql(config);
19654
+ }
19655
+ }));
19656
+ }
19657
+ }
19658
+ if (this.negated) {
19659
+ return sql_syntax_1.sqls `NOT ${result}`;
19660
+ }
19661
+ else {
19662
+ return result;
19663
+ }
19664
+ }
19665
+ }
19666
+ exports.AspectQueryGroup = AspectQueryGroup;
19667
+ //# sourceMappingURL=AspectQuery.js.map
19668
+
19669
+ /***/ }),
19670
+ /* 17 */
19671
+ /***/ (function(module, exports, __webpack_require__) {
19672
+
19673
+ "use strict";
19674
+
19675
+ Object.defineProperty(exports, "__esModule", { value: true });
19676
+ class ServerError extends Error {
19677
+ constructor(message = "Unknown Error", statusCode = 500) {
19678
+ super(message);
19679
+ this.statusCode = statusCode;
19680
+ }
19681
+ toData() {
19682
+ return {
19683
+ isError: true,
19684
+ errorCode: this.statusCode,
19685
+ errorMessage: this.message
19686
+ };
19687
+ }
19688
+ }
19689
+ exports.default = ServerError;
19690
+ //# sourceMappingURL=ServerError.js.map
19691
+
19692
+ /***/ }),
19693
+ /* 18 */
18319
19694
  /***/ (function(module, exports) {
18320
19695
 
18321
19696
  module.exports = require("text-treeview");