@budibase/backend-core 2.32.11 → 2.32.13

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.
Files changed (37) hide show
  1. package/dist/index.js +504 -172
  2. package/dist/index.js.map +4 -4
  3. package/dist/index.js.meta.json +1 -1
  4. package/dist/package.json +4 -4
  5. package/dist/plugins.js.meta.json +1 -1
  6. package/dist/src/db/couch/DatabaseImpl.js +10 -2
  7. package/dist/src/db/couch/DatabaseImpl.js.map +1 -1
  8. package/dist/src/features/features.d.ts +47 -0
  9. package/dist/src/features/features.js +269 -0
  10. package/dist/src/features/features.js.map +1 -0
  11. package/dist/src/features/index.d.ts +2 -39
  12. package/dist/src/features/index.js +6 -235
  13. package/dist/src/features/index.js.map +1 -1
  14. package/dist/src/features/tests/utils.d.ts +3 -0
  15. package/dist/src/features/tests/utils.js +56 -0
  16. package/dist/src/features/tests/utils.js.map +1 -0
  17. package/dist/src/middleware/passport/sso/sso.js +0 -25
  18. package/dist/src/middleware/passport/sso/sso.js.map +1 -1
  19. package/dist/src/sql/sql.js +93 -35
  20. package/dist/src/sql/sql.js.map +1 -1
  21. package/dist/src/users/users.js +8 -1
  22. package/dist/src/users/users.js.map +1 -1
  23. package/dist/tests/core/utilities/structures/accounts.d.ts +1 -6
  24. package/dist/tests/core/utilities/structures/accounts.js +4 -58
  25. package/dist/tests/core/utilities/structures/accounts.js.map +1 -1
  26. package/dist/tests/core/utilities/structures/users.js +2 -5
  27. package/dist/tests/core/utilities/structures/users.js.map +1 -1
  28. package/package.json +4 -4
  29. package/src/db/couch/DatabaseImpl.ts +12 -2
  30. package/src/features/features.ts +300 -0
  31. package/src/features/index.ts +2 -281
  32. package/src/features/tests/utils.ts +64 -0
  33. package/src/middleware/passport/sso/sso.ts +0 -24
  34. package/src/sql/sql.ts +107 -36
  35. package/src/users/users.ts +10 -2
  36. package/tests/core/utilities/structures/accounts.ts +1 -66
  37. package/tests/core/utilities/structures/users.ts +0 -5
