@medusajs/index 3.0.0-snapshot-20250410112222 → 3.0.0-snapshot-20251104004624

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 (93) hide show
  1. package/dist/loaders/index.d.ts.map +1 -1
  2. package/dist/loaders/index.js +1 -1
  3. package/dist/loaders/index.js.map +1 -1
  4. package/dist/migrations/Migration20231019174230.d.ts +1 -1
  5. package/dist/migrations/Migration20231019174230.d.ts.map +1 -1
  6. package/dist/migrations/Migration20231019174230.js +3 -3
  7. package/dist/migrations/Migration20231019174230.js.map +1 -1
  8. package/dist/migrations/Migration20241209173313.d.ts +1 -1
  9. package/dist/migrations/Migration20241209173313.d.ts.map +1 -1
  10. package/dist/migrations/Migration20241209173313.js +1 -1
  11. package/dist/migrations/Migration20241209173313.js.map +1 -1
  12. package/dist/migrations/Migration20250122154720.d.ts +1 -1
  13. package/dist/migrations/Migration20250122154720.d.ts.map +1 -1
  14. package/dist/migrations/Migration20250122154720.js +1 -1
  15. package/dist/migrations/Migration20250122154720.js.map +1 -1
  16. package/dist/migrations/Migration20250127105159.d.ts +1 -1
  17. package/dist/migrations/Migration20250127105159.d.ts.map +1 -1
  18. package/dist/migrations/Migration20250127105159.js +1 -1
  19. package/dist/migrations/Migration20250127105159.js.map +1 -1
  20. package/dist/migrations/Migration20250127144442.d.ts +1 -1
  21. package/dist/migrations/Migration20250127144442.d.ts.map +1 -1
  22. package/dist/migrations/Migration20250127144442.js +1 -1
  23. package/dist/migrations/Migration20250127144442.js.map +1 -1
  24. package/dist/migrations/Migration20250128132404.d.ts +1 -1
  25. package/dist/migrations/Migration20250128132404.d.ts.map +1 -1
  26. package/dist/migrations/Migration20250128132404.js +1 -1
  27. package/dist/migrations/Migration20250128132404.js.map +1 -1
  28. package/dist/migrations/Migration20250218132404.d.ts +1 -1
  29. package/dist/migrations/Migration20250218132404.d.ts.map +1 -1
  30. package/dist/migrations/Migration20250218132404.js +1 -1
  31. package/dist/migrations/Migration20250218132404.js.map +1 -1
  32. package/dist/migrations/Migration20250515161913.d.ts +6 -0
  33. package/dist/migrations/Migration20250515161913.d.ts.map +1 -0
  34. package/dist/migrations/Migration20250515161913.js +62 -0
  35. package/dist/migrations/Migration20250515161913.js.map +1 -0
  36. package/dist/services/data-synchronizer.d.ts.map +1 -1
  37. package/dist/services/data-synchronizer.js +8 -7
  38. package/dist/services/data-synchronizer.js.map +1 -1
  39. package/dist/services/index-data.d.ts +4 -1
  40. package/dist/services/index-data.d.ts.map +1 -1
  41. package/dist/services/index-metadata.d.ts +4 -1
  42. package/dist/services/index-metadata.d.ts.map +1 -1
  43. package/dist/services/index-module-service.d.ts +37 -2
  44. package/dist/services/index-module-service.d.ts.map +1 -1
  45. package/dist/services/index-module-service.js +261 -29
  46. package/dist/services/index-module-service.js.map +1 -1
  47. package/dist/services/index-relation.d.ts +4 -1
  48. package/dist/services/index-relation.d.ts.map +1 -1
  49. package/dist/services/index-sync.d.ts +4 -1
  50. package/dist/services/index-sync.d.ts.map +1 -1
  51. package/dist/services/postgres-provider.d.ts +1 -1
  52. package/dist/services/postgres-provider.d.ts.map +1 -1
  53. package/dist/services/postgres-provider.js +14 -61
  54. package/dist/services/postgres-provider.js.map +1 -1
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/dist/types/index.d.ts +0 -1
  57. package/dist/types/index.d.ts.map +1 -1
  58. package/dist/utils/base-graphql-schema.d.ts +2 -0
  59. package/dist/utils/base-graphql-schema.d.ts.map +1 -0
  60. package/dist/utils/base-graphql-schema.js +10 -0
  61. package/dist/utils/base-graphql-schema.js.map +1 -0
  62. package/dist/utils/build-config.d.ts +5 -1
  63. package/dist/utils/build-config.d.ts.map +1 -1
  64. package/dist/utils/build-config.js +488 -142
  65. package/dist/utils/build-config.js.map +1 -1
  66. package/dist/utils/create-partitions.d.ts +1 -1
  67. package/dist/utils/create-partitions.d.ts.map +1 -1
  68. package/dist/utils/create-partitions.js +33 -12
  69. package/dist/utils/create-partitions.js.map +1 -1
  70. package/dist/utils/default-schema.js +6 -6
  71. package/dist/utils/gql-to-types.d.ts +2 -1
  72. package/dist/utils/gql-to-types.d.ts.map +1 -1
  73. package/dist/utils/gql-to-types.js +19 -12
  74. package/dist/utils/gql-to-types.js.map +1 -1
  75. package/dist/utils/index.d.ts +1 -0
  76. package/dist/utils/index.d.ts.map +1 -1
  77. package/dist/utils/index.js +1 -0
  78. package/dist/utils/index.js.map +1 -1
  79. package/dist/utils/normalze-table-name.d.ts +3 -0
  80. package/dist/utils/normalze-table-name.d.ts.map +1 -0
  81. package/dist/utils/normalze-table-name.js +13 -0
  82. package/dist/utils/normalze-table-name.js.map +1 -0
  83. package/dist/utils/query-builder.d.ts +11 -12
  84. package/dist/utils/query-builder.d.ts.map +1 -1
  85. package/dist/utils/query-builder.js +335 -230
  86. package/dist/utils/query-builder.js.map +1 -1
  87. package/dist/utils/sync/configuration.js +17 -15
  88. package/dist/utils/sync/configuration.js.map +1 -1
  89. package/dist/utils/sync/orchestrator.d.ts +3 -2
  90. package/dist/utils/sync/orchestrator.d.ts.map +1 -1
  91. package/dist/utils/sync/orchestrator.js +19 -7
  92. package/dist/utils/sync/orchestrator.js.map +1 -1
  93. package/package.json +14 -30
