@tstdl/base 0.93.86 → 0.93.87

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/orm/index.d.ts CHANGED
@@ -8,6 +8,6 @@ export * from './entity.js';
8
8
  export * from './query/index.js';
9
9
  export * from './repository.types.js';
10
10
  export * from './schemas/index.js';
11
- export * from './sqls.js';
11
+ export * from './sqls/index.js';
12
12
  export * from './types.js';
13
13
  export * from './utils.js';
package/orm/index.js CHANGED
@@ -8,6 +8,6 @@ export * from './entity.js';
8
8
  export * from './query/index.js';
9
9
  export * from './repository.types.js';
10
10
  export * from './schemas/index.js';
11
- export * from './sqls.js';
11
+ export * from './sqls/index.js';
12
12
  export * from './types.js';
13
13
  export * from './utils.js';
@@ -9,7 +9,7 @@ import type { AnyColumn, SQL, SQLWrapper } from 'drizzle-orm';
9
9
  import type { PartialDeep } from 'type-fest';
10
10
  import type { BaseEntity, Entity, EntityMetadata } from './entity.js';
11
11
  import type { FullTextSearchQuery, Query } from './query/index.js';
12
- import type { TsHeadlineOptions } from './sqls.js';
12
+ import type { TsHeadlineOptions } from './sqls/index.js';
13
13
  type WithSql<T> = {
14
14
  [P in keyof T]: T[P] extends Record ? WithSql<T[P]> : (T[P] | SQL);
15
15
  };
@@ -4,7 +4,7 @@ import { NotSupportedError } from '../../errors/not-supported.error.js';
4
4
  import { hasOwnProperty, mapObject, mapObjectKeysToSnakeCase, objectEntries } from '../../utils/object/object.js';
5
5
  import { toSnakeCase } from '../../utils/string/index.js';
6
6
  import { assert, assertDefinedPass, isArray, isDefined, isPrimitive, isRegExp, isString, isUndefined } from '../../utils/type-guards.js';
7
- import { array, isSimilar, isStrictWordSimilar, isWordSimilar, jsonbBuildObject, phraseToTsQuery, plainToTsQuery, setweight, similarity, strictWordSimilarity, toTsQuery, toTsVector, websearchToTsQuery, wordSimilarity } from '../sqls.js';
7
+ import { array, isSimilar, isStrictWordSimilar, isWordSimilar, jsonbBuildObject, phraseToTsQuery, plainToTsQuery, setweight, similarity, strictWordSimilarity, toTsQuery, toTsVector, websearchToTsQuery, wordSimilarity } from '../sqls/index.js';
8
8
  const sqlTrue = sql `true`;