package/dist/index.js CHANGED
@@ -61260,7 +61260,7 @@ __export(src_exports, {
61260
61260
  sql: () => sql_exports,
61261
61261
  tenancy: () => tenancy,
61262
61262
  timers: () => timers_exports,
61263
- userUtils: () => utils_exports5,
61263
+ userUtils: () => utils_exports6,
61264
61264
  users: () => users_exports3,
61265
61265
  utils: () => utils_exports4,
61266
61266
  withEnv: () => withEnv
@@ -61911,6 +61911,7 @@ __export(filters_exports, {
61911
61911
  ColumnSplitter: () => ColumnSplitter,
61912
61912
  NoEmptyFilterStrings: () => NoEmptyFilterStrings,
61913
61913
  buildQuery: () => buildQuery,
61914
+ buildQueryLegacy: () => buildQueryLegacy,
61914
61915
  cleanupQuery: () => cleanupQuery,
61915
61916
  fixupFilterArrays: () => fixupFilterArrays,
61916
61917
  getKeyNumbering: () => getKeyNumbering,
@@ -61926,6 +61927,164 @@ __export(filters_exports, {
61926
61927
  });
61927
61928
  var import_dayjs = __toESM(require_dayjs_min());
61928
61929
 
61930
+ // ../shared-core/src/utils.ts
61931
+ var utils_exports = {};
61932
+ __export(utils_exports, {
61933
+ filterValueToLabel: () => filterValueToLabel,
61934
+ hasSchema: () => hasSchema,
61935
+ isSupportedUserSearch: () => isSupportedUserSearch,
61936
+ parallelForeach: () => parallelForeach,
61937
+ processSearchFilters: () => processSearchFilters,
61938
+ trimOtherProps: () => trimOtherProps,
61939
+ unreachable: () => unreachable
61940
+ });
61941
+ function unreachable(value, message = `No such case in exhaustive switch: ${value}`) {
61942
+ throw new Error(message);
61943
+ }
61944
+ async function parallelForeach(items, task, maxConcurrency) {
61945
+ const promises = [];
61946
+ let index2 = 0;
61947
+ const processItem = async (item) => {
61948
+ try {
61949
+ await task(item);
61950
+ } finally {
61951
+ processNext();
61952
+ }
61953
+ };
61954
+ const processNext = () => {
61955
+ if (index2 >= items.length) {
61956
+ return;
61957
+ }
61958
+ const item = items[index2];
61959
+ index2++;
61960
+ const promise = processItem(item);
61961
+ promises.push(promise);
61962
+ if (promises.length >= maxConcurrency) {
61963
+ Promise.race(promises).then(processNext);
61964
+ } else {
61965
+ processNext();
61966
+ }
61967
+ };
61968
+ processNext();
61969
+ await Promise.all(promises);
61970
+ }
61971
+ function filterValueToLabel() {
61972
+ return Object.keys(OperatorOptions).reduce(
61973
+ (acc, key) => {
61974
+ const ops = OperatorOptions;
61975
+ const op = ops[key];
61976
+ acc[op["value"]] = op.label;
61977
+ return acc;
61978
+ },
61979
+ {}
61980
+ );
61981
+ }
61982
+ function hasSchema(test) {
61983
+ return typeof test === "object" && !Array.isArray(test) && test !== null && !(test instanceof Date) && Object.keys(test).length > 0;
61984
+ }
61985
+ function trimOtherProps(object, allowedProps) {
61986
+ const result = Object.keys(object).filter((key) => allowedProps.includes(key)).reduce(
61987
+ (acc, key) => ({ ...acc, [key]: object[key] }),
61988
+ {}
61989
+ );
61990
+ return result;
61991
+ }
61992
+ function isSupportedUserSearch(query) {
61993
+ const allowed = [
61994
+ { op: "string" /* STRING */, key: "email" },
61995
+ { op: "equal" /* EQUAL */, key: "_id" },
61996
+ { op: "oneOf" /* ONE_OF */, key: "_id" }
61997
+ ];
61998
+ for (const [key, operation] of Object.entries(query)) {
61999
+ if (typeof operation !== "object") {
62000
+ return false;
62001
+ }
62002
+ if (isLogicalSearchOperator(key)) {
62003
+ for (const condition of query[key].conditions) {
62004
+ if (!isSupportedUserSearch(condition)) {
62005
+ return false;
62006
+ }
62007
+ }
62008
+ return true;
62009
+ }
62010
+ const fields = Object.keys(operation || {});
62011
+ if (fields.length === 0) {
62012
+ continue;
62013
+ }
62014
+ const allowedOperation = allowed.find(
62015
+ (allow) => allow.op === key && fields.length === 1 && fields[0] === allow.key
62016
+ );
62017
+ if (!allowedOperation) {
62018
+ return false;
62019
+ }
62020
+ }
62021
+ return true;
62022
+ }
62023
+ var processSearchFilters = (filters) => {
62024
+ if (!filters) {
62025
+ return;
62026
+ }
62027
+ const defaultCfg = {
62028
+ logicalOperator: "all" /* ALL */,
62029
+ groups: []
62030
+ };
62031
+ const filterAllowedKeys = [
62032
+ "field",
62033
+ "operator",
62034
+ "value",
62035
+ "type",
62036
+ "externalType",
62037
+ "valueType",
62038
+ "noValue",
62039
+ "formulaType"
62040
+ ];
62041
+ if (Array.isArray(filters)) {
62042
+ let baseGroup = {
62043
+ filters: [],
62044
+ logicalOperator: "all" /* ALL */
62045
+ };
62046
+ return filters.reduce((acc, filter) => {
62047
+ const filterPropertyKeys = Object.keys(filter).sort((a, b) => {
62048
+ return a.localeCompare(b);
62049
+ }).filter((key) => key in filter);
62050
+ if (filterPropertyKeys.length == 1) {
62051
+ const key = filterPropertyKeys[0], value = filter[key];
62052
+ if (key === "onEmptyFilter") {
62053
+ acc.onEmptyFilter = value;
62054
+ } else if (key === "operator" && value === "allOr") {
62055
+ baseGroup.logicalOperator = "any" /* ANY */;
62056
+ }
62057
+ return acc;
62058
+ }
62059
+ const allowedFilterSettings = filterPropertyKeys.reduce(
62060
+ (acc2, key) => {
62061
+ const value = filter[key];
62062
+ if (filterAllowedKeys.includes(key)) {
62063
+ if (key === "field") {
62064
+ acc2.push([key, removeKeyNumbering(value)]);
62065
+ } else {
62066
+ acc2.push([key, value]);
62067
+ }
62068
+ }
62069
+ return acc2;
62070
+ },
62071
+ []
62072
+ );
62073
+ const migratedFilter = Object.fromEntries(
62074
+ allowedFilterSettings
62075
+ );
62076
+ baseGroup.filters.push(migratedFilter);
62077
+ if (!acc.groups || !acc.groups.length) {
62078
+ acc.groups = [baseGroup];
62079
+ }
62080
+ return acc;
62081
+ }, defaultCfg);
62082
+ } else if (!filters?.groups) {
62083
+ return;
62084
+ }
62085
+ return filters;
62086
+ };
62087
+
61929
62088
  // ../shared-core/src/helpers/index.ts
61930
62089
  var helpers_exports = {};
61931
62090
  __export(helpers_exports, {
@@ -62130,6 +62289,7 @@ var views_exports = {};
62130
62289
  __export(views_exports, {
62131
62290
  basicFields: () => basicFields,
62132
62291
  calculationFields: () => calculationFields,
62292
+ hasCalculationFields: () => hasCalculationFields,
62133
62293
  isBasicViewField: () => isBasicViewField,
62134
62294
  isCalculationField: () => isCalculationField,
62135
62295
  isCalculationView: () => isCalculationView
@@ -62142,6 +62302,9 @@ function isBasicViewField(field) {
62142
62302
  return !isCalculationField(field);
62143
62303
  }
62144
62304
  function isCalculationView(view) {
62305
+ return view.type === "calculation" /* CALCULATION */;
62306
+ }
62307
+ function hasCalculationFields(view) {
62145
62308
  return Object.values(view.schema || {}).some(isCalculationField);
62146
62309
  }
62147
62310
  function calculationFields(view) {
@@ -62221,7 +62384,7 @@ var NoEmptyFilterStrings = [
62221
62384
  ];
62222
62385
  function recurseLogicalOperators(filters, fn) {
62223
62386
  for (const logical of LOGICAL_OPERATORS) {
62224
- if (filters?.[logical]) {
62387
+ if (filters[logical]) {
62225
62388
  filters[logical].conditions = filters[logical].conditions.map(
62226
62389
  (condition) => fn(condition)
62227
62390
  );
@@ -62241,9 +62404,6 @@ function recurseSearchFilters(filters, processFn) {
62241
62404
  return filters;
62242
62405
  }
62243
62406
  var cleanupQuery = (query) => {
62244
- if (!query) {
62245
- return query;
62246
- }
62247
62407
  for (let filterField of NoEmptyFilterStrings) {
62248
62408
  if (!query[filterField]) {
62249
62409
  continue;
@@ -62334,7 +62494,106 @@ var ColumnSplitter = class {
62334
62494
  };
62335
62495
  }
62336
62496
  };
62337
- var buildQuery = (filter) => {
62497
+ var buildCondition = (expression) => {
62498
+ let query = {
62499
+ string: {},
62500
+ fuzzy: {},
62501
+ range: {},
62502
+ equal: {},
62503
+ notEqual: {},
62504
+ empty: {},
62505
+ notEmpty: {},
62506
+ contains: {},
62507
+ notContains: {},
62508
+ oneOf: {},
62509
+ containsAny: {}
62510
+ };
62511
+ let { operator, field, type, value, externalType, onEmptyFilter } = expression;
62512
+ if (!operator || !field) {
62513
+ return;
62514
+ }
62515
+ const queryOperator = operator;
62516
+ const isHbs = typeof value === "string" && (value.match(HBS_REGEX) || []).length > 0;
62517
+ if (operator === "allOr") {
62518
+ query.allOr = true;
62519
+ return;
62520
+ }
62521
+ if (onEmptyFilter) {
62522
+ query.onEmptyFilter = onEmptyFilter;
62523
+ return;
62524
+ }
62525
+ if (queryOperator === "empty" || queryOperator === "notEmpty") {
62526
+ value = null;
62527
+ }
62528
+ if (type === "datetime" && !isHbs && queryOperator !== "empty" && queryOperator !== "notEmpty") {
62529
+ if (!value) {
62530
+ return;
62531
+ }
62532
+ try {
62533
+ value = new Date(value).toISOString();
62534
+ } catch (error) {
62535
+ return;
62536
+ }
62537
+ }
62538
+ if (type === "number" && typeof value === "string" && !isHbs) {
62539
+ if (queryOperator === "oneOf") {
62540
+ value = value.split(",").map((item) => parseFloat(item));
62541
+ } else {
62542
+ value = parseFloat(value);
62543
+ }
62544
+ }
62545
+ if (type === "boolean") {
62546
+ value = `${value}`?.toLowerCase() === "true";
62547
+ }
62548
+ if (["contains", "notContains", "containsAny"].includes(
62549
+ operator.toLocaleString()
62550
+ ) && type === "array" && typeof value === "string") {
62551
+ value = value.split(",");
62552
+ }
62553
+ if (operator.toLocaleString().startsWith("range") && query.range) {
62554
+ const minint = SqlNumberTypeRangeMap[externalType]?.min || Number.MIN_SAFE_INTEGER;
62555
+ const maxint = SqlNumberTypeRangeMap[externalType]?.max || Number.MAX_SAFE_INTEGER;
62556
+ if (!query.range[field]) {
62557
+ query.range[field] = {
62558
+ low: type === "number" ? minint : "0000-00-00T00:00:00.000Z",
62559
+ high: type === "number" ? maxint : "9999-00-00T00:00:00.000Z"
62560
+ };
62561
+ }
62562
+ if (operator === "rangeLow" && value != null && value !== "") {
62563
+ query.range[field] = {
62564
+ ...query.range[field],
62565
+ low: value
62566
+ };
62567
+ } else if (operator === "rangeHigh" && value != null && value !== "") {
62568
+ query.range[field] = {
62569
+ ...query.range[field],
62570
+ high: value
62571
+ };
62572
+ }
62573
+ } else if (isLogicalSearchOperator(queryOperator)) {
62574
+ } else if (query[queryOperator] && operator !== "onEmptyFilter") {
62575
+ if (type === "boolean") {
62576
+ if (queryOperator === "equal" && value === false) {
62577
+ query.notEqual = query.notEqual || {};
62578
+ query.notEqual[field] = true;
62579
+ } else if (queryOperator === "notEqual" && value === false) {
62580
+ query.equal = query.equal || {};
62581
+ query.equal[field] = true;
62582
+ } else {
62583
+ query[queryOperator] ??= {};
62584
+ query[queryOperator][field] = value;
62585
+ }
62586
+ } else {
62587
+ query[queryOperator] ??= {};
62588
+ query[queryOperator][field] = value;
62589
+ }
62590
+ }
62591
+ return query;
62592
+ };
62593
+ var buildQueryLegacy = (filter) => {
62594
+ if (!Array.isArray(filter)) {
62595
+ return filter;
62596
+ }
62338
62597
  let query = {
62339
62598
  string: {},
62340
62599
  fuzzy: {},
@@ -62383,10 +62642,12 @@ var buildQuery = (filter) => {
62383
62642
  if (type === "boolean") {
62384
62643
  value = `${value}`?.toLowerCase() === "true";
62385
62644
  }
62386
- if (["contains", "notContains", "containsAny"].includes(operator) && type === "array" && typeof value === "string") {
62645
+ if (["contains", "notContains", "containsAny"].includes(
62646
+ operator.toLocaleString()
62647
+ ) && type === "array" && typeof value === "string") {
62387
62648
  value = value.split(",");
62388
62649
  }
62389
- if (operator.startsWith("range") && query.range) {
62650
+ if (operator.toLocaleString().startsWith("range") && query.range) {
62390
62651
  const minint = SqlNumberTypeRangeMap[externalType]?.min || Number.MIN_SAFE_INTEGER;
62391
62652
  const maxint = SqlNumberTypeRangeMap[externalType]?.max || Number.MAX_SAFE_INTEGER;
62392
62653
  if (!query.range[field]) {
@@ -62427,6 +62688,30 @@ var buildQuery = (filter) => {
62427
62688
  });
62428
62689
  return query;
62429
62690
  };
62691
+ var buildQuery = (filter) => {
62692
+ const parsedFilter = processSearchFilters(filter);
62693
+ if (!parsedFilter) {
62694
+ return;
62695
+ }
62696
+ const operatorMap = {
62697
+ ["all" /* ALL */]: "$and" /* AND */,
62698
+ ["any" /* ANY */]: "$or" /* OR */
62699
+ };
62700
+ const globalOnEmpty = parsedFilter.onEmptyFilter ? parsedFilter.onEmptyFilter : null;
62701
+ const globalOperator = operatorMap[parsedFilter.logicalOperator];
62702
+ return {
62703
+ ...globalOnEmpty ? { onEmptyFilter: globalOnEmpty } : {},
62704
+ [globalOperator]: {
62705
+ conditions: parsedFilter.groups?.map((group) => {
62706
+ return {
62707
+ [operatorMap[group.logicalOperator]]: {
62708
+ conditions: group.filters?.map((x) => buildCondition(x)).filter((filter2) => filter2)
62709
+ }
62710
+ };
62711
+ })
62712
+ }
62713
+ };
62714
+ };
62430
62715
  function fixupFilterArrays(filters) {
62431
62716
  for (const searchField of Object.values(ArrayOperator)) {
62432
62717
  const field = filters[searchField];
@@ -62453,7 +62738,7 @@ function search(docs, query) {
62453
62738
  if (query.sort) {
62454
62739
  result = sort(result, query.sort, query.sortOrder || "ascending" /* ASCENDING */);
62455
62740
  }
62456
- let totalRows = result.length;
62741
+ const totalRows = result.length;
62457
62742
  if (query.limit) {
62458
62743
  result = limit(result, query.limit.toString());
62459
62744
  }
@@ -62767,91 +63052,6 @@ var hasFilters = (query) => {
62767
63052
  return check(query);
62768
63053
  };
62769
63054
 
62770
- // ../shared-core/src/utils.ts
62771
- var utils_exports = {};
62772
- __export(utils_exports, {
62773
- filterValueToLabel: () => filterValueToLabel,
62774
- hasSchema: () => hasSchema,
62775
- isSupportedUserSearch: () => isSupportedUserSearch,
62776
- parallelForeach: () => parallelForeach,
62777
- trimOtherProps: () => trimOtherProps,
62778
- unreachable: () => unreachable
62779
- });
62780
- function unreachable(value, message = `No such case in exhaustive switch: ${value}`) {
62781
- throw new Error(message);
62782
- }
62783
- async function parallelForeach(items, task, maxConcurrency) {
62784
- const promises = [];
62785
- let index2 = 0;
62786
- const processItem = async (item) => {
62787
- try {
62788
- await task(item);
62789
- } finally {
62790
- processNext();
62791
- }
62792
- };
62793
- const processNext = () => {
62794
- if (index2 >= items.length) {
62795
- return;
62796
- }
62797
- const item = items[index2];
62798
- index2++;
62799
- const promise = processItem(item);
62800
- promises.push(promise);
62801
- if (promises.length >= maxConcurrency) {
62802
- Promise.race(promises).then(processNext);
62803
- } else {
62804
- processNext();
62805
- }
62806
- };
62807
- processNext();
62808
- await Promise.all(promises);
62809
- }
62810
- function filterValueToLabel() {
62811
- return Object.keys(OperatorOptions).reduce(
62812
- (acc, key) => {
62813
- const ops = OperatorOptions;
62814
- const op = ops[key];
62815
- acc[op["value"]] = op.label;
62816
- return acc;
62817
- },
62818
- {}
62819
- );
62820
- }
62821
- function hasSchema(test) {
62822
- return typeof test === "object" && !Array.isArray(test) && test !== null && !(test instanceof Date) && Object.keys(test).length > 0;
62823
- }
62824
- function trimOtherProps(object, allowedProps) {
62825
- const result = Object.keys(object).filter((key) => allowedProps.includes(key)).reduce(
62826
- (acc, key) => ({ ...acc, [key]: object[key] }),
62827
- {}
62828
- );
62829
- return result;
62830
- }
62831
- function isSupportedUserSearch(query) {
62832
- const allowed = [
62833
- { op: "string" /* STRING */, key: "email" },
62834
- { op: "equal" /* EQUAL */, key: "_id" },
62835
- { op: "oneOf" /* ONE_OF */, key: "_id" }
62836
- ];
62837
- for (let [key, operation] of Object.entries(query)) {
62838
- if (typeof operation !== "object") {
62839
- return false;
62840
- }
62841
- const fields = Object.keys(operation || {});
62842
- if (fields.length === 0) {
62843
- continue;
62844
- }
62845
- const allowedOperation = allowed.find(
62846
- (allow) => allow.op === key && fields.length === 1 && fields[0] === allow.key
62847
- );
62848
- if (!allowedOperation) {
62849
- return false;
62850
- }
62851
- }
62852
- return true;
62853
- }
62854
-
62855
63055
  // ../shared-core/src/sdk/index.ts
62856
63056
  var sdk_exports = {};
62857
63057
  __export(sdk_exports, {
@@ -66928,8 +67128,12 @@ __export(features_exports, {
66928
67128
  FlagSet: () => FlagSet,
66929
67129
  flags: () => flags,
66930
67130
  init: () => init4,
66931
- shutdown: () => shutdown2
67131
+ parseEnvFlags: () => parseEnvFlags,
67132
+ shutdown: () => shutdown2,
67133
+ testutils: () => utils_exports5
66932
67134
  });
67135
+
67136
+ // src/features/features.ts
66933
67137
  var import_posthog_node = require("posthog-node");
66934
67138
  var import_dd_trace3 = __toESM(require("dd-trace"));
66935
67139
 
@@ -67310,7 +67514,7 @@ var Duration = class _Duration {
67310
67514
  }
67311
67515
  };
67312
67516
 
67313
- // src/features/index.ts
67517
+ // src/features/features.ts
67314
67518
  var posthog;
67315
67519
  function init4(opts) {
67316
67520
  if (environment_default.POSTHOG_TOKEN && environment_default.POSTHOG_API_HOST && !environment_default.SELF_HOSTED && environment_default.POSTHOG_FEATURE_FLAGS_ENABLED) {
@@ -67375,6 +67579,21 @@ var NumberFlag = class extends Flag {
67375
67579
  throw new Error(`could not parse value "${value}" as number`);
67376
67580
  }
67377
67581
  };
67582
+ function parseEnvFlags(flags2) {
67583
+ const split = flags2.split(",").map((x) => x.split(":"));
67584
+ const result = [];
67585
+ for (const [tenantId, ...features] of split) {
67586
+ for (let feature of features) {
67587
+ let value = true;
67588
+ if (feature.startsWith("!")) {
67589
+ feature = feature.slice(1);
67590
+ value = false;
67591
+ }
67592
+ result.push({ tenantId, key: feature, value });
67593
+ }
67594
+ }
67595
+ return result;
67596
+ }
67378
67597
  var FlagSet = class {
67379
67598
  constructor(flagSchema) {
67380
67599
  this.flagSchema = flagSchema;
@@ -67409,28 +67628,24 @@ var FlagSet = class {
67409
67628
  const flagValues = this.defaults();
67410
67629
  const currentTenantId = getTenantId();
67411
67630
  const specificallySetFalse = /* @__PURE__ */ new Set();
67412
- const split = (environment_default.TENANT_FEATURE_FLAGS || "").split(",").map((x) => x.split(":"));
67413
- for (const [tenantId, ...features] of split) {
67631
+ for (const { tenantId, key, value } of parseEnvFlags(
67632
+ environment_default.TENANT_FEATURE_FLAGS || ""
67633
+ )) {
67414
67634
  if (!tenantId || tenantId !== "*" && tenantId !== currentTenantId) {
67415
67635
  continue;
67416
67636
  }
67417
67637
  tags[`readFromEnvironmentVars`] = true;
67418
- for (let feature of features) {
67419
- let value = true;
67420
- if (feature.startsWith("!")) {
67421
- feature = feature.slice(1);
67422
- value = false;
67423
- specificallySetFalse.add(feature);
67424
- }
67425
- if (!this.isFlagName(feature)) {
67426
- continue;
67427
- }
67428
- if (typeof flagValues[feature] !== "boolean") {
67429
- throw new Error(`Feature: ${feature} is not a boolean`);
67430
- }
67431
- flagValues[feature] = value;
67432
- tags[`flags.${feature}.source`] = "environment";
67638
+ if (value === false) {
67639
+ specificallySetFalse.add(key);
67640
+ }
67641
+ if (!this.isFlagName(key)) {
67642
+ continue;
67433
67643
  }
67644
+ if (typeof flagValues[key] !== "boolean") {
67645
+ throw new Error(`Feature: ${key} is not a boolean`);
67646
+ }
67647
+ flagValues[key] = value;
67648
+ tags[`flags.${key}.source`] = "environment";
67434
67649
  }
67435
67650
  const license = ctx?.user?.license;
67436
67651
  if (license) {
@@ -67497,6 +67712,60 @@ var flags = new FlagSet({
67497
67712
  ["ENRICHED_RELATIONSHIPS" /* ENRICHED_RELATIONSHIPS */]: Flag.boolean(environment_default.isDev())
67498
67713
  });
67499
67714
 
67715
+ // src/features/tests/utils.ts
67716
+ var utils_exports5 = {};
67717
+ __export(utils_exports5, {
67718
+ setFeatureFlags: () => setFeatureFlags2,
67719
+ withFeatureFlags: () => withFeatureFlags
67720
+ });
67721
+ function getCurrentFlags() {
67722
+ const result = {};
67723
+ for (const { tenantId, key, value } of parseEnvFlags(
67724
+ process.env.TENANT_FEATURE_FLAGS || ""
67725
+ )) {
67726
+ const tenantFlags = result[tenantId] || {};
67727
+ if (tenantFlags[key] === false) {
67728
+ continue;
67729
+ }
67730
+ tenantFlags[key] = value;
67731
+ result[tenantId] = tenantFlags;
67732
+ }
67733
+ return result;
67734
+ }
67735
+ function buildFlagString(flags2) {
67736
+ const parts = [];
67737
+ for (const [tenantId, tenantFlags] of Object.entries(flags2)) {
67738
+ for (const [key, value] of Object.entries(tenantFlags)) {
67739
+ if (value === false) {
67740
+ parts.push(`${tenantId}:!${key}`);
67741
+ } else {
67742
+ parts.push(`${tenantId}:${key}`);
67743
+ }
67744
+ }
67745
+ }
67746
+ return parts.join(",");
67747
+ }
67748
+ function setFeatureFlags2(tenantId, flags2) {
67749
+ const current = getCurrentFlags();
67750
+ for (const [key, value] of Object.entries(flags2)) {
67751
+ const tenantFlags = current[tenantId] || {};
67752
+ tenantFlags[key] = value;
67753
+ current[tenantId] = tenantFlags;
67754
+ }
67755
+ const flagString = buildFlagString(current);
67756
+ return setEnv({ TENANT_FEATURE_FLAGS: flagString });
67757
+ }
67758
+ function withFeatureFlags(tenantId, flags2, f) {
67759
+ const cleanup3 = setFeatureFlags2(tenantId, flags2);
67760
+ const result = f();
67761
+ if (result instanceof Promise) {
67762
+ return result.finally(cleanup3);
67763
+ } else {
67764
+ cleanup3();
67765
+ return result;
67766
+ }
67767
+ }
67768
+
67500
67769
  // src/db/couch/DatabaseImpl.ts
67501
67770
  var DATABASE_NOT_FOUND = "Database does not exist.";
67502
67771
  function buildNano(couchInfo) {
@@ -67760,11 +68029,21 @@ var DatabaseImpl = class _DatabaseImpl {
67760
68029
  return this.performCall(() => {
67761
68030
  return async () => {
67762
68031
  const response = await directCouchUrlCall(args);
67763
- const json = await response.json();
68032
+ const text = await response.text();
67764
68033
  if (response.status > 300) {
68034
+ let json;
68035
+ try {
68036
+ json = JSON.parse(text);
68037
+ } catch (err) {
68038
+ console.error(`SQS error: ${text}`);
68039
+ throw new CouchDBError(
68040
+ "error while running SQS query, please try again later",
68041
+ { name: "sqs_error", status: response.status }
68042
+ );
68043
+ }
67765
68044
  throw json;
67766
68045
  }
67767
- return json;
68046
+ return JSON.parse(text);
67768
68047
  };
67769
68048
  });
67770
68049
  }
@@ -68777,8 +69056,8 @@ __export(users_exports3, {
68777
69056
  });
68778
69057
 
68779
69058
  // src/users/utils.ts
68780
- var utils_exports5 = {};
68781
- __export(utils_exports5, {
69059
+ var utils_exports6 = {};
69060
+ __export(utils_exports6, {
68782
69061
  getAccountHolderFromUserIds: () => getAccountHolderFromUserIds,
68783
69062
  hasAdminPermissions: () => hasAdminPermissions2,
68784
69063
  hasAppBuilderPermissions: () => hasAppBuilderPermissions2,
@@ -72803,6 +73082,12 @@ async function paginatedUsers({
72803
73082
  userList = await bulkGetGlobalUsersById(query?.oneOf?._id, {
72804
73083
  cleanup: true
72805
73084
  });
73085
+ } else if (query) {
73086
+ const response = await db.allDocs(
73087
+ getGlobalUserParams(null, { ...opts, limit: void 0 })
73088
+ );
73089
+ userList = response.rows.map((row) => row.doc);
73090
+ userList = filters_exports.search(userList, { query, limit: opts.limit }).rows;
72806
73091
  } else {
72807
73092
  const response = await db.allDocs(getGlobalUserParams(null, opts));
72808
73093
  userList = response.rows.map((row) => row.doc);
@@ -73620,7 +73905,6 @@ __export(google_exports, {
73620
73905
  });
73621
73906
 
73622
73907
  // src/middleware/passport/sso/sso.ts
73623
- var import_node_fetch5 = __toESM(require("node-fetch"));
73624
73908
  var ssoSaveUserNoOp = (user) => Promise.resolve(user);
73625
73909
  async function authenticate2(details, requireLocalAccount = true, done, saveUserFn) {
73626
73910
  if (!saveUserFn) {
@@ -73675,24 +73959,10 @@ async function authenticate2(details, requireLocalAccount = true, done, saveUser
73675
73959
  }
73676
73960
  return done(null, ssoUser);
73677
73961
  }
73678
- async function getProfilePictureUrl(user, details) {
73679
- const pictureUrl = details.profile?._json.picture;
73680
- if (pictureUrl) {
73681
- const response = await (0, import_node_fetch5.default)(pictureUrl);
73682
- if (response.status === 200) {
73683
- const type = response.headers.get("content-type");
73684
- if (type.startsWith("image/")) {
73685
- return pictureUrl;
73686
- }
73687
- }
73688
- }
73689
- }
73690
73962
  async function syncUser(user, details) {
73691
73963
  let firstName;
73692
73964
  let lastName;
73693
- let pictureUrl;
73694
73965
  let oauth2;
73695
- let thirdPartyProfile;
73696
73966
  if (details.profile) {
73697
73967
  const profile = details.profile;
73698
73968
  if (profile.name) {
@@ -73704,10 +73974,6 @@ async function syncUser(user, details) {
73704
73974
  lastName = name.familyName;
73705
73975
  }
73706
73976
  }
73707
- pictureUrl = await getProfilePictureUrl(user, details);
73708
- thirdPartyProfile = {
73709
- ...profile._json
73710
- };
73711
73977
  }
73712
73978
  if (details.oauth2) {
73713
73979
  oauth2 = {
@@ -73720,8 +73986,6 @@ async function syncUser(user, details) {
73720
73986
  providerType: details.providerType,
73721
73987
  firstName,
73722
73988
  lastName,
73723
- thirdPartyProfile,
73724
- pictureUrl,
73725
73989
  oauth2
73726
73990
  };
73727
73991
  }
@@ -73783,7 +74047,7 @@ __export(oidc_exports, {
73783
74047
  getCallbackUrl: () => getCallbackUrl2,
73784
74048
  strategyFactory: () => strategyFactory2
73785
74049
  });
73786
- var import_node_fetch6 = __toESM(require("node-fetch"));
74050
+ var import_node_fetch5 = __toESM(require("node-fetch"));
73787
74051
  var OIDCStrategy = require_lib9().Strategy;
73788
74052
  function buildVerifyFn2(saveUserFn) {
73789
74053
  return async (issuer, sub, profile, jwtClaims, accessToken, refreshToken, idToken, params2, done) => {
@@ -73843,7 +74107,7 @@ async function fetchStrategyConfig(oidcConfig, callbackUrl) {
73843
74107
  "Configuration invalid. Must contain clientID, clientSecret, callbackUrl and configUrl"
73844
74108
  );
73845
74109
  }
73846
- const response = await (0, import_node_fetch6.default)(configUrl);
74110
+ const response = await (0, import_node_fetch5.default)(configUrl);
73847
74111
  if (!response.ok) {
73848
74112
  throw new Error(
73849
74113
  `Unexpected response when fetching openid-configuration: ${response.statusText}`
@@ -75270,25 +75534,56 @@ var InternalBuilder = class {
75270
75534
  const { column } = this.splitter.run(key);
75271
75535
  return this.table.schema[column];
75272
75536
  }
75273
- // Takes a string like foo and returns a quoted string like [foo] for SQL Server
75274
- // and "foo" for Postgres.
75275
- quote(str) {
75537
+ quoteChars() {
75276
75538
  switch (this.client) {
75277
- case "sqlite3" /* SQL_LITE */:
75278
75539
  case "oracledb" /* ORACLE */:
75279
75540
  case "pg" /* POSTGRES */:
75280
- return `"${str}"`;
75541
+ return ['"', '"'];
75281
75542
  case "mssql" /* MS_SQL */:
75282
- return `[${str}]`;
75543
+ return ["[", "]"];
75283
75544
  case "mariadb" /* MARIADB */:
75284
75545
  case "mysql2" /* MY_SQL */:
75285
- return `\`${str}\``;
75546
+ case "sqlite3" /* SQL_LITE */:
75547
+ return ["`", "`"];
75286
75548
  }
75287
75549
  }
75288
- // Takes a string like a.b.c and returns a quoted identifier like [a].[b].[c]
75289
- // for SQL Server and `a`.`b`.`c` for MySQL.
75550
+ // Takes a string like foo and returns a quoted string like [foo] for SQL Server
75551
+ // and "foo" for Postgres.
75552
+ quote(str) {
75553
+ const [start2, end2] = this.quoteChars();
75554
+ return `${start2}${str}${end2}`;
75555
+ }
75556
+ isQuoted(key) {
75557
+ const [start2, end2] = this.quoteChars();
75558
+ return key.startsWith(start2) && key.endsWith(end2);
75559
+ }
75560
+ // Takes a string like a.b.c or an array like ["a", "b", "c"] and returns a
75561
+ // quoted identifier like [a].[b].[c] for SQL Server and `a`.`b`.`c` for
75562
+ // MySQL.
75290
75563
  quotedIdentifier(key) {
75291
- return key.split(".").map((part) => this.quote(part)).join(".");
75564
+ if (!Array.isArray(key)) {
75565
+ key = this.splitIdentifier(key);
75566
+ }
75567
+ return key.map((part) => this.quote(part)).join(".");
75568
+ }
75569
+ // Turns an identifier like a.b.c or `a`.`b`.`c` into ["a", "b", "c"]
75570
+ splitIdentifier(key) {
75571
+ const [start2, end2] = this.quoteChars();
75572
+ if (this.isQuoted(key)) {
75573
+ return key.slice(1, -1).split(`${end2}.${start2}`);
75574
+ }
75575
+ return key.split(".");
75576
+ }
75577
+ qualifyIdentifier(key) {
75578
+ const tableName = this.getTableName();
75579
+ const parts = this.splitIdentifier(key);
75580
+ if (parts[0] !== tableName) {
75581
+ parts.unshift(tableName);
75582
+ }
75583
+ if (this.isQuoted(key)) {
75584
+ return this.quotedIdentifier(parts);
75585
+ }
75586
+ return parts.join(".");
75292
75587
  }
75293
75588
  isFullSelectStatementRequired() {
75294
75589
  const { meta } = this.query;
@@ -75339,13 +75634,22 @@ var InternalBuilder = class {
75339
75634
  // OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
75340
75635
  // so when we use them we need to wrap them in to_char(). This function
75341
75636
  // converts a field name to the appropriate identifier.
75342
- convertClobs(field) {
75343
- const parts = field.split(".");
75637
+ convertClobs(field, opts) {
75638
+ if (this.client !== "oracledb" /* ORACLE */) {
75639
+ throw new Error(
75640
+ "you've called convertClobs on a DB that's not Oracle, this is a mistake"
75641
+ );
75642
+ }
75643
+ const parts = this.splitIdentifier(field);
75344
75644
  const col = parts.pop();
75345
75645
  const schema = this.table.schema[col];
75346
75646
  let identifier = this.quotedIdentifier(field);
75347
75647
  if (schema.type === "string" /* STRING */ || schema.type === "longform" /* LONGFORM */ || schema.type === "bb_reference_single" /* BB_REFERENCE_SINGLE */ || schema.type === "bb_reference" /* BB_REFERENCE */ || schema.type === "options" /* OPTIONS */ || schema.type === "barcodeqr" /* BARCODEQR */) {
75348
- identifier = `to_char(${identifier})`;
75648
+ if (opts?.forSelect) {
75649
+ identifier = `to_char(${identifier}) as ${this.quotedIdentifier(col)}`;
75650
+ } else {
75651
+ identifier = `to_char(${identifier})`;
75652
+ }
75349
75653
  }
75350
75654
  return identifier;
75351
75655
  }
@@ -75842,28 +76146,56 @@ var InternalBuilder = class {
75842
76146
  const fields = this.query.resource?.fields || [];
75843
76147
  const tableName = this.getTableName();
75844
76148
  if (fields.length > 0) {
75845
- query = query.groupBy(fields.map((field) => `${tableName}.${field}`));
75846
- query = query.select(fields.map((field) => `${tableName}.${field}`));
76149
+ const qualifiedFields = fields.map((field) => this.qualifyIdentifier(field));
76150
+ if (this.client === "oracledb" /* ORACLE */) {
76151
+ const groupByFields = qualifiedFields.map(
76152
+ (field) => this.convertClobs(field)
76153
+ );
76154
+ const selectFields = qualifiedFields.map(
76155
+ (field) => this.convertClobs(field, { forSelect: true })
76156
+ );
76157
+ query = query.groupByRaw(groupByFields.join(", ")).select(this.knex.raw(selectFields.join(", ")));
76158
+ } else {
76159
+ query = query.groupBy(qualifiedFields).select(qualifiedFields);
76160
+ }
75847
76161
  }
75848
76162
  for (const aggregation of aggregations) {
75849
76163
  const op = aggregation.calculationType;
75850
- const field = `${tableName}.${aggregation.field} as ${aggregation.name}`;
75851
- switch (op) {
75852
- case "count" /* COUNT */:
75853
- query = query.count(field);
75854
- break;
75855
- case "sum" /* SUM */:
75856
- query = query.sum(field);
75857
- break;
75858
- case "avg" /* AVG */:
75859
- query = query.avg(field);
75860
- break;
75861
- case "min" /* MIN */:
75862
- query = query.min(field);
75863
- break;
75864
- case "max" /* MAX */:
75865
- query = query.max(field);
75866
- break;
76164
+ if (op === "count" /* COUNT */) {
76165
+ if ("distinct" in aggregation && aggregation.distinct) {
76166
+ if (this.client === "oracledb" /* ORACLE */) {
76167
+ const field = this.convertClobs(`${tableName}.${aggregation.field}`);
76168
+ query = query.select(
76169
+ this.knex.raw(
76170
+ `COUNT(DISTINCT ${field}) as ${this.quotedIdentifier(
76171
+ aggregation.name
76172
+ )}`
76173
+ )
76174
+ );
76175
+ } else {
76176
+ query = query.countDistinct(
76177
+ `${tableName}.${aggregation.field} as ${aggregation.name}`
76178
+ );
76179
+ }
76180
+ } else {
76181
+ query = query.count(`* as ${aggregation.name}`);
76182
+ }
76183
+ } else {
76184
+ const field = `${tableName}.${aggregation.field} as ${aggregation.name}`;
76185
+ switch (op) {
76186
+ case "sum" /* SUM */:
76187
+ query = query.sum(field);
76188
+ break;
76189
+ case "avg" /* AVG */:
76190
+ query = query.avg(field);
76191
+ break;
76192
+ case "min" /* MIN */:
76193
+ query = query.min(field);
76194
+ break;
76195
+ case "max" /* MAX */:
76196
+ query = query.max(field);
76197
+ break;
76198
+ }
75867
76199
  }
75868
76200
  }
75869
76201
  return query;