@atproto/ozone 0.1.68 → 0.1.70
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/CHANGELOG.md +16 -0
- package/dist/api/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/api/moderation/queryStatuses.js +1 -33
- package/dist/api/moderation/queryStatuses.js.map +1 -1
- package/dist/background.d.ts +49 -6
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +149 -14
- package/dist/background.js.map +1 -1
- package/dist/config/config.d.ts +1 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +1 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +1 -0
- package/dist/config/env.js.map +1 -1
- package/dist/daemon/context.d.ts +9 -3
- package/dist/daemon/context.d.ts.map +1 -1
- package/dist/daemon/context.js +33 -3
- package/dist/daemon/context.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +3 -6
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/materialized-view-refresher.d.ts +5 -0
- package/dist/daemon/materialized-view-refresher.d.ts.map +1 -0
- package/dist/daemon/materialized-view-refresher.js +29 -0
- package/dist/daemon/materialized-view-refresher.js.map +1 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts +5 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.d.ts.map +1 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.js +158 -0
- package/dist/db/migrations/20241220T144630860Z-stats-materialized-views.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/account_events_stats.d.ts +15 -0
- package/dist/db/schema/account_events_stats.d.ts.map +1 -0
- package/dist/db/schema/account_events_stats.js +5 -0
- package/dist/db/schema/account_events_stats.js.map +1 -0
- package/dist/db/schema/account_record_events_stats.d.ts +15 -0
- package/dist/db/schema/account_record_events_stats.d.ts.map +1 -0
- package/dist/db/schema/account_record_events_stats.js +5 -0
- package/dist/db/schema/account_record_events_stats.js.map +1 -0
- package/dist/db/schema/account_record_status_stats.d.ts +15 -0
- package/dist/db/schema/account_record_status_stats.d.ts.map +1 -0
- package/dist/db/schema/account_record_status_stats.js +5 -0
- package/dist/db/schema/account_record_status_stats.js.map +1 -0
- package/dist/db/schema/index.d.ts +5 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/record_events_stats.d.ts +14 -0
- package/dist/db/schema/record_events_stats.d.ts.map +1 -0
- package/dist/db/schema/record_events_stats.js +5 -0
- package/dist/db/schema/record_events_stats.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +2 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +2 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +230 -2
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +126 -1
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +5 -0
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js +5 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/generator.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/feed/generator.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/generator.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +40 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js +20 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +7 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/mod-service/index.d.ts +4 -62
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +80 -74
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/status.d.ts +115 -4
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/status.js +51 -34
- package/dist/mod-service/status.js.map +1 -1
- package/dist/mod-service/types.d.ts +16 -1
- package/dist/mod-service/types.d.ts.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +49 -41
- package/dist/mod-service/views.js.map +1 -1
- package/dist/util.d.ts +34 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +132 -0
- package/dist/util.js.map +1 -1
- package/package.json +3 -3
- package/src/api/moderation/queryStatuses.ts +1 -63
- package/src/background.ts +140 -14
- package/src/config/config.ts +2 -0
- package/src/config/env.ts +4 -0
- package/src/daemon/context.ts +43 -5
- package/src/daemon/index.ts +3 -6
- package/src/daemon/materialized-view-refresher.ts +27 -0
- package/src/db/migrations/20241220T144630860Z-stats-materialized-views.ts +218 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/account_events_stats.ts +16 -0
- package/src/db/schema/account_record_events_stats.ts +15 -0
- package/src/db/schema/account_record_status_stats.ts +15 -0
- package/src/db/schema/index.ts +10 -1
- package/src/db/schema/record_events_stats.ts +15 -0
- package/src/index.ts +1 -7
- package/src/lexicon/index.ts +2 -0
- package/src/lexicon/lexicons.ts +138 -1
- package/src/lexicon/types/app/bsky/feed/defs.ts +9 -0
- package/src/lexicon/types/app/bsky/feed/generator.ts +4 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +62 -0
- package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +11 -1
- package/src/mod-service/index.ts +181 -118
- package/src/mod-service/status.ts +55 -28
- package/src/mod-service/types.ts +22 -1
- package/src/mod-service/views.ts +64 -50
- package/src/util.ts +145 -0
- package/tests/__snapshots__/get-record.test.ts.snap +28 -0
- package/tests/__snapshots__/get-records.test.ts.snap +14 -0
- package/tests/__snapshots__/get-repo.test.ts.snap +11 -0
- package/tests/__snapshots__/get-repos.test.ts.snap +11 -0
- package/tests/__snapshots__/moderation-events.test.ts.snap +19 -0
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +114 -0
- package/tests/get-record.test.ts +4 -0
- package/tests/get-records.test.ts +4 -0
- package/tests/get-repo.test.ts +4 -0
- package/tests/get-repos.test.ts +4 -0
- package/tests/moderation-events.test.ts +4 -0
- package/tests/moderation-statuses.test.ts +4 -0
- package/tests/query-labels.test.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/util.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.formatLabelerHeader = exports.defaultLabelerHeader = exports.parseLabelerHeader = exports.LABELER_HEADER_NAME = exports.retryHttp = exports.RETRYABLE_HTTP_STATUS_CODES = exports.getSigningKeyId = void 0;
|
|
7
|
+
exports.startInterval = startInterval;
|
|
8
|
+
exports.isCausedBySignal = isCausedBySignal;
|
|
9
|
+
exports.boundAbortController = boundAbortController;
|
|
10
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
4
11
|
const common_1 = require("@atproto/common");
|
|
5
12
|
const xrpc_1 = require("@atproto/xrpc");
|
|
6
13
|
const structured_headers_1 = require("structured-headers");
|
|
@@ -71,4 +78,129 @@ const formatLabelerHeader = (parsed) => {
|
|
|
71
78
|
return parts.join(',');
|
|
72
79
|
};
|
|
73
80
|
exports.formatLabelerHeader = formatLabelerHeader;
|
|
81
|
+
/**
|
|
82
|
+
* Utility function similar to `setInterval()`. The main difference is that the
|
|
83
|
+
* execution is controlled through a signal and that the function will wait for
|
|
84
|
+
* `interval` milliseconds *between* the end of the previous execution and the
|
|
85
|
+
* start of the next one (instead of starting the execution every `interval`
|
|
86
|
+
* milliseconds), ensuring that the function is not running concurrently.
|
|
87
|
+
*
|
|
88
|
+
* @param fn The function to execute. That function must not throw any error
|
|
89
|
+
* other than {@link signal}'s {@link AbortSignal.reason} or an {@link Error}
|
|
90
|
+
* that has the {@link signal}'s {@link AbortSignal.reason} as its
|
|
91
|
+
* {@link Error.cause}.
|
|
92
|
+
*
|
|
93
|
+
* @returns A promise that resolves when the signal is aborted, and the last
|
|
94
|
+
* execution is done.
|
|
95
|
+
*
|
|
96
|
+
* @throws {AbortSignal['reason']} if the {@link signal} is already aborted.
|
|
97
|
+
* @throws {TypeError} if {@link fn} throws an unexpected error (with the
|
|
98
|
+
* unexpected error as the {@link Error.cause}).
|
|
99
|
+
*/
|
|
100
|
+
async function startInterval(fn, interval, signal, runImmediately = false) {
|
|
101
|
+
signal.throwIfAborted();
|
|
102
|
+
// Renaming for clarity
|
|
103
|
+
const inputSignal = signal;
|
|
104
|
+
const intervalController = new AbortController();
|
|
105
|
+
const intervalSignal = intervalController.signal;
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
let timer;
|
|
108
|
+
const run = async () => {
|
|
109
|
+
// Cloning the signal for this particular run to prevent memory leaks
|
|
110
|
+
const runController = boundAbortController(intervalSignal);
|
|
111
|
+
const runSignal = runController.signal;
|
|
112
|
+
try {
|
|
113
|
+
await fn(runSignal);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err != null && isCausedBySignal(err, runSignal)) {
|
|
117
|
+
// Silently ignore the error if it is caused by the signal. At this
|
|
118
|
+
// point, the interval controller was aborted, which will cause the
|
|
119
|
+
// promise to resolve in the "finally" block bellow.
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Invalid behavior: stop the interval and reject the promise.
|
|
123
|
+
const error = new TypeError('Unexpected error', { cause: err });
|
|
124
|
+
// Rejecting here will make `resolve()` in the "finally" block to be a
|
|
125
|
+
// no-op. Rejecting before aborting the controller to ensure the
|
|
126
|
+
// promise does not get resolved by the `abort` event listeners.
|
|
127
|
+
reject(error);
|
|
128
|
+
// Using `error` as abort reason to avoid creating an AbortError.
|
|
129
|
+
intervalController.abort(error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
// Cleanup the listeners added by `boundAbortController`
|
|
134
|
+
runController.abort();
|
|
135
|
+
if (intervalSignal.aborted)
|
|
136
|
+
resolve();
|
|
137
|
+
else
|
|
138
|
+
schedule();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const schedule = () => {
|
|
142
|
+
(0, node_assert_1.default)(timer === undefined, 'unexpected state');
|
|
143
|
+
timer = setTimeout(() => {
|
|
144
|
+
timer = undefined; // "running" state
|
|
145
|
+
void run();
|
|
146
|
+
}, interval);
|
|
147
|
+
};
|
|
148
|
+
inputSignal.addEventListener('abort',
|
|
149
|
+
// This function will only be called if the `inputSignal` is aborted
|
|
150
|
+
// before the interval controller is aborted.
|
|
151
|
+
() => {
|
|
152
|
+
// Stop the interval, using the input signal's reason
|
|
153
|
+
intervalController.abort(inputSignal.reason);
|
|
154
|
+
if (timer === undefined) {
|
|
155
|
+
// `fn` is currently running; `run`'s finally block will resolve the
|
|
156
|
+
// promise.
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// The execution was scheduled but not started yet. Clear the timer
|
|
160
|
+
// and resolve the promise.
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
resolve();
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
// Remove the listener whenever the interval is aborted.
|
|
166
|
+
{ signal: intervalSignal });
|
|
167
|
+
if (runImmediately)
|
|
168
|
+
void run();
|
|
169
|
+
else
|
|
170
|
+
schedule();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Determines whether the cause of an error is a signal's reason
|
|
175
|
+
*/
|
|
176
|
+
function isCausedBySignal(err, signal) {
|
|
177
|
+
if (!signal.aborted)
|
|
178
|
+
return false;
|
|
179
|
+
if (signal.reason == null)
|
|
180
|
+
return false; // Ignore nullish reasons
|
|
181
|
+
return (err === signal.reason ||
|
|
182
|
+
(err instanceof Error && err.cause === signal.reason));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Creates an AbortController that will be aborted when any of the given signals
|
|
186
|
+
* is aborted.
|
|
187
|
+
*
|
|
188
|
+
* @note Make sure to call `abortController.abort()` when you are done with
|
|
189
|
+
* the controller to avoid memory leaks.
|
|
190
|
+
*
|
|
191
|
+
* @throws if any of the input signals is already aborted.
|
|
192
|
+
*/
|
|
193
|
+
function boundAbortController(...signals) {
|
|
194
|
+
for (const signal of signals) {
|
|
195
|
+
signal?.throwIfAborted();
|
|
196
|
+
}
|
|
197
|
+
const abortController = new AbortController();
|
|
198
|
+
const abort = function (event) {
|
|
199
|
+
abortController.abort(event.target?.reason);
|
|
200
|
+
};
|
|
201
|
+
for (const signal of signals) {
|
|
202
|
+
signal?.addEventListener('abort', abort, { signal: abortController.signal });
|
|
203
|
+
}
|
|
204
|
+
return abortController;
|
|
205
|
+
}
|
|
74
206
|
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/ozone",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.70",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Backend service for moderating the Bluesky network.",
|
|
6
6
|
"keywords": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"typed-emitter": "^2.1.0",
|
|
33
33
|
"uint8arrays": "3.0.0",
|
|
34
34
|
"undici": "^6.14.1",
|
|
35
|
-
"@atproto/api": "^0.13.
|
|
35
|
+
"@atproto/api": "^0.13.29",
|
|
36
36
|
"@atproto/common": "^0.4.6",
|
|
37
37
|
"@atproto/crypto": "^0.4.3",
|
|
38
38
|
"@atproto/identity": "^0.4.5",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"ts-node": "^10.8.2",
|
|
53
53
|
"typescript": "^5.6.3",
|
|
54
54
|
"@atproto/lex-cli": "^0.5.5",
|
|
55
|
-
"@atproto/pds": "^0.4.
|
|
55
|
+
"@atproto/pds": "^0.4.87"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*",
|
|
@@ -1,75 +1,13 @@
|
|
|
1
1
|
import { Server } from '../../lexicon'
|
|
2
2
|
import AppContext from '../../context'
|
|
3
|
-
import { getReviewState } from '../util'
|
|
4
3
|
|
|
5
4
|
export default function (server: Server, ctx: AppContext) {
|
|
6
5
|
server.tools.ozone.moderation.queryStatuses({
|
|
7
6
|
auth: ctx.authVerifier.modOrAdminToken,
|
|
8
7
|
handler: async ({ params }) => {
|
|
9
|
-
const {
|
|
10
|
-
includeAllUserRecords,
|
|
11
|
-
subject,
|
|
12
|
-
takendown,
|
|
13
|
-
appealed,
|
|
14
|
-
reviewState,
|
|
15
|
-
reviewedAfter,
|
|
16
|
-
reviewedBefore,
|
|
17
|
-
reportedAfter,
|
|
18
|
-
reportedBefore,
|
|
19
|
-
ignoreSubjects,
|
|
20
|
-
lastReviewedBy,
|
|
21
|
-
hostingDeletedBefore,
|
|
22
|
-
hostingDeletedAfter,
|
|
23
|
-
hostingUpdatedBefore,
|
|
24
|
-
hostingUpdatedAfter,
|
|
25
|
-
hostingStatuses,
|
|
26
|
-
sortDirection = 'desc',
|
|
27
|
-
sortField = 'lastReportedAt',
|
|
28
|
-
includeMuted = false,
|
|
29
|
-
onlyMuted = false,
|
|
30
|
-
limit = 50,
|
|
31
|
-
cursor,
|
|
32
|
-
tags = [],
|
|
33
|
-
excludeTags = [],
|
|
34
|
-
collections = [],
|
|
35
|
-
subjectType,
|
|
36
|
-
queueCount,
|
|
37
|
-
queueIndex,
|
|
38
|
-
queueSeed,
|
|
39
|
-
} = params
|
|
40
8
|
const db = ctx.db
|
|
41
9
|
const modService = ctx.modService(db)
|
|
42
|
-
const results = await modService.getSubjectStatuses(
|
|
43
|
-
reviewState: getReviewState(reviewState),
|
|
44
|
-
includeAllUserRecords,
|
|
45
|
-
subject,
|
|
46
|
-
takendown,
|
|
47
|
-
appealed,
|
|
48
|
-
reviewedAfter,
|
|
49
|
-
reviewedBefore,
|
|
50
|
-
reportedAfter,
|
|
51
|
-
reportedBefore,
|
|
52
|
-
includeMuted,
|
|
53
|
-
hostingDeletedBefore,
|
|
54
|
-
hostingDeletedAfter,
|
|
55
|
-
hostingUpdatedBefore,
|
|
56
|
-
hostingUpdatedAfter,
|
|
57
|
-
hostingStatuses,
|
|
58
|
-
onlyMuted,
|
|
59
|
-
ignoreSubjects,
|
|
60
|
-
sortDirection,
|
|
61
|
-
lastReviewedBy,
|
|
62
|
-
sortField,
|
|
63
|
-
limit,
|
|
64
|
-
cursor,
|
|
65
|
-
tags,
|
|
66
|
-
excludeTags,
|
|
67
|
-
collections,
|
|
68
|
-
subjectType,
|
|
69
|
-
queueCount,
|
|
70
|
-
queueIndex,
|
|
71
|
-
queueSeed,
|
|
72
|
-
})
|
|
10
|
+
const results = await modService.getSubjectStatuses(params)
|
|
73
11
|
const subjectStatuses = results.statuses.map((status) =>
|
|
74
12
|
modService.views.formatSubjectStatus(status),
|
|
75
13
|
)
|
package/src/background.ts
CHANGED
|
@@ -1,35 +1,161 @@
|
|
|
1
1
|
import PQueue from 'p-queue'
|
|
2
2
|
import { Database } from './db'
|
|
3
3
|
import { dbLogger } from './logger'
|
|
4
|
+
import { boundAbortController, isCausedBySignal, startInterval } from './util'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
type Task = (db: Database, signal: AbortSignal) => Promise<void>
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* A simple queue for in-process, out-of-band/backgrounded work
|
|
10
|
+
*/
|
|
7
11
|
export class BackgroundQueue {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
constructor(public db: Database) {}
|
|
12
|
+
private abortController = new AbortController()
|
|
13
|
+
private queue = new PQueue({ concurrency: 20 })
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
public get signal() {
|
|
16
|
+
return this.abortController.signal
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public get destroyed() {
|
|
20
|
+
return this.signal.aborted
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor(protected db: Database) {}
|
|
24
|
+
|
|
25
|
+
getStats() {
|
|
26
|
+
return {
|
|
27
|
+
runningCount: this.queue.pending,
|
|
28
|
+
waitingCount: this.queue.size,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Add a task that will be executed at some point in the future.
|
|
34
|
+
*
|
|
35
|
+
* The task will be executed even if the backgroundQueue is destroyed, unless
|
|
36
|
+
* the provided `signal` is aborted.
|
|
37
|
+
*
|
|
38
|
+
* The `signal` provided to the task will be aborted whenever either the
|
|
39
|
+
* backgroundQueue is destroyed or the provided `signal` is aborted.
|
|
40
|
+
*/
|
|
41
|
+
async add(task: Task, signal?: AbortSignal): Promise<void> {
|
|
13
42
|
if (this.destroyed) {
|
|
14
43
|
return
|
|
15
44
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
45
|
+
|
|
46
|
+
const abortController = boundAbortController(this.signal, signal)
|
|
47
|
+
|
|
48
|
+
return this.queue.add<void>(async () => {
|
|
49
|
+
try {
|
|
50
|
+
// Do not run the task if the signal provided to the task has become
|
|
51
|
+
// aborted. Do not use `abortController.signal` here since we do not
|
|
52
|
+
// want to abort the task if the backgroundQueue is being destroyed.
|
|
53
|
+
if (signal?.aborted) return
|
|
54
|
+
|
|
55
|
+
// The task will receive a "combined signal" allowing it to abort if
|
|
56
|
+
// either the backgroundQueue is destroyed or the provided signal is
|
|
57
|
+
// aborted.
|
|
58
|
+
await task(this.db, abortController.signal)
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (!isCausedBySignal(err, abortController.signal)) {
|
|
61
|
+
dbLogger.error(err, 'background queue task failed')
|
|
62
|
+
}
|
|
63
|
+
} finally {
|
|
64
|
+
abortController.abort()
|
|
65
|
+
}
|
|
66
|
+
})
|
|
21
67
|
}
|
|
22
68
|
|
|
23
69
|
async processAll() {
|
|
24
70
|
await this.queue.onIdle()
|
|
25
71
|
}
|
|
26
72
|
|
|
27
|
-
|
|
28
|
-
|
|
73
|
+
/**
|
|
74
|
+
* On destroy we stop accepting new tasks, but complete all
|
|
75
|
+
* pending/in-progress tasks. Tasks can decide to abort their current
|
|
76
|
+
* operation based on the signal they received. The application calls this
|
|
77
|
+
* only once http connections have drained (tasks no longer being added).
|
|
78
|
+
*/
|
|
29
79
|
async destroy() {
|
|
30
|
-
this.
|
|
80
|
+
this.abortController.abort()
|
|
31
81
|
await this.queue.onIdle()
|
|
32
82
|
}
|
|
33
83
|
}
|
|
34
84
|
|
|
35
|
-
|
|
85
|
+
/**
|
|
86
|
+
* A simple periodic background task runner. This class will schedule a task to
|
|
87
|
+
* run through a provided {@link BackgroundQueue} at a fixed interval. The task
|
|
88
|
+
* will never run more than once concurrently, and will wait at least `interval`
|
|
89
|
+
* milliseconds between the end of one run and the start of the next.
|
|
90
|
+
*/
|
|
91
|
+
export class PeriodicBackgroundTask {
|
|
92
|
+
private abortController: AbortController
|
|
93
|
+
|
|
94
|
+
private intervalPromise?: Promise<void>
|
|
95
|
+
private runningPromise?: Promise<void>
|
|
96
|
+
|
|
97
|
+
public get signal() {
|
|
98
|
+
return this.abortController.signal
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public get destroyed() {
|
|
102
|
+
return this.signal.aborted
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
constructor(
|
|
106
|
+
protected backgroundQueue: BackgroundQueue,
|
|
107
|
+
protected interval: number,
|
|
108
|
+
protected task: Task,
|
|
109
|
+
) {
|
|
110
|
+
if (!Number.isFinite(interval) || interval <= 0) {
|
|
111
|
+
throw new TypeError('interval must be a positive number')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Bind this class's signal to the backgroundQueue's signal (destroying this
|
|
115
|
+
// instance if the backgroundQueue is destroyed)
|
|
116
|
+
this.abortController = boundAbortController(backgroundQueue.signal)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public run(signal?: AbortSignal): Promise<void> {
|
|
120
|
+
// `startInterval` already ensures that only one run is in progress at a
|
|
121
|
+
// time. However, we want to be able to expose a `run()` method that can be
|
|
122
|
+
// used to force a run, which could cause concurrent executions. We prevent
|
|
123
|
+
// this using the `runningPromise` property.
|
|
124
|
+
|
|
125
|
+
if (this.runningPromise) return this.runningPromise
|
|
126
|
+
|
|
127
|
+
// Combine the `this.signal` with the provided `signal`, if any.
|
|
128
|
+
const abortController = boundAbortController(this.signal, signal)
|
|
129
|
+
|
|
130
|
+
const promise = this.backgroundQueue.add(this.task, abortController.signal)
|
|
131
|
+
|
|
132
|
+
return (this.runningPromise = promise).finally(() => {
|
|
133
|
+
if (this.runningPromise === promise) this.runningPromise = undefined
|
|
134
|
+
|
|
135
|
+
// Cleanup the listeners added by `boundAbortController`
|
|
136
|
+
abortController.abort()
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public start() {
|
|
141
|
+
// Noop if already started. Throws if this.signal is aborted (instance is
|
|
142
|
+
// destroyed).
|
|
143
|
+
this.intervalPromise ||= startInterval(
|
|
144
|
+
async (signal) => this.run(signal),
|
|
145
|
+
this.interval,
|
|
146
|
+
this.signal,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public async destroy() {
|
|
151
|
+
// @NOTE This instance does not "own" the backgroundQueue, so we do not
|
|
152
|
+
// destroy it here.
|
|
153
|
+
|
|
154
|
+
this.abortController.abort()
|
|
155
|
+
console.error('ABOOOORT')
|
|
156
|
+
|
|
157
|
+
await this.intervalPromise
|
|
158
|
+
this.intervalPromise = undefined
|
|
159
|
+
console.error('DONE -_-')
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/config/config.ts
CHANGED
|
@@ -24,6 +24,7 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
|
|
|
24
24
|
poolSize: env.dbPoolSize,
|
|
25
25
|
poolMaxUses: env.dbPoolMaxUses,
|
|
26
26
|
poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs,
|
|
27
|
+
materializedViewRefreshIntervalMs: env.dbMaterializedViewRefreshIntervalMs,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
assert(env.appviewUrl, 'appviewUrl is required')
|
|
@@ -122,6 +123,7 @@ export type DatabaseConfig = {
|
|
|
122
123
|
poolSize?: number
|
|
123
124
|
poolMaxUses?: number
|
|
124
125
|
poolIdleTimeoutMs?: number
|
|
126
|
+
materializedViewRefreshIntervalMs?: number
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
export type AppviewConfig = {
|
package/src/config/env.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const readEnv = (): OzoneEnvironment => {
|
|
|
20
20
|
dbPoolSize: envInt('OZONE_DB_POOL_SIZE'),
|
|
21
21
|
dbPoolMaxUses: envInt('OZONE_DB_POOL_MAX_USES'),
|
|
22
22
|
dbPoolIdleTimeoutMs: envInt('OZONE_DB_POOL_IDLE_TIMEOUT_MS'),
|
|
23
|
+
dbMaterializedViewRefreshIntervalMs: envInt(
|
|
24
|
+
'OZONE_DB_MATERIALIZED_VIEW_REFRESH_INTERVAL_MS',
|
|
25
|
+
),
|
|
23
26
|
didPlcUrl: envStr('OZONE_DID_PLC_URL'),
|
|
24
27
|
didCacheStaleTTL: envInt('OZONE_DID_CACHE_STALE_TTL'),
|
|
25
28
|
didCacheMaxTTL: envInt('OZONE_DID_CACHE_MAX_TTL'),
|
|
@@ -53,6 +56,7 @@ export type OzoneEnvironment = {
|
|
|
53
56
|
dbPoolSize?: number
|
|
54
57
|
dbPoolMaxUses?: number
|
|
55
58
|
dbPoolIdleTimeoutMs?: number
|
|
59
|
+
dbMaterializedViewRefreshIntervalMs?: number
|
|
56
60
|
didPlcUrl?: string
|
|
57
61
|
didCacheStaleTTL?: number
|
|
58
62
|
didCacheMaxTTL?: number
|
package/src/daemon/context.ts
CHANGED
|
@@ -6,17 +6,20 @@ import { OzoneConfig, OzoneSecrets } from '../config'
|
|
|
6
6
|
import { Database } from '../db'
|
|
7
7
|
import { EventPusher } from './event-pusher'
|
|
8
8
|
import { EventReverser } from './event-reverser'
|
|
9
|
-
import { ModerationService
|
|
9
|
+
import { ModerationService } from '../mod-service'
|
|
10
10
|
import { BackgroundQueue } from '../background'
|
|
11
11
|
import { getSigningKeyId } from '../util'
|
|
12
|
+
import { MaterializedViewRefresher } from './materialized-view-refresher'
|
|
13
|
+
import { allFulfilled } from '@atproto/common'
|
|
12
14
|
|
|
13
15
|
export type DaemonContextOptions = {
|
|
14
16
|
db: Database
|
|
15
17
|
cfg: OzoneConfig
|
|
16
|
-
|
|
18
|
+
backgroundQueue: BackgroundQueue
|
|
17
19
|
signingKey: Keypair
|
|
18
20
|
eventPusher: EventPusher
|
|
19
21
|
eventReverser: EventReverser
|
|
22
|
+
materializedViewRefresher: MaterializedViewRefresher
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export class DaemonContext {
|
|
@@ -67,13 +70,19 @@ export class DaemonContext {
|
|
|
67
70
|
|
|
68
71
|
const eventReverser = new EventReverser(db, modService)
|
|
69
72
|
|
|
73
|
+
const materializedViewRefresher = new MaterializedViewRefresher(
|
|
74
|
+
backgroundQueue,
|
|
75
|
+
cfg.db.materializedViewRefreshIntervalMs,
|
|
76
|
+
)
|
|
77
|
+
|
|
70
78
|
return new DaemonContext({
|
|
71
79
|
db,
|
|
72
80
|
cfg,
|
|
73
|
-
|
|
81
|
+
backgroundQueue,
|
|
74
82
|
signingKey,
|
|
75
83
|
eventPusher,
|
|
76
84
|
eventReverser,
|
|
85
|
+
materializedViewRefresher,
|
|
77
86
|
...(overrides ?? {}),
|
|
78
87
|
})
|
|
79
88
|
}
|
|
@@ -86,8 +95,8 @@ export class DaemonContext {
|
|
|
86
95
|
return this.opts.cfg
|
|
87
96
|
}
|
|
88
97
|
|
|
89
|
-
get
|
|
90
|
-
return this.opts.
|
|
98
|
+
get backgroundQueue(): BackgroundQueue {
|
|
99
|
+
return this.opts.backgroundQueue
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
get eventPusher(): EventPusher {
|
|
@@ -97,6 +106,35 @@ export class DaemonContext {
|
|
|
97
106
|
get eventReverser(): EventReverser {
|
|
98
107
|
return this.opts.eventReverser
|
|
99
108
|
}
|
|
109
|
+
|
|
110
|
+
get materializedViewRefresher(): MaterializedViewRefresher {
|
|
111
|
+
return this.opts.materializedViewRefresher
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async start() {
|
|
115
|
+
this.eventPusher.start()
|
|
116
|
+
this.eventReverser.start()
|
|
117
|
+
this.materializedViewRefresher.start()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async processAll() {
|
|
121
|
+
// Sequential because the materialized view values depend on the events.
|
|
122
|
+
await this.eventPusher.processAll()
|
|
123
|
+
await this.materializedViewRefresher.run()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async destroy() {
|
|
127
|
+
try {
|
|
128
|
+
await allFulfilled([
|
|
129
|
+
this.eventReverser.destroy(),
|
|
130
|
+
this.eventPusher.destroy(),
|
|
131
|
+
this.materializedViewRefresher.destroy(),
|
|
132
|
+
])
|
|
133
|
+
} finally {
|
|
134
|
+
await this.backgroundQueue.destroy()
|
|
135
|
+
await this.db.close()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
100
138
|
}
|
|
101
139
|
|
|
102
140
|
export default DaemonContext
|
package/src/daemon/index.ts
CHANGED
|
@@ -18,17 +18,14 @@ export class OzoneDaemon {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async start() {
|
|
21
|
-
this.ctx.
|
|
22
|
-
this.ctx.eventReverser.start()
|
|
21
|
+
await this.ctx.start()
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
async processAll() {
|
|
26
|
-
await this.ctx.
|
|
25
|
+
await this.ctx.processAll()
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
async destroy() {
|
|
30
|
-
await this.ctx.
|
|
31
|
-
await this.ctx.eventPusher.destroy()
|
|
32
|
-
await this.ctx.db.close()
|
|
29
|
+
await this.ctx.destroy()
|
|
33
30
|
}
|
|
34
31
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MINUTE } from '@atproto/common'
|
|
2
|
+
import { sql } from 'kysely'
|
|
3
|
+
import { BackgroundQueue, PeriodicBackgroundTask } from '../background'
|
|
4
|
+
|
|
5
|
+
export class MaterializedViewRefresher extends PeriodicBackgroundTask {
|
|
6
|
+
constructor(backgroundQueue: BackgroundQueue, interval = 30 * MINUTE) {
|
|
7
|
+
super(backgroundQueue, interval, async ({ db }, signal) => {
|
|
8
|
+
for (const view of [
|
|
9
|
+
'account_events_stats',
|
|
10
|
+
'record_events_stats',
|
|
11
|
+
'account_record_events_stats',
|
|
12
|
+
'account_record_status_stats',
|
|
13
|
+
]) {
|
|
14
|
+
if (signal.aborted) break
|
|
15
|
+
|
|
16
|
+
// Kysely does not provide a way to cancel a running query. Because of
|
|
17
|
+
// this, killing the process during a refresh will cause the process to
|
|
18
|
+
// wait for the current refresh to finish before exiting. This is not
|
|
19
|
+
// ideal, but it is the best we can do until Kysely provides a way to
|
|
20
|
+
// cancel a query.
|
|
21
|
+
await sql`REFRESH MATERIALIZED VIEW CONCURRENTLY ${sql.id(view)}`.execute(
|
|
22
|
+
db,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|