From 05f42acdafc194ade74b5234d18816bd248032cd Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 21 Jun 2026 15:43:55 +0300 Subject: [PATCH 1/2] fix: map CoreFields SQL keys to row property names in query results --- src/lib/mysql/domain/materialized-view.ts | 15 ++--- src/lib/mysql/domain/table.ts | 19 ++++--- src/lib/mysql/domain/view.ts | 15 ++--- src/lib/mysql/repository/materialized-view.ts | 14 ++--- src/lib/mysql/repository/table.ts | 18 +++--- src/lib/mysql/repository/view.ts | 14 ++--- src/lib/pg/domain/materialized-view.ts | 15 ++--- src/lib/pg/domain/table.ts | 35 ++++++------ src/lib/pg/domain/view.ts | 15 ++--- src/lib/pg/repository/materialized-view.ts | 14 ++--- src/lib/pg/repository/table.ts | 34 +++++------ src/lib/pg/repository/view.ts | 14 ++--- src/shared-types/index.ts | 7 +++ src/shared-types/sql-field-keys.ts | 38 +++++++++++++ src/shared-types/sql-field-keys.unit.spec.ts | 56 +++++++++++++++++++ 15 files changed, 215 insertions(+), 108 deletions(-) create mode 100644 src/shared-types/sql-field-keys.ts create mode 100644 src/shared-types/sql-field-keys.unit.spec.ts diff --git a/src/lib/mysql/domain/materialized-view.ts b/src/lib/mysql/domain/materialized-view.ts index e3b9c49..5d21c87 100644 --- a/src/lib/mysql/domain/materialized-view.ts +++ b/src/lib/mysql/domain/materialized-view.ts @@ -10,6 +10,7 @@ import { export type BaseMaterializedViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; + RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; @@ -200,8 +201,8 @@ export class BaseMaterializedView< orderBy: Extract | (BMVG["AdditionalSortingFields"] extends string ? BMVG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -239,8 +240,8 @@ export class BaseMaterializedView< params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -280,7 +281,7 @@ export class BaseMaterializedView< * - `highWaterMark`: The max number of records buffered in the stream. * - `objectMode`: If `true`, the stream operates in object mode (default for object streams). * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -294,8 +295,8 @@ export class BaseMaterializedView< }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/mysql/domain/table.ts b/src/lib/mysql/domain/table.ts index c8c55b1..7046135 100644 --- a/src/lib/mysql/domain/table.ts +++ b/src/lib/mysql/domain/table.ts @@ -11,6 +11,7 @@ export type BaseTableGeneric = { AdditionalSortingFields?: string; CreateFields?: SharedTypes.TRawParams; CoreFields: SharedTypes.TRawParams; + RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; UpdateFields?: SharedTypes.TRawParams; }; @@ -464,8 +465,8 @@ export class BaseTable< orderBy: Extract | (BTG["AdditionalSortingFields"] extends string ? BTG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -537,8 +538,8 @@ export class BaseTable< params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -558,8 +559,8 @@ export class BaseTable< * * @returns A promise that resolves to the retrieved record with the selected fields or a message if not found. */ - async getOneByPk(pk: T): Promise<{ message?: string; one?: BTG["CoreFields"]; }> { - const one = await this.model.getOneByPk(pk); + async getOneByPk(pk: T): Promise<{ message?: string; one?: SharedTypes.ResolveRowFields; }> { + const one = await this.model.getOneByPk>(pk); if (!one) return { message: `Not found from ${this.model.tableName}` }; @@ -585,7 +586,7 @@ export class BaseTable< * - `highWaterMark`: The max number of records buffered in the stream. * - `objectMode`: If `true`, the stream operates in object mode (default for object streams). * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -599,8 +600,8 @@ export class BaseTable< }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/mysql/domain/view.ts b/src/lib/mysql/domain/view.ts index 1c0680f..1cdb493 100644 --- a/src/lib/mysql/domain/view.ts +++ b/src/lib/mysql/domain/view.ts @@ -10,6 +10,7 @@ import { export type BaseViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; + RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; @@ -175,8 +176,8 @@ export class BaseView< orderBy: Extract | (BVG["AdditionalSortingFields"] extends string ? BVG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -214,8 +215,8 @@ export class BaseView< params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -244,7 +245,7 @@ export class BaseView< * - `highWaterMark`: The max number of records buffered in the stream. * - `objectMode`: If `true`, the stream operates in object mode (default for object streams). * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -258,8 +259,8 @@ export class BaseView< }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/mysql/repository/materialized-view.ts b/src/lib/mysql/repository/materialized-view.ts index 1cf30c9..dba579a 100644 --- a/src/lib/mysql/repository/materialized-view.ts +++ b/src/lib/mysql/repository/materialized-view.ts @@ -218,8 +218,8 @@ export class MaterializedView | (MVG["AdditionalSortingFields"] extends string ? MVG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -257,8 +257,8 @@ export class MaterializedView>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -298,7 +298,7 @@ export class MaterializedView` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -312,8 +312,8 @@ export class MaterializedView>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/mysql/repository/table.ts b/src/lib/mysql/repository/table.ts index eea70cc..a57a93c 100644 --- a/src/lib/mysql/repository/table.ts +++ b/src/lib/mysql/repository/table.ts @@ -486,8 +486,8 @@ export class Table { orderBy: Extract | (TG["AdditionalSortingFields"] extends string ? TG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -559,8 +559,8 @@ export class Table { params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -580,8 +580,8 @@ export class Table { * * @returns A promise that resolves to the retrieved record with the selected fields or a message if not found. */ - async getOneByPk(pk: T): Promise<{ message?: string; one?: TG["CoreFields"]; }> { - const one = await this.model.getOneByPk(pk); + async getOneByPk(pk: T): Promise<{ message?: string; one?: SharedTypes.ResolveRowFields; }> { + const one = await this.model.getOneByPk>(pk); if (!one) return { message: `Not found from ${this.model.tableName}` }; @@ -607,7 +607,7 @@ export class Table { * - `highWaterMark`: The max number of records buffered in the stream. * - `objectMode`: If `true`, the stream operates in object mode (default for object streams). * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -621,8 +621,8 @@ export class Table { }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/mysql/repository/view.ts b/src/lib/mysql/repository/view.ts index 228e3a0..b046711 100644 --- a/src/lib/mysql/repository/view.ts +++ b/src/lib/mysql/repository/view.ts @@ -216,8 +216,8 @@ export class View { orderBy: Extract | (VG["AdditionalSortingFields"] extends string ? VG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.#model.getArrByParams>( + }): Promise>> { + return this.#model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -255,8 +255,8 @@ export class View { params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.#model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.#model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -285,7 +285,7 @@ export class View { * - `highWaterMark`: The max number of records buffered in the stream. * - `objectMode`: If `true`, the stream operates in object mode (default for object streams). * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -299,8 +299,8 @@ export class View { }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.#model.streamArrByParams>( + ): Promise>> { + return this.#model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/pg/domain/materialized-view.ts b/src/lib/pg/domain/materialized-view.ts index 7d2dfa2..345be91 100644 --- a/src/lib/pg/domain/materialized-view.ts +++ b/src/lib/pg/domain/materialized-view.ts @@ -10,6 +10,7 @@ import { export type BaseMaterializedViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; + RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; @@ -200,8 +201,8 @@ export class BaseMaterializedView< orderBy: Extract | (BMVG["AdditionalSortingFields"] extends string ? BMVG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -239,8 +240,8 @@ export class BaseMaterializedView< params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -282,7 +283,7 @@ export class BaseMaterializedView< * - `rowMode`: If set to `"array"`, rows will be returned as arrays instead of objects. * - `types`: Custom type parser map for Postgres types. * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -296,8 +297,8 @@ export class BaseMaterializedView< }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/pg/domain/table.ts b/src/lib/pg/domain/table.ts index 62da486..b7a0594 100644 --- a/src/lib/pg/domain/table.ts +++ b/src/lib/pg/domain/table.ts @@ -11,6 +11,7 @@ export type BaseTableGeneric = { AdditionalSortingFields?: string; CreateFields?: SharedTypes.TRawParams; CoreFields: SharedTypes.TRawParams; + RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; UpdateFields?: SharedTypes.TRawParams; }; @@ -354,8 +355,8 @@ export class BaseTable< async createOne[] = Extract[]>( recordParams: Types.TConditionalRawParamsType, saveOptions?: { returningFields?: T; }, - ): Promise> { - const res = await this.model.createOne>(recordParams, saveOptions); + ): Promise : SharedTypes.PickRowFields> { + const res = await this.model.createOne : SharedTypes.PickRowFields>(recordParams, saveOptions); if (!res) throw new Error(`Save to ${this.model.tableName} table error`); @@ -376,8 +377,8 @@ export class BaseTable< async createMany[] = Extract[]>( recordParams: Types.TConditionalRawParamsType[], saveOptions?: { returningFields?: T; }, - ): Promise<(T extends undefined ? BTG["CoreFields"][] : Pick[])[]> { - const res = await this.model.createMany[]>(recordParams, saveOptions); + ): Promise<(T extends undefined ? SharedTypes.ResolveRowFields[] : SharedTypes.PickRowFields[])[]> { + const res = await this.model.createMany[] : SharedTypes.PickRowFields[]>(recordParams, saveOptions); if (!res) throw new Error(`Save to ${this.model.tableName} table error`); @@ -485,8 +486,8 @@ export class BaseTable< orderBy: Extract | (BTG["AdditionalSortingFields"] extends string ? BTG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -558,8 +559,8 @@ export class BaseTable< params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -579,8 +580,8 @@ export class BaseTable< * * @returns A promise that resolves to the retrieved record with the selected fields or a message if not found. */ - async getOneByPk(pk: T): Promise<{ message?: string; one?: BTG["CoreFields"]; }> { - const one = await this.model.getOneByPk(pk); + async getOneByPk(pk: T): Promise<{ message?: string; one?: SharedTypes.ResolveRowFields; }> { + const one = await this.model.getOneByPk>(pk); if (!one) return { message: `Not found from ${this.model.tableName}` }; @@ -608,7 +609,7 @@ export class BaseTable< * - `rowMode`: If set to `"array"`, rows will be returned as arrays instead of objects. * - `types`: Custom type parser map for Postgres types. * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -622,8 +623,8 @@ export class BaseTable< }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -650,8 +651,8 @@ export class BaseTable< returningFields?: T; }, updateFields: Types.TConditionalRawParamsType, - ): Promise { - return this.model.updateByParams({ $and: queryConditions.params, $or: queryConditions.paramsOr, returningFields: queryConditions.returningFields }, updateFields); + ): Promise[]> { + return this.model.updateByParams({ $and: queryConditions.params, $or: queryConditions.paramsOr, returningFields: queryConditions.returningFields }, updateFields) as Promise[]>; } /** @@ -668,8 +669,8 @@ export class BaseTable< primaryKeyValue: T, updateFields: Types.TConditionalRawParamsType, updateOptions?: { returningFields?: R; }, - ): Promise { - const one = await this.model.updateOneByPk(primaryKeyValue, updateFields, updateOptions); + ): Promise | undefined> { + const one = await this.model.updateOneByPk, T>(primaryKeyValue, updateFields, updateOptions); return one; } diff --git a/src/lib/pg/domain/view.ts b/src/lib/pg/domain/view.ts index ce96930..c1699df 100644 --- a/src/lib/pg/domain/view.ts +++ b/src/lib/pg/domain/view.ts @@ -10,6 +10,7 @@ import { export type BaseViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; + RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; @@ -199,8 +200,8 @@ export class BaseView< orderBy: Extract | (BVG["AdditionalSortingFields"] extends string ? BVG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.model.getArrByParams>( + }): Promise>> { + return this.model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -238,8 +239,8 @@ export class BaseView< params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -270,7 +271,7 @@ export class BaseView< * - `rowMode`: If set to `"array"`, rows will be returned as arrays instead of objects. * - `types`: Custom type parser map for Postgres types. * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -284,8 +285,8 @@ export class BaseView< }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.model.streamArrByParams>( + ): Promise>> { + return this.model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/pg/repository/materialized-view.ts b/src/lib/pg/repository/materialized-view.ts index 94bdd71..e5da3d7 100644 --- a/src/lib/pg/repository/materialized-view.ts +++ b/src/lib/pg/repository/materialized-view.ts @@ -217,8 +217,8 @@ export class MaterializedView | (MVG["AdditionalSortingFields"] extends string ? MVG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.#model.getArrByParams>( + }): Promise>> { + return this.#model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -256,8 +256,8 @@ export class MaterializedView>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.#model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.#model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -299,7 +299,7 @@ export class MaterializedView` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -313,8 +313,8 @@ export class MaterializedView>> { - return this.#model.streamArrByParams>( + ): Promise>> { + return this.#model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/lib/pg/repository/table.ts b/src/lib/pg/repository/table.ts index 267377a..8c293d1 100644 --- a/src/lib/pg/repository/table.ts +++ b/src/lib/pg/repository/table.ts @@ -377,8 +377,8 @@ export class Table { async createOne[] = Extract[]>( recordParams: Types.TConditionalRawParamsType, saveOptions?: { returningFields?: T; }, - ): Promise> { - const res = await this.#model.createOne>(recordParams, saveOptions); + ): Promise : SharedTypes.PickRowFields> { + const res = await this.#model.createOne : SharedTypes.PickRowFields>(recordParams, saveOptions); if (!res) throw new Error(`Save to ${this.#model.tableName} table error`); @@ -399,8 +399,8 @@ export class Table { async createMany[] = Extract[]>( recordParams: Types.TConditionalRawParamsType[], saveOptions?: { returningFields?: T; }, - ): Promise<(T extends undefined ? TG["CoreFields"][] : Pick[])[]> { - const res = await this.#model.createMany[]>(recordParams, saveOptions); + ): Promise<(T extends undefined ? SharedTypes.ResolveRowFields[] : SharedTypes.PickRowFields[])[]> { + const res = await this.#model.createMany[] : SharedTypes.PickRowFields[]>(recordParams, saveOptions); if (!res) throw new Error(`Save to ${this.#model.tableName} table error`); @@ -508,8 +508,8 @@ export class Table { orderBy: Extract | (TG["AdditionalSortingFields"] extends string ? TG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.#model.getArrByParams>( + }): Promise>> { + return this.#model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -581,8 +581,8 @@ export class Table { params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.#model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.#model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -602,8 +602,8 @@ export class Table { * * @returns A promise that resolves to the retrieved record with the selected fields or a message if not found. */ - async getOneByPk(pk: T): Promise<{ message?: string; one?: TG["CoreFields"]; }> { - const one = await this.#model.getOneByPk(pk); + async getOneByPk(pk: T): Promise<{ message?: string; one?: SharedTypes.ResolveRowFields; }> { + const one = await this.#model.getOneByPk>(pk); if (!one) return { message: `Not found from ${this.#model.tableName}` }; @@ -631,7 +631,7 @@ export class Table { * - `rowMode`: If set to `"array"`, rows will be returned as arrays instead of objects. * - `types`: Custom type parser map for Postgres types. * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -645,8 +645,8 @@ export class Table { }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.#model.streamArrByParams>( + ): Promise>> { + return this.#model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -673,8 +673,8 @@ export class Table { returningFields?: T; }, updateFields: Types.TConditionalRawParamsType, - ): Promise { - return this.#model.updateByParams({ $and: queryConditions.params, $or: queryConditions.paramsOr, returningFields: queryConditions.returningFields }, updateFields); + ): Promise[]> { + return this.#model.updateByParams({ $and: queryConditions.params, $or: queryConditions.paramsOr, returningFields: queryConditions.returningFields }, updateFields) as Promise[]>; } /** @@ -691,8 +691,8 @@ export class Table { primaryKeyValue: T, updateFields: Types.TConditionalRawParamsType, updateOptions?: { returningFields?: R; }, - ): Promise { - const one = await this.#model.updateOneByPk(primaryKeyValue, updateFields, updateOptions); + ): Promise | undefined> { + const one = await this.#model.updateOneByPk, T>(primaryKeyValue, updateFields, updateOptions); return one; } diff --git a/src/lib/pg/repository/view.ts b/src/lib/pg/repository/view.ts index b97b533..b45f611 100644 --- a/src/lib/pg/repository/view.ts +++ b/src/lib/pg/repository/view.ts @@ -216,8 +216,8 @@ export class View { orderBy: Extract | (VG["AdditionalSortingFields"] extends string ? VG["AdditionalSortingFields"] : never); ordering: SharedTypes.TOrdering; }[]; - }): Promise>> { - return this.#model.getArrByParams>( + }): Promise>> { + return this.#model.getArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, @@ -255,8 +255,8 @@ export class View { params: Types.TSearchParams>; paramsOr?: Types.TSearchParams>[]; selected?: [T, ...T[]]; - }): Promise<{ message?: string; one?: Pick; }> { - const one = await this.#model.getOneByParams>( + }): Promise<{ message?: string; one?: SharedTypes.PickRowFields; }> { + const one = await this.#model.getOneByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], ); @@ -287,7 +287,7 @@ export class View { * - `rowMode`: If set to `"array"`, rows will be returned as arrays instead of objects. * - `types`: Custom type parser map for Postgres types. * - * @returns A readable stream emitting records of type `Pick` on the `"data"` event. + * @returns A readable stream emitting records of type `SharedTypes.PickRowFields` on the `"data"` event. */ async streamArrByParams( options: { @@ -301,8 +301,8 @@ export class View { }[]; }, streamOptions?: StreamOptions, - ): Promise>> { - return this.#model.streamArrByParams>( + ): Promise>> { + return this.#model.streamArrByParams>( { $and: options.params, $or: options.paramsOr }, options.selected as string[], options.pagination, diff --git a/src/shared-types/index.ts b/src/shared-types/index.ts index 4d25ba1..87451bb 100644 --- a/src/shared-types/index.ts +++ b/src/shared-types/index.ts @@ -5,6 +5,13 @@ export interface ITypedPgStream extends Readable { // eslint-disable-next-line @typescript-eslint/no-explicit-any on(event: string, listener: (...args: any[]) => void): this; } +export type { + PickRowFields, + ResolveRowFields, + RowFieldsFromCore, + TRowFieldsGeneric, + UnquoteSqlKey, +} from "./sql-field-keys.js"; export type TLogger = { error: (str: string) => void; info: (str: string) => void; }; export type TOrdering = "ASC" | "DESC"; export type TPagination = { limit: number; offset: number; }; diff --git a/src/shared-types/sql-field-keys.ts b/src/shared-types/sql-field-keys.ts new file mode 100644 index 0000000..feceb02 --- /dev/null +++ b/src/shared-types/sql-field-keys.ts @@ -0,0 +1,38 @@ +type TRawParams = { + [key: string]: object | string | number | boolean | null | undefined; +}; + +/** + * Picks row fields by SQL `selected` keys from `CoreFields`. + */ +export type PickRowFields< + TCore extends TRawParams, + TSelected extends keyof TCore, +> = Pick, UnquoteSqlKey>; + +/** + * Resolves row shape: explicit `RowFields` or derived from `CoreFields`. + */ +export type ResolveRowFields = + TG["RowFields"] extends TRawParams ? TG["RowFields"] : RowFieldsFromCore; + +/** + * Maps all `CoreFields` keys to row object keys (unquoted when needed). + */ +export type RowFieldsFromCore = { + [K in keyof T as UnquoteSqlKey]: T[K]; +}; + +export type TRowFieldsGeneric = { + CoreFields: TRawParams; + RowFields?: TRawParams; +}; + +/** + * Converts a SQL identifier key from `CoreFields` to the JS property name returned by the driver. + * + * @example + * UnquoteSqlKey<'"typeID"'> → 'typeID' + * UnquoteSqlKey<'type_id'> → 'type_id' + */ +export type UnquoteSqlKey = S extends `"${infer Inner}"` ? Inner : S; diff --git a/src/shared-types/sql-field-keys.unit.spec.ts b/src/shared-types/sql-field-keys.unit.spec.ts new file mode 100644 index 0000000..a2af88e --- /dev/null +++ b/src/shared-types/sql-field-keys.unit.spec.ts @@ -0,0 +1,56 @@ +import { + describe, + expectTypeOf, + it, +} from "vitest"; + +import type { + PickRowFields, + ResolveRowFields, + RowFieldsFromCore, + UnquoteSqlKey, +} from "./sql-field-keys.js"; + +type EveCoreFields = { + "\"typeID\"": number; + "\"groupName\"": string | null; + description: string | null; + published: boolean | null; +}; + +describe("sql field key types", () => { + it("should unquote SQL identifiers", () => { + expectTypeOf>().toEqualTypeOf<"typeID">(); + expectTypeOf>().toEqualTypeOf<"description">(); + }); + + it("should map CoreFields to row fields", () => { + expectTypeOf>().toEqualTypeOf<{ + typeID: number; + groupName: string | null; + description: string | null; + published: boolean | null; + }>(); + }); + + it("should pick row fields by SQL selected keys", () => { + expectTypeOf>().toEqualTypeOf<{ + typeID: number; + }>(); + }); + + it("should resolve row fields from generic", () => { + type Generic = { CoreFields: EveCoreFields; }; + + expectTypeOf>().toEqualTypeOf>(); + }); + + it("should prefer explicit RowFields override", () => { + type Generic = { + CoreFields: EveCoreFields; + RowFields: { custom: string; }; + }; + + expectTypeOf>().toEqualTypeOf<{ custom: string; }>(); + }); +}); From 93e38b9e3e96113b03ac3fbcc480328a4960d090 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 21 Jun 2026 16:02:24 +0300 Subject: [PATCH 2/2] refactor: remove unused RowFields generic override --- src/examples/pg/02/data-access-layer/index.ts | 2 + .../data-access-layer/repository-manager.ts | 10 ++++ .../02/data-access-layer/repository/index.ts | 1 + .../repository/inv-type/domain.ts | 24 ++++++++++ .../repository/inv-type/index.ts | 2 + .../repository/inv-type/types.ts | 23 ++++++++++ src/examples/pg/02/index.ts | 46 +++++++++++++++++++ src/lib/mysql/domain/materialized-view.ts | 1 - src/lib/mysql/domain/table.ts | 1 - src/lib/mysql/domain/view.ts | 1 - src/lib/pg/domain/materialized-view.ts | 1 - src/lib/pg/domain/table.ts | 1 - src/lib/pg/domain/view.ts | 1 - src/shared-types/index.ts | 1 - src/shared-types/sql-field-keys.ts | 11 ++--- src/shared-types/sql-field-keys.unit.spec.ts | 9 ---- 16 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 src/examples/pg/02/data-access-layer/index.ts create mode 100644 src/examples/pg/02/data-access-layer/repository-manager.ts create mode 100644 src/examples/pg/02/data-access-layer/repository/index.ts create mode 100644 src/examples/pg/02/data-access-layer/repository/inv-type/domain.ts create mode 100644 src/examples/pg/02/data-access-layer/repository/inv-type/index.ts create mode 100644 src/examples/pg/02/data-access-layer/repository/inv-type/types.ts create mode 100644 src/examples/pg/02/index.ts diff --git a/src/examples/pg/02/data-access-layer/index.ts b/src/examples/pg/02/data-access-layer/index.ts new file mode 100644 index 0000000..751956c --- /dev/null +++ b/src/examples/pg/02/data-access-layer/index.ts @@ -0,0 +1,2 @@ +export * as RepositoryManager from "./repository-manager.js"; +export * as Repository from "./repository/index.js"; diff --git a/src/examples/pg/02/data-access-layer/repository-manager.ts b/src/examples/pg/02/data-access-layer/repository-manager.ts new file mode 100644 index 0000000..60bd866 --- /dev/null +++ b/src/examples/pg/02/data-access-layer/repository-manager.ts @@ -0,0 +1,10 @@ +import { PG } from "../../../../index.js"; + +import * as Repository from "./repository/index.js"; + +export const create = (creds: PG.ModelTypes.TDBCreds) => new PG.RepositoryManager( + { + invType: Repository.InvType.domain(creds), + }, + { config: creds, isLoggerEnabled: false, logger: console }, +); diff --git a/src/examples/pg/02/data-access-layer/repository/index.ts b/src/examples/pg/02/data-access-layer/repository/index.ts new file mode 100644 index 0000000..5a92718 --- /dev/null +++ b/src/examples/pg/02/data-access-layer/repository/index.ts @@ -0,0 +1 @@ +export * as InvType from "./inv-type/index.js"; diff --git a/src/examples/pg/02/data-access-layer/repository/inv-type/domain.ts b/src/examples/pg/02/data-access-layer/repository/inv-type/domain.ts new file mode 100644 index 0000000..519d166 --- /dev/null +++ b/src/examples/pg/02/data-access-layer/repository/inv-type/domain.ts @@ -0,0 +1,24 @@ +import { PG } from "../../../../../../index.js"; + +import * as Types from "./types.js"; + +export const domain = (dbCreds: PG.ModelTypes.TDBCreds) => { + return new PG.Repository.Table<{ CoreFields: Types.TableFields; }>({ + dbCreds, + schema: { + tableName: "invTypes", + + primaryKey: "\"typeID\"", + + tableFields: [ + "\"typeID\"", + "\"groupID\"", + "\"typeName\"", + "published", + ], + + createField: null, + updateField: null, + }, + }); +}; diff --git a/src/examples/pg/02/data-access-layer/repository/inv-type/index.ts b/src/examples/pg/02/data-access-layer/repository/inv-type/index.ts new file mode 100644 index 0000000..2e19d95 --- /dev/null +++ b/src/examples/pg/02/data-access-layer/repository/inv-type/index.ts @@ -0,0 +1,2 @@ +export * from "./domain.js"; +export * as Types from "./types.js"; diff --git a/src/examples/pg/02/data-access-layer/repository/inv-type/types.ts b/src/examples/pg/02/data-access-layer/repository/inv-type/types.ts new file mode 100644 index 0000000..b87d168 --- /dev/null +++ b/src/examples/pg/02/data-access-layer/repository/inv-type/types.ts @@ -0,0 +1,23 @@ +import type { PickRowFields } from "../../../../../../shared-types/index.js"; + +/** Fields allowed in SELECT via `selected`. Keys are SQL identifiers. */ +export type EntityListed = Pick; + +/** Row shape returned by node-pg for `EntityListed`. Keys are JS property names. */ +export type RowEntityListed = PickRowFields; + +export type SearchFields = Partial; + +/** + * SQL column identifiers as TypeScript keys. + * Use these keys in params, selected, orderBy and tableFields. + */ +export type TableFields = { + "\"typeID\"": number; + "\"groupID\"": number; + "\"typeName\"": string; + published: boolean | null; +}; diff --git a/src/examples/pg/02/index.ts b/src/examples/pg/02/index.ts new file mode 100644 index 0000000..9f5bfe9 --- /dev/null +++ b/src/examples/pg/02/index.ts @@ -0,0 +1,46 @@ +/* eslint-disable no-console */ + +import { RepositoryManager } from "./data-access-layer/index.js"; + +const creds = { + database: "eve", + host: "localhost", + password: "admin", + port: 5432, + user: "postgres", +}; + +const repositoryManager = RepositoryManager.create(creds); + +await repositoryManager.init(); + +const invTypeRepository = repositoryManager.repository.invType; + +// `selected` uses SQL keys from CoreFields (`"typeID"`), not JS keys (`typeID`). +const { one: invType } = await invTypeRepository.getOneByParams({ + params: { "\"typeID\"": 34 }, + selected: ["\"typeID\"", "\"typeName\""], +}); + +if (invType) { + // Row uses JS keys as returned by node-pg — no `as any`. + console.log({ + name: invType.typeName, + typeId: invType.typeID, + }); +} else { + console.log("invType not found"); +} + +const invTypes = await invTypeRepository.getArrByParams({ + pagination: { limit: 10, offset: 0 }, + params: { published: true }, + selected: ["\"typeID\"", "\"typeName\""], +}); + +for (const row of invTypes) { + console.log({ + name: row.typeName, + typeId: row.typeID, + }); +} diff --git a/src/lib/mysql/domain/materialized-view.ts b/src/lib/mysql/domain/materialized-view.ts index 5d21c87..7524fcd 100644 --- a/src/lib/mysql/domain/materialized-view.ts +++ b/src/lib/mysql/domain/materialized-view.ts @@ -10,7 +10,6 @@ import { export type BaseMaterializedViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; - RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; diff --git a/src/lib/mysql/domain/table.ts b/src/lib/mysql/domain/table.ts index 7046135..1c93cc7 100644 --- a/src/lib/mysql/domain/table.ts +++ b/src/lib/mysql/domain/table.ts @@ -11,7 +11,6 @@ export type BaseTableGeneric = { AdditionalSortingFields?: string; CreateFields?: SharedTypes.TRawParams; CoreFields: SharedTypes.TRawParams; - RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; UpdateFields?: SharedTypes.TRawParams; }; diff --git a/src/lib/mysql/domain/view.ts b/src/lib/mysql/domain/view.ts index 1cdb493..d1e07d5 100644 --- a/src/lib/mysql/domain/view.ts +++ b/src/lib/mysql/domain/view.ts @@ -10,7 +10,6 @@ import { export type BaseViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; - RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; diff --git a/src/lib/pg/domain/materialized-view.ts b/src/lib/pg/domain/materialized-view.ts index 345be91..64b7141 100644 --- a/src/lib/pg/domain/materialized-view.ts +++ b/src/lib/pg/domain/materialized-view.ts @@ -10,7 +10,6 @@ import { export type BaseMaterializedViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; - RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; diff --git a/src/lib/pg/domain/table.ts b/src/lib/pg/domain/table.ts index b7a0594..21ceddf 100644 --- a/src/lib/pg/domain/table.ts +++ b/src/lib/pg/domain/table.ts @@ -11,7 +11,6 @@ export type BaseTableGeneric = { AdditionalSortingFields?: string; CreateFields?: SharedTypes.TRawParams; CoreFields: SharedTypes.TRawParams; - RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; UpdateFields?: SharedTypes.TRawParams; }; diff --git a/src/lib/pg/domain/view.ts b/src/lib/pg/domain/view.ts index c1699df..6bff2aa 100644 --- a/src/lib/pg/domain/view.ts +++ b/src/lib/pg/domain/view.ts @@ -10,7 +10,6 @@ import { export type BaseViewGeneric = { AdditionalSortingFields?: string; CoreFields: SharedTypes.TRawParams; - RowFields?: SharedTypes.TRawParams; SearchFields?: Types.TDomainFields; }; diff --git a/src/shared-types/index.ts b/src/shared-types/index.ts index 87451bb..f6f6d3a 100644 --- a/src/shared-types/index.ts +++ b/src/shared-types/index.ts @@ -9,7 +9,6 @@ export type { PickRowFields, ResolveRowFields, RowFieldsFromCore, - TRowFieldsGeneric, UnquoteSqlKey, } from "./sql-field-keys.js"; export type TLogger = { error: (str: string) => void; info: (str: string) => void; }; diff --git a/src/shared-types/sql-field-keys.ts b/src/shared-types/sql-field-keys.ts index feceb02..53d5ce3 100644 --- a/src/shared-types/sql-field-keys.ts +++ b/src/shared-types/sql-field-keys.ts @@ -11,10 +11,10 @@ export type PickRowFields< > = Pick, UnquoteSqlKey>; /** - * Resolves row shape: explicit `RowFields` or derived from `CoreFields`. + * Row shape returned by the driver for all `CoreFields` columns. */ -export type ResolveRowFields = - TG["RowFields"] extends TRawParams ? TG["RowFields"] : RowFieldsFromCore; +export type ResolveRowFields = + RowFieldsFromCore; /** * Maps all `CoreFields` keys to row object keys (unquoted when needed). @@ -23,11 +23,6 @@ export type RowFieldsFromCore = { [K in keyof T as UnquoteSqlKey]: T[K]; }; -export type TRowFieldsGeneric = { - CoreFields: TRawParams; - RowFields?: TRawParams; -}; - /** * Converts a SQL identifier key from `CoreFields` to the JS property name returned by the driver. * diff --git a/src/shared-types/sql-field-keys.unit.spec.ts b/src/shared-types/sql-field-keys.unit.spec.ts index a2af88e..13c6322 100644 --- a/src/shared-types/sql-field-keys.unit.spec.ts +++ b/src/shared-types/sql-field-keys.unit.spec.ts @@ -44,13 +44,4 @@ describe("sql field key types", () => { expectTypeOf>().toEqualTypeOf>(); }); - - it("should prefer explicit RowFields override", () => { - type Generic = { - CoreFields: EveCoreFields; - RowFields: { custom: string; }; - }; - - expectTypeOf>().toEqualTypeOf<{ custom: string; }>(); - }); });