@budibase/server 1.2.42 → 1.2.44-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__mocks__/mongodb.ts +4 -0
- package/__mocks__/node-fetch.ts +8 -1
- package/builder/assets/index.6b1c808b.css +6 -0
- package/builder/assets/index.98345036.js +874 -0
- package/builder/assets/{vendor.6bcafa60.js → vendor.67dbd8fa.js} +1 -1
- package/builder/index.html +3 -3
- package/coverage/clover.xml +495 -472
- package/coverage/coverage-final.json +15 -15
- package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
- package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +1 -1
- package/coverage/lcov-report/api/controllers/datasource.js.html +1 -1
- package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
- package/coverage/lcov-report/api/controllers/index.html +1 -1
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
- package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
- package/coverage/lcov-report/api/controllers/migrations.js.html +1 -1
- package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
- package/coverage/lcov-report/api/controllers/query/index.html +1 -1
- package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/role.js.html +1 -1
- package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/index.html +25 -25
- package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +317 -191
- package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/utils.js.html +27 -18
- package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
- package/coverage/lcov-report/api/controllers/script.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/external.js.html +2 -2
- package/coverage/lcov-report/api/controllers/table/index.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
- package/coverage/lcov-report/api/controllers/user.js.html +19 -19
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
- package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +5 -5
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +1 -1
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/migrations.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +1 -1
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/utils/index.html +1 -1
- package/coverage/lcov-report/api/routes/utils/validators.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +1 -1
- package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
- package/coverage/lcov-report/automations/bullboard.js.html +1 -1
- package/coverage/lcov-report/automations/index.html +1 -1
- package/coverage/lcov-report/automations/index.js.html +1 -1
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +1 -1
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/loop.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
- package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/triggers.js.html +1 -1
- package/coverage/lcov-report/constants/index.html +1 -1
- package/coverage/lcov-report/constants/index.js.html +30 -30
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
- package/coverage/lcov-report/db/index.html +1 -1
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
- package/coverage/lcov-report/db/newid.js.html +4 -4
- package/coverage/lcov-report/db/utils.js.html +69 -69
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +1 -1
- package/coverage/lcov-report/index.html +19 -19
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
- package/coverage/lcov-report/middleware/builder.js.html +11 -11
- package/coverage/lcov-report/middleware/currentapp.js.html +33 -33
- package/coverage/lcov-report/middleware/index.html +1 -1
- package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
- package/coverage/lcov-report/middleware/publicApi.js.html +1 -1
- package/coverage/lcov-report/middleware/resourceId.js.html +12 -12
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/utils.js.html +2 -2
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
- package/coverage/lcov-report/utilities/global.js.html +31 -31
- package/coverage/lcov-report/utilities/index.html +1 -1
- package/coverage/lcov-report/utilities/index.js.html +23 -23
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +2 -2
- package/coverage/lcov-report/utilities/queue/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
- package/coverage/lcov-report/utilities/security.js.html +1 -1
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/rows.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +7 -7
- package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
- package/coverage/lcov.info +803 -763
- package/dist/api/controllers/row/internalSearch.js +39 -4
- package/dist/api/controllers/row/utils.js +3 -1
- package/dist/api/controllers/static/index.js +26 -2
- package/dist/api/controllers/static/templates/preview.hbs +103 -0
- package/dist/api/controllers/table/external.js +1 -1
- package/dist/api/routes/static.js +3 -0
- package/dist/automations/logging/index.js +3 -0
- package/dist/integrations/base/query.js +27 -0
- package/dist/integrations/base/sql.js +62 -30
- package/dist/integrations/base/utils.js +13 -27
- package/dist/integrations/microsoftSqlServer.js +5 -2
- package/dist/integrations/mongodb.js +35 -8
- package/dist/integrations/mysql.js +2 -2
- package/dist/integrations/postgres.js +5 -2
- package/dist/package.json +8 -8
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utilities/fileSystem/index.js +2 -1
- package/package.json +9 -9
- package/src/api/controllers/row/internalSearch.js +45 -3
- package/src/api/controllers/row/utils.js +4 -1
- package/src/api/controllers/static/index.ts +21 -1
- package/src/api/controllers/static/templates/preview.hbs +103 -0
- package/src/api/controllers/table/external.js +1 -1
- package/src/api/routes/static.ts +12 -0
- package/src/api/routes/tests/internalSearch.spec.js +176 -0
- package/src/api/routes/tests/static.spec.js +19 -7
- package/src/automations/logging/index.ts +3 -0
- package/src/integrations/base/query.ts +16 -0
- package/src/integrations/base/sql.ts +67 -30
- package/src/integrations/base/utils.ts +9 -18
- package/src/integrations/microsoftSqlServer.ts +3 -2
- package/src/integrations/mongodb.ts +35 -8
- package/src/integrations/mysql.ts +1 -1
- package/src/integrations/postgres.ts +2 -1
- package/src/integrations/tests/mongo.spec.js +218 -0
- package/src/integrations/tests/sql.spec.js +101 -11
- package/src/utilities/fileSystem/index.js +2 -0
- package/builder/assets/index.08dd0c5e.css +0 -6
- package/builder/assets/index.b0f613b5.js +0 -975
|
@@ -10,6 +10,7 @@ import { QueryOptions } from "../../definitions/datasource"
|
|
|
10
10
|
import { isIsoDateString, SqlClient } from "../utils"
|
|
11
11
|
import SqlTableQueryBuilder from "./sqlTable"
|
|
12
12
|
import environment from "../../environment"
|
|
13
|
+
import { removeKeyNumbering } from "./utils"
|
|
13
14
|
|
|
14
15
|
const envLimit = environment.SQL_MAX_ROWS
|
|
15
16
|
? parseInt(environment.SQL_MAX_ROWS)
|
|
@@ -133,12 +134,13 @@ class InternalBuilder {
|
|
|
133
134
|
fn: (key: string, value: any) => void
|
|
134
135
|
) {
|
|
135
136
|
for (let [key, value] of Object.entries(structure)) {
|
|
136
|
-
const
|
|
137
|
+
const updatedKey = removeKeyNumbering(key)
|
|
138
|
+
const isRelationshipField = updatedKey.includes(".")
|
|
137
139
|
if (!opts.relationship && !isRelationshipField) {
|
|
138
|
-
fn(`${opts.tableName}.${
|
|
140
|
+
fn(`${opts.tableName}.${updatedKey}`, value)
|
|
139
141
|
}
|
|
140
142
|
if (opts.relationship && isRelationshipField) {
|
|
141
|
-
fn(
|
|
143
|
+
fn(updatedKey, value)
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
}
|
|
@@ -157,6 +159,61 @@ class InternalBuilder {
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
const contains = (mode: object, any: boolean = false) => {
|
|
163
|
+
const fnc = allOr ? "orWhere" : "where"
|
|
164
|
+
const rawFnc = `${fnc}Raw`
|
|
165
|
+
const not = mode === filters?.notContains ? "NOT " : ""
|
|
166
|
+
function stringifyArray(value: Array<any>, quoteStyle = '"'): string {
|
|
167
|
+
for (let i in value) {
|
|
168
|
+
if (typeof value[i] === "string") {
|
|
169
|
+
value[i] = `${quoteStyle}${value[i]}${quoteStyle}`
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return `[${value.join(",")}]`
|
|
173
|
+
}
|
|
174
|
+
if (this.client === SqlClient.POSTGRES) {
|
|
175
|
+
iterate(mode, (key: string, value: Array<any>) => {
|
|
176
|
+
const wrap = any ? "" : "'"
|
|
177
|
+
const containsOp = any ? "\\?| array" : "@>"
|
|
178
|
+
const fieldNames = key.split(/\./g)
|
|
179
|
+
const tableName = fieldNames[0]
|
|
180
|
+
const columnName = fieldNames[1]
|
|
181
|
+
// @ts-ignore
|
|
182
|
+
query = query[rawFnc](
|
|
183
|
+
`${not}"${tableName}"."${columnName}"::jsonb ${containsOp} ${wrap}${stringifyArray(
|
|
184
|
+
value,
|
|
185
|
+
any ? "'" : '"'
|
|
186
|
+
)}${wrap}`
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
} else if (this.client === SqlClient.MY_SQL) {
|
|
190
|
+
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS"
|
|
191
|
+
iterate(mode, (key: string, value: Array<any>) => {
|
|
192
|
+
// @ts-ignore
|
|
193
|
+
query = query[rawFnc](
|
|
194
|
+
`${not}${jsonFnc}(${key}, '${stringifyArray(value)}')`
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
} else {
|
|
198
|
+
const andOr = mode === filters?.containsAny ? " OR " : " AND "
|
|
199
|
+
iterate(mode, (key: string, value: Array<any>) => {
|
|
200
|
+
let statement = ""
|
|
201
|
+
for (let i in value) {
|
|
202
|
+
if (typeof value[i] === "string") {
|
|
203
|
+
value[i] = `%"${value[i]}"%`
|
|
204
|
+
} else {
|
|
205
|
+
value[i] = `%${value[i]}%`
|
|
206
|
+
}
|
|
207
|
+
statement +=
|
|
208
|
+
(statement ? andOr : "") +
|
|
209
|
+
`LOWER(${likeKey(this.client, key)}) LIKE ?`
|
|
210
|
+
}
|
|
211
|
+
// @ts-ignore
|
|
212
|
+
query = query[rawFnc](`${not}(${statement})`, value)
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
160
217
|
if (!filters) {
|
|
161
218
|
return query
|
|
162
219
|
}
|
|
@@ -227,32 +284,13 @@ class InternalBuilder {
|
|
|
227
284
|
})
|
|
228
285
|
}
|
|
229
286
|
if (filters.contains) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (typeof value === "string") {
|
|
238
|
-
value = `"${value}"`
|
|
239
|
-
}
|
|
240
|
-
// @ts-ignore
|
|
241
|
-
query = query[rawFnc](
|
|
242
|
-
`"${tableName}"."${columnName}"::jsonb @> '[${value}]'`
|
|
243
|
-
)
|
|
244
|
-
})
|
|
245
|
-
} else if (this.client === SqlClient.MY_SQL) {
|
|
246
|
-
iterate(filters.contains, (key: string, value: any) => {
|
|
247
|
-
if (typeof value === "string") {
|
|
248
|
-
value = `"${value}"`
|
|
249
|
-
}
|
|
250
|
-
// @ts-ignore
|
|
251
|
-
query = query[rawFnc](`JSON_CONTAINS(${key}, '${value}')`)
|
|
252
|
-
})
|
|
253
|
-
} else {
|
|
254
|
-
iterate(filters.contains, like)
|
|
255
|
-
}
|
|
287
|
+
contains(filters.contains)
|
|
288
|
+
}
|
|
289
|
+
if (filters.notContains) {
|
|
290
|
+
contains(filters.notContains)
|
|
291
|
+
}
|
|
292
|
+
if (filters.containsAny) {
|
|
293
|
+
contains(filters.containsAny, true)
|
|
256
294
|
}
|
|
257
295
|
return query
|
|
258
296
|
}
|
|
@@ -582,4 +620,3 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
582
620
|
}
|
|
583
621
|
|
|
584
622
|
export default SqlQueryBuilder
|
|
585
|
-
module.exports = SqlQueryBuilder
|
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
const QUERY_START_REGEX = /\d[0-9]*:/g
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// query is the opinionated function
|
|
12
|
-
if (Integration.prototype.query) {
|
|
13
|
-
const integration = new Integration(datasource.config)
|
|
14
|
-
return integration.query(json)
|
|
15
|
-
} else {
|
|
16
|
-
throw "Datasource does not support query."
|
|
17
|
-
}
|
|
3
|
+
export function removeKeyNumbering(key: any): string {
|
|
4
|
+
if (typeof key === "string" && key.match(QUERY_START_REGEX) != null) {
|
|
5
|
+
const parts = key.split(":")
|
|
6
|
+
// remove the number
|
|
7
|
+
parts.shift()
|
|
8
|
+
return parts.join(":")
|
|
9
|
+
} else {
|
|
10
|
+
return key
|
|
18
11
|
}
|
|
19
|
-
|
|
20
|
-
module.exports.makeExternalQuery = makeExternalQuery
|
|
21
12
|
}
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
finaliseExternalTables,
|
|
17
17
|
SqlClient,
|
|
18
18
|
} from "./utils"
|
|
19
|
+
import Sql from "./base/sql"
|
|
19
20
|
|
|
20
21
|
module MSSQLModule {
|
|
21
22
|
const sqlServer = require("mssql")
|
|
22
|
-
const Sql = require("./base/sql")
|
|
23
23
|
const DEFAULT_SCHEMA = "dbo"
|
|
24
24
|
|
|
25
25
|
interface MSSQLConfig {
|
|
@@ -97,7 +97,8 @@ module MSSQLModule {
|
|
|
97
97
|
class SqlServerIntegration extends Sql implements DatasourcePlus {
|
|
98
98
|
private readonly config: MSSQLConfig
|
|
99
99
|
private index: number = 0
|
|
100
|
-
|
|
100
|
+
private readonly pool: any
|
|
101
|
+
private client: any
|
|
101
102
|
public tables: Record<string, Table> = {}
|
|
102
103
|
public schemaErrors: Record<string, string> = {}
|
|
103
104
|
|
|
@@ -92,12 +92,15 @@ module MongoDBModule {
|
|
|
92
92
|
if (json[field] instanceof Object) {
|
|
93
93
|
json[field] = self.createObjectIds(json[field])
|
|
94
94
|
}
|
|
95
|
-
if (
|
|
96
|
-
|
|
95
|
+
if (
|
|
96
|
+
(field === "_id" || field?.startsWith("$")) &&
|
|
97
|
+
typeof json[field] === "string"
|
|
98
|
+
) {
|
|
99
|
+
const id = json[field].match(
|
|
97
100
|
/(?<=objectid\(['"]).*(?=['"]\))/gi
|
|
98
101
|
)?.[0]
|
|
99
102
|
if (id) {
|
|
100
|
-
json[
|
|
103
|
+
json[field] = ObjectID.createFromHexString(id)
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
}
|
|
@@ -114,10 +117,31 @@ module MongoDBModule {
|
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
parseQueryParams(params: string, mode: string) {
|
|
117
|
-
let queryParams =
|
|
118
|
-
let
|
|
119
|
-
let
|
|
120
|
-
let
|
|
120
|
+
let queryParams = []
|
|
121
|
+
let openCount = 0
|
|
122
|
+
let inQuotes = false
|
|
123
|
+
let i = 0
|
|
124
|
+
let startIndex = 0
|
|
125
|
+
for (let c of params) {
|
|
126
|
+
if (c === '"' && i > 0 && params[i - 1] !== "\\") {
|
|
127
|
+
inQuotes = !inQuotes
|
|
128
|
+
}
|
|
129
|
+
if (c === "{" && !inQuotes) {
|
|
130
|
+
openCount++
|
|
131
|
+
if (openCount === 1) {
|
|
132
|
+
startIndex = i
|
|
133
|
+
}
|
|
134
|
+
} else if (c === "}" && !inQuotes) {
|
|
135
|
+
if (openCount === 1) {
|
|
136
|
+
queryParams.push(JSON.parse(params.substring(startIndex, i + 1)))
|
|
137
|
+
}
|
|
138
|
+
openCount--
|
|
139
|
+
}
|
|
140
|
+
i++
|
|
141
|
+
}
|
|
142
|
+
let group1 = queryParams[0] ?? {}
|
|
143
|
+
let group2 = queryParams[1] ?? {}
|
|
144
|
+
let group3 = queryParams[2] ?? {}
|
|
121
145
|
if (mode === "update") {
|
|
122
146
|
return {
|
|
123
147
|
filter: group1,
|
|
@@ -176,7 +200,10 @@ module MongoDBModule {
|
|
|
176
200
|
return await collection.findOne(json)
|
|
177
201
|
}
|
|
178
202
|
case "findOneAndUpdate": {
|
|
179
|
-
|
|
203
|
+
if (typeof query.json === "string") {
|
|
204
|
+
json = this.parseQueryParams(query.json, "update")
|
|
205
|
+
}
|
|
206
|
+
let findAndUpdateJson = this.createObjectIds(json) as {
|
|
180
207
|
filter: FilterQuery<any>
|
|
181
208
|
update: UpdateQuery<any>
|
|
182
209
|
options: FindOneAndUpdateOption<any>
|
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
} from "./utils"
|
|
18
18
|
import dayjs from "dayjs"
|
|
19
19
|
const { NUMBER_REGEX } = require("../utilities")
|
|
20
|
+
import Sql from "./base/sql"
|
|
20
21
|
|
|
21
22
|
module MySQLModule {
|
|
22
23
|
const mysql = require("mysql2/promise")
|
|
23
|
-
const Sql = require("./base/sql")
|
|
24
24
|
|
|
25
25
|
interface MySQLConfig {
|
|
26
26
|
host: string
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
finaliseExternalTables,
|
|
15
15
|
SqlClient,
|
|
16
16
|
} from "./utils"
|
|
17
|
+
import Sql from "./base/sql"
|
|
17
18
|
|
|
18
19
|
module PostgresModule {
|
|
19
20
|
const { Client, types } = require("pg")
|
|
20
|
-
const Sql = require("./base/sql")
|
|
21
21
|
const { escapeDangerousCharacters } = require("../utilities")
|
|
22
22
|
|
|
23
23
|
// Return "date" and "timestamp" types as plain strings.
|
|
@@ -117,6 +117,7 @@ module PostgresModule {
|
|
|
117
117
|
private readonly client: any
|
|
118
118
|
private readonly config: PostgresConfig
|
|
119
119
|
private index: number = 1
|
|
120
|
+
private open: boolean
|
|
120
121
|
public tables: Record<string, Table> = {}
|
|
121
122
|
public schemaErrors: Record<string, string> = {}
|
|
122
123
|
|
|
@@ -102,4 +102,222 @@ describe("MongoDB Integration", () => {
|
|
|
102
102
|
expect(error).toBeDefined()
|
|
103
103
|
restore()
|
|
104
104
|
})
|
|
105
|
+
|
|
106
|
+
it("creates ObjectIds if the _id fields contains a match on ObjectId", async () => {
|
|
107
|
+
const query = {
|
|
108
|
+
json: {
|
|
109
|
+
filter: {
|
|
110
|
+
_id: "ObjectId('ACBD12345678ABCD12345678')",
|
|
111
|
+
name: "ObjectId('name')"
|
|
112
|
+
},
|
|
113
|
+
update: {
|
|
114
|
+
_id: "ObjectId('FFFF12345678ABCD12345678')",
|
|
115
|
+
name: "ObjectId('updatedName')",
|
|
116
|
+
},
|
|
117
|
+
options: {
|
|
118
|
+
upsert: false,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
extra: { collection: "testCollection", actionTypes: "updateOne" },
|
|
122
|
+
}
|
|
123
|
+
await config.integration.update(query)
|
|
124
|
+
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
|
125
|
+
|
|
126
|
+
const args = config.integration.client.updateOne.mock.calls[0]
|
|
127
|
+
expect(args[0]).toEqual({
|
|
128
|
+
_id: mongo.ObjectID.createFromHexString("ACBD12345678ABCD12345678"),
|
|
129
|
+
name: "ObjectId('name')",
|
|
130
|
+
})
|
|
131
|
+
expect(args[1]).toEqual({
|
|
132
|
+
_id: mongo.ObjectID.createFromHexString("FFFF12345678ABCD12345678"),
|
|
133
|
+
name: "ObjectId('updatedName')",
|
|
134
|
+
})
|
|
135
|
+
expect(args[2]).toEqual({
|
|
136
|
+
upsert: false
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it("creates ObjectIds if the $ operator fields contains a match on ObjectId", async () => {
|
|
141
|
+
const query = {
|
|
142
|
+
json: {
|
|
143
|
+
filter: {
|
|
144
|
+
_id: {
|
|
145
|
+
$eq: "ObjectId('ACBD12345678ABCD12345678')",
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
update: {
|
|
149
|
+
$set: {
|
|
150
|
+
_id: "ObjectId('FFFF12345678ABCD12345678')",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
options: {
|
|
154
|
+
upsert: true,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
extra: { collection: "testCollection", actionTypes: "updateOne" },
|
|
158
|
+
}
|
|
159
|
+
await config.integration.update(query)
|
|
160
|
+
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
|
161
|
+
|
|
162
|
+
const args = config.integration.client.updateOne.mock.calls[0]
|
|
163
|
+
expect(args[0]).toEqual({
|
|
164
|
+
_id: {
|
|
165
|
+
$eq: mongo.ObjectID.createFromHexString("ACBD12345678ABCD12345678"),
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
expect(args[1]).toEqual({
|
|
169
|
+
$set: {
|
|
170
|
+
_id: mongo.ObjectID.createFromHexString("FFFF12345678ABCD12345678"),
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
expect(args[2]).toEqual({
|
|
174
|
+
upsert: true
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it("supports findOneAndUpdate", async () => {
|
|
179
|
+
const query = {
|
|
180
|
+
json: {
|
|
181
|
+
filter: {
|
|
182
|
+
_id: {
|
|
183
|
+
$eq: "ObjectId('ACBD12345678ABCD12345678')",
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
update: {
|
|
187
|
+
$set: {
|
|
188
|
+
name: "UPDATED",
|
|
189
|
+
age: 99
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
options: {
|
|
193
|
+
upsert: false,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
extra: { collection: "testCollection", actionTypes: "findOneAndUpdate" },
|
|
197
|
+
}
|
|
198
|
+
await config.integration.read(query)
|
|
199
|
+
expect(config.integration.client.findOneAndUpdate).toHaveBeenCalled()
|
|
200
|
+
|
|
201
|
+
const args = config.integration.client.findOneAndUpdate.mock.calls[0]
|
|
202
|
+
expect(args[0]).toEqual({
|
|
203
|
+
_id: {
|
|
204
|
+
$eq: mongo.ObjectID.createFromHexString("ACBD12345678ABCD12345678"),
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
expect(args[1]).toEqual({
|
|
208
|
+
$set: {
|
|
209
|
+
name: "UPDATED",
|
|
210
|
+
age: 99
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
expect(args[2]).toEqual({
|
|
214
|
+
upsert: false
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it("can parse nested objects with arrays", async () => {
|
|
219
|
+
const query = {
|
|
220
|
+
json: `{
|
|
221
|
+
"_id": {
|
|
222
|
+
"$eq": "ObjectId('ACBD12345678ABCD12345678')"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"$set": {
|
|
227
|
+
"value": {
|
|
228
|
+
"data": [
|
|
229
|
+
{ "cid": 1 },
|
|
230
|
+
{ "cid": 2 },
|
|
231
|
+
{ "nested": {
|
|
232
|
+
"name": "test"
|
|
233
|
+
}}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"upsert": true
|
|
240
|
+
}`,
|
|
241
|
+
extra: { collection: "testCollection", actionTypes: "updateOne" },
|
|
242
|
+
}
|
|
243
|
+
await config.integration.update(query)
|
|
244
|
+
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
|
245
|
+
|
|
246
|
+
const args = config.integration.client.updateOne.mock.calls[0]
|
|
247
|
+
expect(args[0]).toEqual({
|
|
248
|
+
_id: {
|
|
249
|
+
$eq: mongo.ObjectID.createFromHexString("ACBD12345678ABCD12345678"),
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
expect(args[1]).toEqual({
|
|
253
|
+
$set: {
|
|
254
|
+
value: {
|
|
255
|
+
data: [
|
|
256
|
+
{ cid: 1 },
|
|
257
|
+
{ cid: 2 },
|
|
258
|
+
{ nested: {
|
|
259
|
+
name: "test"
|
|
260
|
+
}}
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
expect(args[2]).toEqual({
|
|
266
|
+
upsert: true
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it("ignores braces within strings when parsing nested objects", async () => {
|
|
271
|
+
const query = {
|
|
272
|
+
json: `{
|
|
273
|
+
"_id": {
|
|
274
|
+
"$eq": "ObjectId('ACBD12345678ABCD12345678')"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"$set": {
|
|
279
|
+
"value": {
|
|
280
|
+
"data": [
|
|
281
|
+
{ "cid": 1 },
|
|
282
|
+
{ "cid": 2 },
|
|
283
|
+
{ "nested": {
|
|
284
|
+
"name": "te}st"
|
|
285
|
+
}}
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"upsert": true,
|
|
292
|
+
"extra": "ad\\"{\\"d"
|
|
293
|
+
}`,
|
|
294
|
+
extra: { collection: "testCollection", actionTypes: "updateOne" },
|
|
295
|
+
}
|
|
296
|
+
await config.integration.update(query)
|
|
297
|
+
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
|
298
|
+
|
|
299
|
+
const args = config.integration.client.updateOne.mock.calls[0]
|
|
300
|
+
expect(args[0]).toEqual({
|
|
301
|
+
_id: {
|
|
302
|
+
$eq: mongo.ObjectID.createFromHexString("ACBD12345678ABCD12345678"),
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
expect(args[1]).toEqual({
|
|
306
|
+
$set: {
|
|
307
|
+
value: {
|
|
308
|
+
data: [
|
|
309
|
+
{ cid: 1 },
|
|
310
|
+
{ cid: 2 },
|
|
311
|
+
{ nested: {
|
|
312
|
+
name: "te}st"
|
|
313
|
+
}}
|
|
314
|
+
]
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
})
|
|
318
|
+
expect(args[2]).toEqual({
|
|
319
|
+
upsert: true,
|
|
320
|
+
extra: "ad\"{\"d"
|
|
321
|
+
})
|
|
322
|
+
})
|
|
105
323
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const Sql = require("../base/sql")
|
|
1
|
+
const Sql = require("../base/sql").default
|
|
2
2
|
const { SqlClient } = require("../utils")
|
|
3
3
|
|
|
4
4
|
const TABLE_NAME = "test"
|
|
@@ -240,18 +240,18 @@ describe("SQL query builder", () => {
|
|
|
240
240
|
})
|
|
241
241
|
})
|
|
242
242
|
|
|
243
|
-
it("should use like expression for MS-SQL when filter is contains", () => {
|
|
243
|
+
it("should use AND like expression for MS-SQL when filter is contains", () => {
|
|
244
244
|
const query = new Sql(SqlClient.MS_SQL, 10)._query(generateReadJson({
|
|
245
245
|
filters: {
|
|
246
246
|
contains: {
|
|
247
|
-
age: 20,
|
|
248
|
-
name: "John"
|
|
247
|
+
age: [20, 25],
|
|
248
|
+
name: ["John", "Mary"]
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
}))
|
|
252
252
|
expect(query).toEqual({
|
|
253
|
-
bindings: [10, "%20%", "%
|
|
254
|
-
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where LOWER(${TABLE_NAME}.age) LIKE @p1 and LOWER(${TABLE_NAME}.name) LIKE @
|
|
253
|
+
bindings: [10, "%20%", "%25%", `%"John"%`, `%"Mary"%`],
|
|
254
|
+
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where (LOWER(${TABLE_NAME}.age) LIKE @p1 AND LOWER(${TABLE_NAME}.age) LIKE @p2) and (LOWER(${TABLE_NAME}.name) LIKE @p3 AND LOWER(${TABLE_NAME}.name) LIKE @p4)) as [${TABLE_NAME}]`
|
|
255
255
|
})
|
|
256
256
|
})
|
|
257
257
|
|
|
@@ -259,14 +259,14 @@ describe("SQL query builder", () => {
|
|
|
259
259
|
const query = new Sql(SqlClient.MY_SQL, 10)._query(generateReadJson({
|
|
260
260
|
filters: {
|
|
261
261
|
contains: {
|
|
262
|
-
age: 20,
|
|
263
|
-
name: "John"
|
|
262
|
+
age: [20],
|
|
263
|
+
name: ["John"]
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
}))
|
|
267
267
|
expect(query).toEqual({
|
|
268
268
|
bindings: [10],
|
|
269
|
-
sql: `select * from (select * from \`${TABLE_NAME}\` where JSON_CONTAINS(${TABLE_NAME}.age, '20') and JSON_CONTAINS(${TABLE_NAME}.name, '"John"') limit ?) as \`${TABLE_NAME}\``
|
|
269
|
+
sql: `select * from (select * from \`${TABLE_NAME}\` where JSON_CONTAINS(${TABLE_NAME}.age, '[20]') and JSON_CONTAINS(${TABLE_NAME}.name, '["John"]') limit ?) as \`${TABLE_NAME}\``
|
|
270
270
|
})
|
|
271
271
|
})
|
|
272
272
|
|
|
@@ -274,8 +274,8 @@ describe("SQL query builder", () => {
|
|
|
274
274
|
const query = new Sql(SqlClient.POSTGRES, 10)._query(generateReadJson({
|
|
275
275
|
filters: {
|
|
276
276
|
contains: {
|
|
277
|
-
age: 20,
|
|
278
|
-
name: "John"
|
|
277
|
+
age: [20],
|
|
278
|
+
name: ["John"]
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
}))
|
|
@@ -284,4 +284,94 @@ describe("SQL query builder", () => {
|
|
|
284
284
|
sql: `select * from (select * from \"${TABLE_NAME}\" where \"${TABLE_NAME}\".\"age\"::jsonb @> '[20]' and \"${TABLE_NAME}\".\"name\"::jsonb @> '["John"]' limit $1) as \"${TABLE_NAME}\"`
|
|
285
285
|
})
|
|
286
286
|
})
|
|
287
|
+
|
|
288
|
+
it("should use NOT like expression for MS-SQL when filter is notContains", () => {
|
|
289
|
+
const query = new Sql(SqlClient.MS_SQL, 10)._query(generateReadJson({
|
|
290
|
+
filters: {
|
|
291
|
+
notContains: {
|
|
292
|
+
age: [20],
|
|
293
|
+
name: ["John"]
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}))
|
|
297
|
+
expect(query).toEqual({
|
|
298
|
+
bindings: [10, "%20%", `%"John"%`],
|
|
299
|
+
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where NOT (LOWER(${TABLE_NAME}.age) LIKE @p1) and NOT (LOWER(${TABLE_NAME}.name) LIKE @p2)) as [${TABLE_NAME}]`
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it("should use NOT JSON_CONTAINS expression for MySQL when filter is notContains", () => {
|
|
304
|
+
const query = new Sql(SqlClient.MY_SQL, 10)._query(generateReadJson({
|
|
305
|
+
filters: {
|
|
306
|
+
notContains: {
|
|
307
|
+
age: [20],
|
|
308
|
+
name: ["John"]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}))
|
|
312
|
+
expect(query).toEqual({
|
|
313
|
+
bindings: [10],
|
|
314
|
+
sql: `select * from (select * from \`${TABLE_NAME}\` where NOT JSON_CONTAINS(${TABLE_NAME}.age, '[20]') and NOT JSON_CONTAINS(${TABLE_NAME}.name, '["John"]') limit ?) as \`${TABLE_NAME}\``
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it("should use jsonb operator NOT expression for PostgreSQL when filter is notContains", () => {
|
|
319
|
+
const query = new Sql(SqlClient.POSTGRES, 10)._query(generateReadJson({
|
|
320
|
+
filters: {
|
|
321
|
+
notContains: {
|
|
322
|
+
age: [20],
|
|
323
|
+
name: ["John"]
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}))
|
|
327
|
+
expect(query).toEqual({
|
|
328
|
+
bindings: [10],
|
|
329
|
+
sql: `select * from (select * from \"${TABLE_NAME}\" where NOT \"${TABLE_NAME}\".\"age\"::jsonb @> '[20]' and NOT \"${TABLE_NAME}\".\"name\"::jsonb @> '["John"]' limit $1) as \"${TABLE_NAME}\"`
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it("should use OR like expression for MS-SQL when filter is containsAny", () => {
|
|
334
|
+
const query = new Sql(SqlClient.MS_SQL, 10)._query(generateReadJson({
|
|
335
|
+
filters: {
|
|
336
|
+
containsAny: {
|
|
337
|
+
age: [20, 25],
|
|
338
|
+
name: ["John", "Mary"]
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}))
|
|
342
|
+
expect(query).toEqual({
|
|
343
|
+
bindings: [10, "%20%", "%25%", `%"John"%`, `%"Mary"%`],
|
|
344
|
+
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where (LOWER(${TABLE_NAME}.age) LIKE @p1 OR LOWER(${TABLE_NAME}.age) LIKE @p2) and (LOWER(${TABLE_NAME}.name) LIKE @p3 OR LOWER(${TABLE_NAME}.name) LIKE @p4)) as [${TABLE_NAME}]`
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it("should use JSON_OVERLAPS expression for MySQL when filter is containsAny", () => {
|
|
349
|
+
const query = new Sql(SqlClient.MY_SQL, 10)._query(generateReadJson({
|
|
350
|
+
filters: {
|
|
351
|
+
containsAny: {
|
|
352
|
+
age: [20, 25],
|
|
353
|
+
name: ["John", "Mary"]
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}))
|
|
357
|
+
expect(query).toEqual({
|
|
358
|
+
bindings: [10],
|
|
359
|
+
sql: `select * from (select * from \`${TABLE_NAME}\` where JSON_OVERLAPS(${TABLE_NAME}.age, '[20,25]') and JSON_OVERLAPS(${TABLE_NAME}.name, '["John","Mary"]') limit ?) as \`${TABLE_NAME}\``
|
|
360
|
+
})
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
it("should use ?| operator expression for PostgreSQL when filter is containsAny", () => {
|
|
364
|
+
const query = new Sql(SqlClient.POSTGRES, 10)._query(generateReadJson({
|
|
365
|
+
filters: {
|
|
366
|
+
containsAny: {
|
|
367
|
+
age: [20, 25],
|
|
368
|
+
name: ["John", "Mary"]
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}))
|
|
372
|
+
expect(query).toEqual({
|
|
373
|
+
bindings: [10],
|
|
374
|
+
sql: `select * from (select * from \"${TABLE_NAME}\" where \"${TABLE_NAME}\".\"age\"::jsonb ?| array [20,25] and \"${TABLE_NAME}\".\"name\"::jsonb ?| array ['John','Mary'] limit $1) as \"${TABLE_NAME}\"`
|
|
375
|
+
})
|
|
376
|
+
})
|
|
287
377
|
})
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
streamUpload,
|
|
16
16
|
deleteFolder,
|
|
17
17
|
downloadTarball,
|
|
18
|
+
deleteFiles,
|
|
18
19
|
} = require("./utilities")
|
|
19
20
|
const { updateClientLibrary } = require("./clientLibrary")
|
|
20
21
|
const env = require("../../environment")
|
|
@@ -327,5 +328,6 @@ exports.cleanup = appIds => {
|
|
|
327
328
|
exports.upload = upload
|
|
328
329
|
exports.retrieve = retrieve
|
|
329
330
|
exports.retrieveToTmp = retrieveToTmp
|
|
331
|
+
exports.deleteFiles = deleteFiles
|
|
330
332
|
exports.TOP_LEVEL_PATH = TOP_LEVEL_PATH
|
|
331
333
|
exports.NODE_MODULES_PATH = NODE_MODULES_PATH
|