@things-factory/shell 10.1.33 → 10.1.34
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/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/get-query-builder-from-list-params.d.ts +2 -0
- package/dist-server/utils/get-query-builder-from-list-params.js +18 -8
- package/dist-server/utils/get-query-builder-from-list-params.js.map +1 -1
- package/dist-server/utils/keyset-cursor.d.ts +1 -0
- package/dist-server/utils/keyset-cursor.js +4 -2
- package/dist-server/utils/keyset-cursor.js.map +1 -1
- package/package.json +2 -2
|
@@ -54,6 +54,8 @@ export declare function getQueryBuilderFromListParams<Type>(options: {
|
|
|
54
54
|
relationColumn?: string;
|
|
55
55
|
};
|
|
56
56
|
};
|
|
57
|
+
/** Trusted server-defined joined column expressions. Never populate this from request input. */
|
|
58
|
+
columnExpressions?: Record<string, string>;
|
|
57
59
|
searchMode?: SearchMode;
|
|
58
60
|
/**
|
|
59
61
|
* 페이지 크기를 안 준 호출에 씌울 기본 상한.
|
|
@@ -47,7 +47,7 @@ const nextParamKey = (name) => `${name.replace(/[^A-Za-z0-9_]/g, '_')}_p${paramS
|
|
|
47
47
|
* @returns {SelectQueryBuilder<Type>} The constructed SelectQueryBuilder (not yet executed).
|
|
48
48
|
*/
|
|
49
49
|
function getQueryBuilderFromListParams(options) {
|
|
50
|
-
const { repository, params, domain, alias, searchables, filtersMap = {}, searchMode = 'contains', defaultLimit, maxLimit, sortables, cursorTiebreak } = options;
|
|
50
|
+
const { repository, params, domain, alias, searchables, filtersMap = {}, columnExpressions = {}, searchMode = 'contains', defaultLimit, maxLimit, sortables, cursorTiebreak } = options;
|
|
51
51
|
const { inherited = list_param_js_1.InheritedValueType.None } = params || {};
|
|
52
52
|
const selectQueryBuilder = repository.createQueryBuilder(alias);
|
|
53
53
|
const entityAlias = selectQueryBuilder.alias;
|
|
@@ -91,7 +91,7 @@ function getQueryBuilderFromListParams(options) {
|
|
|
91
91
|
// Apply column filters
|
|
92
92
|
if (columnFilters.length > 0) {
|
|
93
93
|
columnFilters.forEach(filter => {
|
|
94
|
-
addCondition(metadata, selectQueryBuilder, selectQueryBuilder, filter, filtersMap, true, searchMode);
|
|
94
|
+
addCondition(metadata, selectQueryBuilder, selectQueryBuilder, filter, filtersMap, true, searchMode, columnExpressions);
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
// Apply search filters (explicit per-field search filters + the expanded global term)
|
|
@@ -99,7 +99,7 @@ function getQueryBuilderFromListParams(options) {
|
|
|
99
99
|
if (allSearchFilters.length > 0) {
|
|
100
100
|
selectQueryBuilder.andWhere(new typeorm_1.Brackets(qb => {
|
|
101
101
|
allSearchFilters.forEach(filter => {
|
|
102
|
-
addCondition(metadata, selectQueryBuilder, qb, filter, filtersMap, false, searchMode);
|
|
102
|
+
addCondition(metadata, selectQueryBuilder, qb, filter, filtersMap, false, searchMode, columnExpressions);
|
|
103
103
|
});
|
|
104
104
|
}));
|
|
105
105
|
}
|
|
@@ -136,11 +136,11 @@ function getQueryBuilderFromListParams(options) {
|
|
|
136
136
|
throw new Error(`[getQueryBuilderFromListParams] cursor paging needs a unique tiebreak column but entity "${metadata.name}" ` +
|
|
137
137
|
'exposes no primary column. Pass cursorTiebreak explicitly.');
|
|
138
138
|
}
|
|
139
|
-
(0, keyset_cursor_js_1.applyKeysetCursor)(selectQueryBuilder, { alias: entityAlias, sortings: effectiveSortings, cursor });
|
|
139
|
+
(0, keyset_cursor_js_1.applyKeysetCursor)(selectQueryBuilder, { alias: entityAlias, sortings: effectiveSortings, cursor, columnExpressions });
|
|
140
140
|
}
|
|
141
141
|
// Apply sorting
|
|
142
142
|
if (effectiveSortings && effectiveSortings.length > 0) {
|
|
143
|
-
addSorting(selectQueryBuilder, effectiveSortings, entityAlias, filtersMap, metadata);
|
|
143
|
+
addSorting(selectQueryBuilder, effectiveSortings, entityAlias, filtersMap, metadata, columnExpressions);
|
|
144
144
|
}
|
|
145
145
|
return selectQueryBuilder;
|
|
146
146
|
}
|
|
@@ -187,8 +187,16 @@ function addPagination(selectQueryBuilder, pagination, guard = {}) {
|
|
|
187
187
|
* @param filtersMap - A mapping of filter names to column names and relation column names.
|
|
188
188
|
* @param andCondition - A flag indicating whether to use "AND" or "OR" for combining conditions.
|
|
189
189
|
*/
|
|
190
|
-
function addCondition(metadata, selectQueryBuilder, whereExpressionBuilder, filter, filtersMap = {}, andCondition, searchMode = 'contains') {
|
|
190
|
+
function addCondition(metadata, selectQueryBuilder, whereExpressionBuilder, filter, filtersMap = {}, andCondition, searchMode = 'contains', columnExpressions = {}) {
|
|
191
191
|
const { name, operator, value } = filter;
|
|
192
|
+
if (Object.prototype.hasOwnProperty.call(columnExpressions, name)) {
|
|
193
|
+
const { clause, parameters } = getClauseAndParameters(columnExpressions[name], nextParamKey(name), operator, value, searchMode);
|
|
194
|
+
if (andCondition)
|
|
195
|
+
whereExpressionBuilder.andWhere(clause, parameters);
|
|
196
|
+
else
|
|
197
|
+
whereExpressionBuilder.orWhere(clause, parameters);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
192
200
|
var entityAlias = selectQueryBuilder.alias;
|
|
193
201
|
var { relationColumn, columnName } = filtersMap[name] || {};
|
|
194
202
|
/*
|
|
@@ -306,9 +314,11 @@ function addCondition(metadata, selectQueryBuilder, whereExpressionBuilder, filt
|
|
|
306
314
|
* @param filtersMap - A mapping of filter names to column names and relation column names.
|
|
307
315
|
* @param metadata - The EntityMetadata of the TypeORM entity.
|
|
308
316
|
*/
|
|
309
|
-
function addSorting(selectQueryBuilder, sortings, entityAlias, filtersMap, metadata) {
|
|
317
|
+
function addSorting(selectQueryBuilder, sortings, entityAlias, filtersMap, metadata, columnExpressions = {}) {
|
|
310
318
|
sortings.forEach((sorting, index) => {
|
|
311
|
-
const sortField =
|
|
319
|
+
const sortField = Object.prototype.hasOwnProperty.call(columnExpressions, sorting.name)
|
|
320
|
+
? columnExpressions[sorting.name]
|
|
321
|
+
: determineSortField(sorting.name, entityAlias, filtersMap, selectQueryBuilder, metadata);
|
|
312
322
|
if (index === 0) {
|
|
313
323
|
selectQueryBuilder.orderBy(sortField, sorting.desc ? 'DESC' : 'ASC');
|
|
314
324
|
}
|
|
@@ -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 { 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"]}
|
|
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 /** Trusted server-defined joined column expressions. Never populate this from request input. */\n columnExpressions?: Record<string, 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 columnExpressions = {},\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, columnExpressions)\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, columnExpressions)\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, columnExpressions })\n }\n\n // Apply sorting\n if (effectiveSortings && effectiveSortings.length > 0) {\n addSorting(selectQueryBuilder, effectiveSortings, entityAlias, filtersMap, metadata, columnExpressions)\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 columnExpressions: Record<string, string> = {}\n): void {\n const { name, operator, value } = filter\n if (Object.prototype.hasOwnProperty.call(columnExpressions, name)) {\n const { clause, parameters } = getClauseAndParameters(columnExpressions[name], nextParamKey(name), operator, value, searchMode)\n if (andCondition) whereExpressionBuilder.andWhere(clause, parameters)\n else whereExpressionBuilder.orWhere(clause, parameters)\n return\n }\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 columnExpressions: Record<string, string> = {}\n) {\n sortings.forEach((sorting, index) => {\n const sortField = Object.prototype.hasOwnProperty.call(columnExpressions, sorting.name)\n ? columnExpressions[sorting.name]\n : 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"]}
|
|
@@ -51,6 +51,8 @@ function buildNextCursor(lastRow, sortings) {
|
|
|
51
51
|
*/
|
|
52
52
|
function applyKeysetCursor(qb, options) {
|
|
53
53
|
const { alias, sortings, cursor } = options;
|
|
54
|
+
const column = (name) => Object.prototype.hasOwnProperty.call(options.columnExpressions ?? {}, name)
|
|
55
|
+
? options.columnExpressions[name] : `${alias}.${name}`;
|
|
54
56
|
const values = decodeCursor(cursor);
|
|
55
57
|
if (values.length !== sortings.length) {
|
|
56
58
|
throw new Error(`[keyset-cursor] cursor has ${values.length} values but the current sorting has ${sortings.length} axes — ` +
|
|
@@ -63,10 +65,10 @@ function applyKeysetCursor(qb, options) {
|
|
|
63
65
|
/* 앞 축들은 같고(=), i 번째 축에서 갈린다(< 또는 >). */
|
|
64
66
|
for (let j = 0; j < i; j++) {
|
|
65
67
|
params[`ksEq${i}_${j}`] = values[j];
|
|
66
|
-
parts.push(`${
|
|
68
|
+
parts.push(`${column(sortings[j].name)} = :ksEq${i}_${j}`);
|
|
67
69
|
}
|
|
68
70
|
params[`ksCmp${i}`] = values[i];
|
|
69
|
-
parts.push(`${
|
|
71
|
+
parts.push(`${column(sorting.name)} ${sorting.desc ? '<' : '>'} :ksCmp${i}`);
|
|
70
72
|
outer.orWhere(`(${parts.join(' AND ')})`, params);
|
|
71
73
|
});
|
|
72
74
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyset-cursor.js","sourceRoot":"","sources":["../../server/utils/keyset-cursor.ts"],"names":[],"mappings":";;AAyBA,oCAEC;AAED,oCAUC;AAQD,wCAIC;AAGD,0CAGC;AAYD,
|
|
1
|
+
{"version":3,"file":"keyset-cursor.js","sourceRoot":"","sources":["../../server/utils/keyset-cursor.ts"],"names":[],"mappings":"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","sourcesContent":["import { Brackets, type SelectQueryBuilder } from 'typeorm'\n\nimport type { Sorting } from '../service/common-types/list-param.js'\n\n/*\n * 커서(keyset) 페이징 — \"이 행 다음부터\".\n *\n * ── offset 이 틀리는 지점 ───────────────────────────────────────────────────\n * `OFFSET n` 은 목록이 정지해 있다고 가정한다. 머리에 행이 쌓이는 목록(이벤트 저널·로그·최신순 피드)은\n * 1페이지를 읽는 사이 들어온 수만큼 뒤가 밀린다 → 본 행이 또 나오거나 못 본 행이 사라진다.\n * 커서는 **위치가 아니라 값**을 기억하므로 앞에 무엇이 쌓이든 이어지는 지점이 변하지 않는다.\n *\n * ── 왜 행 생성자(row constructor)를 안 쓰나 ─────────────────────────────────\n * `(a, b) < (x, y)` 는 간결하지만 지원 DB 5종에서 고르게 동작하지 않는다(MSSQL·Oracle 제약).\n * 그래서 같은 의미를 OR/AND 로 펼쳐 쓴다 — 이식성이 규칙이다.\n *\n * ── 괄호 ────────────────────────────────────────────────────────────────────\n * OR 를 품은 조건은 반드시 스스로 닫는다. TypeORM 은 문자열 조건을 감싸주지 않아서\n * (isolateWhereStatements 기본 false) 괄호를 빠뜨리면 앞의 AND 조건 — 도메인·테넌트 포함 — 이\n * 통째로 무력화된다. 여기서는 `Brackets` 로 TypeORM 이 직접 괄호를 치게 한다.\n */\n\n/** 커서에 담기는 것 — 마지막 행의 정렬 축 값들(타이브레이커 포함). */\ntype CursorValues = (string | number | null)[]\n\nexport function encodeCursor(values: CursorValues): string {\n return Buffer.from(JSON.stringify(values), 'utf8').toString('base64url')\n}\n\nexport function decodeCursor(cursor: string): CursorValues {\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'))\n if (!Array.isArray(parsed)) throw new Error('not an array')\n return parsed\n } catch {\n /* 못 읽는 커서를 조용히 무시하면 사용자는 **1페이지를 다시 보면서 다음 페이지라고 믿는다.**\n * 위치가 어긋난 목록은 빈 목록보다 나쁘다 — 명시 실패한다. */\n throw new Error(`[keyset-cursor] malformed cursor: ${cursor.slice(0, 24)}…`)\n }\n}\n\n/**\n * 정렬 축 + 타이브레이커 확정.\n *\n * 마지막 축은 **반드시 유일한 컬럼**(보통 PK)이어야 한다. 같은 값이 여러 행에 있으면 그 경계에서\n * 행이 건너뛰어지거나 반복된다 — 커서가 \"다음\" 을 특정하지 못하기 때문이다.\n */\nexport function cursorSortings(sortings: Sorting[] | undefined, tiebreak: string): Sorting[] {\n const base = (sortings ?? []).filter(s => s.name !== tiebreak)\n /* 타이브레이커 방향은 첫 축을 따라간다 — 섞이면 정렬이 뒤엉킨다. */\n return [...base, { name: tiebreak, desc: base[0]?.desc ?? true }]\n}\n\n/** 이번 페이지의 마지막 행 → 다음 커서. 페이지가 비었으면 다음이 없다. */\nexport function buildNextCursor(lastRow: any, sortings: Sorting[]): string | undefined {\n if (!lastRow) return undefined\n return encodeCursor(sortings.map(s => lastRow[s.name] ?? null))\n}\n\n/**\n * `after` 커서 조건을 붙인다.\n *\n * 축이 (k1 desc, k2 desc) 이고 커서가 (v1, v2) 라면 다음 행의 조건은\n * `(k1 < v1) OR (k1 = v1 AND k2 < v2)`\n * 이다. 축이 늘어나도 같은 모양으로 펼친다.\n *\n * NULL 주의: 정렬 축에 NULL 이 섞이면 비교가 UNKNOWN 이 되어 그 행들이 조용히 빠진다.\n * 커서 축으로는 항상 값이 있는 컬럼을 쓸 것(타이브레이커가 PK 인 이유이기도 하다).\n */\nexport function applyKeysetCursor<T>(\n qb: SelectQueryBuilder<T>,\n options: { alias: string; sortings: Sorting[]; cursor: string; columnExpressions?: Record<string, string> }\n): void {\n const { alias, sortings, cursor } = options\n const column = (name: string) => Object.prototype.hasOwnProperty.call(options.columnExpressions ?? {}, name)\n ? options.columnExpressions![name] : `${alias}.${name}`\n const values = decodeCursor(cursor)\n if (values.length !== sortings.length) {\n throw new Error(\n `[keyset-cursor] cursor has ${values.length} values but the current sorting has ${sortings.length} axes — ` +\n 'the sort order changed since the cursor was issued. Restart from the first page.'\n )\n }\n\n qb.andWhere(\n new Brackets(outer => {\n sortings.forEach((sorting, i) => {\n const params: Record<string, unknown> = {}\n const parts: string[] = []\n /* 앞 축들은 같고(=), i 번째 축에서 갈린다(< 또는 >). */\n for (let j = 0; j < i; j++) {\n params[`ksEq${i}_${j}`] = values[j]\n parts.push(`${column(sortings[j].name)} = :ksEq${i}_${j}`)\n }\n params[`ksCmp${i}`] = values[i]\n parts.push(`${column(sorting.name)} ${sorting.desc ? '<' : '>'} :ksCmp${i}`)\n outer.orWhere(`(${parts.join(' AND ')})`, params)\n })\n })\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/shell",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.34",
|
|
4
4
|
"description": "Core module for framework",
|
|
5
5
|
"bin": {
|
|
6
6
|
"things-factory": "bin/things-factory",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"pg": "^8.7.3",
|
|
127
127
|
"sqlite3": "^5.0.8"
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "5e16fd9f0cbb71c8d395b58a95bd9e99c93975c7"
|
|
130
130
|
}
|