@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.
- package/document-management/server/services/document-workflow.service.js +1 -1
- package/orm/server/repository.js +1 -1
- package/orm/server/transactional.d.ts +2 -0
- package/orm/server/transactional.js +4 -0
- package/orm/sqls/sqls.d.ts +2 -2
- package/orm/sqls/sqls.js +54 -30
- package/orm/tests/build-jsonb.test.d.ts +1 -0
- package/orm/tests/build-jsonb.test.js +39 -0
- package/orm/tests/query-converter-complex.test.js +24 -19
- package/orm/tests/sql-helpers.test.js +1 -1
- package/package.json +1 -1
- package/task-queue/postgres/task-queue.d.ts +1 -0
- package/task-queue/postgres/task-queue.js +14 -4
- package/test4.js +1 -1
- package/testing/integration-setup.js +1 -1
|
@@ -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.
|
|
45
|
+
if (this.isFork) {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
this.#taskQueue.process({ concurrency: 5, cancellationSignal }, async (taskContext) => await this.processWorkflowJob(taskContext));
|
package/orm/server/repository.js
CHANGED
|
@@ -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(),
|
package/orm/sqls/sqls.d.ts
CHANGED
|
@@ -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?: (
|
|
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,
|
|
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((
|
|
75
|
+
.flatMap((value) => toArray(value).filter((item) => isInstanceOf(item, Column)));
|
|
75
76
|
const participatingColumns = distinct(allColumns);
|
|
76
|
-
const
|
|
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
|
-
|
|
81
|
+
kaseWhen.when(enumValue(enumeration, null, key), SQL_FALSE);
|
|
82
|
+
continue;
|
|
79
83
|
}
|
|
80
|
-
const
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 = (
|
|
120
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
147
|
+
kaseWhen.when(enumValue(enumeration, null, key), condition);
|
|
127
148
|
}
|
|
128
|
-
return
|
|
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
|
-
|
|
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 (
|
|
457
|
+
if (isLiteralObject(value)) {
|
|
443
458
|
return jsonbBuildObject(value);
|
|
444
459
|
}
|
|
445
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
53
|
-
expect(
|
|
54
|
-
expect(
|
|
55
|
-
expect(
|
|
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
|
|
69
|
+
const { sql, params } = dialect.sqlToQuery(condition);
|
|
70
70
|
// Tokenizer is supported via JSON object syntax
|
|
71
|
-
expect(
|
|
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
|
|
86
|
+
const { sql, params } = dialect.sqlToQuery(condition);
|
|
86
87
|
// This should fall back to convertParadeComparisonQuery with recursive jsonb build
|
|
87
|
-
expect(
|
|
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
|
|
99
|
-
expect(
|
|
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
|
|
111
|
-
expect(
|
|
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
|
|
124
|
-
expect(
|
|
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
|
@@ -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.
|
|
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:
|
|
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:
|
|
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')),
|