@tstdl/base 0.93.155 → 0.93.157

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.
@@ -42,7 +42,7 @@ let DocumentWorkflowService = DocumentWorkflowService_1 = class DocumentWorkflow
42
42
  documentService = inject(forwardRef(() => DocumentService));
43
43
  repository = injectRepository(DocumentWorkflow);
44
44
  [afterResolve](_, { cancellationSignal }) {
45
- if (this.isInTransaction) {
45
+ if (this.isFork) {
46
46
  return;
47
47
  }
48
48
  this.#taskQueue.process({ concurrency: 5, cancellationSignal }, async (taskContext) => await this.processWorkflowJob(taskContext));
@@ -101,7 +101,7 @@ let EntityRepository = class EntityRepository extends Transactional {
101
101
  return this.#table;
102
102
  }
103
103
  [afterResolve]() {
104
- if (!this.isInTransaction) {
104
+ if (!this.isFork) {
105
105
  void this.expirationLoop();
106
106
  }
107
107
  }
@@ -11,6 +11,7 @@ export type TransactionInitOptions = TransactionConfig & {
11
11
  };
12
12
  export type TransactionHandler<R> = (transaction: Transaction) => Promise<R>;
13
13
  type TransactionalContext<ContextData = unknown> = {
14
+ isFork: boolean;
14
15
  session: Database | PgTransaction;
15
16
  instances: WeakMap<Type, WeakMap<Database | PgTransaction, any>>;
16
17
  data: ContextData;
@@ -26,6 +27,7 @@ export declare abstract class Transactional<ContextData = unknown> {
26
27
  protected transactionalContextData: ContextData | undefined;
27
28
  readonly session: Database | PgTransaction;
28
29
  readonly isInTransaction: boolean;
30
+ readonly isFork: boolean;
29
31
  constructor();
30
32
  /**
31
33
  * Starts a new database transaction.
@@ -42,6 +42,7 @@ export class Transactional {
42
42
  transactionalContextData = this.#context.data;
43
43
  session = this.#context.session ?? inject(Database);
44
44
  isInTransaction = this.session instanceof DrizzlePgTransaction;
45
+ isFork = (this.#context.isFork ?? false) || this.isInTransaction;
45
46
  constructor() {
46
47
  this.#classConstructor = new.target;
47
48
  }
@@ -77,6 +78,7 @@ export class Transactional {
77
78
  return this;
78
79
  }
79
80
  const context = {
81
+ isFork: true,
80
82
  session,
81
83
  instances: this.#instances,
82
84
  data: this.getTransactionalContextData(),
@@ -119,6 +121,7 @@ export class Transactional {
119
121
  return await this.transaction(handler);
120
122
  }
121
123
  const context = {
124
+ isFork: true,
122
125
  session: existingTransaction.pgTransaction,
123
126
  instances: this.#instances,
124
127
  data: this.getTransactionalContextData(),
@@ -139,6 +142,7 @@ export class Transactional {
139
142
  async transaction(handler, config) {
140
143
  const transaction = await this.startTransaction(config);
141
144
  const context = {
145
+ isFork: true,
142
146
  session: transaction.pgTransaction,
143
147
  instances: this.#instances,
144
148
  data: this.getTransactionalContextData(),
@@ -13,7 +13,6 @@ import type { Uuid } from '../types.js';
13
13
  /** Represents valid units for PostgreSQL interval values. */
14
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
15
  export type ExclusiveColumnCondition = Column | boolean | SQL;
16
- export declare const simpleJsonKeyPattern: RegExp;
17
16
  export type TsHeadlineOptions = {
18
17
  /**
19
18
  * The longest headline to output.
@@ -64,6 +63,7 @@ export declare const RANDOM_UUID_V4: SQL<Uuid>;
64
63
  export declare const RANDOM_UUID_V7: SQL<Uuid>;
65
64
  export declare const SQL_TRUE: SQL<boolean>;
66
65
  export declare const SQL_FALSE: SQL<boolean>;
66
+ export declare const SQL_NULL: SQL<null>;
67
67
  export declare function enumValue<T extends EnumerationObject>(enumeration: T, dbEnum: PgEnumFromEnumeration<T> | string | null, value: EnumerationValue<T>): SQL<string>;
68
68
  /**
69
69
  * Generates a SQL `CASE` expression to enforce strict, mutually exclusive column usage based on a discriminator value.
@@ -114,7 +114,7 @@ export declare function exclusiveNotNull(...columns: Column[]): SQL;
114
114
  * that defines the default condition to apply when a `Column` is provided in `conditionMapping`.
115
115
  * By default, it generates an `IS NOT NULL` check.
116
116
  */
117
- export declare function enumerationCaseWhen<T extends EnumerationObject>(enumeration: T, discriminator: Column, conditionMapping: Record<EnumerationValue<T>, Column | [Column, ...Column[]] | boolean | SQL>, defaultColumnCondition?: (column: [Column, ...Column[]]) => SQL<unknown> | undefined): SQL;
117
+ export declare function enumerationCaseWhen<T extends EnumerationObject>(enumeration: T, discriminator: Column, conditionMapping: Record<EnumerationValue<T>, Column | [Column, ...Column[]] | boolean | SQL>, defaultColumnCondition?: (columns: Column | [Column, ...Column[]]) => SQL<unknown>): SQL;
118
118
  export declare function array<T>(values: readonly (SQL<T> | SQLChunk | T)[]): SQL<T[]>;
119
119
  export declare function autoAlias<T>(column: AnyColumn<{
120
120
  data: T;
package/orm/sqls/sqls.js CHANGED
@@ -8,7 +8,7 @@ import { and, Column, eq, isSQLWrapper, sql, isNotNull as sqlIsNotNull, isNull a
8
8
  import { match, P } from 'ts-pattern';
9
9
  import { distinct, toArray } from '../../utils/array/array.js';
10
10
  import { objectEntries, objectValues } from '../../utils/object/object.js';
11
- import { assertDefined, isArray, isBoolean, isDefined, isInstanceOf, isNotNull, isNull, isNullOrUndefined, isNumber, isObject, isString } from '../../utils/type-guards.js';
11
+ import { assertDefined, isArray, isBoolean, isDefined, isInstanceOf, isLiteralObject, isNotNull, isNull, isNullOrUndefined, isNumber, isString } from '../../utils/type-guards.js';
12
12
  import { getEnumName } from '../enums.js';
13
13
  import { caseWhen } from './case-when.js';
14
14
  const isJsonbSymbol = Symbol('isJsonb');
@@ -25,7 +25,6 @@ function isJsonb(value) {
25
25
  }
26
26
  return false;
27
27
  }
28
- export const simpleJsonKeyPattern = /^[a-zA-Z0-9_-]+$/u;
29
28
  /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
30
29
  export const TRANSACTION_TIMESTAMP = sql `transaction_timestamp()`;
31
30
  /** Drizzle SQL helper for generating a random UUID (v4). Returns a Uuid string. */
@@ -34,6 +33,7 @@ export const RANDOM_UUID_V4 = sql `gen_random_uuid()`;
34
33
  export const RANDOM_UUID_V7 = sql `uuidv7()`;
35
34
  export const SQL_TRUE = sql `TRUE`;
36
35
  export const SQL_FALSE = sql `FALSE`;
36
+ export const SQL_NULL = sql `NULL`;
37
37
  export function enumValue(enumeration, dbEnum, value) {
38
38
  if (isNull(dbEnum)) {
39
39
  const enumName = getEnumName(enumeration);
@@ -69,25 +69,45 @@ export function enumValue(enumeration, dbEnum, value) {
69
69
  * @returns A SQL object representing the complete `CASE discriminator WHEN ... THEN ... ELSE FALSE` statement.
70
70
  */
71
71
  export function exclusiveColumn(enumeration, discriminator, conditionMapping) {
72
+ // 1. Gather all unique columns across the entire mapping
72
73
  const allColumns = objectValues(conditionMapping)
73
74
  .filter(isNotNull)
74
- .flatMap((value) => toArray(value).filter((value) => isInstanceOf(value, Column)));
75
+ .flatMap((value) => toArray(value).filter((item) => isInstanceOf(item, Column)));
75
76
  const participatingColumns = distinct(allColumns);
76
- const mapping = objectEntries(conditionMapping).map(([key, value]) => {
77
+ const kaseWhen = caseWhen(discriminator);
78
+ // 2. Iterate and build conditions safely
79
+ for (const [key, value] of objectEntries(conditionMapping)) {
77
80
  if (isNull(value)) {
78
- return [key, SQL_FALSE];
81
+ kaseWhen.when(enumValue(enumeration, null, key), SQL_FALSE);
82
+ continue;
79
83
  }
80
- const requiredColumns = toArray(value).filter((value) => isInstanceOf(value, Column));
84
+ const conditions = [];
85
+ const items = toArray(value);
86
+ // Identify required vs null columns
87
+ const requiredColumns = items.filter((item) => isInstanceOf(item, Column));
81
88
  const nullColumns = participatingColumns.filter((column) => !requiredColumns.includes(column));
82
- const customConditions = toArray(value)
83
- .filter((val) => !isInstanceOf(val, Column) && (val !== true))
84
- .map((condition) => isBoolean(condition) ? (condition ? SQL_TRUE : SQL_FALSE) : condition);
85
- const condition = and(...requiredColumns.map((col) => sqlIsNotNull(col)), ...nullColumns.map((col) => sqlIsNull(col)), ...customConditions);
86
- return [key, condition];
87
- });
88
- const kaseWhen = caseWhen(discriminator);
89
- for (const [key, condition] of mapping) {
90
- kaseWhen.when(enumValue(enumeration, null, key), condition);
89
+ // Push column constraints
90
+ for (const col of requiredColumns) {
91
+ conditions.push(sqlIsNotNull(col));
92
+ }
93
+ for (const col of nullColumns) {
94
+ conditions.push(sqlIsNull(col));
95
+ }
96
+ // Handle custom conditions (SQL or booleans)
97
+ for (const item of items) {
98
+ if (!isInstanceOf(item, Column)) {
99
+ if (isBoolean(item)) {
100
+ if (!item) {
101
+ conditions.push(SQL_FALSE);
102
+ }
103
+ }
104
+ else {
105
+ conditions.push(item);
106
+ }
107
+ }
108
+ }
109
+ const finalCondition = and(...conditions) ?? SQL_TRUE;
110
+ kaseWhen.when(enumValue(enumeration, null, key), finalCondition);
91
111
  }
92
112
  return kaseWhen.else(SQL_FALSE);
93
113
  }
@@ -116,20 +136,17 @@ export function exclusiveNotNull(...columns) {
116
136
  * that defines the default condition to apply when a `Column` is provided in `conditionMapping`.
117
137
  * By default, it generates an `IS NOT NULL` check.
118
138
  */
119
- export function enumerationCaseWhen(enumeration, discriminator, conditionMapping, defaultColumnCondition = (column) => isArray(column) ? and(...column.map((col) => sqlIsNotNull(col))) : sqlIsNotNull(column)) {
120
- const whens = [];
139
+ export function enumerationCaseWhen(enumeration, discriminator, conditionMapping, defaultColumnCondition = (columns) => isArray(columns) ? and(...columns.map((col) => sqlIsNotNull(col))) : sqlIsNotNull(columns)) {
140
+ const kaseWhen = caseWhen(discriminator);
121
141
  for (const [key, value] of objectEntries(conditionMapping)) {
122
142
  const condition = match(value)
123
- .with(P.boolean, (bool) => bool ? SQL_TRUE : SQL_FALSE)
124
- .when((value) => isInstanceOf(value, Column), (col) => defaultColumnCondition(col))
143
+ .with(P.boolean, (bool) => (bool ? SQL_TRUE : SQL_FALSE))
144
+ // Check for both a single Column OR an Array of Columns
145
+ .when((val) => isInstanceOf(val, Column) || (isArray(val) && val.every((v) => isInstanceOf(v, Column))), (cols) => defaultColumnCondition(cols))
125
146
  .otherwise((rawSql) => rawSql);
126
- whens.push(sql ` WHEN ${enumValue(enumeration, null, key)} THEN ${condition}`);
147
+ kaseWhen.when(enumValue(enumeration, null, key), condition);
127
148
  }
128
- return sql `
129
- CASE ${discriminator}
130
- ${sql.join(whens, sql `\n`)}
131
- ELSE FALSE
132
- END`;
149
+ return kaseWhen.else(SQL_FALSE);
133
150
  }
134
151
  export function array(values) {
135
152
  const chunks = values.map((value) => isSQLWrapper(value) ? value : sql `${value}`);
@@ -411,9 +428,7 @@ export function jsonbBuildObject(properties) {
411
428
  const chunks = [];
412
429
  for (const [key, value] of entries) {
413
430
  if (isDefined(value)) {
414
- const isSimpleKey = simpleJsonKeyPattern.test(key);
415
- const sqlKey = isSimpleKey ? sql.raw(`'${key}'`) : sql `${key}`;
416
- chunks.push(sqlKey, buildJsonb(value));
431
+ chunks.push(sql `${key}::text`, buildJsonb(value));
417
432
  }
418
433
  }
419
434
  if (chunks.length == 0) {
@@ -439,8 +454,17 @@ export function buildJsonb(value) {
439
454
  const elements = value.map((inner) => buildJsonb(inner));
440
455
  return markAsJsonb(sql `jsonb_build_array(${sql.join(elements, sql `, `)})`);
441
456
  }
442
- if (isObject(value)) {
457
+ if (isLiteralObject(value)) {
443
458
  return jsonbBuildObject(value);
444
459
  }
445
- return markAsJsonb(sql `${JSON.stringify(value)}::jsonb`);
460
+ if (isString(value)) {
461
+ return markAsJsonb(sql `to_jsonb(${value}::text)`);
462
+ }
463
+ if (isNumber(value)) {
464
+ return markAsJsonb(sql `to_jsonb(${value}::numeric)`);
465
+ }
466
+ if (isBoolean(value)) {
467
+ return markAsJsonb(sql `to_jsonb(${value}::boolean)`);
468
+ }
469
+ return markAsJsonb(sql `to_jsonb(${value})`);
446
470
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import { PgDialect } from 'drizzle-orm/pg-core';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { buildJsonb } from '../sqls/sqls.js';
4
+ describe('buildJsonb', () => {
5
+ const dialect = new PgDialect();
6
+ test('should build jsonb from simple object', () => {
7
+ const query = buildJsonb({ a: 1, b: 'foo' });
8
+ const { sql, params } = dialect.sqlToQuery(query);
9
+ expect(sql).toBe('jsonb_build_object($1::text, to_jsonb($2), $3::text, to_jsonb($4))');
10
+ expect(params).toEqual(['a', 1, 'b', 'foo']);
11
+ });
12
+ test('should build jsonb from object with non-simple keys', () => {
13
+ const query = buildJsonb({ 'Betriebs-Nr.': '18182952' });
14
+ const { sql, params } = dialect.sqlToQuery(query);
15
+ // This is what failed before: it lacked the ::text cast
16
+ expect(sql).toBe('jsonb_build_object($1::text, to_jsonb($2))');
17
+ expect(params).toEqual(['Betriebs-Nr.', '18182952']);
18
+ });
19
+ test('should build jsonb from nested structures', () => {
20
+ const query = buildJsonb({
21
+ additionalData: { 'Betriebs-Nr.': '18182952' },
22
+ tags: ['a', 'b']
23
+ });
24
+ const { sql, params } = dialect.sqlToQuery(query);
25
+ expect(sql).toBe('jsonb_build_object($1::text, jsonb_build_object($2::text, to_jsonb($3)), $4::text, jsonb_build_array(to_jsonb($5), to_jsonb($6)))');
26
+ expect(params).toEqual(['additionalData', 'Betriebs-Nr.', '18182952', 'tags', 'a', 'b']);
27
+ });
28
+ test('should handle numbers correctly', () => {
29
+ const query = buildJsonb({ score: 0.5 });
30
+ const { sql, params } = dialect.sqlToQuery(query);
31
+ expect(sql).toBe('jsonb_build_object($1::text, to_jsonb($2))');
32
+ expect(params).toEqual(['score', 0.5]);
33
+ });
34
+ test('should handle null and empty structures', () => {
35
+ expect(dialect.sqlToQuery(buildJsonb(null)).sql).toBe('\'null\'::jsonb');
36
+ expect(dialect.sqlToQuery(buildJsonb({})).sql).toBe('\'{}\'::jsonb');
37
+ expect(dialect.sqlToQuery(buildJsonb([])).sql).toBe('\'[]\'::jsonb');
38
+ });
39
+ });
@@ -7,13 +7,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { describe, expect, test } from 'vitest';
11
- import { sql } from 'drizzle-orm';
12
10
  import { PgDialect } from 'drizzle-orm/pg-core';
13
- import { StringProperty, Integer } from '../../schema/index.js';
11
+ import { Integer, StringProperty } from '../../schema/index.js';
12
+ import { describe, expect, test } from 'vitest';
13
+ import { Table } from '../decorators.js';
14
14
  import { Entity } from '../entity.js';
15
- import { Column, Table } from '../decorators.js';
16
- import { getDrizzleTableFromType, getColumnDefinitionsMap } from '../server/drizzle/schema-converter.js';
15
+ import { getColumnDefinitionsMap, getDrizzleTableFromType } from '../server/drizzle/schema-converter.js';
17
16
  import { convertQuery } from '../server/query-converter.js';
18
17
  describe('ORM Query Converter Complex', () => {
19
18
  const dialect = new PgDialect();
@@ -49,10 +48,11 @@ describe('ORM Query Converter Complex', () => {
49
48
  },
50
49
  };
51
50
  const condition = convertQuery(q, table, colMap);
52
- const sqlStr = dialect.sqlToQuery(condition).sql;
53
- expect(sqlStr).toContain('setweight(to_tsvector($1, "test"."complex_items"."name"), \'A\')');
54
- expect(sqlStr).toContain('setweight(to_tsvector($2, "test"."complex_items"."description"), \'B\')');
55
- expect(sqlStr).toContain('websearch_to_tsquery($3, $4)');
51
+ const { sql, params } = dialect.sqlToQuery(condition);
52
+ expect(sql).toContain('setweight(to_tsvector($1, "test"."complex_items"."name"), \'A\')');
53
+ expect(sql).toContain('setweight(to_tsvector($2, "test"."complex_items"."description"), \'B\')');
54
+ expect(sql).toContain('websearch_to_tsquery($3, $4)');
55
+ expect(params).toEqual(['english', 'english', 'english', 'search term']);
56
56
  });
57
57
  test('should handle ParadeDB $parade match with tokenizer', () => {
58
58
  const q = {
@@ -66,9 +66,10 @@ describe('ORM Query Converter Complex', () => {
66
66
  },
67
67
  };
68
68
  const condition = convertQuery(q, table, colMap);
69
- const sqlStr = dialect.sqlToQuery(condition).sql;
69
+ const { sql, params } = dialect.sqlToQuery(condition);
70
70
  // Tokenizer is supported via JSON object syntax
71
- expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'match\', jsonb_build_object(\'value\', $1::jsonb, \'tokenizer\', jsonb_build_object(\'type\', $2::jsonb, \'min_gram\', $3::jsonb, \'max_gram\', $4::jsonb)))::pdb.query');
71
+ expect(sql).toContain('"test"."complex_items"."name" @@@ jsonb_build_object($1::text, jsonb_build_object($2::text, to_jsonb($3), $4::text, jsonb_build_object($5::text, to_jsonb($6), $7::text, to_jsonb($8), $9::text, to_jsonb($10))))::pdb.query');
72
+ expect(params).toEqual(['match', 'value', 'test', 'tokenizer', 'type', 'ngram', 'min_gram', 3, 'max_gram', 3]);
72
73
  });
73
74
  test('should handle ParadeDB $parade range', () => {
74
75
  const q = {
@@ -82,9 +83,10 @@ describe('ORM Query Converter Complex', () => {
82
83
  },
83
84
  };
84
85
  const condition = convertQuery(q, table, colMap);
85
- const sqlStr = dialect.sqlToQuery(condition).sql;
86
+ const { sql, params } = dialect.sqlToQuery(condition);
86
87
  // This should fall back to convertParadeComparisonQuery with recursive jsonb build
87
- expect(sqlStr).toContain('"test"."complex_items"."value" @@@ jsonb_build_object(\'range\', jsonb_build_object(\'lower_bound\', jsonb_build_object(\'included\', $1::jsonb), \'upper_bound\', jsonb_build_object(\'excluded\', $2::jsonb)))::pdb.query');
88
+ expect(sql).toContain('"test"."complex_items"."value" @@@ jsonb_build_object($1::text, jsonb_build_object($2::text, jsonb_build_object($3::text, to_jsonb($4)), $5::text, jsonb_build_object($6::text, to_jsonb($7))))::pdb.query');
89
+ expect(params).toEqual(['range', 'lower_bound', 'included', 10, 'upper_bound', 'excluded', 20]);
88
90
  });
89
91
  test('should handle ParadeDB $parade phrasePrefix', () => {
90
92
  const q = {
@@ -95,8 +97,9 @@ describe('ORM Query Converter Complex', () => {
95
97
  },
96
98
  };
97
99
  const condition = convertQuery(q, table, colMap);
98
- const sqlStr = dialect.sqlToQuery(condition).sql;
99
- expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'phrase_prefix\', jsonb_build_object(\'phrases\', jsonb_build_array($1::jsonb, $2::jsonb), \'max_expansions\', $3::jsonb))::pdb.query');
100
+ const { sql, params } = dialect.sqlToQuery(condition);
101
+ expect(sql).toContain('"test"."complex_items"."name" @@@ jsonb_build_object($1::text, jsonb_build_object($2::text, jsonb_build_array(to_jsonb($3), to_jsonb($4)), $5::text, to_jsonb($6)))::pdb.query');
102
+ expect(params).toEqual(['phrase_prefix', 'phrases', 'hello', 'wor', 'max_expansions', 10]);
100
103
  });
101
104
  test('should handle ParadeDB $parade regexPhrase', () => {
102
105
  const q = {
@@ -107,8 +110,9 @@ describe('ORM Query Converter Complex', () => {
107
110
  },
108
111
  };
109
112
  const condition = convertQuery(q, table, colMap);
110
- const sqlStr = dialect.sqlToQuery(condition).sql;
111
- expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'regex_phrase\', jsonb_build_object(\'regexes\', jsonb_build_array($1::jsonb, $2::jsonb), \'slop\', $3::jsonb))::pdb.query');
113
+ const { sql, params } = dialect.sqlToQuery(condition);
114
+ expect(sql).toContain('"test"."complex_items"."name" @@@ jsonb_build_object($1::text, jsonb_build_object($2::text, jsonb_build_array(to_jsonb($3), to_jsonb($4)), $5::text, to_jsonb($6)))::pdb.query');
115
+ expect(params).toEqual(['regex_phrase', 'regexes', 'he.*', 'wo.*', 'slop', 1]);
112
116
  });
113
117
  test('should handle ParadeDB top-level moreLikeThis', () => {
114
118
  const q = {
@@ -120,7 +124,8 @@ describe('ORM Query Converter Complex', () => {
120
124
  },
121
125
  };
122
126
  const condition = convertQuery(q, table, colMap);
123
- const sqlStr = dialect.sqlToQuery(condition).sql;
124
- expect(sqlStr).toContain('"test"."complex_items"."id" @@@ jsonb_build_object(\'more_like_this\', jsonb_build_object(\'key_value\', $1::jsonb, \'fields\', to_jsonb(ARRAY[$2, $3])))::pdb.query');
127
+ const { sql, params } = dialect.sqlToQuery(condition);
128
+ expect(sql).toContain('"test"."complex_items"."id" @@@ jsonb_build_object($1::text, jsonb_build_object($2::text, to_jsonb($3), $4::text, to_jsonb(ARRAY[$5, $6])))::pdb.query');
129
+ expect(params).toEqual(['more_like_this', 'key_value', '123', 'fields', 'name', 'description']);
125
130
  });
126
131
  });
@@ -42,7 +42,7 @@ describe('ORM SQL Helpers', () => {
42
42
  test('interval should generate postgres interval syntax', () => {
43
43
  const i = interval(5, 'days');
44
44
  const sqlStr = dialect.sqlToQuery(i).sql;
45
- expect(sqlStr).toContain("||' days')::interval");
45
+ expect(sqlStr).toContain("|| ' days')::interval");
46
46
  });
47
47
  test('coalesce should join multiple columns', () => {
48
48
  const c = coalesce(testTable.colA, testTable.colB, sql `'default'`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.155",
3
+ "version": "0.93.157",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -135,6 +135,7 @@ export declare class PostgresTaskQueue<Definitions extends TaskDefinitionMap = T
135
135
  private lowFrequencyMaintenanceLoop;
136
136
  private mediumFrequencyMaintenanceLoop;
137
137
  private highFrequencyMaintenanceLoop;
138
+ private logPromiseAllSettledErrors;
138
139
  getBatchConsumer<Type extends TaskTypes<Definitions>>(size: number, cancellationSignal: CancellationSignal, options?: {
139
140
  forceDequeue?: boolean;
140
141
  types?: Type[];
@@ -137,7 +137,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
137
137
  result: null,
138
138
  };
139
139
  [afterResolve]() {
140
- if (!this.isInTransaction) {
140
+ if (!this.isFork) {
141
141
  this.maintenanceLoop();
142
142
  }
143
143
  }
@@ -1311,10 +1311,11 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1311
1311
  async lowFrequencyMaintenanceLoop() {
1312
1312
  while (this.#cancellationSignal.isUnset) {
1313
1313
  try {
1314
- await Promise.allSettled([
1314
+ const results = await Promise.allSettled([
1315
1315
  this.performArchival(),
1316
1316
  this.performArchivePurge(),
1317
1317
  ]);
1318
+ this.logPromiseAllSettledErrors(results, 'low frequency maintenance loop');
1318
1319
  }
1319
1320
  catch (error) {
1320
1321
  this.#logger.error('Error during low frequency maintenance loop', error);
@@ -1327,10 +1328,11 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1327
1328
  async mediumFrequencyMaintenanceLoop() {
1328
1329
  while (this.#cancellationSignal.isUnset) {
1329
1330
  try {
1330
- await Promise.allSettled([
1331
+ const results = await Promise.allSettled([
1331
1332
  this.processExpirations(),
1332
1333
  this.processPriorityAging(),
1333
1334
  ]);
1335
+ this.logPromiseAllSettledErrors(results, 'medium frequency maintenance loop');
1334
1336
  }
1335
1337
  catch (error) {
1336
1338
  this.#logger.error('Error during medium frequency maintenance loop', error);
@@ -1343,11 +1345,12 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1343
1345
  async highFrequencyMaintenanceLoop() {
1344
1346
  while (this.#cancellationSignal.isUnset) {
1345
1347
  try {
1346
- await Promise.allSettled([
1348
+ const results = await Promise.allSettled([
1347
1349
  this.processZombieRetries(),
1348
1350
  this.processZombieExhaustions(),
1349
1351
  this.processHardTimeouts(),
1350
1352
  ]);
1353
+ this.logPromiseAllSettledErrors(results, 'high frequency maintenance loop');
1351
1354
  }
1352
1355
  catch (error) {
1353
1356
  this.#logger.error('Error during high frequency maintenance loop', error);
@@ -1357,6 +1360,13 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1357
1360
  }
1358
1361
  }
1359
1362
  }
1363
+ logPromiseAllSettledErrors(results, context) {
1364
+ for (const result of results) {
1365
+ if (result.status == 'rejected') {
1366
+ this.#logger.error(`Error during ${context}`, result.reason);
1367
+ }
1368
+ }
1369
+ }
1360
1370
  async *getBatchConsumer(size, cancellationSignal, options) {
1361
1371
  const continue$ = merge(this.#messageBus.allMessages$).pipe(filter((namespace) => namespace == this.#namespace));
1362
1372
  const mergedContinue$ = merge(continue$, cancellationSignal);
package/test4.js CHANGED
@@ -23,7 +23,7 @@ const config = {
23
23
  },
24
24
  },
25
25
  s3: {
26
- endpoint: string('S3_ENDPOINT', 'http://localhost:9000'),
26
+ endpoint: string('S3_ENDPOINT', 'http://localhost:19552'),
27
27
  accessKey: string('S3_ACCESS_KEY', 'tstdl-dev'),
28
28
  secretKey: string('S3_SECRET_KEY', 'tstdl-dev'),
29
29
  bucket: string('S3_BUCKET', undefined),
@@ -125,7 +125,7 @@ export async function setupIntegrationTest(options = {}) {
125
125
  if (options.modules?.objectStorage) {
126
126
  const bucketPerModule = options.s3?.bucketPerModule ?? configParser.boolean('S3_BUCKET_PER_MODULE', true);
127
127
  configureS3ObjectStorage({
128
- endpoint: options.s3?.endpoint ?? configParser.string('S3_ENDPOINT', 'http://127.0.0.1:9000'),
128
+ endpoint: options.s3?.endpoint ?? configParser.string('S3_ENDPOINT', 'http://127.0.0.1:19552'),
129
129
  accessKey: options.s3?.accessKey ?? configParser.string('S3_ACCESS_KEY', 'tstdl-dev'),
130
130
  secretKey: options.s3?.secretKey ?? configParser.string('S3_SECRET_KEY', 'tstdl-dev'),
131
131
  bucket: bucketPerModule ? undefined : (options.s3?.bucket ?? configParser.string('S3_BUCKET', 'test-bucket')),