orchid-orm 1.17.14 → 1.17.15

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as pqb from 'pqb';
2
- import { Query, QueryWithTable, SetQueryTableAlias, WhereArg, UpdateData, CreateData, Db, IsolationLevel, TransactionOptions, Adapter, FromArgs, FromResult, AdapterOptions, QueryLogOptions, NoPrimaryKeyOption, RelationConfigBase, RelationQuery, SetQueryReturnsOne, SetQueryReturnsOneOptional, SetQueryReturnsAll, RelationQueryBase, ColumnsShape, DefaultColumnTypes, QueryData, QueryBase, QueryBeforeHook, QueryAfterHook, AfterHook, WhereResult, MergeQuery, SetQueryReturns, QueryReturnType } from 'pqb';
2
+ import { Query, QueryWithTable, SetQueryTableAlias, WhereArg, UpdateData, CreateData, Db, IsolationLevel, TransactionOptions, Adapter, FromArgs, FromResult, AdapterOptions, QueryLogOptions, NoPrimaryKeyOption, RelationConfigBase, RelationQuery, SetQueryReturnsOne, SetQueryReturnsOneOptional, SetQueryReturnsAll, RelationQueryBase, ComputedColumnsBase, QueryDefaultReturnData, ColumnsShape, DefaultColumnTypes, QueryData, QueryBase, QueryBeforeHook, QueryAfterHook, AfterHook, WhereResult, MergeQuery, SetQueryReturns, QueryReturnType } from 'pqb';
3
3
  export { OrchidOrmError, OrchidOrmInternalError, columnTypes, raw, testTransaction } from 'pqb';
4
4
  import * as orchid_core from 'orchid-core';
5
5
  import { EmptyObject, MaybeArray, StringKey, ColumnTypesBase, ColumnShapeQueryType, ColumnShapeOutput, ColumnShapeInput, ColumnsShapeBase } from 'orchid-core';
