diff --git a/package.json b/package.json index bcdceee..aeea625 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@yizhi/postgres", - "version": "1.0.1", + "version": "1.0.4", "main": "dist/index.js", "types": "typing/index.d.ts", "scripts": {}, @@ -19,4 +19,4 @@ "@types/pinyin": "^2.10.2", "typescript": "^5.6.3" } -} +} \ No newline at end of file diff --git a/src/entity.ts b/src/entity.ts index d94d884..dc778fe 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -11,6 +11,8 @@ export interface IEntityFieldConfig { prop: string /** 是否是主键 */ primary: boolean + /** 是否是虚拟字段 */ + virtual: boolean /** 编码方式(存入数据库时) */ encode: (data: any) => string /** 解码方式(查询数据库时) */ @@ -134,11 +136,12 @@ export function Table(table: string): Decorators.ClassDecorator { * @param encode 字段编码凡是 * @param decode 字段解码方式 * @param primary 是否是主键 + * @param virtual 是否是虚拟字段 */ -export function Field(name: string | undefined, encode: IEntityFieldConfig["encode"], decode: IEntityFieldConfig["decode"], primary: boolean): Decorators.PropDecorator { +export function Field(name: string | undefined, encode: IEntityFieldConfig["encode"], decode: IEntityFieldConfig["decode"], primary: boolean, virtual: boolean): Decorators.PropDecorator { return function (target, propertyKey) { const conf = entityConfigOf(target.constructor) - conf.fields.push({ name: name ?? propertyKey, prop: propertyKey, encode, decode, primary }); + conf.fields.push({ name: name ?? propertyKey, prop: propertyKey, encode, decode, primary, virtual }); } } @@ -148,22 +151,32 @@ export function Field(name: string | undefined, encode: IEntityFieldConfig["enco * @param name 字段名称 * @param option 字段选项 */ -export function IntColumn(name: string, option?: { primary: boolean }): Decorators.PropDecorator -export function IntColumn(option?: { primary: boolean }): Decorators.PropDecorator +export function IntColumn(name: string, option?: { primary: boolean, virtual?: boolean }): Decorators.PropDecorator +export function IntColumn(option?: { primary: boolean, virtual?: boolean }): Decorators.PropDecorator export function IntColumn(name?: any, option?: any) { if (typeof name != "string") { option = name; name = undefined; } - return Field(name, v => escapeValue(v), v => v, option?.primary ?? false); + return Field(name, v => escapeValue(v), v => v, option?.primary ?? false, option?.virtual ?? false); } /** * 定义浮点数(包括定点小数) * @param name 字段名称 + * @param option 字段选项 */ -export function FloatColumn(name?: string) { return Field(name, v => escapeValue(v), v => v, false); } +export function FloatColumn(option?: { virtual?: boolean }): Decorators.PropDecorator +export function FloatColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator +export function FloatColumn(name?: any, option?: any) { + if (typeof name !== "string") { + option = name; + name = undefined; + } + + return Field(name, v => escapeValue(v), v => v, false, option?.virtual ?? false); +} /** @@ -171,21 +184,29 @@ export function FloatColumn(name?: string) { return Field(name, v => escapeValue * @param name 字段名称 * @param option 字段选项 */ -export function StringColumn(name: string, option?: { primary?: boolean }): Decorators.PropDecorator -export function StringColumn(option?: { primary?: boolean }): Decorators.PropDecorator +export function StringColumn(option?: { primary?: boolean, virtual?: boolean }): Decorators.PropDecorator +export function StringColumn(name: string, option?: { primary?: boolean, virtual?: boolean }): Decorators.PropDecorator export function StringColumn(name?: any, option?: any) { if (typeof name != "string") { option = name name = undefined } - return Field(name, v => escapeValue(v), v => v, option?.primary ?? false); + return Field(name, v => escapeValue(v), v => v, option?.primary ?? false, option?.virtual ?? false); } /** * 定义布尔字段 * @param name 字段名称 + * @param option 字段选项 */ -export function BooleanColumn(name?: string) { +export function BooleanColumn(option?: { virtual?: boolean }): Decorators.PropDecorator +export function BooleanColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator +export function BooleanColumn(name?: any, option?: any) { + if (typeof name != "string") { + option = name + name = undefined + } + const checker = (v: any) => { if (typeof v == "boolean") return v; if (typeof v != "string") return false; @@ -193,14 +214,22 @@ export function BooleanColumn(name?: string) { return ["true", "yes", "on"].includes(v) } - return Field(name, (v) => escapeValue(checker(v)), checker, false); + return Field(name, (v) => escapeValue(checker(v)), checker, false, option?.virtual ?? false); } /** * 定义日期字段 * @param name 字段名称 + * @param option 字段选项 */ -export function DateColumn(name?: string) { +export function DateColumn(option?: { virtual?: boolean }): Decorators.PropDecorator +export function DateColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator +export function DateColumn(name?: any, option?: any) { + if (typeof name != "string") { + option = name + name = undefined + } + return Field(name, v => { if (v instanceof DateColumn) v = moment(v); if (typeof v == "string") v = moment(v); @@ -209,14 +238,21 @@ export function DateColumn(name?: string) { }, v => { if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD"); return null; - }, false); + }, false, option?.virtual ?? false); } /** * 定义日期时间字段 * @param name 字段名称 */ -export function DateTimeColumn(name?: string) { +export function DateTimeColumn(option?: { virtual?: boolean }): Decorators.PropDecorator +export function DateTimeColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator +export function DateTimeColumn(name?: any, option?: any) { + if (typeof name != "string") { + option = name + name = undefined + } + return Field(name, v => { if (v instanceof DateColumn) v = moment(v); if (typeof v == "string") v = moment(v); @@ -225,12 +261,12 @@ export function DateTimeColumn(name?: string) { }, v => { if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD HH:mm:ss"); return null; - }, false); + }, false, option?.virtual ?? false); } //数组定义 -function _Array(name: string | undefined, typeName: string, itemEncoder: (v: any) => (string | null), itemDecoder: ((v: any) => (T | null)) | undefined) { +function _Array(name: string | undefined, typeName: string, itemEncoder: (v: any) => (string | null), itemDecoder: ((v: any) => (T | null)) | undefined, virtual: boolean) { return Field(name, v => { if (!(v instanceof Array)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_ARRAY", `字段${name}需要给定一个数组`); return escapeValue(`{${v.map(item => itemEncoder(item)).join(",")}}`) + `::${typeName}`; @@ -238,7 +274,7 @@ function _Array(name: string | undefined, typeName: string, itemEncoder: (v: if (!(v instanceof Array)) return null; if (itemDecoder) return v.map(item => itemDecoder(item)); else return v; - }, false); + }, false, virtual); } /** @@ -246,8 +282,8 @@ function _Array(name: string | undefined, typeName: string, itemEncoder: (v: * @param name 字段名称 * @param option 字段选项 */ -export function IntArrayColumn(name: string, option?: { bytes?: 2 | 4 | 8 }): Decorators.PropDecorator -export function IntArrayColumn(option?: { bytes?: 2 | 4 | 8 }): Decorators.PropDecorator +export function IntArrayColumn(name: string, option?: { bytes?: 2 | 4 | 8, virtual?: boolean }): Decorators.PropDecorator +export function IntArrayColumn(option?: { bytes?: 2 | 4 | 8, virtual?: boolean }): Decorators.PropDecorator export function IntArrayColumn(name?: any, option?: any) { if (name && typeof name !== "string") { option = name @@ -262,26 +298,41 @@ export function IntArrayColumn(name?: any, option?: any) { item = parseInt(item); if (isNaN(item) || !isFinite(item)) return null; return item; - }); + }, option?.virtual ?? false); } /** * 定义字符串数组 * @param name 字段名称 + * @param option 字段选项 */ -export function StringArrayColumn(name?: string) { +export function StringArrayColumn(option?: { virtual?: boolean }): Decorators.PropDecorator +export function StringArrayColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator +export function StringArrayColumn(name?: any, option?: any) { + if (typeof name != "string") { + option = name + name = undefined + } + return _Array(name, "varchar[]", item => { if (typeof item == "string") return item; else return null; - }, item => item); + }, item => item, option?.virtual ?? false); } /** * 定义jsonb字段 * @param name 字段名称 */ -export function JsonColumn(name?: string) { - return Field(name, v => escapeValue(JSON.stringify(v)) + "::jsonb", v => v, false); +export function JsonColumn(option?: { virtual?: boolean }): Decorators.PropDecorator +export function JsonColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator +export function JsonColumn(name?: any, option?: any) { + if (typeof name != "string") { + option = name + name = undefined + } + + return Field(name, v => escapeValue(JSON.stringify(v)) + "::jsonb", v => v, false, option?.virtual ?? false); } /** @@ -292,7 +343,7 @@ export function TSVectorColumn(name?: string) { return Field(name, v => { if (!(v instanceof TSVector)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_TSVECTOR", `字段${name}需要给定一个TSVector`) return `E'${v.value}'::tsvector`; - }, v => TSVector.fromValue(v), false); + }, v => TSVector.fromValue(v), false, false); } //获取实体配置 diff --git a/src/index.ts b/src/index.ts index 8783c28..5c51879 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,16 @@ export { TSVectorColumn, JoinOne, JoinMany, BasicEntity, + IEntityConfig, } from "./entity"; +export { + IInsertBuilder, + IUpdateBuilder, + IDeleteBuilder, + ISelectBuilder, +} from "./query"; + export * from "./type"; export * from "./database"; export * from "./error"; diff --git a/src/query.ts b/src/query.ts index 1648b23..12f9ee4 100644 --- a/src/query.ts +++ b/src/query.ts @@ -23,6 +23,7 @@ interface IQueryFunc { (sql: string, args?: any[]): Promise> } +type DataOrCustom = T | (() => string); type StringArgs = Str extends `${string}${P}${infer R}${S}${infer T}` ? (R | StringArgs) : never; @@ -189,7 +190,7 @@ export interface IInsertBuilder extends IReturningFn { * 设置要插入的数据 * @param data 要插入的数据 */ - data>(data: Pick | Pick[]): this; + data>(data: { [P in Name]: DataOrCustom } | Array<{ [P in Name]: DataOrCustom }>): this; /** 执行插入 */ query(): Promise @@ -200,7 +201,7 @@ export interface IUpdateBuilder extends IWhereFn, IRet * 设置更新数据 * @param data 要更新的数据 */ - set(data: Partial>>): this + set(data: { [P in BasicFieldName]?: DataOrCustom }): this /** 执行更新 */ query(): Promise @@ -435,7 +436,7 @@ function buildCondition(builder: SQLBuilder, option: C wheres.push(`${fieldName}<${field.encode(opv)}`); break; case "lte": - wheres.push(`${fieldName}<=${field.encode(topVal)}`); + wheres.push(`${fieldName}<=${field.encode(opv)}`); break; case "like": wheres.push(`${fieldName} like ${escapeValue(opv)}`); @@ -754,7 +755,7 @@ export class InsertBuilder extends WithReturning(SQLBuild this.#query = query; } - public data>(data: Pick | Pick[]) { + public data>(data: { [P in Name]: DataOrCustom } | Array<{ [P in Name]: DataOrCustom }>) { data = (data instanceof Array) ? data : [data]; this.#values = []; this.#columns = []; @@ -763,9 +764,11 @@ export class InsertBuilder extends WithReturning(SQLBuild for (const item of data) { const buffer: (string | null)[] = []; for (const col of this.config.fields) { + if (col.virtual) continue; if (!(col.prop in item)) continue; const val = item[col.prop as keyof typeof item]; if (val === null || val === undefined) buffer.push("null"); + else if (typeof val === "function") buffer.push(val()); else buffer.push(col.encode(val)); if (!columnsSet) this.#columns.push(escapeID(col.name)); } @@ -801,12 +804,14 @@ export class UpdateBuilder extends WithReturning(WithWher this.#query = query; } - public set(data: Partial>>) { + public set(data: { [P in BasicFieldName]?: DataOrCustom }) { this.#updates = [] for (const col of this.config.fields) { + if (col.virtual) continue; const val = data[col.prop as keyof typeof data]; if (val === undefined) continue; - if (val === null) this.#updates.push(`${escapeID(col.name)}=null`); + else if (val === null) this.#updates.push(`${escapeID(col.name)}=null`); + else if (typeof val === "function") this.#updates.push(`${escapeID(col.name)}=${val()}`); else this.#updates.push(`${escapeID(col.name)}=${col.encode(val)}`); } return this;