@isrd-isi-edu/ermrestjs 2.0.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.
Files changed (71) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +55 -0
  3. package/dist/ermrest.d.ts +3481 -0
  4. package/dist/ermrest.js +45 -0
  5. package/dist/ermrest.js.gz +0 -0
  6. package/dist/ermrest.js.map +1 -0
  7. package/dist/ermrest.min.js +45 -0
  8. package/dist/ermrest.min.js.gz +0 -0
  9. package/dist/ermrest.min.js.map +1 -0
  10. package/dist/ermrest.ver.txt +1 -0
  11. package/dist/stats.html +4949 -0
  12. package/js/ag_reference.js +1483 -0
  13. package/js/core.js +4931 -0
  14. package/js/datapath.js +336 -0
  15. package/js/export.js +956 -0
  16. package/js/filters.js +192 -0
  17. package/js/format.js +344 -0
  18. package/js/hatrac.js +1130 -0
  19. package/js/json_ld_validator.js +285 -0
  20. package/js/parser.js +2320 -0
  21. package/js/setup/node.js +27 -0
  22. package/js/utils/helpers.js +2300 -0
  23. package/js/utils/json_ld_schema.js +680 -0
  24. package/js/utils/pseudocolumn_helpers.js +2196 -0
  25. package/package.json +79 -0
  26. package/src/index.ts +204 -0
  27. package/src/models/comment.ts +14 -0
  28. package/src/models/deferred-promise.ts +16 -0
  29. package/src/models/display-name.ts +5 -0
  30. package/src/models/errors.ts +408 -0
  31. package/src/models/path-prefix-alias-mapping.ts +130 -0
  32. package/src/models/reference/bulk-create-foreign-key-object.ts +133 -0
  33. package/src/models/reference/citation.ts +98 -0
  34. package/src/models/reference/contextualize.ts +535 -0
  35. package/src/models/reference/google-dataset-metadata.ts +72 -0
  36. package/src/models/reference/index.ts +14 -0
  37. package/src/models/reference/page.ts +520 -0
  38. package/src/models/reference/reference-aggregate-fn.ts +37 -0
  39. package/src/models/reference/reference.ts +2813 -0
  40. package/src/models/reference/related-reference.ts +467 -0
  41. package/src/models/reference/tuple.ts +652 -0
  42. package/src/models/reference-column/asset-pseudo-column.ts +498 -0
  43. package/src/models/reference-column/column-aggregate.ts +313 -0
  44. package/src/models/reference-column/facet-column.ts +1380 -0
  45. package/src/models/reference-column/foreign-key-pseudo-column.ts +626 -0
  46. package/src/models/reference-column/inbound-foreign-key-pseudo-column.ts +131 -0
  47. package/src/models/reference-column/index.ts +13 -0
  48. package/src/models/reference-column/key-pseudo-column.ts +236 -0
  49. package/src/models/reference-column/pseudo-column.ts +850 -0
  50. package/src/models/reference-column/reference-column.ts +740 -0
  51. package/src/models/source-object-node.ts +156 -0
  52. package/src/models/source-object-wrapper.ts +694 -0
  53. package/src/models/table-source-definitions.ts +98 -0
  54. package/src/services/authn.ts +43 -0
  55. package/src/services/catalog.ts +37 -0
  56. package/src/services/config.ts +202 -0
  57. package/src/services/error.ts +247 -0
  58. package/src/services/handlebars.ts +607 -0
  59. package/src/services/history.ts +136 -0
  60. package/src/services/http.ts +536 -0
  61. package/src/services/logger.ts +70 -0
  62. package/src/services/mustache.ts +0 -0
  63. package/src/utils/column-utils.ts +308 -0
  64. package/src/utils/constants.ts +526 -0
  65. package/src/utils/markdown-utils.ts +855 -0
  66. package/src/utils/reference-utils.ts +1658 -0
  67. package/src/utils/template-utils.ts +0 -0
  68. package/src/utils/type-utils.ts +89 -0
  69. package/src/utils/value-utils.ts +127 -0
  70. package/tsconfig.json +30 -0
  71. package/vite.config.mts +104 -0
