@things-factory/shell 10.0.0-y.0 → 10.0.0-zeta.10

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 (38) hide show
  1. package/client/entries/public/home.js +1 -1
  2. package/dist-server/bootstrap-hooks.d.ts +51 -0
  3. package/dist-server/bootstrap-hooks.js +39 -0
  4. package/dist-server/bootstrap-hooks.js.map +1 -0
  5. package/dist-server/index.d.ts +1 -0
  6. package/dist-server/index.js +1 -0
  7. package/dist-server/index.js.map +1 -1
  8. package/dist-server/initializers/database.js +4 -1
  9. package/dist-server/initializers/database.js.map +1 -1
  10. package/dist-server/server-dev.js +13 -10
  11. package/dist-server/server-dev.js.map +1 -1
  12. package/dist-server/server.js +12 -8
  13. package/dist-server/server.js.map +1 -1
  14. package/dist-server/service/domain/domain-mutation.js +3 -2
  15. package/dist-server/service/domain/domain-mutation.js.map +1 -1
  16. package/dist-server/tsconfig.tsbuildinfo +1 -1
  17. package/dist-server/typeorm/get-data-encryption-key.js +11 -1
  18. package/dist-server/typeorm/get-data-encryption-key.js.map +1 -1
  19. package/dist-server/utils/apply-domain-filter.d.ts +17 -0
  20. package/dist-server/utils/apply-domain-filter.js +30 -0
  21. package/dist-server/utils/apply-domain-filter.js.map +1 -0
  22. package/dist-server/utils/condition-builder.js +15 -7
  23. package/dist-server/utils/condition-builder.js.map +1 -1
  24. package/dist-server/utils/get-domain.js +5 -2
  25. package/dist-server/utils/get-domain.js.map +1 -1
  26. package/dist-server/utils/get-query-builder-from-list-params.d.ts +39 -9
  27. package/dist-server/utils/get-query-builder-from-list-params.js +83 -40
  28. package/dist-server/utils/get-query-builder-from-list-params.js.map +1 -1
  29. package/dist-server/utils/list-params-converter.js +8 -6
  30. package/dist-server/utils/list-params-converter.js.map +1 -1
  31. package/dist-server/utils/list-query-builder.js +2 -14
  32. package/dist-server/utils/list-query-builder.js.map +1 -1
  33. package/jest.config.js +5 -0
  34. package/package.json +6 -6
  35. package/tests/get-query-builder-features.test.ts +229 -0
  36. package/tests/list-param-operators.test.ts +87 -0
  37. package/tests/manifest-contract.test.ts +38 -0
  38. package/client/store.ts +0 -15
@@ -2,14 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dataEncryptionKey = void 0;
4
4
  const env_1 = require("@things-factory/env");
5
+ /* shell 이 dev 편의를 위해 제공하는 기본 키. 프로덕션에서는 앱이 자기 config 로
6
+ 반드시 override 하는 것이 정석/권장이다. */
7
+ const DEFAULT_DATA_ENCRYPTION_KEY = 'V6g5oHJZb7KcYzIyL6cM95XvIDouon5b';
5
8
  var _DATA_ENCRYPTION_KEY = env_1.config.get('dataEncryptionKey');
6
9
  if (!_DATA_ENCRYPTION_KEY) {
7
10
  if (process.env.NODE_ENV == 'production') {
8
11
  throw new TypeError('dataEncryptionKey not configured.');
9
12
  }
10
13
  else {
11
- _DATA_ENCRYPTION_KEY = 'V6g5oHJZb7KcYzIyL6cM95XvIDouon5b';
14
+ _DATA_ENCRYPTION_KEY = DEFAULT_DATA_ENCRYPTION_KEY;
12
15
  }
13
16
  }
17
+ /* 프로덕션인데 앱이 override 하지 않아 레포에 공개된 기본 키가 그대로 쓰이는 경우:
18
+ 암호화가 사실상 무의미하므로 강한 보안 경고를 남긴다(부팅·데이터는 건드리지 않음). */
19
+ if (process.env.NODE_ENV == 'production' && _DATA_ENCRYPTION_KEY == DEFAULT_DATA_ENCRYPTION_KEY) {
20
+ env_1.logger.warn('[SECURITY] dataEncryptionKey is using the built-in default committed in the repository, ' +
21
+ 'which provides NO real protection for encrypted data. ' +
22
+ 'Override `dataEncryptionKey` in your application config for production.');
23
+ }
14
24
  exports.dataEncryptionKey = _DATA_ENCRYPTION_KEY;
15
25
  //# sourceMappingURL=get-data-encryption-key.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-data-encryption-key.js","sourceRoot":"","sources":["../../server/typeorm/get-data-encryption-key.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAE5C,IAAI,oBAAoB,GAAG,YAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;AAE1D,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,YAAY,EAAE,CAAC;QACzC,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAA;IAC1D,CAAC;SAAM,CAAC;QACN,oBAAoB,GAAG,kCAAkC,CAAA;IAC3D,CAAC;AACH,CAAC;AAEY,QAAA,iBAAiB,GAAG,oBAAoB,CAAA","sourcesContent":["import { config } from '@things-factory/env'\n\nvar _DATA_ENCRYPTION_KEY = config.get('dataEncryptionKey')\n\nif (!_DATA_ENCRYPTION_KEY) {\n if (process.env.NODE_ENV == 'production') {\n throw new TypeError('dataEncryptionKey not configured.')\n } else {\n _DATA_ENCRYPTION_KEY = 'V6g5oHJZb7KcYzIyL6cM95XvIDouon5b'\n }\n}\n\nexport const dataEncryptionKey = _DATA_ENCRYPTION_KEY\n"]}
