orchid-orm 1.17.14 → 1.17.16

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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { columnTypes, QueryHooks, getColumnTypes, getQueryAs, setQueryObjectValue, pushQueryOn, VirtualColumn, pushQueryValue, isQueryReturnsAll, toSQLCacheKey, OrchidOrmInternalError, NotFoundError, Adapter, Db, anyShape, getClonedQueryData } from 'pqb';
1
+ import { columnTypes, QueryHooks, getColumnTypes, getQueryAs, setQueryObjectValue, pushQueryOn, VirtualColumn, pushQueryValue, isQueryReturnsAll, toSQLCacheKey, OrchidOrmInternalError, NotFoundError, Adapter, Db, anyShape, addComputedColumns, getClonedQueryData } from 'pqb';
2
2
  export { OrchidOrmError, OrchidOrmInternalError, columnTypes, raw, testTransaction } from 'pqb';
3
3
  import { getStackTrace, applyMixins, getCallerFilePath, snakeCaseKey, toSnakeCase, emptyArray, toArray } from 'orchid-core';
4
4
  import { AsyncLocalStorage } from 'node:async_hooks';
@@ -24,7 +24,7 @@ const createBaseTable = ({
24
24
  const base = (_a = class {
25
25
  constructor() {
26
26
  this.snakeCase = snakeCase;
27
- this.columnTypes = columnTypes$1;
27
+ this.types = columnTypes$1;
28
28
  this.q = {};
29
29
  this.language = language;
30
30
  }
@@ -72,6 +72,99 @@ const createBaseTable = ({
72
72
  }
73
73
  return this.constructor.prototype.columns = shape;
74
74
  }
75
+ /**
76
+ * You can add a generated column in the migration (see [generated](/guide/migration-column-methods.html#generated-column)),
77
+ * such column will persist in the database, it can be indexed.
78
+ *
79
+ * Or you can add a computed column on the ORM level, without adding it to the database, in such a way:
80
+ *
81
+ * ```ts
82
+ * import { BaseTable } from './baseTable';
83
+ *
84
+ * export class UserTable extends BaseTable {
85
+ * readonly table = 'user';
86
+ * columns = this.setColumns((t) => ({
87
+ * id: t.identity().primaryKey(),
88
+ * firstName: t.string(),
89
+ * lastName: t.string(),
90
+ * }));
91
+ *
92
+ * computed = this.setComputed({
93
+ * fullName: (q) =>
94
+ * q.sql`${q.column('firstName')} || ' ' || ${q.column('lastName')}`.type(
95
+ * (t) => t.string(),
96
+ * ),
97
+ * });
98
+ * }
99
+ * ```
100
+ *
101
+ * `setComputed` takes an object where keys are computed column names, and values are functions returning raw SQL.
102
+ *
103
+ * 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.
104
+ *
105
+ * Computed columns are not selected by default, only on demand:
106
+ *
107
+ * ```ts
108
+ * const a = await db.user.take();
109
+ * a.fullName; // not selected
110
+ *
111
+ * const b = await db.user.select('*', 'fullName');
112
+ * b.fullName; // selected
113
+ *
114
+ * // Table post belongs to user as an author.
115
+ * // it's possible to select joined computed column:
116
+ * const posts = await db.post
117
+ * .join('author')
118
+ * .select('post.title', 'author.fullName');
119
+ * ```
120
+ *
121
+ * SQL query can be generated dynamically based on the current request context.
122
+ *
123
+ * Imagine we are using [AsyncLocalStorage](https://nodejs.org/api/async_context.html#asynchronous-context-tracking)
124
+ * to keep track of current user's language.
125
+ *
126
+ * And we have articles translated to different languages, each article has `title_en`, `title_uk`, `title_be` and so on.
127
+ *
128
+ * We can define a computed `title` by passing a function into `sql` method:
129
+ *
130
+ * ```ts
131
+ * type Locale = 'en' | 'uk' | 'be';
132
+ * const asyncLanguageStorage = new AsyncLocalStorage<Locale>();
133
+ * const defaultLocale: Locale = 'en';
134
+ *
135
+ * export class ArticleTable extends BaseTable {
136
+ * readonly table = 'article';
137
+ * columns = this.setColumns((t) => ({
138
+ * id: t.identity().primaryKey(),
139
+ * title_en: t.text(),
140
+ * title_uk: t.text().nullable(),
141
+ * title_be: t.text().nullable(),
142
+ * }));
143
+ *
144
+ * computed = this.setComputed({
145
+ * title: (q) =>
146
+ * q
147
+ * // .sql can take a function that accepts `sql` argument and must return SQL
148
+ * .sql((sql) => {
149
+ * // get locale dynamically based on current storage value
150
+ * const locale = asyncLanguageStorage.getStore() || defaultLocale;
151
+ *
152
+ * // use COALESCE in case when localized title is NULL, use title_en
153
+ * return sql`COALESCE(
154
+ * ${q.column(`title_${locale}`)},
155
+ * ${q.column(`title_${defaultLocale}`)}
156
+ * )`;
157
+ * })
158
+ * .type((t) => t.text()),
159
+ * });
160
+ * }
161
+ * ```
162
+ *
163
+ * @param computed - object where keys are column names and values are functions returning raw SQL
164
+ */
165
+ setComputed(computed) {
166
+ return computed;
167
+ }
75
168
  belongsTo(fn, options) {
76
169
  return {
77
170
  type: "belongsTo",
@@ -102,7 +195,7 @@ const createBaseTable = ({
102
195
  }
103
196
  }, _a.nowSQL = nowSQL, _a.exportAs = exportAs, _a.schema = schemaProvider, _a);
104
197
  applyMixins(base, [QueryHooks]);
105
- base.prototype.columnTypes = columnTypes$1;
198
+ base.prototype.types = columnTypes$1;
106
199
  return base;
107
200
  };
108
201
 
@@ -1704,7 +1797,7 @@ const orchidORM = (_a, tables) => {
1704
1797
  qb,
1705
1798
  table.table,
1706
1799
  table.columns,
1707
- table.columnTypes,
1800
+ table.types,
1708
1801
  transactionStorage,
1709
1802
  options2
1710
1803
  );
@@ -1712,6 +1805,12 @@ const orchidORM = (_a, tables) => {
1712
1805
  dbTable.db = result;
1713
1806
  dbTable.filePath = table.filePath;
1714
1807
  dbTable.name = table.constructor.name;
1808
+ if (table.computed) {
1809
+ addComputedColumns(
1810
+ dbTable,
1811
+ table.computed
1812
+ );
1813
+ }
1715
1814
  result[key] = dbTable;
1716
1815
  }
1717
1816
  applyRelations(qb, tableInstances, result);