9
9
  /**
10
10
  * Resolves a target to a Drizzle PgColumn or SQLWrapper.
@@ -23,7 +23,7 @@ import { assertDefined, assertDefinedPass, isArray, isBoolean, isDefined, isFunc
23
23
  import { typeExtends } from '../../utils/type/index.js';
24
24
  import { millisecondsPerSecond } from '../../utils/units.js';
25
25
  import { Entity } from '../entity.js';
26
- import { distance, isSimilar, isStrictWordSimilar, isWordSimilar, TRANSACTION_TIMESTAMP, tsHeadline, tsRankCd } from '../sqls.js';
26
+ import { distance, isSimilar, isStrictWordSimilar, isWordSimilar, TRANSACTION_TIMESTAMP, tsHeadline, tsRankCd } from '../sqls/index.js';
27
27
  import { getColumnDefinitions, getColumnDefinitionsMap, getDrizzleTableFromType } from './drizzle/schema-converter.js';
28
28
  import { convertQuery, getTsQuery, getTsVector, resolveTargetColumn } from './query-converter.js';
29
29
  import { ENCRYPTION_SECRET } from './tokens.js';
@@ -0,0 +1,25 @@
1
+ import { type SQL, type SQLWrapper } from 'drizzle-orm';
2
+ export declare class CaseBuilder<TReturn = never> {
3
+ readonly cases: SQL[];
4
+ caseExpression?: SQL | SQLWrapper;
5
+ constructor(expression?: SQL | SQLWrapper);
6
+ /** Adds a WHEN clause. */
7
+ when<TValue>(pattern: SQL | SQLWrapper | string | number | boolean | undefined, result: TValue | SQL | SQLWrapper): CaseBuilder<TReturn | TValue>;
8
+ /**
9
+ * Adds an ELSE clause and finishes the statement.
10
+ * If no WHEN clauses were added, it returns the value directly.
11
+ */
12
+ else<TElse>(value: TElse | SQL | SQLWrapper): SQL<TReturn | TElse>;
13
+ /**
14
+ * Finishes the statement without an ELSE (defaults to NULL).
15
+ * If no WHEN clauses were added, it returns NULL directly.
16
+ */
17
+ end(): SQL<TReturn | null>;
18
+ private finalize;
19
+ }
20
+ /**
21
+ * Creates a "Searched Case" builder.
22
+ * Syntax: CASE WHEN condition THEN result ...
23
+ */
24
+ export declare function caseWhen<TValue>(caseExpression: SQL | SQLWrapper): CaseBuilder<TValue>;
25
+ export declare function caseWhen<TValue>(condition: SQL | SQLWrapper | undefined, value: TValue | SQL | SQLWrapper): CaseBuilder<TValue>;
@@ -0,0 +1,54 @@
1
+ import { isDefined, isUndefined } from '../../utils/type-guards.js';
2
+ import { sql } from 'drizzle-orm';
3
+ export class CaseBuilder {
4
+ cases = [];
5
+ caseExpression;
6
+ constructor(expression) {
7
+ this.caseExpression = expression;
8
+ }
9
+ /** Adds a WHEN clause. */
10
+ when(pattern, result) {
11
+ if (isUndefined(pattern)) {
12
+ this.cases.push(sql `WHEN ${pattern} THEN ${result}`);
13
+ }
14
+ return this;
15
+ }
16
+ /**
17
+ * Adds an ELSE clause and finishes the statement.
18
+ * If no WHEN clauses were added, it returns the value directly.
19
+ */
20
+ else(value) {
21
+ if (this.cases.length == 0) {
22
+ return sql `${value}`;
23
+ }
24
+ return this.finalize(value);
25
+ }
26
+ /**
27
+ * Finishes the statement without an ELSE (defaults to NULL).
28
+ * If no WHEN clauses were added, it returns NULL directly.
29
+ */
30
+ end() {
31
+ if (this.cases.length == 0) {
32
+ return sql `NULL`;
33
+ }
34
+ return this.finalize();
35
+ }
36
+ finalize(elseValue) {
37
+ const chunks = [sql `CASE`];
38
+ if (isDefined(this.caseExpression)) {
39
+ chunks.push(sql `${this.caseExpression}`);
40
+ }
41
+ chunks.push(sql.join(this.cases, sql ` `));
42
+ const endChunk = isDefined(elseValue)
43
+ ? sql `ELSE ${elseValue} END`
44
+ : sql `END`;
45
+ chunks.push(endChunk);
46
+ return sql.join(chunks, sql ` `);
47
+ }
48
+ }
49
+ export function caseWhen(condition, value) {
50
+ if (isDefined(value)) {
51
+ return new CaseBuilder().when(condition, value);
52
+ }
53
+ return new CaseBuilder(condition);
54
+ }
@@ -0,0 +1,2 @@
1
+ export * from './sqls.js';
2
+ export * from './case-when.js';
@@ -0,0 +1,2 @@
1
+ export * from './sqls.js';
2
+ export * from './case-when.js';
@@ -6,18 +6,13 @@
6
6
  */
7
7
  import { Column, type AnyColumn, type SQL, type SQLChunk } from 'drizzle-orm';
8
8
  import type { GetSelectTableSelection, SelectResultField, TableLike } from 'drizzle-orm/query-builders/select.types';
