@things-factory/shell 10.0.19 → 10.1.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.
- package/client/themes/tooltip-theme.css +27 -5
- package/client/themes/ui-tokens.css +85 -5
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/typeorm/long-text-column.d.ts +33 -0
- package/dist-server/typeorm/long-text-column.js +48 -0
- package/dist-server/typeorm/long-text-column.js.map +1 -0
- package/dist-server/utils/get-query-builder-from-list-params.js +34 -1
- package/dist-server/utils/get-query-builder-from-list-params.js.map +1 -1
- package/package.json +4 -4
- package/tests/relation-sort-camelcase-alias.test.ts +131 -0
- package/translations/en.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 긴 문자열(모델 JSON · base64 썸네일 등) 컬럼의 **드라이버별 타입**.
|
|
3
|
+
*
|
|
4
|
+
* `varchar` 는 드라이버마다 상한이 다르고, 상한을 넘으면 개발용 sqlite 에서는 그냥
|
|
5
|
+
* 들어가지만 운영 드라이버에서 잘리거나 터진다. 그래서 드라이버를 보고 타입을 고른다.
|
|
6
|
+
*
|
|
7
|
+
* | 드라이버 | 타입 |
|
|
8
|
+
* | -------------- | ----------------- |
|
|
9
|
+
* | mysql·mariadb | `longtext` |
|
|
10
|
+
* | oracle | `clob` |
|
|
11
|
+
* | mssql | `nvarchar(MAX)` |
|
|
12
|
+
* | 그 외(pg·sqlite) | `varchar` (상한 없음) |
|
|
13
|
+
*
|
|
14
|
+
* ## 왜 공용으로 두나
|
|
15
|
+
*
|
|
16
|
+
* 이 분기가 지금 **48개 파일에 그대로 베껴져 있다.** 베낀 자리 하나만 고쳐지면 나머지
|
|
17
|
+
* 47곳은 조용히 옛 판단을 유지한다 — 드라이버가 늘거나 상한이 바뀔 때 그것이 드러난다.
|
|
18
|
+
* 판단을 한 자리에 둔다.
|
|
19
|
+
*
|
|
20
|
+
* ## 쓰는 법
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* @Column({ nullable: true, ...longTextColumn() })
|
|
24
|
+
* @Field({ nullable: true })
|
|
25
|
+
* source?: string
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* `nullable` 같은 나머지 옵션은 호출하는 쪽이 정한다 — 이 함수는 **타입만** 정한다.
|
|
29
|
+
*/
|
|
30
|
+
export declare function longTextColumn(): {
|
|
31
|
+
type: any;
|
|
32
|
+
length?: any;
|
|
33
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.longTextColumn = longTextColumn;
|
|
4
|
+
const env_1 = require("@things-factory/env");
|
|
5
|
+
const ORMCONFIG = env_1.config.get('ormconfig', {});
|
|
6
|
+
const DATABASE_TYPE = ORMCONFIG.type;
|
|
7
|
+
/**
|
|
8
|
+
* 긴 문자열(모델 JSON · base64 썸네일 등) 컬럼의 **드라이버별 타입**.
|
|
9
|
+
*
|
|
10
|
+
* `varchar` 는 드라이버마다 상한이 다르고, 상한을 넘으면 개발용 sqlite 에서는 그냥
|
|
11
|
+
* 들어가지만 운영 드라이버에서 잘리거나 터진다. 그래서 드라이버를 보고 타입을 고른다.
|
|
12
|
+
*
|
|
13
|
+
* | 드라이버 | 타입 |
|
|
14
|
+
* | -------------- | ----------------- |
|
|
15
|
+
* | mysql·mariadb | `longtext` |
|
|
16
|
+
* | oracle | `clob` |
|
|
17
|
+
* | mssql | `nvarchar(MAX)` |
|
|
18
|
+
* | 그 외(pg·sqlite) | `varchar` (상한 없음) |
|
|
19
|
+
*
|
|
20
|
+
* ## 왜 공용으로 두나
|
|
21
|
+
*
|
|
22
|
+
* 이 분기가 지금 **48개 파일에 그대로 베껴져 있다.** 베낀 자리 하나만 고쳐지면 나머지
|
|
23
|
+
* 47곳은 조용히 옛 판단을 유지한다 — 드라이버가 늘거나 상한이 바뀔 때 그것이 드러난다.
|
|
24
|
+
* 판단을 한 자리에 둔다.
|
|
25
|
+
*
|
|
26
|
+
* ## 쓰는 법
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* @Column({ nullable: true, ...longTextColumn() })
|
|
30
|
+
* @Field({ nullable: true })
|
|
31
|
+
* source?: string
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* `nullable` 같은 나머지 옵션은 호출하는 쪽이 정한다 — 이 함수는 **타입만** 정한다.
|
|
35
|
+
*/
|
|
36
|
+
function longTextColumn() {
|
|
37
|
+
if (DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb') {
|
|
38
|
+
return { type: 'longtext' };
|
|
39
|
+
}
|
|
40
|
+
if (DATABASE_TYPE == 'oracle') {
|
|
41
|
+
return { type: 'clob' };
|
|
42
|
+
}
|
|
43
|
+
if (DATABASE_TYPE == 'mssql') {
|
|
44
|
+
return { type: 'nvarchar', length: 'MAX' };
|
|
45
|
+
}
|
|
46
|
+
return { type: 'varchar' };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=long-text-column.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"long-text-column.js","sourceRoot":"","sources":["../../server/typeorm/long-text-column.ts"],"names":[],"mappings":";;AAkCA,wCAWC;AA7CD,6CAA4C;AAE5C,MAAM,SAAS,GAAG,YAAM,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AAC7C,MAAM,aAAa,GAAG,SAAS,CAAC,IAAI,CAAA;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,SAAgB,cAAc;IAC5B,IAAI,aAAa,IAAI,OAAO,IAAI,aAAa,IAAI,SAAS,EAAE,CAAC;QAC3D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;IAC7B,CAAC;IACD,IAAI,aAAa,IAAI,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzB,CAAC;IACD,IAAI,aAAa,IAAI,OAAO,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC5C,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;AAC5B,CAAC","sourcesContent":["import { config } from '@things-factory/env'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n/**\n * 긴 문자열(모델 JSON · base64 썸네일 등) 컬럼의 **드라이버별 타입**.\n *\n * `varchar` 는 드라이버마다 상한이 다르고, 상한을 넘으면 개발용 sqlite 에서는 그냥\n * 들어가지만 운영 드라이버에서 잘리거나 터진다. 그래서 드라이버를 보고 타입을 고른다.\n *\n * | 드라이버 | 타입 |\n * | -------------- | ----------------- |\n * | mysql·mariadb | `longtext` |\n * | oracle | `clob` |\n * | mssql | `nvarchar(MAX)` |\n * | 그 외(pg·sqlite) | `varchar` (상한 없음) |\n *\n * ## 왜 공용으로 두나\n *\n * 이 분기가 지금 **48개 파일에 그대로 베껴져 있다.** 베낀 자리 하나만 고쳐지면 나머지\n * 47곳은 조용히 옛 판단을 유지한다 — 드라이버가 늘거나 상한이 바뀔 때 그것이 드러난다.\n * 판단을 한 자리에 둔다.\n *\n * ## 쓰는 법\n *\n * ```ts\n * @Column({ nullable: true, ...longTextColumn() })\n * @Field({ nullable: true })\n * source?: string\n * ```\n *\n * `nullable` 같은 나머지 옵션은 호출하는 쪽이 정한다 — 이 함수는 **타입만** 정한다.\n */\nexport function longTextColumn(): { type: any; length?: any } {\n if (DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb') {\n return { type: 'longtext' }\n }\n if (DATABASE_TYPE == 'oracle') {\n return { type: 'clob' }\n }\n if (DATABASE_TYPE == 'mssql') {\n return { type: 'nvarchar', length: 'MAX' }\n }\n return { type: 'varchar' }\n}\n"]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getQueryBuilderFromListParams = getQueryBuilderFromListParams;
|
|
4
4
|
const typeorm_1 = require("typeorm");
|
|
5
|
+
const DriverUtils_js_1 = require("typeorm/driver/DriverUtils.js");
|
|
5
6
|
const list_param_js_1 = require("../service/common-types/list-param.js");
|
|
6
7
|
const apply_domain_filter_js_1 = require("./apply-domain-filter.js");
|
|
7
8
|
const keyset_cursor_js_1 = require("./keyset-cursor.js");
|
|
@@ -368,7 +369,39 @@ function applyJoins(selectQueryBuilder, entityAlias, relationColumn, metadata, j
|
|
|
368
369
|
selectQueryBuilder[joinType](`${currentAlias}.${column}`, nextAlias);
|
|
369
370
|
}
|
|
370
371
|
if (selectField && columns.at(-1) == column /* 최종 alias만 추가 */) {
|
|
371
|
-
|
|
372
|
+
/*
|
|
373
|
+
* The select alias has to be spelled the way TypeORM will look it up.
|
|
374
|
+
*
|
|
375
|
+
* Sorting by a joined column and paginating puts the query on TypeORM's distinct-id path,
|
|
376
|
+
* which wraps everything in a subquery and re-addresses each ORDER BY as
|
|
377
|
+
* `distinctAlias.<buildAlias(aliasName, column.databaseName)>` — the **database** name (see
|
|
378
|
+
* SelectQueryBuilder.createOrderByCombinedWithSelectExpression). This addSelect used the
|
|
379
|
+
* **property** name, so the two only met where the two spellings happen to be identical.
|
|
380
|
+
*
|
|
381
|
+
* name, description and priority are one lowercase word each, so they were identical and it
|
|
382
|
+
* worked by luck. dueAt is not: the property is `dueAt`, the column is `due_at` under the
|
|
383
|
+
* snake_case NamingStrategy, and sorting 내 결재함 by 기한 died with
|
|
384
|
+
*
|
|
385
|
+
* SQLITE_ERROR: no such column:
|
|
386
|
+
* distinctAlias.ap_activity_threads_for_dueAt_activity_instances_for_dueAt_due_at
|
|
387
|
+
*
|
|
388
|
+
* Building the alias the same way TypeORM does makes them agree by construction. Lowercase
|
|
389
|
+
* columns produce the identical string as before, so nothing else moves.
|
|
390
|
+
*/
|
|
391
|
+
const targetColumn = relation.inverseEntityMetadata.findColumnWithPropertyName(columnName);
|
|
392
|
+
if (!targetColumn) {
|
|
393
|
+
/*
|
|
394
|
+
* columnName is a property name, not a database column name — `dueAt`, never `due_at`.
|
|
395
|
+
* Failing loudly here beats an alias nobody can resolve: TypeORM's own lookup on the
|
|
396
|
+
* distinct path dereferences the column without a null check, so a wrong spelling would
|
|
397
|
+
* surface much later as a TypeError with no mention of this map.
|
|
398
|
+
*/
|
|
399
|
+
const available = relation.inverseEntityMetadata.columns.map(col => col.propertyName).join(', ');
|
|
400
|
+
throw new Error(`[getQueryBuilderFromListParams] column "${columnName}" not found on entity` +
|
|
401
|
+
` "${relation.inverseEntityMetadata.name}" (relationColumn "${relationColumn}").` +
|
|
402
|
+
` Use the property name, not the database column name. Available: ${available}`);
|
|
403
|
+
}
|
|
404
|
+
selectQueryBuilder.addSelect(`${nextAlias}.${columnName}`, DriverUtils_js_1.DriverUtils.buildAlias(selectQueryBuilder.connection.driver, undefined, nextAlias, targetColumn.databaseName));
|
|
372
405
|
}
|
|
373
406
|
currentAlias = nextAlias;
|
|
374
407
|
currentMetadata = relation.inverseEntityMetadata;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-query-builder-from-list-params.js","sourceRoot":"","sources":["../../server/utils/get-query-builder-from-list-params.ts"],"names":[],"mappings":"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","sourcesContent":["import { Brackets, EntityMetadata, Repository, SelectQueryBuilder, WhereExpressionBuilder } from 'typeorm'\nimport { RelationMetadata } from 'typeorm/metadata/RelationMetadata.js'\nimport { Filter, Sorting, Pagination, ListParam, InheritedValueType } from '../service/common-types/list-param.js'\nimport { Domain } from '../service/domain/domain.js'\nimport { applyDomainInheritanceFilter } from './apply-domain-filter.js'\nimport { applyKeysetCursor, cursorSortings } from './keyset-cursor.js'\n\n/**\n * `search` 연산자의 매칭 방식.\n * - contains(기본): `%v%` — 어디든 포함(유연하나 인덱스 미활용).\n * - startsWith: `v%` — 접두 일치(인덱스 활용 가능 → 대용량에서 빠름).\n * - exact: `v` — 정확 일치(대소문자 무시).\n */\nexport type SearchMode = 'contains' | 'startsWith' | 'exact'\n\n/*\n * 바인딩 파라미터 이름 발급기 — 같은 컬럼에 조건이 여럿 걸려도 서로 값을 덮어쓰지 않게 한다.\n * 전역 증가 카운터로 충분하다(파라미터는 질의 하나 안에서만 의미가 있고, 유일하기만 하면 된다).\n */\nlet paramSeq = 0\nconst nextParamKey = (name: string): string => `${name.replace(/[^A-Za-z0-9_]/g, '_')}_p${paramSeq++}`\n\n/**\n * Builds a TypeORM SelectQueryBuilder from a generic ListParam (filters, search, sortings,\n * pagination) plus optional multi-tenant domain scoping. Returns the builder so callers can\n * further chain joins/select/where before executing (e.g. getMany / getManyAndCount).\n *\n * Supported filter operators (`Filter.operator`):\n * eq, noteq, like, nlike, i_like, i_nlike, search, lt, gt, lte, gte,\n * in, notin, notin_with_null, between,\n * is_null, is_not_null, is_true, is_false, is_not_true, is_not_false,\n * is_present, is_blank, is_empty_num_id.\n * An unknown operator throws a clear, actionable error.\n *\n * Behavior notes:\n * - `search` uses case-insensitive substring match (`LOWER(col) LIKE %v%`) and requires the column\n * to be listed in `searchables`.\n * - `like`/`i_like` filters are IGNORED (with a console.warn) unless the column is in `searchables`,\n * to avoid unindexed full-table scans.\n * - Boolean operators are emitted as parameter-bound comparisons (portable across all supported DBs).\n * - Domain scoping is applied only when `domain` is provided, honoring `params.inherited`\n * (None = own domain, Include = own + parent, Only = parent).\n * - Pagination uses skip/take; with joins this triggers TypeORM's two-query strategy for correctness.\n * - Unknown filter columns / operators / relations throw a clear, actionable error (fail-fast).\n *\n * @param options.repository TypeORM repository (or a tx-scoped repository) to build from.\n * @param options.params ListParam: `{ filters, sortings, pagination, inherited }`.\n * @param [options.domain] Optional current domain `{ id, parentId }` for tenant scoping.\n * @param [options.alias] Optional entity alias (defaults to the entity name).\n * @param [options.searchables] Columns eligible for `search`/`like` filtering.\n * @param [options.filtersMap] Maps a filter/sort name to a column or relation column, e.g.\n * `{ ownerName: { columnName: 'name', relationColumn: 'owner' } }`.\n * @param [options.searchMode] `search` matching mode: 'contains'(default) / 'startsWith' / 'exact'.\n * 'startsWith' emits `v%` (prefix) which can use an index on large tables.\n * @returns {SelectQueryBuilder<Type>} The constructed SelectQueryBuilder (not yet executed).\n */\nexport function getQueryBuilderFromListParams<Type>(options: {\n repository: Repository<Type>\n params: ListParam\n domain?: Domain\n alias?: string\n searchables?: string[]\n filtersMap?: { [name: string]: { columnName: string; relationColumn?: string } }\n searchMode?: SearchMode\n /**\n * 페이지 크기를 안 준 호출에 씌울 기본 상한.\n *\n * 지정하지 않으면 **예전과 똑같이 무제한**이다(하위호환). 다만 무제한으로 나가는 질의는 경고를 남긴다 —\n * 표가 작을 때는 아무 문제가 없다가 데이터가 늘면 조용히 무너지는 종류의 결함이라, 늘기 전에 보여야 한다.\n */\n defaultLimit?: number\n /** 호출자가 요구한 페이지 크기의 상한. 넘겨도 여기서 잘린다(서버 보호). */\n maxLimit?: number\n /**\n * 정렬 허용 컬럼. 주면 목록에 없는 정렬은 **명시 실패**한다.\n *\n * `searchables` 로 인덱스 없는 LIKE 는 막으면서 정렬은 아무 컬럼이나 받던 비대칭을 메운다 —\n * 인덱스 없는 컬럼 정렬은 큰 표에서 전체 정렬 스캔을 부른다.\n */\n sortables?: string[]\n /**\n * 커서 페이징의 타이브레이커 컬럼. 기본은 엔티티 PK.\n *\n * 마지막 정렬 축은 반드시 **유일**해야 한다 — 같은 값이 여러 행에 있으면 페이지 경계에서\n * 행이 건너뛰어지거나 반복된다.\n */\n cursorTiebreak?: string\n}): SelectQueryBuilder<Type> {\n const {\n repository,\n params,\n domain,\n alias,\n searchables,\n filtersMap = {},\n searchMode = 'contains',\n defaultLimit,\n maxLimit,\n sortables,\n cursorTiebreak\n } = options\n const { inherited = InheritedValueType.None } = params || {}\n\n const selectQueryBuilder = repository.createQueryBuilder(alias)\n const entityAlias = selectQueryBuilder.alias\n\n // Apply filters to the query\n const columnFilters =\n params.filters?.filter(filter => {\n if (filter.operator === 'search') {\n return false\n }\n if (filter.operator.toLowerCase().includes('like') && (!searchables || !searchables.includes(filter.name))) {\n console.warn('\"searchables\" setting is required for LIKE searches to avoid heavy database load', filter.name)\n return false\n }\n return true\n }) || []\n\n /*\n * 전역 검색어(`params.search`) → 서버가 자기 `searchables` 로 필드별 search 필터를 만든다.\n * 클라이언트가 필드 목록을 알 필요가 없고, 서버 목록과 어긋나 조용히 무시될 일도 없다.\n * 아래 searchFilters 와 같은 통(OR 묶음)에 들어가므로, filters 로 보낸 것과 함께 써도 자연스럽게 합쳐진다.\n */\n const term = params.search?.trim()\n const termFilters: Filter[] =\n term && searchables instanceof Array ? searchables.map(name => ({ name, operator: 'search', value: term })) : []\n if (term && !(searchables instanceof Array)) {\n console.warn(\n '[getQueryBuilderFromListParams] params.search was given but this resolver declares no \"searchables\" — ' +\n 'the term was ignored. Declare searchables to make free-text search work.'\n )\n }\n\n const searchFilters =\n searchables instanceof Array\n ? params.filters?.filter(filter => {\n if (filter.operator !== 'search') {\n return false\n }\n if (!searchables.includes(filter.name)) {\n console.warn(\n '\"searchables\" setting is required for LIKE searches to avoid heavy database load',\n filter.name\n )\n return false\n }\n return true\n }) || []\n : []\n\n const pagination = params.pagination\n const sortings = params.sortings\n const metadata = repository.metadata\n\n // Apply column filters\n if (columnFilters.length > 0) {\n columnFilters.forEach(filter => {\n addCondition(metadata, selectQueryBuilder, selectQueryBuilder, filter, filtersMap, true, searchMode)\n })\n }\n\n // Apply search filters (explicit per-field search filters + the expanded global term)\n const allSearchFilters = [...searchFilters, ...termFilters]\n if (allSearchFilters.length > 0) {\n selectQueryBuilder.andWhere(\n new Brackets(qb => {\n allSearchFilters.forEach(filter => {\n addCondition(metadata, selectQueryBuilder, qb, filter, filtersMap, false, searchMode)\n })\n })\n )\n }\n\n // Apply domain filters\n if (domain) {\n applyDomainInheritanceFilter(selectQueryBuilder, entityAlias, domain, inherited)\n }\n\n /* 정렬 화이트리스트 검증은 **호출자가 요구한 축**에만 적용한다(아래에서 우리가 덧붙이는\n * 타이브레이커까지 목록에 넣으라고 요구하면 호출부가 내부 사정을 알아야 한다). */\n if (sortings?.length && sortables) {\n for (const sorting of sortings) {\n if (!sortables.includes(sorting.name)) {\n throw new Error(\n `[getQueryBuilderFromListParams] sorting by \"${sorting.name}\" is not allowed on entity \"${metadata.name}\". ` +\n `Allowed: ${sortables.join(', ')}`\n )\n }\n }\n }\n\n /*\n * 커서(keyset) 페이징 — `after` 가 있으면 offset 을 쓰지 않는다.\n * 정렬 축 끝에 유일 컬럼(기본 PK)을 덧붙여 \"다음 행\" 이 항상 하나로 정해지게 한다.\n */\n const cursor = pagination?.after\n const tiebreak = cursorTiebreak ?? metadata.primaryColumns[0]?.propertyName\n const effectiveSortings = cursor && tiebreak ? cursorSortings(sortings, tiebreak) : sortings\n\n // Apply pagination\n addPagination(selectQueryBuilder, pagination, {\n defaultLimit,\n maxLimit,\n entityName: metadata.name,\n keyset: !!cursor\n })\n\n if (cursor) {\n if (!tiebreak) {\n throw new Error(\n `[getQueryBuilderFromListParams] cursor paging needs a unique tiebreak column but entity \"${metadata.name}\" ` +\n 'exposes no primary column. Pass cursorTiebreak explicitly.'\n )\n }\n applyKeysetCursor(selectQueryBuilder, { alias: entityAlias, sortings: effectiveSortings!, cursor })\n }\n\n // Apply sorting\n if (effectiveSortings && effectiveSortings.length > 0) {\n addSorting(selectQueryBuilder, effectiveSortings, entityAlias, filtersMap, metadata)\n }\n\n return selectQueryBuilder\n}\n\n/**\n * Adds pagination to the SelectQueryBuilder based on the provided Pagination object.\n *\n * @param selectQueryBuilder - The SelectQueryBuilder to which pagination should be applied.\n * @param pagination - The Pagination object containing page and limit information.\n */\nfunction addPagination<T>(\n selectQueryBuilder: SelectQueryBuilder<T>,\n pagination?: Pagination,\n guard: { defaultLimit?: number; maxLimit?: number; entityName?: string; keyset?: boolean } = {}\n) {\n const { defaultLimit, maxLimit, entityName, keyset } = guard\n const { page, limit } = pagination ?? {}\n\n /* 요구된 페이지 크기 → 상한으로 조인다. 상한이 없으면 요구를 그대로 존중(예전 동작). */\n const asked = limit && limit > 0 ? limit : undefined\n const effective = asked === undefined ? defaultLimit : maxLimit ? Math.min(asked, maxLimit) : asked\n\n if (effective === undefined) {\n /* 상한이 아무 데도 없다 = **표 전체가 나간다.** 표가 작을 때는 멀쩡하다가 데이터가 늘면 조용히\n * 무너지는 종류라, 늘기 전에 보이게 한다. 기존 호출부를 깨지 않으려고 막지는 않는다. */\n console.warn(\n `[getQueryBuilderFromListParams] unbounded list query on \"${entityName ?? 'entity'}\" — ` +\n 'no pagination.limit from the caller and no defaultLimit on the resolver, so every matching row will be returned. ' +\n 'Set defaultLimit (and maxLimit) to keep this safe as the table grows.'\n )\n return\n }\n\n /* 커서 모드에서는 offset 을 쓰지 않는다 — 위치가 아니라 값으로 이어 붙이는 것이 커서의 요점이다.\n * (둘을 같이 쓰면 커서로 좁힌 결과를 또 건너뛰어 행이 사라진다.) */\n if (keyset) {\n selectQueryBuilder.take(effective)\n return\n }\n\n if (page && page > 0) {\n selectQueryBuilder.skip(effective * (page - 1)).take(effective)\n } else {\n selectQueryBuilder.take(effective)\n }\n}\n\n/**\n * Adds a filtering condition to the SelectQueryBuilder based on the provided filter and mapping options.\n *\n * @param metadata - The EntityMetadata of the TypeORM entity.\n * @param selectQueryBuilder - The SelectQueryBuilder to which the condition will be added.\n * @param whereExpressionBuilder - The WhereExpressionBuilder to construct the where clause.\n * @param filter - The Filter object containing the filter criteria.\n * @param filtersMap - A mapping of filter names to column names and relation column names.\n * @param andCondition - A flag indicating whether to use \"AND\" or \"OR\" for combining conditions.\n */\nfunction addCondition<T>(\n metadata: EntityMetadata,\n selectQueryBuilder: SelectQueryBuilder<T>,\n whereExpressionBuilder: WhereExpressionBuilder,\n filter: Filter,\n filtersMap: { [name: string]: { columnName: string; relationColumn?: string } } = {},\n andCondition: boolean,\n searchMode: SearchMode = 'contains'\n): void {\n const { name, operator, value } = filter\n var entityAlias = selectQueryBuilder.alias\n\n var { relationColumn, columnName } = filtersMap[name] || {}\n /*\n 1. relationColumn과 columnName이 지정된 경우 \n - relation inverse 테이블에서, columnName을 찾는다.\n 2. relationColumn만 지정된 경우는 없어야 한다.\n - 이 경우 columnName 은 'name' 이라고 판단한다.\n 3. columnName이 지정된 경우.\n - 이 경우는 columnName 만 적용한다.\n */\n if (relationColumn) {\n const columns = relationColumn.split('.')\n var entityMetadata: EntityMetadata\n var relation: RelationMetadata\n\n for (const rcolumn of columns) {\n if (relation) {\n const { propertyName } = relationColumnMeta\n const property = `${entityAlias}.${propertyName}`\n\n entityAlias = `${entityAlias}-${entityMetadata.tableName}-for-${columnName || 'name'}` as string\n\n if (andCondition) {\n selectQueryBuilder.innerJoin(property, entityAlias)\n } else {\n selectQueryBuilder.leftJoin(property, entityAlias)\n }\n } else {\n entityMetadata = metadata\n }\n\n var relationColumnMeta = entityMetadata.columns.find(column => column.propertyName === rcolumn)\n if (!relationColumnMeta) {\n console.warn(`relationColumn \"${relationColumn}\" in filtersMap for \"${name}\" is not a relation column`)\n return\n }\n\n relation = relationColumnMeta.relationMetadata\n entityMetadata = relation.inverseEntityMetadata\n }\n\n var columnMeta = entityMetadata.columns.find(column => column.propertyName === (columnName || 'name'))\n if (!columnMeta) {\n console.warn(`columnName \"${columnName}\" in filtersMap for \"${name}\" is not a column`)\n return\n }\n } else {\n var columnMeta = metadata.columns.find(column => column.propertyName === (columnName || name))\n if (!columnMeta) {\n /* relationId 에 대한 필터링은 해당 컬럼값 자체의 비교로 한다. */\n var relationIdMeta = metadata.relationIds.find(relationId => relationId.propertyName === (columnName || name))\n if (relationIdMeta) {\n columnMeta = relationIdMeta.relation.joinColumns[0]\n } else {\n /* 미지의 컬럼: 이전엔 warn 후 흘러가 columnMeta.databaseName 에서 불투명 TypeError 로 크래시했다.\n 원인을 바로 알 수 있도록 명확·실행가능한 오류를 던진다(fail-fast). */\n const target = columnName || name\n const available = metadata.columns.map(column => column.propertyName).join(', ')\n throw new Error(\n `[getQueryBuilderFromListParams] unknown filter column \"${target}\" on entity \"${metadata.name}\". ` +\n `Add a filtersMap entry for \"${name}\", or use one of: ${available}`\n )\n }\n } else {\n var relation = columnMeta.relationMetadata\n }\n\n if (relation) {\n /* filterMap에 의해서 relationColumn 이 지정되지 않았더라도, name 또는 columnName의 column이 relation인 경우에는\n - 조건절 구성을 위한 타겟필드명은 'name' 으로만 한정된다.\n */\n var relationColumnMeta = columnMeta\n var entityMetadata = relation.inverseEntityMetadata\n columnMeta = entityMetadata.columns.find(column => column.propertyName === 'name')\n if (!columnMeta) {\n console.warn(`relation column \"${columnName || name}\" does not have \"name\" column`)\n return\n }\n }\n }\n\n const dbNameForColumn = columnMeta.databaseName\n const alias = relationColumnMeta ? `${name}-filter` : entityAlias\n\n /* relation columne인 경우 name을 alias로 사용한다. */\n const field = `${alias}.${dbNameForColumn}`\n\n /*\n * 파라미터 이름은 **조건마다 유일**해야 한다.\n *\n * 예전에는 필터 `name` 을 그대로 플레이스홀더로 썼다. 그래서 **같은 컬럼에 조건을 둘 이상** 걸면\n * 둘 다 `:같은이름` 이 되어 뒤엣것이 앞엣것의 값을 덮어썼다. SQL 에는 조건이 멀쩡히 둘 다 있는데\n * 값은 하나만 남으므로, 오류도 경고도 없이 **결과만 틀린다.**\n *\n * (구간 조회는 전용 `between` 연산자가 있고 그쪽은 `:name_1`·`:name_2` 로 나눠 써서 무사하다.\n * 문제가 되는 건 `gte`+`lte` 를 따로 거는 조합처럼 같은 이름이 두 번 나오는 모든 경우다.)\n */\n var { clause, parameters } = getClauseAndParameters(field, nextParamKey(name), operator, value, searchMode)\n\n if (relationColumnMeta) {\n const { propertyName } = relationColumnMeta\n const property = `${entityAlias}.${propertyName}`\n if (andCondition) {\n selectQueryBuilder.innerJoin(property, alias, clause, parameters)\n } else {\n selectQueryBuilder.leftJoin(property, alias)\n whereExpressionBuilder.orWhere(clause, parameters)\n }\n } else {\n andCondition\n ? whereExpressionBuilder.andWhere(clause, parameters)\n : whereExpressionBuilder.orWhere(clause, parameters)\n }\n}\n\n/**\n * Adds sorting to the SelectQueryBuilder based on the provided Sorting objects.\n *\n * @param selectQueryBuilder - The SelectQueryBuilder to which sorting should be applied.\n * @param sortings - An array of Sorting objects defining the sort order.\n * @param entityAlias - The alias of the entity in the query.\n * @param filtersMap - A mapping of filter names to column names and relation column names.\n * @param metadata - The EntityMetadata of the TypeORM entity.\n */\nfunction addSorting<T>(\n selectQueryBuilder: SelectQueryBuilder<T>,\n sortings: Sorting[],\n entityAlias: string,\n filtersMap: { [name: string]: { columnName: string; relationColumn?: string } },\n metadata: EntityMetadata\n) {\n sortings.forEach((sorting, index) => {\n const sortField = determineSortField(sorting.name, entityAlias, filtersMap, selectQueryBuilder, metadata)\n if (index === 0) {\n selectQueryBuilder.orderBy(sortField, sorting.desc ? 'DESC' : 'ASC')\n } else {\n selectQueryBuilder.addOrderBy(sortField, sorting.desc ? 'DESC' : 'ASC')\n }\n })\n}\n\n/**\n * Determines the sorting field for a given sorting name, considering possible relation columns.\n *\n * @param sortingName - The name of the field to sort by.\n * @param entityAlias - The alias of the entity in the query.\n * @param filtersMap - A mapping of filter names to column names and relation column names.\n * @param selectQueryBuilder - The SelectQueryBuilder instance to apply sorting to.\n * @param metadata - The EntityMetadata of the TypeORM entity.\n * @returns {string} - The fully qualified sorting field.\n */\nfunction determineSortField<T>(\n sortingName: string,\n entityAlias: string,\n filtersMap: { [name: string]: { columnName: string; relationColumn?: string } },\n selectQueryBuilder: SelectQueryBuilder<T>,\n metadata: EntityMetadata\n): string {\n const filter = filtersMap[sortingName]\n\n if (!filter) {\n return sortingName.split('.').length > 1 ? sortingName : `${entityAlias}.${sortingName}`\n }\n\n const { columnName, relationColumn } = filter\n\n if (relationColumn) {\n const relationAlias = applyJoins(\n selectQueryBuilder,\n entityAlias,\n relationColumn,\n metadata,\n 'leftJoin',\n columnName || sortingName,\n true\n )\n return `${relationAlias}.${columnName}`\n } else {\n return `${entityAlias}.${columnName}`\n }\n}\n\n/**\n * Applies the necessary joins to the SelectQueryBuilder based on the relation column.\n *\n * @param selectQueryBuilder - The SelectQueryBuilder where the joins will be applied.\n * @param entityAlias - The current alias of the entity in the query.\n * @param relationColumn - The dot-notated string representing the relation chain (e.g., \"user.profile.address\").\n * @param metadata - The EntityMetadata of the entity.\n * @param joinType - The type of join to use (\"innerJoin\" or \"leftJoin\").\n * @param columnName - The name of the column used for filtering or sorting, default to 'name'.\n * @param selectField - Whether to include the field in the SELECT clause.\n * @returns {string} - The alias to be used for the final field in the relation chain.\n */\nfunction applyJoins<T>(\n selectQueryBuilder: SelectQueryBuilder<T>,\n entityAlias: string,\n relationColumn: string,\n metadata: EntityMetadata,\n joinType: 'innerJoin' | 'leftJoin' = 'leftJoin',\n columnName: string = 'name',\n selectField: boolean = false\n): string {\n const columns = relationColumn.split('.')\n let currentAlias = entityAlias\n let currentMetadata = metadata\n\n for (const column of columns) {\n const relation = currentMetadata.relations.find(rel => rel.propertyName === column)\n\n if (!relation) {\n const available = currentMetadata.relations.map(rel => rel.propertyName).join(', ')\n throw new Error(\n `[getQueryBuilderFromListParams] relation \"${column}\" not found on entity \"${currentMetadata.name}\"` +\n ` (relationColumn \"${relationColumn}\"). Available relations: ${available}`\n )\n }\n\n const nextAlias = `${currentAlias}_${relation.inverseEntityMetadata.tableName}_for_${columnName}`\n\n if (!selectQueryBuilder.expressionMap.aliases.some(alias => alias.name === nextAlias)) {\n selectQueryBuilder[joinType](`${currentAlias}.${column}`, nextAlias)\n }\n if (selectField && columns.at(-1) == column /* 최종 alias만 추가 */) {\n selectQueryBuilder.addSelect(`${nextAlias}.${columnName}`, `${nextAlias}_${columnName}`)\n }\n\n currentAlias = nextAlias\n currentMetadata = relation.inverseEntityMetadata\n }\n\n return currentAlias\n}\n\n/** search 값에 searchMode 에 맞는 와일드카드를 씌운다. */\nfunction wrapSearch(value: string, mode: SearchMode): string {\n switch (mode) {\n case 'startsWith':\n return `${value}%`\n case 'exact':\n return value\n case 'contains':\n default:\n return `%${value}%`\n }\n}\n\n/**\n * Generates the SQL clause and parameters based on the provided filter.\n *\n * @param field - The database field to filter on.\n * @param name - The name of the filter.\n * @param operator - The operator to use in the filter.\n * @param value - The value to filter with.\n * @returns An object containing the SQL clause and the parameters.\n */\nfunction getClauseAndParameters(\n field: string,\n name: string,\n operator: string,\n value: any,\n searchMode: SearchMode = 'contains'\n): { clause: string; parameters: { [key: string]: any } } {\n const values = value instanceof Array ? value : [value]\n let clause = ''\n let parameters: { [key: string]: any } = {}\n\n switch (operator) {\n case 'eq':\n clause = `${field} = :${name}`\n parameters = { [name]: value }\n break\n case 'like':\n clause = `${field} LIKE :${name}`\n parameters = { [name]: `%${value}%` }\n break\n case 'search':\n /* searchMode 로 매칭 방식 선택(기본 contains). startsWith 는 접두 인덱스 활용 가능. */\n clause = `LOWER(${field}) LIKE :${name}`\n parameters = { [name]: wrapSearch(String(value).toLowerCase(), searchMode) }\n break\n case 'i_like':\n clause = `LOWER(${field}) LIKE :${name}`\n parameters = { [name]: `%${String(value).toLowerCase()}%` }\n break\n case 'nlike':\n clause = `${field} NOT LIKE :${name}`\n parameters = { [name]: `%${value}%` }\n break\n case 'i_nlike':\n clause = `LOWER(${field}) NOT LIKE :${name}`\n parameters = { [name]: `%${String(value).toLowerCase()}%` }\n break\n case 'lt':\n clause = `${field} < :${name}`\n parameters = { [name]: value }\n break\n case 'gt':\n clause = `${field} > :${name}`\n parameters = { [name]: value }\n break\n case 'lte':\n clause = `${field} <= :${name}`\n parameters = { [name]: value }\n break\n case 'gte':\n clause = `${field} >= :${name}`\n parameters = { [name]: value }\n break\n case 'noteq':\n clause = `${field} != :${name}`\n parameters = { [name]: value }\n break\n case 'in':\n clause = `${field} IN (:...${name})`\n parameters = { [name]: values }\n break\n case 'notin':\n clause = `${field} NOT IN (:...${name})`\n parameters = { [name]: values }\n break\n /* OR 를 품은 절은 **반드시 스스로 괄호로 닫는다.** 이 문자열은 andWhere 로 들어가는데 TypeORM 은\n * 문자열 조건을 감싸주지 않는다(isolateWhereStatements 기본 false). 괄호가 없으면 실제 SQL 이\n * `... AND domain_id = ? AND instance_id IN (?) AND col IS NULL OR col NOT IN (?)`\n * 가 되고, AND 가 OR 보다 강해 `(앞의 모든 조건) OR (col NOT IN ...)` 으로 해석된다 —\n * **도메인·테넌트 조건이 통째로 무력화되어 남의 데이터가 섞여 나온다.**\n * (아래 is_not_true/is_not_false/is_empty_num_id 는 원래 괄호를 갖고 있었고, 이것만 빠져 있었다.) */\n case 'notin_with_null':\n clause = `(${field} IS NULL OR ${field} NOT IN (:...${name}))`\n parameters = { [name]: values }\n break\n case 'is_null':\n clause = `${field} IS NULL`\n break\n case 'is_not_null':\n clause = `${field} IS NOT NULL`\n break\n /* boolean 비교는 파라미터 바인딩으로 (IS TRUE/FALSE 는 MSSQL/Oracle 미지원 → 이식성 확보) */\n case 'is_false':\n clause = `${field} = :${name}`\n parameters = { [name]: false }\n break\n case 'is_true':\n clause = `${field} = :${name}`\n parameters = { [name]: true }\n break\n case 'is_not_false':\n /* NOT FALSE = true 또는 NULL */\n clause = `(${field} IS NULL OR ${field} = :${name})`\n parameters = { [name]: true }\n break\n case 'is_not_true':\n /* NOT TRUE = false 또는 NULL */\n clause = `(${field} IS NULL OR ${field} = :${name})`\n parameters = { [name]: false }\n break\n case 'is_present':\n clause = `${field} IS NOT NULL`\n break\n case 'is_blank':\n clause = `${field} IS NULL`\n break\n case 'is_empty_num_id':\n clause = `(${field} IS NULL OR ${field} = :${name})`\n parameters = { [name]: 0 }\n break\n case 'between':\n clause = `${field} BETWEEN :${name}_1 AND :${name}_2`\n parameters = { [`${name}_1`]: values[0], [`${name}_2`]: values[1] }\n break\n default:\n /* 지원하지 않는 연산자: 이전엔 clause='' 로 andWhere('') 가 되어 malformed/무음이었다.\n 명확한 오류로 원인을 알린다(fail-fast). */\n throw new Error(\n `[getQueryBuilderFromListParams] unsupported filter operator \"${operator}\" for field \"${field}\".`\n )\n }\n\n return { clause, parameters }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"get-query-builder-from-list-params.js","sourceRoot":"","sources":["../../server/utils/get-query-builder-from-list-params.ts"],"names":[],"mappings":"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","sourcesContent":["import { Brackets, EntityMetadata, Repository, SelectQueryBuilder, WhereExpressionBuilder } from 'typeorm'\nimport { DriverUtils } from 'typeorm/driver/DriverUtils.js'\nimport { RelationMetadata } from 'typeorm/metadata/RelationMetadata.js'\nimport { Filter, Sorting, Pagination, ListParam, InheritedValueType } from '../service/common-types/list-param.js'\nimport { Domain } from '../service/domain/domain.js'\nimport { applyDomainInheritanceFilter } from './apply-domain-filter.js'\nimport { applyKeysetCursor, cursorSortings } from './keyset-cursor.js'\n\n/**\n * `search` 연산자의 매칭 방식.\n * - contains(기본): `%v%` — 어디든 포함(유연하나 인덱스 미활용).\n * - startsWith: `v%` — 접두 일치(인덱스 활용 가능 → 대용량에서 빠름).\n * - exact: `v` — 정확 일치(대소문자 무시).\n */\nexport type SearchMode = 'contains' | 'startsWith' | 'exact'\n\n/*\n * 바인딩 파라미터 이름 발급기 — 같은 컬럼에 조건이 여럿 걸려도 서로 값을 덮어쓰지 않게 한다.\n * 전역 증가 카운터로 충분하다(파라미터는 질의 하나 안에서만 의미가 있고, 유일하기만 하면 된다).\n */\nlet paramSeq = 0\nconst nextParamKey = (name: string): string => `${name.replace(/[^A-Za-z0-9_]/g, '_')}_p${paramSeq++}`\n\n/**\n * Builds a TypeORM SelectQueryBuilder from a generic ListParam (filters, search, sortings,\n * pagination) plus optional multi-tenant domain scoping. Returns the builder so callers can\n * further chain joins/select/where before executing (e.g. getMany / getManyAndCount).\n *\n * Supported filter operators (`Filter.operator`):\n * eq, noteq, like, nlike, i_like, i_nlike, search, lt, gt, lte, gte,\n * in, notin, notin_with_null, between,\n * is_null, is_not_null, is_true, is_false, is_not_true, is_not_false,\n * is_present, is_blank, is_empty_num_id.\n * An unknown operator throws a clear, actionable error.\n *\n * Behavior notes:\n * - `search` uses case-insensitive substring match (`LOWER(col) LIKE %v%`) and requires the column\n * to be listed in `searchables`.\n * - `like`/`i_like` filters are IGNORED (with a console.warn) unless the column is in `searchables`,\n * to avoid unindexed full-table scans.\n * - Boolean operators are emitted as parameter-bound comparisons (portable across all supported DBs).\n * - Domain scoping is applied only when `domain` is provided, honoring `params.inherited`\n * (None = own domain, Include = own + parent, Only = parent).\n * - Pagination uses skip/take; with joins this triggers TypeORM's two-query strategy for correctness.\n * - Unknown filter columns / operators / relations throw a clear, actionable error (fail-fast).\n *\n * @param options.repository TypeORM repository (or a tx-scoped repository) to build from.\n * @param options.params ListParam: `{ filters, sortings, pagination, inherited }`.\n * @param [options.domain] Optional current domain `{ id, parentId }` for tenant scoping.\n * @param [options.alias] Optional entity alias (defaults to the entity name).\n * @param [options.searchables] Columns eligible for `search`/`like` filtering.\n * @param [options.filtersMap] Maps a filter/sort name to a column or relation column, e.g.\n * `{ ownerName: { columnName: 'name', relationColumn: 'owner' } }`.\n * @param [options.searchMode] `search` matching mode: 'contains'(default) / 'startsWith' / 'exact'.\n * 'startsWith' emits `v%` (prefix) which can use an index on large tables.\n * @returns {SelectQueryBuilder<Type>} The constructed SelectQueryBuilder (not yet executed).\n */\nexport function getQueryBuilderFromListParams<Type>(options: {\n repository: Repository<Type>\n params: ListParam\n domain?: Domain\n alias?: string\n searchables?: string[]\n filtersMap?: { [name: string]: { columnName: string; relationColumn?: string } }\n searchMode?: SearchMode\n /**\n * 페이지 크기를 안 준 호출에 씌울 기본 상한.\n *\n * 지정하지 않으면 **예전과 똑같이 무제한**이다(하위호환). 다만 무제한으로 나가는 질의는 경고를 남긴다 —\n * 표가 작을 때는 아무 문제가 없다가 데이터가 늘면 조용히 무너지는 종류의 결함이라, 늘기 전에 보여야 한다.\n */\n defaultLimit?: number\n /** 호출자가 요구한 페이지 크기의 상한. 넘겨도 여기서 잘린다(서버 보호). */\n maxLimit?: number\n /**\n * 정렬 허용 컬럼. 주면 목록에 없는 정렬은 **명시 실패**한다.\n *\n * `searchables` 로 인덱스 없는 LIKE 는 막으면서 정렬은 아무 컬럼이나 받던 비대칭을 메운다 —\n * 인덱스 없는 컬럼 정렬은 큰 표에서 전체 정렬 스캔을 부른다.\n */\n sortables?: string[]\n /**\n * 커서 페이징의 타이브레이커 컬럼. 기본은 엔티티 PK.\n *\n * 마지막 정렬 축은 반드시 **유일**해야 한다 — 같은 값이 여러 행에 있으면 페이지 경계에서\n * 행이 건너뛰어지거나 반복된다.\n */\n cursorTiebreak?: string\n}): SelectQueryBuilder<Type> {\n const {\n repository,\n params,\n domain,\n alias,\n searchables,\n filtersMap = {},\n searchMode = 'contains',\n defaultLimit,\n maxLimit,\n sortables,\n cursorTiebreak\n } = options\n const { inherited = InheritedValueType.None } = params || {}\n\n const selectQueryBuilder = repository.createQueryBuilder(alias)\n const entityAlias = selectQueryBuilder.alias\n\n // Apply filters to the query\n const columnFilters =\n params.filters?.filter(filter => {\n if (filter.operator === 'search') {\n return false\n }\n if (filter.operator.toLowerCase().includes('like') && (!searchables || !searchables.includes(filter.name))) {\n console.warn('\"searchables\" setting is required for LIKE searches to avoid heavy database load', filter.name)\n return false\n }\n return true\n }) || []\n\n /*\n * 전역 검색어(`params.search`) → 서버가 자기 `searchables` 로 필드별 search 필터를 만든다.\n * 클라이언트가 필드 목록을 알 필요가 없고, 서버 목록과 어긋나 조용히 무시될 일도 없다.\n * 아래 searchFilters 와 같은 통(OR 묶음)에 들어가므로, filters 로 보낸 것과 함께 써도 자연스럽게 합쳐진다.\n */\n const term = params.search?.trim()\n const termFilters: Filter[] =\n term && searchables instanceof Array ? searchables.map(name => ({ name, operator: 'search', value: term })) : []\n if (term && !(searchables instanceof Array)) {\n console.warn(\n '[getQueryBuilderFromListParams] params.search was given but this resolver declares no \"searchables\" — ' +\n 'the term was ignored. Declare searchables to make free-text search work.'\n )\n }\n\n const searchFilters =\n searchables instanceof Array\n ? params.filters?.filter(filter => {\n if (filter.operator !== 'search') {\n return false\n }\n if (!searchables.includes(filter.name)) {\n console.warn(\n '\"searchables\" setting is required for LIKE searches to avoid heavy database load',\n filter.name\n )\n return false\n }\n return true\n }) || []\n : []\n\n const pagination = params.pagination\n const sortings = params.sortings\n const metadata = repository.metadata\n\n // Apply column filters\n if (columnFilters.length > 0) {\n columnFilters.forEach(filter => {\n addCondition(metadata, selectQueryBuilder, selectQueryBuilder, filter, filtersMap, true, searchMode)\n })\n }\n\n // Apply search filters (explicit per-field search filters + the expanded global term)\n const allSearchFilters = [...searchFilters, ...termFilters]\n if (allSearchFilters.length > 0) {\n selectQueryBuilder.andWhere(\n new Brackets(qb => {\n allSearchFilters.forEach(filter => {\n addCondition(metadata, selectQueryBuilder, qb, filter, filtersMap, false, searchMode)\n })\n })\n )\n }\n\n // Apply domain filters\n if (domain) {\n applyDomainInheritanceFilter(selectQueryBuilder, entityAlias, domain, inherited)\n }\n\n /* 정렬 화이트리스트 검증은 **호출자가 요구한 축**에만 적용한다(아래에서 우리가 덧붙이는\n * 타이브레이커까지 목록에 넣으라고 요구하면 호출부가 내부 사정을 알아야 한다). */\n if (sortings?.length && sortables) {\n for (const sorting of sortings) {\n if (!sortables.includes(sorting.name)) {\n throw new Error(\n `[getQueryBuilderFromListParams] sorting by \"${sorting.name}\" is not allowed on entity \"${metadata.name}\". ` +\n `Allowed: ${sortables.join(', ')}`\n )\n }\n }\n }\n\n /*\n * 커서(keyset) 페이징 — `after` 가 있으면 offset 을 쓰지 않는다.\n * 정렬 축 끝에 유일 컬럼(기본 PK)을 덧붙여 \"다음 행\" 이 항상 하나로 정해지게 한다.\n */\n const cursor = pagination?.after\n const tiebreak = cursorTiebreak ?? metadata.primaryColumns[0]?.propertyName\n const effectiveSortings = cursor && tiebreak ? cursorSortings(sortings, tiebreak) : sortings\n\n // Apply pagination\n addPagination(selectQueryBuilder, pagination, {\n defaultLimit,\n maxLimit,\n entityName: metadata.name,\n keyset: !!cursor\n })\n\n if (cursor) {\n if (!tiebreak) {\n throw new Error(\n `[getQueryBuilderFromListParams] cursor paging needs a unique tiebreak column but entity \"${metadata.name}\" ` +\n 'exposes no primary column. Pass cursorTiebreak explicitly.'\n )\n }\n applyKeysetCursor(selectQueryBuilder, { alias: entityAlias, sortings: effectiveSortings!, cursor })\n }\n\n // Apply sorting\n if (effectiveSortings && effectiveSortings.length > 0) {\n addSorting(selectQueryBuilder, effectiveSortings, entityAlias, filtersMap, metadata)\n }\n\n return selectQueryBuilder\n}\n\n/**\n * Adds pagination to the SelectQueryBuilder based on the provided Pagination object.\n *\n * @param selectQueryBuilder - The SelectQueryBuilder to which pagination should be applied.\n * @param pagination - The Pagination object containing page and limit information.\n */\nfunction addPagination<T>(\n selectQueryBuilder: SelectQueryBuilder<T>,\n pagination?: Pagination,\n guard: { defaultLimit?: number; maxLimit?: number; entityName?: string; keyset?: boolean } = {}\n) {\n const { defaultLimit, maxLimit, entityName, keyset } = guard\n const { page, limit } = pagination ?? {}\n\n /* 요구된 페이지 크기 → 상한으로 조인다. 상한이 없으면 요구를 그대로 존중(예전 동작). */\n const asked = limit && limit > 0 ? limit : undefined\n const effective = asked === undefined ? defaultLimit : maxLimit ? Math.min(asked, maxLimit) : asked\n\n if (effective === undefined) {\n /* 상한이 아무 데도 없다 = **표 전체가 나간다.** 표가 작을 때는 멀쩡하다가 데이터가 늘면 조용히\n * 무너지는 종류라, 늘기 전에 보이게 한다. 기존 호출부를 깨지 않으려고 막지는 않는다. */\n console.warn(\n `[getQueryBuilderFromListParams] unbounded list query on \"${entityName ?? 'entity'}\" — ` +\n 'no pagination.limit from the caller and no defaultLimit on the resolver, so every matching row will be returned. ' +\n 'Set defaultLimit (and maxLimit) to keep this safe as the table grows.'\n )\n return\n }\n\n /* 커서 모드에서는 offset 을 쓰지 않는다 — 위치가 아니라 값으로 이어 붙이는 것이 커서의 요점이다.\n * (둘을 같이 쓰면 커서로 좁힌 결과를 또 건너뛰어 행이 사라진다.) */\n if (keyset) {\n selectQueryBuilder.take(effective)\n return\n }\n\n if (page && page > 0) {\n selectQueryBuilder.skip(effective * (page - 1)).take(effective)\n } else {\n selectQueryBuilder.take(effective)\n }\n}\n\n/**\n * Adds a filtering condition to the SelectQueryBuilder based on the provided filter and mapping options.\n *\n * @param metadata - The EntityMetadata of the TypeORM entity.\n * @param selectQueryBuilder - The SelectQueryBuilder to which the condition will be added.\n * @param whereExpressionBuilder - The WhereExpressionBuilder to construct the where clause.\n * @param filter - The Filter object containing the filter criteria.\n * @param filtersMap - A mapping of filter names to column names and relation column names.\n * @param andCondition - A flag indicating whether to use \"AND\" or \"OR\" for combining conditions.\n */\nfunction addCondition<T>(\n metadata: EntityMetadata,\n selectQueryBuilder: SelectQueryBuilder<T>,\n whereExpressionBuilder: WhereExpressionBuilder,\n filter: Filter,\n filtersMap: { [name: string]: { columnName: string; relationColumn?: string } } = {},\n andCondition: boolean,\n searchMode: SearchMode = 'contains'\n): void {\n const { name, operator, value } = filter\n var entityAlias = selectQueryBuilder.alias\n\n var { relationColumn, columnName } = filtersMap[name] || {}\n /*\n 1. relationColumn과 columnName이 지정된 경우 \n - relation inverse 테이블에서, columnName을 찾는다.\n 2. relationColumn만 지정된 경우는 없어야 한다.\n - 이 경우 columnName 은 'name' 이라고 판단한다.\n 3. columnName이 지정된 경우.\n - 이 경우는 columnName 만 적용한다.\n */\n if (relationColumn) {\n const columns = relationColumn.split('.')\n var entityMetadata: EntityMetadata\n var relation: RelationMetadata\n\n for (const rcolumn of columns) {\n if (relation) {\n const { propertyName } = relationColumnMeta\n const property = `${entityAlias}.${propertyName}`\n\n entityAlias = `${entityAlias}-${entityMetadata.tableName}-for-${columnName || 'name'}` as string\n\n if (andCondition) {\n selectQueryBuilder.innerJoin(property, entityAlias)\n } else {\n selectQueryBuilder.leftJoin(property, entityAlias)\n }\n } else {\n entityMetadata = metadata\n }\n\n var relationColumnMeta = entityMetadata.columns.find(column => column.propertyName === rcolumn)\n if (!relationColumnMeta) {\n console.warn(`relationColumn \"${relationColumn}\" in filtersMap for \"${name}\" is not a relation column`)\n return\n }\n\n relation = relationColumnMeta.relationMetadata\n entityMetadata = relation.inverseEntityMetadata\n }\n\n var columnMeta = entityMetadata.columns.find(column => column.propertyName === (columnName || 'name'))\n if (!columnMeta) {\n console.warn(`columnName \"${columnName}\" in filtersMap for \"${name}\" is not a column`)\n return\n }\n } else {\n var columnMeta = metadata.columns.find(column => column.propertyName === (columnName || name))\n if (!columnMeta) {\n /* relationId 에 대한 필터링은 해당 컬럼값 자체의 비교로 한다. */\n var relationIdMeta = metadata.relationIds.find(relationId => relationId.propertyName === (columnName || name))\n if (relationIdMeta) {\n columnMeta = relationIdMeta.relation.joinColumns[0]\n } else {\n /* 미지의 컬럼: 이전엔 warn 후 흘러가 columnMeta.databaseName 에서 불투명 TypeError 로 크래시했다.\n 원인을 바로 알 수 있도록 명확·실행가능한 오류를 던진다(fail-fast). */\n const target = columnName || name\n const available = metadata.columns.map(column => column.propertyName).join(', ')\n throw new Error(\n `[getQueryBuilderFromListParams] unknown filter column \"${target}\" on entity \"${metadata.name}\". ` +\n `Add a filtersMap entry for \"${name}\", or use one of: ${available}`\n )\n }\n } else {\n var relation = columnMeta.relationMetadata\n }\n\n if (relation) {\n /* filterMap에 의해서 relationColumn 이 지정되지 않았더라도, name 또는 columnName의 column이 relation인 경우에는\n - 조건절 구성을 위한 타겟필드명은 'name' 으로만 한정된다.\n */\n var relationColumnMeta = columnMeta\n var entityMetadata = relation.inverseEntityMetadata\n columnMeta = entityMetadata.columns.find(column => column.propertyName === 'name')\n if (!columnMeta) {\n console.warn(`relation column \"${columnName || name}\" does not have \"name\" column`)\n return\n }\n }\n }\n\n const dbNameForColumn = columnMeta.databaseName\n const alias = relationColumnMeta ? `${name}-filter` : entityAlias\n\n /* relation columne인 경우 name을 alias로 사용한다. */\n const field = `${alias}.${dbNameForColumn}`\n\n /*\n * 파라미터 이름은 **조건마다 유일**해야 한다.\n *\n * 예전에는 필터 `name` 을 그대로 플레이스홀더로 썼다. 그래서 **같은 컬럼에 조건을 둘 이상** 걸면\n * 둘 다 `:같은이름` 이 되어 뒤엣것이 앞엣것의 값을 덮어썼다. SQL 에는 조건이 멀쩡히 둘 다 있는데\n * 값은 하나만 남으므로, 오류도 경고도 없이 **결과만 틀린다.**\n *\n * (구간 조회는 전용 `between` 연산자가 있고 그쪽은 `:name_1`·`:name_2` 로 나눠 써서 무사하다.\n * 문제가 되는 건 `gte`+`lte` 를 따로 거는 조합처럼 같은 이름이 두 번 나오는 모든 경우다.)\n */\n var { clause, parameters } = getClauseAndParameters(field, nextParamKey(name), operator, value, searchMode)\n\n if (relationColumnMeta) {\n const { propertyName } = relationColumnMeta\n const property = `${entityAlias}.${propertyName}`\n if (andCondition) {\n selectQueryBuilder.innerJoin(property, alias, clause, parameters)\n } else {\n selectQueryBuilder.leftJoin(property, alias)\n whereExpressionBuilder.orWhere(clause, parameters)\n }\n } else {\n andCondition\n ? whereExpressionBuilder.andWhere(clause, parameters)\n : whereExpressionBuilder.orWhere(clause, parameters)\n }\n}\n\n/**\n * Adds sorting to the SelectQueryBuilder based on the provided Sorting objects.\n *\n * @param selectQueryBuilder - The SelectQueryBuilder to which sorting should be applied.\n * @param sortings - An array of Sorting objects defining the sort order.\n * @param entityAlias - The alias of the entity in the query.\n * @param filtersMap - A mapping of filter names to column names and relation column names.\n * @param metadata - The EntityMetadata of the TypeORM entity.\n */\nfunction addSorting<T>(\n selectQueryBuilder: SelectQueryBuilder<T>,\n sortings: Sorting[],\n entityAlias: string,\n filtersMap: { [name: string]: { columnName: string; relationColumn?: string } },\n metadata: EntityMetadata\n) {\n sortings.forEach((sorting, index) => {\n const sortField = determineSortField(sorting.name, entityAlias, filtersMap, selectQueryBuilder, metadata)\n if (index === 0) {\n selectQueryBuilder.orderBy(sortField, sorting.desc ? 'DESC' : 'ASC')\n } else {\n selectQueryBuilder.addOrderBy(sortField, sorting.desc ? 'DESC' : 'ASC')\n }\n })\n}\n\n/**\n * Determines the sorting field for a given sorting name, considering possible relation columns.\n *\n * @param sortingName - The name of the field to sort by.\n * @param entityAlias - The alias of the entity in the query.\n * @param filtersMap - A mapping of filter names to column names and relation column names.\n * @param selectQueryBuilder - The SelectQueryBuilder instance to apply sorting to.\n * @param metadata - The EntityMetadata of the TypeORM entity.\n * @returns {string} - The fully qualified sorting field.\n */\nfunction determineSortField<T>(\n sortingName: string,\n entityAlias: string,\n filtersMap: { [name: string]: { columnName: string; relationColumn?: string } },\n selectQueryBuilder: SelectQueryBuilder<T>,\n metadata: EntityMetadata\n): string {\n const filter = filtersMap[sortingName]\n\n if (!filter) {\n return sortingName.split('.').length > 1 ? sortingName : `${entityAlias}.${sortingName}`\n }\n\n const { columnName, relationColumn } = filter\n\n if (relationColumn) {\n const relationAlias = applyJoins(\n selectQueryBuilder,\n entityAlias,\n relationColumn,\n metadata,\n 'leftJoin',\n columnName || sortingName,\n true\n )\n return `${relationAlias}.${columnName}`\n } else {\n return `${entityAlias}.${columnName}`\n }\n}\n\n/**\n * Applies the necessary joins to the SelectQueryBuilder based on the relation column.\n *\n * @param selectQueryBuilder - The SelectQueryBuilder where the joins will be applied.\n * @param entityAlias - The current alias of the entity in the query.\n * @param relationColumn - The dot-notated string representing the relation chain (e.g., \"user.profile.address\").\n * @param metadata - The EntityMetadata of the entity.\n * @param joinType - The type of join to use (\"innerJoin\" or \"leftJoin\").\n * @param columnName - The name of the column used for filtering or sorting, default to 'name'.\n * @param selectField - Whether to include the field in the SELECT clause.\n * @returns {string} - The alias to be used for the final field in the relation chain.\n */\nfunction applyJoins<T>(\n selectQueryBuilder: SelectQueryBuilder<T>,\n entityAlias: string,\n relationColumn: string,\n metadata: EntityMetadata,\n joinType: 'innerJoin' | 'leftJoin' = 'leftJoin',\n columnName: string = 'name',\n selectField: boolean = false\n): string {\n const columns = relationColumn.split('.')\n let currentAlias = entityAlias\n let currentMetadata = metadata\n\n for (const column of columns) {\n const relation = currentMetadata.relations.find(rel => rel.propertyName === column)\n\n if (!relation) {\n const available = currentMetadata.relations.map(rel => rel.propertyName).join(', ')\n throw new Error(\n `[getQueryBuilderFromListParams] relation \"${column}\" not found on entity \"${currentMetadata.name}\"` +\n ` (relationColumn \"${relationColumn}\"). Available relations: ${available}`\n )\n }\n\n const nextAlias = `${currentAlias}_${relation.inverseEntityMetadata.tableName}_for_${columnName}`\n\n if (!selectQueryBuilder.expressionMap.aliases.some(alias => alias.name === nextAlias)) {\n selectQueryBuilder[joinType](`${currentAlias}.${column}`, nextAlias)\n }\n if (selectField && columns.at(-1) == column /* 최종 alias만 추가 */) {\n /*\n * The select alias has to be spelled the way TypeORM will look it up.\n *\n * Sorting by a joined column and paginating puts the query on TypeORM's distinct-id path,\n * which wraps everything in a subquery and re-addresses each ORDER BY as\n * `distinctAlias.<buildAlias(aliasName, column.databaseName)>` — the **database** name (see\n * SelectQueryBuilder.createOrderByCombinedWithSelectExpression). This addSelect used the\n * **property** name, so the two only met where the two spellings happen to be identical.\n *\n * name, description and priority are one lowercase word each, so they were identical and it\n * worked by luck. dueAt is not: the property is `dueAt`, the column is `due_at` under the\n * snake_case NamingStrategy, and sorting 내 결재함 by 기한 died with\n *\n * SQLITE_ERROR: no such column:\n * distinctAlias.ap_activity_threads_for_dueAt_activity_instances_for_dueAt_due_at\n *\n * Building the alias the same way TypeORM does makes them agree by construction. Lowercase\n * columns produce the identical string as before, so nothing else moves.\n */\n const targetColumn = relation.inverseEntityMetadata.findColumnWithPropertyName(columnName)\n\n if (!targetColumn) {\n /*\n * columnName is a property name, not a database column name — `dueAt`, never `due_at`.\n * Failing loudly here beats an alias nobody can resolve: TypeORM's own lookup on the\n * distinct path dereferences the column without a null check, so a wrong spelling would\n * surface much later as a TypeError with no mention of this map.\n */\n const available = relation.inverseEntityMetadata.columns.map(col => col.propertyName).join(', ')\n throw new Error(\n `[getQueryBuilderFromListParams] column \"${columnName}\" not found on entity` +\n ` \"${relation.inverseEntityMetadata.name}\" (relationColumn \"${relationColumn}\").` +\n ` Use the property name, not the database column name. Available: ${available}`\n )\n }\n\n selectQueryBuilder.addSelect(\n `${nextAlias}.${columnName}`,\n DriverUtils.buildAlias(selectQueryBuilder.connection.driver, undefined, nextAlias, targetColumn.databaseName)\n )\n }\n\n currentAlias = nextAlias\n currentMetadata = relation.inverseEntityMetadata\n }\n\n return currentAlias\n}\n\n/** search 값에 searchMode 에 맞는 와일드카드를 씌운다. */\nfunction wrapSearch(value: string, mode: SearchMode): string {\n switch (mode) {\n case 'startsWith':\n return `${value}%`\n case 'exact':\n return value\n case 'contains':\n default:\n return `%${value}%`\n }\n}\n\n/**\n * Generates the SQL clause and parameters based on the provided filter.\n *\n * @param field - The database field to filter on.\n * @param name - The name of the filter.\n * @param operator - The operator to use in the filter.\n * @param value - The value to filter with.\n * @returns An object containing the SQL clause and the parameters.\n */\nfunction getClauseAndParameters(\n field: string,\n name: string,\n operator: string,\n value: any,\n searchMode: SearchMode = 'contains'\n): { clause: string; parameters: { [key: string]: any } } {\n const values = value instanceof Array ? value : [value]\n let clause = ''\n let parameters: { [key: string]: any } = {}\n\n switch (operator) {\n case 'eq':\n clause = `${field} = :${name}`\n parameters = { [name]: value }\n break\n case 'like':\n clause = `${field} LIKE :${name}`\n parameters = { [name]: `%${value}%` }\n break\n case 'search':\n /* searchMode 로 매칭 방식 선택(기본 contains). startsWith 는 접두 인덱스 활용 가능. */\n clause = `LOWER(${field}) LIKE :${name}`\n parameters = { [name]: wrapSearch(String(value).toLowerCase(), searchMode) }\n break\n case 'i_like':\n clause = `LOWER(${field}) LIKE :${name}`\n parameters = { [name]: `%${String(value).toLowerCase()}%` }\n break\n case 'nlike':\n clause = `${field} NOT LIKE :${name}`\n parameters = { [name]: `%${value}%` }\n break\n case 'i_nlike':\n clause = `LOWER(${field}) NOT LIKE :${name}`\n parameters = { [name]: `%${String(value).toLowerCase()}%` }\n break\n case 'lt':\n clause = `${field} < :${name}`\n parameters = { [name]: value }\n break\n case 'gt':\n clause = `${field} > :${name}`\n parameters = { [name]: value }\n break\n case 'lte':\n clause = `${field} <= :${name}`\n parameters = { [name]: value }\n break\n case 'gte':\n clause = `${field} >= :${name}`\n parameters = { [name]: value }\n break\n case 'noteq':\n clause = `${field} != :${name}`\n parameters = { [name]: value }\n break\n case 'in':\n clause = `${field} IN (:...${name})`\n parameters = { [name]: values }\n break\n case 'notin':\n clause = `${field} NOT IN (:...${name})`\n parameters = { [name]: values }\n break\n /* OR 를 품은 절은 **반드시 스스로 괄호로 닫는다.** 이 문자열은 andWhere 로 들어가는데 TypeORM 은\n * 문자열 조건을 감싸주지 않는다(isolateWhereStatements 기본 false). 괄호가 없으면 실제 SQL 이\n * `... AND domain_id = ? AND instance_id IN (?) AND col IS NULL OR col NOT IN (?)`\n * 가 되고, AND 가 OR 보다 강해 `(앞의 모든 조건) OR (col NOT IN ...)` 으로 해석된다 —\n * **도메인·테넌트 조건이 통째로 무력화되어 남의 데이터가 섞여 나온다.**\n * (아래 is_not_true/is_not_false/is_empty_num_id 는 원래 괄호를 갖고 있었고, 이것만 빠져 있었다.) */\n case 'notin_with_null':\n clause = `(${field} IS NULL OR ${field} NOT IN (:...${name}))`\n parameters = { [name]: values }\n break\n case 'is_null':\n clause = `${field} IS NULL`\n break\n case 'is_not_null':\n clause = `${field} IS NOT NULL`\n break\n /* boolean 비교는 파라미터 바인딩으로 (IS TRUE/FALSE 는 MSSQL/Oracle 미지원 → 이식성 확보) */\n case 'is_false':\n clause = `${field} = :${name}`\n parameters = { [name]: false }\n break\n case 'is_true':\n clause = `${field} = :${name}`\n parameters = { [name]: true }\n break\n case 'is_not_false':\n /* NOT FALSE = true 또는 NULL */\n clause = `(${field} IS NULL OR ${field} = :${name})`\n parameters = { [name]: true }\n break\n case 'is_not_true':\n /* NOT TRUE = false 또는 NULL */\n clause = `(${field} IS NULL OR ${field} = :${name})`\n parameters = { [name]: false }\n break\n case 'is_present':\n clause = `${field} IS NOT NULL`\n break\n case 'is_blank':\n clause = `${field} IS NULL`\n break\n case 'is_empty_num_id':\n clause = `(${field} IS NULL OR ${field} = :${name})`\n parameters = { [name]: 0 }\n break\n case 'between':\n clause = `${field} BETWEEN :${name}_1 AND :${name}_2`\n parameters = { [`${name}_1`]: values[0], [`${name}_2`]: values[1] }\n break\n default:\n /* 지원하지 않는 연산자: 이전엔 clause='' 로 andWhere('') 가 되어 malformed/무음이었다.\n 명확한 오류로 원인을 알린다(fail-fast). */\n throw new Error(\n `[getQueryBuilderFromListParams] unsupported filter operator \"${operator}\" for field \"${field}\".`\n )\n }\n\n return { clause, parameters }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/shell",
|
|
3
|
-
"version": "10.0
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"description": "Core module for framework",
|
|
5
5
|
"bin": {
|
|
6
6
|
"things-factory": "bin/things-factory",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@apollo/client": "^3.6.9",
|
|
36
|
-
"@apollo/server": "^4.
|
|
36
|
+
"@apollo/server": "^4.13.0",
|
|
37
37
|
"@as-integrations/koa": "^0.3.0",
|
|
38
38
|
"@fontsource/roboto": "^5.0.3",
|
|
39
39
|
"@graphql-codegen/cli": "^5.0.2",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@things-factory/ejs-remote": "^10.0.8",
|
|
57
57
|
"@things-factory/env": "^10.0.8",
|
|
58
58
|
"@things-factory/operato-license-checker": "^4.0.4",
|
|
59
|
-
"@things-factory/styles": "^10.0
|
|
59
|
+
"@things-factory/styles": "^10.1.0",
|
|
60
60
|
"@things-factory/utils": "^10.0.8",
|
|
61
61
|
"@types/koa": "^3.0.1",
|
|
62
62
|
"@types/koa-bodyparser": "^4.3.13",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"pg": "^8.7.3",
|
|
127
127
|
"sqlite3": "^5.0.8"
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "8407b1e9d1f47f6676af144d4a33c8ac01cb49c4"
|
|
130
130
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import 'reflect-metadata'
|
|
2
|
+
import { Column, DataSource, Entity, ManyToOne, PrimaryColumn, Repository } from 'typeorm'
|
|
3
|
+
|
|
4
|
+
import { getQueryBuilderFromListParams } from '../server/utils/get-query-builder-from-list-params.js'
|
|
5
|
+
import { NamingStrategy } from '../server/initializers/naming-strategy.js'
|
|
6
|
+
import { ListParam } from '../server/service/common-types/list-param.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sorting by a joined column whose property name differs from its database column name.
|
|
10
|
+
*
|
|
11
|
+
* ## What broke
|
|
12
|
+
*
|
|
13
|
+
* 내 결재함 sorted by 기한 returned no rows and raised
|
|
14
|
+
*
|
|
15
|
+
* SQLITE_ERROR: no such column:
|
|
16
|
+
* distinctAlias.ap_activity_threads_for_dueAt_activity_instances_for_dueAt_due_at
|
|
17
|
+
*
|
|
18
|
+
* Sorting by a joined column while paginating puts the query on TypeORM's distinct-id path, which
|
|
19
|
+
* wraps everything in a subquery and re-addresses each ORDER BY by its own convention —
|
|
20
|
+
* `<joinAlias>_<column.databaseName>`. applyJoins had been building the select alias from the
|
|
21
|
+
* property name instead, so the two only met where the two spellings coincide.
|
|
22
|
+
*
|
|
23
|
+
* name, description and priority are one lowercase word each, so they coincided and it worked by
|
|
24
|
+
* luck. dueAt does not: the property is `dueAt`, the column is `due_at` under the app's snake_case
|
|
25
|
+
* NamingStrategy.
|
|
26
|
+
*
|
|
27
|
+
* ## What is fixed
|
|
28
|
+
*
|
|
29
|
+
* The alias is built with the same DriverUtils.buildAlias call TypeORM uses to look it up, so the
|
|
30
|
+
* two agree by construction rather than by coincidence.
|
|
31
|
+
*
|
|
32
|
+
* The NamingStrategy matters here: without it TypeORM leaves `dueAt` as `dueAt` and nothing can go
|
|
33
|
+
* wrong. This suite therefore mirrors the app's configuration rather than TypeORM's default.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
@Entity()
|
|
37
|
+
class Shipment {
|
|
38
|
+
@PrimaryColumn({ type: 'integer' })
|
|
39
|
+
id!: number
|
|
40
|
+
|
|
41
|
+
/* Property and column spelled differently — this is the case that broke. */
|
|
42
|
+
@Column({ type: 'integer', nullable: true })
|
|
43
|
+
dueAt!: number | null
|
|
44
|
+
|
|
45
|
+
/* Property and column identical — the case that worked by coincidence. */
|
|
46
|
+
@Column({ type: 'text', nullable: true })
|
|
47
|
+
name!: string | null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Entity()
|
|
51
|
+
class Order {
|
|
52
|
+
@PrimaryColumn({ type: 'integer' })
|
|
53
|
+
id!: number
|
|
54
|
+
|
|
55
|
+
@ManyToOne(() => Shipment, { nullable: true })
|
|
56
|
+
shipment!: Shipment | null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let dataSource: DataSource
|
|
60
|
+
let repository: Repository<Order>
|
|
61
|
+
|
|
62
|
+
beforeAll(async () => {
|
|
63
|
+
dataSource = new DataSource({
|
|
64
|
+
type: 'better-sqlite3',
|
|
65
|
+
database: ':memory:',
|
|
66
|
+
synchronize: true,
|
|
67
|
+
namingStrategy: new NamingStrategy(),
|
|
68
|
+
entities: [Order, Shipment]
|
|
69
|
+
})
|
|
70
|
+
await dataSource.initialize()
|
|
71
|
+
|
|
72
|
+
const shipments = dataSource.getRepository(Shipment)
|
|
73
|
+
const late = await shipments.save({ id: 1, dueAt: 100, name: 'charlie' })
|
|
74
|
+
const soon = await shipments.save({ id: 2, dueAt: 300, name: 'alpha' })
|
|
75
|
+
const mid = await shipments.save({ id: 3, dueAt: 200, name: 'bravo' })
|
|
76
|
+
|
|
77
|
+
repository = dataSource.getRepository(Order)
|
|
78
|
+
await repository.save([
|
|
79
|
+
{ id: 10, shipment: soon },
|
|
80
|
+
{ id: 11, shipment: late },
|
|
81
|
+
{ id: 12, shipment: mid }
|
|
82
|
+
])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
afterAll(async () => {
|
|
86
|
+
await dataSource?.destroy()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
/** Pagination is what puts the query on the distinct-id path, so every case here asks for a page. */
|
|
90
|
+
function params(sortings: { name: string; desc?: boolean }[]): ListParam {
|
|
91
|
+
return { filters: [], sortings, pagination: { page: 1, limit: 10 } } as unknown as ListParam
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const filtersMap = {
|
|
95
|
+
dueAt: { columnName: 'dueAt', relationColumn: 'shipment' },
|
|
96
|
+
shipmentName: { columnName: 'name', relationColumn: 'shipment' }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function idsSortedBy(name: string, desc = false) {
|
|
100
|
+
const rows = await getQueryBuilderFromListParams({
|
|
101
|
+
repository,
|
|
102
|
+
params: params([{ name, desc }]),
|
|
103
|
+
filtersMap
|
|
104
|
+
}).getMany()
|
|
105
|
+
|
|
106
|
+
return rows.map(row => row.id)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
describe('sorting by a joined column while paginating', () => {
|
|
110
|
+
it('orders by a camelCase column instead of failing on a missing alias', async () => {
|
|
111
|
+
expect(await idsSortedBy('dueAt')).toEqual([11, 12, 10])
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('reverses that order', async () => {
|
|
115
|
+
expect(await idsSortedBy('dueAt', true)).toEqual([10, 12, 11])
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('leaves a lowercase column ordering exactly as before', async () => {
|
|
119
|
+
expect(await idsSortedBy('shipmentName')).toEqual([10, 12, 11])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('names the property, not the database column, and says so when given the wrong one', () => {
|
|
123
|
+
expect(() =>
|
|
124
|
+
getQueryBuilderFromListParams({
|
|
125
|
+
repository,
|
|
126
|
+
params: params([{ name: 'dueAt' }]),
|
|
127
|
+
filtersMap: { dueAt: { columnName: 'due_at', relationColumn: 'shipment' } }
|
|
128
|
+
})
|
|
129
|
+
).toThrow(/column "due_at" not found on entity "Shipment"/)
|
|
130
|
+
})
|
|
131
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"text.are_you_sure": "are you sure?",
|
|
26
26
|
"text.ask ai": "ask ai",
|
|
27
27
|
"text.column visibility setting": "column visibility",
|
|
28
|
+
"text.copied": "copied",
|
|
29
|
+
"text.copy all": "copy all",
|
|
30
|
+
"text.copy failed": "copy failed",
|
|
28
31
|
"text.data_created_successfully": "data created successfully",
|
|
29
32
|
"text.data_deleted_successfully": "data deleted successfully",
|
|
30
33
|
"text.data_updated_successfully": "data updated successfully",
|
package/translations/ja.json
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"text.are_you_sure": "進行しますか?",
|
|
26
26
|
"text.ask ai": "AIに聞く",
|
|
27
27
|
"text.column visibility setting": "カラムの表示設定",
|
|
28
|
+
"text.copied": "コピーしました",
|
|
29
|
+
"text.copy all": "すべてコピー",
|
|
30
|
+
"text.copy failed": "コピーできませんでした",
|
|
28
31
|
"text.data_created_successfully": "資料が正常に作成されました.",
|
|
29
32
|
"text.data_deleted_successfully": "資料の削除に成功しました.",
|
|
30
33
|
"text.data_updated_successfully": "資料の修正に成功しました.",
|
package/translations/ko.json
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"text.are_you_sure": "진행하시겠습니까?",
|
|
26
26
|
"text.ask ai": "AI에게 묻기",
|
|
27
27
|
"text.column visibility setting": "컬럼 보이기",
|
|
28
|
+
"text.copied": "복사했습니다",
|
|
29
|
+
"text.copy all": "전체 복사",
|
|
30
|
+
"text.copy failed": "복사하지 못했습니다",
|
|
28
31
|
"text.data_created_successfully": "성공적으로 자료를 생성하였습니다.",
|
|
29
32
|
"text.data_deleted_successfully": "성공적으로 자료를 삭제하였습니다.",
|
|
30
33
|
"text.data_updated_successfully": "성공적으로 자료를 수정하였습니다.",
|
package/translations/ms.json
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"text.are_you_sure": "adakah anda pasti?",
|
|
26
26
|
"text.ask ai": "tanya ai",
|
|
27
27
|
"text.column visibility setting": "tetapan kebolehlihatan kolum",
|
|
28
|
+
"text.copied": "disalin",
|
|
29
|
+
"text.copy all": "salin semua",
|
|
30
|
+
"text.copy failed": "gagal menyalin",
|
|
28
31
|
"text.data_created_successfully": "data berjaya dicipta",
|
|
29
32
|
"text.data_deleted_successfully": "data telah berjaya dipadam",
|
|
30
33
|
"text.data_updated_successfully": "data berjaya dikemaskini",
|
package/translations/zh.json
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"text.are_you_sure": "您确定要进行吗?",
|
|
26
26
|
"text.ask ai": "询问 AI",
|
|
27
27
|
"text.column visibility setting": "列显示",
|
|
28
|
+
"text.copied": "已复制",
|
|
29
|
+
"text.copy all": "复制全部",
|
|
30
|
+
"text.copy failed": "复制失败",
|
|
28
31
|
"text.data_created_successfully": "数据创建成功。",
|
|
29
32
|
"text.data_deleted_successfully": "数据删除成功。",
|
|
30
33
|
"text.data_updated_successfully": "数据更新成功。",
|