@tstdl/base 0.93.197 → 0.93.199
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/cryptography/cryptography.js +16 -2
- package/cryptography/module.js +11 -1
- package/examples/document-management/main.js +4 -0
- package/orm/enums.d.ts +4 -2
- package/orm/enums.js +19 -8
- package/orm/server/database-schema.js +3 -5
- package/orm/server/drizzle/schema-converter.d.ts +1 -1
- package/orm/server/drizzle/schema-converter.js +7 -6
- package/orm/sqls/sqls.d.ts +1 -1
- package/orm/sqls/sqls.js +17 -10
- package/package.json +3 -3
- package/task-queue/postgres/task-queue.js +56 -25
|
@@ -74,7 +74,15 @@ export function sign(algorithm, key, data) {
|
|
|
74
74
|
length: algorithm.outputLength * 8,
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
|
-
const arrayBufferPromise =
|
|
77
|
+
const arrayBufferPromise = (async () => {
|
|
78
|
+
let arrayBuffer = await globalThis.crypto.subtle.sign(finalAlgorithm, key, bytes);
|
|
79
|
+
// TODO: TEMPORARY because of bug in node 25.9
|
|
80
|
+
if (isNotString(algorithm) && algorithm.name.startsWith('KMAC') && ('outputLength' in algorithm) && (arrayBuffer.byteLength == algorithm.outputLength / 8)) {
|
|
81
|
+
const bitAlgorithm = { ...finalAlgorithm, outputLength: algorithm.outputLength * 8 };
|
|
82
|
+
arrayBuffer = await globalThis.crypto.subtle.sign(bitAlgorithm, key, bytes);
|
|
83
|
+
}
|
|
84
|
+
return arrayBuffer;
|
|
85
|
+
})();
|
|
78
86
|
const result = {
|
|
79
87
|
toBuffer: async () => await arrayBufferPromise,
|
|
80
88
|
toUint8Array: async () => new Uint8Array(await arrayBufferPromise),
|
|
@@ -104,7 +112,13 @@ export async function verify(algorithm, key, signature, data) {
|
|
|
104
112
|
length: algorithm.outputLength * 8,
|
|
105
113
|
};
|
|
106
114
|
}
|
|
107
|
-
|
|
115
|
+
let verified = await globalThis.crypto.subtle.verify(finalAlgorithm, key, signatureBytes, dataBytes);
|
|
116
|
+
// TODO: TEMPORARY because of bug in node 25.9
|
|
117
|
+
if (!verified && isNotString(algorithm) && algorithm.name.startsWith('KMAC') && ('outputLength' in algorithm)) {
|
|
118
|
+
const bitAlgorithm = { ...finalAlgorithm, outputLength: algorithm.outputLength * 8 };
|
|
119
|
+
verified = await globalThis.crypto.subtle.verify(bitAlgorithm, key, signatureBytes, dataBytes);
|
|
120
|
+
}
|
|
121
|
+
return verified;
|
|
108
122
|
}
|
|
109
123
|
export async function importKey(format, keyData, algorithm, extractable, keyUsages) {
|
|
110
124
|
return await globalThis.crypto.subtle.importKey(format, keyData, algorithm, extractable, keyUsages);
|
package/cryptography/module.js
CHANGED
|
@@ -72,10 +72,20 @@ export class DerivedKey {
|
|
|
72
72
|
assertDefined(this.#keyUsages, 'This key was not configured to derive a CryptoKey. Use injectDerivedCryptoKey().');
|
|
73
73
|
if (isUndefined(this.#derivedKey)) {
|
|
74
74
|
this.#derivedKey = (async () => {
|
|
75
|
-
|
|
75
|
+
let rawBytes = await this.#computeRawBytes();
|
|
76
76
|
try {
|
|
77
77
|
return await importKey('raw-secret', rawBytes, this.#keyOptions, false, this.#keyUsages);
|
|
78
78
|
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// TODO: TEMPORARY because of bug in node 25.9
|
|
81
|
+
if (error instanceof Error && error.name == 'DataError' && error.message.includes('key length')) {
|
|
82
|
+
const bitAlgorithm = { ...this.#kmacParams, outputLength: this.#kmacParams.outputLength * 8 };
|
|
83
|
+
const baseKey = await this.#baseKey;
|
|
84
|
+
rawBytes = await sign(bitAlgorithm, baseKey, this.#salt).toBuffer();
|
|
85
|
+
return await importKey('raw-secret', rawBytes, this.#keyOptions, false, this.#keyUsages);
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
79
89
|
finally {
|
|
80
90
|
crypto.getRandomValues(new Uint8Array(rawBytes));
|
|
81
91
|
}
|
|
@@ -13,6 +13,7 @@ import { Application } from '../../application/application.js';
|
|
|
13
13
|
import { provideInitializer, provideModule, provideSignalHandler } from '../../application/index.js';
|
|
14
14
|
import { configureAudit } from '../../audit/module.js';
|
|
15
15
|
import { configurePostgresCircuitBreaker } from '../../circuit-breaker/postgres/module.js';
|
|
16
|
+
import { configureSecrets } from '../../cryptography/module.js';
|
|
16
17
|
import { DocumentManagementAuthorizationService } from '../../document-management/index.js';
|
|
17
18
|
import { configureDocumentManagement } from '../../document-management/server/configure.js';
|
|
18
19
|
import { DocumentCategoryTypeService, DocumentCollectionService, DocumentManagementAncillaryService, DocumentManagementApiController, DocumentRequestService } from '../../document-management/server/index.js';
|
|
@@ -95,6 +96,9 @@ function bootstrap() {
|
|
|
95
96
|
configurePostgresRateLimiter();
|
|
96
97
|
configureLocalMessageBus();
|
|
97
98
|
configureDefaultSignalsImplementation();
|
|
99
|
+
configureSecrets({
|
|
100
|
+
key: 'document-management-test',
|
|
101
|
+
});
|
|
98
102
|
configureGenkit({
|
|
99
103
|
gemini: {
|
|
100
104
|
apiKey: config.ai.apiKey,
|
package/orm/enums.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { PgEnum } from 'drizzle-orm/pg-core';
|
|
2
2
|
import type { Enumeration, EnumerationValue, UnionToTuple } from '../types/types.js';
|
|
3
|
-
export declare function registerEnum(enumeration:
|
|
4
|
-
export declare function
|
|
3
|
+
export declare function registerEnum<T extends Enumeration>(enumeration: T, instance: PgEnumFromEnumeration<T>): void;
|
|
4
|
+
export declare function getRegisteredEnum<T extends Enumeration>(enumeration: T): PgEnumFromEnumeration<T> | undefined;
|
|
5
|
+
export declare function tryGetEnumName(enumeration: Enumeration): string | undefined;
|
|
6
|
+
export declare function getEnumName(enumeration: Enumeration): string;
|
|
5
7
|
export type PgEnumFromEnumeration<T extends Enumeration> = PgEnum<Extract<UnionToTuple<`${EnumerationValue<T>}`>, [string, ...string[]]>>;
|
package/orm/enums.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { tryGetEnumName } from '../enumeration/index.js';
|
|
1
|
+
import { tryGetEnumName as tryGetBaseEnumName } from '../enumeration/index.js';
|
|
2
2
|
import { toSnakeCase } from '../utils/string/casing.js';
|
|
3
3
|
import { isUndefined } from '../utils/type-guards.js';
|
|
4
|
-
const
|
|
5
|
-
export function registerEnum(enumeration,
|
|
6
|
-
|
|
4
|
+
const enumInstances = new Map();
|
|
5
|
+
export function registerEnum(enumeration, instance) {
|
|
6
|
+
enumInstances.set(enumeration, instance);
|
|
7
|
+
}
|
|
8
|
+
export function getRegisteredEnum(enumeration) {
|
|
9
|
+
return enumInstances.get(enumeration);
|
|
10
|
+
}
|
|
11
|
+
export function tryGetEnumName(enumeration) {
|
|
12
|
+
const instance = enumInstances.get(enumeration);
|
|
13
|
+
if (isUndefined(instance)) {
|
|
14
|
+
const baseName = tryGetBaseEnumName(enumeration);
|
|
15
|
+
return isUndefined(baseName) ? undefined : toSnakeCase(baseName);
|
|
16
|
+
}
|
|
17
|
+
return instance.enumName;
|
|
7
18
|
}
|
|
8
19
|
export function getEnumName(enumeration) {
|
|
9
|
-
const
|
|
10
|
-
if (isUndefined(
|
|
11
|
-
|
|
20
|
+
const name = tryGetEnumName(enumeration);
|
|
21
|
+
if (isUndefined(name)) {
|
|
22
|
+
throw new Error('Unable to determine enum name. Make sure the enum is registered.');
|
|
12
23
|
}
|
|
13
|
-
return
|
|
24
|
+
return name;
|
|
14
25
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isDefined } from '../../utils/type-guards.js';
|
|
2
1
|
import { registerEnum } from '../enums.js';
|
|
3
2
|
import { getDrizzleTableFromType, getPgEnum } from './drizzle/schema-converter.js';
|
|
4
3
|
/**
|
|
@@ -33,10 +32,9 @@ export class DatabaseSchema {
|
|
|
33
32
|
* @returns The Drizzle enum object.
|
|
34
33
|
*/
|
|
35
34
|
getEnum(enumeration, name) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return getPgEnum(this.name, enumeration); // eslint-disable-line @typescript-eslint/no-unsafe-return
|
|
35
|
+
const pgEnum = getPgEnum(this.name, enumeration, name);
|
|
36
|
+
registerEnum(enumeration, pgEnum);
|
|
37
|
+
return pgEnum;
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
/**
|
|
@@ -12,7 +12,7 @@ export declare function getTableColumnDefinitions(table: PgTableWithColumns<any>
|
|
|
12
12
|
export declare function getColumnDefinitionsMap(table: PgTableWithColumns<any>): ColumnDefinitionsMap;
|
|
13
13
|
export declare function _getDrizzleTableFromType<T extends EntityType, S extends string>(type: T, fallbackSchemaName?: S): PgTableFromType<T, S>;
|
|
14
14
|
export declare function isTableOwning(type: AbstractConstructor): boolean;
|
|
15
|
-
export declare function getPgEnum(schema: string | PgSchema, enumeration: Enumeration,
|
|
15
|
+
export declare function getPgEnum(schema: string | PgSchema, enumeration: Enumeration, contextOrName?: ConverterContext | string): PgEnum<[string, ...string[]]>;
|
|
16
16
|
export declare function getPrimaryKeyColumnDefinitions(type: EntityType, table: PgTableFromType, columnDefinitions?: ColumnDefinition[], columnDefinitionsMap?: ColumnDefinitionsMap): ColumnDefinition[];
|
|
17
17
|
export declare function getPrimaryKeyColumns(type: EntityType, table: PgTableFromType, columnDefinitions?: ColumnDefinition[], columnDefinitionsMap?: ColumnDefinitionsMap): PgColumn[];
|
|
18
18
|
export {};
|
|
@@ -20,7 +20,7 @@ import { assertDefined, assertDefinedPass, assertUint8ArrayPass, isArray, isDefi
|
|
|
20
20
|
import { resolveValueOrProvider } from '../../../utils/value-or-provider.js';
|
|
21
21
|
import { bytea, numericDate, timestamp, tsvector } from '../../data-types/index.js';
|
|
22
22
|
import { TenantBaseEntity, TenantEntity } from '../../entity.js';
|
|
23
|
-
import {
|
|
23
|
+
import { registerEnum, tryGetEnumName } from '../../enums.js';
|
|
24
24
|
import { JsonSchema, NumericDateSchema, NumericSchema, TimestampSchema, TsVectorSchema, UuidSchema } from '../../schemas/index.js';
|
|
25
25
|
import { getEntitySchema, getEntityTableName, getInheritanceMetadata, getTableReflectionDatas, isChildEntity } from '../../utils.js';
|
|
26
26
|
import { decryptBytes, encryptBytes } from '../encryption.js';
|
|
@@ -474,21 +474,22 @@ function getPgColumnBuilder(columnName, dbSchema, schema, _reflectionData, conte
|
|
|
474
474
|
});
|
|
475
475
|
}
|
|
476
476
|
const enums = new MultiKeyMap();
|
|
477
|
-
export function getPgEnum(schema, enumeration,
|
|
477
|
+
export function getPgEnum(schema, enumeration, contextOrName) {
|
|
478
478
|
const dbSchema = isString(schema) ? getDbSchema(schema) : schema;
|
|
479
|
-
const enumName =
|
|
479
|
+
const enumName = isString(contextOrName) ? toSnakeCase(contextOrName) : tryGetEnumName(enumeration);
|
|
480
480
|
if (isUndefined(enumName)) {
|
|
481
481
|
const errorMessage = 'Enum is not registered. Please register it using `databaseSchema.getEnum(MyEnum)` before use.';
|
|
482
|
-
if (isDefined(
|
|
483
|
-
throw new Error(`${errorMessage} (type: ${
|
|
482
|
+
if (isDefined(contextOrName) && !isString(contextOrName)) {
|
|
483
|
+
throw new Error(`${errorMessage} (type: ${contextOrName.type.name}, property: ${contextOrName.property})`);
|
|
484
484
|
}
|
|
485
485
|
throw new Error(errorMessage);
|
|
486
486
|
}
|
|
487
487
|
const values = (isArray(enumeration) ? enumeration : enumValues(enumeration))
|
|
488
488
|
.map((value) => value.toString());
|
|
489
489
|
const dbEnum = dbSchema.enum(enumName, values);
|
|
490
|
-
if (enums.has([dbSchema.schemaName, enumeration])) {
|
|
490
|
+
if (!enums.has([dbSchema.schemaName, enumeration])) {
|
|
491
491
|
enums.set([dbSchema.schemaName, enumeration], dbEnum);
|
|
492
|
+
registerEnum(enumeration, dbEnum);
|
|
492
493
|
}
|
|
493
494
|
return dbEnum;
|
|
494
495
|
}
|
package/orm/sqls/sqls.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export declare const RANDOM_UUID_V7: SQL<Uuid>;
|
|
|
66
66
|
export declare const SQL_TRUE: SQL<boolean>;
|
|
67
67
|
export declare const SQL_FALSE: SQL<boolean>;
|
|
68
68
|
export declare const SQL_NULL: SQL<null>;
|
|
69
|
-
export declare function enumValue<T extends EnumerationObject>(enumeration: T
|
|
69
|
+
export declare function enumValue<T extends EnumerationObject>(enumeration: T | PgEnumFromEnumeration<T> | string, value: EnumerationValue<T>, schema?: string): SQL<string>;
|
|
70
70
|
/**
|
|
71
71
|
* Generates a SQL `CASE` expression to enforce strict, mutually exclusive column usage based on a discriminator value.
|
|
72
72
|
*
|
package/orm/sqls/sqls.js
CHANGED
|
@@ -8,8 +8,8 @@ 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 {
|
|
12
|
-
import { getEnumName } from '../enums.js';
|
|
11
|
+
import { assertString, isArray, isBoolean, isDefined, isFunction, isInstanceOf, isLiteralObject, isNotNull, isNotString, isNull, isNullOrUndefined, isNumber, isString } from '../../utils/type-guards.js';
|
|
12
|
+
import { getEnumName, getRegisteredEnum } from '../enums.js';
|
|
13
13
|
import { caseWhen } from './case-when.js';
|
|
14
14
|
const isJsonbSymbol = Symbol('isJsonb');
|
|
15
15
|
function markAsJsonb(value) {
|
|
@@ -36,13 +36,20 @@ export const RANDOM_UUID_V7 = sql `uuidv7()`;
|
|
|
36
36
|
export const SQL_TRUE = sql `TRUE`;
|
|
37
37
|
export const SQL_FALSE = sql `FALSE`;
|
|
38
38
|
export const SQL_NULL = sql `NULL`;
|
|
39
|
-
export function enumValue(enumeration,
|
|
40
|
-
if (
|
|
39
|
+
export function enumValue(enumeration, value, schema) {
|
|
40
|
+
if (isFunction(enumeration)) {
|
|
41
|
+
return sql `'${sql.raw(String(value))}'::${enumeration}`;
|
|
42
|
+
}
|
|
43
|
+
if (isNotString(enumeration)) {
|
|
44
|
+
const instance = getRegisteredEnum(enumeration);
|
|
45
|
+
if (isDefined(instance)) {
|
|
46
|
+
return enumValue(instance, value, schema);
|
|
47
|
+
}
|
|
41
48
|
const enumName = getEnumName(enumeration);
|
|
42
|
-
|
|
43
|
-
return enumValue(enumeration, enumName, value);
|
|
49
|
+
return enumValue(enumName, value, schema);
|
|
44
50
|
}
|
|
45
|
-
|
|
51
|
+
assertString(enumeration, 'Invalid enumeration provided. Expected a string, a registered enum object, or a Drizzle PgEnum instance.');
|
|
52
|
+
const enumType = isDefined(schema) ? sql `${sql.identifier(schema)}.${sql.identifier(enumeration)}` : sql.identifier(enumeration);
|
|
46
53
|
return sql `'${sql.raw(String(value))}'::${enumType}`;
|
|
47
54
|
}
|
|
48
55
|
/**
|
|
@@ -80,7 +87,7 @@ export function exclusiveColumn(enumeration, discriminator, conditionMapping) {
|
|
|
80
87
|
// 2. Iterate and build conditions safely
|
|
81
88
|
for (const [key, value] of objectEntries(conditionMapping)) {
|
|
82
89
|
if (isNull(value)) {
|
|
83
|
-
kaseWhen.when(enumValue(enumeration,
|
|
90
|
+
kaseWhen.when(enumValue(enumeration, key), SQL_FALSE);
|
|
84
91
|
continue;
|
|
85
92
|
}
|
|
86
93
|
const conditions = [];
|
|
@@ -109,7 +116,7 @@ export function exclusiveColumn(enumeration, discriminator, conditionMapping) {
|
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
const finalCondition = and(...conditions) ?? SQL_TRUE;
|
|
112
|
-
kaseWhen.when(enumValue(enumeration,
|
|
119
|
+
kaseWhen.when(enumValue(enumeration, key), finalCondition);
|
|
113
120
|
}
|
|
114
121
|
return kaseWhen.else(SQL_FALSE);
|
|
115
122
|
}
|
|
@@ -146,7 +153,7 @@ export function enumerationCaseWhen(enumeration, discriminator, conditionMapping
|
|
|
146
153
|
// Check for both a single Column OR an Array of Columns
|
|
147
154
|
.when((val) => isInstanceOf(val, Column) || (isArray(val) && val.every((v) => isInstanceOf(v, Column))), (cols) => defaultColumnCondition(cols))
|
|
148
155
|
.otherwise((rawSql) => rawSql);
|
|
149
|
-
kaseWhen.when(enumValue(enumeration,
|
|
156
|
+
kaseWhen.when(enumValue(enumeration, key), condition);
|
|
150
157
|
}
|
|
151
158
|
return kaseWhen.else(SQL_FALSE);
|
|
152
159
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.199",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -153,8 +153,8 @@
|
|
|
153
153
|
"type-fest": "^5.5"
|
|
154
154
|
},
|
|
155
155
|
"peerDependencies": {
|
|
156
|
-
"@aws-sdk/client-s3": "^3.
|
|
157
|
-
"@aws-sdk/s3-request-presigner": "^3.
|
|
156
|
+
"@aws-sdk/client-s3": "^3.1026",
|
|
157
|
+
"@aws-sdk/s3-request-presigner": "^3.1026",
|
|
158
158
|
"@genkit-ai/google-genai": "^1.31",
|
|
159
159
|
"@google-cloud/storage": "^7.19",
|
|
160
160
|
"@toon-format/toon": "^2.1.0",
|
|
@@ -112,7 +112,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
112
112
|
#takeNewUpdate = {
|
|
113
113
|
namespace: this.#namespace,
|
|
114
114
|
type: sql `excluded.type`,
|
|
115
|
-
status: caseWhen(gt(sql `excluded.unresolved_schedule_dependencies`, 0), enumValue(
|
|
115
|
+
status: caseWhen(gt(sql `excluded.unresolved_schedule_dependencies`, 0), enumValue(taskStatus, TaskStatus.Waiting)).else(enumValue(taskStatus, TaskStatus.Pending)),
|
|
116
116
|
token: null,
|
|
117
117
|
priority: sql `excluded.priority`,
|
|
118
118
|
idempotencyKey: sql `excluded.idempotency_key`,
|
|
@@ -187,6 +187,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
187
187
|
},
|
|
188
188
|
}));
|
|
189
189
|
const itemsWithIdempotency = entitiesWithIndex.filter((e) => isNotNull(e.entity.idempotencyKey));
|
|
190
|
+
itemsWithIdempotency.sort((a, b) => a.entity.idempotencyKey.localeCompare(b.entity.idempotencyKey));
|
|
190
191
|
const itemsWithoutIdempotency = entitiesWithIndex.filter((e) => isNull(e.entity.idempotencyKey));
|
|
191
192
|
const hasDependencies = itemsWithDistinctDependencies.some((item) => ((item.scheduleAfter?.length ?? 0) > 0) || ((item.completeAfter?.length ?? 0) > 0) || (isDefined(item.parentId) && (item.parentRequires != false) && !(isArray(item.parentRequires) && (item.parentRequires.length == 0))));
|
|
192
193
|
const mustUseTransaction = (entitiesWithIndex.length > 1) || hasDependencies;
|
|
@@ -330,7 +331,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
330
331
|
.from(taskTable)
|
|
331
332
|
.where(inArray(taskTable.id, distinctDependencyIds))
|
|
332
333
|
.unionAll(session
|
|
333
|
-
.select({ id: taskArchiveTable.id, status: sql `${enumValue(
|
|
334
|
+
.select({ id: taskArchiveTable.id, status: sql `${enumValue(taskStatus, TaskStatus.Completed)}` })
|
|
334
335
|
.from(taskArchiveTable)
|
|
335
336
|
.where(inArray(taskArchiveTable.id, distinctDependencyIds)));
|
|
336
337
|
if (dependencyStatuses.length > 0) {
|
|
@@ -382,7 +383,6 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
382
383
|
decrementsToApply.set(dep.taskId, current);
|
|
383
384
|
}
|
|
384
385
|
const values = [...decrementsToApply]
|
|
385
|
-
.toSorted(([idA], [idB]) => idA.localeCompare(idB))
|
|
386
386
|
.map(([taskId, d]) => sql `(${taskId}::uuid, ${d.schedule}::int, ${d.complete}::int)`);
|
|
387
387
|
const updates = session.$with('updates').as((qb) => qb
|
|
388
388
|
.select({
|
|
@@ -391,8 +391,14 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
391
391
|
completeIncrement: sql `(complete)::int`.as('complete_increment'),
|
|
392
392
|
})
|
|
393
393
|
.from(sql `(VALUES ${sql.join(values, sql `, `)}) AS t(id, schedule, complete)`));
|
|
394
|
+
const locked = session.$with('locked').as((qb) => qb
|
|
395
|
+
.select({ id: taskTable.id })
|
|
396
|
+
.from(taskTable)
|
|
397
|
+
.innerJoin(updates, eq(taskTable.id, updates.taskId))
|
|
398
|
+
.orderBy(asc(taskTable.id))
|
|
399
|
+
.for('update'));
|
|
394
400
|
const updatedRows = await session
|
|
395
|
-
.with(updates)
|
|
401
|
+
.with(updates, locked)
|
|
396
402
|
.update(taskTable)
|
|
397
403
|
.set({
|
|
398
404
|
unresolvedScheduleDependencies: sql `${taskTable.unresolvedScheduleDependencies} + ${updates.scheduleIncrement}`,
|
|
@@ -400,7 +406,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
400
406
|
status: caseWhen(and(eq(taskTable.status, TaskStatus.Pending), gt(sql `${taskTable.unresolvedScheduleDependencies} + ${updates.scheduleIncrement}`, 0)), TaskStatus.Waiting).else(taskTable.status),
|
|
401
407
|
})
|
|
402
408
|
.from(updates)
|
|
403
|
-
.where(eq(taskTable.id, updates.taskId))
|
|
409
|
+
.where(and(eq(taskTable.id, updates.taskId), inArray(taskTable.id, session.select({ id: locked.id }).from(locked))))
|
|
404
410
|
.returning({ id: taskTable.id, status: taskTable.status, namespace: taskTable.namespace });
|
|
405
411
|
const notifiedNamespaces = new Set();
|
|
406
412
|
for (const row of updatedRows) {
|
|
@@ -549,13 +555,13 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
549
555
|
)
|
|
550
556
|
UPDATE ${taskTable}
|
|
551
557
|
SET
|
|
552
|
-
status = ${enumValue(
|
|
558
|
+
status = ${enumValue(taskStatus, TaskStatus.Cancelled)},
|
|
553
559
|
token = NULL,
|
|
554
560
|
complete_timestamp = ${TRANSACTION_TIMESTAMP}
|
|
555
561
|
FROM task_tree
|
|
556
562
|
WHERE
|
|
557
563
|
${taskTable.id} = task_tree.id
|
|
558
|
-
AND ${taskTable.status} NOT IN (${sql.join(terminalStatuses.map((s) => enumValue(
|
|
564
|
+
AND ${taskTable.status} NOT IN (${sql.join(terminalStatuses.map((s) => enumValue(taskStatus, s)), sql `, `)})
|
|
559
565
|
RETURNING ${taskTable.id} as id, ${taskTable.namespace} as namespace
|
|
560
566
|
`);
|
|
561
567
|
if (cancelledRows.rows.length > 0) {
|
|
@@ -688,11 +694,11 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
688
694
|
await session
|
|
689
695
|
.update(taskTable)
|
|
690
696
|
.set({
|
|
691
|
-
status: caseWhen(gt(taskTable.unresolvedScheduleDependencies, 0), enumValue(
|
|
697
|
+
status: caseWhen(gt(taskTable.unresolvedScheduleDependencies, 0), enumValue(taskStatus, TaskStatus.Waiting)).else(enumValue(taskStatus, TaskStatus.Pending)),
|
|
692
698
|
token: null,
|
|
693
699
|
scheduleTimestamp: timestamp,
|
|
694
700
|
visibilityDeadline: null,
|
|
695
|
-
tries: caseWhen(eq(taskTable.status, enumValue(
|
|
701
|
+
tries: caseWhen(eq(taskTable.status, enumValue(taskStatus, TaskStatus.Running)), greatest(0, sql `${taskTable.tries} - 1`)).else(taskTable.tries),
|
|
696
702
|
})
|
|
697
703
|
.where(and(inArray(taskTable.id, ids), notInArray(taskTable.status, terminalStatuses)));
|
|
698
704
|
}
|
|
@@ -701,11 +707,11 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
701
707
|
await session
|
|
702
708
|
.update(taskTable)
|
|
703
709
|
.set({
|
|
704
|
-
status: caseWhen(gt(taskTable.unresolvedScheduleDependencies, 0), enumValue(
|
|
710
|
+
status: caseWhen(gt(taskTable.unresolvedScheduleDependencies, 0), enumValue(taskStatus, TaskStatus.Waiting)).else(enumValue(taskStatus, TaskStatus.Pending)),
|
|
705
711
|
token: null,
|
|
706
712
|
scheduleTimestamp: timestamp,
|
|
707
713
|
visibilityDeadline: null,
|
|
708
|
-
tries: caseWhen(eq(taskTable.status, enumValue(
|
|
714
|
+
tries: caseWhen(eq(taskTable.status, enumValue(taskStatus, TaskStatus.Running)), greatest(0, sql `${taskTable.tries} - 1`)).else(taskTable.tries),
|
|
709
715
|
})
|
|
710
716
|
.where(and(eq(taskTable.namespace, this.#namespace), arrayOverlaps(taskTable.tags, toArray(tags)), notInArray(taskTable.status, terminalStatuses)));
|
|
711
717
|
}
|
|
@@ -718,7 +724,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
718
724
|
const [updatedRow] = await session
|
|
719
725
|
.update(taskTable)
|
|
720
726
|
.set({
|
|
721
|
-
status: caseWhen(exceededMaxExecutionTime, enumValue(
|
|
727
|
+
status: caseWhen(exceededMaxExecutionTime, enumValue(taskStatus, TaskStatus.TimedOut)).else(taskTable.status),
|
|
722
728
|
visibilityDeadline: caseWhen(exceededMaxExecutionTime, null).else(sql `${TRANSACTION_TIMESTAMP} + ${interval(this.visibilityTimeout, 'milliseconds')}`),
|
|
723
729
|
completeTimestamp: caseWhen(exceededMaxExecutionTime, TRANSACTION_TIMESTAMP).else(taskTable.completeTimestamp),
|
|
724
730
|
errors: caseWhen(exceededMaxExecutionTime, sql `${taskTable.errors} || ${buildJsonb([{ code: 'MaxTimeExceeded', message: 'Hard Execution Timeout' }])}`).else(taskTable.errors),
|
|
@@ -755,6 +761,12 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
755
761
|
updateState: sql `(state)::jsonb`.as('update_state'),
|
|
756
762
|
})
|
|
757
763
|
.from(sql `(VALUES ${sql.join(rows, sql `, `)}) AS t(id, token, progress, state)`));
|
|
764
|
+
const locked = session.$with('locked').as((qb) => qb
|
|
765
|
+
.select({ id: taskTable.id })
|
|
766
|
+
.from(taskTable)
|
|
767
|
+
.innerJoin(updates, eq(taskTable.id, updates.updateId))
|
|
768
|
+
.orderBy(asc(taskTable.id))
|
|
769
|
+
.for('update'));
|
|
758
770
|
const updated = session.$with('updated').as(() => session
|
|
759
771
|
.update(taskTable)
|
|
760
772
|
.set({
|
|
@@ -763,10 +775,10 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
763
775
|
state: coalesce(updates.updateState, taskTable.state),
|
|
764
776
|
})
|
|
765
777
|
.from(updates)
|
|
766
|
-
.where(and(eq(taskTable.id, updates.updateId), sql `${taskTable.token} IS NOT DISTINCT FROM ${updates.updateToken}`, eq(taskTable.status, TaskStatus.Running), gt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`)))
|
|
778
|
+
.where(and(eq(taskTable.id, updates.updateId), inArray(taskTable.id, session.select({ id: locked.id }).from(locked)), sql `${taskTable.token} IS NOT DISTINCT FROM ${updates.updateToken}`, eq(taskTable.status, TaskStatus.Running), gt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`)))
|
|
767
779
|
.returning({ id: taskTable.id }));
|
|
768
780
|
const result = await session
|
|
769
|
-
.with(updates, updated)
|
|
781
|
+
.with(updates, locked, updated)
|
|
770
782
|
.select({ id: updated.id })
|
|
771
783
|
.from(updated);
|
|
772
784
|
return result.map((r) => r.id);
|
|
@@ -775,7 +787,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
775
787
|
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
776
788
|
const [updatedTask] = await tx.pgTransaction.update(taskTable)
|
|
777
789
|
.set({
|
|
778
|
-
status: caseWhen(gt(taskTable.unresolvedCompleteDependencies, 0), enumValue(
|
|
790
|
+
status: caseWhen(gt(taskTable.unresolvedCompleteDependencies, 0), enumValue(taskStatus, TaskStatus.WaitingChildren)).else(enumValue(taskStatus, TaskStatus.Completed)),
|
|
779
791
|
token: null,
|
|
780
792
|
result: options?.result,
|
|
781
793
|
progress: caseWhen(gt(taskTable.unresolvedCompleteDependencies, 0), task.progress).else(sql.raw('1')),
|
|
@@ -810,10 +822,16 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
810
822
|
updateProgress: sql `(progress)::numeric`.as('update_progress'),
|
|
811
823
|
})
|
|
812
824
|
.from(sql `(VALUES ${sql.join(rows, sql `, `)}) AS t(id, token, result, progress)`));
|
|
825
|
+
const locked = tx.pgTransaction.$with('locked').as((qb) => qb
|
|
826
|
+
.select({ id: taskTable.id })
|
|
827
|
+
.from(taskTable)
|
|
828
|
+
.innerJoin(updates, eq(taskTable.id, updates.updateId))
|
|
829
|
+
.orderBy(asc(taskTable.id))
|
|
830
|
+
.for('update'));
|
|
813
831
|
const updated = tx.pgTransaction.$with('updated').as(() => tx.pgTransaction
|
|
814
832
|
.update(taskTable)
|
|
815
833
|
.set({
|
|
816
|
-
status: caseWhen(gt(taskTable.unresolvedCompleteDependencies, 0), enumValue(
|
|
834
|
+
status: caseWhen(gt(taskTable.unresolvedCompleteDependencies, 0), enumValue(taskStatus, TaskStatus.WaitingChildren)).else(enumValue(taskStatus, TaskStatus.Completed)),
|
|
817
835
|
token: null,
|
|
818
836
|
result: updates.updateResult,
|
|
819
837
|
progress: caseWhen(gt(taskTable.unresolvedCompleteDependencies, 0), updates.updateProgress).else(1),
|
|
@@ -821,10 +839,10 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
821
839
|
visibilityDeadline: null,
|
|
822
840
|
})
|
|
823
841
|
.from(updates)
|
|
824
|
-
.where(and(eq(taskTable.id, updates.updateId), sql `${taskTable.token} IS NOT DISTINCT FROM ${updates.updateToken}`))
|
|
842
|
+
.where(and(eq(taskTable.id, updates.updateId), inArray(taskTable.id, tx.pgTransaction.select({ id: locked.id }).from(locked)), sql `${taskTable.token} IS NOT DISTINCT FROM ${updates.updateToken}`))
|
|
825
843
|
.returning({ id: taskTable.id, status: taskTable.status }));
|
|
826
844
|
const updatedRows = await tx.pgTransaction
|
|
827
|
-
.with(updates, updated)
|
|
845
|
+
.with(updates, locked, updated)
|
|
828
846
|
.select({ id: updated.id, status: updated.status })
|
|
829
847
|
.from(updated);
|
|
830
848
|
if (updatedRows.length > 0) {
|
|
@@ -879,6 +897,12 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
879
897
|
updateComplete: sql `(complete_timestamp)`.as('update_complete'),
|
|
880
898
|
})
|
|
881
899
|
.from(sql `(VALUES ${sql.join(rows, sql `, `)}) AS t(id, token, tries, status, error, schedule_timestamp, complete_timestamp)`));
|
|
900
|
+
const locked = tx.pgTransaction.$with('locked').as((qb) => qb
|
|
901
|
+
.select({ id: taskTable.id })
|
|
902
|
+
.from(taskTable)
|
|
903
|
+
.innerJoin(updates, eq(taskTable.id, updates.updateId))
|
|
904
|
+
.orderBy(asc(taskTable.id))
|
|
905
|
+
.for('update'));
|
|
882
906
|
const updated = tx.pgTransaction.$with('updated').as(() => tx.pgTransaction
|
|
883
907
|
.update(taskTable)
|
|
884
908
|
.set({
|
|
@@ -890,10 +914,10 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
890
914
|
completeTimestamp: sql `${updates.updateComplete}`,
|
|
891
915
|
})
|
|
892
916
|
.from(updates)
|
|
893
|
-
.where(and(eq(taskTable.id, updates.updateId), sql `${taskTable.token} IS NOT DISTINCT FROM ${updates.updateToken}`, eq(taskTable.tries, updates.updateTries)))
|
|
917
|
+
.where(and(eq(taskTable.id, updates.updateId), inArray(taskTable.id, tx.pgTransaction.select({ id: locked.id }).from(locked)), sql `${taskTable.token} IS NOT DISTINCT FROM ${updates.updateToken}`, eq(taskTable.tries, updates.updateTries)))
|
|
894
918
|
.returning({ id: taskTable.id, status: taskTable.status }));
|
|
895
919
|
const result = await tx.pgTransaction
|
|
896
|
-
.with(updates, updated)
|
|
920
|
+
.with(updates, locked, updated)
|
|
897
921
|
.select({ id: updated.id, status: updated.status })
|
|
898
922
|
.from(updated);
|
|
899
923
|
if (result.length > 0) {
|
|
@@ -916,7 +940,9 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
916
940
|
await this.resolveDependenciesMany([{ id, status, namespace: options?.namespace }], options);
|
|
917
941
|
}
|
|
918
942
|
async resolveDependenciesMany(tasks, options) {
|
|
919
|
-
const tasksToResolve = tasks
|
|
943
|
+
const tasksToResolve = tasks
|
|
944
|
+
.filter((t) => terminalStatuses.includes(t.status))
|
|
945
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
920
946
|
if (tasksToResolve.length == 0) {
|
|
921
947
|
return;
|
|
922
948
|
}
|
|
@@ -1013,7 +1039,6 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1013
1039
|
}
|
|
1014
1040
|
if (decrementsToApply.size > 0) {
|
|
1015
1041
|
const decrementValues = [...decrementsToApply]
|
|
1016
|
-
.toSorted(([idA], [idB]) => idA.localeCompare(idB))
|
|
1017
1042
|
.map(([taskId, d]) => sql `(${taskId}::uuid, ${d.schedule}::int, ${d.complete}::int)`);
|
|
1018
1043
|
const updates = tx.pgTransaction.$with('updates').as((qb) => qb
|
|
1019
1044
|
.select({
|
|
@@ -1022,8 +1047,14 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1022
1047
|
completeDecrement: sql `(complete)::int`.as('complete_decrement'),
|
|
1023
1048
|
})
|
|
1024
1049
|
.from(sql `(VALUES ${sql.join(decrementValues, sql `, `)}) AS t(id, schedule, complete)`));
|
|
1050
|
+
const locked = tx.pgTransaction.$with('locked').as((qb) => qb
|
|
1051
|
+
.select({ id: taskTable.id })
|
|
1052
|
+
.from(taskTable)
|
|
1053
|
+
.innerJoin(updates, eq(taskTable.id, updates.taskId))
|
|
1054
|
+
.orderBy(asc(taskTable.id))
|
|
1055
|
+
.for('update'));
|
|
1025
1056
|
const updatedRows = await tx.pgTransaction
|
|
1026
|
-
.with(updates)
|
|
1057
|
+
.with(updates, locked)
|
|
1027
1058
|
.update(taskTable)
|
|
1028
1059
|
.set({
|
|
1029
1060
|
unresolvedScheduleDependencies: greatest(0, sql `${taskTable.unresolvedScheduleDependencies} - ${updates.scheduleDecrement}`),
|
|
@@ -1034,7 +1065,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1034
1065
|
token: caseWhen(and(eq(taskTable.status, TaskStatus.WaitingChildren), eq(greatest(0, sql `${taskTable.unresolvedCompleteDependencies} - ${updates.completeDecrement}`), 0)), null).else(taskTable.token),
|
|
1035
1066
|
})
|
|
1036
1067
|
.from(updates)
|
|
1037
|
-
.where(eq(taskTable.id, updates.taskId))
|
|
1068
|
+
.where(and(eq(taskTable.id, updates.taskId), inArray(taskTable.id, tx.pgTransaction.select({ id: locked.id }).from(locked))))
|
|
1038
1069
|
.returning({
|
|
1039
1070
|
id: taskTable.id,
|
|
1040
1071
|
namespace: taskTable.namespace,
|
|
@@ -1161,7 +1192,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
|
1161
1192
|
.with(selection)
|
|
1162
1193
|
.update(taskTable)
|
|
1163
1194
|
.set({
|
|
1164
|
-
status: caseWhen(lt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`), enumValue(
|
|
1195
|
+
status: caseWhen(lt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`), enumValue(taskStatus, TaskStatus.TimedOut)).else(caseWhen(lt(taskTable.tries, this.maxTries), enumValue(taskStatus, TaskStatus.Retrying)).else(enumValue(taskStatus, TaskStatus.Orphaned))),
|
|
1165
1196
|
token: null,
|
|
1166
1197
|
visibilityDeadline: null,
|
|
1167
1198
|
completeTimestamp: caseWhen(or(lt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`), gte(taskTable.tries, this.maxTries)), TRANSACTION_TIMESTAMP).else(null),
|