9
- import type { EnumerationObject, EnumerationValue } from '../types/types.js';
10
- import { type PgEnumFromEnumeration } from './enums.js';
11
- import type { TsVectorWeight } from './query/index.js';
12
- import type { Uuid } from './types.js';
13
- /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
14
- export declare const TRANSACTION_TIMESTAMP: SQL<Date>;
15
- /** Drizzle SQL helper for generating a random UUID (v4). Returns a Uuid string. */
16
- export declare const RANDOM_UUID_V4: SQL<Uuid>;
17
- /** Drizzle SQL helper for generating a random UUID (v7). Returns a Uuid string. */
18
- export declare const RANDOM_UUID_V7: SQL<Uuid>;
9
+ import type { EnumerationObject, EnumerationValue } from '../../types/types.js';
10
+ import { type PgEnumFromEnumeration } from '../enums.js';
11
+ import type { TsVectorWeight } from '../query/index.js';
12
+ import type { Uuid } from '../types.js';
19
13
  /** Represents valid units for PostgreSQL interval values. */
20
14
  export type IntervalUnit = 'millennium' | 'millenniums' | 'millennia' | 'century' | 'centuries' | 'decade' | 'decades' | 'year' | 'years' | 'day' | 'days' | 'hour' | 'hours' | 'minute' | 'minutes' | 'second' | 'seconds' | 'millisecond' | 'milliseconds' | 'microsecond' | 'microseconds';
15
+ export type ExclusiveColumnCondition = Column | boolean | SQL;
21
16
  export type TsHeadlineOptions = {
22
17
  /**
23
18
  * The longest headline to output.
@@ -60,14 +55,41 @@ export type TsHeadlineOptions = {
60
55
  */
61
56
  fragmentDelimiter?: string;
62
57
  };
58
+ /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
59
+ export declare const TRANSACTION_TIMESTAMP: SQL<Date>;
60
+ /** Drizzle SQL helper for generating a random UUID (v4). Returns a Uuid string. */
61
+ export declare const RANDOM_UUID_V4: SQL<Uuid>;
62
+ /** Drizzle SQL helper for generating a random UUID (v7). Returns a Uuid string. */
63
+ export declare const RANDOM_UUID_V7: SQL<Uuid>;
64
+ export declare const SQL_TRUE: SQL<boolean>;
65
+ export declare const SQL_FALSE: SQL<boolean>;
63
66
  export declare function enumValue<T extends EnumerationObject>(enumeration: T, dbEnum: PgEnumFromEnumeration<T> | string | null, value: EnumerationValue<T>): SQL<string>;
64
67
  /**
65
- * Generates a SQL condition that ensures exactly one of the specified columns is non-null and optional custom conditions.
66
- * @param enumeration The enumeration object
67
- * @param discriminator The column used to discriminate between different enumeration values
68
- * @param conditionMapping An object mapping enumeration values to columns and conditions
68
+ * Generates a SQL `CASE` expression to enforce strict, mutually exclusive column usage based on a discriminator value.
69
+ *
70
+ * This function is useful for "Table per Hierarchy" inheritance patterns or polymorphic associations where specific columns should only be present when a discriminator holds a specific value.
71
+ * Particularly in conjunction with check constraints, it ensures data integrity by enforcing that only the relevant columns for a given discriminator value are populated.
72
+ *
73
+ * The logic ensures that for a specific enum value:
74
+ * 1. Columns explicitly mapped to that value are enforced as **IS NOT NULL**.
75
+ * 2. Columns mapped to *other* enum values (but not the current one) are enforced as **IS NULL**.
76
+ * 3. Any custom SQL conditions provided are combined via `AND`.
77
+ *
78
+ * @remarks
79
+ * The function collects all columns defined across the entire `conditionMapping`. If a column appears anywhere in the mapping but is not associated with the current discriminator value being evaluated, it is automatically asserted as `NULL`.
80
+ *
81
+ * @param enumeration - The source enumeration object containing the valid discriminator values.
82
+ * @param discriminator - The database column acting as the type discriminator.
83
+ * @param conditionMapping - A configuration object where keys are enum values and values are:
84
+ * - A `Column` (enforced as NOT NULL).
85
+ * - A `SQL` condition (passed through).
86
+ * - A boolean (converted to SQL TRUE/FALSE).
87
+ * - An array containing a mix of the above.
88
+ * - `null` (implies this enum value is invalid or should result in `FALSE`).
89
+ *
90
+ * @returns A SQL object representing the complete `CASE discriminator WHEN ... THEN ... ELSE FALSE` statement.
69
91
  */
