@budibase/shared-core 2.9.19 → 2.9.21-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/constants.ts DELETED
@@ -1,98 +0,0 @@
1
- export const OperatorOptions = {
2
- Equals: {
3
- value: "equal",
4
- label: "Equals",
5
- },
6
- NotEquals: {
7
- value: "notEqual",
8
- label: "Not equals",
9
- },
10
- Empty: {
11
- value: "empty",
12
- label: "Is empty",
13
- },
14
- NotEmpty: {
15
- value: "notEmpty",
16
- label: "Is not empty",
17
- },
18
- StartsWith: {
19
- value: "string",
20
- label: "Starts with",
21
- },
22
- Like: {
23
- value: "fuzzy",
24
- label: "Like",
25
- },
26
- MoreThan: {
27
- value: "rangeLow",
28
- label: "More than or equal to",
29
- },
30
- LessThan: {
31
- value: "rangeHigh",
32
- label: "Less than or equal to",
33
- },
34
- Contains: {
35
- value: "contains",
36
- label: "Contains",
37
- },
38
- NotContains: {
39
- value: "notContains",
40
- label: "Does not contain",
41
- },
42
- In: {
43
- value: "oneOf",
44
- label: "Is in",
45
- },
46
- ContainsAny: {
47
- value: "containsAny",
48
- label: "Has any",
49
- },
50
- }
51
-
52
- export const SqlNumberTypeRangeMap = {
53
- integer: {
54
- max: 2147483647,
55
- min: -2147483648,
56
- },
57
- int: {
58
- max: 2147483647,
59
- min: -2147483648,
60
- },
61
- smallint: {
62
- max: 32767,
63
- min: -32768,
64
- },
65
- mediumint: {
66
- max: 8388607,
67
- min: -8388608,
68
- },
69
- }
70
-
71
- export enum SocketEvent {
72
- UserUpdate = "UserUpdate",
73
- UserDisconnect = "UserDisconnect",
74
- Heartbeat = "Heartbeat",
75
- }
76
-
77
- export enum GridSocketEvent {
78
- RowChange = "RowChange",
79
- TableChange = "TableChange",
80
- SelectTable = "SelectTable",
81
- SelectCell = "SelectCell",
82
- }
83
-
84
- export enum BuilderSocketEvent {
85
- SelectApp = "SelectApp",
86
- TableChange = "TableChange",
87
- DatasourceChange = "DatasourceChange",
88
- LockTransfer = "LockTransfer",
89
- ScreenChange = "ScreenChange",
90
- AppMetadataChange = "AppMetadataChange",
91
- SelectResource = "SelectResource",
92
- AppPublishChange = "AppPublishChange",
93
- AutomationChange = "AutomationChange",
94
- }
95
-
96
- export const SocketSessionTTL = 60
97
- export const ValidQueryNameRegex = /^[^()]*$/
98
- export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g
package/src/filters.ts DELETED
@@ -1,473 +0,0 @@
1
- import { Datasource, FieldType, SortDirection, SortType } from "@budibase/types"
2
- import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
3
- import { deepGet } from "./helpers"
4
-
5
- const HBS_REGEX = /{{([^{].*?)}}/g
6
-
7
- /**
8
- * Returns the valid operator options for a certain data type
9
- * @param type the data type
10
- */
11
- export const getValidOperatorsForType = (
12
- type: FieldType,
13
- field: string,
14
- datasource: Datasource & { tableId: any } // TODO: is this table id ever populated?
15
- ) => {
16
- const Op = OperatorOptions
17
- const stringOps = [
18
- Op.Equals,
19
- Op.NotEquals,
20
- Op.StartsWith,
21
- Op.Like,
22
- Op.Empty,
23
- Op.NotEmpty,
24
- Op.In,
25
- ]
26
- const numOps = [
27
- Op.Equals,
28
- Op.NotEquals,
29
- Op.MoreThan,
30
- Op.LessThan,
31
- Op.Empty,
32
- Op.NotEmpty,
33
- Op.In,
34
- ]
35
- let ops: {
36
- value: string
37
- label: string
38
- }[] = []
39
- if (type === "string") {
40
- ops = stringOps
41
- } else if (type === "number" || type === "bigint") {
42
- ops = numOps
43
- } else if (type === "options") {
44
- ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
45
- } else if (type === "array") {
46
- ops = [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty, Op.ContainsAny]
47
- } else if (type === "boolean") {
48
- ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
49
- } else if (type === "longform") {
50
- ops = stringOps
51
- } else if (type === "datetime") {
52
- ops = numOps
53
- } else if (type === "formula") {
54
- ops = stringOps.concat([Op.MoreThan, Op.LessThan])
55
- }
56
-
57
- // Only allow equal/not equal for _id in SQL tables
58
- const externalTable = datasource?.tableId?.includes("datasource_plus")
59
- if (field === "_id" && externalTable) {
60
- ops = [Op.Equals, Op.NotEquals, Op.In]
61
- }
62
-
63
- return ops
64
- }
65
-
66
- /**
67
- * Operators which do not support empty strings as values
68
- */
69
- export const NoEmptyFilterStrings = [
70
- OperatorOptions.StartsWith.value,
71
- OperatorOptions.Like.value,
72
- OperatorOptions.Equals.value,
73
- OperatorOptions.NotEquals.value,
74
- OperatorOptions.Contains.value,
75
- OperatorOptions.NotContains.value,
76
- ] as (keyof QueryFields)[]
77
-
78
- /**
79
- * Removes any fields that contain empty strings that would cause inconsistent
80
- * behaviour with how backend tables are filtered (no value means no filter).
81
- */
82
- const cleanupQuery = (query: Query) => {
83
- if (!query) {
84
- return query
85
- }
86
- for (let filterField of NoEmptyFilterStrings) {
87
- if (!query[filterField]) {
88
- continue
89
- }
90
-
91
- for (let [key, value] of Object.entries(query[filterField]!)) {
92
- if (value == null || value === "") {
93
- delete query[filterField]![key]
94
- }
95
- }
96
- }
97
- return query
98
- }
99
-
100
- /**
101
- * Removes a numeric prefix on field names designed to give fields uniqueness
102
- */
103
- const removeKeyNumbering = (key: string) => {
104
- if (typeof key === "string" && key.match(/\d[0-9]*:/g) != null) {
105
- const parts = key.split(":")
106
- parts.shift()
107
- return parts.join(":")
108
- } else {
109
- return key
110
- }
111
- }
112
-
113
- type Filter = {
114
- operator: keyof Query
115
- field: string
116
- type: any
117
- value: any
118
- externalType: keyof typeof SqlNumberTypeRangeMap
119
- }
120
-
121
- type Query = QueryFields & QueryConfig
122
- type QueryFields = {
123
- string?: {
124
- [key: string]: string
125
- }
126
- fuzzy?: {
127
- [key: string]: string
128
- }
129
- range?: {
130
- [key: string]: {
131
- high: number | string
132
- low: number | string
133
- }
134
- }
135
- equal?: {
136
- [key: string]: any
137
- }
138
- notEqual?: {
139
- [key: string]: any
140
- }
141
- empty?: {
142
- [key: string]: any
143
- }
144
- notEmpty?: {
145
- [key: string]: any
146
- }
147
- oneOf?: {
148
- [key: string]: any[]
149
- }
150
- contains?: {
151
- [key: string]: any[]
152
- }
153
- notContains?: {
154
- [key: string]: any[]
155
- }
156
- containsAny?: {
157
- [key: string]: any[]
158
- }
159
- }
160
-
161
- type QueryConfig = {
162
- allOr?: boolean
163
- }
164
-
165
- type QueryFieldsType = keyof QueryFields
166
-
167
- /**
168
- * Builds a lucene JSON query from the filter structure generated in the builder
169
- * @param filter the builder filter structure
170
- */
171
- export const buildLuceneQuery = (filter: Filter[]) => {
172
- let query: Query = {
173
- string: {},
174
- fuzzy: {},
175
- range: {},
176
- equal: {},
177
- notEqual: {},
178
- empty: {},
179
- notEmpty: {},
180
- contains: {},
181
- notContains: {},
182
- oneOf: {},
183
- containsAny: {},
184
- }
185
- if (Array.isArray(filter)) {
186
- filter.forEach(expression => {
187
- let { operator, field, type, value, externalType } = expression
188
- const isHbs =
189
- typeof value === "string" && (value.match(HBS_REGEX) || []).length > 0
190
- // Parse all values into correct types
191
- if (operator === "allOr") {
192
- query.allOr = true
193
- return
194
- }
195
- if (
196
- type === "datetime" &&
197
- !isHbs &&
198
- operator !== "empty" &&
199
- operator !== "notEmpty"
200
- ) {
201
- // Ensure date value is a valid date and parse into correct format
202
- if (!value) {
203
- return
204
- }
205
- try {
206
- value = new Date(value).toISOString()
207
- } catch (error) {
208
- return
209
- }
210
- }
211
- if (type === "number" && typeof value === "string") {
212
- if (operator === "oneOf") {
213
- value = value.split(",").map(item => parseFloat(item))
214
- } else if (!isHbs) {
215
- value = parseFloat(value)
216
- }
217
- }
218
- if (type === "boolean") {
219
- value = `${value}`?.toLowerCase() === "true"
220
- }
221
- if (
222
- ["contains", "notContains", "containsAny"].includes(operator) &&
223
- type === "array" &&
224
- typeof value === "string"
225
- ) {
226
- value = value.split(",")
227
- }
228
- if (operator.startsWith("range") && query.range) {
229
- const minint =
230
- SqlNumberTypeRangeMap[externalType]?.min || Number.MIN_SAFE_INTEGER
231
- const maxint =
232
- SqlNumberTypeRangeMap[externalType]?.max || Number.MAX_SAFE_INTEGER
233
- if (!query.range[field]) {
234
- query.range[field] = {
235
- low: type === "number" ? minint : "0000-00-00T00:00:00.000Z",
236
- high: type === "number" ? maxint : "9999-00-00T00:00:00.000Z",
237
- }
238
- }
239
- if ((operator as any) === "rangeLow" && value != null && value !== "") {
240
- query.range[field].low = value
241
- } else if (
242
- (operator as any) === "rangeHigh" &&
243
- value != null &&
244
- value !== ""
245
- ) {
246
- query.range[field].high = value
247
- }
248
- } else if (query[operator]) {
249
- if (type === "boolean") {
250
- // Transform boolean filters to cope with null.
251
- // "equals false" needs to be "not equals true"
252
- // "not equals false" needs to be "equals true"
253
- if (operator === "equal" && value === false) {
254
- query.notEqual = query.notEqual || {}
255
- query.notEqual[field] = true
256
- } else if (operator === "notEqual" && value === false) {
257
- query.equal = query.equal || {}
258
- query.equal[field] = true
259
- } else {
260
- query[operator] = query[operator] || {}
261
- query[operator]![field] = value
262
- }
263
- } else {
264
- query[operator] = query[operator] || {}
265
- query[operator]![field] = value
266
- }
267
- }
268
- })
269
- }
270
- return query
271
- }
272
-
273
- /**
274
- * Performs a client-side lucene search on an array of data
275
- * @param docs the data
276
- * @param query the JSON lucene query
277
- */
278
- export const runLuceneQuery = (docs: any[], query?: Query) => {
279
- if (!docs || !Array.isArray(docs)) {
280
- return []
281
- }
282
- if (!query) {
283
- return docs
284
- }
285
-
286
- // Make query consistent first
287
- query = cleanupQuery(query)
288
-
289
- // Iterates over a set of filters and evaluates a fail function against a doc
290
- const match =
291
- (
292
- type: QueryFieldsType,
293
- failFn: (docValue: any, testValue: any) => boolean
294
- ) =>
295
- (doc: any) => {
296
- const filters = Object.entries(query![type] || {})
297
- for (let i = 0; i < filters.length; i++) {
298
- const [key, testValue] = filters[i]
299
- const docValue = deepGet(doc, removeKeyNumbering(key))
300
- if (failFn(docValue, testValue)) {
301
- return false
302
- }
303
- }
304
- return true
305
- }
306
-
307
- // Process a string match (fails if the value does not start with the string)
308
- const stringMatch = match("string", (docValue: string, testValue: string) => {
309
- return (
310
- !docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
311
- )
312
- })
313
-
314
- // Process a fuzzy match (treat the same as starts with when running locally)
315
- const fuzzyMatch = match("fuzzy", (docValue: string, testValue: string) => {
316
- return (
317
- !docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
318
- )
319
- })
320
-
321
- // Process a range match
322
- const rangeMatch = match(
323
- "range",
324
- (
325
- docValue: string | number | null,
326
- testValue: { low: number; high: number }
327
- ) => {
328
- return (
329
- docValue == null ||
330
- docValue === "" ||
331
- +docValue < testValue.low ||
332
- +docValue > testValue.high
333
- )
334
- }
335
- )
336
-
337
- // Process an equal match (fails if the value is different)
338
- const equalMatch = match(
339
- "equal",
340
- (docValue: any, testValue: string | null) => {
341
- return testValue != null && testValue !== "" && docValue !== testValue
342
- }
343
- )
344
-
345
- // Process a not-equal match (fails if the value is the same)
346
- const notEqualMatch = match(
347
- "notEqual",
348
- (docValue: any, testValue: string | null) => {
349
- return testValue != null && testValue !== "" && docValue === testValue
350
- }
351
- )
352
-
353
- // Process an empty match (fails if the value is not empty)
354
- const emptyMatch = match("empty", (docValue: string | null) => {
355
- return docValue != null && docValue !== ""
356
- })
357
-
358
- // Process a not-empty match (fails is the value is empty)
359
- const notEmptyMatch = match("notEmpty", (docValue: string | null) => {
360
- return docValue == null || docValue === ""
361
- })
362
-
363
- // Process an includes match (fails if the value is not included)
364
- const oneOf = match("oneOf", (docValue: any, testValue: any) => {
365
- if (typeof testValue === "string") {
366
- testValue = testValue.split(",")
367
- if (typeof docValue === "number") {
368
- testValue = testValue.map((item: string) => parseFloat(item))
369
- }
370
- }
371
- return !testValue?.includes(docValue)
372
- })
373
-
374
- const containsAny = match("containsAny", (docValue: any, testValue: any) => {
375
- return !docValue?.includes(...testValue)
376
- })
377
-
378
- const contains = match(
379
- "contains",
380
- (docValue: string | any[], testValue: any[]) => {
381
- return !testValue?.every((item: any) => docValue?.includes(item))
382
- }
383
- )
384
-
385
- const notContains = match(
386
- "notContains",
387
- (docValue: string | any[], testValue: any[]) => {
388
- return testValue?.every((item: any) => docValue?.includes(item))
389
- }
390
- )
391
-
392
- // Match a document against all criteria
393
- const docMatch = (doc: any) => {
394
- return (
395
- stringMatch(doc) &&
396
- fuzzyMatch(doc) &&
397
- rangeMatch(doc) &&
398
- equalMatch(doc) &&
399
- notEqualMatch(doc) &&
400
- emptyMatch(doc) &&
401
- notEmptyMatch(doc) &&
402
- oneOf(doc) &&
403
- contains(doc) &&
404
- containsAny(doc) &&
405
- notContains(doc)
406
- )
407
- }
408
-
409
- // Process all docs
410
- return docs.filter(docMatch)
411
- }
412
-
413
- /**
414
- * Performs a client-side sort from the equivalent server-side lucene sort
415
- * parameters.
416
- * @param docs the data
417
- * @param sort the sort column
418
- * @param sortOrder the sort order ("ascending" or "descending")
419
- * @param sortType the type of sort ("string" or "number")
420
- */
421
- export const luceneSort = (
422
- docs: any[],
423
- sort: string,
424
- sortOrder: SortDirection,
425
- sortType = SortType.STRING
426
- ) => {
427
- if (!sort || !sortOrder || !sortType) {
428
- return docs
429
- }
430
- const parse =
431
- sortType === "string" ? (x: any) => `${x}` : (x: string) => parseFloat(x)
432
- return docs
433
- .slice()
434
- .sort((a: { [x: string]: any }, b: { [x: string]: any }) => {
435
- const colA = parse(a[sort])
436
- const colB = parse(b[sort])
437
- if (sortOrder.toLowerCase() === "descending") {
438
- return colA > colB ? -1 : 1
439
- } else {
440
- return colA > colB ? 1 : -1
441
- }
442
- })
443
- }
444
-
445
- /**
446
- * Limits the specified docs to the specified number of rows from the equivalent
447
- * server-side lucene limit parameters.
448
- * @param docs the data
449
- * @param limit the number of docs to limit to
450
- */
451
- export const luceneLimit = (docs: any[], limit: string) => {
452
- const numLimit = parseFloat(limit)
453
- if (isNaN(numLimit)) {
454
- return docs
455
- }
456
- return docs.slice(0, numLimit)
457
- }
458
-
459
- export const hasFilters = (query?: Query) => {
460
- if (!query) {
461
- return false
462
- }
463
- const skipped = ["allOr"]
464
- for (let [key, value] of Object.entries(query)) {
465
- if (skipped.includes(key) || typeof value !== "object") {
466
- continue
467
- }
468
- if (Object.keys(value).length !== 0) {
469
- return true
470
- }
471
- }
472
- return false
473
- }
@@ -1,85 +0,0 @@
1
- import { User } from "@budibase/types"
2
-
3
- /**
4
- * Gets a key within an object. The key supports dot syntax for retrieving deep
5
- * fields - e.g. "a.b.c".
6
- * Exact matches of keys with dots in them take precedence over nested keys of
7
- * the same path - e.g. getting "a.b" from { "a.b": "foo", a: { b: "bar" } }
8
- * will return "foo" over "bar".
9
- * @param obj the object
10
- * @param key the key
11
- * @return {*|null} the value or null if a value was not found for this key
12
- */
13
- export const deepGet = (obj: { [x: string]: any }, key: string) => {
14
- if (!obj || !key) {
15
- return null
16
- }
17
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
18
- return obj[key]
19
- }
20
- const split = key.split(".")
21
- for (let i = 0; i < split.length; i++) {
22
- obj = obj?.[split[i]]
23
- }
24
- return obj
25
- }
26
-
27
- /**
28
- * Gets the initials to show in a user avatar.
29
- * @param user the user
30
- */
31
- export const getUserInitials = (user: User) => {
32
- if (!user) {
33
- return "?"
34
- }
35
- let initials = ""
36
- initials += user.firstName ? user.firstName[0] : ""
37
- initials += user.lastName ? user.lastName[0] : ""
38
- if (initials !== "") {
39
- return initials
40
- }
41
- return user.email?.[0] || "U"
42
- }
43
-
44
- /**
45
- * Gets a deterministic colour for a particular user
46
- * @param user the user
47
- */
48
- export const getUserColor = (user: User) => {
49
- let id = user?._id
50
- if (!id) {
51
- return "var(--spectrum-global-color-blue-400)"
52
- }
53
-
54
- // In order to generate the same color for global users as app users, we need
55
- // to remove the app-specific table prefix
56
- id = id.replace("ro_ta_users_", "")
57
-
58
- // Generate a hue based on the ID
59
- let hue = 1
60
- for (let i = 0; i < id.length; i++) {
61
- hue += id.charCodeAt(i)
62
- hue = hue % 36
63
- }
64
- return `hsl(${hue * 10}, 50%, 40%)`
65
- }
66
-
67
- /**
68
- * Gets a friendly label to describe who a user is.
69
- * @param user the user
70
- */
71
- export const getUserLabel = (user: User) => {
72
- if (!user) {
73
- return ""
74
- }
75
- const { firstName, lastName, email } = user
76
- if (firstName && lastName) {
77
- return `${firstName} ${lastName}`
78
- } else if (firstName) {
79
- return firstName
80
- } else if (lastName) {
81
- return lastName
82
- } else {
83
- return email
84
- }
85
- }
@@ -1,2 +0,0 @@
1
- export * from "./helpers"
2
- export * from "./integrations"
@@ -1,18 +0,0 @@
1
- import { Datasource, SourceName } from "@budibase/types"
2
-
3
- export function isGoogleSheets(type: SourceName) {
4
- return type === SourceName.GOOGLE_SHEETS
5
- }
6
-
7
- export function isSQL(datasource: Datasource): boolean {
8
- if (!datasource || !datasource.source) {
9
- return false
10
- }
11
- const SQL = [
12
- SourceName.POSTGRES,
13
- SourceName.SQL_SERVER,
14
- SourceName.MYSQL,
15
- SourceName.ORACLE,
16
- ]
17
- return SQL.indexOf(datasource.source) !== -1
18
- }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from "./constants"
2
- export * as dataFilters from "./filters"
3
- export * as helpers from "./helpers"
4
- export * as utils from "./utils"
5
- export * as sdk from "./sdk"