1
+ {"version":3,"file":"get-data-encryption-key.js","sourceRoot":"","sources":["../../server/typeorm/get-data-encryption-key.ts"],"names":[],"mappings":";;;AAAA,6CAAoD;AAEpD;iCACiC;AACjC,MAAM,2BAA2B,GAAG,kCAAkC,CAAA;AAEtE,IAAI,oBAAoB,GAAG,YAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;AAE1D,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,YAAY,EAAE,CAAC;QACzC,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAA;IAC1D,CAAC;SAAM,CAAC;QACN,oBAAoB,GAAG,2BAA2B,CAAA;IACpD,CAAC;AACH,CAAC;AAED;qDACqD;AACrD,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,YAAY,IAAI,oBAAoB,IAAI,2BAA2B,EAAE,CAAC;IAChG,YAAM,CAAC,IAAI,CACT,0FAA0F;QACxF,wDAAwD;QACxD,yEAAyE,CAC5E,CAAA;AACH,CAAC;AAEY,QAAA,iBAAiB,GAAG,oBAAoB,CAAA","sourcesContent":["import { config, logger } from '@things-factory/env'\n\n/* shell 이 dev 편의를 위해 제공하는 기본 키. 프로덕션에서는 앱이 자기 config 로\n 반드시 override 하는 것이 정석/권장이다. */\nconst DEFAULT_DATA_ENCRYPTION_KEY = 'V6g5oHJZb7KcYzIyL6cM95XvIDouon5b'\n\nvar _DATA_ENCRYPTION_KEY = config.get('dataEncryptionKey')\n\nif (!_DATA_ENCRYPTION_KEY) {\n if (process.env.NODE_ENV == 'production') {\n throw new TypeError('dataEncryptionKey not configured.')\n } else {\n _DATA_ENCRYPTION_KEY = DEFAULT_DATA_ENCRYPTION_KEY\n }\n}\n\n/* 프로덕션인데 앱이 override 하지 않아 레포에 공개된 기본 키가 그대로 쓰이는 경우:\n 암호화가 사실상 무의미하므로 강한 보안 경고를 남긴다(부팅·데이터는 건드리지 않음). */\nif (process.env.NODE_ENV == 'production' && _DATA_ENCRYPTION_KEY == DEFAULT_DATA_ENCRYPTION_KEY) {\n logger.warn(\n '[SECURITY] dataEncryptionKey is using the built-in default committed in the repository, ' +\n 'which provides NO real protection for encrypted data. ' +\n 'Override `dataEncryptionKey` in your application config for production.'\n )\n}\n\nexport const dataEncryptionKey = _DATA_ENCRYPTION_KEY\n"]}
@@ -0,0 +1,17 @@
1
+ import { WhereExpressionBuilder } from 'typeorm';
2
+ import { InheritedValueType } from '../service/common-types/list-param.js';
3
+ /**
4
+ * 멀티테넌트 도메인 상속 필터를 쿼리에 적용한다.
5
+ *
6
+ * `getQueryBuilderFromListParams` 와 (deprecated) `buildQuery` 양쪽에 동일하게 중복돼 있던
7
+ * 도메인 필터 로직의 단일 소스. 도메인 필터는 테넌트 격리(보안)에 직결되므로 한 곳에서만 정의한다.
8
+ *
9
+ * @param qb 조건을 추가할 대상(WhereExpressionBuilder / SelectQueryBuilder)
10
+ * @param alias 엔티티 alias
11
+ * @param domain 현재 도메인 (id, parentId)
12
+ * @param inherited 상속 방식: None(자기 도메인) / Include(자기+부모) / Only(부모만)
13
+ */
14
+ export declare function applyDomainInheritanceFilter(qb: WhereExpressionBuilder, alias: string, domain: {
15
+ id: string;
16
+ parentId?: string | null;
17
+ }, inherited?: InheritedValueType): void;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyDomainInheritanceFilter = applyDomainInheritanceFilter;
4
+ const list_param_js_1 = require("../service/common-types/list-param.js");
5
+ /**
6
+ * 멀티테넌트 도메인 상속 필터를 쿼리에 적용한다.
7
+ *
8
+ * `getQueryBuilderFromListParams` 와 (deprecated) `buildQuery` 양쪽에 동일하게 중복돼 있던
9
+ * 도메인 필터 로직의 단일 소스. 도메인 필터는 테넌트 격리(보안)에 직결되므로 한 곳에서만 정의한다.
10
+ *
11
+ * @param qb 조건을 추가할 대상(WhereExpressionBuilder / SelectQueryBuilder)
12
+ * @param alias 엔티티 alias
13
+ * @param domain 현재 도메인 (id, parentId)
14
+ * @param inherited 상속 방식: None(자기 도메인) / Include(자기+부모) / Only(부모만)
15
+ */
16
+ function applyDomainInheritanceFilter(qb, alias, domain, inherited = list_param_js_1.InheritedValueType.None) {
17
+ if (!inherited || inherited === list_param_js_1.InheritedValueType.None) {
18
+ qb.andWhere(`${alias}.domain = :domain`, { domain: domain.id });
19
+ }
20
+ else if (inherited === list_param_js_1.InheritedValueType.Include) {
21
+ qb.andWhere(`${alias}.domain IN (:...domains)`, { domains: [domain.id, domain.parentId].filter(Boolean) });
22
+ }
23
+ else if (inherited === list_param_js_1.InheritedValueType.Only) {
24
+ qb.andWhere(`${alias}.domain = :domain`, { domain: domain.parentId || 'Impossible' });
25
+ }
26
+ else {
27
+ qb.andWhere(`${alias}.domain = :domain`, { domain: 'Impossible' });
28
+ }
29
+ }
30
+ //# sourceMappingURL=apply-domain-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-domain-filter.js","sourceRoot":"","sources":["../../server/utils/apply-domain-filter.ts"],"names":[],"mappings":";;AAeA,oEAeC;AA5BD,yEAA0E;AAE1E;;;;;;;;;;GAUG;AACH,SAAgB,4BAA4B,CAC1C,EAA0B,EAC1B,KAAa,EACb,MAAgD,EAChD,YAAgC,kCAAkB,CAAC,IAAI;IAEvD,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,kCAAkB,CAAC,IAAI,EAAE,CAAC;QACxD,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,mBAAmB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IACjE,CAAC;SAAM,IAAI,SAAS,KAAK,kCAAkB,CAAC,OAAO,EAAE,CAAC;QACpD,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,0BAA0B,EAAE,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC5G,CAAC;SAAM,IAAI,SAAS,KAAK,kCAAkB,CAAC,IAAI,EAAE,CAAC;QACjD,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,mBAAmB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,IAAI,YAAY,EAAE,CAAC,CAAA;IACvF,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,mBAAmB,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;IACpE,CAAC;AACH,CAAC","sourcesContent":["import { WhereExpressionBuilder } from 'typeorm'\n\nimport { InheritedValueType } from '../service/common-types/list-param.js'\n\n/**\n * 멀티테넌트 도메인 상속 필터를 쿼리에 적용한다.\n *\n * `getQueryBuilderFromListParams` 와 (deprecated) `buildQuery` 양쪽에 동일하게 중복돼 있던\n * 도메인 필터 로직의 단일 소스. 도메인 필터는 테넌트 격리(보안)에 직결되므로 한 곳에서만 정의한다.\n *\n * @param qb 조건을 추가할 대상(WhereExpressionBuilder / SelectQueryBuilder)\n * @param alias 엔티티 alias\n * @param domain 현재 도메인 (id, parentId)\n * @param inherited 상속 방식: None(자기 도메인) / Include(자기+부모) / Only(부모만)\n */\nexport function applyDomainInheritanceFilter(\n qb: WhereExpressionBuilder,\n alias: string,\n domain: { id: string; parentId?: string | null },\n inherited: InheritedValueType = InheritedValueType.None\n): void {\n if (!inherited || inherited === InheritedValueType.None) {\n qb.andWhere(`${alias}.domain = :domain`, { domain: domain.id })\n } else if (inherited === InheritedValueType.Include) {\n qb.andWhere(`${alias}.domain IN (:...domains)`, { domains: [domain.id, domain.parentId].filter(Boolean) })\n } else if (inherited === InheritedValueType.Only) {\n qb.andWhere(`${alias}.domain = :domain`, { domain: domain.parentId || 'Impossible' })\n } else {\n qb.andWhere(`${alias}.domain = :domain`, { domain: 'Impossible' })\n }\n}\n"]}
@@ -95,33 +95,41 @@ const buildCondition = function (alias, fieldName, operator, value, relation, se
95
95
  return {
96
96
  clause: `${alias}.${fieldName} IS NOT NULL`
97
97
  };
98
+ /* boolean 비교는 파라미터 바인딩으로 (IS TRUE/FALSE 는 MSSQL/Oracle 미지원 → 이식성 확보) */
98
99
  case 'is_false':
99
100
  return {
100
- clause: `${alias}.${fieldName} IS FALSE`
101
+ clause: `${alias}.${fieldName} = :args${seq}`,
102
+ parameters: { [`args${seq}`]: false }
101
103
  };
102
104
  case 'is_true':
103
105
  return {
104
- clause: `${alias}.${fieldName} IS TRUE`
106
+ clause: `${alias}.${fieldName} = :args${seq}`,
107
+ parameters: { [`args${seq}`]: true }
105
108
  };
106
109
  case 'is_not_false':
110
+ /* NOT FALSE = true 또는 NULL */
107
111
  return {
108
- clause: `${alias}.${fieldName} IS NOT FALSE`
112
+ clause: `(${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} = :args${seq})`,
113
+ parameters: { [`args${seq}`]: true }
109
114
  };
110
115
  case 'is_not_true':
116
+ /* NOT TRUE = false 또는 NULL */
111
117
  return {
112
- clause: `${alias}.${fieldName} IS NOT TRUE`
118
+ clause: `(${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} = :args${seq})`,
119
+ parameters: { [`args${seq}`]: false }
113
120
  };
114
121
  case 'is_present':
115
122
  return {
116
- clause: `${alias}.${fieldName} IS PRESENT`
123
+ clause: `${alias}.${fieldName} IS NOT NULL`
117
124
  };
118
125
  case 'is_blank':
119
126
  return {
120
- clause: `${alias}.${fieldName} IS BLANK`
127
+ clause: `${alias}.${fieldName} IS NULL`
121
128
  };
122
129
  case 'is_empty_num_id':
123
130
  return {
124
- clause: `${alias}.${fieldName} IS EMPTY NUMERIC ID`
131
+ clause: `(${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} = :args${seq})`,
132
+ parameters: { [`args${seq}`]: 0 }
125
133
  };
126
134
  case 'between':
127
135
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"condition-builder.js","sourceRoot":"","sources":["../../server/utils/condition-builder.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash'\n\n/**\n * Generates and returns a condition clause based on the provided parameters.\n *\n * @param alias {string} Alias or table name of the entity.\n * @param fieldName {string} Field or column name.\n * @param operator {string} Comparison operator.\n * @param value {any} Value or an array of values to compare.\n * @param relation {boolean | string} Indicates if it's a related field or the name of the relation (optional).\n * @param seq {number} Integer representing the order of condition generation (internal use).\n * @returns {Object} An object containing the generated condition clause and parameters.\n */\nexport const buildCondition = function (alias: string, fieldName: string, operator: string, value: any, relation: boolean | string, seq: number) {\n seq++\n\n fieldName = _.snakeCase(fieldName)\n const values = value instanceof Array ? value : [value]\n\n switch (operator) {\n case 'eq':\n return {\n clause: `${alias}.${fieldName} = :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'like':\n return {\n clause: `${alias}.${fieldName} LIKE :args${seq}`,\n parameters: { [`args${seq}`]: `${value}` }\n }\n\n case 'search':\n case 'i_like':\n return {\n clause: `LOWER(${alias}.${fieldName}) LIKE :args${seq}`,\n parameters: { [`args${seq}`]: `${String(value).toLowerCase()}` }\n }\n\n case 'nlike':\n return {\n clause: `${alias}.${fieldName} NOT LIKE :args${seq}`,\n value: { [`args${seq}`]: `${value}` }\n }\n\n case 'i_nlike':\n return {\n clause: `LOWER(${alias}.${fieldName}) NOT LIKE :args${seq}`,\n value: { [`args${seq}`]: `${String(value).toLowerCase()}` }\n }\n\n case 'lt':\n return {\n clause: `${alias}.${fieldName} < :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'gt':\n return {\n clause: `${alias}.${fieldName} > :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'lte':\n return {\n clause: `${alias}.${fieldName} <= :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'gte':\n return {\n clause: `${alias}.${fieldName} >= :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'noteq':\n return {\n clause: `${alias}.${fieldName} != :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'in':\n return {\n clause: relation ? `${fieldName}.id IN (:...args${seq})` : `${alias}.${fieldName} IN (:...args${seq})`,\n parameters: { [`args${seq}`]: values }\n }\n\n case 'notin':\n return {\n clause: relation ? `${fieldName}.id NOT IN (:...args${seq})` : `${alias}.${fieldName} NOT IN (:...args${seq})`,\n parameters: { [`args${seq}`]: values }\n }\n\n case 'notin_with_null':\n return {\n clause: relation\n ? `(${fieldName}.id IS NULL OR ${fieldName}.id NOT IN (:...args${seq})`\n : `${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} NOT IN (:...args${seq}))`,\n parameters: { [`args${seq}`]: values }\n }\n\n case 'is_null':\n return {\n clause: `${alias}.${fieldName} IS NULL`\n }\n case 'is_not_null':\n return {\n clause: `${alias}.${fieldName} IS NOT NULL`\n }\n case 'is_false':\n return {\n clause: `${alias}.${fieldName} IS FALSE`\n }\n case 'is_true':\n return {\n clause: `${alias}.${fieldName} IS TRUE`\n }\n case 'is_not_false':\n return {\n clause: `${alias}.${fieldName} IS NOT FALSE`\n }\n case 'is_not_true':\n return {\n clause: `${alias}.${fieldName} IS NOT TRUE`\n }\n case 'is_present':\n return {\n clause: `${alias}.${fieldName} IS PRESENT`\n }\n case 'is_blank':\n return {\n clause: `${alias}.${fieldName} IS BLANK`\n }\n case 'is_empty_num_id':\n return {\n clause: `${alias}.${fieldName} IS EMPTY NUMERIC ID`\n }\n\n case 'between':\n return {\n clause: `${alias}.${fieldName} BETWEEN :args${seq}_1 AND :args${seq}_2`,\n parameters: { [`args${seq}_1`]: values[0], [`args${seq}_2`]: values[1] }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"condition-builder.js","sourceRoot":"","sources":["../../server/utils/condition-builder.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash'\n\n/**\n * Generates and returns a condition clause based on the provided parameters.\n *\n * @param alias {string} Alias or table name of the entity.\n * @param fieldName {string} Field or column name.\n * @param operator {string} Comparison operator.\n * @param value {any} Value or an array of values to compare.\n * @param relation {boolean | string} Indicates if it's a related field or the name of the relation (optional).\n * @param seq {number} Integer representing the order of condition generation (internal use).\n * @returns {Object} An object containing the generated condition clause and parameters.\n */\nexport const buildCondition = function (alias: string, fieldName: string, operator: string, value: any, relation: boolean | string, seq: number) {\n seq++\n\n fieldName = _.snakeCase(fieldName)\n const values = value instanceof Array ? value : [value]\n\n switch (operator) {\n case 'eq':\n return {\n clause: `${alias}.${fieldName} = :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'like':\n return {\n clause: `${alias}.${fieldName} LIKE :args${seq}`,\n parameters: { [`args${seq}`]: `${value}` }\n }\n\n case 'search':\n case 'i_like':\n return {\n clause: `LOWER(${alias}.${fieldName}) LIKE :args${seq}`,\n parameters: { [`args${seq}`]: `${String(value).toLowerCase()}` }\n }\n\n case 'nlike':\n return {\n clause: `${alias}.${fieldName} NOT LIKE :args${seq}`,\n value: { [`args${seq}`]: `${value}` }\n }\n\n case 'i_nlike':\n return {\n clause: `LOWER(${alias}.${fieldName}) NOT LIKE :args${seq}`,\n value: { [`args${seq}`]: `${String(value).toLowerCase()}` }\n }\n\n case 'lt':\n return {\n clause: `${alias}.${fieldName} < :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'gt':\n return {\n clause: `${alias}.${fieldName} > :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'lte':\n return {\n clause: `${alias}.${fieldName} <= :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'gte':\n return {\n clause: `${alias}.${fieldName} >= :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'noteq':\n return {\n clause: `${alias}.${fieldName} != :args${seq}`,\n parameters: { [`args${seq}`]: value }\n }\n\n case 'in':\n return {\n clause: relation ? `${fieldName}.id IN (:...args${seq})` : `${alias}.${fieldName} IN (:...args${seq})`,\n parameters: { [`args${seq}`]: values }\n }\n\n case 'notin':\n return {\n clause: relation ? `${fieldName}.id NOT IN (:...args${seq})` : `${alias}.${fieldName} NOT IN (:...args${seq})`,\n parameters: { [`args${seq}`]: values }\n }\n\n case 'notin_with_null':\n return {\n clause: relation\n ? `(${fieldName}.id IS NULL OR ${fieldName}.id NOT IN (:...args${seq})`\n : `${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} NOT IN (:...args${seq}))`,\n parameters: { [`args${seq}`]: values }\n }\n\n case 'is_null':\n return {\n clause: `${alias}.${fieldName} IS NULL`\n }\n case 'is_not_null':\n return {\n clause: `${alias}.${fieldName} IS NOT NULL`\n }\n /* boolean 비교는 파라미터 바인딩으로 (IS TRUE/FALSE 는 MSSQL/Oracle 미지원 → 이식성 확보) */\n case 'is_false':\n return {\n clause: `${alias}.${fieldName} = :args${seq}`,\n parameters: { [`args${seq}`]: false }\n }\n case 'is_true':\n return {\n clause: `${alias}.${fieldName} = :args${seq}`,\n parameters: { [`args${seq}`]: true }\n }\n case 'is_not_false':\n /* NOT FALSE = true 또는 NULL */\n return {\n clause: `(${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} = :args${seq})`,\n parameters: { [`args${seq}`]: true }\n }\n case 'is_not_true':\n /* NOT TRUE = false 또는 NULL */\n return {\n clause: `(${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} = :args${seq})`,\n parameters: { [`args${seq}`]: false }\n }\n case 'is_present':\n return {\n clause: `${alias}.${fieldName} IS NOT NULL`\n }\n case 'is_blank':\n return {\n clause: `${alias}.${fieldName} IS NULL`\n }\n case 'is_empty_num_id':\n return {\n clause: `(${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} = :args${seq})`,\n parameters: { [`args${seq}`]: 0 }\n }\n\n case 'between':\n return {\n clause: `${alias}.${fieldName} BETWEEN :args${seq}_1 AND :args${seq}_2`,\n parameters: { [`args${seq}_1`]: values[0], [`args${seq}_2`]: values[1] }\n }\n }\n}\n"]}
@@ -173,8 +173,11 @@ function findSubdomainFromPath(context, path) {
173
173
  }
174
174
  var parsed = getUrlFromContext(context, path);
175
175
  var { pathname } = parsed;
176
- const match = pathname.match(new RegExp(`^/${getSubdomainTypeFromURL(context)}/([^\/]+)`));
177
- return match && match[1];
176
+ // 도메인 타입 prefix 를 현재 요청 경로(context)가 아니라 대상 path(예: redirect_to) 에서
177
+ // 직접 파싱한다. checkin 시점의 요청 경로는 '/auth/checkin' 이라 prefix 가 비어
178
+ // 정규식이 '^//(...)' 가 되어 '/project/2026-00019/...' 같은 redirect_to 와 매칭 실패하던 문제 수정.
179
+ // getPathInfo 는 domainTypes(예: ['domain','project']) 중 어떤 prefix 든 인식한다.
180
+ return (0, utils_1.getPathInfo)(pathname, domainTypes)?.subdomain;
178
181
  }
179
182
  /**
180
183
  * Retrieves the route prefix for a specific domain type.
@@ -1 +1 @@
1
- {"version":3,"file":"get-domain.js","sourceRoot":"","sources":["../../server/utils/get-domain.ts"],"names":[],"mappings":"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","sourcesContent":["import { URL } from 'url'\n\nimport { config } from '@things-factory/env'\nimport { getPathInfo } from '@things-factory/utils'\n\nimport { getRepository } from '../initializers/database'\nimport { Domain } from '../service/domain/domain'\n\nconst protocol: string = config.get('protocol')\nconst fixed = config.get('subdomain')\nconst domainTypes = config.get('domainTypes') || ['domain']\n\n/* 하위 호환성을 위해서, domain prefix가 없는 경우, domain을 추가해준다. */\nif (domainTypes.indexOf('domain') < 0) {\n domainTypes.push('domain')\n}\n\nconst routePrefixForDomainType = `:domainType(${domainTypes.join('|')})`\n\n/**\n * Creates a URL based on the given context and path.\n *\n * @param context {Object} An object containing the current request context information.\n * @param path {string} The path to be added to the created URL (optional).\n * @returns {URL} The generated URL object.\n */\nexport function getUrlFromContext(context, path = '') {\n const { method, href, host, header } = context\n const { referer } = header || {}\n\n var base = href\n\n if (method !== 'GET' && referer) {\n var { host: refererHost } = new URL(referer)\n if (refererHost === host) {\n base = referer\n }\n }\n\n let url: URL = new URL(path, base)\n\n const originalProtocol = context.headers['x-forwarded-proto']\n const originalHost = context.headers['x-forwarded-host']\n const originalPort = context.headers['x-forwarded-port']\n\n if (protocol || originalProtocol) {\n url.protocol = protocol || originalProtocol\n }\n\n if (originalHost) {\n url.host = originalHost\n if (originalPort) {\n url.port = originalPort\n }\n }\n\n return url\n}\n\n/**\n * Extracts a subdomain from the path.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {string} The extracted subdomain.\n */\nfunction getSubdomainFromPath(context: any) {\n var { path } = context\n\n var subdomain = getPathInfo(path || '', domainTypes)?.subdomain\n if (subdomain) {\n return subdomain\n }\n\n var {\n header: { referer }\n } = context\n\n if (referer) {\n var { pathname } = new URL(referer)\n return getPathInfo(pathname || '', domainTypes)?.subdomain\n }\n}\n\n/**\n * Extracts the subdomain type from the given context's path or referer header.\n *\n * @param context - The context object containing path and header information.\n * @returns The subdomain type if found, otherwise undefined.\n */\nfunction getSubdomainTypeFromPath(context: any) {\n var { path } = context\n\n var prefix = getPathInfo(path || '', domainTypes)?.prefix\n if (prefix) {\n return prefix\n }\n\n var {\n header: { referer }\n } = context\n\n if (referer) {\n var { pathname } = new URL(referer)\n return getPathInfo(pathname || '', domainTypes)?.prefix\n }\n}\n\n/**\n * Extracts a subdomain from the URL context.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {string} The extracted subdomain.\n */\nfunction getSubdomainFromURL(context) {\n return fixed || getSubdomainFromPath(context)\n}\n\nfunction getSubdomainTypeFromURL(context) {\n return fixed ? '' : getSubdomainTypeFromPath(context)\n}\n\n/**\n * Asynchronously searches for a domain object based on the URL context.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {Promise<Domain>} A promise that resolves to the domain object.\n */\nexport async function getDomainFromURL(context: any): Promise<Domain> {\n const { header } = context\n\n const subdomain = header['x-things-factory-domain'] || getSubdomainFromURL(context)\n const extType = header['x-things-factory-type'] || getSubdomainTypeFromURL(context)\n\n if (subdomain) {\n return await getRepository(Domain).findOne({\n where:\n fixed || extType == 'domain' || !extType\n ? {\n subdomain\n }\n : {\n subdomain,\n extType\n },\n cache: true\n })\n }\n}\n\n/**\n * Generates a context path based on the subdomain.\n *\n * @param subdomain {string} The subdomain.\n * @returns {string} The generated context path.\n */\nexport function getContextPath(domain: Domain) {\n const type = domain?.extType || 'domain'\n return fixed || !domain ? '' : `${type}/${domain?.subdomain}/`\n}\n\n/**\n * Generates a redirection path considering the subdomain.\n *\n * @param context {Object} An object containing the current request context information.\n * @param subdomain {Domain} The target domain.\n * @param redirectTo {string} The path to redirect to (optional).\n * @returns {string} The generated redirection path.\n */\nexport function getRedirectSubdomainPath(context, domain: Partial<Domain>, redirectTo: string = '/') {\n if (fixed || !domain) {\n return redirectTo || '/'\n }\n\n const type = domain.extType || 'domain'\n const subdomain = domain.subdomain || fixed\n\n var parsed = getUrlFromContext(context, redirectTo)\n var { hostname, pathname } = parsed\n\n const contextPath = `/${type}/${subdomain}`\n const match = pathname.match(new RegExp(`^/${type}/([^\\/]+)`))\n\n if (match) {\n parsed.pathname = pathname.replace(match[0], contextPath)\n } else {\n parsed.pathname = `${contextPath}${pathname}`\n }\n\n return parsed.toString()\n}\n\n/**\n * Finds a subdomain from the given path.\n *\n * @param context {Object} An object containing the current request context information.\n * @param path {string} The path to search in.\n * @returns {string} The found subdomain.\n */\nexport function findSubdomainFromPath(context, path) {\n if (fixed) {\n return fixed\n }\n\n var parsed = getUrlFromContext(context, path)\n var { pathname } = parsed\n\n const match = pathname.match(new RegExp(`^/${getSubdomainTypeFromURL(context)}/([^\\/]+)`))\n return match && match[1]\n}\n\n/**\n * Retrieves the route prefix for a specific domain type.\n *\n * @returns {string} The route prefix associated with the domain type.\n */\nexport function getRoutePrefixForDomainType() {\n return routePrefixForDomainType\n}\n\n/**\n * Generates a site root path based on the current environment.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {string} The generated site root path.\n */\nexport function getSiteRootPath(context) {\n return '/'\n}\n"]}
1
+ {"version":3,"file":"get-domain.js","sourceRoot":"","sources":["../../server/utils/get-domain.ts"],"names":[],"mappings":"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","sourcesContent":["import { URL } from 'url'\n\nimport { config } from '@things-factory/env'\nimport { getPathInfo } from '@things-factory/utils'\n\nimport { getRepository } from '../initializers/database'\nimport { Domain } from '../service/domain/domain'\n\nconst protocol: string = config.get('protocol')\nconst fixed = config.get('subdomain')\nconst domainTypes = config.get('domainTypes') || ['domain']\n\n/* 하위 호환성을 위해서, domain prefix가 없는 경우, domain을 추가해준다. */\nif (domainTypes.indexOf('domain') < 0) {\n domainTypes.push('domain')\n}\n\nconst routePrefixForDomainType = `:domainType(${domainTypes.join('|')})`\n\n/**\n * Creates a URL based on the given context and path.\n *\n * @param context {Object} An object containing the current request context information.\n * @param path {string} The path to be added to the created URL (optional).\n * @returns {URL} The generated URL object.\n */\nexport function getUrlFromContext(context, path = '') {\n const { method, href, host, header } = context\n const { referer } = header || {}\n\n var base = href\n\n if (method !== 'GET' && referer) {\n var { host: refererHost } = new URL(referer)\n if (refererHost === host) {\n base = referer\n }\n }\n\n let url: URL = new URL(path, base)\n\n const originalProtocol = context.headers['x-forwarded-proto']\n const originalHost = context.headers['x-forwarded-host']\n const originalPort = context.headers['x-forwarded-port']\n\n if (protocol || originalProtocol) {\n url.protocol = protocol || originalProtocol\n }\n\n if (originalHost) {\n url.host = originalHost\n if (originalPort) {\n url.port = originalPort\n }\n }\n\n return url\n}\n\n/**\n * Extracts a subdomain from the path.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {string} The extracted subdomain.\n */\nfunction getSubdomainFromPath(context: any) {\n var { path } = context\n\n var subdomain = getPathInfo(path || '', domainTypes)?.subdomain\n if (subdomain) {\n return subdomain\n }\n\n var {\n header: { referer }\n } = context\n\n if (referer) {\n var { pathname } = new URL(referer)\n return getPathInfo(pathname || '', domainTypes)?.subdomain\n }\n}\n\n/**\n * Extracts the subdomain type from the given context's path or referer header.\n *\n * @param context - The context object containing path and header information.\n * @returns The subdomain type if found, otherwise undefined.\n */\nfunction getSubdomainTypeFromPath(context: any) {\n var { path } = context\n\n var prefix = getPathInfo(path || '', domainTypes)?.prefix\n if (prefix) {\n return prefix\n }\n\n var {\n header: { referer }\n } = context\n\n if (referer) {\n var { pathname } = new URL(referer)\n return getPathInfo(pathname || '', domainTypes)?.prefix\n }\n}\n\n/**\n * Extracts a subdomain from the URL context.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {string} The extracted subdomain.\n */\nfunction getSubdomainFromURL(context) {\n return fixed || getSubdomainFromPath(context)\n}\n\nfunction getSubdomainTypeFromURL(context) {\n return fixed ? '' : getSubdomainTypeFromPath(context)\n}\n\n/**\n * Asynchronously searches for a domain object based on the URL context.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {Promise<Domain>} A promise that resolves to the domain object.\n */\nexport async function getDomainFromURL(context: any): Promise<Domain> {\n const { header } = context\n\n const subdomain = header['x-things-factory-domain'] || getSubdomainFromURL(context)\n const extType = header['x-things-factory-type'] || getSubdomainTypeFromURL(context)\n\n if (subdomain) {\n return await getRepository(Domain).findOne({\n where:\n fixed || extType == 'domain' || !extType\n ? {\n subdomain\n }\n : {\n subdomain,\n extType\n },\n cache: true\n })\n }\n}\n\n/**\n * Generates a context path based on the subdomain.\n *\n * @param subdomain {string} The subdomain.\n * @returns {string} The generated context path.\n */\nexport function getContextPath(domain: Domain) {\n const type = domain?.extType || 'domain'\n return fixed || !domain ? '' : `${type}/${domain?.subdomain}/`\n}\n\n/**\n * Generates a redirection path considering the subdomain.\n *\n * @param context {Object} An object containing the current request context information.\n * @param subdomain {Domain} The target domain.\n * @param redirectTo {string} The path to redirect to (optional).\n * @returns {string} The generated redirection path.\n */\nexport function getRedirectSubdomainPath(context, domain: Partial<Domain>, redirectTo: string = '/') {\n if (fixed || !domain) {\n return redirectTo || '/'\n }\n\n const type = domain.extType || 'domain'\n const subdomain = domain.subdomain || fixed\n\n var parsed = getUrlFromContext(context, redirectTo)\n var { hostname, pathname } = parsed\n\n const contextPath = `/${type}/${subdomain}`\n const match = pathname.match(new RegExp(`^/${type}/([^\\/]+)`))\n\n if (match) {\n parsed.pathname = pathname.replace(match[0], contextPath)\n } else {\n parsed.pathname = `${contextPath}${pathname}`\n }\n\n return parsed.toString()\n}\n\n/**\n * Finds a subdomain from the given path.\n *\n * @param context {Object} An object containing the current request context information.\n * @param path {string} The path to search in.\n * @returns {string} The found subdomain.\n */\nexport function findSubdomainFromPath(context, path) {\n if (fixed) {\n return fixed\n }\n\n var parsed = getUrlFromContext(context, path)\n var { pathname } = parsed\n\n // 도메인 타입 prefix 를 현재 요청 경로(context)가 아니라 대상 path(예: redirect_to) 에서\n // 직접 파싱한다. checkin 시점의 요청 경로는 '/auth/checkin' 이라 prefix 가 비어\n // 정규식이 '^//(...)' 가 되어 '/project/2026-00019/...' 같은 redirect_to 와 매칭 실패하던 문제 수정.\n // getPathInfo 는 domainTypes(예: ['domain','project']) 중 어떤 prefix 든 인식한다.\n return getPathInfo(pathname, domainTypes)?.subdomain\n}\n\n/**\n * Retrieves the route prefix for a specific domain type.\n *\n * @returns {string} The route prefix associated with the domain type.\n */\nexport function getRoutePrefixForDomainType() {\n return routePrefixForDomainType\n}\n\n/**\n * Generates a site root path based on the current environment.\n *\n * @param context {Object} An object containing the current request context information.\n * @returns {string} The generated site root path.\n */\nexport function getSiteRootPath(context) {\n return '/'\n}\n"]}
@@ -2,16 +2,45 @@ import { Repository, SelectQueryBuilder } from 'typeorm';
2
2
  import { ListParam } from '../service/common-types/list-param.js';
3
3
  import { Domain } from '../service/domain/domain.js';
4
4
  /**
5
- * Creates a TypeORM SelectQueryBuilder based on the provided parameters.
5
+ * `search` 연산자의 매칭 방식.
6
+ * - contains(기본): `%v%` — 어디든 포함(유연하나 인덱스 미활용).
7
+ * - startsWith: `v%` — 접두 일치(인덱스 활용 가능 → 대용량에서 빠름).
8
+ * - exact: `v` — 정확 일치(대소문자 무시).
9
+ */
10
+ export type SearchMode = 'contains' | 'startsWith' | 'exact';
11
+ /**
12
+ * Builds a TypeORM SelectQueryBuilder from a generic ListParam (filters, search, sortings,
13
+ * pagination) plus optional multi-tenant domain scoping. Returns the builder so callers can
14
+ * further chain joins/select/where before executing (e.g. getMany / getManyAndCount).
15
+ *
16
+ * Supported filter operators (`Filter.operator`):
17
+ * eq, noteq, like, nlike, i_like, i_nlike, search, lt, gt, lte, gte,
18
+ * in, notin, notin_with_null, between,
19
+ * is_null, is_not_null, is_true, is_false, is_not_true, is_not_false,
20
+ * is_present, is_blank, is_empty_num_id.
21
+ * An unknown operator throws a clear, actionable error.
22
+ *
23
+ * Behavior notes:
24
+ * - `search` uses case-insensitive substring match (`LOWER(col) LIKE %v%`) and requires the column
25
+ * to be listed in `searchables`.
26
+ * - `like`/`i_like` filters are IGNORED (with a console.warn) unless the column is in `searchables`,
27
+ * to avoid unindexed full-table scans.
28
+ * - Boolean operators are emitted as parameter-bound comparisons (portable across all supported DBs).
29
+ * - Domain scoping is applied only when `domain` is provided, honoring `params.inherited`
30
+ * (None = own domain, Include = own + parent, Only = parent).
31
+ * - Pagination uses skip/take; with joins this triggers TypeORM's two-query strategy for correctness.
32
+ * - Unknown filter columns / operators / relations throw a clear, actionable error (fail-fast).
6
33
  *
7
- * @param options - An object containing the query building options.
8
- * @param options.repository - The TypeORM repository for database operations.
9
- * @param options.params - The ListParam object containing filters, sortings, and pagination.
10
- * @param [options.domain] - Optional domain object for applying domain-specific filters.
11
- * @param [options.alias] - The alias to be used in the SQL queries.
12
- * @param [options.searchables] - List of columns that are searchable.
13
- * @param [options.filtersMap] - Mapping of filter names to their corresponding columns or relation columns.
14
- * @returns {SelectQueryBuilder<Type>} - The constructed SelectQueryBuilder instance.
34
+ * @param options.repository TypeORM repository (or a tx-scoped repository) to build from.
35
+ * @param options.params ListParam: `{ filters, sortings, pagination, inherited }`.
36
+ * @param [options.domain] Optional current domain `{ id, parentId }` for tenant scoping.
37
+ * @param [options.alias] Optional entity alias (defaults to the entity name).
38
+ * @param [options.searchables] Columns eligible for `search`/`like` filtering.
39
+ * @param [options.filtersMap] Maps a filter/sort name to a column or relation column, e.g.
40
+ * `{ ownerName: { columnName: 'name', relationColumn: 'owner' } }`.
41
+ * @param [options.searchMode] `search` matching mode: 'contains'(default) / 'startsWith' / 'exact'.
42
+ * 'startsWith' emits `v%` (prefix) which can use an index on large tables.
43
+ * @returns {SelectQueryBuilder<Type>} The constructed SelectQueryBuilder (not yet executed).
15
44
  */
16
45
  export declare function getQueryBuilderFromListParams<Type>(options: {
17
46
  repository: Repository<Type>;
@@ -25,4 +54,5 @@ export declare function getQueryBuilderFromListParams<Type>(options: {
25
54
  relationColumn?: string;
26
55
  };
27
56
  };
57
+ searchMode?: SearchMode;
28
58
  }): SelectQueryBuilder<Type>;
@@ -3,20 +3,43 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getQueryBuilderFromListParams = getQueryBuilderFromListParams;
4
4
  const typeorm_1 = require("typeorm");
5
5
  const list_param_js_1 = require("../service/common-types/list-param.js");
6
+ const apply_domain_filter_js_1 = require("./apply-domain-filter.js");
6
7
  /**
7
- * Creates a TypeORM SelectQueryBuilder based on the provided parameters.
8
+ * Builds a TypeORM SelectQueryBuilder from a generic ListParam (filters, search, sortings,
9
+ * pagination) plus optional multi-tenant domain scoping. Returns the builder so callers can
10
+ * further chain joins/select/where before executing (e.g. getMany / getManyAndCount).
8
11
  *
9
- * @param options - An object containing the query building options.
10
- * @param options.repository - The TypeORM repository for database operations.
11
- * @param options.params - The ListParam object containing filters, sortings, and pagination.
12
- * @param [options.domain] - Optional domain object for applying domain-specific filters.
13
- * @param [options.alias] - The alias to be used in the SQL queries.
14
- * @param [options.searchables] - List of columns that are searchable.
15
- * @param [options.filtersMap] - Mapping of filter names to their corresponding columns or relation columns.
16
- * @returns {SelectQueryBuilder<Type>} - The constructed SelectQueryBuilder instance.
12
+ * Supported filter operators (`Filter.operator`):
13
+ * eq, noteq, like, nlike, i_like, i_nlike, search, lt, gt, lte, gte,
14
+ * in, notin, notin_with_null, between,
15
+ * is_null, is_not_null, is_true, is_false, is_not_true, is_not_false,
16
+ * is_present, is_blank, is_empty_num_id.
17
+ * An unknown operator throws a clear, actionable error.
18
+ *
19
+ * Behavior notes:
20
+ * - `search` uses case-insensitive substring match (`LOWER(col) LIKE %v%`) and requires the column
21
+ * to be listed in `searchables`.
22
+ * - `like`/`i_like` filters are IGNORED (with a console.warn) unless the column is in `searchables`,
23
+ * to avoid unindexed full-table scans.
24
+ * - Boolean operators are emitted as parameter-bound comparisons (portable across all supported DBs).
25
+ * - Domain scoping is applied only when `domain` is provided, honoring `params.inherited`
26
+ * (None = own domain, Include = own + parent, Only = parent).
27
+ * - Pagination uses skip/take; with joins this triggers TypeORM's two-query strategy for correctness.
28
+ * - Unknown filter columns / operators / relations throw a clear, actionable error (fail-fast).
29
+ *
30
+ * @param options.repository TypeORM repository (or a tx-scoped repository) to build from.
31
+ * @param options.params ListParam: `{ filters, sortings, pagination, inherited }`.
32
+ * @param [options.domain] Optional current domain `{ id, parentId }` for tenant scoping.
33
+ * @param [options.alias] Optional entity alias (defaults to the entity name).
34
+ * @param [options.searchables] Columns eligible for `search`/`like` filtering.
35
+ * @param [options.filtersMap] Maps a filter/sort name to a column or relation column, e.g.
36
+ * `{ ownerName: { columnName: 'name', relationColumn: 'owner' } }`.
37
+ * @param [options.searchMode] `search` matching mode: 'contains'(default) / 'startsWith' / 'exact'.
38
+ * 'startsWith' emits `v%` (prefix) which can use an index on large tables.
39
+ * @returns {SelectQueryBuilder<Type>} The constructed SelectQueryBuilder (not yet executed).
17
40
  */
18
41
  function getQueryBuilderFromListParams(options) {
19
- const { repository, params, domain, alias, searchables, filtersMap = {} } = options;
42
+ const { repository, params, domain, alias, searchables, filtersMap = {}, searchMode = 'contains' } = options;
20
43
  const { inherited = list_param_js_1.InheritedValueType.None } = params || {};
21
44
  const selectQueryBuilder = repository.createQueryBuilder(alias);
22
45
  const entityAlias = selectQueryBuilder.alias;
@@ -49,33 +72,20 @@ function getQueryBuilderFromListParams(options) {
49
72
  // Apply column filters
50
73
  if (columnFilters.length > 0) {
51
74
  columnFilters.forEach(filter => {
52
- addCondition(metadata, selectQueryBuilder, selectQueryBuilder, filter, filtersMap, true);
75
+ addCondition(metadata, selectQueryBuilder, selectQueryBuilder, filter, filtersMap, true, searchMode);
53
76
  });
54
77
  }
55
78
  // Apply search filters
56
79
  if (searchFilters.length > 0) {
57
80
  selectQueryBuilder.andWhere(new typeorm_1.Brackets(qb => {
58
81
  searchFilters.forEach(filter => {
59
- addCondition(metadata, selectQueryBuilder, qb, filter, filtersMap, false);
82
+ addCondition(metadata, selectQueryBuilder, qb, filter, filtersMap, false, searchMode);
60
83
  });
61
84
  }));
62
85
  }
63
86
  // Apply domain filters
64
87
  if (domain) {
65
- if (!inherited || inherited === list_param_js_1.InheritedValueType.None) {
66
- selectQueryBuilder.andWhere(`${entityAlias}.domain = :domain`, { domain: domain.id });
67
- }
68
- else if (inherited === list_param_js_1.InheritedValueType.Include) {
69
- selectQueryBuilder.andWhere(`${entityAlias}.domain IN (:...domains)`, {
70
- domains: [domain.id, domain.parentId].filter(Boolean)
71
- });
72
- }
73
- else if (inherited === list_param_js_1.InheritedValueType.Only) {
74
- selectQueryBuilder.andWhere(`${entityAlias}.domain = :domain`, { domain: domain.parentId || 'Impossible' });
75
- }
76
- else {
77
- selectQueryBuilder.andWhere(`${entityAlias}.domain = :domain`, { domain: 'Impossible' });
78
- }
88
+ (0, apply_domain_filter_js_1.applyDomainInheritanceFilter)(selectQueryBuilder, entityAlias, domain, inherited);
79
89
  }
80
90
  // Apply pagination
81
91
  addPagination(selectQueryBuilder, pagination);
@@ -112,7 +122,7 @@ function addPagination(selectQueryBuilder, pagination) {
112
122
  * @param filtersMap - A mapping of filter names to column names and relation column names.
113
123
  * @param andCondition - A flag indicating whether to use "AND" or "OR" for combining conditions.
114
124
  */
115
- function addCondition(metadata, selectQueryBuilder, whereExpressionBuilder, filter, filtersMap = {}, andCondition) {
125
+ function addCondition(metadata, selectQueryBuilder, whereExpressionBuilder, filter, filtersMap = {}, andCondition, searchMode = 'contains') {
116
126
  const { name, operator, value } = filter;
117
127
  var entityAlias = selectQueryBuilder.alias;
118
128
  var { relationColumn, columnName } = filtersMap[name] || {};
@@ -166,9 +176,12 @@ function addCondition(metadata, selectQueryBuilder, whereExpressionBuilder, filt
166
176
  columnMeta = relationIdMeta.relation.joinColumns[0];
167
177
  }
168
178
  else {
169
- columnName
170
- ? console.warn(`columnName "${columnName}" in filtersMap for "${name}" is not a column`)
171
- : console.warn(`name "${name}" is not a column`);
179
+ /* 미지의 컬럼: 이전엔 warn 후 흘러가 columnMeta.databaseName 에서 불투명 TypeError 로 크래시했다.
180
+ 원인을 바로 있도록 명확·실행가능한 오류를 던진다(fail-fast). */
181
+ const target = columnName || name;
182
+ const available = metadata.columns.map(column => column.propertyName).join(', ');
183
+ throw new Error(`[getQueryBuilderFromListParams] unknown filter column "${target}" on entity "${metadata.name}". ` +
184
+ `Add a filtersMap entry for "${name}", or use one of: ${available}`);
172
185
  }
173
186
  }
174
187
  else {
@@ -191,7 +204,7 @@ function addCondition(metadata, selectQueryBuilder, whereExpressionBuilder, filt
191
204
  const alias = relationColumnMeta ? `${name}-filter` : entityAlias;
192
205
  /* relation columne인 경우 name을 alias로 사용한다. */
193
206
  const field = `${alias}.${dbNameForColumn}`;
194
- var { clause, parameters } = getClauseAndParameters(field, name, operator, value);
207
+ var { clause, parameters } = getClauseAndParameters(field, name, operator, value, searchMode);
195
208
  if (relationColumnMeta) {
196
209
  const { propertyName } = relationColumnMeta;
197
210
  const property = `${entityAlias}.${propertyName}`;
@@ -272,7 +285,9 @@ function applyJoins(selectQueryBuilder, entityAlias, relationColumn, metadata, j
272
285
  for (const column of columns) {
273
286
  const relation = currentMetadata.relations.find(rel => rel.propertyName === column);
274
287
  if (!relation) {
275
- throw new Error(`Relation not found for column: ${column}`);
288
+ const available = currentMetadata.relations.map(rel => rel.propertyName).join(', ');
289
+ throw new Error(`[getQueryBuilderFromListParams] relation "${column}" not found on entity "${currentMetadata.name}"` +
290
+ ` (relationColumn "${relationColumn}"). Available relations: ${available}`);
276
291
  }
277
292
  const nextAlias = `${currentAlias}_${relation.inverseEntityMetadata.tableName}_for_${columnName}`;
278
293
  if (!selectQueryBuilder.expressionMap.aliases.some(alias => alias.name === nextAlias)) {
@@ -286,6 +301,18 @@ function applyJoins(selectQueryBuilder, entityAlias, relationColumn, metadata, j
286
301
  }
287
302
  return currentAlias;
288
303
  }
304
+ /** search 값에 searchMode 에 맞는 와일드카드를 씌운다. */
305
+ function wrapSearch(value, mode) {
306
+ switch (mode) {
307
+ case 'startsWith':
308
+ return `${value}%`;
309
+ case 'exact':
310
+ return value;
311
+ case 'contains':
312
+ default:
313
+ return `%${value}%`;
314
+ }
315
+ }
289
316
  /**
290
317
  * Generates the SQL clause and parameters based on the provided filter.
291
318
  *
@@ -295,7 +322,7 @@ function applyJoins(selectQueryBuilder, entityAlias, relationColumn, metadata, j
295
322
  * @param value - The value to filter with.
296
323
  * @returns An object containing the SQL clause and the parameters.
297
324
  */
298
- function getClauseAndParameters(field, name, operator, value) {
325
+ function getClauseAndParameters(field, name, operator, value, searchMode = 'contains') {
299
326
  const values = value instanceof Array ? value : [value];
300
327
  let clause = '';
301
328
  let parameters = {};
@@ -309,6 +336,10 @@ function getClauseAndParameters(field, name, operator, value) {
309
336
  parameters = { [name]: `%${value}%` };
310
337
  break;
311
338
  case 'search':
339
+ /* searchMode 로 매칭 방식 선택(기본 contains). startsWith 는 접두 인덱스 활용 가능. */
340
+ clause = `LOWER(${field}) LIKE :${name}`;
341
+ parameters = { [name]: wrapSearch(String(value).toLowerCase(), searchMode) };
342
+ break;
312
343
  case 'i_like':
313
344
  clause = `LOWER(${field}) LIKE :${name}`;
314
345
  parameters = { [name]: `%${String(value).toLowerCase()}%` };
@@ -359,31 +390,43 @@ function getClauseAndParameters(field, name, operator, value) {
359
390
  case 'is_not_null':
360
391
  clause = `${field} IS NOT NULL`;
361
392
  break;
393
+ /* boolean 비교는 파라미터 바인딩으로 (IS TRUE/FALSE 는 MSSQL/Oracle 미지원 → 이식성 확보) */
362
394
  case 'is_false':
363
- clause = `${field} IS FALSE`;
395
+ clause = `${field} = :${name}`;
396
+ parameters = { [name]: false };
364
397
  break;
365
398
  case 'is_true':
366
- clause = `${field} IS TRUE`;
399
+ clause = `${field} = :${name}`;
400
+ parameters = { [name]: true };
367
401
  break;
368
402
  case 'is_not_false':
369
- clause = `${field} IS NOT FALSE`;
403
+ /* NOT FALSE = true 또는 NULL */
404
+ clause = `(${field} IS NULL OR ${field} = :${name})`;
405
+ parameters = { [name]: true };
370
406
  break;
371
407
  case 'is_not_true':
372
- clause = `${field} IS NOT TRUE`;
408
+ /* NOT TRUE = false 또는 NULL */
409
+ clause = `(${field} IS NULL OR ${field} = :${name})`;
410
+ parameters = { [name]: false };
373
411
  break;
374
412
  case 'is_present':
375
- clause = `${field} IS PRESENT`;
413
+ clause = `${field} IS NOT NULL`;
376
414
  break;
377
415
  case 'is_blank':
378
- clause = `${field} IS BLANK`;
416
+ clause = `${field} IS NULL`;
379
417
  break;
380
418
  case 'is_empty_num_id':
381
- clause = `${field} IS EMPTY NUMERIC ID`;
419
+ clause = `(${field} IS NULL OR ${field} = :${name})`;
420
+ parameters = { [name]: 0 };
382
421
  break;
383
422
  case 'between':
384
423
  clause = `${field} BETWEEN :${name}_1 AND :${name}_2`;
385
424
  parameters = { [`${name}_1`]: values[0], [`${name}_2`]: values[1] };
386
425
  break;
426
+ default:
427
+ /* 지원하지 않는 연산자: 이전엔 clause='' 로 andWhere('') 가 되어 malformed/무음이었다.
428
+ 명확한 오류로 원인을 알린다(fail-fast). */
429
+ throw new Error(`[getQueryBuilderFromListParams] unsupported filter operator "${operator}" for field "${field}".`);
387
430
  }
388
431
  return { clause, parameters };
389
432
  }