70
- export declare function exclusiveReference<T extends EnumerationObject>(enumeration: T, discriminator: Column, conditionMapping: Record<EnumerationValue<T>, Column | [Column, condition: boolean | SQL] | null>): SQL;
92
+ export declare function exclusiveColumn<T extends EnumerationObject>(enumeration: T, discriminator: Column, conditionMapping: Record<EnumerationValue<T>, ExclusiveColumnCondition | [ExclusiveColumnCondition, ...ExclusiveColumnCondition[]] | null>): SQL;
71
93
  export declare function exclusiveNotNull(...columns: Column[]): SQL;
72
94
  /**
73
95
  * Generates a SQL `CASE ... WHEN ... END` statement for dynamic condition mapping based on an enumeration.
@@ -4,17 +4,21 @@
4
4
  * simplifying common SQL operations like generating UUIDs, working with intervals,
5
5
  * and aggregating data.
6
6
  */
7
- import { and, Column, eq, sql, isNotNull as sqlIsNotNull, Table } from 'drizzle-orm';
7
+ import { and, Column, eq, sql, isNotNull as sqlIsNotNull, isNull as sqlIsNull, Table } from 'drizzle-orm';
8
8
  import { match, P } from 'ts-pattern';
9
- import { mapObjectValues, objectEntries, objectValues } from '../utils/object/object.js';
10
- import { assertDefined, isArray, isDefined, isInstanceOf, isNotNull, isNull, isNumber, isString } from '../utils/type-guards.js';
11
- import { getEnumName } from './enums.js';
9
+ import { distinct, toArray } from '../../utils/array/array.js';
10
+ import { objectEntries, objectValues } from '../../utils/object/object.js';
11
+ import { assertDefined, isArray, isBoolean, isDefined, isInstanceOf, isNotNull, isNull, isNumber, isString } from '../../utils/type-guards.js';
12
+ import { getEnumName } from '../enums.js';
13
+ import { caseWhen } from './case-when.js';
12
14
  /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
13
15
  export const TRANSACTION_TIMESTAMP = sql `transaction_timestamp()`;
14
16
  /** Drizzle SQL helper for generating a random UUID (v4). Returns a Uuid string. */
15
17
  export const RANDOM_UUID_V4 = sql `gen_random_uuid()`;
16
18
  /** Drizzle SQL helper for generating a random UUID (v7). Returns a Uuid string. */
17
19
  export const RANDOM_UUID_V7 = sql `uuidv7()`;
