@magda/org-tree 1.2.1-alpha.0 → 2.0.0-alpha.0

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__;/**
@@ -17911,279 +17443,1864 @@ exports.default = NestedSetModelQueryer;
17911
17443
  lodash.upperCase = upperCase;
17912
17444
  lodash.upperFirst = upperFirst;
17913
17445
 
17914
- // Add aliases.
17915
- lodash.each = forEach;
17916
- lodash.eachRight = forEachRight;
17917
- lodash.first = head;
17446
+ // Add aliases.
17447
+ lodash.each = forEach;
17448
+ lodash.eachRight = forEachRight;
17449
+ lodash.first = head;
17450
+
17451
+ mixin(lodash, (function() {
17452
+ var source = {};
17453
+ baseForOwn(lodash, function(func, methodName) {
17454
+ if (!hasOwnProperty.call(lodash.prototype, methodName)) {
17455
+ source[methodName] = func;
17456
+ }
17457
+ });
17458
+ return source;
17459
+ }()), { 'chain': false });
17460
+
17461
+ /*------------------------------------------------------------------------*/
17462
+
17463
+ /**
17464
+ * The semantic version number.
17465
+ *
17466
+ * @static
17467
+ * @memberOf _
17468
+ * @type {string}
17469
+ */
17470
+ lodash.VERSION = VERSION;
17471
+
17472
+ // Assign default placeholders.
17473
+ arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
17474
+ lodash[methodName].placeholder = lodash;
17475
+ });
17476
+
17477
+ // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
17478
+ arrayEach(['drop', 'take'], function(methodName, index) {
17479
+ LazyWrapper.prototype[methodName] = function(n) {
17480
+ n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
17481
+
17482
+ var result = (this.__filtered__ && !index)
17483
+ ? new LazyWrapper(this)
17484
+ : this.clone();
17485
+
17486
+ if (result.__filtered__) {
17487
+ result.__takeCount__ = nativeMin(n, result.__takeCount__);
17488
+ } else {
17489
+ result.__views__.push({
17490
+ 'size': nativeMin(n, MAX_ARRAY_LENGTH),
17491
+ 'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
17492
+ });
17493
+ }
17494
+ return result;
17495
+ };
17496
+
17497
+ LazyWrapper.prototype[methodName + 'Right'] = function(n) {
17498
+ return this.reverse()[methodName](n).reverse();
17499
+ };
17500
+ });
17501
+
17502
+ // Add `LazyWrapper` methods that accept an `iteratee` value.
17503
+ arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
17504
+ var type = index + 1,
17505
+ isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
17506
+
17507
+ LazyWrapper.prototype[methodName] = function(iteratee) {
17508
+ var result = this.clone();
17509
+ result.__iteratees__.push({
17510
+ 'iteratee': getIteratee(iteratee, 3),
17511
+ 'type': type
17512
+ });
17513
+ result.__filtered__ = result.__filtered__ || isFilter;
17514
+ return result;
17515
+ };
17516
+ });
17517
+
17518
+ // Add `LazyWrapper` methods for `_.head` and `_.last`.
17519
+ arrayEach(['head', 'last'], function(methodName, index) {
17520
+ var takeName = 'take' + (index ? 'Right' : '');
17521
+
17522
+ LazyWrapper.prototype[methodName] = function() {
17523
+ return this[takeName](1).value()[0];
17524
+ };
17525
+ });
17526
+
17527
+ // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
17528
+ arrayEach(['initial', 'tail'], function(methodName, index) {
17529
+ var dropName = 'drop' + (index ? '' : 'Right');
17530
+
17531
+ LazyWrapper.prototype[methodName] = function() {
17532
+ return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
17533
+ };
17534
+ });
17535
+
17536
+ LazyWrapper.prototype.compact = function() {
17537
+ return this.filter(identity);
17538
+ };
17539
+
17540
+ LazyWrapper.prototype.find = function(predicate) {
17541
+ return this.filter(predicate).head();
17542
+ };
17543
+
17544
+ LazyWrapper.prototype.findLast = function(predicate) {
17545
+ return this.reverse().find(predicate);
17546
+ };
17547
+
17548
+ LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
17549
+ if (typeof path == 'function') {
17550
+ return new LazyWrapper(this);
17551
+ }
17552
+ return this.map(function(value) {
17553
+ return baseInvoke(value, path, args);
17554
+ });
17555
+ });
17556
+
17557
+ LazyWrapper.prototype.reject = function(predicate) {
17558
+ return this.filter(negate(getIteratee(predicate)));
17559
+ };
17560
+
17561
+ LazyWrapper.prototype.slice = function(start, end) {
17562
+ start = toInteger(start);
17563
+
17564
+ var result = this;
17565
+ if (result.__filtered__ && (start > 0 || end < 0)) {
17566
+ return new LazyWrapper(result);
17567
+ }
17568
+ if (start < 0) {
17569
+ result = result.takeRight(-start);
17570
+ } else if (start) {
17571
+ result = result.drop(start);
17572
+ }
17573
+ if (end !== undefined) {
17574
+ end = toInteger(end);
17575
+ result = end < 0 ? result.dropRight(-end) : result.take(end - start);
17576
+ }
17577
+ return result;
17578
+ };
17579
+
17580
+ LazyWrapper.prototype.takeRightWhile = function(predicate) {
17581
+ return this.reverse().takeWhile(predicate).reverse();
17582
+ };
17583
+
17584
+ LazyWrapper.prototype.toArray = function() {
17585
+ return this.take(MAX_ARRAY_LENGTH);
17586
+ };
17587
+
17588
+ // Add `LazyWrapper` methods to `lodash.prototype`.
17589
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
17590
+ var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
17591
+ isTaker = /^(?:head|last)$/.test(methodName),
17592
+ lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
17593
+ retUnwrapped = isTaker || /^find/.test(methodName);
17594
+
17595
+ if (!lodashFunc) {
17596
+ return;
17597
+ }
17598
+ lodash.prototype[methodName] = function() {
17599
+ var value = this.__wrapped__,
17600
+ args = isTaker ? [1] : arguments,
17601
+ isLazy = value instanceof LazyWrapper,
17602
+ iteratee = args[0],
17603
+ useLazy = isLazy || isArray(value);
17604
+
17605
+ var interceptor = function(value) {
17606
+ var result = lodashFunc.apply(lodash, arrayPush([value], args));
17607
+ return (isTaker && chainAll) ? result[0] : result;
17608
+ };
17609
+
17610
+ if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
17611
+ // Avoid lazy use if the iteratee has a "length" value other than `1`.
17612
+ isLazy = useLazy = false;
17613
+ }
17614
+ var chainAll = this.__chain__,
17615
+ isHybrid = !!this.__actions__.length,
17616
+ isUnwrapped = retUnwrapped && !chainAll,
17617
+ onlyLazy = isLazy && !isHybrid;
17618
+
17619
+ if (!retUnwrapped && useLazy) {
17620
+ value = onlyLazy ? value : new LazyWrapper(this);
17621
+ var result = func.apply(value, args);
17622
+ result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
17623
+ return new LodashWrapper(result, chainAll);
17624
+ }
17625
+ if (isUnwrapped && onlyLazy) {
17626
+ return func.apply(this, args);
17627
+ }
17628
+ result = this.thru(interceptor);
17629
+ return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
17630
+ };
17631
+ });
17632
+
17633
+ // Add `Array` methods to `lodash.prototype`.
17634
+ arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
17635
+ var func = arrayProto[methodName],
17636
+ chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
17637
+ retUnwrapped = /^(?:pop|shift)$/.test(methodName);
17638
+
17639
+ lodash.prototype[methodName] = function() {
17640
+ var args = arguments;
17641
+ if (retUnwrapped && !this.__chain__) {
17642
+ var value = this.value();
17643
+ return func.apply(isArray(value) ? value : [], args);
17644
+ }
17645
+ return this[chainName](function(value) {
17646
+ return func.apply(isArray(value) ? value : [], args);
17647
+ });
17648
+ };
17649
+ });
17650
+
17651
+ // Map minified method names to their real names.
17652
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
17653
+ var lodashFunc = lodash[methodName];
17654
+ if (lodashFunc) {
17655
+ var key = lodashFunc.name + '';
17656
+ if (!hasOwnProperty.call(realNames, key)) {
17657
+ realNames[key] = [];
17658
+ }
17659
+ realNames[key].push({ 'name': methodName, 'func': lodashFunc });
17660
+ }
17661
+ });
17662
+
17663
+ realNames[createHybrid(undefined, WRAP_BIND_KEY_FLAG).name] = [{
17664
+ 'name': 'wrapper',
17665
+ 'func': undefined
17666
+ }];
17667
+
17668
+ // Add methods to `LazyWrapper`.
17669
+ LazyWrapper.prototype.clone = lazyClone;
17670
+ LazyWrapper.prototype.reverse = lazyReverse;
17671
+ LazyWrapper.prototype.value = lazyValue;
17672
+
17673
+ // Add chain sequence methods to the `lodash` wrapper.
17674
+ lodash.prototype.at = wrapperAt;
17675
+ lodash.prototype.chain = wrapperChain;
17676
+ lodash.prototype.commit = wrapperCommit;
17677
+ lodash.prototype.next = wrapperNext;
17678
+ lodash.prototype.plant = wrapperPlant;
17679
+ lodash.prototype.reverse = wrapperReverse;
17680
+ lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
17681
+
17682
+ // Add lazy aliases.
17683
+ lodash.prototype.first = lodash.prototype.head;
17684
+
17685
+ if (symIterator) {
17686
+ lodash.prototype[symIterator] = wrapperToIterator;
17687
+ }
17688
+ return lodash;
17689
+ });
17690
+
17691
+ /*--------------------------------------------------------------------------*/
17692
+
17693
+ // Export lodash.
17694
+ var _ = runInContext();
17695
+
17696
+ // Some AMD build optimizers, like r.js, check for condition patterns like:
17697
+ if (true) {
17698
+ // Expose Lodash on the global object to prevent errors when Lodash is
17699
+ // loaded by a script tag in the presence of an AMD loader.
17700
+ // See http://requirejs.org/docs/errors.html#mismatch for more details.
17701
+ // Use `_.noConflict` to remove Lodash from the global object.
17702
+ root._ = _;
17703
+
17704
+ // Define as an anonymous module so, through path mapping, it can be
17705
+ // referenced as the "underscore" module.
17706
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
17707
+ return _;
17708
+ }).call(exports, __webpack_require__, exports, module),
17709
+ __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
17710
+ }
17711
+ // Check for `exports` after `define` in case a build optimizer adds it.
17712
+ else {}
17713
+ }.call(this));
17714
+
17715
+ /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(14)(module)))
17716
+
17717
+ /***/ }),
17718
+ /* 10 */
17719
+ /***/ (function(module, exports, __webpack_require__) {
17720
+
17721
+ "use strict";
17722
+
17723
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17724
+ if (k2 === undefined) k2 = k;
17725
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
17726
+ }) : (function(o, m, k, k2) {
17727
+ if (k2 === undefined) k2 = k;
17728
+ o[k2] = m[k];
17729
+ }));
17730
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17731
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17732
+ }) : function(o, v) {
17733
+ o["default"] = v;
17734
+ });
17735
+ var __importStar = (this && this.__importStar) || function (mod) {
17736
+ if (mod && mod.__esModule) return mod;
17737
+ var result = {};
17738
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
17739
+ __setModuleDefault(result, mod);
17740
+ return result;
17741
+ };
17742
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17743
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
17744
+ return new (P || (P = Promise))(function (resolve, reject) {
17745
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17746
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17747
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
17748
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
17749
+ });
17750
+ };
17751
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17752
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17753
+ };
17754
+ Object.defineProperty(exports, "__esModule", { value: true });
17755
+ 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;
17756
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
17757
+ const AuthDecision_1 = __webpack_require__(11);
17758
+ const lodash_1 = __webpack_require__(9);
17759
+ const ServerError_1 = __importDefault(__webpack_require__(17));
17760
+ /**
17761
+ * Escape SQL identifier string
17762
+ * Although postgreSQL does allow non-ASCII characters in identifiers, to make it simple, we will remove any non-ASCII characters.
17763
+ *
17764
+ * @export
17765
+ * @param {string} idStr
17766
+ * @return {*} {string}
17767
+ */
17768
+ function escapeIdentifierStr(idStr) {
17769
+ return '"' + idStr.replace(/[^\x20-\x7e]/g, "").replace(/"/g, "\"'") + '"';
17770
+ }
17771
+ exports.escapeIdentifierStr = escapeIdentifierStr;
17772
+ /**
17773
+ * Escape SQL identifier (e.g. column names, or table names).
17774
+ * `xxx."ss.dd` will be escaped as `"xxx"."""ss"."dd"`
17775
+ * Although postgreSQL does allow non-ASCII characters in identifiers, to make it simple, we will remove any non-ASCII characters.
17776
+ *
17777
+ * @export
17778
+ * @param {string} id
17779
+ * @return {*} {SQLSyntax}
17780
+ */
17781
+ function escapeIdentifier(id) {
17782
+ const sanitisedIdStr = id.replace(/[^\x20-\x7e]/g, "");
17783
+ const parts = sanitisedIdStr.split(".");
17784
+ const escapedIdStr = parts.length > 1
17785
+ ? parts.map((item) => escapeIdentifierStr(item)).join(".")
17786
+ : escapeIdentifierStr(sanitisedIdStr);
17787
+ return sql_syntax_1.default.createUnsafely(escapedIdStr);
17788
+ }
17789
+ exports.escapeIdentifier = escapeIdentifier;
17790
+ /**
17791
+ * Make a postgreSQL identifier in SQLSyntax from tableRef (optional) & column name.
17792
+ *
17793
+ * @export
17794
+ * @param {String} columnName
17795
+ * @param {String} [tableRef=""]
17796
+ * @param {Boolean} [useLowerCaseColumnName=true]
17797
+ * @return {*} {SQLSyntax}
17798
+ */
17799
+ function getTableColumnName(columnName, tableRef = "", useLowerCaseColumnName = false) {
17800
+ const id = [
17801
+ tableRef,
17802
+ useLowerCaseColumnName ? columnName.toLowerCase : useLowerCaseColumnName
17803
+ ]
17804
+ .filter((item) => item)
17805
+ .join(".");
17806
+ return escapeIdentifier(id);
17807
+ }
17808
+ exports.getTableColumnName = getTableColumnName;
17809
+ /**
17810
+ * Create a record for given table with given data object.
17811
+ * This method will use the key / value pairs of the object as column name / value of the new record.
17812
+ * It will return the newly created record
17813
+ *
17814
+ * @export
17815
+ * @param {pg.Client | pg.Pool} poolOrClient
17816
+ * @param {string} table
17817
+ * @param {{ [key: string]: Value }} data
17818
+ * @return {*}
17819
+ */
17820
+ function createTableRecord(poolOrClient, table, data, allowFieldList, autoGenerateUuid = true) {
17821
+ return __awaiter(this, void 0, void 0, function* () {
17822
+ if (!table.trim()) {
17823
+ throw new Error("invalid empty table name is supplied.");
17824
+ }
17825
+ if (allowFieldList === null || allowFieldList === void 0 ? void 0 : allowFieldList.length) {
17826
+ const keys = Object.keys(data);
17827
+ const diff = lodash_1.difference(keys, allowFieldList);
17828
+ if (diff === null || diff === void 0 ? void 0 : diff.length) {
17829
+ throw new ServerError_1.default(`Failed to create record, the following fields are not allowed: ${diff.join(",")}`, 400);
17830
+ }
17831
+ }
17832
+ if (autoGenerateUuid) {
17833
+ data["id"] = sql_syntax_1.sqls `uuid_generate_v4()`;
17834
+ }
17835
+ const [fieldList, valueList] = Object.keys(data).reduce((result, currentKey) => {
17836
+ const currentValue = data[currentKey];
17837
+ result[0].push(escapeIdentifier(currentKey));
17838
+ result[1].push(sql_syntax_1.sqls `${currentValue}`);
17839
+ return result;
17840
+ }, [[], []]);
17841
+ const sqlSyntax = sql_syntax_1.sqls `INSERT INTO ${escapeIdentifier(table)}
17842
+ (${sql_syntax_1.default.csv(...fieldList)})
17843
+ VALUES
17844
+ (${sql_syntax_1.default.csv(...valueList)})
17845
+ RETURNING *`;
17846
+ const result = yield poolOrClient.query(...sqlSyntax.toQuery());
17847
+ return result.rows[0];
17848
+ });
17849
+ }
17850
+ exports.createTableRecord = createTableRecord;
17851
+ function updateTableRecord(poolOrClient, table, id, data, allowFieldList) {
17852
+ return __awaiter(this, void 0, void 0, function* () {
17853
+ if (!id.trim()) {
17854
+ throw new ServerError_1.default("Failed to delete the record: empty id was provided.", 400);
17855
+ }
17856
+ if (!table.trim()) {
17857
+ throw new ServerError_1.default("invalid empty table name is supplied.", 500);
17858
+ }
17859
+ if (allowFieldList === null || allowFieldList === void 0 ? void 0 : allowFieldList.length) {
17860
+ const keys = Object.keys(data);
17861
+ const diff = lodash_1.difference(keys, allowFieldList);
17862
+ if (diff === null || diff === void 0 ? void 0 : diff.length) {
17863
+ throw new ServerError_1.default(`Failed to update record, the following fields are not allowed: ${diff.join(",")}`, 400);
17864
+ }
17865
+ }
17866
+ const updates = Object.keys(data).reduce((result, currentKey) => {
17867
+ const currentValue = data[currentKey];
17868
+ result.push(sql_syntax_1.sqls `${escapeIdentifier(currentKey)} = ${sql_syntax_1.sqls `${currentValue}`}`);
17869
+ return result;
17870
+ }, []);
17871
+ const sqlSyntax = sql_syntax_1.sqls `UPDATE ${escapeIdentifier(table)}
17872
+ SET ${sql_syntax_1.default.csv(...updates)}
17873
+ WHERE id = ${id}
17874
+ RETURNING *`;
17875
+ const result = yield poolOrClient.query(...sqlSyntax.toQuery());
17876
+ return result.rows[0];
17877
+ });
17878
+ }
17879
+ exports.updateTableRecord = updateTableRecord;
17880
+ function deleteTableRecord(poolOrClient, table, id) {
17881
+ return __awaiter(this, void 0, void 0, function* () {
17882
+ if (!id.trim()) {
17883
+ throw new ServerError_1.default("Failed to delete the record: empty id was provided.", 400);
17884
+ }
17885
+ if (!table.trim()) {
17886
+ throw new ServerError_1.default("invalid empty table name is supplied.", 500);
17887
+ }
17888
+ const sqlSyntax = sql_syntax_1.sqls `DELETE FROM ${escapeIdentifier(table)} WHERE id = ${id}`;
17889
+ yield poolOrClient.query(...sqlSyntax.toQuery());
17890
+ });
17891
+ }
17892
+ exports.deleteTableRecord = deleteTableRecord;
17893
+ function parseIntParam(p) {
17894
+ if (!p) {
17895
+ return 0;
17896
+ }
17897
+ const result = parseInt(p === null || p === void 0 ? void 0 : p.toString());
17898
+ if (isNaN(result)) {
17899
+ return 0;
17900
+ }
17901
+ return result;
17902
+ }
17903
+ exports.parseIntParam = parseIntParam;
17904
+ exports.MAX_PAGE_RECORD_NUMBER = 500;
17905
+ function searchTableRecord(poolOrClient, table, contiditions = [], queryConfig) {
17906
+ var _a, _b, _c;
17907
+ return __awaiter(this, void 0, void 0, function* () {
17908
+ if (!table.trim()) {
17909
+ throw new ServerError_1.default("invalid empty table name is supplied.");
17910
+ }
17911
+ const objectKind = (queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.objectKind) ? queryConfig.objectKind
17912
+ : "authObject";
17913
+ const authDecision = (queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.authDecision) ? queryConfig.authDecision
17914
+ : AuthDecision_1.UnconditionalTrueDecision;
17915
+ let limit = parseIntParam(queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.limit);
17916
+ const offset = parseIntParam(queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.offset);
17917
+ if (limit > exports.MAX_PAGE_RECORD_NUMBER) {
17918
+ limit = exports.MAX_PAGE_RECORD_NUMBER;
17919
+ }
17920
+ const config = (queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.toSqlConfig) ? queryConfig.toSqlConfig
17921
+ : {
17922
+ prefixes: [
17923
+ `input.${objectKind}.${lodash_1.camelCase(table.replace(/s$/, ""))}`
17924
+ ]
17925
+ };
17926
+ const authConditions = authDecision.toSql(config);
17927
+ const where = sql_syntax_1.default.where(sql_syntax_1.default.joinWithAnd([...contiditions, authConditions]));
17928
+ const sqlSyntax = sql_syntax_1.sqls `SELECT ${(queryConfig === null || queryConfig === void 0 ? void 0 : queryConfig.selectedFields) ? sql_syntax_1.default.csv(...queryConfig.selectedFields)
17929
+ : sql_syntax_1.sqls `*`}
17930
+ FROM ${escapeIdentifier(table)}
17931
+ ${((_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`)
17932
+ : sql_syntax_1.default.empty}
17933
+ ${where}
17934
+ ${(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"
17935
+ ? sql_syntax_1.default.csv(...queryConfig.groupBy)
17936
+ : queryConfig.groupBy}`
17937
+ : sql_syntax_1.default.empty}
17938
+ ${offset ? sql_syntax_1.sqls `OFFSET ${offset}` : sql_syntax_1.default.empty}
17939
+ ${limit ? sql_syntax_1.sqls `LIMIT ${limit}` : sql_syntax_1.default.empty}
17940
+ `;
17941
+ const result = yield poolOrClient.query(...sqlSyntax.toQuery());
17942
+ if (!((_c = result === null || result === void 0 ? void 0 : result.rows) === null || _c === void 0 ? void 0 : _c.length)) {
17943
+ return [];
17944
+ }
17945
+ else {
17946
+ return result.rows;
17947
+ }
17948
+ });
17949
+ }
17950
+ exports.searchTableRecord = searchTableRecord;
17951
+ function getTableRecord(poolOrClient, table, id, authDecision = AuthDecision_1.UnconditionalTrueDecision, objectKind = "authObject", toSqlConfig) {
17952
+ return __awaiter(this, void 0, void 0, function* () {
17953
+ const records = yield searchTableRecord(poolOrClient, table, [sql_syntax_1.sqls `id = ${id}`], {
17954
+ authDecision,
17955
+ objectKind,
17956
+ toSqlConfig
17957
+ });
17958
+ if (!records.length) {
17959
+ return null;
17960
+ }
17961
+ else {
17962
+ return records[0];
17963
+ }
17964
+ });
17965
+ }
17966
+ exports.getTableRecord = getTableRecord;
17967
+ function countTableRecord(poolOrClient, table, contiditions = [], authDecision, objectKind, toSqlConfig) {
17968
+ return __awaiter(this, void 0, void 0, function* () {
17969
+ const records = yield searchTableRecord(poolOrClient, table, contiditions, {
17970
+ authDecision,
17971
+ objectKind,
17972
+ toSqlConfig,
17973
+ selectedFields: [sql_syntax_1.sqls `COUNT(*) AS total`]
17974
+ });
17975
+ if (!records.length) {
17976
+ return 0;
17977
+ }
17978
+ else {
17979
+ return records[0]["total"];
17980
+ }
17981
+ });
17982
+ }
17983
+ exports.countTableRecord = countTableRecord;
17984
+ //# sourceMappingURL=SQLUtils.js.map
17985
+
17986
+ /***/ }),
17987
+ /* 11 */
17988
+ /***/ (function(module, exports, __webpack_require__) {
17989
+
17990
+ "use strict";
17991
+
17992
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17993
+ if (k2 === undefined) k2 = k;
17994
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
17995
+ }) : (function(o, m, k, k2) {
17996
+ if (k2 === undefined) k2 = k;
17997
+ o[k2] = m[k];
17998
+ }));
17999
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18000
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18001
+ }) : function(o, v) {
18002
+ o["default"] = v;
18003
+ });
18004
+ var __importStar = (this && this.__importStar) || function (mod) {
18005
+ if (mod && mod.__esModule) return mod;
18006
+ var result = {};
18007
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18008
+ __setModuleDefault(result, mod);
18009
+ return result;
18010
+ };
18011
+ Object.defineProperty(exports, "__esModule", { value: true });
18012
+ exports.ConciseOperand = exports.ConciseExpression = exports.ConciseRule = exports.UnconditionalFalseDecision = exports.UnconditionalTrueDecision = exports.isTrueEquivalent = void 0;
18013
+ const AspectQuery_1 = __webpack_require__(16);
18014
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
18015
+ class AuthDecision {
18016
+ constructor(hasResidualRules, residualRules, result, hasWarns = false, warns = [], unknowns) {
18017
+ if (typeof hasResidualRules !== "boolean") {
18018
+ throw new Error("Failed to create AuthDecision: invalid hasResidualRules type");
18019
+ }
18020
+ if (hasResidualRules && !(residualRules === null || residualRules === void 0 ? void 0 : residualRules.length)) {
18021
+ throw new Error("Failed to create AuthDecision: residualRules must have at least one item when hasResidualRules == true");
18022
+ }
18023
+ this.hasResidualRules = hasResidualRules;
18024
+ this.result = result;
18025
+ this.residualRules = residualRules;
18026
+ this.hasWarns = hasWarns;
18027
+ this.warns = warns;
18028
+ this.unknowns = unknowns;
18029
+ }
18030
+ static fromJson(data) {
18031
+ var _a;
18032
+ 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);
18033
+ }
18034
+ toAspectQueryGroups(prefixes) {
18035
+ if (this.hasResidualRules) {
18036
+ return this.residualRules.map((item) => item.toAspectQueryGroup(prefixes));
18037
+ }
18038
+ else {
18039
+ if (isTrueEquivalent(this.result)) {
18040
+ // unconditional true
18041
+ return [new AspectQuery_1.AspectQueryGroup([new AspectQuery_1.AspectQueryTrue()])];
18042
+ }
18043
+ else {
18044
+ return [new AspectQuery_1.AspectQueryGroup([new AspectQuery_1.AspectQueryFalse()])];
18045
+ }
18046
+ }
18047
+ }
18048
+ toSql(config) {
18049
+ return sql_syntax_1.default.joinWithOr(this.toAspectQueryGroups(config.prefixes).map((item) => item.toSql(config)));
18050
+ }
18051
+ }
18052
+ exports.default = AuthDecision;
18053
+ function isTrueEquivalent(value) {
18054
+ const typeStr = typeof value;
18055
+ if (typeStr === "boolean") {
18056
+ return value;
18057
+ }
18058
+ else if (typeStr === "undefined") {
18059
+ return false;
18060
+ }
18061
+ else if (typeof (value === null || value === void 0 ? void 0 : value.length) !== "undefined") {
18062
+ return !!value.length;
18063
+ }
18064
+ else {
18065
+ return !!value;
18066
+ }
18067
+ }
18068
+ exports.isTrueEquivalent = isTrueEquivalent;
18069
+ exports.UnconditionalTrueDecision = new AuthDecision(false, undefined, true);
18070
+ exports.UnconditionalFalseDecision = new AuthDecision(false, undefined, false);
18071
+ class ConciseRule {
18072
+ constructor(fullName, name, value, expressions, isDefault = false) {
18073
+ if (!isDefault && !(expressions === null || expressions === void 0 ? void 0 : expressions.length)) {
18074
+ throw new Error("Invalid ConciseRule data: it must contain at least one ConciseExpression item unless it's a default rule.");
18075
+ }
18076
+ this.default = isDefault ? true : false;
18077
+ this.fullName = fullName;
18078
+ this.name = name;
18079
+ this.value = value;
18080
+ this.expressions = (expressions === null || expressions === void 0 ? void 0 : expressions.length) ? expressions : [];
18081
+ }
18082
+ static fromJson(data) {
18083
+ var _a;
18084
+ 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)));
18085
+ }
18086
+ toAspectQueryGroup(prefixes) {
18087
+ var _a;
18088
+ return new AspectQuery_1.AspectQueryGroup((_a = this.expressions) === null || _a === void 0 ? void 0 : _a.map((item) => item.toAspectQuery(prefixes)), true, !isTrueEquivalent(this.value));
18089
+ }
18090
+ }
18091
+ exports.ConciseRule = ConciseRule;
18092
+ class ConciseExpression {
18093
+ constructor(operands, operator, negated = false) {
18094
+ var _a, _b;
18095
+ this.negated = negated ? true : false;
18096
+ this.operands = operands;
18097
+ this.operator = operator;
18098
+ if (!((_a = this.operands) === null || _a === void 0 ? void 0 : _a.length)) {
18099
+ throw new Error("invalid ConciseExpression data: it must have at least one operand.");
18100
+ }
18101
+ if (((_b = this.operands) === null || _b === void 0 ? void 0 : _b.length) > 1 && typeof operator !== "string") {
18102
+ throw new Error("invalid ConciseExpression data: when operands number > 1, operator must be a valid string value.");
18103
+ }
18104
+ }
18105
+ static fromJson(data) {
18106
+ var _a;
18107
+ 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);
18108
+ }
18109
+ getSqlOperator() {
18110
+ switch (this.operator) {
18111
+ case "=":
18112
+ return sql_syntax_1.sqls `=`;
18113
+ case ">":
18114
+ return sql_syntax_1.sqls `>`;
18115
+ case "<":
18116
+ return sql_syntax_1.sqls `<`;
18117
+ case ">=":
18118
+ return sql_syntax_1.sqls `>=`;
18119
+ case "<=":
18120
+ return sql_syntax_1.sqls `<=`;
18121
+ default:
18122
+ throw new Error(`Failed to convert auth decision operator to SQL operator: unsupported operator: ${this.operator}`);
18123
+ }
18124
+ }
18125
+ toAspectQuery(prefixes) {
18126
+ var _a, _b;
18127
+ if (((_a = this.operands) === null || _a === void 0 ? void 0 : _a.length) == 1) {
18128
+ const [aspectId, path, isCollection] = this.operands[0].extractAspectIdAndPath(prefixes);
18129
+ if (isCollection) {
18130
+ return new AspectQuery_1.AspectQueryArrayNotEmpty(aspectId, path, this.negated);
18131
+ }
18132
+ else {
18133
+ return new AspectQuery_1.AspectQueryExists(aspectId, path, this.negated);
18134
+ }
18135
+ }
18136
+ else if (((_b = this.operands) === null || _b === void 0 ? void 0 : _b.length) == 2) {
18137
+ const refOperand = this.operands.find((item) => item.isRef);
18138
+ const valOperand = this.operands.find((item) => !item.isRef);
18139
+ if (!valOperand) {
18140
+ throw new Error("Failed to convert auth decision expression to AspectQuery: " +
18141
+ `expression with both terms are references is currently not supported. Expression: ${this}`);
18142
+ }
18143
+ if (!refOperand) {
18144
+ // it's unlikely both terms are values as our decision API has already done the evaluation for this case.
18145
+ throw new Error("Failed to convert auth decision expression to AspectQuery: " +
18146
+ `Terms shouldn't be both value. Expression: ${this}`);
18147
+ }
18148
+ const [aspectId, path, isCollection] = refOperand.extractAspectIdAndPath(prefixes);
18149
+ if (isCollection && this.operator != "=") {
18150
+ throw new Error("Failed to convert auth decision expression to AspectQuery: " +
18151
+ `Only \`=\` operator is supported for collection reference. Expression: ${this}`);
18152
+ }
18153
+ if (isCollection && this.operator == "=") {
18154
+ return new AspectQuery_1.AspectQueryValueInArray(valOperand.toAspectQueryValue(), aspectId, path, this.negated);
18155
+ }
18156
+ else {
18157
+ return new AspectQuery_1.AspectQueryWithValue(valOperand.toAspectQueryValue(), this.getSqlOperator(), this.operands[0].isRef, aspectId, path, this.negated);
18158
+ }
18159
+ }
18160
+ else {
18161
+ throw new Error(`Failed to convert auth decision expression to AspectQuery: more than 2 operands found. Expression: ${this}`);
18162
+ }
18163
+ }
18164
+ }
18165
+ exports.ConciseExpression = ConciseExpression;
18166
+ class ConciseOperand {
18167
+ constructor(isRef, value) {
18168
+ if (typeof isRef !== "boolean") {
18169
+ throw new Error("Invalid ConciseOperand data: isRef must be a boolean value.");
18170
+ }
18171
+ this.isRef = isRef;
18172
+ if (isRef && typeof value !== "string") {
18173
+ throw new Error("Invalid ConciseOperand data: when `isRef`== true, `value` must be a string value (ref string).");
18174
+ }
18175
+ this.value = value;
18176
+ }
18177
+ static fromJson(data) {
18178
+ return new ConciseOperand(data === null || data === void 0 ? void 0 : data.isRef, data === null || data === void 0 ? void 0 : data.value);
18179
+ }
18180
+ refString() {
18181
+ if (!this.isRef) {
18182
+ throw new Error("Cannot convert non-ref term to a ref string");
18183
+ }
18184
+ else {
18185
+ if (typeof this.value === "string") {
18186
+ return this.value;
18187
+ }
18188
+ else {
18189
+ throw new Error(`ref term has non-string type value: ${this.value}`);
18190
+ }
18191
+ }
18192
+ }
18193
+ isCollectionRef() {
18194
+ return this.refString().endsWith("[_]");
18195
+ }
18196
+ refStringWithoutPrefixes(prefixes) {
18197
+ const sortedPrefixes = prefixes.sort((a, b) => b.length - a.length);
18198
+ return sortedPrefixes.reduce((ref, prefix) => {
18199
+ if (ref.startsWith(prefix)) {
18200
+ return ref.substring(prefix.length);
18201
+ }
18202
+ else {
18203
+ return ref;
18204
+ }
18205
+ }, this.refString());
18206
+ }
18207
+ extractAspectIdAndPath(prefixes) {
18208
+ // make it work for both "input.object.record" & "input.object.record." prefixe input
18209
+ // we remove the first leading `.` char (if any)
18210
+ let ref = this.refStringWithoutPrefixes(prefixes).replace(/^\./, "");
18211
+ const isCollection = this.isCollectionRef();
18212
+ if (isCollection) {
18213
+ ref = ref.replace(/\[_\]$/, "");
18214
+ }
18215
+ const parts = ref.split(".").filter((item) => item);
18216
+ if (parts.length < 2) {
18217
+ return [ref, [], isCollection];
18218
+ }
18219
+ else {
18220
+ return [parts[0], parts.slice(1, parts.length), isCollection];
18221
+ }
18222
+ }
18223
+ toAspectQueryValue() {
18224
+ if (this.isRef) {
18225
+ throw new Error(`Attempt to covert reference \`Operand\` to \`AspectQueryValue\`: ${this}`);
18226
+ }
18227
+ return new AspectQuery_1.AspectQueryValue(this.value);
18228
+ }
18229
+ }
18230
+ exports.ConciseOperand = ConciseOperand;
18231
+ //# sourceMappingURL=AuthDecision.js.map
18232
+
18233
+ /***/ }),
18234
+ /* 12 */,
18235
+ /* 13 */
18236
+ /***/ (function(module, exports, __webpack_require__) {
18237
+
18238
+ "use strict";
18239
+
18240
+ var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) {
18241
+ if (k2 === undefined) k2 = k;
18242
+ Object.defineProperty(o, k2, { enumerable: true, get: function () {return m[k];} });
18243
+ } : function (o, m, k, k2) {
18244
+ if (k2 === undefined) k2 = k;
18245
+ o[k2] = m[k];
18246
+ });
18247
+ var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) {
18248
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18249
+ } : function (o, v) {
18250
+ o["default"] = v;
18251
+ });
18252
+ var __importStar = this && this.__importStar || function (mod) {
18253
+ if (mod && mod.__esModule) return mod;
18254
+ var result = {};
18255
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18256
+ __setModuleDefault(result, mod);
18257
+ return result;
18258
+ };
18259
+ var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) {
18260
+ function adopt(value) {return value instanceof P ? value : new P(function (resolve) {resolve(value);});}
18261
+ return new (P || (P = Promise))(function (resolve, reject) {
18262
+ function fulfilled(value) {try {step(generator.next(value));} catch (e) {reject(e);}}
18263
+ function rejected(value) {try {step(generator["throw"](value));} catch (e) {reject(e);}}
18264
+ function step(result) {result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);}
18265
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
18266
+ });
18267
+ };
18268
+ var __importDefault = this && this.__importDefault || function (mod) {
18269
+ return mod && mod.__esModule ? mod : { "default": mod };
18270
+ };
18271
+ Object.defineProperty(exports, "__esModule", { value: true });
18272
+ exports.NodeNotFoundError = void 0;
18273
+ const lodash_1 = __importDefault(__webpack_require__(9));
18274
+ const tsmonad_1 = __webpack_require__(15);
18275
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
18276
+ const SQLUtils_1 = __webpack_require__(10);
18277
+ const AuthDecision_1 = __webpack_require__(11);
18278
+ const textTree = __webpack_require__(18);
18279
+ class NodeNotFoundError extends Error {}
18280
+
18281
+ exports.NodeNotFoundError = NodeNotFoundError;
18282
+ function isNonEmptyArray(v) {
18283
+ if (!v || !lodash_1.default.isArray(v) || !v.length)
18284
+ return false;
18285
+ return true;
18286
+ }
18287
+ const INVALID_CHAR_REGEX = /[^a-z_\d]/i;
18288
+ function isValidSqlIdentifier(id) {
18289
+ if (INVALID_CHAR_REGEX.test(id))
18290
+ return false;
18291
+ return true;
18292
+ }
18293
+ class NestedSetModelQueryer {
18294
+ /**
18295
+ * Creates an instance of NestedSetModelQueryer.
18296
+ * @param {pg.Pool} dbPool
18297
+ * @param {string} tableName
18298
+ * @param {string[]} [defaultSelectFieldList=null] default select fields; If null, all fields (i.e. `SELECT "id", "name"`) will be returned
18299
+ * @memberof NestedSetModelQueryer
18300
+ */
18301
+ constructor(dbPool, tableName, defaultSelectFieldList = null, defaultInsertFieldList = null) {
18302
+ /**
18303
+ * default select fields if [], all fields (i.e. `SELECT "id", "name"`) will be returned
18304
+ *
18305
+ * @type {string[]}
18306
+ * @memberof NestedSetModelQueryer
18307
+ */
18308
+ this.defaultSelectFieldList = ["id", "name"];
18309
+ /**
18310
+ * Default field list that will be used when insert nodes into tree.
18311
+ * By default, only `name` field will be saved to database
18312
+ * e.g. If your tree nodes have three properties (besides `id`, `left`, `right` --- they auto generated):
18313
+ * - name
18314
+ * - description
18315
+ * - fullName
18316
+ *
18317
+ * Then you should set `defaultInsertFieldList` to ["name", "description", "fullName"]
18318
+ *
18319
+ * @type {string[]}
18320
+ * @memberof NestedSetModelQueryer
18321
+ */
18322
+ this.defaultInsertFieldList = ["name"];
18323
+ if (!dbPool)
18324
+ throw new Error("dbPool cannot be empty!");
18325
+ if (!tableName)
18326
+ throw new Error("tableName cannot be empty!");
18327
+ if (!isValidSqlIdentifier(tableName)) {
18328
+ throw new Error(`tableName: ${tableName} contains invalid characters!`);
18329
+ }
18330
+ this.pool = dbPool;
18331
+ this.tableName = tableName;
18332
+ if (defaultSelectFieldList) {
18333
+ if (!lodash_1.default.isArray(defaultSelectFieldList))
18334
+ throw new Error("defaultSelectFieldList should be an array");
18335
+ this.defaultSelectFieldList = defaultSelectFieldList;
18336
+ }
18337
+ if (defaultSelectFieldList) {
18338
+ if (!lodash_1.default.isArray(defaultSelectFieldList))
18339
+ throw new Error("defaultSelectFieldList should be an array");
18340
+ this.defaultSelectFieldList = defaultSelectFieldList;
18341
+ }
18342
+ if (defaultInsertFieldList) {
18343
+ if (!lodash_1.default.isArray(defaultInsertFieldList))
18344
+ throw new Error("defaultInsertFieldList should be an array");
18345
+ this.defaultInsertFieldList = defaultInsertFieldList;
18346
+ }
18347
+ }
18348
+ selectFields(tableAliasOrName = "", fields = null) {
18349
+ const fieldList = isNonEmptyArray(fields) ?
18350
+ fields :
18351
+ this.defaultSelectFieldList;
18352
+ if (!isNonEmptyArray(fieldList)) {
18353
+ return sql_syntax_1.sqls`*`;
18354
+ }
18355
+ if (!isValidSqlIdentifier(tableAliasOrName)) {
18356
+ throw new Error(`'tableAliasOrName' ${tableAliasOrName} contains invalid characters.`);
18357
+ }
18358
+ // --- do not double quote `tableAliasOrName`
18359
+ // --- or you will get missing FROM-clause entry for table error
18360
+ return sql_syntax_1.default.createUnsafely(fieldList.
18361
+ map(f => {
18362
+ if (!isValidSqlIdentifier(f)) {
18363
+ throw new Error(`Field name ${f} contains invalid characters.`);
18364
+ }
18365
+ return tableAliasOrName === "" ?
18366
+ `"${f}"` :
18367
+ `${tableAliasOrName}."${f}"`;
18368
+ }).
18369
+ join(", "));
18370
+ }
18371
+ /**
18372
+ * Get nodes by name
18373
+ * You hardly need this one --- only for write test case (you can get a id from name)
18374
+ *
18375
+ * @param {string} name
18376
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18377
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18378
+ * @returns {Promise<NodeRecord[]>}
18379
+ * @memberof NestedSetModelQueryer
18380
+ */
18381
+ getNodes(nodesQuery = {}, fields = null, client = null) {
18382
+ return __awaiter(this, void 0, void 0, function* () {
18383
+ const clauses = [
18384
+ nodesQuery.name ?
18385
+ sql_syntax_1.sqls`"name" = ${nodesQuery.name}` :
18386
+ sql_syntax_1.default.empty,
18387
+ nodesQuery.leafNodesOnly ?
18388
+ sql_syntax_1.sqls`"left" = ( "right" - 1 )` :
18389
+ sql_syntax_1.default.empty];
18390
+
18391
+ const whereClause = sql_syntax_1.default.where(sql_syntax_1.default.joinWithAnd(clauses));
18392
+ const query = sql_syntax_1.sqls`SELECT ${this.selectFields("", fields)} FROM ${SQLUtils_1.escapeIdentifier(this.tableName)} ${whereClause}`;
18393
+ const result = yield (client ? client : this.pool).query(...query.toQuery());
18394
+ if (!result || !result.rows || !result.rows.length)
18395
+ return [];
18396
+ return result.rows;
18397
+ });
18398
+ }
18399
+ /**
18400
+ *
18401
+ * Get a node by its id
18402
+ * @param {string} id
18403
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18404
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18405
+ * @returns {Promise<NodeRecord>}
18406
+ * @memberof NestedSetModelQueryer
18407
+ */
18408
+ getNodeById(id, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18409
+ return __awaiter(this, void 0, void 0, function* () {
18410
+ const authConditions = authDecision.toSql({
18411
+ prefixes: ["input.authObject.orgUnit"] });
18412
+
18413
+ 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([
18414
+ sql_syntax_1.sqls`"id" = ${id}`,
18415
+ authConditions])
18416
+ }`.toQuery());
18417
+ if (!result || !result.rows || !result.rows.length)
18418
+ return tsmonad_1.Maybe.nothing();
18419
+ return tsmonad_1.Maybe.just(result.rows[0]);
18420
+ });
18421
+ }
18422
+ /**
18423
+ * Get the root node of the tree
18424
+ * Return null if empty tree
18425
+ *
18426
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18427
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18428
+ * @returns {Promise<NodeRecord>}
18429
+ * @memberof NestedSetModelQueryer
18430
+ */
18431
+ getRootNode(fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18432
+ return __awaiter(this, void 0, void 0, function* () {
18433
+ const authConditions = authDecision.toSql({
18434
+ prefixes: ["input.authObject.orgUnit"] });
17918
18435
 
17919
- mixin(lodash, (function() {
17920
- var source = {};
17921
- baseForOwn(lodash, function(func, methodName) {
17922
- if (!hasOwnProperty.call(lodash.prototype, methodName)) {
17923
- source[methodName] = func;
17924
- }
17925
- });
17926
- return source;
17927
- }()), { 'chain': false });
18436
+ 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([
18437
+ sql_syntax_1.sqls`"left" = 1`,
18438
+ authConditions])
18439
+ }`.toQuery());
18440
+ if (!result || !result.rows || !result.rows.length)
18441
+ return tsmonad_1.Maybe.nothing();
18442
+ return tsmonad_1.Maybe.just(result.rows[0]);
18443
+ });
18444
+ }
18445
+ /**
18446
+ * Get All children of a given node
18447
+ * (including immediate children and children of immediate children etc.)
18448
+ * If the node has no child (i.e. a leaf node), an empty array will be returned
18449
+ *
18450
+ * @param {string} parentNodeId
18451
+ * @param {boolean} [includeMyself=false]
18452
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18453
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18454
+ * @returns {Promise<NodeRecord[]>}
18455
+ * @memberof NestedSetModelQueryer
18456
+ */
18457
+ getAllChildren(parentNodeId, includeMyself = false, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18458
+ return __awaiter(this, void 0, void 0, function* () {
18459
+ const authConditions = authDecision.toSql({
18460
+ prefixes: ["input.authObject.orgUnit"],
18461
+ tableRef: "Children" });
18462
+
18463
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18464
+ const conditions = [
18465
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`>=` : sql_syntax_1.sqls`>`} Parents."left"`,
18466
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`<=` : sql_syntax_1.sqls`<`} Parents."right"`,
18467
+ sql_syntax_1.sqls`Parents."id" = ${parentNodeId}`,
18468
+ authConditions];
18469
+
18470
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children", fields)}
18471
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18472
+ WHERE ${sql_syntax_1.default.joinWithAnd(conditions)}`.toQuery());
18473
+ if (!result || !result.rows || !result.rows.length)
18474
+ return [];
18475
+ return result.rows;
18476
+ });
18477
+ }
18478
+ /**
18479
+ * Get All parents of a given node
18480
+ * (including immediate parent and parents of immediate parent etc.)
18481
+ * If the node has no parent (i.e. a root node), an empty array will be returned (unless `includeMyself` = true)
18482
+ *
18483
+ * @param {string} childNodeId
18484
+ * @param {boolean} [includeMyself=false]
18485
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18486
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18487
+ * @returns {Promise<NodeRecord[]>}
18488
+ * @memberof NestedSetModelQueryer
18489
+ */
18490
+ getAllParents(childNodeId, includeMyself = false, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18491
+ return __awaiter(this, void 0, void 0, function* () {
18492
+ const authConditions = authDecision.toSql({
18493
+ prefixes: ["input.authObject.orgUnit"],
18494
+ tableRef: "Parents" });
18495
+
18496
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18497
+ const conditions = [
18498
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`>=` : sql_syntax_1.sqls`>`} Parents."left"`,
18499
+ sql_syntax_1.sqls`Children."left" ${includeMyself ? sql_syntax_1.sqls`<=` : sql_syntax_1.sqls`<`} Parents."right"`,
18500
+ sql_syntax_1.sqls`Children."id" = ${childNodeId}`,
18501
+ authConditions];
18502
+
18503
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Parents", fields)}
18504
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18505
+ WHERE ${sql_syntax_1.default.joinWithAnd(conditions)}`.toQuery());
18506
+ if (!result || !result.rows || !result.rows.length)
18507
+ return [];
18508
+ return result.rows;
18509
+ });
18510
+ }
18511
+ /**
18512
+ * Get Immediate Children of a Node
18513
+ * If the node has no child (i.e. a leaf node), an empty array will be returned
18514
+ *
18515
+ * @param {string} parentNodeId
18516
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18517
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18518
+ * @returns {Promise<NodeRecord[]>}
18519
+ * @memberof NestedSetModelQueryer
18520
+ */
18521
+ getImmediateChildren(parentNodeId, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18522
+ return __awaiter(this, void 0, void 0, function* () {
18523
+ const authConditions = authDecision.toSql({
18524
+ prefixes: ["input.authObject.orgUnit"],
18525
+ tableRef: "Children" });
17928
18526
 
17929
- /*------------------------------------------------------------------------*/
18527
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18528
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children", fields)}
18529
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18530
+ WHERE Children."left" BETWEEN Parents."left" AND Parents."right"
18531
+ AND Parents."left" = (
18532
+ SELECT MAX(S."left") FROM ${tbl} AS S
18533
+ WHERE S."left" < Children."left" AND S."right" > Children."right"
18534
+ )
18535
+ AND Parents."id" = ${parentNodeId}
18536
+ ${authConditions.isEmpty ?
18537
+ sql_syntax_1.default.empty :
18538
+ sql_syntax_1.sqls` AND ${authConditions}`}
18539
+ ORDER BY Children."left" ASC`.toQuery());
18540
+ if (!result || !result.rows || !result.rows.length)
18541
+ return [];
18542
+ return result.rows;
18543
+ });
18544
+ }
18545
+ /**
18546
+ * Get Immediate Parent of a Node
18547
+ * If the node has no parent (i.e. a root node), null will be returned
18548
+ *
18549
+ * @param {string} childNodeId
18550
+ * @param {string[]} [fields=null] Selected Fields; If null, use this.defaultSelectFieldList
18551
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18552
+ * @returns {Promise<NodeRecord>}
18553
+ * @memberof NestedSetModelQueryer
18554
+ */
18555
+ getImmediateParent(childNodeId, fields = null, client = null, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18556
+ return __awaiter(this, void 0, void 0, function* () {
18557
+ const authConditions = authDecision.toSql({
18558
+ prefixes: ["input.authObject.orgUnit"],
18559
+ tableRef: "Parents" });
17930
18560
 
17931
- /**
17932
- * The semantic version number.
18561
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18562
+ const result = yield (client ? client : this.pool).query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Parents", fields)}
18563
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18564
+ WHERE Children.left BETWEEN Parents.left AND Parents.right
18565
+ AND Parents.left = (
18566
+ SELECT MAX(S.left) FROM ${tbl} AS S
18567
+ WHERE S.left < Children.left AND S.right > Children.right
18568
+ )
18569
+ AND Children.id = ${childNodeId}
18570
+ ${authConditions.isEmpty ?
18571
+ sql_syntax_1.default.empty :
18572
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18573
+ if (!result || !result.rows || !result.rows.length)
18574
+ return tsmonad_1.Maybe.nothing();
18575
+ return tsmonad_1.Maybe.just(result.rows[0]);
18576
+ });
18577
+ }
18578
+ /**
18579
+ * Get all nodes at level n from top
18580
+ * e.g. get all nodes at level 3:
18581
+ * this.getAllNodesAtLevel(3)
18582
+ * Root node is at level 1
17933
18583
  *
17934
- * @static
17935
- * @memberOf _
17936
- * @type {string}
18584
+ * @param {number} level
18585
+ * @returns {Promise<NodeRecord[]>}
18586
+ * @memberof NestedSetModelQueryer
17937
18587
  */
17938
- lodash.VERSION = VERSION;
18588
+ getAllNodesAtLevel(level, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18589
+ return __awaiter(this, void 0, void 0, function* () {
18590
+ const authConditions = authDecision.toSql({
18591
+ prefixes: ["input.authObject.orgUnit"],
18592
+ tableRef: "t2" });
18593
+
18594
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18595
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("t2")}
18596
+ FROM ${tbl} AS t1, ${tbl} AS t2
18597
+ WHERE t2.left BETWEEN t1.left AND t1.right
18598
+ ${authConditions.isEmpty ?
18599
+ sql_syntax_1.default.empty :
18600
+ sql_syntax_1.sqls` AND ${authConditions}`}
18601
+ GROUP BY t2.id
18602
+ HAVING COUNT(t1.id) = ${level}`.toQuery());
18603
+ if (!result || !result.rows || !result.rows.length)
18604
+ return [];
18605
+ return result.rows;
18606
+ });
18607
+ }
18608
+ /**
18609
+ * Get level no. of a given node
18610
+ * Starts from 1. i.e. The root node is 1
18611
+ *
18612
+ * @param {string} nodeId
18613
+ * @returns {Promise<number>}
18614
+ * @throws NodeNotFoundError If the node can't be found in the tree
18615
+ * @memberof NestedSetModelQueryer
18616
+ */
18617
+ getLevelOfNode(nodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18618
+ return __awaiter(this, void 0, void 0, function* () {
18619
+ const authConditions = authDecision.toSql({
18620
+ prefixes: ["input.authObject.orgUnit"],
18621
+ tableRef: "Parents" });
18622
+
18623
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18624
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT COUNT(Parents.id) AS level
18625
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18626
+ WHERE Children.left BETWEEN Parents.left AND Parents.right AND Children.id = ${nodeId}
18627
+ ${authConditions.isEmpty ?
18628
+ sql_syntax_1.default.empty :
18629
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18630
+ if (!result || !result.rows || !result.rows.length)
18631
+ throw new NodeNotFoundError();
18632
+ const level = parseInt(result.rows[0]["level"]);
18633
+ if (!lodash_1.default.isNumber(level) || lodash_1.default.isNaN(level) || level < 1)
18634
+ throw new Error(`Could find a valid level for node ${nodeId}: ${level}`);
18635
+ return level;
18636
+ });
18637
+ }
18638
+ /**
18639
+ * Get total height (no. of the levels) of the tree
18640
+ * Starts with 1 level.
18641
+ *
18642
+ * @returns {Promise<number>}
18643
+ * @throws NodeNotFoundError If the root node can't be found in the tree
18644
+ * @memberof NestedSetModelQueryer
18645
+ */
18646
+ getTreeHeight(authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18647
+ return __awaiter(this, void 0, void 0, function* () {
18648
+ const authConditions = authDecision.toSql({
18649
+ prefixes: ["input.authObject.orgUnit"],
18650
+ tableRef: "t1" });
18651
+
18652
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18653
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT MAX(level) AS height
18654
+ FROM(
18655
+ SELECT COUNT(t1.id)
18656
+ FROM ${tbl} AS t1, ${tbl} AS t2
18657
+ WHERE t2.left BETWEEN t1.left AND t1.right
18658
+ ${authConditions.isEmpty ?
18659
+ sql_syntax_1.default.empty :
18660
+ sql_syntax_1.sqls` AND ${authConditions}`}
18661
+ GROUP BY t2.id
18662
+ ) AS L(level)`.toQuery());
18663
+ if (!result || !result.rows || !result.rows.length)
18664
+ throw new NodeNotFoundError();
18665
+ const height = parseInt(result.rows[0]["height"]);
18666
+ if (!lodash_1.default.isNumber(height) || lodash_1.default.isNaN(height) || height < 0)
18667
+ throw new Error(`Invalid height for tree: ${height}`);
18668
+ return height;
18669
+ });
18670
+ }
18671
+ /**
18672
+ * Get left most immediate child of a node
18673
+ *
18674
+ * @param {string} parentNodeId
18675
+ * @returns {Promise<Maybe<NodeRecord>>}
18676
+ * @memberof NestedSetModelQueryer
18677
+ */
18678
+ getLeftMostImmediateChild(parentNodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18679
+ return __awaiter(this, void 0, void 0, function* () {
18680
+ const authConditions = authDecision.toSql({
18681
+ prefixes: ["input.authObject.orgUnit"],
18682
+ tableRef: "Children" });
18683
+
18684
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18685
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children")}
18686
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18687
+ WHERE Children.left = Parents.left + 1 AND Parents.id = ${parentNodeId}
18688
+ ${authConditions.isEmpty ?
18689
+ sql_syntax_1.default.empty :
18690
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18691
+ if (!result || !result.rows || !result.rows.length)
18692
+ return tsmonad_1.Maybe.nothing();
18693
+ return tsmonad_1.Maybe.just(result.rows[0]);
18694
+ });
18695
+ }
18696
+ /**
18697
+ * Get right most immediate child of a node
18698
+ *
18699
+ * @param {string} parentNodeId
18700
+ * @returns {Promise<Maybe<NodeRecord>>}
18701
+ * @memberof NestedSetModelQueryer
18702
+ */
18703
+ getRightMostImmediateChild(parentNodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18704
+ return __awaiter(this, void 0, void 0, function* () {
18705
+ const authConditions = authDecision.toSql({
18706
+ prefixes: ["input.authObject.orgUnit"],
18707
+ tableRef: "Children" });
18708
+
18709
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18710
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("Children")}
18711
+ FROM ${tbl} AS Parents, ${tbl} AS Children
18712
+ WHERE Children.right = Parents.right - 1 AND Parents.id = ${parentNodeId}
18713
+ ${authConditions.isEmpty ?
18714
+ sql_syntax_1.default.empty :
18715
+ sql_syntax_1.sqls` AND ${authConditions}`}`.toQuery());
18716
+ if (!result || !result.rows || !result.rows.length)
18717
+ return tsmonad_1.Maybe.nothing();
18718
+ return tsmonad_1.Maybe.just(result.rows[0]);
18719
+ });
18720
+ }
18721
+ /**
18722
+ * Get all nodes on the top to down path between the `higherNode` to the `lowerNode`
18723
+ * Sort from higher level nodes to lower level node
18724
+ * If a path doesn't exist, null will be returned
18725
+ * If you pass a lower node to the `higherNodeId` and a higher node to `lowerNodeId`, null will be returned
18726
+ *
18727
+ * @param {string} higherNodeId
18728
+ * @param {string} lowerNodeId
18729
+ * @returns {Promise<Maybe<NodeRecord[]>}
18730
+ * @memberof NestedSetModelQueryer
18731
+ */
18732
+ getTopDownPathBetween(higherNodeId, lowerNodeId, authDecision = AuthDecision_1.UnconditionalTrueDecision) {
18733
+ return __awaiter(this, void 0, void 0, function* () {
18734
+ const authConditions = authDecision.toSql({
18735
+ prefixes: ["input.authObject.orgUnit"],
18736
+ tableRef: "t2" });
18737
+
18738
+ const tbl = SQLUtils_1.escapeIdentifier(this.tableName);
18739
+ const result = yield this.pool.query(...sql_syntax_1.sqls`SELECT ${this.selectFields("t2")}
18740
+ FROM ${tbl} AS t1, ${tbl} AS t2, ${tbl} AS t3
18741
+ WHERE t1.id = ${higherNodeId} AND t3.id = ${lowerNodeId}
18742
+ AND t2.left BETWEEN t1.left AND t1.right
18743
+ AND t3.left BETWEEN t2.left AND t2.right
18744
+ ${authConditions.isEmpty ?
18745
+ sql_syntax_1.default.empty :
18746
+ sql_syntax_1.sqls` AND ${authConditions}`}
18747
+ ORDER BY (t2.right-t2.left) DESC`.toQuery());
18748
+ if (!result || !result.rows || !result.rows.length)
18749
+ return tsmonad_1.Maybe.nothing();
18750
+ return tsmonad_1.Maybe.just(result.rows);
18751
+ });
18752
+ }
18753
+ /**
18754
+ * Compare the relative position of the two nodes
18755
+ * If node1 is superior to node2, return "ancestor"
18756
+ * if node1 is the subordinate of node2, return "descendant"
18757
+ * If node1 = node2 return "equal"
18758
+ * If there is no path can be found between Node1 and Node2 return "unrelated"
18759
+ *
18760
+ * @param {string} node1Id
18761
+ * @param {string} node2Id
18762
+ * @param {pg.Client} [client=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18763
+ * @returns {Promise<CompareNodeResult>}
18764
+ * @memberof NestedSetModelQueryer
18765
+ */
18766
+ compareNodes(node1Id, node2Id, client = null) {
18767
+ return __awaiter(this, void 0, void 0, function* () {
18768
+ const tbl = this.tableName;
18769
+ const result = yield (client ? client : this.pool).query(`SELECT (
18770
+ CASE
18771
+ WHEN CAST($1 AS varchar) = CAST($2 AS varchar)
18772
+ THEN 0
18773
+ WHEN t1.left BETWEEN t2.left AND t2.right
18774
+ THEN -1
18775
+ WHEN t2.left BETWEEN t1.left AND t1.right
18776
+ THEN 1
18777
+ ELSE null
18778
+ END
18779
+ ) AS "result"
18780
+ FROM "${tbl}" AS t1, "${tbl}" AS t2
18781
+ WHERE t1.id = CAST($1 AS uuid) AND t2.id = CAST($2 AS uuid)`, [node1Id, node2Id]);
18782
+ if (!result || !result.rows || !result.rows.length)
18783
+ return "unrelated";
18784
+ const comparisonResult = result.rows[0]["result"];
18785
+ if (typeof comparisonResult === "number") {
18786
+ switch (comparisonResult) {
18787
+ case 1:
18788
+ return "ancestor";
18789
+ case -1:
18790
+ return "descendant";
18791
+ case 0:
18792
+ return "equal";}
17939
18793
 
17940
- // Assign default placeholders.
17941
- arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
17942
- lodash[methodName].placeholder = lodash;
18794
+ }
18795
+ return "unrelated";
17943
18796
  });
17944
-
17945
- // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
17946
- arrayEach(['drop', 'take'], function(methodName, index) {
17947
- LazyWrapper.prototype[methodName] = function(n) {
17948
- n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
17949
-
17950
- var result = (this.__filtered__ && !index)
17951
- ? new LazyWrapper(this)
17952
- : this.clone();
17953
-
17954
- if (result.__filtered__) {
17955
- result.__takeCount__ = nativeMin(n, result.__takeCount__);
17956
- } else {
17957
- result.__views__.push({
17958
- 'size': nativeMin(n, MAX_ARRAY_LENGTH),
17959
- 'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
17960
- });
18797
+ }
18798
+ getInsertFields(insertFieldList = null) {
18799
+ const fieldList = isNonEmptyArray(insertFieldList) ?
18800
+ insertFieldList :
18801
+ this.defaultInsertFieldList;
18802
+ if (!isNonEmptyArray(fieldList)) {
18803
+ throw new Error("Insert fields must be an non-empty array!");
18804
+ }
18805
+ return fieldList;
18806
+ }
18807
+ getNodesInsertSql(nodes, sqlValues, insertFieldList = null, tableAliasOrName = "") {
18808
+ if (!isNonEmptyArray(nodes)) {
18809
+ throw new Error("`sqlValues` parameter should be an non-empty array!");
18810
+ }
18811
+ if (!lodash_1.default.isArray(sqlValues)) {
18812
+ throw new Error("`sqlValues` parameter should be an array!");
18813
+ }
18814
+ if (!isValidSqlIdentifier(tableAliasOrName)) {
18815
+ throw new Error(`tableAliasOrName: ${tableAliasOrName} contains invalid characters!`);
18816
+ }
18817
+ const tbl = this.tableName;
18818
+ const fieldList = this.getInsertFields(insertFieldList);
18819
+ const columnsList = fieldList.
18820
+ map(f => {
18821
+ if (!isValidSqlIdentifier(f)) {
18822
+ throw new Error(`column name: ${f} contains invalid characters!`);
18823
+ }
18824
+ return tableAliasOrName == "" ?
18825
+ `"${f}"` :
18826
+ `${tableAliasOrName}."${f}"`;
18827
+ }).
18828
+ join(", ");
18829
+ const valuesList = nodes.
18830
+ map(node => "(" +
18831
+ fieldList.
18832
+ map(f => {
18833
+ sqlValues.push(node[f]);
18834
+ return `$${sqlValues.length}`;
18835
+ }).
18836
+ join(", ") +
18837
+ ")").
18838
+ join(", ");
18839
+ return `INSERT INTO "${tbl}" (${columnsList}) VALUES ${valuesList}`;
18840
+ }
18841
+ /**
18842
+ * Create the root node of the tree.
18843
+ * If a root node already exists, an error will be thrown.
18844
+ *
18845
+ * @param {NodeRecord} node
18846
+ * @param {pg.Client} [existingClient=null] Optional pg client; Use supplied client connection for query rather than a random connection from Pool
18847
+ * @returns {Promise<string>} newly created node ID
18848
+ * @memberof NestedSetModelQueryer
18849
+ */
18850
+ createRootNode(node, existingClient = null) {
18851
+ return __awaiter(this, void 0, void 0, function* () {
18852
+ const tbl = this.tableName;
18853
+ const client = existingClient ?
18854
+ existingClient :
18855
+ yield this.pool.connect();
18856
+ const fields = Object.keys(node);
18857
+ if (!fields.length) {
18858
+ throw new Error("`node` parameter cannot be an empty object with no key.");
18859
+ }
18860
+ let nodeId;
18861
+ try {
18862
+ yield client.query("BEGIN");
18863
+ let result = yield client.query(`SELECT "id" FROM "${tbl}" WHERE "left" = 1 LIMIT 1`);
18864
+ if (result && isNonEmptyArray(result.rows)) {
18865
+ throw new Error(`A root node with id: ${result.rows[0]["id"]} already exists`);
17961
18866
  }
17962
- return result;
17963
- };
17964
-
17965
- LazyWrapper.prototype[methodName + 'Right'] = function(n) {
17966
- return this.reverse()[methodName](n).reverse();
17967
- };
17968
- });
17969
-
17970
- // Add `LazyWrapper` methods that accept an `iteratee` value.
17971
- arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
17972
- var type = index + 1,
17973
- isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
17974
-
17975
- LazyWrapper.prototype[methodName] = function(iteratee) {
17976
- var result = this.clone();
17977
- result.__iteratees__.push({
17978
- 'iteratee': getIteratee(iteratee, 3),
17979
- 'type': type
17980
- });
17981
- result.__filtered__ = result.__filtered__ || isFilter;
17982
- return result;
17983
- };
17984
- });
17985
-
17986
- // Add `LazyWrapper` methods for `_.head` and `_.last`.
17987
- arrayEach(['head', 'last'], function(methodName, index) {
17988
- var takeName = 'take' + (index ? 'Right' : '');
17989
-
17990
- LazyWrapper.prototype[methodName] = function() {
17991
- return this[takeName](1).value()[0];
17992
- };
18867
+ const countResult = yield client.query(`SELECT COUNT("id") AS "num" FROM "${tbl}" WHERE "left" != 1`);
18868
+ let countNum = countResult && countResult.rows && countResult.rows.length ?
18869
+ parseInt(countResult.rows[0].num) :
18870
+ 0;
18871
+ countNum = isNaN(countNum) ? 0 : countNum;
18872
+ const right = countNum ? (countNum + 1) * 2 : 2;
18873
+ const sqlValues = [];
18874
+ result = yield client.query(this.getNodesInsertSql([
18875
+ Object.assign(Object.assign({}, node), { left: 1, right })],
18876
+ sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
18877
+ if (!result || !isNonEmptyArray(result.rows)) {
18878
+ throw new Error("Cannot locate create root node ID!");
18879
+ }
18880
+ yield client.query("COMMIT");
18881
+ nodeId = result.rows[0]["id"];
18882
+ }
18883
+ catch (e) {
18884
+ yield client.query("ROLLBACK");
18885
+ throw e;
18886
+ } finally
18887
+ {
18888
+ if (!existingClient) {
18889
+ client.release();
18890
+ }
18891
+ }
18892
+ return nodeId;
17993
18893
  });
18894
+ }
18895
+ getNodeDataWithinTx(client, nodeId, fields) {
18896
+ return __awaiter(this, void 0, void 0, function* () {
18897
+ const node = yield this.getNodeById(nodeId, fields, client);
18898
+ return node.caseOf({
18899
+ just: node => node,
18900
+ nothing: () => {
18901
+ throw new NodeNotFoundError(`Cannot locate tree node record with id: ${nodeId}`);
18902
+ } });
17994
18903
 
17995
- // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
17996
- arrayEach(['initial', 'tail'], function(methodName, index) {
17997
- var dropName = 'drop' + (index ? '' : 'Right');
17998
-
17999
- LazyWrapper.prototype[methodName] = function() {
18000
- return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
18001
- };
18002
18904
  });
18003
-
18004
- LazyWrapper.prototype.compact = function() {
18005
- return this.filter(identity);
18006
- };
18007
-
18008
- LazyWrapper.prototype.find = function(predicate) {
18009
- return this.filter(predicate).head();
18010
- };
18011
-
18012
- LazyWrapper.prototype.findLast = function(predicate) {
18013
- return this.reverse().find(predicate);
18014
- };
18015
-
18016
- LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
18017
- if (typeof path == 'function') {
18018
- return new LazyWrapper(this);
18905
+ }
18906
+ /**
18907
+ * Insert a node to the tree under a parent node
18908
+ *
18909
+ * @param {string} parentNodeId
18910
+ * @param {NodeRecord} node
18911
+ * @returns {Promise<string>}
18912
+ * @throws NodeNotFoundError if parent node not found
18913
+ * @memberof NestedSetModelQueryer
18914
+ */
18915
+ insertNode(parentNodeId, node) {
18916
+ return __awaiter(this, void 0, void 0, function* () {
18917
+ if (!parentNodeId) {
18918
+ throw new Error("`parentNodeId` cannot be empty!");
18019
18919
  }
18020
- return this.map(function(value) {
18021
- return baseInvoke(value, path, args);
18022
- });
18920
+ const fields = Object.keys(node);
18921
+ if (!fields.length) {
18922
+ throw new Error("`node` parameter cannot be an empty object with no key.");
18923
+ }
18924
+ const tbl = this.tableName;
18925
+ const client = yield this.pool.connect();
18926
+ let nodeId;
18927
+ try {
18928
+ yield client.query("BEGIN");
18929
+ const { right: parentRight } = yield this.getNodeDataWithinTx(client, parentNodeId, ["right"]);
18930
+ yield client.query(`UPDATE "${tbl}"
18931
+ SET
18932
+ "left" = CASE WHEN "left" > $1 THEN "left" + 2 ELSE "left" END,
18933
+ "right" = CASE WHEN "right" >= $1 THEN "right" + 2 ELSE "right" END
18934
+ WHERE "right" >= $1`, [parentRight]);
18935
+ const sqlValues = [];
18936
+ const result = yield client.query(this.getNodesInsertSql([
18937
+ Object.assign(Object.assign({}, node), { left: parentRight, right: parentRight + 1 })],
18938
+ sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
18939
+ if (!result || !isNonEmptyArray(result.rows)) {
18940
+ throw new Error("Cannot locate created node ID!");
18941
+ }
18942
+ yield client.query("COMMIT");
18943
+ nodeId = result.rows[0]["id"];
18944
+ }
18945
+ catch (e) {
18946
+ yield client.query("ROLLBACK");
18947
+ throw e;
18948
+ } finally
18949
+ {
18950
+ client.release();
18951
+ }
18952
+ return nodeId;
18023
18953
  });
18954
+ }
18955
+ /**
18956
+ * Insert a node to the right of its sibling
18957
+ * If `siblingNodeId` belongs to a root node, an error will be thrown
18958
+ *
18959
+ * @param {string} siblingNodeId
18960
+ * @param {NodeRecord} node
18961
+ * @returns {Promise<string>}
18962
+ * @throws NodeNotFoundError If the node can't be found in the tree
18963
+ * @memberof NestedSetModelQueryer
18964
+ */
18965
+ insertNodeToRightOfSibling(siblingNodeId, node) {
18966
+ return __awaiter(this, void 0, void 0, function* () {
18967
+ if (!siblingNodeId) {
18968
+ throw new Error("`siblingNodeId` cannot be empty!");
18969
+ }
18970
+ const fields = Object.keys(node);
18971
+ if (!fields.length) {
18972
+ throw new Error("`node` parameter cannot be an empty object with no key.");
18973
+ }
18974
+ const tbl = this.tableName;
18975
+ const client = yield this.pool.connect();
18976
+ let nodeId;
18977
+ try {
18978
+ yield client.query("BEGIN");
18979
+ const { left: siblingLeft, right: siblingRight } = yield this.getNodeDataWithinTx(client, siblingNodeId, [
18980
+ "left",
18981
+ "right"]);
18024
18982
 
18025
- LazyWrapper.prototype.reject = function(predicate) {
18026
- return this.filter(negate(getIteratee(predicate)));
18027
- };
18028
-
18029
- LazyWrapper.prototype.slice = function(start, end) {
18030
- start = toInteger(start);
18031
-
18032
- var result = this;
18033
- if (result.__filtered__ && (start > 0 || end < 0)) {
18034
- return new LazyWrapper(result);
18983
+ if (siblingLeft === 1) {
18984
+ throw new Error("Cannot add sibling to the Root node!");
18985
+ }
18986
+ yield client.query(`UPDATE "${tbl}"
18987
+ SET
18988
+ "left" = CASE WHEN "left" < $1 THEN "left" ELSE "left" + 2 END,
18989
+ "right" = CASE WHEN "right" < $1 THEN "right" ELSE "right" + 2 END
18990
+ WHERE "right" > $1`, [siblingRight]);
18991
+ const sqlValues = [];
18992
+ const result = yield client.query(this.getNodesInsertSql([
18993
+ Object.assign(Object.assign({}, node), { left: siblingRight + 1, right: siblingRight + 2 })],
18994
+ sqlValues, fields.concat(["left", "right"])) + " RETURNING id", sqlValues);
18995
+ if (!result || !isNonEmptyArray(result.rows)) {
18996
+ throw new Error("Cannot locate created node ID!");
18997
+ }
18998
+ yield client.query("COMMIT");
18999
+ nodeId = result.rows[0]["id"];
18035
19000
  }
18036
- if (start < 0) {
18037
- result = result.takeRight(-start);
18038
- } else if (start) {
18039
- result = result.drop(start);
19001
+ catch (e) {
19002
+ yield client.query("ROLLBACK");
19003
+ throw e;
19004
+ } finally
19005
+ {
19006
+ client.release();
18040
19007
  }
18041
- if (end !== undefined) {
18042
- end = toInteger(end);
18043
- result = end < 0 ? result.dropRight(-end) : result.take(end - start);
19008
+ return nodeId;
19009
+ });
19010
+ }
19011
+ /**
19012
+ * Move a subtree (the specified root node and all its subordinates)
19013
+ * to under a new parent.
19014
+ *
19015
+ * If the specifed sub tree root node is a child of the new parent node,
19016
+ * an error will be be thrown
19017
+ *
19018
+ * @param {string} subTreeRootNodeId
19019
+ * @param {string} newParentId
19020
+ * @returns {Promise<void>}
19021
+ * @throws NodeNotFoundError If the node can't be found in the tree
19022
+ * @memberof NestedSetModelQueryer
19023
+ */
19024
+ moveSubTreeTo(subTreeRootNodeId, newParentId) {
19025
+ return __awaiter(this, void 0, void 0, function* () {
19026
+ if (!subTreeRootNodeId) {
19027
+ throw new Error("`subTreeRootNodeId` cannot be empty!");
18044
19028
  }
18045
- return result;
18046
- };
18047
-
18048
- LazyWrapper.prototype.takeRightWhile = function(predicate) {
18049
- return this.reverse().takeWhile(predicate).reverse();
18050
- };
18051
-
18052
- LazyWrapper.prototype.toArray = function() {
18053
- return this.take(MAX_ARRAY_LENGTH);
18054
- };
18055
-
18056
- // Add `LazyWrapper` methods to `lodash.prototype`.
18057
- baseForOwn(LazyWrapper.prototype, function(func, methodName) {
18058
- var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
18059
- isTaker = /^(?:head|last)$/.test(methodName),
18060
- lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
18061
- retUnwrapped = isTaker || /^find/.test(methodName);
18062
-
18063
- if (!lodashFunc) {
18064
- return;
19029
+ if (!newParentId) {
19030
+ throw new Error("`newParentId` cannot be empty!");
18065
19031
  }
18066
- lodash.prototype[methodName] = function() {
18067
- var value = this.__wrapped__,
18068
- args = isTaker ? [1] : arguments,
18069
- isLazy = value instanceof LazyWrapper,
18070
- iteratee = args[0],
18071
- useLazy = isLazy || isArray(value);
18072
-
18073
- var interceptor = function(value) {
18074
- var result = lodashFunc.apply(lodash, arrayPush([value], args));
18075
- return (isTaker && chainAll) ? result[0] : result;
18076
- };
18077
-
18078
- if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
18079
- // Avoid lazy use if the iteratee has a "length" value other than `1`.
18080
- isLazy = useLazy = false;
19032
+ const tbl = this.tableName;
19033
+ const client = yield this.pool.connect();
19034
+ try {
19035
+ yield client.query("BEGIN");
19036
+ const comparisonResult = yield this.compareNodes(subTreeRootNodeId, newParentId, client);
19037
+ if (comparisonResult === "ancestor") {
19038
+ throw new Error(`Cannot move a higher level node (id: ${subTreeRootNodeId})to its subordinate (id: ${newParentId})`);
18081
19039
  }
18082
- var chainAll = this.__chain__,
18083
- isHybrid = !!this.__actions__.length,
18084
- isUnwrapped = retUnwrapped && !chainAll,
18085
- onlyLazy = isLazy && !isHybrid;
19040
+ const { left: originRootLeft, right: originRootRight } = yield this.getNodeDataWithinTx(client, subTreeRootNodeId, [
19041
+ "left",
19042
+ "right"]);
18086
19043
 
18087
- if (!retUnwrapped && useLazy) {
18088
- value = onlyLazy ? value : new LazyWrapper(this);
18089
- var result = func.apply(value, args);
18090
- result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
18091
- return new LodashWrapper(result, chainAll);
18092
- }
18093
- if (isUnwrapped && onlyLazy) {
18094
- return func.apply(this, args);
19044
+ if (originRootLeft === "1") {
19045
+ throw new Error("Cannot move Tree root node as substree.");
18095
19046
  }
18096
- result = this.thru(interceptor);
18097
- return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
18098
- };
19047
+ const { right: newParentRight } = yield this.getNodeDataWithinTx(client, newParentId, ["right"]);
19048
+ yield client.query(`
19049
+ UPDATE "${tbl}"
19050
+ SET
19051
+ "left" = "left" + CASE
19052
+ WHEN $3::int4 < $1::int4
19053
+ THEN CASE
19054
+ WHEN "left" BETWEEN $1 AND $2
19055
+ THEN $3 - $1
19056
+ WHEN "left" BETWEEN $3 AND ($1 - 1)
19057
+ THEN $2 - $1 + 1
19058
+ ELSE 0 END
19059
+ WHEN $3::int4 > $2::int4
19060
+ THEN CASE
19061
+ WHEN "left" BETWEEN $1 AND $2
19062
+ THEN $3 - $2 - 1
19063
+ WHEN "left" BETWEEN ($2 + 1) AND ($3 - 1)
19064
+ THEN $1 - $2 - 1
19065
+ ELSE 0 END
19066
+ ELSE 0 END,
19067
+ "right" = "right" + CASE
19068
+ WHEN $3::int4 < $1::int4
19069
+ THEN CASE
19070
+ WHEN "right" BETWEEN $1 AND $2
19071
+ THEN $3 - $1
19072
+ WHEN "right" BETWEEN $3 AND ($1 - 1)
19073
+ THEN $2 - $1 + 1
19074
+ ELSE 0 END
19075
+ WHEN $3::int4 > $2::int4
19076
+ THEN CASE
19077
+ WHEN "right" BETWEEN $1 AND $2
19078
+ THEN $3 - $2 - 1
19079
+ WHEN "right" BETWEEN ($2 + 1) AND ($3 - 1)
19080
+ THEN $1 - $2 - 1
19081
+ ELSE 0 END
19082
+ ELSE 0 END
19083
+ `, [originRootLeft, originRootRight, newParentRight]);
19084
+ yield client.query("COMMIT");
19085
+ }
19086
+ catch (e) {
19087
+ yield client.query("ROLLBACK");
19088
+ throw e;
19089
+ } finally
19090
+ {
19091
+ client.release();
19092
+ }
18099
19093
  });
19094
+ }
19095
+ /**
19096
+ * Delete a subtree (and all its dependents)
19097
+ * If you sent in a root node id (and `allowRootNodeId` is true), the whole tree will be removed
19098
+ * When `allowRootNodeId` is false and you passed a root node id, an error will be thrown
19099
+ *
19100
+ * @param {string} subTreeRootNodeId
19101
+ * @param {boolean} [allowRootNodeId=false]
19102
+ * @returns {Promise<void>}
19103
+ * @throws NodeNotFoundError If the node can't be found in the tree
19104
+ * @memberof NestedSetModelQueryer
19105
+ */
19106
+ deleteSubTree(subTreeRootNodeId, allowRootNodeId = false) {
19107
+ return __awaiter(this, void 0, void 0, function* () {
19108
+ if (!subTreeRootNodeId) {
19109
+ throw new Error("`subTreeRootNodeId` cannot be empty!");
19110
+ }
19111
+ const tbl = this.tableName;
19112
+ const client = yield this.pool.connect();
19113
+ try {
19114
+ yield client.query("BEGIN");
19115
+ const { left: subTreeRootLeft, right: subTreeRootRight } = yield this.getNodeDataWithinTx(client, subTreeRootNodeId, [
19116
+ "left",
19117
+ "right"]);
18100
19118
 
18101
- // Add `Array` methods to `lodash.prototype`.
18102
- arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
18103
- var func = arrayProto[methodName],
18104
- chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
18105
- retUnwrapped = /^(?:pop|shift)$/.test(methodName);
19119
+ if (subTreeRootLeft === 1 && !allowRootNodeId) {
19120
+ throw new Error("Root node id is not allowed!");
19121
+ }
19122
+ // --- delete the sub tree nodes
19123
+ yield client.query(`DELETE FROM "${tbl}" WHERE "left" BETWEEN $1 AND $2`, [subTreeRootLeft, subTreeRootRight]);
19124
+ // --- closing the gap after deletion
19125
+ yield client.query(`
19126
+ UPDATE "${tbl}"
19127
+ SET "left" = CASE
19128
+ WHEN "left" > $1
19129
+ THEN "left" - ($2 - $1 + 1)
19130
+ ELSE "left" END,
19131
+ "right" = CASE
19132
+ WHEN "right" > $1
19133
+ THEN "right" - ($2 - $1 + 1)
19134
+ ELSE "right" END
19135
+ WHERE "left" > $1 OR "right" > $1
19136
+ `, [subTreeRootLeft, subTreeRootRight]);
19137
+ yield client.query("COMMIT");
19138
+ }
19139
+ catch (e) {
19140
+ yield client.query("ROLLBACK");
19141
+ throw e;
19142
+ } finally
19143
+ {
19144
+ client.release();
19145
+ }
19146
+ });
19147
+ }
19148
+ /**
19149
+ * Delete a single node from the tree
19150
+ * Its childrens will become its parent's children
19151
+ * Deleting a root node is not allowed.
19152
+ * You can, however, delete the whole sub tree from root node or update the root node, instead.
19153
+ *
19154
+ * @param {string} nodeId
19155
+ * @returns {Promise<void>}
19156
+ * @throws NodeNotFoundError If the node can't be found in the tree
19157
+ * @memberof NestedSetModelQueryer
19158
+ */
19159
+ deleteNode(nodeId) {
19160
+ return __awaiter(this, void 0, void 0, function* () {
19161
+ if (!nodeId) {
19162
+ throw new Error("`nodeId` cannot be empty!");
19163
+ }
19164
+ const tbl = this.tableName;
19165
+ const client = yield this.pool.connect();
19166
+ try {
19167
+ yield client.query("BEGIN");
19168
+ const { left: subTreeRootLeft, right: subTreeRootRight } = yield this.getNodeDataWithinTx(client, nodeId, [
19169
+ "left",
19170
+ "right"]);
18106
19171
 
18107
- lodash.prototype[methodName] = function() {
18108
- var args = arguments;
18109
- if (retUnwrapped && !this.__chain__) {
18110
- var value = this.value();
18111
- return func.apply(isArray(value) ? value : [], args);
19172
+ if (subTreeRootLeft === 1) {
19173
+ throw new Error("Delete a root node is not allowed!");
18112
19174
  }
18113
- return this[chainName](function(value) {
18114
- return func.apply(isArray(value) ? value : [], args);
18115
- });
18116
- };
19175
+ // --- delete the node
19176
+ // --- In nested set model, children are still bind to the deleted node's parent after deletion
19177
+ yield client.query(`DELETE FROM "${tbl}" WHERE "id" = $1`, [
19178
+ nodeId]);
19179
+
19180
+ // --- closing the gap after deletion
19181
+ yield client.query(`
19182
+ UPDATE "${tbl}"
19183
+ SET "left" = CASE
19184
+ WHEN "left" > $1 AND "right" < $2
19185
+ THEN "left" - 1
19186
+ WHEN "left" > $1 AND "right" > $2
19187
+ THEN "left" - 2
19188
+ ELSE "left" END,
19189
+ "right" = CASE
19190
+ WHEN "left" > $1 AND "right" < $2
19191
+ THEN "right" - 1
19192
+ ELSE ("right" - 2) END
19193
+ WHERE "left" > $1 OR "right" > $1
19194
+ `, [subTreeRootLeft, subTreeRootRight]);
19195
+ yield client.query("COMMIT");
19196
+ }
19197
+ catch (e) {
19198
+ yield client.query("ROLLBACK");
19199
+ throw e;
19200
+ } finally
19201
+ {
19202
+ client.release();
19203
+ }
19204
+ });
19205
+ }
19206
+ /**
19207
+ * Update node data of the node specified by the nodeId
19208
+ * The followings fields will be ignored (as they should be generated by program):
19209
+ * - `left`
19210
+ * - `right`
19211
+ * - `id`
19212
+ *
19213
+ * @param {string} nodeId
19214
+ * @param {NodeRecord} nodeData
19215
+ * @param {pg.Client} [client=null]
19216
+ * @returns {Promise<void>}
19217
+ * @memberof NestedSetModelQueryer
19218
+ */
19219
+ updateNode(nodeId, nodeData, client = null) {
19220
+ return __awaiter(this, void 0, void 0, function* () {
19221
+ if (nodeId.trim() === "") {
19222
+ throw new Error("nodeId can't be empty!");
19223
+ }
19224
+ const sqlValues = [nodeId];
19225
+ const updateFields = Object.keys(nodeData).filter(k => k !== "left" && k !== "right" && k !== "id");
19226
+ if (!updateFields.length) {
19227
+ throw new Error("No valid node data passed for updating.");
19228
+ }
19229
+ const setFieldList = updateFields.
19230
+ map(f => {
19231
+ if (!isValidSqlIdentifier(f)) {
19232
+ throw new Error(`field name: ${f} contains invalid characters!`);
19233
+ }
19234
+ sqlValues.push(nodeData[f]);
19235
+ return `"${f}" = $${sqlValues.length}`;
19236
+ }).
19237
+ join(", ");
19238
+ yield (client ? client : this.pool).query(`UPDATE "${this.tableName}" SET ${setFieldList} WHERE "id" = $1`, sqlValues);
18117
19239
  });
19240
+ }
19241
+ getChildTextTreeNodes(parentId) {
19242
+ return __awaiter(this, void 0, void 0, function* () {
19243
+ const nodes = yield this.getImmediateChildren(parentId);
19244
+ if (!nodes || !nodes.length)
19245
+ return [];
19246
+ const textNodeList = [];
19247
+ for (let i = 0; i < nodes.length; i++) {
19248
+ const nodeChildren = yield this.getChildTextTreeNodes(nodes[i].id);
19249
+ if (nodeChildren.length) {
19250
+ textNodeList.push({
19251
+ text: nodes[i].name,
19252
+ children: nodeChildren });
18118
19253
 
18119
- // Map minified method names to their real names.
18120
- baseForOwn(LazyWrapper.prototype, function(func, methodName) {
18121
- var lodashFunc = lodash[methodName];
18122
- if (lodashFunc) {
18123
- var key = lodashFunc.name + '';
18124
- if (!hasOwnProperty.call(realNames, key)) {
18125
- realNames[key] = [];
19254
+ } else
19255
+ {
19256
+ textNodeList.push(nodes[i].name);
18126
19257
  }
18127
- realNames[key].push({ 'name': methodName, 'func': lodashFunc });
18128
19258
  }
19259
+ return textNodeList;
18129
19260
  });
19261
+ }
19262
+ /**
19263
+ * Generate the Text View of the tree
19264
+ * Provided as Dev tool only
19265
+ *
19266
+ * E.g. output could be:
19267
+ * └─ Albert
19268
+ * ├─ Chuck
19269
+ * │ ├─ Fred
19270
+ * │ ├─ Eddie
19271
+ * │ └─ Donna
19272
+ * └─ Bert
19273
+ *
19274
+ * @returns {Promise<string>}
19275
+ * @memberof NestedSetModelQueryer
19276
+ */
19277
+ getTreeTextView() {
19278
+ return __awaiter(this, void 0, void 0, function* () {
19279
+ const rootNodeMaybe = yield this.getRootNode();
19280
+ return rootNodeMaybe.caseOf({
19281
+ just: rootNode => __awaiter(this, void 0, void 0, function* () {
19282
+ const tree = [];
19283
+ const children = yield this.getChildTextTreeNodes(rootNode.id);
19284
+ if (children.length) {
19285
+ tree.push({
19286
+ text: rootNode.name,
19287
+ children });
18130
19288
 
18131
- realNames[createHybrid(undefined, WRAP_BIND_KEY_FLAG).name] = [{
18132
- 'name': 'wrapper',
18133
- 'func': undefined
18134
- }];
18135
-
18136
- // Add methods to `LazyWrapper`.
18137
- LazyWrapper.prototype.clone = lazyClone;
18138
- LazyWrapper.prototype.reverse = lazyReverse;
18139
- LazyWrapper.prototype.value = lazyValue;
18140
-
18141
- // Add chain sequence methods to the `lodash` wrapper.
18142
- lodash.prototype.at = wrapperAt;
18143
- lodash.prototype.chain = wrapperChain;
18144
- lodash.prototype.commit = wrapperCommit;
18145
- lodash.prototype.next = wrapperNext;
18146
- lodash.prototype.plant = wrapperPlant;
18147
- lodash.prototype.reverse = wrapperReverse;
18148
- lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
18149
-
18150
- // Add lazy aliases.
18151
- lodash.prototype.first = lodash.prototype.head;
18152
-
18153
- if (symIterator) {
18154
- lodash.prototype[symIterator] = wrapperToIterator;
18155
- }
18156
- return lodash;
18157
- });
18158
-
18159
- /*--------------------------------------------------------------------------*/
18160
-
18161
- // Export lodash.
18162
- var _ = runInContext();
18163
-
18164
- // Some AMD build optimizers, like r.js, check for condition patterns like:
18165
- if (true) {
18166
- // Expose Lodash on the global object to prevent errors when Lodash is
18167
- // loaded by a script tag in the presence of an AMD loader.
18168
- // See http://requirejs.org/docs/errors.html#mismatch for more details.
18169
- // Use `_.noConflict` to remove Lodash from the global object.
18170
- root._ = _;
19289
+ } else
19290
+ {
19291
+ tree.push(rootNode.name);
19292
+ }
19293
+ return textTree(tree);
19294
+ }),
19295
+ nothing: () => __awaiter(this, void 0, void 0, function* () {return "Empty Tree";}) });
18171
19296
 
18172
- // Define as an anonymous module so, through path mapping, it can be
18173
- // referenced as the "underscore" module.
18174
- !(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
18175
- return _;
18176
- }).call(exports, __webpack_require__, exports, module),
18177
- __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
18178
- }
18179
- // Check for `exports` after `define` in case a build optimizer adds it.
18180
- else {}
18181
- }.call(this));
19297
+ });
19298
+ }}
18182
19299
 
18183
- /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(11)(module)))
19300
+ exports.default = NestedSetModelQueryer;
18184
19301
 
18185
19302
  /***/ }),
18186
- /* 11 */
19303
+ /* 14 */
18187
19304
  /***/ (function(module, exports) {
18188
19305
 
18189
19306
  module.exports = function(module) {
@@ -18211,14 +19328,259 @@ module.exports = function(module) {
18211
19328
 
18212
19329
 
18213
19330
  /***/ }),
18214
- /* 12 */
19331
+ /* 15 */
18215
19332
  /***/ (function(module, exports, __webpack_require__) {
18216
19333
 
18217
19334
  !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}])});
18218
19335
  //# sourceMappingURL=tsmonad.js.map
18219
19336
 
18220
19337
  /***/ }),
18221
- /* 13 */
19338
+ /* 16 */
19339
+ /***/ (function(module, exports, __webpack_require__) {
19340
+
19341
+ "use strict";
19342
+
19343
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19344
+ if (k2 === undefined) k2 = k;
19345
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
19346
+ }) : (function(o, m, k, k2) {
19347
+ if (k2 === undefined) k2 = k;
19348
+ o[k2] = m[k];
19349
+ }));
19350
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19351
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19352
+ }) : function(o, v) {
19353
+ o["default"] = v;
19354
+ });
19355
+ var __importStar = (this && this.__importStar) || function (mod) {
19356
+ if (mod && mod.__esModule) return mod;
19357
+ var result = {};
19358
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
19359
+ __setModuleDefault(result, mod);
19360
+ return result;
19361
+ };
19362
+ Object.defineProperty(exports, "__esModule", { value: true });
19363
+ exports.AspectQueryGroup = exports.AspectQueryValueInArray = exports.AspectQueryArrayNotEmpty = exports.AspectQueryWithValue = exports.AspectQueryExists = exports.AspectQueryFalse = exports.AspectQueryTrue = exports.AspectQuery = exports.AspectQueryValue = void 0;
19364
+ const sql_syntax_1 = __importStar(__webpack_require__(6));
19365
+ const SQLUtils_1 = __webpack_require__(10);
19366
+ class AspectQueryValue {
19367
+ constructor(value) {
19368
+ this.value = value;
19369
+ switch (typeof value) {
19370
+ case "number":
19371
+ this.postgresType = sql_syntax_1.sqls `NUMERIC`;
19372
+ case "boolean":
19373
+ this.postgresType = sql_syntax_1.sqls `BOOL`;
19374
+ case "string":
19375
+ this.postgresType = sql_syntax_1.sqls `TEXT`;
19376
+ default:
19377
+ throw new Error("getPostgresValueTypeCastStr: unsupported data type: `${" +
19378
+ typeof value +
19379
+ "}`");
19380
+ }
19381
+ }
19382
+ }
19383
+ exports.AspectQueryValue = AspectQueryValue;
19384
+ class AspectQuery {
19385
+ constructor(aspectId, path = [], negated = false) {
19386
+ this.aspectId = aspectId;
19387
+ this.path = path;
19388
+ this.negated = negated;
19389
+ }
19390
+ /**
19391
+ * Public interface of all types of AspectQuery. Call this method to covert AspectQuery to SQL statement.
19392
+ * Sub-class might choose to override this method to alter generic logic.
19393
+ *
19394
+ * @param {AspectQueryToSqlConfig} config
19395
+ * @return {SQLSyntax}
19396
+ * @memberof AspectQuery
19397
+ */
19398
+ toSql(config) {
19399
+ if (!this.aspectId) {
19400
+ throw new Error("Invalid AspectQuery: aspectId cannot be empty.");
19401
+ }
19402
+ const sqlQuery = this.sqlQueries(config);
19403
+ if (this.negated) {
19404
+ return sql_syntax_1.sqls `NOT ${sqlQuery}`;
19405
+ }
19406
+ else {
19407
+ return sqlQuery;
19408
+ }
19409
+ }
19410
+ }
19411
+ exports.AspectQuery = AspectQuery;
19412
+ class AspectQueryTrue extends AspectQuery {
19413
+ constructor() {
19414
+ super(undefined);
19415
+ }
19416
+ sqlQueries(config) {
19417
+ return sql_syntax_1.sqls `TRUE`;
19418
+ }
19419
+ // override default toSql as we don't need any validation logic
19420
+ toSql(config) {
19421
+ return this.sqlQueries(config);
19422
+ }
19423
+ }
19424
+ exports.AspectQueryTrue = AspectQueryTrue;
19425
+ class AspectQueryFalse extends AspectQuery {
19426
+ constructor() {
19427
+ super(undefined);
19428
+ }
19429
+ sqlQueries(config) {
19430
+ return sql_syntax_1.sqls `FALSE`;
19431
+ }
19432
+ // override default toSql as we don't need any validation logic
19433
+ toSql(config) {
19434
+ return this.sqlQueries(config);
19435
+ }
19436
+ }
19437
+ exports.AspectQueryFalse = AspectQueryFalse;
19438
+ class AspectQueryExists extends AspectQuery {
19439
+ sqlQueries(config) {
19440
+ var _a;
19441
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19442
+ if ((_a = this.path) === null || _a === void 0 ? void 0 : _a.length) {
19443
+ return sql_syntax_1.sqls `(${fieldRef} #> string_to_array(${this.path.join(",")}, ',')) IS NOT NULL`;
19444
+ }
19445
+ else {
19446
+ return fieldRef.isNotNull();
19447
+ }
19448
+ }
19449
+ }
19450
+ exports.AspectQueryExists = AspectQueryExists;
19451
+ class AspectQueryWithValue extends AspectQuery {
19452
+ constructor(value, operator, placeReferenceFirst, aspectId, path = [], negated = false) {
19453
+ super(aspectId, path, negated);
19454
+ this.value = value;
19455
+ this.operator = operator;
19456
+ this.placeReferenceFirst = placeReferenceFirst;
19457
+ }
19458
+ sqlQueries(config) {
19459
+ var _a;
19460
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19461
+ const tableDataRef = !((_a = this.path) === null || _a === void 0 ? void 0 : _a.length)
19462
+ ? sql_syntax_1.sqls `${fieldRef}::${this.value.postgresType}`
19463
+ : sql_syntax_1.sqls `(${fieldRef} #>> string_to_array(${this.path.join(",")}, ','))::${this.value.postgresType}`;
19464
+ return this.placeReferenceFirst
19465
+ ? sql_syntax_1.sqls `COALESCE(${tableDataRef} ${this.operator} ${this.value.value}::${this.value.postgresType}, false)`
19466
+ : sql_syntax_1.sqls `COALESCE(${this.value.value}::${this.value.postgresType} ${this.operator} ${tableDataRef}, false)`;
19467
+ }
19468
+ }
19469
+ exports.AspectQueryWithValue = AspectQueryWithValue;
19470
+ class AspectQueryArrayNotEmpty extends AspectQuery {
19471
+ sqlQueries(config) {
19472
+ var _a;
19473
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19474
+ const tableDataRef = !((_a = this.path) === null || _a === void 0 ? void 0 : _a.length)
19475
+ ? sql_syntax_1.sqls `(${fieldRef} #> string_to_array('0',','))`
19476
+ : sql_syntax_1.sqls `(${fieldRef} #>> string_to_array(${[...this.path, "0"].join(",")}, ','))`;
19477
+ return tableDataRef.isNotNull();
19478
+ }
19479
+ }
19480
+ exports.AspectQueryArrayNotEmpty = AspectQueryArrayNotEmpty;
19481
+ class AspectQueryValueInArray extends AspectQuery {
19482
+ constructor(value, aspectId, path = [], negated = false) {
19483
+ super(aspectId, path, negated);
19484
+ this.value = value;
19485
+ }
19486
+ sqlQueries(config) {
19487
+ var _a;
19488
+ const fieldRef = SQLUtils_1.getTableColumnName(this.aspectId, config.tableRef, config.useLowerCaseColumnName);
19489
+ const tableDataRef = !((_a = this.path) === null || _a === void 0 ? void 0 : _a.length)
19490
+ ? sql_syntax_1.sqls `COALESCE(
19491
+ (
19492
+ (${fieldRef}::JSONB #> string_to_array('0',','))::JSONB
19493
+ ) @> to_json(${this.value.value})::JSONB,
19494
+ FALSE
19495
+ )`
19496
+ : sql_syntax_1.sqls `COALESCE(
19497
+ (
19498
+ (${fieldRef}::JSONB #> string_to_array(${this.path.join(",")}, ','))::JSONB
19499
+ ) @> to_json(${this.value.value})::JSONB,
19500
+ FALSE
19501
+ )`;
19502
+ return tableDataRef;
19503
+ }
19504
+ }
19505
+ exports.AspectQueryValueInArray = AspectQueryValueInArray;
19506
+ class AspectQueryGroup {
19507
+ constructor(queries, joinWithAnd = true, negated = false) {
19508
+ this.queries = queries;
19509
+ this.joinWithAnd = joinWithAnd;
19510
+ this.negated = negated;
19511
+ }
19512
+ toSql(config) {
19513
+ var _a;
19514
+ if (!((_a = this.queries) === null || _a === void 0 ? void 0 : _a.length)) {
19515
+ return sql_syntax_1.default.empty;
19516
+ }
19517
+ let result;
19518
+ if (this.joinWithAnd) {
19519
+ if (this.queries.findIndex((item) => item instanceof AspectQueryFalse) !== -1) {
19520
+ result = sql_syntax_1.sqls `FALSE`;
19521
+ }
19522
+ else {
19523
+ result = sql_syntax_1.default.joinWithAnd(this.queries.map((q) => {
19524
+ if (q instanceof AspectQueryTrue) {
19525
+ return sql_syntax_1.default.empty;
19526
+ }
19527
+ else {
19528
+ return q.toSql(config);
19529
+ }
19530
+ }));
19531
+ }
19532
+ }
19533
+ else {
19534
+ if (this.queries.findIndex((item) => item instanceof AspectQueryTrue) !== -1) {
19535
+ result = sql_syntax_1.sqls `TRUE`;
19536
+ }
19537
+ else {
19538
+ result = sql_syntax_1.default.joinWithOr(this.queries.map((q) => {
19539
+ if (q instanceof AspectQueryFalse) {
19540
+ return sql_syntax_1.default.empty;
19541
+ }
19542
+ else {
19543
+ return q.toSql(config);
19544
+ }
19545
+ }));
19546
+ }
19547
+ }
19548
+ if (this.negated) {
19549
+ return sql_syntax_1.sqls `NOT ${result}`;
19550
+ }
19551
+ else {
19552
+ return result;
19553
+ }
19554
+ }
19555
+ }
19556
+ exports.AspectQueryGroup = AspectQueryGroup;
19557
+ //# sourceMappingURL=AspectQuery.js.map
19558
+
19559
+ /***/ }),
19560
+ /* 17 */
19561
+ /***/ (function(module, exports, __webpack_require__) {
19562
+
19563
+ "use strict";
19564
+
19565
+ Object.defineProperty(exports, "__esModule", { value: true });
19566
+ class ServerError extends Error {
19567
+ constructor(message = "Unknown Error", statusCode = 500) {
19568
+ super(message);
19569
+ this.statusCode = statusCode;
19570
+ }
19571
+ toData() {
19572
+ return {
19573
+ isError: true,
19574
+ errorCode: this.statusCode,
19575
+ errorMessage: this.message
19576
+ };
19577
+ }
19578
+ }
19579
+ exports.default = ServerError;
19580
+ //# sourceMappingURL=ServerError.js.map
19581
+
19582
+ /***/ }),
19583
+ /* 18 */
18222
19584
  /***/ (function(module, exports) {
18223
19585
 
18224
19586
  module.exports = require("text-treeview");