@jskit-ai/crud-core 0.1.53 → 0.1.55

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/crud-core",
4
- version: "0.1.53",
4
+ version: "0.1.55",
5
5
  kind: "runtime",
6
6
  description: "Shared CRUD helpers used by CRUD modules.",
7
7
  dependsOn: [
@@ -26,7 +26,7 @@ export default Object.freeze({
26
26
  mutations: {
27
27
  dependencies: {
28
28
  runtime: {
29
- "@jskit-ai/crud-core": "0.1.53"
29
+ "@jskit-ai/crud-core": "0.1.55"
30
30
  },
31
31
  dev: {}
32
32
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/crud-core",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -13,10 +13,9 @@
13
13
  "./shared/crudFieldMetaSupport": "./src/shared/crudFieldMetaSupport.js",
14
14
  "./shared/crudNamespaceSupport": "./src/shared/crudNamespaceSupport.js",
15
15
  "./server/repositorySupport": "./src/server/repositorySupport.js",
16
- "./server/repositoryMethods": "./src/server/repositoryMethods.js",
16
+ "./server/resourceRuntime": "./src/server/resourceRuntime/index.js",
17
17
  "./server/createHooksToCollectChildren": "./src/server/createHooksToCollectChildren.js",
18
- "./server/createCrudRepositoryFromResource": "./src/server/createCrudRepositoryFromResource.js",
19
- "./server/lookupProviders": "./src/server/lookupProviders.js",
18
+ "./server/lookups": "./src/server/lookups.js",
20
19
  "./server/serviceEvents": "./src/server/serviceEvents.js",
21
20
  "./server/serviceMethods": "./src/server/serviceMethods.js",
22
21
  "./server/fieldAccess": "./src/server/fieldAccess.js",
@@ -27,12 +26,12 @@
27
26
  },
28
27
  "dependencies": {
29
28
  "@tanstack/vue-query": "^5.90.5",
30
- "@jskit-ai/database-runtime": "0.1.45",
31
- "@jskit-ai/kernel": "0.1.45",
32
- "@jskit-ai/realtime": "0.1.44",
33
- "@jskit-ai/shell-web": "0.1.44",
34
- "@jskit-ai/users-core": "0.1.55",
35
- "@jskit-ai/users-web": "0.1.60",
29
+ "@jskit-ai/database-runtime": "0.1.47",
30
+ "@jskit-ai/kernel": "0.1.47",
31
+ "@jskit-ai/realtime": "0.1.46",
32
+ "@jskit-ai/shell-web": "0.1.46",
33
+ "@jskit-ai/users-core": "0.1.57",
34
+ "@jskit-ai/users-web": "0.1.62",
36
35
  "typebox": "^1.0.81"
37
36
  }
38
37
  }
@@ -11,13 +11,13 @@ import { normalizeCrudLookupApiPath } from "./lookupPathSupport.js";
11
11
  const DEFAULT_LOOKUP_INCLUDE = "*";
12
12
  const DEFAULT_LOOKUP_MAX_DEPTH = 3;
13
13
  const MAX_LOOKUP_MAX_DEPTH = 10;
14
- const LOOKUP_PROVIDER_OWNERSHIP_FILTER_VALUES = Object.freeze([
14
+ const LOOKUP_OWNERSHIP_FILTER_VALUES = Object.freeze([
15
15
  "public",
16
16
  "user",
17
17
  "workspace",
18
18
  "workspace_user"
19
19
  ]);
20
- const LOOKUP_PROVIDER_OWNERSHIP_FILTER_SET = new Set(LOOKUP_PROVIDER_OWNERSHIP_FILTER_VALUES);
20
+ const LOOKUP_OWNERSHIP_FILTER_SET = new Set(LOOKUP_OWNERSHIP_FILTER_VALUES);
21
21
 
22
22
  function normalizeLookupRelationEntry(entry = {}, outputKeys = new Set()) {
23
23
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
@@ -162,7 +162,7 @@ function createCrudLookupRuntime(resource = {}, { outputKeys = [] } = {}) {
162
162
  context: "crud lookup runtime container key"
163
163
  });
164
164
  const namespace =
165
- normalizeCrudLookupNamespace(resource?.resource) ||
165
+ normalizeCrudLookupNamespace(resource?.namespace) ||
166
166
  normalizeCrudLookupNamespace(resource?.apiPath);
167
167
  const defaults = resolveLookupRuntimeDefaults(resource);
168
168
 
@@ -374,10 +374,10 @@ function buildLookupHydrationPlan(
374
374
  };
375
375
  }
376
376
 
377
- function resolveLookupProviderResolver(repositoryOptions = {}, callOptions = {}, { context = "crudRepository" } = {}) {
378
- const resolver = callOptions?.resolveLookupProvider || repositoryOptions?.resolveLookupProvider;
377
+ function resolveLookupResolver(repositoryOptions = {}, callOptions = {}, { context = "crudRepository" } = {}) {
378
+ const resolver = callOptions?.resolveLookup || repositoryOptions?.resolveLookup;
379
379
  if (typeof resolver !== "function") {
380
- throw new TypeError(`${context} requires resolveLookupProvider(relation) to hydrate lookups.`);
380
+ throw new TypeError(`${context} requires resolveLookup(relation) to hydrate lookups.`);
381
381
  }
382
382
  return resolver;
383
383
  }
@@ -477,28 +477,28 @@ function resolveGroupChildInclude(group = {}) {
477
477
  return [...includeSet].join(",");
478
478
  }
479
479
 
480
- function normalizeLookupProvider(provider, relation = {}, { context = "crudRepository" } = {}) {
480
+ function normalizeLookup(provider, relation = {}, { context = "crudRepository" } = {}) {
481
481
  if (!provider || typeof provider !== "object" || Array.isArray(provider)) {
482
- throw new Error(`${context} could not resolve lookup provider for namespace "${relation.namespace}".`);
482
+ throw new Error(`${context} could not resolve lookup "${relation.namespace}".`);
483
483
  }
484
484
  if (typeof provider.listByIds !== "function") {
485
- throw new Error(`${context} lookup provider for namespace "${relation.namespace}" must expose listByIds(ids, options).`);
485
+ throw new Error(`${context} lookup "${relation.namespace}" must expose listByIds(ids, options).`);
486
486
  }
487
487
  return provider;
488
488
  }
489
489
 
490
- function resolveLookupProviderOwnershipFilter(provider = {}, { context = "crudRepository" } = {}) {
490
+ function resolveLookupOwnershipFilter(provider = {}, { context = "crudRepository" } = {}) {
491
491
  const normalizedOwnershipFilter = normalizeText(provider?.ownershipFilter).toLowerCase();
492
492
  if (!normalizedOwnershipFilter) {
493
493
  return "";
494
494
  }
495
495
 
496
- if (LOOKUP_PROVIDER_OWNERSHIP_FILTER_SET.has(normalizedOwnershipFilter)) {
496
+ if (LOOKUP_OWNERSHIP_FILTER_SET.has(normalizedOwnershipFilter)) {
497
497
  return normalizedOwnershipFilter;
498
498
  }
499
499
 
500
500
  throw new TypeError(
501
- `${context} lookup provider ownershipFilter must be one of: ${LOOKUP_PROVIDER_OWNERSHIP_FILTER_VALUES.join(", ")}.`
501
+ `${context} lookup ownershipFilter must be one of: ${LOOKUP_OWNERSHIP_FILTER_VALUES.join(", ")}.`
502
502
  );
503
503
  }
504
504
 
@@ -509,26 +509,26 @@ function resolveLookupVisibilityContext(
509
509
  { context = "crudRepository" } = {}
510
510
  ) {
511
511
  const parentVisibilityContext = normalizeVisibilityContext(callOptions?.visibilityContext);
512
- const providerOwnershipFilter = resolveLookupProviderOwnershipFilter(provider, {
512
+ const lookupOwnershipFilter = resolveLookupOwnershipFilter(provider, {
513
513
  context
514
514
  });
515
515
 
516
- if (!providerOwnershipFilter) {
516
+ if (!lookupOwnershipFilter) {
517
517
  if (parentVisibilityContext.visibility !== "public") {
518
518
  throw new Error(
519
- `${context} lookup provider for namespace "${relation.namespace}" must declare ownershipFilter when parent visibility is "${parentVisibilityContext.visibility}".`
519
+ `${context} lookup "${relation.namespace}" must declare ownershipFilter when parent visibility is "${parentVisibilityContext.visibility}".`
520
520
  );
521
521
  }
522
522
  return callOptions?.visibilityContext;
523
523
  }
524
524
 
525
525
  const nextVisibilityContext = {
526
- visibility: providerOwnershipFilter
526
+ visibility: lookupOwnershipFilter
527
527
  };
528
- if (providerOwnershipFilter === "workspace" || providerOwnershipFilter === "workspace_user") {
528
+ if (lookupOwnershipFilter === "workspace" || lookupOwnershipFilter === "workspace_user") {
529
529
  nextVisibilityContext.scopeOwnerId = parentVisibilityContext.scopeOwnerId;
530
530
  }
531
- if (providerOwnershipFilter === "user" || providerOwnershipFilter === "workspace_user") {
531
+ if (lookupOwnershipFilter === "user" || lookupOwnershipFilter === "workspace_user") {
532
532
  nextVisibilityContext.userId = parentVisibilityContext.userId;
533
533
  }
534
534
 
@@ -624,7 +624,7 @@ async function hydrateCrudLookupRecords(
624
624
  return sourceRecords;
625
625
  }
626
626
 
627
- const resolveLookupProvider = resolveLookupProviderResolver(repositoryOptions, callOptions, {
627
+ const resolveLookup = resolveLookupResolver(repositoryOptions, callOptions, {
628
628
  context: runtime?.context || "crudRepository"
629
629
  });
630
630
 
@@ -636,7 +636,7 @@ async function hydrateCrudLookupRecords(
636
636
  continue;
637
637
  }
638
638
 
639
- const provider = normalizeLookupProvider(resolveLookupProvider(group.relation), group.relation, {
639
+ const provider = normalizeLookup(resolveLookup(group.relation), group.relation, {
640
640
  context: runtime?.context || "crudRepository"
641
641
  });
642
642
  const childVisibilityContext = resolveLookupVisibilityContext(
@@ -4,7 +4,7 @@ import {
4
4
  } from "@jskit-ai/kernel/shared/support/crudLookup";
5
5
  import { toSnakeCase } from "@jskit-ai/kernel/shared/support/stringCase";
6
6
 
7
- function requireCrudLookupNamespace(value = "", { context = "crudLookupProvider" } = {}) {
7
+ function requireCrudLookupNamespace(value = "", { context = "crudLookup" } = {}) {
8
8
  const normalizedNamespace = normalizeCrudLookupNamespace(value);
9
9
  if (!normalizedNamespace) {
10
10
  throw new Error(`${context} requires relation.namespace.`);
@@ -13,7 +13,7 @@ function requireCrudLookupNamespace(value = "", { context = "crudLookupProvider"
13
13
  return normalizedNamespace;
14
14
  }
15
15
 
16
- function resolveCrudLookupProviderToken(namespace = "", { context = "crudLookupProvider" } = {}) {
16
+ function resolveCrudLookupToken(namespace = "", { context = "crudLookup" } = {}) {
17
17
  const normalizedNamespace = requireCrudLookupNamespace(namespace, {
18
18
  context
19
19
  });
@@ -22,10 +22,10 @@ function resolveCrudLookupProviderToken(namespace = "", { context = "crudLookupP
22
22
  .map((segment) => toSnakeCase(segment))
23
23
  .filter(Boolean)
24
24
  .join(".");
25
- return `crud.lookup.${tokenPart}`;
25
+ return `lookup.${tokenPart}`;
26
26
  }
27
27
 
28
- function resolveCrudLookupNamespaceFromRelation(relation = {}, { context = "crudLookupProvider" } = {}) {
28
+ function resolveCrudLookupNamespaceFromRelation(relation = {}, { context = "crudLookup" } = {}) {
29
29
  const normalizedNamespace =
30
30
  normalizeCrudLookupNamespace(relation?.namespace) ||
31
31
  normalizeCrudLookupNamespace(relation?.apiPath);
@@ -41,5 +41,5 @@ export {
41
41
  normalizeCrudLookupNamespace,
42
42
  requireCrudLookupNamespace,
43
43
  resolveCrudLookupNamespaceFromRelation,
44
- resolveCrudLookupProviderToken
44
+ resolveCrudLookupToken
45
45
  };
@@ -1,55 +1,55 @@
1
1
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
2
  import {
3
- resolveCrudLookupProviderToken,
3
+ resolveCrudLookupToken,
4
4
  resolveCrudLookupNamespaceFromRelation
5
5
  } from "./lookupPathSupport.js";
6
6
 
7
- const LOOKUP_PROVIDER_OWNERSHIP_FILTER_VALUES = Object.freeze([
7
+ const LOOKUP_OWNERSHIP_FILTER_VALUES = Object.freeze([
8
8
  "public",
9
9
  "user",
10
10
  "workspace",
11
11
  "workspace_user"
12
12
  ]);
13
- const LOOKUP_PROVIDER_OWNERSHIP_FILTER_SET = new Set(LOOKUP_PROVIDER_OWNERSHIP_FILTER_VALUES);
13
+ const LOOKUP_OWNERSHIP_FILTER_SET = new Set(LOOKUP_OWNERSHIP_FILTER_VALUES);
14
14
 
15
- function normalizeLookupProviderOwnershipFilter(value, { context = "crudLookupProvider ownershipFilter" } = {}) {
15
+ function normalizeLookupOwnershipFilter(value, { context = "crudLookup ownershipFilter" } = {}) {
16
16
  const normalized = normalizeText(value).toLowerCase();
17
17
  if (!normalized) {
18
18
  return "";
19
19
  }
20
20
 
21
- if (LOOKUP_PROVIDER_OWNERSHIP_FILTER_SET.has(normalized)) {
21
+ if (LOOKUP_OWNERSHIP_FILTER_SET.has(normalized)) {
22
22
  return normalized;
23
23
  }
24
24
 
25
25
  throw new TypeError(
26
- `${context} must be one of: ${LOOKUP_PROVIDER_OWNERSHIP_FILTER_VALUES.join(", ")}.`
26
+ `${context} must be one of: ${LOOKUP_OWNERSHIP_FILTER_VALUES.join(", ")}.`
27
27
  );
28
28
  }
29
29
 
30
- function createCrudLookupProviderResolver(scope, { context = "crudLookupProvider" } = {}) {
30
+ function createCrudLookupResolver(scope, { context = "crudLookup" } = {}) {
31
31
  if (!scope || typeof scope.make !== "function") {
32
32
  throw new Error(`${context} requires scope.make().`);
33
33
  }
34
34
 
35
- return function resolveLookupProvider(relation = {}) {
35
+ return function resolveLookup(relation = {}) {
36
36
  const namespace = resolveCrudLookupNamespaceFromRelation(relation, {
37
37
  context
38
38
  });
39
39
  return scope.make(
40
- resolveCrudLookupProviderToken(namespace, {
40
+ resolveCrudLookupToken(namespace, {
41
41
  context
42
42
  })
43
43
  );
44
44
  };
45
45
  }
46
46
 
47
- function createCrudLookupProvider(repository, { context = "crudLookupProvider", ownershipFilter = "" } = {}) {
47
+ function createCrudLookup(repository, { context = "crudLookup", ownershipFilter = "" } = {}) {
48
48
  if (!repository || typeof repository.listByIds !== "function") {
49
49
  throw new Error(`${context} requires repository.listByIds(ids, options).`);
50
50
  }
51
51
 
52
- const normalizedOwnershipFilter = normalizeLookupProviderOwnershipFilter(
52
+ const normalizedOwnershipFilter = normalizeLookupOwnershipFilter(
53
53
  ownershipFilter || repository?.ownershipFilter,
54
54
  {
55
55
  context: `${context} ownershipFilter`
@@ -69,7 +69,7 @@ function createCrudLookupProvider(repository, { context = "crudLookupProvider",
69
69
  }
70
70
 
71
71
  export {
72
- resolveCrudLookupProviderToken,
73
- createCrudLookupProviderResolver,
74
- createCrudLookupProvider
72
+ resolveCrudLookupToken,
73
+ createCrudLookupResolver,
74
+ createCrudLookup
75
75
  };
@@ -8,7 +8,12 @@ import {
8
8
  resolveCrudLookupContainerKey,
9
9
  resolveCrudLookupFieldKeys
10
10
  } from "@jskit-ai/kernel/shared/support/crudLookup";
11
- import { isCrudRuntimeOutputOnlyFieldKey } from "../shared/crudFieldMetaSupport.js";
11
+ import {
12
+ CRUD_FIELD_REPOSITORY_STORAGE_COLUMN,
13
+ CRUD_FIELD_REPOSITORY_STORAGE_VIRTUAL,
14
+ isCrudRuntimeOutputOnlyFieldKey,
15
+ normalizeCrudFieldRepositoryConfig
16
+ } from "../shared/crudFieldMetaSupport.js";
12
17
 
13
18
  const DEFAULT_LIST_LIMIT = 20;
14
19
  const MAX_LIST_LIMIT = 100;
@@ -70,7 +75,8 @@ function resolveColumnName(fieldKey, overrides = {}) {
70
75
  function buildRepositoryColumnMetadata({
71
76
  outputKeys = [],
72
77
  writeKeys = [],
73
- columnOverrides = {}
78
+ columnOverrides = {},
79
+ fieldStorageByKey = {}
74
80
  } = {}) {
75
81
  const normalizedOutputKeys = (Array.isArray(outputKeys) ? outputKeys : [])
76
82
  .map((key) => String(key || "").trim())
@@ -80,6 +86,9 @@ function buildRepositoryColumnMetadata({
80
86
  .filter(Boolean);
81
87
 
82
88
  const deriveMapping = (key) => {
89
+ if (fieldStorageByKey?.[key] !== CRUD_FIELD_REPOSITORY_STORAGE_COLUMN) {
90
+ return null;
91
+ }
83
92
  const column = resolveColumnName(key, columnOverrides);
84
93
  if (!column) {
85
94
  return null;
@@ -100,6 +109,13 @@ function buildRepositoryColumnMetadata({
100
109
  });
101
110
  }
102
111
 
112
+ function resolveOptionalObjectSchemaProperties(schema, options = {}) {
113
+ if (!schema) {
114
+ return {};
115
+ }
116
+ return requireObjectSchemaProperties(schema, options);
117
+ }
118
+
103
119
  function requireObjectSchemaProperties(schema, { context = "crudRepository", schemaLabel = "schema" } = {}) {
104
120
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
105
121
  throw new TypeError(`${context} requires ${schemaLabel} to be an object schema.`);
@@ -189,6 +205,7 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
189
205
 
190
206
  const outputSchema = operations?.view?.outputValidator?.schema;
191
207
  const writeSchema = operations?.create?.bodyValidator?.schema;
208
+ const patchSchema = operations?.patch?.bodyValidator?.schema;
192
209
  const outputProperties = requireObjectSchemaProperties(outputSchema, {
193
210
  context,
194
211
  schemaLabel: "operations.view.outputValidator.schema"
@@ -197,6 +214,10 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
197
214
  context,
198
215
  schemaLabel: "operations.create.bodyValidator.schema"
199
216
  });
217
+ const patchProperties = resolveOptionalObjectSchemaProperties(patchSchema, {
218
+ context,
219
+ schemaLabel: "operations.patch.bodyValidator.schema"
220
+ });
200
221
  const lookupContainerKey = resolveCrudLookupContainerKey(resource, {
201
222
  context: `${context} resource.contract.lookup.containerKey`
202
223
  });
@@ -205,20 +226,63 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
205
226
  (key) => !isCrudRuntimeOutputOnlyFieldKey(key, { lookupContainerKey })
206
227
  )
207
228
  );
208
- const writeKeys = Object.freeze(Object.keys(writeProperties));
229
+ const writeKeys = Object.freeze([
230
+ ...Object.keys(writeProperties),
231
+ ...Object.keys(patchProperties).filter((key) => !Object.hasOwn(writeProperties, key))
232
+ ]);
209
233
 
234
+ const fieldStorageByKey = {};
210
235
  const columnOverrides = {};
211
236
  for (const entry of normalizeResourceFieldMetaEntries(resource.fieldMeta)) {
212
237
  const key = normalizeText(entry.key);
213
- const dbColumn = normalizeText(entry.dbColumn);
214
- if (!key || !dbColumn) {
238
+ if (!key) {
215
239
  continue;
216
240
  }
217
- columnOverrides[key] = dbColumn;
241
+ const repositoryConfig = normalizeCrudFieldRepositoryConfig(entry, {
242
+ context: `${context} resource.fieldMeta`,
243
+ fieldKey: key
244
+ });
245
+ fieldStorageByKey[key] = repositoryConfig.storage;
246
+ if (repositoryConfig.column) {
247
+ columnOverrides[key] = repositoryConfig.column;
248
+ }
249
+ }
250
+
251
+ for (const key of [...outputKeys, ...writeKeys]) {
252
+ if (!fieldStorageByKey[key]) {
253
+ fieldStorageByKey[key] = CRUD_FIELD_REPOSITORY_STORAGE_COLUMN;
254
+ }
255
+ }
256
+
257
+ const virtualOutputKeys = [];
258
+ const columnBackedOutputKeys = [];
259
+ for (const key of outputKeys) {
260
+ const storage = fieldStorageByKey[key] || CRUD_FIELD_REPOSITORY_STORAGE_COLUMN;
261
+ if (storage === CRUD_FIELD_REPOSITORY_STORAGE_VIRTUAL) {
262
+ virtualOutputKeys.push(key);
263
+ continue;
264
+ }
265
+ columnBackedOutputKeys.push(key);
266
+ }
267
+
268
+ for (const key of virtualOutputKeys) {
269
+ if (Object.hasOwn(writeProperties, key)) {
270
+ throw new Error(
271
+ `${context} resource create schema field "${key}" cannot use repository.storage "virtual".`
272
+ );
273
+ }
274
+ if (Object.hasOwn(patchProperties, key)) {
275
+ throw new Error(
276
+ `${context} resource patch schema field "${key}" cannot use repository.storage "virtual".`
277
+ );
278
+ }
218
279
  }
219
280
 
220
281
  const listSearchColumns = [];
221
282
  for (const [key, schema] of Object.entries(outputProperties)) {
283
+ if ((fieldStorageByKey[key] || CRUD_FIELD_REPOSITORY_STORAGE_COLUMN) !== CRUD_FIELD_REPOSITORY_STORAGE_COLUMN) {
284
+ continue;
285
+ }
222
286
  if (!schemaIncludesStringType(schema)) {
223
287
  continue;
224
288
  }
@@ -232,6 +296,9 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
232
296
 
233
297
  const parentFilterColumns = {};
234
298
  for (const key of resolveCrudLookupFieldKeys(resource, { allowKeys: writeKeys })) {
299
+ if ((fieldStorageByKey[key] || CRUD_FIELD_REPOSITORY_STORAGE_COLUMN) !== CRUD_FIELD_REPOSITORY_STORAGE_COLUMN) {
300
+ continue;
301
+ }
235
302
  const columnName = resolveColumnName(key, columnOverrides);
236
303
  if (!columnName) {
237
304
  continue;
@@ -249,7 +316,10 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
249
316
  return Object.freeze({
250
317
  outputKeys,
251
318
  writeKeys,
319
+ fieldStorageByKey: Object.freeze(fieldStorageByKey),
252
320
  columnOverrides: Object.freeze(columnOverrides),
321
+ columnBackedOutputKeys: Object.freeze(columnBackedOutputKeys),
322
+ virtualOutputKeys: Object.freeze(virtualOutputKeys),
253
323
  listSearchColumns: Object.freeze(listSearchColumns),
254
324
  parentFilterColumns: Object.freeze(parentFilterColumns),
255
325
  outputRecordIdKeys: Object.freeze(outputRecordIdKeys)