@@ -0,0 +1,467 @@
1
+ // models
2
+ import { Reference, type Tuple, type VisibleColumn } from '@isrd-isi-edu/ermrestjs/src/models/reference';
3
+ import type SourceObjectWrapper from '@isrd-isi-edu/ermrestjs/src/models/source-object-wrapper';
4
+ import { parse } from '@isrd-isi-edu/ermrestjs/js/parser';
5
+ import { CommentType } from '@isrd-isi-edu/ermrestjs/src/models/comment';
6
+ import { DisplayName } from '@isrd-isi-edu/ermrestjs/src/models/display-name';
7
+ import { BatchUnlinkResponse, InvalidInputError } from '@isrd-isi-edu/ermrestjs/src/models/errors';
8
+
9
+ // utils
10
+ import { isObject, isObjectAndNotNull, verify } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
11
+ import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils';
12
+ import { _facetFilterTypes, _parserAliases } from '@isrd-isi-edu/ermrestjs/src/utils/constants';
13
+ import { _compressSource } from '@isrd-isi-edu/ermrestjs/js/utils/pseudocolumn_helpers';
14
+ import { fixedEncodeURIComponent } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
15
+
16
+ import { Catalog, ForeignKeyRef, Table } from '@isrd-isi-edu/ermrestjs/js/core';
17
+ import { Location } from '@isrd-isi-edu/ermrestjs/js/parser';
18
+ import { _isValidModelCommentDisplay, _processSourceObjectComment, generateKeyValueFilters } from '@isrd-isi-edu/ermrestjs/js/utils/helpers';
19
+
20
+ export class RelatedReference extends Reference {
21
+ private _mainTable: Table;
22
+ private _mainTuple?: Tuple;
23
+ private _origFKR: ForeignKeyRef;
24
+ private _compressedDataSource?: any;
25
+ private _derivedAssociationReference?: DerivedAssociationReference;
26
+
27
+ public _relatedKeyColumnPositions: number[];
28
+ public _relatedFkColumnPositions: number[];
29
+
30
+ constructor(
31
+ location: Location,
32
+ catalog: Catalog,
33
+ mainTable: Table,
34
+ origFKR: ForeignKeyRef,
35
+ relatedKeyColumnPositions: number[],
36
+ relatedFkColumnPositions: number[],
37
+ compressedDataSource?: any,
38
+ mainTuple?: Tuple,
39
+ displayname?: DisplayName,
40
+ comment?: CommentType,
41
+ pseudoColumn?: VisibleColumn,
42
+ ) {
43
+ super(location, catalog, displayname, comment, pseudoColumn);
44
+
45
+ this._mainTable = mainTable;
46
+ this._mainTuple = mainTuple;
47
+ this._origFKR = origFKR;
48
+
49
+ this._relatedKeyColumnPositions = relatedKeyColumnPositions;
50
+ this._relatedFkColumnPositions = relatedFkColumnPositions;
51
+
52
+ this._compressedDataSource = compressedDataSource;
53
+ }
54
+
55
+ /**
56
+ * the main table
57
+ * NOTE: the table that this reference represents is the related one, and
58
+ * it is not the same as the main table.
59
+ */
60
+ get mainTable(): Table {
61
+ return this._mainTable;
62
+ }
63
+
64
+ /**
65
+ * the main tuple
66
+ */
67
+ get mainTuple(): Tuple | undefined {
68
+ return this._mainTuple;
69
+ }
70
+
71
+ get origFKR(): ForeignKeyRef {
72
+ return this._origFKR;
73
+ }
74
+
75
+ get compressedDataSource(): any {
76
+ return this._compressedDataSource;
77
+ }
78
+
79
+ set derivedAssociationReference(ref: DerivedAssociationReference) {
80
+ this._derivedAssociationReference = ref;
81
+ }
82
+
83
+ get derivedAssociationReference(): DerivedAssociationReference | undefined {
84
+ return this._derivedAssociationReference;
85
+ }
86
+
87
+ /**
88
+ * create a new instance with the same properties.
89
+ *
90
+ * you can customized the properties of the new instance by passing new ones to this function.
91
+ * You must pass undefined for other props that you don't want to change.
92
+ */
93
+ copy(displayname?: DisplayName, comment?: CommentType, pseudoColumn?: VisibleColumn): RelatedReference {
94
+ const res = new RelatedReference(
95
+ this.location,
96
+ this.location.catalogObject,
97
+ this._mainTable,
98
+ this._origFKR,
99
+ this._relatedKeyColumnPositions,
100
+ this._relatedFkColumnPositions,
101
+ this._compressedDataSource,
102
+ this._mainTuple,
103
+ typeof displayname !== 'undefined' ? displayname : this._displayname,
104
+ typeof comment !== 'undefined' ? comment : this._comment,
105
+ typeof pseudoColumn !== 'undefined' ? pseudoColumn : this._pseudoColumn,
106
+ );
107
+
108
+ if (this.derivedAssociationReference) {
109
+ res.derivedAssociationReference = this.derivedAssociationReference;
110
+ }
111
+
112
+ res.setContext(this.context);
113
+ return res;
114
+ }
115
+
116
+ /**
117
+ * If the current reference is derived from an association related table and filtered, this
118
+ * function will delete the set of tuples included and return a set of success responses and
119
+ * a set of errors for the corresponding delete actions for the provided entity set from the
120
+ * corresponding association table denoted by the list of tuples.
121
+ *
122
+ * For example, assume
123
+ * Table1(K1,C1) <- AssociationTable(FK1, FK2) -> Table2(K2,C2)
124
+ * and the current tuples are from Table2 with k2 = "2" and k2 = "3".
125
+ * With origFKRData = {"k1": "1"} this function will return a set of success and error responses for
126
+ * delete requests to AssociationTable with FK1 = "1" as a part of the path and FK2 = "2" and FK2 = "3"
127
+ * as the filters that define the set and how they are related to Table1.
128
+ *
129
+ * To make sure a deletion occurs only for the tuples specified, we need to verify each reference path that
130
+ * is created includes a parent constraint and has one or more filters based on the other side of the association
131
+ * table's uniqueness constraint. Some more information about the validations that need to occur based on the above example:
132
+ * - parent value has to be not null
133
+ * - FK1 has to have a not null constraint
134
+ * - child values have to have at least 1 value and all not null
135
+ * - for FK2, all selected values are not null
136
+ *
137
+ * @param {Array} mainTuple - an ERMrest.Tuple from Table1 (from example above)
138
+ * @param {Array} tuples - an array of ERMrest.Tuple objects from Table2 (same as self) (from example above)
139
+ * @param {Object} contextHeaderParams the object that we want to log.
140
+ *
141
+ * @returns {Object} an ERMrest.BatchUnlinkResponse "error" object
142
+ **/
143
+ deleteBatchAssociationTuples(
144
+ parentTuple: Tuple,
145
+ tuples: Array<Tuple>,
146
+ contextHeaderParams?: Record<string, unknown>,
147
+ ): Promise<BatchUnlinkResponse> {
148
+ return new Promise((resolve, reject) => {
149
+ try {
150
+ verify(parentTuple, '`parentTuple` must be specified');
151
+ verify(tuples, '`tuples` must be specified');
152
+ verify(tuples.length > 0, '`tuples` must have at least one row to delete');
153
+ // Can occur using an unfiltered reference
154
+ verify(this.derivedAssociationReference, 'The current reference must have a derived association reference defined');
155
+
156
+ const encode = fixedEncodeURIComponent;
157
+ if (!contextHeaderParams || !isObject(contextHeaderParams)) {
158
+ contextHeaderParams = { action: 'delete' };
159
+ }
160
+ const config = {
161
+ headers: this._generateContextHeader(contextHeaderParams),
162
+ };
163
+
164
+ let successTupleData: Array<Record<string, unknown>> = [];
165
+ let failedTupleData: Array<Record<string, unknown>> = [];
166
+ const deleteSubmessage: Array<string> = [];
167
+ const associationRef = this.derivedAssociationReference!;
168
+ const mainKeyCol = associationRef.origFKR.colset.columns[0];
169
+
170
+ // the path starting from association table, with filters based on the fk to the main table
171
+ let compactPath = encode(associationRef.table.schema.name) + ':' + encode(associationRef.table.name) + '/';
172
+ compactPath += encode(mainKeyCol.name) + '=' + encode(parentTuple.data[associationRef.origFKR.mapping.get(mainKeyCol).name]) + '&';
173
+
174
+ // keyColumns should be a set of columns that are unique and not-null
175
+ // columns tells us what the key column names are in the fkr "_to" relationship
176
+ const keyColumns = associationRef.associationToRelatedFKR.colset.columns;
177
+ // mapping tells us what the column name is on the leaf tuple, so we know what data to fetch from each tuple for identifying
178
+ const mapping = associationRef.associationToRelatedFKR.mapping;
179
+
180
+ // add the filters based on the fk to the related table
181
+ const keyFromAssocToRelatedData = tuples.map((t) => {
182
+ const res: Record<string, unknown> = {};
183
+ keyColumns.forEach((col) => {
184
+ res[col.name] = t.data[mapping.get(col).name];
185
+ });
186
+ return res;
187
+ });
188
+ const keyValueRes = generateKeyValueFilters(
189
+ keyColumns,
190
+ keyFromAssocToRelatedData,
191
+ associationRef.table.schema.catalog,
192
+ compactPath.length + 1, // 1 is for added `/` between filter and compactPath
193
+ associationRef.displayname.value as string,
194
+ );
195
+ if (!keyValueRes.successful && keyValueRes.message) {
196
+ reject(new InvalidInputError(keyValueRes.message));
197
+ return;
198
+ }
199
+
200
+ // send the requests one at a time
201
+ const recursiveDelete = (index: number) => {
202
+ const currFilter = keyValueRes.filters![index];
203
+ const url = [associationRef.location.service, 'catalog', associationRef.location.catalog, 'entity', compactPath + currFilter.path].join(
204
+ '/',
205
+ );
206
+
207
+ this.server.http
208
+ .delete(url, config)
209
+ .then(() => {
210
+ successTupleData = successTupleData.concat(currFilter.keyData);
211
+ })
212
+ .catch((err: any) => {
213
+ failedTupleData = failedTupleData.concat(currFilter.keyData);
214
+ deleteSubmessage.push(err.data);
215
+ })
216
+ .finally(() => {
217
+ if (index < keyValueRes.filters!.length - 1) {
218
+ recursiveDelete(index + 1);
219
+ } else {
220
+ resolve(new BatchUnlinkResponse(successTupleData, failedTupleData, deleteSubmessage.join('\n')));
221
+ }
222
+ });
223
+ };
224
+
225
+ recursiveDelete(0);
226
+ } catch (e) {
227
+ reject(e);
228
+ }
229
+ });
230
+ }
231
+ }
232
+
233
+ export class DerivedAssociationReference extends Reference {
234
+ private _origFKR: ForeignKeyRef;
235
+ private _associationToRelatedFKR: ForeignKeyRef;
236
+
237
+ constructor(location: Location, catalog: Catalog, origFKR: ForeignKeyRef, associationToRelatedFKR: ForeignKeyRef) {
238
+ super(location, catalog);
239
+
240
+ this._associationToRelatedFKR = associationToRelatedFKR;
241
+ this._origFKR = origFKR;
242
+ }
243
+ /**
244
+ * the main tuple
245
+ */
246
+ get associationToRelatedFKR(): ForeignKeyRef {
247
+ return this._associationToRelatedFKR;
248
+ }
249
+
250
+ get origFKR(): ForeignKeyRef {
251
+ return this._origFKR;
252
+ }
253
+
254
+ copy(): DerivedAssociationReference {
255
+ const ref = new DerivedAssociationReference(this.location, this.location.catalogObject, this._origFKR, this._associationToRelatedFKR);
256
+ ref.setContext(this.context);
257
+ return ref;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * given a reference, the first inbound fk, and/or the sourceObjectWrapper summarizing the path, return the related reference.
263
+ * @param mainRef the main reference
264
+ * @param fkr the inbound fk
265
+ * @param mainTuple the main tuple for the main reference
266
+ * @param checkForAssociation whether we should check for association
267
+ * @param sourceObjectWrapper the sourceObjectWrapper summarizing the path
268
+ */
269
+ export const generateRelatedReference = (
270
+ mainRef: Reference,
271
+ fkr: ForeignKeyRef,
272
+ mainTuple?: Tuple,
273
+ checkForAssociation?: boolean,
274
+ sourceObjectWrapper?: SourceObjectWrapper,
275
+ ): RelatedReference => {
276
+ const useFaceting = isObjectAndNotNull(mainTuple);
277
+
278
+ let location, displayname, relatedKeyColumnPositions, relatedFkColumnPositions, derivedAssociationReference;
279
+ let comment, commentDisplayMode, commentRenderMarkdown, tableDisplay, fkDisplay;
280
+ const filterSource: any[] = [];
281
+ const catalog = mainRef.table.schema.catalog;
282
+ let dataSource: any = [{ inbound: fkr.constraint_names[0] }];
283
+ const fkrTable = fkr.colset.columns[0].table;
284
+ let relatedTable;
285
+
286
+ if (checkForAssociation && fkrTable.isPureBinaryAssociation) {
287
+ // find the other foreignkey
288
+ const pureBinaryFKs = fkrTable.pureBinaryForeignKeys;
289
+ let otherFK: ForeignKeyRef;
290
+ for (let j = 0; j < pureBinaryFKs.length; j++) {
291
+ if (pureBinaryFKs[j] !== fkr) {
292
+ otherFK = pureBinaryFKs[j];
293
+ break;
294
+ }
295
+ }
296
+
297
+ relatedTable = otherFK!.key.table;
298
+ const assocTable = otherFK!.colset.columns[0].table;
299
+
300
+ // all the display settings must come from the same table (assoc table)
301
+ // so if we get the comment from assoc table, displayname must also be from assoc table
302
+ fkDisplay = otherFK!.getDisplay(mainRef.context);
303
+ tableDisplay = assocTable.getDisplay(mainRef.context);
304
+
305
+ // displayname
306
+ if (fkDisplay.toName) {
307
+ displayname = { isHTML: false, value: fkDisplay.toName, unformatted: fkDisplay.toName };
308
+ } else {
309
+ displayname = assocTable.displayname;
310
+ }
311
+
312
+ // comment
313
+ if (fkDisplay.toComment) {
314
+ comment = fkDisplay.toComment.unformatted;
315
+ } else {
316
+ comment = tableDisplay.comment ? tableDisplay.comment.unformatted : null;
317
+ }
318
+ if (_isValidModelCommentDisplay(fkDisplay.toCommentDisplayMode)) {
319
+ commentDisplayMode = fkDisplay.toCommentDisplayMode;
320
+ } else {
321
+ commentDisplayMode = tableDisplay.tableCommentDisplayMode;
322
+ }
323
+ if (typeof fkDisplay.commentRenderMarkdown === 'boolean') {
324
+ commentRenderMarkdown = fkDisplay.commentRenderMarkdown;
325
+ } else {
326
+ commentRenderMarkdown = tableDisplay.commentRenderMarkdown;
327
+ }
328
+
329
+ // uri and location
330
+ if (!useFaceting) {
331
+ location = parse(mainRef.location.compactUri + '/' + fkr.toString() + '/' + otherFK!.toString(true), catalog);
332
+ // constructor will take care of this
333
+ // location.referenceObject = newRef;
334
+ }
335
+
336
+ // additional values for sorting related references
337
+ relatedKeyColumnPositions = fkr.key.colset._getColumnPositions();
338
+ relatedFkColumnPositions = otherFK!.colset._getColumnPositions();
339
+
340
+ // will be used to determine whether this related reference is derived from association relation or not
341
+ derivedAssociationReference = new DerivedAssociationReference(parse(mainRef.location.compactUri + '/' + fkr.toString()), catalog, fkr, otherFK!);
342
+
343
+ // build the filter source (the alias is used in the read function to get the proper acls)
344
+ filterSource.push({ inbound: otherFK!.constraint_names[0], alias: _parserAliases.ASSOCIATION_TABLE });
345
+
346
+ // build the data source
347
+ dataSource.push({ outbound: otherFK!.constraint_names[0] });
348
+ } else {
349
+ relatedTable = fkrTable;
350
+ fkDisplay = fkr.getDisplay(mainRef.context);
351
+ tableDisplay = relatedTable.getDisplay(mainRef.context);
352
+
353
+ // displayname
354
+ if (fkDisplay.fromName) {
355
+ displayname = { isHTML: false, value: fkDisplay.fromName, unformatted: fkDisplay.fromName };
356
+ } else {
357
+ displayname = relatedTable.displayname;
358
+ }
359
+
360
+ // comment
361
+ if (fkDisplay.fromComment) {
362
+ comment = fkDisplay.fromComment.unformatted;
363
+ } else {
364
+ comment = tableDisplay.comment ? tableDisplay.comment.unformatted : null;
365
+ }
366
+ if (_isValidModelCommentDisplay(fkDisplay.fromCommentDisplayMode)) {
367
+ commentDisplayMode = fkDisplay.fromCommentDisplayMode;
368
+ } else {
369
+ commentDisplayMode = tableDisplay.tableCommentDisplayMode;
370
+ }
371
+ if (typeof fkDisplay.commentRenderMarkdown === 'boolean') {
372
+ commentRenderMarkdown = fkDisplay.commentRenderMarkdown;
373
+ } else {
374
+ commentRenderMarkdown = tableDisplay.commentRenderMarkdown;
375
+ }
376
+
377
+ // uri and location
378
+ if (!useFaceting) {
379
+ location = parse(mainRef.location.compactUri + '/' + fkr.toString(), catalog);
380
+ // constructor will take care of this
381
+ // newRef._location.referenceObject = newRef;
382
+ }
383
+
384
+ // additional values for sorting related references
385
+ relatedKeyColumnPositions = fkr.key.colset._getColumnPositions();
386
+ relatedFkColumnPositions = fkr.colset._getColumnPositions();
387
+ }
388
+
389
+ let sourceObject;
390
+ if (sourceObjectWrapper) {
391
+ sourceObject = sourceObjectWrapper.sourceObject;
392
+ }
393
+
394
+ // if markdown_name in source object is defined
395
+ if (sourceObject && typeof sourceObject.markdown_name === 'string') {
396
+ displayname = {
397
+ value: renderMarkdown(sourceObject.markdown_name, true),
398
+ unformatted: sourceObject.markdown_name,
399
+ isHTML: true,
400
+ };
401
+ }
402
+
403
+ if (sourceObject && sourceObject.source) {
404
+ dataSource = sourceObject.source;
405
+ } else if (relatedTable.shortestKey.length === 1) {
406
+ dataSource = dataSource.concat(relatedTable.shortestKey[0].name);
407
+ }
408
+
409
+ // complete the path
410
+ filterSource.push({ outbound: fkr.constraint_names[0] });
411
+
412
+ if (useFaceting) {
413
+ let facets;
414
+ location = parse(
415
+ [
416
+ catalog.server.uri,
417
+ 'catalog',
418
+ catalog.id,
419
+ 'entity',
420
+ fixedEncodeURIComponent(relatedTable.schema.name) + ':' + fixedEncodeURIComponent(relatedTable.name),
421
+ ].join('/'),
422
+ catalog,
423
+ );
424
+
425
+ // if the sourceObjectWrapper is passed, filter source is reverse of that.
426
+ // NOTE the related table might have filters, that's why we have to do this and cannot
427
+ // just rely on the structure
428
+ if (isObjectAndNotNull(sourceObjectWrapper)) {
429
+ const addAlias = checkForAssociation && fkrTable.isPureBinaryAssociation;
430
+ facets = sourceObjectWrapper!.getReverseAsFacet(mainTuple!, fkr.key.table, addAlias ? _parserAliases.ASSOCIATION_TABLE : '');
431
+ } else {
432
+ //filters
433
+ const filters: any[] = [];
434
+ fkr.key.table.shortestKey.forEach(function (col) {
435
+ const filter: any = {
436
+ source: filterSource.concat(col.name),
437
+ };
438
+ filter[_facetFilterTypes.CHOICE] = [mainTuple!.data[col.name]];
439
+ filters.push(filter);
440
+ });
441
+
442
+ facets = { and: filters };
443
+ }
444
+
445
+ // the facets are basd on the value of shortest key of current table
446
+ location.facets = facets;
447
+ }
448
+
449
+ const newRef = new RelatedReference(
450
+ location!,
451
+ catalog,
452
+ mainRef.table,
453
+ fkr,
454
+ relatedKeyColumnPositions,
455
+ relatedFkColumnPositions,
456
+ _compressSource(dataSource),
457
+ mainTuple,
458
+ displayname,
459
+ _processSourceObjectComment(sourceObject, comment, commentRenderMarkdown, commentDisplayMode),
460
+ );
461
+
462
+ if (derivedAssociationReference) {
463
+ newRef.derivedAssociationReference = derivedAssociationReference;
464
+ }
465
+
466
+ return newRef;
467
+ };