@@ -349,7 +349,9 @@ type MapRelations<T extends Table> = T extends {
349
349
 
350
350
  type TableClass<T extends Table = Table> = new () => T;
351
351
  type TableClasses = Record<string, TableClass>;
352
- type TableToDb<T extends Table, RelationQueries extends Record<string, RelationQueryBase>> = Db<T['table'], T['columns'], RelationQueries, T['columnTypes']> & {
352
+ type TableToDb<T extends Table, RelationQueries extends Record<string, RelationQueryBase>> = Db<T['table'], T['computed'] extends ComputedColumnsBase<never> ? T['columns'] & {
353
+ [K in keyof T['computed']]: ReturnType<T['computed'][K]>['_type'];
354
+ } : T['columns'], RelationQueries, T['types'], QueryDefaultReturnData<T['columns']>> & {
353
355
  definedAs: string;
354
356
  db: OrchidORM;
355
357
  getFilePath(): string;
@@ -361,10 +363,14 @@ type Table = {
361
363
  table: string;
362
364
  columns: ColumnsShape;
363
365
  schema?: string;
364
- columnTypes: ColumnTypesBase;
366
+ types: ColumnTypesBase;
365
367
  noPrimaryKey?: boolean;
366
368
  filePath: string;
367
369
  language?: string;
370
+ /**
371
+ * collect computed columns returned by {@link BaseTable.setColumns}
372
+ */
373
+ computed?: ComputedColumnsBase<never>;
368
374
  };
369
375
  type Queryable<T extends Table> = Partial<ColumnShapeQueryType<T['columns']>>;
370
376
  type Selectable<T extends Table> = ColumnShapeOutput<T['columns']>;
@@ -388,7 +394,7 @@ declare const createBaseTable: <ColumnTypes extends Record<string, orchid_core.A
388
394
  schema?: string | undefined;
389
395
  noPrimaryKey?: boolean | undefined;
390
396
  snakeCase: boolean | undefined;
391
- columnTypes: Record<string, orchid_core.AnyColumnTypeCreator> extends ColumnTypes ? {
397
+ types: Record<string, orchid_core.AnyColumnTypeCreator> extends ColumnTypes ? {
392
398
  timestamps<T extends orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>>(this: {
393
399
  name(name: string): {
394
400
  timestamp(): T;
@@ -662,6 +668,230 @@ declare const createBaseTable: <ColumnTypes extends Record<string, orchid_core.A
662
668
  }) | undefined): {};
663
669
  check(check: orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>): {};
664
670
  } : ColumnTypes) => T_15): T_15;
671
+ /**
672
+ * You can add a generated column in the migration (see [generated](/guide/migration-column-methods.html#generated-column)),
673
+ * such column will persist in the database, it can be indexed.
674
+ *
675
+ * Or you can add a computed column on the ORM level, without adding it to the database, in such a way:
676
+ *
677
+ * ```ts
678
+ * import { BaseTable } from './baseTable';
679
+ *
680
+ * export class UserTable extends BaseTable {
681
+ * readonly table = 'user';
682
+ * columns = this.setColumns((t) => ({
683
+ * id: t.identity().primaryKey(),
684
+ * firstName: t.string(),
685
+ * lastName: t.string(),
686
+ * }));
687
+ *
688
+ * computed = this.setComputed({
689
+ * fullName: (q) =>
690
+ * q.sql`${q.column('firstName')} || ' ' || ${q.column('lastName')}`.type(
691
+ * (t) => t.string(),
692
+ * ),
693
+ * });
694
+ * }
695
+ * ```
696
+ *
697
+ * `setComputed` takes an object where keys are computed column names, and values are functions returning raw SQL.
698
+ *
699
+ * Use `q.column` as shown above to reference a table column, it will be prefixed with a correct table name even if the table is joined under a different name.
700
+ *
701
+ * Computed columns are not selected by default, only on demand:
702
+ *
703
+ * ```ts
704
+ * const a = await db.user.take();
705
+ * a.fullName; // not selected
706
+ *
707
+ * const b = await db.user.select('*', 'fullName');
708
+ * b.fullName; // selected
709
+ *
710
+ * // Table post belongs to user as an author.
711
+ * // it's possible to select joined computed column:
712
+ * const posts = await db.post
713
+ * .join('author')
714
+ * .select('post.title', 'author.fullName');
715
+ * ```
716
+ *
717
+ * SQL query can be generated dynamically based on the current request context.
718
+ *
719
+ * Imagine we are using [AsyncLocalStorage](https://nodejs.org/api/async_context.html#asynchronous-context-tracking)
720
+ * to keep track of current user's language.
721
+ *
722
+ * And we have articles translated to different languages, each article has `title_en`, `title_uk`, `title_be` and so on.
723
+ *
724
+ * We can define a computed `title` by passing a function into `sql` method:
725
+ *
726
+ * ```ts
727
+ * type Locale = 'en' | 'uk' | 'be';
728
+ * const asyncLanguageStorage = new AsyncLocalStorage<Locale>();
729
+ * const defaultLocale: Locale = 'en';
730
+ *
731
+ * export class ArticleTable extends BaseTable {
732
+ * readonly table = 'article';
733
+ * columns = this.setColumns((t) => ({
734
+ * id: t.identity().primaryKey(),
735
+ * title_en: t.text(),
736
+ * title_uk: t.text().nullable(),
737
+ * title_be: t.text().nullable(),
738
+ * }));
739
+ *
740
+ * computed = this.setComputed({
741
+ * title: (q) =>
742
+ * q
743
+ * // .sql can take a function that accepts `sql` argument and must return SQL
744
+ * .sql((sql) => {
745
+ * // get locale dynamically based on current storage value
746
+ * const locale = asyncLanguageStorage.getStore() || defaultLocale;
747
+ *
748
+ * // use COALESCE in case when localized title is NULL, use title_en
749
+ * return sql`COALESCE(
750
+ * ${q.column(`title_${locale}`)},
751
+ * ${q.column(`title_${defaultLocale}`)}
752
+ * )`;
753
+ * })
754
+ * .type((t) => t.text()),
755
+ * });
756
+ * }
757
+ * ```
758
+ *
759
+ * @param computed - object where keys are column names and values are functions returning raw SQL
760
+ */
761
+ setComputed<Table_3 extends string, Shape_1 extends ColumnsShape, Computed extends ComputedColumnsBase<Db<Table_3, Shape_1, {}, Record<string, orchid_core.AnyColumnTypeCreator> extends ColumnTypes ? {
762
+ timestamps<T extends orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>>(this: {
763
+ name(name: string): {
764
+ timestamp(): T;
765
+ };
766
+ timestamp(): T;
767
+ timestampsSnakeCase(): {
768
+ createdAt: orchid_core.ColumnWithDefault<T, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
769
+ updatedAt: orchid_core.ColumnWithDefault<T, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
770
+ };
771
+ }): {
772
+ createdAt: orchid_core.ColumnWithDefault<T, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
773
+ updatedAt: orchid_core.ColumnWithDefault<T, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
774
+ };
775
+ timestampsSnakeCase<T_1 extends orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>>(this: {
776
+ name(name: string): {
777
+ timestamp(): T_1;
778
+ };
779
+ timestamp(): T_1;
780
+ }): {
781
+ createdAt: orchid_core.ColumnWithDefault<T_1, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
782
+ updatedAt: orchid_core.ColumnWithDefault<T_1, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
783
+ };
784
+ timestampsNoTZ<T_2 extends orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>>(this: {
785
+ name(name: string): {
786
+ timestampNoTZ(): T_2;
787
+ };
788
+ timestampNoTZ(): T_2;
789
+ timestampsNoTZSnakeCase(): {
790
+ createdAt: orchid_core.ColumnWithDefault<T_2, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
791
+ updatedAt: orchid_core.ColumnWithDefault<T_2, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
792
+ };
793
+ }): {
794
+ createdAt: orchid_core.ColumnWithDefault<T_2, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
795
+ updatedAt: orchid_core.ColumnWithDefault<T_2, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
796
+ };
797
+ timestampsNoTZSnakeCase<T_3 extends orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>>(this: {
798
+ name(name: string): {
799
+ timestampNoTZ(): T_3;
800
+ };
801
+ timestampNoTZ(): T_3;
802
+ }): {
803
+ createdAt: orchid_core.ColumnWithDefault<T_3, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
804
+ updatedAt: orchid_core.ColumnWithDefault<T_3, orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>>;
805
+ };
806
+ name: typeof orchid_core.name;
807
+ sql: {
808
+ (sql: TemplateStringsArray, ...values: unknown[]): orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>;
809
+ (sql: string): orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>;
810
+ (values: Record<string, unknown>, sql: string): orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>;
811
+ (values: Record<string, unknown>): (strings: TemplateStringsArray, ...values: unknown[]) => orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>;
812
+ };
813
+ smallint(): pqb.SmallIntColumn;
814
+ integer(): pqb.IntegerColumn;
815
+ bigint(): pqb.BigIntColumn;
816
+ numeric<Precision extends number | undefined = undefined, Scale extends number | undefined = undefined>(precision?: Precision | undefined, scale?: Scale | undefined): pqb.DecimalColumn<Precision, Scale>;
817
+ decimal<Precision_1 extends number | undefined = undefined, Scale_1 extends number | undefined = undefined>(precision?: Precision_1 | undefined, scale?: Scale_1 | undefined): pqb.DecimalColumn<Precision_1, Scale_1>;
818
+ real(): pqb.RealColumn;
819
+ doublePrecision(): pqb.DoublePrecisionColumn;
820
+ identity(options?: pqb.TableData.Identity | undefined): orchid_core.ColumnWithDefault<pqb.IntegerColumn, orchid_core.Expression<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>>>;
821
+ smallSerial(): pqb.SmallSerialColumn;
822
+ serial(): pqb.SerialColumn;
823
+ bigSerial(): pqb.BigSerialColumn;
824
+ money(): pqb.MoneyColumn;
825
+ varchar<Limit extends number | undefined = undefined>(limit?: Limit | undefined): pqb.VarCharColumn<Limit>;
826
+ char<Limit_1 extends number | undefined = undefined>(limit?: Limit_1 | undefined): pqb.CharColumn<Limit_1>;
827
+ text(min: number, max: number): pqb.TextColumn;
828
+ string<Limit_2 extends number = 255>(limit?: Limit_2): pqb.VarCharColumn<Limit_2>;
829
+ citext(min: number, max: number): pqb.CitextColumn;
830
+ bytea(): pqb.ByteaColumn;
831
+ date(): pqb.DateColumn;
832
+ timestampNoTZ<Precision_2 extends number>(precision?: Precision_2 | undefined): pqb.TimestampColumn<Precision_2>;
833
+ timestamp<Precision_3 extends number | undefined = undefined>(precision?: Precision_3 | undefined): pqb.TimestampTZColumn<number>;
834
+ time<Precision_4 extends number | undefined = undefined>(precision?: Precision_4 | undefined): pqb.TimeColumn<Precision_4>;
835
+ interval<Fields extends string | undefined = undefined, Precision_5 extends number | undefined = undefined>(fields?: Fields | undefined, precision?: Precision_5 | undefined): pqb.IntervalColumn<Fields, Precision_5>;
836
+ boolean(): pqb.BooleanColumn;
837
+ enum<U extends string, T_4 extends [U, ...U[]]>(dataType: string, type: T_4): pqb.EnumColumn<U, T_4>;
838
+ point(): pqb.PointColumn;
839
+ line(): pqb.LineColumn;
840
+ lseg(): pqb.LsegColumn;
841
+ box(): pqb.BoxColumn;
842
+ path(): pqb.PathColumn;
843
+ polygon(): pqb.PolygonColumn;
844
+ circle(): pqb.CircleColumn;
845
+ cidr(): pqb.CidrColumn;
846
+ inet(): pqb.InetColumn;
847
+ macaddr(): pqb.MacAddrColumn;
848
+ macaddr8(): pqb.MacAddr8Column;
849
+ bit<Length extends number>(length: Length): pqb.BitColumn<Length>;
850
+ bitVarying<Length_1 extends number | undefined = undefined>(length?: Length_1 | undefined): pqb.BitVaryingColumn<Length_1>;
851
+ tsvector(): pqb.TsVectorColumn;
852
+ tsquery(): pqb.TsQueryColumn;
853
+ uuid(): pqb.UUIDColumn;
854
+ xml(): pqb.XMLColumn;
855
+ json<Type extends orchid_core.JSONType<unknown, {}> = orchid_core.JSONUnknown>(schemaOrFn?: Type | ((j: {
856
+ unknown: () => orchid_core.JSONUnknown;
857
+ boolean: () => orchid_core.JSONBoolean;
858
+ null: () => orchid_core.JSONNull;
859
+ number: <T_5 extends number = number>() => orchid_core.JSONNumber<T_5>;
860
+ string: <T_6 extends string = string>() => orchid_core.JSONString<T_6>;
861
+ array: <T_7 extends orchid_core.JSONType<unknown, {}>>(item: T_7) => orchid_core.JSONArray<T_7, "many">;
862
+ object: <Shape extends orchid_core.JSONObjectShape>(shape: Shape) => orchid_core.JSONObject<Shape, "strip", orchid_core.JSONType<unknown, {}>>;
863
+ literal: <T_8 extends orchid_core.JSONPrimitive>(value: T_8) => orchid_core.JSONLiteral<T_8>;
864
+ discriminatedUnion: <Discriminator extends string, Types extends orchid_core.JSONDiscriminatedUnionArg<Discriminator>>(discriminator: Discriminator, types: Types) => orchid_core.JSONDiscriminatedUnion<Discriminator, Types>;
865
+ enum: <U_1 extends string, T_9 extends [U_1, ...U_1[]]>(options: T_9) => orchid_core.JSONEnum<string, T_9>;
866
+ intersection: <Left extends orchid_core.JSONType<unknown, {}>, Right extends orchid_core.JSONType<unknown, {}>>(left: Left, right: Right) => orchid_core.JSONIntersection<Left, Right>;
867
+ lazy: <T_10 extends orchid_core.JSONType<unknown, {}>>(fn: () => T_10) => orchid_core.JSONLazy<T_10>;
868
+ nativeEnum: <T_11 extends orchid_core.EnumLike>(type: T_11) => orchid_core.JSONNativeEnum<T_11>;
869
+ record: <Key extends orchid_core.JSONString<string> | orchid_core.JSONNumber<number>, Value extends orchid_core.JSONType<unknown, {}>>(...args: [value: Value] | [key: Key, value: Value]) => orchid_core.JSONRecord<Key, Value>;
870
+ tuple: <T_12 extends orchid_core.JSONTupleItems, Rest extends orchid_core.JSONType<unknown, {}> | undefined = undefined>(items: T_12, rest?: Rest | undefined) => orchid_core.JSONTuple<T_12, Rest>;
871
+ union: <T_13 extends orchid_core.JSONUnionArgs>(...types: T_13) => orchid_core.JSONUnion<T_13>;
872
+ }) => Type) | undefined): pqb.JSONColumn<Type>;
873
+ jsonText(): pqb.JSONTextColumn;
874
+ array<Item extends pqb.ColumnType<unknown, pqb.BaseOperators, unknown, unknown, unknown>>(item: Item): pqb.ArrayColumn<Item>;
875
+ type(dataType: string): pqb.CustomTypeColumn;
876
+ domain(dataType: string): pqb.DomainColumn;
877
+ primaryKey(columns: string[], options?: {
878
+ name?: string | undefined;
879
+ } | undefined): {};
880
+ index(columns: orchid_core.MaybeArray<string | pqb.IndexColumnOptions>, options?: pqb.IndexOptions): {};
881
+ unique(columns: orchid_core.MaybeArray<string | pqb.IndexColumnOptions>, options?: pqb.IndexOptions | undefined): {};
882
+ searchIndex(columns: orchid_core.MaybeArray<string | pqb.IndexColumnOptions>, options?: pqb.IndexOptions | undefined): {};
883
+ constraint<Table_1 extends string | (() => orchid_core.ForeignKeyTable), Columns extends Table_1 extends () => orchid_core.ForeignKeyTable ? [orchid_core.ColumnNameOfTable<ReturnType<Table_1>>, ...orchid_core.ColumnNameOfTable<ReturnType<Table_1>>[]] : [string, ...string[]]>({ name, references, check, dropMode, }: {
884
+ name?: string | undefined;
885
+ references?: [columns: string[], fnOrTable: Table_1, foreignColumns: Columns, options?: pqb.ForeignKeyOptions | undefined] | undefined;
886
+ check?: orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}> | undefined;
887
+ dropMode?: pqb.DropMode | undefined;
888
+ }): {};
889
+ foreignKey<Table_2 extends string | (() => orchid_core.ForeignKeyTable), Columns_1 extends Table_2 extends () => orchid_core.ForeignKeyTable ? [orchid_core.ColumnNameOfTable<ReturnType<Table_2>>, ...orchid_core.ColumnNameOfTable<ReturnType<Table_2>>[]] : [string, ...string[]]>(columns: string[], fnOrTable: Table_2, foreignColumns: Columns_1, options?: (pqb.ForeignKeyOptions & {
890
+ name?: string | undefined;
891
+ dropMode?: pqb.DropMode | undefined;
892
+ }) | undefined): {};
893
+ check(check: orchid_core.RawSQLBase<orchid_core.ColumnTypeBase<unknown, orchid_core.BaseOperators, unknown, unknown, unknown, orchid_core.ColumnDataBase>, {}>): {};
894
+ } : ColumnTypes, QueryDefaultReturnData<Shape_1>>>>(computed: Computed): Computed;
665
895
  belongsTo<Self extends any, Related extends TableClass<Table>, Scope extends Query, Options extends BelongsToOptions<Self, Related, Scope>>(this: Self, fn: () => Related, options: Options): {
666
896
  type: "belongsTo";
667
897
  fn: () => Related;
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ const createBaseTable = ({
25
25
  const base = (_a = class {
26
26
  constructor() {
27
27
  this.snakeCase = snakeCase;
28
- this.columnTypes = columnTypes;
28
+ this.types = columnTypes;
29
29
  this.q = {};
30
30
  this.language = language;
31
31
  }
@@ -73,6 +73,99 @@ const createBaseTable = ({
73
73
  }
74
74
  return this.constructor.prototype.columns = shape;
75
75
  }
76
+ /**
77
+ * You can add a generated column in the migration (see [generated](/guide/migration-column-methods.html#generated-column)),
78
+ * such column will persist in the database, it can be indexed.
79
+ *
80
+ * Or you can add a computed column on the ORM level, without adding it to the database, in such a way:
81
+ *
82
+ * ```ts
83
+ * import { BaseTable } from './baseTable';
84
+ *
85
+ * export class UserTable extends BaseTable {
86
+ * readonly table = 'user';
87
+ * columns = this.setColumns((t) => ({
88
+ * id: t.identity().primaryKey(),
89
+ * firstName: t.string(),
90
+ * lastName: t.string(),
91
+ * }));
92
+ *
93
+ * computed = this.setComputed({
94
+ * fullName: (q) =>
95
+ * q.sql`${q.column('firstName')} || ' ' || ${q.column('lastName')}`.type(
96
+ * (t) => t.string(),
97
+ * ),
98
+ * });
99
+ * }
100
+ * ```
101
+ *
102
+ * `setComputed` takes an object where keys are computed column names, and values are functions returning raw SQL.
103
+ *
104
+ * Use `q.column` as shown above to reference a table column, it will be prefixed with a correct table name even if the table is joined under a different name.
105
+ *
106
+ * Computed columns are not selected by default, only on demand:
107
+ *
108
+ * ```ts
109
+ * const a = await db.user.take();
110
+ * a.fullName; // not selected
111
+ *
112
+ * const b = await db.user.select('*', 'fullName');
113
+ * b.fullName; // selected
114
+ *
115
+ * // Table post belongs to user as an author.
116
+ * // it's possible to select joined computed column:
117
+ * const posts = await db.post
118
+ * .join('author')
119
+ * .select('post.title', 'author.fullName');
120
+ * ```
121
+ *
122
+ * SQL query can be generated dynamically based on the current request context.
123
+ *
124
+ * Imagine we are using [AsyncLocalStorage](https://nodejs.org/api/async_context.html#asynchronous-context-tracking)
125
+ * to keep track of current user's language.
126
+ *
127
+ * And we have articles translated to different languages, each article has `title_en`, `title_uk`, `title_be` and so on.
128
+ *
129
+ * We can define a computed `title` by passing a function into `sql` method:
130
+ *
131
+ * ```ts
132
+ * type Locale = 'en' | 'uk' | 'be';
133
+ * const asyncLanguageStorage = new AsyncLocalStorage<Locale>();
134
+ * const defaultLocale: Locale = 'en';
135
+ *
136
+ * export class ArticleTable extends BaseTable {
137
+ * readonly table = 'article';
138
+ * columns = this.setColumns((t) => ({
139
+ * id: t.identity().primaryKey(),
140
+ * title_en: t.text(),
141
+ * title_uk: t.text().nullable(),
142
+ * title_be: t.text().nullable(),
143
+ * }));
144
+ *
145
+ * computed = this.setComputed({
146
+ * title: (q) =>
147
+ * q
148
+ * // .sql can take a function that accepts `sql` argument and must return SQL
149
+ * .sql((sql) => {
150
+ * // get locale dynamically based on current storage value
151
+ * const locale = asyncLanguageStorage.getStore() || defaultLocale;
152
+ *
153
+ * // use COALESCE in case when localized title is NULL, use title_en
154
+ * return sql`COALESCE(
155
+ * ${q.column(`title_${locale}`)},
156
+ * ${q.column(`title_${defaultLocale}`)}
157
+ * )`;
158
+ * })
159
+ * .type((t) => t.text()),
160
+ * });
161
+ * }
162
+ * ```
163
+ *
164
+ * @param computed - object where keys are column names and values are functions returning raw SQL
165
+ */
166
+ setComputed(computed) {
167
+ return computed;
168
+ }
76
169
  belongsTo(fn, options) {
77
170
  return {
78
171
  type: "belongsTo",
@@ -103,7 +196,7 @@ const createBaseTable = ({
103
196
  }
104
197
  }, _a.nowSQL = nowSQL, _a.exportAs = exportAs, _a.schema = schemaProvider, _a);
105
198
  orchidCore.applyMixins(base, [pqb.QueryHooks]);
106
- base.prototype.columnTypes = columnTypes;
199
+ base.prototype.types = columnTypes;
107
200
  return base;
108
201
  };
109
202
 
@@ -1705,7 +1798,7 @@ const orchidORM = (_a, tables) => {
1705
1798
  qb,
1706
1799
  table.table,
1707
1800
  table.columns,
1708
- table.columnTypes,
1801
+ table.types,
1709
1802
  transactionStorage,
1710
1803
  options2
1711
1804
  );
@@ -1713,6 +1806,12 @@ const orchidORM = (_a, tables) => {
1713
1806
  dbTable.db = result;
1714
1807
  dbTable.filePath = table.filePath;
1715
1808
  dbTable.name = table.constructor.name;
1809
+ if (table.computed) {
1810
+ pqb.addComputedColumns(
1811
+ dbTable,
1812
+ table.computed
1813
+ );
1814
+ }
1716
1815
  result[key] = dbTable;
1717
1816
  }
1718
1817
  applyRelations(qb, tableInstances, result);