@tstdl/base 0.93.126 → 0.93.128
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/api/client/client.js +45 -9
- package/api/client/tests/api-client.test.d.ts +1 -0
- package/api/client/tests/api-client.test.js +194 -0
- package/api/types.d.ts +34 -2
- package/api/types.js +9 -2
- package/authentication/client/authentication.service.js +30 -11
- package/authentication/client/http-client.middleware.js +10 -3
- package/authentication/server/authentication.service.d.ts +12 -0
- package/authentication/server/authentication.service.js +14 -2
- package/authentication/tests/authentication.client-error-handling.test.js +23 -66
- package/authentication/tests/authentication.client-service-refresh.test.js +14 -14
- package/cancellation/token.d.ts +6 -0
- package/cancellation/token.js +8 -0
- package/http/client/adapters/undici.adapter.js +0 -2
- package/http/client/http-client-request.d.ts +2 -0
- package/http/client/http-client-request.js +4 -0
- package/http/client/http-client-response.d.ts +1 -1
- package/http/client/http-client-response.js +3 -2
- package/http/utils.d.ts +6 -0
- package/http/utils.js +71 -0
- package/injector/graph.js +27 -6
- package/injector/injector.js +2 -0
- package/mail/drizzle/0000_numerous_the_watchers.sql +8 -0
- package/mail/drizzle/meta/0000_snapshot.json +1 -32
- package/mail/drizzle/meta/_journal.json +2 -9
- package/object-storage/s3/tests/s3.object-storage.integration.test.js +22 -53
- package/orm/tests/repository-expiration.test.js +3 -3
- package/package.json +1 -1
- package/rate-limit/tests/postgres-rate-limiter.test.js +9 -7
- package/task-queue/tests/complex.test.js +22 -22
- package/task-queue/tests/dependencies.test.js +15 -13
- package/task-queue/tests/queue.test.js +13 -13
- package/task-queue/tests/worker.test.js +12 -12
- package/testing/integration-setup.d.ts +2 -0
- package/testing/integration-setup.js +13 -7
- package/utils/backoff.d.ts +27 -3
- package/utils/backoff.js +31 -9
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/retry-with-backoff.d.ts +22 -0
- package/utils/retry-with-backoff.js +64 -0
- package/utils/tests/backoff.test.d.ts +1 -0
- package/utils/tests/backoff.test.js +41 -0
- package/utils/tests/retry-with-backoff.test.d.ts +1 -0
- package/utils/tests/retry-with-backoff.test.js +49 -0
- package/mail/drizzle/0000_previous_malcolm_colcord.sql +0 -13
- package/mail/drizzle/0001_flimsy_bloodscream.sql +0 -5
- package/mail/drizzle/meta/0001_snapshot.json +0 -69
package/utils/backoff.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CancellationSignal } from '../cancellation/token.js';
|
|
2
2
|
export type BackoffStrategy = 'linear' | 'exponential';
|
|
3
|
+
export type JitterStrategy = 'full' | 'proportional';
|
|
3
4
|
/**
|
|
4
5
|
* Configuration for the backoff behavior.
|
|
5
6
|
*/
|
|
@@ -11,6 +12,13 @@ export type BackoffOptions = {
|
|
|
11
12
|
* @default 'exponential'
|
|
12
13
|
*/
|
|
13
14
|
strategy?: BackoffStrategy;
|
|
15
|
+
/**
|
|
16
|
+
* The strategy to use for applying jitter.
|
|
17
|
+
* - `full`: Random value between 0 and the calculated delay.
|
|
18
|
+
* - `proportional`: Random value within `± jitter * delay`.
|
|
19
|
+
* @default 'proportional'
|
|
20
|
+
*/
|
|
21
|
+
jitterStrategy?: JitterStrategy;
|
|
14
22
|
/**
|
|
15
23
|
* The initial delay in milliseconds. Must be non-negative.
|
|
16
24
|
* @default 1000
|
|
@@ -32,7 +40,8 @@ export type BackoffOptions = {
|
|
|
32
40
|
/**
|
|
33
41
|
* A factor to randomize the delay, e.g., 0.1 for 10% jitter.
|
|
34
42
|
* This helps prevent the "thundering herd" problem in distributed systems.
|
|
35
|
-
*
|
|
43
|
+
* For `proportional` jitter, the actual delay will be `delay ± delay * jitter`.
|
|
44
|
+
* For `full` jitter, this value is ignored.
|
|
36
45
|
* Must be a value between 0 and 1.
|
|
37
46
|
* @default 0.15
|
|
38
47
|
*/
|
|
@@ -59,14 +68,20 @@ export type BackoffLoopController = {
|
|
|
59
68
|
/**
|
|
60
69
|
* Schedules a backoff delay before the next iteration of the loop.
|
|
61
70
|
* If this is not called, the backoff delay is reset for the next attempt.
|
|
71
|
+
* @param delay An optional explicit delay in milliseconds to use for the next backoff.
|
|
72
|
+
* If not provided, the backoff strategy is used.
|
|
62
73
|
*/
|
|
63
|
-
backoff: () => void;
|
|
74
|
+
backoff: (delay?: number) => void;
|
|
64
75
|
/**
|
|
65
76
|
* Immediately breaks out of the loop.
|
|
66
77
|
*/
|
|
67
78
|
break: () => void;
|
|
68
79
|
};
|
|
69
|
-
export type
|
|
80
|
+
export type BackoffStatus = {
|
|
81
|
+
attempt: number;
|
|
82
|
+
currentDelay: number;
|
|
83
|
+
};
|
|
84
|
+
export type BackoffLoopFunction = (controller: BackoffLoopController, cancellationSignal: CancellationSignal, status: BackoffStatus) => unknown;
|
|
70
85
|
/**
|
|
71
86
|
* A function yielded by `backoffGenerator` to control the next iteration.
|
|
72
87
|
*/
|
|
@@ -82,6 +97,7 @@ export type BackoffGeneratorCallback = (options?: {
|
|
|
82
97
|
*/
|
|
83
98
|
export declare const DEFAULT_BACKOFF_OPTIONS: {
|
|
84
99
|
readonly strategy: "exponential";
|
|
100
|
+
readonly jitterStrategy: "proportional";
|
|
85
101
|
readonly initialDelay: 1000;
|
|
86
102
|
readonly increase: 2;
|
|
87
103
|
readonly maximumDelay: 30000;
|
|
@@ -93,6 +109,14 @@ export declare const DEFAULT_BACKOFF_OPTIONS: {
|
|
|
93
109
|
export declare class BackoffHelper {
|
|
94
110
|
private readonly options;
|
|
95
111
|
private currentDelay;
|
|
112
|
+
private attempt;
|
|
113
|
+
/**
|
|
114
|
+
* The current status of the backoff.
|
|
115
|
+
*/
|
|
116
|
+
get status(): {
|
|
117
|
+
attempt: number;
|
|
118
|
+
currentDelay: number;
|
|
119
|
+
};
|
|
96
120
|
/**
|
|
97
121
|
* Creates a new BackoffHelper.
|
|
98
122
|
* @param options Partial backoff options, which will be merged with sane defaults.
|
package/utils/backoff.js
CHANGED
|
@@ -8,6 +8,7 @@ import { isDefined } from './type-guards.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export const DEFAULT_BACKOFF_OPTIONS = {
|
|
10
10
|
strategy: 'exponential',
|
|
11
|
+
jitterStrategy: 'proportional',
|
|
11
12
|
initialDelay: 1000,
|
|
12
13
|
increase: 2,
|
|
13
14
|
maximumDelay: 30_000, // 30 seconds
|
|
@@ -19,6 +20,16 @@ export const DEFAULT_BACKOFF_OPTIONS = {
|
|
|
19
20
|
export class BackoffHelper {
|
|
20
21
|
options;
|
|
21
22
|
currentDelay;
|
|
23
|
+
attempt;
|
|
24
|
+
/**
|
|
25
|
+
* The current status of the backoff.
|
|
26
|
+
*/
|
|
27
|
+
get status() {
|
|
28
|
+
return {
|
|
29
|
+
attempt: this.attempt,
|
|
30
|
+
currentDelay: this.currentDelay,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
22
33
|
/**
|
|
23
34
|
* Creates a new BackoffHelper.
|
|
24
35
|
* @param options Partial backoff options, which will be merged with sane defaults.
|
|
@@ -39,12 +50,14 @@ export class BackoffHelper {
|
|
|
39
50
|
console.warn('Using an exponential backoff with an increase factor <= 1 is not recommended as the delay will not grow.');
|
|
40
51
|
}
|
|
41
52
|
this.currentDelay = this.options.initialDelay;
|
|
53
|
+
this.attempt = 0;
|
|
42
54
|
}
|
|
43
55
|
/**
|
|
44
56
|
* Resets the current delay to the initial delay.
|
|
45
57
|
*/
|
|
46
58
|
reset() {
|
|
47
59
|
this.currentDelay = this.options.initialDelay;
|
|
60
|
+
this.attempt = 0;
|
|
48
61
|
}
|
|
49
62
|
/**
|
|
50
63
|
* Calculates and returns the next backoff delay based on the configured strategy.
|
|
@@ -52,13 +65,18 @@ export class BackoffHelper {
|
|
|
52
65
|
* @returns The next delay in milliseconds.
|
|
53
66
|
*/
|
|
54
67
|
getNextDelay() {
|
|
55
|
-
const
|
|
68
|
+
const baseDelay = this.currentDelay;
|
|
69
|
+
const newDelay = getNewDelay(this.options.strategy, baseDelay, this.options.increase, this.options.maximumDelay);
|
|
56
70
|
this.currentDelay = newDelay;
|
|
71
|
+
this.attempt++;
|
|
72
|
+
if (this.options.jitterStrategy == 'full') {
|
|
73
|
+
return randomFloat(0, baseDelay);
|
|
74
|
+
}
|
|
57
75
|
if (this.options.jitter > 0) {
|
|
58
|
-
const jitterAmount =
|
|
59
|
-
return Math.max(0,
|
|
76
|
+
const jitterAmount = baseDelay * randomFloat(-this.options.jitter, this.options.jitter); // ±jitter
|
|
77
|
+
return Math.max(0, baseDelay + jitterAmount);
|
|
60
78
|
}
|
|
61
|
-
return
|
|
79
|
+
return baseDelay;
|
|
62
80
|
}
|
|
63
81
|
}
|
|
64
82
|
/**
|
|
@@ -72,9 +90,9 @@ export class BackoffHelper {
|
|
|
72
90
|
*/
|
|
73
91
|
export async function autoBackoffLoop(loopFunction, options = {}) {
|
|
74
92
|
const { errorHandler, ...loopOptions } = options;
|
|
75
|
-
await backoffLoop(async (controller, signal) => {
|
|
93
|
+
await backoffLoop(async (controller, signal, status) => {
|
|
76
94
|
try {
|
|
77
|
-
await loopFunction(controller, signal);
|
|
95
|
+
await loopFunction(controller, signal, status);
|
|
78
96
|
}
|
|
79
97
|
catch (error) {
|
|
80
98
|
errorHandler?.(error);
|
|
@@ -95,22 +113,26 @@ export async function backoffLoop(loopFunction, options = {}) {
|
|
|
95
113
|
const backoffHelper = new BackoffHelper(backoffOptions);
|
|
96
114
|
const loopToken = new CancellationToken();
|
|
97
115
|
let shouldBackoff = false;
|
|
116
|
+
let customBackoffDelay;
|
|
98
117
|
if (isDefined(cancellationSignal)) {
|
|
99
118
|
loopToken.inherit(cancellationSignal, { set: true, unset: false, complete: false, error: false });
|
|
100
119
|
}
|
|
101
120
|
const controller = {
|
|
102
|
-
backoff: () =>
|
|
121
|
+
backoff: (delay) => {
|
|
122
|
+
shouldBackoff = true;
|
|
123
|
+
customBackoffDelay = delay;
|
|
124
|
+
},
|
|
103
125
|
break: () => loopToken.set(),
|
|
104
126
|
};
|
|
105
127
|
while (loopToken.isUnset) {
|
|
106
|
-
await loopFunction(controller, loopToken.signal);
|
|
128
|
+
await loopFunction(controller, loopToken.signal, backoffHelper.status);
|
|
107
129
|
// Exit immediately if the loop function requested a break.
|
|
108
130
|
if (loopToken.isSet) {
|
|
109
131
|
return;
|
|
110
132
|
}
|
|
111
133
|
if (shouldBackoff) {
|
|
112
134
|
shouldBackoff = false;
|
|
113
|
-
const delay = backoffHelper.getNextDelay();
|
|
135
|
+
const delay = customBackoffDelay ?? backoffHelper.getNextDelay();
|
|
114
136
|
await cancelableTimeout(delay, loopToken.signal);
|
|
115
137
|
}
|
|
116
138
|
else {
|
package/utils/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export * from './random.js';
|
|
|
39
39
|
export * from './reactive-value-to-signal.js';
|
|
40
40
|
export * from './reflection.js';
|
|
41
41
|
export * from './repl.js';
|
|
42
|
+
export * from './retry-with-backoff.js';
|
|
42
43
|
export * from './set.js';
|
|
43
44
|
export * from './singleton.js';
|
|
44
45
|
export * from './sort.js';
|
package/utils/index.js
CHANGED
|
@@ -39,6 +39,7 @@ export * from './random.js';
|
|
|
39
39
|
export * from './reactive-value-to-signal.js';
|
|
40
40
|
export * from './reflection.js';
|
|
41
41
|
export * from './repl.js';
|
|
42
|
+
export * from './retry-with-backoff.js';
|
|
42
43
|
export * from './set.js';
|
|
43
44
|
export * from './singleton.js';
|
|
44
45
|
export * from './sort.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CancellationSignal } from '../cancellation/token.js';
|
|
2
|
+
import { type BackoffOptions } from './backoff.js';
|
|
3
|
+
export type RetryWithBackoffOptions = {
|
|
4
|
+
/** Timeout for each individual attempt in milliseconds. */
|
|
5
|
+
timeout?: number;
|
|
6
|
+
/** Retry configuration. If not provided, it will not retry. */
|
|
7
|
+
retry?: {
|
|
8
|
+
/** Whether to retry when an error occurs. */
|
|
9
|
+
shouldRetry: (error: Error, attempt: number) => boolean | Promise<boolean>;
|
|
10
|
+
/** Backoff configuration for retries. */
|
|
11
|
+
backoff?: BackoffOptions;
|
|
12
|
+
};
|
|
13
|
+
/** Optional cancellation signal to abort the execution. */
|
|
14
|
+
signal?: CancellationSignal;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Executes a function with a specified policy including timeout and retries with backoff.
|
|
18
|
+
* @param func The function to execute. It receives a cancellation signal that is set when the attempt times out or is cancelled.
|
|
19
|
+
* @param options The retry options.
|
|
20
|
+
* @returns The result of the function.
|
|
21
|
+
*/
|
|
22
|
+
export declare function retryWithBackoff<T>(func: (signal: CancellationSignal) => T | Promise<T>, options?: RetryWithBackoffOptions): Promise<T>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { HttpError } from '../http/http.error.js';
|
|
2
|
+
import { isDefined, isInstanceOf, isNumber, isString } from '../utils/type-guards.js';
|
|
3
|
+
import { backoffLoop } from './backoff.js';
|
|
4
|
+
import { withTimeout } from './timing.js';
|
|
5
|
+
/**
|
|
6
|
+
* Executes a function with a specified policy including timeout and retries with backoff.
|
|
7
|
+
* @param func The function to execute. It receives a cancellation signal that is set when the attempt times out or is cancelled.
|
|
8
|
+
* @param options The retry options.
|
|
9
|
+
* @returns The result of the function.
|
|
10
|
+
*/
|
|
11
|
+
export async function retryWithBackoff(func, options = {}) {
|
|
12
|
+
const { timeout, retry, signal } = options;
|
|
13
|
+
let result;
|
|
14
|
+
let lastError;
|
|
15
|
+
await backoffLoop(async (controller, loopSignal, status) => {
|
|
16
|
+
try {
|
|
17
|
+
const attemptSignal = isDefined(signal)
|
|
18
|
+
? signal.createChild().inherit(loopSignal)
|
|
19
|
+
: loopSignal;
|
|
20
|
+
if (isDefined(timeout)) {
|
|
21
|
+
result = await withTimeout(timeout, async () => await func(attemptSignal));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
result = await func(attemptSignal);
|
|
25
|
+
}
|
|
26
|
+
controller.break();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
lastError = error;
|
|
30
|
+
if (isDefined(retry) && await retry.shouldRetry(lastError, status.attempt)) {
|
|
31
|
+
const retryAfter = tryGetRetryAfterDelay(lastError);
|
|
32
|
+
controller.backoff(retryAfter);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, {
|
|
39
|
+
...retry?.backoff,
|
|
40
|
+
cancellationSignal: signal,
|
|
41
|
+
});
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
function tryGetRetryAfterDelay(error) {
|
|
45
|
+
if (isInstanceOf(error, HttpError)) {
|
|
46
|
+
const retryAfter = error.responseInstance?.headers.tryGetSingle('Retry-After');
|
|
47
|
+
if (isDefined(retryAfter)) {
|
|
48
|
+
if (isNumber(retryAfter)) {
|
|
49
|
+
return retryAfter * 1000;
|
|
50
|
+
}
|
|
51
|
+
if (isString(retryAfter)) {
|
|
52
|
+
const seconds = Number.parseInt(retryAfter, 10);
|
|
53
|
+
if (!Number.isNaN(seconds)) {
|
|
54
|
+
return seconds * 1000;
|
|
55
|
+
}
|
|
56
|
+
const date = Date.parse(retryAfter);
|
|
57
|
+
if (!Number.isNaN(date)) {
|
|
58
|
+
return Math.max(0, date - Date.now());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { BackoffHelper } from '../backoff.js';
|
|
3
|
+
describe('BackoffHelper', () => {
|
|
4
|
+
it('should support full jitter', () => {
|
|
5
|
+
const helper = new BackoffHelper({
|
|
6
|
+
initialDelay: 1000,
|
|
7
|
+
jitter: 1,
|
|
8
|
+
jitterStrategy: 'full',
|
|
9
|
+
});
|
|
10
|
+
for (let i = 0; i < 100; i++) {
|
|
11
|
+
const delay = helper.getNextDelay();
|
|
12
|
+
// Full jitter should be between 0 and the base delay (1000 for first call, then 2000, 4000, etc.)
|
|
13
|
+
expect(delay).toBeGreaterThanOrEqual(0);
|
|
14
|
+
expect(delay).toBeLessThanOrEqual(1000 * Math.pow(2, i));
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
it('should support proportional jitter (default)', () => {
|
|
18
|
+
const helper = new BackoffHelper({
|
|
19
|
+
initialDelay: 1000,
|
|
20
|
+
jitter: 0.1,
|
|
21
|
+
jitterStrategy: 'proportional',
|
|
22
|
+
});
|
|
23
|
+
for (let i = 0; i < 1; i++) {
|
|
24
|
+
const delay = helper.getNextDelay();
|
|
25
|
+
// Proportional jitter should be 1000 ± 100 (900 to 1100)
|
|
26
|
+
expect(delay).toBeGreaterThanOrEqual(900);
|
|
27
|
+
expect(delay).toBeLessThanOrEqual(1100);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
it('should provide status emission', () => {
|
|
31
|
+
const helper = new BackoffHelper({ initialDelay: 1000 });
|
|
32
|
+
expect(helper.status.attempt).toBe(0);
|
|
33
|
+
expect(helper.status.currentDelay).toBe(1000);
|
|
34
|
+
helper.getNextDelay();
|
|
35
|
+
expect(helper.status.attempt).toBe(1);
|
|
36
|
+
expect(helper.status.currentDelay).toBe(2000);
|
|
37
|
+
helper.reset();
|
|
38
|
+
expect(helper.status.attempt).toBe(0);
|
|
39
|
+
expect(helper.status.currentDelay).toBe(1000);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TimeoutError } from '../../errors/timeout.error.js';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { retryWithBackoff } from '../retry-with-backoff.js';
|
|
4
|
+
describe('retryWithBackoff', () => {
|
|
5
|
+
it('should return result on success', async () => {
|
|
6
|
+
const result = await retryWithBackoff(async () => 'success');
|
|
7
|
+
expect(result).toBe('success');
|
|
8
|
+
});
|
|
9
|
+
it('should retry on failure if shouldRetry returns true', async () => {
|
|
10
|
+
let attempts = 0;
|
|
11
|
+
const result = await retryWithBackoff(async () => {
|
|
12
|
+
attempts++;
|
|
13
|
+
if (attempts < 3) {
|
|
14
|
+
throw new Error('fail');
|
|
15
|
+
}
|
|
16
|
+
return 'success';
|
|
17
|
+
}, {
|
|
18
|
+
retry: {
|
|
19
|
+
shouldRetry: (_error, attempt) => attempt < 5,
|
|
20
|
+
backoff: { initialDelay: 0 },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
expect(result).toBe('success');
|
|
24
|
+
expect(attempts).toBe(3);
|
|
25
|
+
});
|
|
26
|
+
it('should throw after max retries', async () => {
|
|
27
|
+
let attempts = 0;
|
|
28
|
+
const execute = retryWithBackoff(async () => {
|
|
29
|
+
attempts++;
|
|
30
|
+
throw new Error('fail');
|
|
31
|
+
}, {
|
|
32
|
+
retry: {
|
|
33
|
+
shouldRetry: (_error, attempt) => attempt < 2,
|
|
34
|
+
backoff: { initialDelay: 0 },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
await expect(execute).rejects.toThrow('fail');
|
|
38
|
+
expect(attempts).toBe(3); // 0, 1, 2
|
|
39
|
+
});
|
|
40
|
+
it('should timeout if execution takes too long', async () => {
|
|
41
|
+
const execute = retryWithBackoff(async () => {
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
43
|
+
return 'success';
|
|
44
|
+
}, {
|
|
45
|
+
timeout: 50,
|
|
46
|
+
});
|
|
47
|
+
await expect(execute).rejects.toThrow(TimeoutError);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
CREATE TABLE "mail"."log" (
|
|
2
|
-
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
-
"timestamp" timestamp with time zone NOT NULL,
|
|
4
|
-
"template" text,
|
|
5
|
-
"data" jsonb NOT NULL,
|
|
6
|
-
"send_result" jsonb,
|
|
7
|
-
"errors" text[],
|
|
8
|
-
"revision" integer NOT NULL,
|
|
9
|
-
"revision_timestamp" timestamp with time zone NOT NULL,
|
|
10
|
-
"create_timestamp" timestamp with time zone NOT NULL,
|
|
11
|
-
"delete_timestamp" timestamp with time zone,
|
|
12
|
-
"attributes" jsonb DEFAULT '{}'::jsonb NOT NULL
|
|
13
|
-
);
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
ALTER TABLE "mail"."log" DROP COLUMN "revision";--> statement-breakpoint
|
|
2
|
-
ALTER TABLE "mail"."log" DROP COLUMN "revision_timestamp";--> statement-breakpoint
|
|
3
|
-
ALTER TABLE "mail"."log" DROP COLUMN "create_timestamp";--> statement-breakpoint
|
|
4
|
-
ALTER TABLE "mail"."log" DROP COLUMN "delete_timestamp";--> statement-breakpoint
|
|
5
|
-
ALTER TABLE "mail"."log" DROP COLUMN "attributes";
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "28ddafef-64f4-437d-9406-82a02c76570e",
|
|
3
|
-
"prevId": "f8cdba37-11b9-477a-9f5a-5ef4b5026011",
|
|
4
|
-
"version": "7",
|
|
5
|
-
"dialect": "postgresql",
|
|
6
|
-
"tables": {
|
|
7
|
-
"mail.log": {
|
|
8
|
-
"name": "log",
|
|
9
|
-
"schema": "mail",
|
|
10
|
-
"columns": {
|
|
11
|
-
"id": {
|
|
12
|
-
"name": "id",
|
|
13
|
-
"type": "uuid",
|
|
14
|
-
"primaryKey": true,
|
|
15
|
-
"notNull": true,
|
|
16
|
-
"default": "gen_random_uuid()"
|
|
17
|
-
},
|
|
18
|
-
"timestamp": {
|
|
19
|
-
"name": "timestamp",
|
|
20
|
-
"type": "timestamp with time zone",
|
|
21
|
-
"primaryKey": false,
|
|
22
|
-
"notNull": true
|
|
23
|
-
},
|
|
24
|
-
"template": {
|
|
25
|
-
"name": "template",
|
|
26
|
-
"type": "text",
|
|
27
|
-
"primaryKey": false,
|
|
28
|
-
"notNull": false
|
|
29
|
-
},
|
|
30
|
-
"data": {
|
|
31
|
-
"name": "data",
|
|
32
|
-
"type": "jsonb",
|
|
33
|
-
"primaryKey": false,
|
|
34
|
-
"notNull": true
|
|
35
|
-
},
|
|
36
|
-
"send_result": {
|
|
37
|
-
"name": "send_result",
|
|
38
|
-
"type": "jsonb",
|
|
39
|
-
"primaryKey": false,
|
|
40
|
-
"notNull": false
|
|
41
|
-
},
|
|
42
|
-
"errors": {
|
|
43
|
-
"name": "errors",
|
|
44
|
-
"type": "text[]",
|
|
45
|
-
"primaryKey": false,
|
|
46
|
-
"notNull": false
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
"indexes": {},
|
|
50
|
-
"foreignKeys": {},
|
|
51
|
-
"compositePrimaryKeys": {},
|
|
52
|
-
"uniqueConstraints": {},
|
|
53
|
-
"policies": {},
|
|
54
|
-
"checkConstraints": {},
|
|
55
|
-
"isRLSEnabled": false
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
"enums": {},
|
|
59
|
-
"schemas": {},
|
|
60
|
-
"sequences": {},
|
|
61
|
-
"roles": {},
|
|
62
|
-
"policies": {},
|
|
63
|
-
"views": {},
|
|
64
|
-
"_meta": {
|
|
65
|
-
"columns": {},
|
|
66
|
-
"schemas": {},
|
|
67
|
-
"tables": {}
|
|
68
|
-
}
|
|
69
|
-
}
|