@magda/org-tree 1.3.0-rc.0 → 2.0.0-alpha.2

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