@@ -8,6 +8,47 @@ var _QueryBuilder_searchVectorColumnName;
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.QueryBuilder = exports.OPERATOR_MAP = void 0;
10
10
  const utils_1 = require("@medusajs/framework/utils");
11
+ const normalze_table_name_1 = require("./normalze-table-name");
12
+ const AND_OPERATOR = "$and";
13
+ const OR_OPERATOR = "$or";
14
+ const NOT_OPERATOR = "$not";
15
+ function escapeJsonPathString(val) {
16
+ // Escape for JSONPath string
17
+ return val.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'");
18
+ }
19
+ function buildSafeJsonPathQuery(field, operator, value) {
20
+ let jsonPathOperator = operator;
21
+ let caseInsensitiveFlag = "";
22
+ if (operator === "=") {
23
+ jsonPathOperator = "==";
24
+ }
25
+ else if (operator.toUpperCase().includes("LIKE")) {
26
+ jsonPathOperator = "like_regex";
27
+ if (operator.toUpperCase() === "ILIKE") {
28
+ caseInsensitiveFlag = ' flag "i"';
29
+ }
30
+ }
31
+ else if (operator === "IS") {
32
+ jsonPathOperator = "==";
33
+ }
34
+ else if (operator === "IS NOT") {
35
+ jsonPathOperator = "!=";
36
+ }
37
+ if (typeof value === "string") {
38
+ let val = value;
39
+ if (jsonPathOperator === "like_regex") {
40
+ // Convert SQL LIKE wildcards to regex
41
+ val = val.replace(/%/g, ".*").replace(/_/g, ".");
42
+ }
43
+ value = `"${escapeJsonPathString(val)}"`;
44
+ }
45
+ else {
46
+ if ((operator === "IS" || operator === "IS NOT") && value === null) {
47
+ value = "null";
48
+ }
49
+ }
50
+ return `$.${field} ${jsonPathOperator} ${value}${caseInsensitiveFlag}`;
51
+ }
11
52
  exports.OPERATOR_MAP = {
12
53
  $eq: "=",
13
54
  $lt: "<",
@@ -16,6 +57,7 @@ exports.OPERATOR_MAP = {
16
57
  $gte: ">=",
17
58
  $ne: "!=",
18
59
  $in: "IN",
60
+ $nin: "NOT IN",
19
61
  $is: "IS",
20
62
  $like: "LIKE",
21
63
  $ilike: "ILIKE",
@@ -32,9 +74,29 @@ class QueryBuilder {
32
74
  this.allSchemaFields = new Set(Object.values(this.schema).flatMap((entity) => entity.fields ?? []));
33
75
  this.rawConfig = args.rawConfig;
34
76
  this.requestedFields = args.requestedFields;
77
+ this.idsOnly = args.idsOnly ?? false;
78
+ }
79
+ isLogicalOperator(key) {
80
+ return key === AND_OPERATOR || key === OR_OPERATOR || key === NOT_OPERATOR;
35
81
  }
36
82
  getStructureKeys(structure) {
37
- return Object.keys(structure ?? {}).filter((key) => key !== "entity");
83
+ const collectKeys = (obj, keys = new Set()) => {
84
+ if (!(0, utils_1.isObject)(obj)) {
85
+ return keys;
86
+ }
87
+ Object.keys(obj).forEach((key) => {
88
+ if (this.isLogicalOperator(key)) {
89
+ if (Array.isArray(obj[key])) {
90
+ obj[key].forEach((item) => collectKeys(item, keys));
91
+ }
92
+ }
93
+ else if (key !== "entity") {
94
+ keys.add(key);
95
+ }
96
+ });
97
+ return keys;
98
+ };
99
+ return [...collectKeys(structure ?? {})];
38
100
  }
39
101
  getEntity(path, throwWhenNotFound = true) {
40
102
  if (!this.schema._schemaPropertiesMap[path]) {
@@ -46,20 +108,18 @@ class QueryBuilder {
46
108
  return this.schema._schemaPropertiesMap[path];
47
109
  }
48
110
  getGraphQLType(path, field) {
111
+ if (this.isLogicalOperator(field)) {
112
+ return "JSON";
113
+ }
49
114
  const entity = this.getEntity(path)?.ref?.entity;
50
115
  const fieldRef = this.entityMap[entity]._fields[field];
51
116
  if (!fieldRef) {
52
117
  throw new Error(`Field ${field} is not indexed.`);
53
118
  }
54
- let currentType = fieldRef.type;
55
- let isArray = false;
56
- while (currentType.ofType) {
57
- if (currentType instanceof utils_1.GraphQLUtils.GraphQLList) {
58
- isArray = true;
59
- }
60
- currentType = currentType.ofType;
61
- }
62
- return currentType.name + (isArray ? "[]" : "");
119
+ const fieldType = fieldRef.type.toString();
120
+ const isArray = fieldType.startsWith("[");
121
+ const currentType = fieldType.replace(/\[|\]|\!/g, "");
122
+ return currentType + (isArray ? "[]" : "");
63
123
  }
64
124
  transformValueToType(path, field, value) {
65
125
  if (value === null) {
@@ -120,10 +180,11 @@ class QueryBuilder {
120
180
  coalesce: generateCoalesceExpression,
121
181
  };
122
182
  }
123
- parseWhere(aliasMapping, obj, builder) {
183
+ parseWhere(aliasMapping, obj, builder, parentPath = "") {
124
184
  const keys = Object.keys(obj);
125
185
  const getPathAndField = (key) => {
126
- const path = key.split(".");
186
+ const fullKey = parentPath ? `${parentPath}.${key}` : key;
187
+ const path = fullKey.split(".");
127
188
  const field = [path.pop()];
128
189
  while (!aliasMapping[path.join(".")] && path.length > 0) {
129
190
  field.unshift(path.pop());
@@ -140,52 +201,125 @@ class QueryBuilder {
140
201
  return JSON.stringify(result);
141
202
  };
142
203
  keys.forEach((key) => {
204
+ const pathAsArray = (parentPath ? `${parentPath}.${key}` : key).split(".");
205
+ const fieldOrLogicalOperator = pathAsArray.pop();
143
206
  let value = obj[key];
144
- if ((key === "$and" || key === "$or") && !Array.isArray(value)) {
207
+ if (this.isLogicalOperator(fieldOrLogicalOperator) &&
208
+ !Array.isArray(value)) {
145
209
  value = [value];
146
210
  }
147
- if (key === "$and" && Array.isArray(value)) {
211
+ if (fieldOrLogicalOperator === AND_OPERATOR && Array.isArray(value)) {
148
212
  builder.where((qb) => {
149
213
  value.forEach((cond) => {
150
- qb.andWhere((subBuilder) => this.parseWhere(aliasMapping, cond, subBuilder));
214
+ qb.andWhere((subBuilder) => this.parseWhere(aliasMapping, cond, subBuilder, pathAsArray.join(".")));
151
215
  });
152
216
  });
153
217
  }
154
- else if (key === "$or" && Array.isArray(value)) {
218
+ else if (fieldOrLogicalOperator === OR_OPERATOR &&
219
+ Array.isArray(value)) {
155
220
  builder.where((qb) => {
156
221
  value.forEach((cond) => {
157
- qb.orWhere((subBuilder) => this.parseWhere(aliasMapping, cond, subBuilder));
222
+ qb.orWhere((subBuilder) => this.parseWhere(aliasMapping, cond, subBuilder, pathAsArray.join(".")));
158
223
  });
159
224
  });
160
225
  }
161
- else if ((0, utils_1.isObject)(value) && !Array.isArray(value)) {
162
- const subKeys = Object.keys(value);
163
- subKeys.forEach((subKey) => {
164
- let operator = exports.OPERATOR_MAP[subKey];
165
- if (operator) {
166
- const { field, attr } = getPathAndField(key);
167
- const nested = new Array(field.length).join("->?");
168
- const subValue = this.transformValueToType(attr, field, value[subKey]);
169
- const castType = this.getPostgresCastType(attr, [field]).cast;
170
- const val = operator === "IN" ? subValue : [subValue];
171
- if (operator === "=" && subValue === null) {
172
- operator = "IS";
173
- }
174
- else if (operator === "!=" && subValue === null) {
175
- operator = "IS NOT";
176
- }
177
- if (operator === "=") {
178
- builder.whereRaw(`${aliasMapping[attr]}.data @> '${getPathOperation(attr, field, subValue)}'::jsonb`);
179
- }
180
- else {
181
- builder.whereRaw(`(${aliasMapping[attr]}.data${nested}->>?)${castType} ${operator} ?`, [...field, ...val]);
182
- }
226
+ else if (fieldOrLogicalOperator === NOT_OPERATOR &&
227
+ (Array.isArray(value) || (0, utils_1.isObject)(value))) {
228
+ builder.whereNot((qb) => {
229
+ if (Array.isArray(value)) {
230
+ value.forEach((cond) => {
231
+ qb.andWhere((subBuilder) => this.parseWhere(aliasMapping, cond, subBuilder, pathAsArray.join(".")));
232
+ });
183
233
  }
184
234
  else {
185
- throw new Error(`Unsupported operator: ${subKey}`);
235
+ this.parseWhere(aliasMapping, value, qb, pathAsArray.join("."));
186
236
  }
187
237
  });
188
238
  }
239
+ else if ((0, utils_1.isObject)(value) &&
240
+ !Array.isArray(value) &&
241
+ !this.isLogicalOperator(fieldOrLogicalOperator)) {
242
+ const currentPath = parentPath ? `${parentPath}.${key}` : key;
243
+ const subKeys = Object.keys(value);
244
+ const hasOperators = subKeys.some((subKey) => exports.OPERATOR_MAP[subKey]);
245
+ if (hasOperators) {
246
+ const { field, attr } = getPathAndField(key);
247
+ const subKeys = Object.keys(value);
248
+ subKeys.forEach((subKey) => {
249
+ let operator = exports.OPERATOR_MAP[subKey];
250
+ if (operator) {
251
+ const nested = new Array(field.length).join("->?");
252
+ const subValue = this.transformValueToType(attr, field, value[subKey]);
253
+ let val = operator === "IN" || operator === "NOT IN"
254
+ ? subValue
255
+ : [subValue];
256
+ if (operator === "=" && subValue === null) {
257
+ operator = "IS";
258
+ }
259
+ else if (operator === "!=" && subValue === null) {
260
+ operator = "IS NOT";
261
+ }
262
+ if (operator === "=") {
263
+ const hasId = field[field.length - 1] === "id";
264
+ if (hasId) {
265
+ builder.whereRaw(`${aliasMapping[attr]}.id = ?`, subValue);
266
+ }
267
+ else {
268
+ builder.whereRaw(`${aliasMapping[attr]}.data @> '${getPathOperation(attr, field, subValue)}'::jsonb`);
269
+ }
270
+ }
271
+ else if (operator === "IN" || operator === "NOT IN") {
272
+ if (val && !Array.isArray(val)) {
273
+ val = [val];
274
+ }
275
+ if (!val || val.length === 0) {
276
+ return;
277
+ }
278
+ const inPlaceholders = val.map(() => "?").join(",");
279
+ const hasId = field[field.length - 1] === "id";
280
+ const isNegated = operator === "NOT IN";
281
+ if (hasId) {
282
+ builder.whereRaw(`${aliasMapping[attr]}.id ${isNegated ? "NOT IN" : "IN"} (${inPlaceholders})`, val);
283
+ }
284
+ else {
285
+ const targetField = field[field.length - 1];
286
+ const jsonbValues = val.map((item) => JSON.stringify({
287
+ [targetField]: item === null ? null : item,
288
+ }));
289
+ if (isNegated) {
290
+ builder.whereRaw(`NOT EXISTS (SELECT 1 FROM unnest(ARRAY[${inPlaceholders}]::JSONB[]) AS v(val) WHERE ${aliasMapping[attr]}.data${nested} @> v.val)`, jsonbValues);
291
+ }
292
+ else {
293
+ builder.whereRaw(`${aliasMapping[attr]}.data${nested} @> ANY(ARRAY[${inPlaceholders}]::JSONB[])`, jsonbValues);
294
+ }
295
+ }
296
+ }
297
+ else {
298
+ const potentialIdFields = field[field.length - 1];
299
+ const hasId = potentialIdFields === "id";
300
+ if (hasId) {
301
+ builder.whereRaw(`(${aliasMapping[attr]}.id) ${operator} ?`, [
302
+ ...val,
303
+ ]);
304
+ }
305
+ else {
306
+ const targetField = field[field.length - 1];
307
+ const jsonPath = buildSafeJsonPathQuery(targetField, operator, val[0]);
308
+ builder.whereRaw(`${aliasMapping[attr]}.data${nested} @@ ?`, [
309
+ jsonPath,
310
+ ]);
311
+ }
312
+ }
313
+ }
314
+ else {
315
+ throw new Error(`Unsupported operator: ${subKey}`);
316
+ }
317
+ });
318
+ }
319
+ else {
320
+ this.parseWhere(aliasMapping, value, builder, currentPath);
321
+ }
322
+ }
189
323
  else {
190
324
  const { field, attr } = getPathAndField(key);
191
325
  const nested = new Array(field.length).join("->?");
@@ -194,49 +328,76 @@ class QueryBuilder {
194
328
  if (value.length === 0) {
195
329
  return;
196
330
  }
197
- const castType = this.getPostgresCastType(attr, field).cast;
198
331
  const inPlaceholders = value.map(() => "?").join(",");
199
- builder.whereRaw(`(${aliasMapping[attr]}.data${nested}->>?)${castType} IN (${inPlaceholders})`, [...field, ...value]);
332
+ const hasId = field[field.length - 1] === "id";
333
+ if (hasId) {
334
+ builder.whereRaw(`${aliasMapping[attr]}.id IN (${inPlaceholders})`, [...value]);
335
+ }
336
+ else {
337
+ const targetField = field[field.length - 1];
338
+ const jsonbValues = value.map((item) => JSON.stringify({ [targetField]: item === null ? null : item }));
339
+ builder.whereRaw(`${aliasMapping[attr]}.data${nested} @> ANY(ARRAY[${inPlaceholders}]::JSONB[])`, jsonbValues);
340
+ }
200
341
  }
201
342
  else if ((0, utils_1.isDefined)(value)) {
202
- const operator = value === null ? "IS" : "=";
343
+ let operator = "=";
203
344
  if (operator === "=") {
204
- builder.whereRaw(`${aliasMapping[attr]}.data @> '${getPathOperation(attr, field, value)}'::jsonb`);
345
+ const hasId = field[field.length - 1] === "id";
346
+ if (hasId) {
347
+ builder.whereRaw(`${aliasMapping[attr]}.id = ?`, value);
348
+ }
349
+ else {
350
+ builder.whereRaw(`${aliasMapping[attr]}.data @> '${getPathOperation(attr, field, value)}'::jsonb`);
351
+ }
205
352
  }
206
353
  else {
207
- const castType = this.getPostgresCastType(attr, field).cast;
208
- builder.whereRaw(`(${aliasMapping[attr]}.data${nested}->>?)${castType} ${operator} ?`, [...field, value]);
354
+ if (value === null) {
355
+ operator = "IS";
356
+ }
357
+ const hasId = field[field.length - 1] === "id";
358
+ if (hasId) {
359
+ builder.whereRaw(`(${aliasMapping[attr]}.id) ${operator} ?`, [
360
+ value,
361
+ ]);
362
+ }
363
+ else {
364
+ const targetField = field[field.length - 1];
365
+ const jsonPath = buildSafeJsonPathQuery(targetField, operator, value);
366
+ builder.whereRaw(`${aliasMapping[attr]}.data${nested} @@ ?`, [
367
+ jsonPath,
368
+ ]);
369
+ }
209
370
  }
210
371
  }
211
372
  }
212
373
  });
213
374
  return builder;
214
375
  }
215
- getShortAlias(aliasMapping, alias) {
376
+ getShortAlias(aliasMapping, alias, level = 0) {
216
377
  aliasMapping.__aliasIndex ??= 0;
217
378
  if (aliasMapping[alias]) {
218
379
  return aliasMapping[alias];
219
380
  }
220
- aliasMapping[alias] = "t_" + aliasMapping.__aliasIndex++ + "_";
381
+ aliasMapping[alias] =
382
+ "t_" + aliasMapping.__aliasIndex++ + (level > 0 ? `_${level}` : "");
221
383
  return aliasMapping[alias];
222
384
  }
223
385
  buildQueryParts(structure, parentAlias, parentEntity, parentProperty, aliasPath = [], level = 0, aliasMapping = {}) {
224
386
  const currentAliasPath = [...aliasPath, parentProperty].join(".");
225
387
  const isSelectableField = this.allSchemaFields.has(parentProperty);
226
388
  const entities = this.getEntity(currentAliasPath, false);
227
- const entityRef = entities?.ref;
228
389
  // !entityRef.alias means the object has not table, it's a nested object
229
- if (isSelectableField || !entities || !entityRef?.alias) {
390
+ if (isSelectableField || !entities || !entities?.ref?.alias) {
230
391
  // We are currently selecting a specific field of the parent entity or the entity is not found on the index schema
231
392
  // We don't need to build the query parts for this as there is no join
232
393
  return [];
233
394
  }
234
- const mainEntity = entityRef.entity;
235
- const mainAlias = this.getShortAlias(aliasMapping, mainEntity.toLowerCase()) + level;
395
+ const mainEntity = entities;
396
+ const mainAlias = this.getShortAlias(aliasMapping, mainEntity.ref.entity.toLowerCase(), level);
236
397
  const allEntities = [];
237
398
  if (!entities.shortCutOf) {
238
399
  allEntities.push({
239
- entity: mainEntity,
400
+ entity: entities,
240
401
  parEntity: parentEntity,
241
402
  parAlias: parentAlias,
242
403
  alias: mainAlias,
@@ -250,26 +411,24 @@ class QueryBuilder {
250
411
  break;
251
412
  }
252
413
  intermediateAlias.pop();
253
- if (intermediateEntity.ref.entity === parentEntity) {
414
+ if (intermediateEntity.ref.entity === parentEntity?.ref.entity) {
254
415
  break;
255
416
  }
256
417
  const parentIntermediateEntity = this.getEntity(intermediateAlias.join("."));
257
- const alias = this.getShortAlias(aliasMapping, intermediateEntity.ref.entity.toLowerCase()) +
258
- level +
418
+ const alias = this.getShortAlias(aliasMapping, intermediateEntity.ref.entity.toLowerCase(), level) +
259
419
  "_" +
260
420
  x;
261
- const parAlias = parentIntermediateEntity.ref.entity === parentEntity
421
+ const parAlias = parentIntermediateEntity.ref.entity === parentEntity?.ref.entity
262
422
  ? parentAlias
263
- : this.getShortAlias(aliasMapping, parentIntermediateEntity.ref.entity.toLowerCase()) +
264
- level +
423
+ : this.getShortAlias(aliasMapping, parentIntermediateEntity.ref.entity.toLowerCase(), level) +
265
424
  "_" +
266
425
  (x + 1);
267
426
  if (x === 0) {
268
427
  aliasMapping[currentAliasPath] = alias;
269
428
  }
270
429
  allEntities.unshift({
271
- entity: intermediateEntity.ref.entity,
272
- parEntity: parentIntermediateEntity.ref.entity,
430
+ entity: intermediateEntity,
431
+ parEntity: parentIntermediateEntity,
273
432
  parAlias,
274
433
  alias,
275
434
  });
@@ -281,12 +440,20 @@ class QueryBuilder {
281
440
  const { alias, entity, parEntity, parAlias } = join;
282
441
  aliasMapping[currentAliasPath] = alias;
283
442
  if (level > 0) {
284
- const cName = entity.toLowerCase();
285
- const pName = `${parEntity}${entity}`.toLowerCase();
443
+ const cName = (0, normalze_table_name_1.normalizeTableName)(entity.ref.entity);
286
444
  let joinTable = `cat_${cName} AS ${alias}`;
287
- const pivotTable = `cat_pivot_${pName}`;
288
- joinBuilder.leftJoin(`${pivotTable} AS ${alias}_ref`, `${alias}_ref.parent_id`, `${parAlias}.id`);
289
- joinBuilder.leftJoin(joinTable, `${alias}.id`, `${alias}_ref.child_id`);
445
+ if (entity.isInverse || parEntity.isInverse) {
446
+ const pName = `${entity.ref.entity}${parEntity.ref.entity}`.toLowerCase();
447
+ const pivotTable = (0, normalze_table_name_1.getPivotTableName)(pName);
448
+ joinBuilder.leftJoin(`${pivotTable} AS ${alias}_ref`, `${alias}_ref.child_id`, `${parAlias}.id`);
449
+ joinBuilder.leftJoin(joinTable, `${alias}.id`, `${alias}_ref.parent_id`);
450
+ }
451
+ else {
452
+ const pName = `${parEntity.ref.entity}${entity.ref.entity}`.toLowerCase();
453
+ const pivotTable = (0, normalze_table_name_1.getPivotTableName)(pName);
454
+ joinBuilder.leftJoin(`${pivotTable} AS ${alias}_ref`, `${alias}_ref.parent_id`, `${parAlias}.id`);
455
+ joinBuilder.leftJoin(joinTable, `${alias}.id`, `${alias}_ref.child_id`);
456
+ }
290
457
  const joinWhere = this.selector.joinWhere ?? {};
291
458
  const joinKey = Object.keys(joinWhere).find((key) => {
292
459
  const k = key.split(".");
@@ -294,7 +461,7 @@ class QueryBuilder {
294
461
  const curPath = k.join(".");
295
462
  if (curPath === currentAliasPath) {
296
463
  const relEntity = this.getEntity(curPath, false);
297
- return relEntity?.ref?.entity === entity;
464
+ return relEntity?.ref?.entity === entity.ref.entity;
298
465
  }
299
466
  return false;
300
467
  });
@@ -323,7 +490,12 @@ class QueryBuilder {
323
490
  const parentAliasPath = aliasPath.join(".");
324
491
  const alias = aliasMapping[parentAliasPath];
325
492
  delete selectParts[parentAliasPath];
326
- selectParts[currentAliasPath] = this.knex.raw(`${alias}.data->'${parentProperty}'`);
493
+ if (parentProperty === "id") {
494
+ selectParts[currentAliasPath] = `${alias}.id`;
495
+ }
496
+ else if (!this.idsOnly) {
497
+ selectParts[currentAliasPath] = this.knex.raw(`${alias}.data->'${parentProperty}'`);
498
+ }
327
499
  return selectParts;
328
500
  }
329
501
  const alias = aliasMapping[currentAliasPath];
@@ -331,7 +503,9 @@ class QueryBuilder {
331
503
  if (!alias) {
332
504
  return selectParts;
333
505
  }
334
- selectParts[currentAliasPath] = `${alias}.data`;
506
+ if (!this.idsOnly) {
507
+ selectParts[currentAliasPath] = `${alias}.data`;
508
+ }
335
509
  selectParts[currentAliasPath + ".id"] = `${alias}.id`;
336
510
  const children = this.getStructureKeys(structure);
337
511
  for (const child of children) {
@@ -372,21 +546,22 @@ class QueryBuilder {
372
546
  arr.forEach((obj) => nested(obj));
373
547
  return result;
374
548
  }
375
- buildQuery({ hasPagination = true, hasCount = false, returnIdOnly = false, }) {
376
- const queryBuilder = this.knex.queryBuilder();
549
+ buildQuery({ hasPagination = true, hasCount = false, }) {
377
550
  const selectOnlyStructure = this.selector.select;
378
551
  const structure = this.requestedFields;
379
552
  const filter = this.selector.where ?? {};
380
553
  const { orderBy: order, skip, take } = this.options ?? {};
381
554
  const orderBy = this.transformOrderBy((order && !Array.isArray(order) ? [order] : order) ?? []);
555
+ const take_ = !isNaN(+take) ? +take : 15;
556
+ const skip_ = !isNaN(+skip) ? +skip : 0;
382
557
  const rootKey = this.getStructureKeys(structure)[0];
383
558
  const rootStructure = structure[rootKey];
384
- const entity = this.getEntity(rootKey).ref.entity;
385
- const rootEntity = entity.toLowerCase();
559
+ const entity = this.getEntity(rootKey);
560
+ const rootEntity = entity.ref.entity.toLowerCase();
386
561
  const aliasMapping = {};
387
562
  let hasTextSearch = false;
388
563
  let textSearchQuery = null;
389
- const searchQueryFilterProp = `${rootEntity}.q`;
564
+ const searchQueryFilterProp = `${rootKey}.q`;
390
565
  if (searchQueryFilterProp in filter) {
391
566
  if (!filter[searchQueryFilterProp]) {
392
567
  delete filter[searchQueryFilterProp];
@@ -397,179 +572,109 @@ class QueryBuilder {
397
572
  delete filter[searchQueryFilterProp];
398
573
  }
399
574
  }
400
- const joinParts = this.buildQueryParts(rootStructure, "", entity, rootKey, [], 0, aliasMapping);
575
+ const filterSortStructure = (0, utils_1.unflattenObjectKeys)({
576
+ ...(this.rawConfig?.filters
577
+ ? (0, utils_1.unflattenObjectKeys)(this.rawConfig?.filters)
578
+ : {}),
579
+ ...orderBy,
580
+ })[rootKey] ?? {};
581
+ const joinParts = this.buildQueryParts(filterSortStructure, "", entity, rootKey, [], 0, aliasMapping);
401
582
  const rootAlias = aliasMapping[rootKey];
402
- const selectParts = !returnIdOnly
403
- ? this.buildSelectParts(selectOnlyStructure[rootKey], rootKey, aliasMapping)
404
- : { [rootKey + ".id"]: `${rootAlias}.id` };
405
- queryBuilder.select(selectParts);
406
- queryBuilder.from(`cat_${rootEntity} AS ${this.getShortAlias(aliasMapping, rootEntity)}`);
583
+ const innerQueryBuilder = this.knex.queryBuilder();
584
+ // Outer query to select the full data based on the paginated IDs
585
+ const outerQueryBuilder = this.knex.queryBuilder();
586
+ innerQueryBuilder.distinct(`${rootAlias}.id`);
587
+ const orderBySelects = [];
588
+ const orderByClauses = [];
589
+ for (const aliasPath in orderBy) {
590
+ const path = aliasPath.split(".");
591
+ const field = path.pop();
592
+ const attr = path.join(".");
593
+ const alias = aliasMapping[attr];
594
+ const direction = orderBy[aliasPath];
595
+ const pgType = this.getPostgresCastType(attr, [field]);
596
+ const hasId = field === "id";
597
+ let orderExpression = `${rootAlias}.id ${direction}`;
598
+ if (alias) {
599
+ const aggregateAlias = `"${aliasPath}_agg"`;
600
+ let aggregateExpression = `(${alias}.data->>'${field}')${pgType.cast}`;
601
+ if (hasId) {
602
+ aggregateExpression = `${alias}.id`;
603
+ }
604
+ else {
605
+ orderBySelects.push(direction === "ASC"
606
+ ? this.knex.raw(`MIN(${aggregateExpression}) AS ${aggregateAlias}`)
607
+ : this.knex.raw(`MAX(${aggregateExpression}) AS ${aggregateAlias}`));
608
+ orderExpression = `${aggregateAlias} ${direction}`;
609
+ }
610
+ outerQueryBuilder.orderByRaw(`${aggregateExpression} ${direction}`);
611
+ }
612
+ orderByClauses.push(orderExpression);
613
+ }
614
+ // Add ordering columns to the select list of the inner query
615
+ if (orderBySelects.length > 0) {
616
+ innerQueryBuilder.select(orderBySelects);
617
+ }
618
+ innerQueryBuilder.from(`cat_${(0, normalze_table_name_1.normalizeTableName)(rootEntity)} AS ${this.getShortAlias(aliasMapping, rootKey)}`);
407
619
  joinParts.forEach((joinPart) => {
408
- queryBuilder.joinRaw(joinPart);
620
+ innerQueryBuilder.joinRaw(joinPart);
409
621
  });
410
- let searchWhereParts = [];
411
622
  if (hasTextSearch) {
412
- /**
413
- * Build the search where parts for the query,.
414
- * Apply the search query to the search vector column for every joined tabled except
415
- * the pivot joined table.
416
- */
417
- searchWhereParts = [
418
- `${this.getShortAlias(aliasMapping, rootEntity)}.${__classPrivateFieldGet(this, _QueryBuilder_searchVectorColumnName, "f")} @@ plainto_tsquery('simple', '${textSearchQuery}')`,
623
+ const searchWhereParts = [
624
+ `${rootAlias}.${__classPrivateFieldGet(this, _QueryBuilder_searchVectorColumnName, "f")} @@ plainto_tsquery('simple', ?)`,
419
625
  ...joinParts.flatMap((part) => {
420
626
  const aliases = part
421
627
  .split(" as ")
422
628
  .flatMap((chunk) => chunk.split(" on "))
423
629
  .filter((alias) => alias.startsWith('"t_') && !alias.includes("_ref"));
424
- return aliases.map((alias) => `${alias}.${__classPrivateFieldGet(this, _QueryBuilder_searchVectorColumnName, "f")} @@ plainto_tsquery('simple', '${textSearchQuery}')`);
630
+ return aliases.map((alias) => `${alias}.${__classPrivateFieldGet(this, _QueryBuilder_searchVectorColumnName, "f")} @@ plainto_tsquery('simple', ?)`);
425
631
  }),
426
632
  ];
427
- queryBuilder.whereRaw(`(${searchWhereParts.join(" OR ")})`);
633
+ innerQueryBuilder.whereRaw(`(${searchWhereParts.join(" OR ")})`, Array(searchWhereParts.length).fill(textSearchQuery));
428
634
  }
429
- // WHERE clause
430
- this.parseWhere(aliasMapping, filter, queryBuilder);
431
- // ORDER BY clause
432
- for (const aliasPath in orderBy) {
433
- const path = aliasPath.split(".");
434
- const field = path.pop();
435
- const attr = path.join(".");
436
- const pgType = this.getPostgresCastType(attr, [field]);
437
- const alias = aliasMapping[attr];
438
- const direction = orderBy[aliasPath];
439
- queryBuilder.orderByRaw(`(${alias}.data->>'${field}')${pgType.cast}` + " " + direction);
635
+ this.parseWhere(aliasMapping, filter, innerQueryBuilder);
636
+ // Group by root ID in the inner query
637
+ if (orderBySelects.length > 0) {
638
+ innerQueryBuilder.groupBy(`${rootAlias}.id`);
440
639
  }
441
- let take_ = !isNaN(+take) ? +take : 15;
442
- let skip_ = !isNaN(+skip) ? +skip : 0;
443
- let cte = "";
444
- if (hasPagination) {
445
- cte = this.buildCTEData({
446
- hasCount,
447
- searchWhereParts,
448
- take: take_,
449
- skip: skip_,
450
- orderBy,
451
- });
452
- if (hasCount) {
453
- queryBuilder.select(this.knex.raw("pd.count_total"));
454
- }
455
- queryBuilder.joinRaw(`JOIN paginated_data AS pd ON ${rootAlias}.id = pd.id`);
640
+ if (orderByClauses.length > 0) {
641
+ innerQueryBuilder.orderByRaw(orderByClauses.join(", "));
456
642
  }
457
- return cte + queryBuilder.toQuery();
458
- }
459
- buildCTEData({ hasCount, searchWhereParts = [], skip, take, orderBy, }) {
460
- const queryBuilder = this.knex.queryBuilder();
461
- const hasWhere = (0, utils_1.isPresent)(this.rawConfig?.filters) || (0, utils_1.isPresent)(orderBy);
462
- const structure = hasWhere && !searchWhereParts.length
463
- ? (0, utils_1.unflattenObjectKeys)({
464
- ...(this.rawConfig?.filters
465
- ? (0, utils_1.unflattenObjectKeys)(this.rawConfig?.filters)
466
- : {}),
467
- ...orderBy,
468
- })
469
- : this.requestedFields;
470
- const rootKey = this.getStructureKeys(structure)[0];
471
- const rootStructure = structure[rootKey];
472
- const entity = this.getEntity(rootKey).ref.entity;
473
- const rootEntity = entity.toLowerCase();
474
- const aliasMapping = {};
475
- const joinParts = this.buildQueryParts(rootStructure, "", entity, rootKey, [], 0, aliasMapping);
476
- const rootAlias = aliasMapping[rootKey];
477
- queryBuilder.select(this.knex.raw(`${rootAlias}.id as id`));
478
- queryBuilder.from(`cat_${rootEntity} AS ${this.getShortAlias(aliasMapping, rootEntity)}`);
479
- if (hasWhere) {
480
- joinParts.forEach((joinPart) => {
481
- queryBuilder.joinRaw(joinPart);
482
- });
483
- if (searchWhereParts.length) {
484
- queryBuilder.whereRaw(`(${searchWhereParts.join(" OR ")})`);
485
- }
486
- this.parseWhere(aliasMapping, this.selector.where, queryBuilder);
643
+ else {
644
+ innerQueryBuilder.orderBy(`${rootAlias}.id`, "ASC");
487
645
  }
488
- // ORDER BY clause
489
- const orderAliases = [];
490
- for (const aliasPath in orderBy) {
491
- const path = aliasPath.split(".");
492
- const field = path.pop();
493
- const attr = path.join(".");
494
- const pgType = this.getPostgresCastType(attr, [field]);
495
- const alias = aliasMapping[attr];
496
- const direction = orderBy[aliasPath];
497
- const orderAlias = `"${alias}.data->>'${field}'"`;
498
- orderAliases.push(orderAlias + " " + direction);
499
- // transform the order by clause to a select MIN/MAX
500
- queryBuilder.select(direction === "ASC"
501
- ? this.knex.raw(`MIN((${alias}.data->>'${field}')${pgType.cast}) as ${orderAlias}`)
502
- : this.knex.raw(`MAX((${alias}.data->>'${field}')${pgType.cast}) as ${orderAlias}`));
646
+ // Count query to estimate the number of results in parallel
647
+ let countQuery;
648
+ if (hasCount) {
649
+ const estimateQuery = innerQueryBuilder.clone();
650
+ estimateQuery.clearSelect().select(1);
651
+ estimateQuery.clearOrder();
652
+ estimateQuery.clearCounters();
653
+ countQuery = this.knex.raw(`SELECT count_estimate(?) AS estimate_count`, estimateQuery.toQuery());
503
654
  }
504
- queryBuilder.groupByRaw(`${rootAlias}.id`);
505
- const countSubQuery = hasCount
506
- ? `, (SELECT count(id) FROM data_select) as count_total`
507
- : "";
508
- return `
509
- WITH data_select AS (
510
- ${queryBuilder.toQuery()}
511
- ),
512
- paginated_data AS (
513
- SELECT id ${countSubQuery}
514
- FROM data_select
515
- ${orderAliases.length ? "ORDER BY " + orderAliases.join(", ") : ""}
516
- LIMIT ${take}
517
- ${skip ? `OFFSET ${skip}` : ""}
518
- )
519
- `;
655
+ // Apply pagination to the inner query
656
+ if (hasPagination) {
657
+ innerQueryBuilder.limit(take_);
658
+ if (skip_ > 0) {
659
+ innerQueryBuilder.offset(skip_);
660
+ }
661
+ }
662
+ const innerQueryAlias = "paginated_ids";
663
+ outerQueryBuilder.from(`cat_${(0, normalze_table_name_1.normalizeTableName)(rootEntity)} AS ${this.getShortAlias(aliasMapping, rootKey)}`);
664
+ outerQueryBuilder.joinRaw(`INNER JOIN (${innerQueryBuilder.toQuery()}) AS ${innerQueryAlias} ON ${rootAlias}.id = ${innerQueryAlias}.id`);
665
+ this.parseWhere(aliasMapping, filter, outerQueryBuilder);
666
+ const joinPartsOuterQuery = this.buildQueryParts(rootStructure, "", entity, rootKey, [], 0, aliasMapping);
667
+ joinPartsOuterQuery.forEach((joinPart) => {
668
+ outerQueryBuilder.joinRaw(joinPart);
669
+ });
670
+ const finalSelectParts = this.buildSelectParts(selectOnlyStructure[rootKey], rootKey, aliasMapping);
671
+ outerQueryBuilder.select(finalSelectParts);
672
+ const finalSql = outerQueryBuilder.toQuery();
673
+ return {
674
+ sql: finalSql,
675
+ sqlCount: countQuery?.toQuery?.(),
676
+ };
520
677
  }
521
- // NOTE: We are keeping the bellow code for now as reference to alternative implementation for us. DO NOT REMOVE
522
- // public buildQueryCount(): string {
523
- // const queryBuilder = this.knex.queryBuilder()
524
- // const hasWhere = isPresent(this.rawConfig?.filters)
525
- // const structure = hasWhere ? this.rawConfig?.filters! : this.structure
526
- // const rootKey = this.getStructureKeys(structure)[0]
527
- // const rootStructure = structure[rootKey] as Select
528
- // const entity = this.getEntity(rootKey)!.ref.entity
529
- // const rootEntity = entity.toLowerCase()
530
- // const aliasMapping: { [path: string]: string } = {}
531
- // const joinParts = this.buildQueryParts(
532
- // rootStructure,
533
- // "",
534
- // entity,
535
- // rootKey,
536
- // [],
537
- // 0,
538
- // aliasMapping
539
- // )
540
- // const rootAlias = aliasMapping[rootKey]
541
- // queryBuilder.select(this.knex.raw(`COUNT(${rootAlias}.id) as count`))
542
- // queryBuilder.from(
543
- // `cat_${rootEntity} AS ${this.getShortAlias(aliasMapping, rootEntity)}`
544
- // )
545
- // const self = this
546
- // if (hasWhere && joinParts.length) {
547
- // const fromExistsRaw = joinParts.shift()!
548
- // const [joinPartsExists, fromExistsPart] =
549
- // fromExistsRaw.split(" left join ")
550
- // const [fromExists, whereExists] = fromExistsPart.split(" on ")
551
- // joinParts.unshift(joinPartsExists)
552
- // queryBuilder.whereExists(function () {
553
- // this.select(self.knex.raw(`1`))
554
- // this.from(self.knex.raw(`${fromExists}`))
555
- // this.joinRaw(joinParts.join("\n"))
556
- // if (hasWhere) {
557
- // self.parseWhere(aliasMapping, self.selector.where!, this)
558
- // this.whereRaw(self.knex.raw(whereExists))
559
- // return
560
- // }
561
- // this.whereRaw(self.knex.raw(whereExists))
562
- // })
563
- // } else {
564
- // queryBuilder.whereExists(function () {
565
- // this.select(self.knex.raw(`1`))
566
- // if (hasWhere) {
567
- // self.parseWhere(aliasMapping, self.selector.where!, this)
568
- // }
569
- // })
570
- // }
571
- // return queryBuilder.toQuery()
572
- // }
573
678
  buildObjectFromResultset(resultSet) {
574
679
  const structure = this.structure;
575
680
  const rootKey = this.getStructureKeys(structure)[0];