20
+ export const SQL_TRUE = sql `TRUE`;
21
+ export const SQL_FALSE = sql `FALSE`;
18
22
  export function enumValue(enumeration, dbEnum, value) {
19
23
  if (isNull(dbEnum)) {
20
24
  const enumName = getEnumName(enumeration);
@@ -25,23 +29,50 @@ export function enumValue(enumeration, dbEnum, value) {
25
29
  return sql `'${sql.raw(String(value))}'::${enumType}`;
26
30
  }
27
31
  /**
28
- * Generates a SQL condition that ensures exactly one of the specified columns is non-null and optional custom conditions.
29
- * @param enumeration The enumeration object
30
- * @param discriminator The column used to discriminate between different enumeration values
31
- * @param conditionMapping An object mapping enumeration values to columns and conditions
32
+ * Generates a SQL `CASE` expression to enforce strict, mutually exclusive column usage based on a discriminator value.
33
+ *
34
+ * This function is useful for "Table per Hierarchy" inheritance patterns or polymorphic associations where specific columns should only be present when a discriminator holds a specific value.
35
+ * Particularly in conjunction with check constraints, it ensures data integrity by enforcing that only the relevant columns for a given discriminator value are populated.
36
+ *
37
+ * The logic ensures that for a specific enum value:
38
+ * 1. Columns explicitly mapped to that value are enforced as **IS NOT NULL**.
39
+ * 2. Columns mapped to *other* enum values (but not the current one) are enforced as **IS NULL**.
40
+ * 3. Any custom SQL conditions provided are combined via `AND`.
41
+ *
42
+ * @remarks
43
+ * The function collects all columns defined across the entire `conditionMapping`. If a column appears anywhere in the mapping but is not associated with the current discriminator value being evaluated, it is automatically asserted as `NULL`.
44
+ *
45
+ * @param enumeration - The source enumeration object containing the valid discriminator values.
46
+ * @param discriminator - The database column acting as the type discriminator.
47
+ * @param conditionMapping - A configuration object where keys are enum values and values are:
48
+ * - A `Column` (enforced as NOT NULL).
49
+ * - A `SQL` condition (passed through).
50
+ * - A boolean (converted to SQL TRUE/FALSE).
51
+ * - An array containing a mix of the above.
52
+ * - `null` (implies this enum value is invalid or should result in `FALSE`).
53
+ *
54
+ * @returns A SQL object representing the complete `CASE discriminator WHEN ... THEN ... ELSE FALSE` statement.
32
55
  */
33
- export function exclusiveReference(enumeration, discriminator, conditionMapping) {
34
- const columns = objectValues(conditionMapping).filter(isNotNull).map((value) => isInstanceOf(value, Column) ? value : value[0]);
35
- const mapping = mapObjectValues(conditionMapping, (value) => {
36
- if (isInstanceOf(value, Column)) {
37
- return value;
38
- }
56
+ export function exclusiveColumn(enumeration, discriminator, conditionMapping) {
57
+ const allColumns = objectValues(conditionMapping)
58
+ .filter(isNotNull)
59
+ .flatMap((value) => toArray(value).filter((value) => isInstanceOf(value, Column)));
60
+ const participatingColumns = distinct(allColumns);
61
+ const mapping = objectEntries(conditionMapping).map(([key, value]) => {
39
62
  if (isNull(value)) {
40
- return sql `FALSE`;
63
+ return [key, SQL_FALSE];
41
64
  }
42
- return value[1];
65
+ const requiredColumns = toArray(value).filter((value) => isInstanceOf(value, Column));
66
+ const nullColumns = participatingColumns.filter((column) => !requiredColumns.includes(column));
67
+ const customConditions = toArray(value).filter((val) => !isInstanceOf(val, Column)).map((condition) => isBoolean(condition) ? (condition ? SQL_TRUE : SQL_FALSE) : condition);
68
+ const condition = and(...requiredColumns.map((col) => sqlIsNotNull(col)), ...nullColumns.map((col) => sqlIsNull(col)), ...customConditions);
69
+ return [key, condition];
43
70
  });
44
- return and(exclusiveNotNull(...columns), enumerationCaseWhen(enumeration, discriminator, mapping));
71
+ const kaseWhen = caseWhen(discriminator);
72
+ for (const [key, condition] of mapping) {
73
+ kaseWhen.when(enumValue(enumeration, null, key), condition);
74
+ }
75
+ return kaseWhen.else(SQL_FALSE);
45
76
  }
46
77
  export function exclusiveNotNull(...columns) {
47
78
  return eq(numNonNulls(...columns), sql.raw('1'));
@@ -72,7 +103,7 @@ export function enumerationCaseWhen(enumeration, discriminator, conditionMapping
72
103
  const whens = [];
73
104
  for (const [key, value] of objectEntries(conditionMapping)) {
74
105
  const condition = match(value)
75
- .with(P.boolean, (bool) => bool ? sql `TRUE` : sql `FALSE`)
106
+ .with(P.boolean, (bool) => bool ? SQL_TRUE : SQL_FALSE)
76
107
  .when((value) => isInstanceOf(value, Column), (col) => defaultColumnCondition(col))
77
108
  .otherwise((rawSql) => rawSql);
78
109
  whens.push(sql ` WHEN ${enumValue(enumeration, null, key)} THEN ${condition}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.86",
3
+ "version": "0.93.87",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"