velocious 1.0.381 → 1.0.382
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/README.md +18 -0
- package/build/src/background-jobs/store.d.ts +35 -0
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +88 -1
- package/build/src/background-jobs/web/authorization.d.ts +20 -0
- package/build/src/background-jobs/web/authorization.d.ts.map +1 -0
- package/build/src/background-jobs/web/authorization.js +75 -0
- package/build/src/background-jobs/web/controller.d.ts +69 -0
- package/build/src/background-jobs/web/controller.d.ts.map +1 -0
- package/build/src/background-jobs/web/controller.js +212 -0
- package/build/src/background-jobs/web/index.d.ts +41 -0
- package/build/src/background-jobs/web/index.d.ts.map +1 -0
- package/build/src/background-jobs/web/index.js +53 -0
- package/build/src/background-jobs/web/path-matcher.d.ts +38 -0
- package/build/src/background-jobs/web/path-matcher.d.ts.map +1 -0
- package/build/src/background-jobs/web/path-matcher.js +71 -0
- package/build/src/background-jobs/web/registry.d.ts +41 -0
- package/build/src/background-jobs/web/registry.d.ts.map +1 -0
- package/build/src/background-jobs/web/registry.js +41 -0
- package/build/src/configuration.d.ts +11 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +25 -2
- package/build/src/routes/base-route.d.ts +14 -0
- package/build/src/routes/base-route.d.ts.map +1 -1
- package/build/src/routes/base-route.js +5 -1
- package/build/src/routes/basic-route.d.ts +12 -0
- package/build/src/routes/basic-route.d.ts.map +1 -1
- package/build/src/routes/basic-route.js +20 -1
- package/build/src/routes/index.d.ts +11 -0
- package/build/src/routes/index.d.ts.map +1 -1
- package/build/src/routes/index.js +21 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import Controller from "../../controller.js";
|
|
3
|
+
import BackgroundJobsStore from "../store.js";
|
|
4
|
+
import { authorizeJobsRequest } from "./authorization.js";
|
|
5
|
+
import { getJobsMount } from "./registry.js";
|
|
6
|
+
const DASHBOARD_STATUSES = ["queued", "handed_off", "completed", "failed", "orphaned"];
|
|
7
|
+
const SORTABLE_KEYS = ["attempts", "completedAtMs", "createdAtMs", "failedAtMs", "handedOffAtMs", "scheduledAtMs"];
|
|
8
|
+
const DEFAULT_PER_PAGE = 25;
|
|
9
|
+
const MAX_PER_PAGE = 100;
|
|
10
|
+
/**
|
|
11
|
+
* Read-only HTTP API backing the background-jobs dashboard. Mounted by
|
|
12
|
+
* {@link import("./index.js").default} as a route-resolver hook so it can ship
|
|
13
|
+
* inside the velocious package. Every action is gated by {@link authorizeJobsRequest}.
|
|
14
|
+
*/
|
|
15
|
+
export default class VelociousBackgroundJobsWebController extends Controller {
|
|
16
|
+
/** @returns {import("./registry.js").JobsMountOptions} - Options for the mount that matched this request. */
|
|
17
|
+
_mountOptions() {
|
|
18
|
+
const at = this.params().velociousJobsMountAt;
|
|
19
|
+
return getJobsMount(this.getConfiguration(), at) || {};
|
|
20
|
+
}
|
|
21
|
+
/** @returns {BackgroundJobsStore} - Jobs store scoped to the mount's database. */
|
|
22
|
+
_store() {
|
|
23
|
+
if (!this._jobsStore) {
|
|
24
|
+
this._jobsStore = new BackgroundJobsStore({
|
|
25
|
+
configuration: this.getConfiguration(),
|
|
26
|
+
databaseIdentifier: this._mountOptions().databaseIdentifier
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return this._jobsStore;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Adds CORS headers when the request origin is allowed, so the standalone
|
|
33
|
+
* browser dashboard can read the API cross-origin.
|
|
34
|
+
* @param {import("./registry.js").JobsMountOptions} options - Mount options.
|
|
35
|
+
* @returns {void} - No return value.
|
|
36
|
+
*/
|
|
37
|
+
_applyCorsHeaders(options) {
|
|
38
|
+
const allowedOrigins = Array.isArray(options.allowedOrigins) ? options.allowedOrigins : [];
|
|
39
|
+
if (allowedOrigins.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
const origin = this.request().origin();
|
|
42
|
+
const allowAll = allowedOrigins.includes("*");
|
|
43
|
+
if (!origin)
|
|
44
|
+
return;
|
|
45
|
+
if (!allowAll && !allowedOrigins.includes(origin))
|
|
46
|
+
return;
|
|
47
|
+
const response = this.response();
|
|
48
|
+
response.setHeader("Access-Control-Allow-Origin", allowAll ? "*" : origin);
|
|
49
|
+
response.setHeader("Vary", "Origin");
|
|
50
|
+
response.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
|
|
51
|
+
response.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Applies CORS headers, authorizes the request, and runs the action body only
|
|
55
|
+
* when authorized. Renders a 401 otherwise. The base controller has no
|
|
56
|
+
* before-action halting, so authorization is enforced here per action.
|
|
57
|
+
* @param {() => Promise<void>} actionFn - Action body.
|
|
58
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
59
|
+
*/
|
|
60
|
+
async _respond(actionFn) {
|
|
61
|
+
const options = this._mountOptions();
|
|
62
|
+
this._applyCorsHeaders(options);
|
|
63
|
+
const authorized = await authorizeJobsRequest({
|
|
64
|
+
ability: this.currentAbility(),
|
|
65
|
+
configuration: this.getConfiguration(),
|
|
66
|
+
options,
|
|
67
|
+
request: this.request()
|
|
68
|
+
});
|
|
69
|
+
if (!authorized) {
|
|
70
|
+
await this.render({ json: { error: "unauthorized" }, status: 401 });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await actionFn();
|
|
74
|
+
}
|
|
75
|
+
/** @returns {Promise<void>} - Resolves when complete. */
|
|
76
|
+
async health() {
|
|
77
|
+
await this._respond(async () => {
|
|
78
|
+
await this.render({ json: { ok: true, service: "velocious-background-jobs" } });
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** @returns {Promise<void>} - Resolves when complete. */
|
|
82
|
+
async stats() {
|
|
83
|
+
await this._respond(async () => {
|
|
84
|
+
const counts = await this._store().countsByStatus();
|
|
85
|
+
/** @type {Record<string, number>} */
|
|
86
|
+
const byStatus = {};
|
|
87
|
+
let total = 0;
|
|
88
|
+
for (const status of DASHBOARD_STATUSES) {
|
|
89
|
+
byStatus[status] = counts[status] || 0;
|
|
90
|
+
}
|
|
91
|
+
for (const value of Object.values(counts)) {
|
|
92
|
+
total += value;
|
|
93
|
+
}
|
|
94
|
+
await this.render({ json: { counts: byStatus, generatedAtMs: Date.now(), total } });
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** @returns {Promise<void>} - Resolves when complete. */
|
|
98
|
+
async index() {
|
|
99
|
+
await this._respond(async () => {
|
|
100
|
+
const params = this.params();
|
|
101
|
+
const status = this._sanitizeStatus(params.status);
|
|
102
|
+
const jobName = typeof params.jobName === "string" && params.jobName.length > 0 ? params.jobName : undefined;
|
|
103
|
+
const page = this._positiveInt(params.page, 1);
|
|
104
|
+
const perPage = Math.min(this._positiveInt(params.perPage, DEFAULT_PER_PAGE), MAX_PER_PAGE);
|
|
105
|
+
const { sortColumn, sortDirection } = this._sanitizeSort(params.sort);
|
|
106
|
+
const store = this._store();
|
|
107
|
+
const jobs = await store.listJobs({ jobName, limit: perPage, offset: (page - 1) * perPage, sortColumn, sortDirection, status });
|
|
108
|
+
const total = await store.countJobs({ jobName, status });
|
|
109
|
+
await this.render({ json: {
|
|
110
|
+
jobs: jobs.map((job) => this._serializeJob(job)),
|
|
111
|
+
pagination: { page, perPage, total, totalPages: perPage > 0 ? Math.ceil(total / perPage) : 0 }
|
|
112
|
+
} });
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** @returns {Promise<void>} - Resolves when complete. */
|
|
116
|
+
async show() {
|
|
117
|
+
await this._respond(async () => {
|
|
118
|
+
const job = await this._store().getJob(this.params().id);
|
|
119
|
+
if (!job) {
|
|
120
|
+
await this.render({ json: { error: "not_found" }, status: 404 });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
await this.render({ json: { job: this._serializeJob(job) } });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** @returns {Promise<void>} - Resolves when complete. */
|
|
127
|
+
async schedule() {
|
|
128
|
+
await this._respond(async () => {
|
|
129
|
+
const scheduled = await this.getConfiguration().getScheduledBackgroundJobsConfig();
|
|
130
|
+
await this.render({ json: { schedule: this._serializeSchedule(scheduled) } });
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* @param {import("../types.js").BackgroundJobRow} job - Job row.
|
|
135
|
+
* @returns {Record<string, any>} - Serialized job for the API.
|
|
136
|
+
*/
|
|
137
|
+
_serializeJob(job) {
|
|
138
|
+
const redactArgs = Boolean(this._mountOptions().redactArgs);
|
|
139
|
+
return {
|
|
140
|
+
args: redactArgs ? undefined : job.args,
|
|
141
|
+
argsRedacted: redactArgs,
|
|
142
|
+
attempts: job.attempts,
|
|
143
|
+
completedAtMs: job.completedAtMs,
|
|
144
|
+
createdAtMs: job.createdAtMs,
|
|
145
|
+
failedAtMs: job.failedAtMs,
|
|
146
|
+
forked: job.forked,
|
|
147
|
+
handedOffAtMs: job.handedOffAtMs,
|
|
148
|
+
id: job.id,
|
|
149
|
+
jobName: job.jobName,
|
|
150
|
+
lastError: job.lastError,
|
|
151
|
+
maxRetries: job.maxRetries,
|
|
152
|
+
orphanedAtMs: job.orphanedAtMs,
|
|
153
|
+
scheduledAtMs: job.scheduledAtMs,
|
|
154
|
+
status: job.status,
|
|
155
|
+
workerId: job.workerId
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* @param {import("../../configuration-types.js").ScheduledBackgroundJobsConfiguration | undefined} scheduled - Scheduled jobs config.
|
|
160
|
+
* @returns {Array<Record<string, any>>} - Serialized recurring jobs.
|
|
161
|
+
*/
|
|
162
|
+
_serializeSchedule(scheduled) {
|
|
163
|
+
const jobs = scheduled?.jobs;
|
|
164
|
+
if (!jobs || typeof jobs !== "object")
|
|
165
|
+
return [];
|
|
166
|
+
const redactArgs = Boolean(this._mountOptions().redactArgs);
|
|
167
|
+
return Object.keys(jobs).map((name) => {
|
|
168
|
+
const entry = jobs[name] || /** @type {any} */ ({});
|
|
169
|
+
return {
|
|
170
|
+
args: redactArgs ? undefined : (entry.args || []),
|
|
171
|
+
cron: entry.cron,
|
|
172
|
+
enabled: entry.enabled !== false,
|
|
173
|
+
every: entry.every,
|
|
174
|
+
jobName: typeof entry.class === "function" ? entry.class.name : undefined,
|
|
175
|
+
name,
|
|
176
|
+
options: entry.options || {}
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* @param {unknown} value - Raw status param.
|
|
182
|
+
* @returns {string | undefined} - Valid status or undefined.
|
|
183
|
+
*/
|
|
184
|
+
_sanitizeStatus(value) {
|
|
185
|
+
return typeof value === "string" && DASHBOARD_STATUSES.includes(value) ? value : undefined;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* @param {unknown} value - Raw sort param (e.g. "createdAtMs" or "-failedAtMs").
|
|
189
|
+
* @returns {{sortColumn: string, sortDirection: "ASC" | "DESC"}} - Normalized sort.
|
|
190
|
+
*/
|
|
191
|
+
_sanitizeSort(value) {
|
|
192
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
193
|
+
return { sortColumn: "createdAtMs", sortDirection: "DESC" };
|
|
194
|
+
}
|
|
195
|
+
const descending = value.startsWith("-");
|
|
196
|
+
const key = descending ? value.slice(1) : value;
|
|
197
|
+
const sortColumn = SORTABLE_KEYS.includes(key) ? key : "createdAtMs";
|
|
198
|
+
return { sortColumn, sortDirection: descending ? "DESC" : "ASC" };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* @param {unknown} value - Raw numeric param.
|
|
202
|
+
* @param {number} fallback - Fallback when invalid.
|
|
203
|
+
* @returns {number} - Positive integer.
|
|
204
|
+
*/
|
|
205
|
+
_positiveInt(value, fallback) {
|
|
206
|
+
const numeric = Number(Array.isArray(value) ? value[0] : value);
|
|
207
|
+
if (!Number.isFinite(numeric) || numeric < 1)
|
|
208
|
+
return fallback;
|
|
209
|
+
return Math.floor(numeric);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udHJvbGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9iYWNrZ3JvdW5kLWpvYnMvd2ViL2NvbnRyb2xsZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sVUFBVSxNQUFNLHFCQUFxQixDQUFBO0FBQzVDLE9BQU8sbUJBQW1CLE1BQU0sYUFBYSxDQUFBO0FBQzdDLE9BQU8sRUFBQyxvQkFBb0IsRUFBQyxNQUFNLG9CQUFvQixDQUFBO0FBQ3ZELE9BQU8sRUFBQyxZQUFZLEVBQUMsTUFBTSxlQUFlLENBQUE7QUFFMUMsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLFFBQVEsRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLFFBQVEsRUFBRSxVQUFVLENBQUMsQ0FBQTtBQUN0RixNQUFNLGFBQWEsR0FBRyxDQUFDLFVBQVUsRUFBRSxlQUFlLEVBQUUsYUFBYSxFQUFFLFlBQVksRUFBRSxlQUFlLEVBQUUsZUFBZSxDQUFDLENBQUE7QUFDbEgsTUFBTSxnQkFBZ0IsR0FBRyxFQUFFLENBQUE7QUFDM0IsTUFBTSxZQUFZLEdBQUcsR0FBRyxDQUFBO0FBRXhCOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLG9DQUFxQyxTQUFRLFVBQVU7SUFDMUUsNkdBQTZHO0lBQzdHLGFBQWE7UUFDWCxNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsb0JBQW9CLENBQUE7UUFFN0MsT0FBTyxZQUFZLENBQUMsSUFBSSxDQUFDLGdCQUFnQixFQUFFLEVBQUUsRUFBRSxDQUFDLElBQUksRUFBRSxDQUFBO0lBQ3hELENBQUM7SUFFRCxrRkFBa0Y7SUFDbEYsTUFBTTtRQUNKLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDckIsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLG1CQUFtQixDQUFDO2dCQUN4QyxhQUFhLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixF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Q0FBQTtJQUNqRSxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILFlBQVksQ0FBQyxLQUFLLEVBQUUsUUFBUTtRQUMxQixNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUUvRCxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsSUFBSSxPQUFPLEdBQUcsQ0FBQztZQUFFLE9BQU8sUUFBUSxDQUFBO1FBRTdELE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUM1QixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IENvbnRyb2xsZXIgZnJvbSBcIi4uLy4uL2NvbnRyb2xsZXIuanNcIlxuaW1wb3J0IEJhY2tncm91bmRKb2JzU3RvcmUgZnJvbSBcIi4uL3N0b3JlLmpzXCJcbmltcG9ydCB7YXV0aG9yaXplSm9ic1JlcXVlc3R9IGZyb20gXCIuL2F1dGhvcml6YXRpb24uanNcIlxuaW1wb3J0IHtnZXRKb2JzTW91bnR9IGZyb20gXCIuL3JlZ2lzdHJ5LmpzXCJcblxuY29uc3QgREFTSEJPQVJEX1NUQVRVU0VTID0gW1wicXVldWVkXCIsIFwiaGFuZGVkX29mZlwiLCBcImNvbXBsZXRlZFwiLCBcImZhaWxlZFwiLCBcIm9ycGhhbmVkXCJdXG5jb25zdCBTT1JUQUJMRV9LRVlTID0gW1wiYXR0ZW1wdHNcIiwgXCJjb21wbGV0ZWRBdE1zXCIsIFwiY3JlYXRlZEF0TXNcIiwgXCJmYWlsZWRBdE1zXCIsIFwiaGFuZGVkT2ZmQXRNc1wiLCBcInNjaGVkdWxlZEF0TXNcIl1cbmNvbnN0IERFRkFVTFRfUEVSX1BBR0UgPSAyNVxuY29uc3QgTUFYX1BFUl9QQUdFID0gMTAwXG5cbi8qKlxuICogUmVhZC1vbmx5IEhUVFAgQVBJIGJhY2tpbmcgdGhlIGJhY2tncm91bmQtam9icyBkYXNoYm9hcmQuIE1vdW50ZWQgYnlcbiAqIHtAbGluayBpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IGFzIGEgcm91dGUtcmVzb2x2ZXIgaG9vayBzbyBpdCBjYW4gc2hpcFxuICogaW5zaWRlIHRoZSB2ZWxvY2lvdXMgcGFja2FnZS4gRXZlcnkgYWN0aW9uIGlzIGdhdGVkIGJ5IHtAbGluayBhdXRob3JpemVKb2JzUmVxdWVzdH0uXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFZlbG9jaW91c0JhY2tncm91bmRKb2JzV2ViQ29udHJvbGxlciBleHRlbmRzIENvbnRyb2xsZXIge1xuICAvKiogQHJldHVybnMge2ltcG9ydChcIi4vcmVnaXN0cnkuanNcIikuSm9ic01vdW50T3B0aW9uc30gLSBPcHRpb25zIGZvciB0aGUgbW91bnQgdGhhdCBtYXRjaGVkIHRoaXMgcmVxdWVzdC4gKi9cbiAgX21vdW50T3B0aW9ucygpIHtcbiAgICBjb25zdCBhdCA9IHRoaXMucGFyYW1zKCkudmVsb2Npb3VzSm9ic01vdW50QXRcblxuICAgIHJldHVybiBnZXRKb2JzTW91bnQodGhpcy5nZXRDb25maWd1cmF0aW9uKCksIGF0KSB8fCB7fVxuICB9XG5cbiAgLyoqIEByZXR1cm5zIHtCYWNrZ3JvdW5kSm9ic1N0b3JlfSAtIEpvYnMgc3RvcmUgc2NvcGVkIHRvIHRoZSBtb3VudCdzIGRhdGFiYXNlLiAqL1xuICBfc3RvcmUoKSB7XG4gICAgaWYgKCF0aGlzLl9qb2JzU3RvcmUpIHtcbiAgICAgIHRoaXMuX2pvYnNTdG9yZSA9IG5ldyBCYWNrZ3JvdW5kSm9ic1N0b3JlKHtcbiAgICAgICAgY29uZmlndXJhdGlvbjogdGhpcy5nZXRDb25maWd1cmF0aW9uKCksXG4gICAgICAgIGRhdGFiYXNlSWRlbnRpZmllcjogdGhpcy5fbW91bnRPcHRpb25zKCkuZGF0YWJhc2VJZGVudGlmaWVyXG4gICAgICB9KVxuICAgIH1cblxuICAgIHJldHVybiB0aGlzLl9qb2JzU3RvcmVcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGRzIENPUlMgaGVhZGVycyB3aGVuIHRoZSByZXF1ZXN0IG9yaWdpbiBpcyBhbGxvd2VkLCBzbyB0aGUgc3RhbmRhbG9uZVxuICAgKiBicm93c2VyIGRhc2hib2FyZCBjYW4gcmVhZCB0aGUgQVBJIGNyb3NzLW9yaWdpbi5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuL3JlZ2lzdHJ5LmpzXCIpLkpvYnNNb3VudE9wdGlvbnN9IG9wdGlvbnMgLSBNb3VudCBvcHRpb25zLlxuICAgKiBAcmV0dXJucyB7dm9pZH0gLSBObyByZXR1cm4gdmFsdWUuXG4gICAqL1xuICBfYXBwbHlDb3JzSGVhZGVycyhvcHRpb25zKSB7XG4gICAgY29uc3QgYWxsb3dlZE9yaWdpbnMgPSBBcnJheS5pc0FycmF5KG9wdGlvbnMuYWxsb3dlZE9yaWdpbnMpID8gb3B0aW9ucy5hbGxvd2VkT3JpZ2lucyA6IFtdXG5cbiAgICBpZiAoYWxsb3dlZE9yaWdpbnMubGVuZ3RoID09PSAwKSByZXR1cm5cblxuICAgIGNvbnN0IG9yaWdpbiA9IHRoaXMucmVxdWVzdCgpLm9yaWdpbigpXG4gICAgY29uc3QgYWxsb3dBbGwgPSBhbGxvd2VkT3JpZ2lucy5pbmNsdWRlcyhcIipcIilcblxuICAgIGlmICghb3JpZ2luKSByZXR1cm5cbiAgICBpZiAoIWFsbG93QWxsICYmICFhbGxvd2VkT3JpZ2lucy5pbmNsdWRlcyhvcmlnaW4pKSByZXR1cm5cblxuICAgIGNvbnN0IHJlc3BvbnNlID0gdGhpcy5yZXNwb25zZSgpXG5cbiAgICByZXNwb25zZS5zZXRIZWFkZXIoXCJBY2Nlc3MtQ29udHJvbC1BbGxvdy1PcmlnaW5cIiwgYWxsb3dBbGwgPyBcIipcIiA6IG9yaWdpbilcbiAgICByZXNwb25zZS5zZXRIZWFkZXIoXCJWYXJ5XCIsIFwiT3JpZ2luXCIpXG4gICAgcmVzcG9uc2Uuc2V0SGVhZGVyKFwiQWNjZXNzLUNvbnRyb2wtQWxsb3ctSGVhZGVyc1wiLCBcImF1dGhvcml6YXRpb24sIGNvbnRlbnQtdHlwZVwiKVxuICAgIHJlc3BvbnNlLnNldEhlYWRlcihcIkFjY2Vzcy1Db250cm9sLUFsbG93LU1ldGhvZHNcIiwgXCJHRVQsIFBPU1QsIERFTEVURSwgT1BUSU9OU1wiKVxuICB9XG5cbiAgLyoqXG4gICAqIEFwcGxpZXMgQ09SUyBoZWFkZXJzLCBhdXRob3JpemVzIHRoZSByZXF1ZXN0LCBhbmQgcnVucyB0aGUgYWN0aW9uIGJvZHkgb25seVxuICAgKiB3aGVuIGF1dGhvcml6ZWQuIFJlbmRlcnMgYSA0MDEgb3RoZXJ3aXNlLiBUaGUgYmFzZSBjb250cm9sbGVyIGhhcyBub1xuICAgKiBiZWZvcmUtYWN0aW9uIGhhbHRpbmcsIHNvIGF1dGhvcml6YXRpb24gaXMgZW5mb3JjZWQgaGVyZSBwZXIgYWN0aW9uLlxuICAgKiBAcGFyYW0geygpID0+IFByb21pc2U8dm9pZD59IGFjdGlvbkZuIC0gQWN0aW9uIGJvZHkuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBfcmVzcG9uZChhY3Rpb25Gbikge1xuICAgIGNvbnN0IG9wdGlvbnMgPSB0aGlzLl9tb3VudE9wdGlvbnMoKVxuXG4gICAgdGhpcy5fYXBwbHlDb3JzSGVhZGVycyhvcHRpb25zKVxuXG4gICAgY29uc3QgYXV0aG9yaXplZCA9IGF3YWl0IGF1dGhvcml6ZUpvYnNSZXF1ZXN0KHtcbiAgICAgIGFiaWxpdHk6IHRoaXMuY3VycmVudEFiaWxpdHkoKSxcbiAgICAgIGNvbmZpZ3VyYXRpb246IHRoaXMuZ2V0Q29uZmlndXJhdGlvbigpLFxuICAgICAgb3B0aW9ucyxcbiAgICAgIHJlcXVlc3Q6IHRoaXMucmVxdWVzdCgpXG4gICAgfSlcblxuICAgIGlmICghYXV0aG9yaXplZCkge1xuICAgICAgYXdhaXQgdGhpcy5yZW5kZXIoe2pzb246IHtlcnJvcjogXCJ1bmF1dGhvcml6ZWRcIn0sIHN0YXR1czogNDAxfSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGF3YWl0IGFjdGlvbkZuKClcbiAgfVxuXG4gIC8qKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNvbXBsZXRlLiAqL1xuICBhc3luYyBoZWFsdGgoKSB7XG4gICAgYXdhaXQgdGhpcy5fcmVzcG9uZChhc3luYyAoKSA9PiB7XG4gICAgICBhd2FpdCB0aGlzLnJlbmRlcih7anNvbjoge29rOiB0cnVlLCBzZXJ2aWNlOiBcInZlbG9jaW91cy1iYWNrZ3JvdW5kLWpvYnNcIn19KVxuICAgIH0pXG4gIH1cblxuICAvKiogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgd2hlbiBjb21wbGV0ZS4gKi9cbiAgYXN5bmMgc3RhdHMoKSB7XG4gICAgYXdhaXQgdGhpcy5fcmVzcG9uZChhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBjb3VudHMgPSBhd2FpdCB0aGlzLl9zdG9yZSgpLmNvdW50c0J5U3RhdHVzKClcbiAgICAgIC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgbnVtYmVyPn0gKi9cbiAgICAgIGNvbnN0IGJ5U3RhdHVzID0ge31cbiAgICAgIGxldCB0b3RhbCA9IDBcblxuICAgICAgZm9yIChjb25zdCBzdGF0dXMgb2YgREFTSEJPQVJEX1NUQVRVU0VTKSB7XG4gICAgICAgIGJ5U3RhdHVzW3N0YXR1c10gPSBjb3VudHNbc3RhdHVzXSB8fCAwXG4gICAgICB9XG5cbiAgICAgIGZvciAoY29uc3QgdmFsdWUgb2YgT2JqZWN0LnZhbHVlcyhjb3VudHMpKSB7XG4gICAgICAgIHRvdGFsICs9IHZhbHVlXG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHRoaXMucmVuZGVyKHtqc29uOiB7Y291bnRzOiBieVN0YXR1cywgZ2VuZXJhdGVkQXRNczogRGF0ZS5ub3coKSwgdG90YWx9fSlcbiAgICB9KVxuICB9XG5cbiAgLyoqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuICovXG4gIGFzeW5jIGluZGV4KCkge1xuICAgIGF3YWl0IHRoaXMuX3Jlc3BvbmQoYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgcGFyYW1zID0gdGhpcy5wYXJhbXMoKVxuICAgICAgY29uc3Qgc3RhdHVzID0gdGhpcy5fc2FuaXRpemVTdGF0dXMocGFyYW1zLnN0YXR1cylcbiAgICAgIGNvbnN0IGpvYk5hbWUgPSB0eXBlb2YgcGFyYW1zLmpvYk5hbWUgPT09IFwic3RyaW5nXCIgJiYgcGFyYW1zLmpvYk5hbWUubGVuZ3RoID4gMCA/IHBhcmFtcy5qb2JOYW1lIDogdW5kZWZpbmVkXG4gICAgICBjb25zdCBwYWdlID0gdGhpcy5fcG9zaXRpdmVJbnQocGFyYW1zLnBhZ2UsIDEpXG4gICAgICBjb25zdCBwZXJQYWdlID0gTWF0aC5taW4odGhpcy5fcG9zaXRpdmVJbnQocGFyYW1zLnBlclBhZ2UsIERFRkFVTFRfUEVSX1BBR0UpLCBNQVhfUEVSX1BBR0UpXG4gICAgICBjb25zdCB7c29ydENvbHVtbiwgc29ydERpcmVjdGlvbn0gPSB0aGlzLl9zYW5pdGl6ZVNvcnQocGFyYW1zLnNvcnQpXG4gICAgICBjb25zdCBzdG9yZSA9IHRoaXMuX3N0b3JlKClcbiAgICAgIGNvbnN0IGpvYnMgPSBhd2FpdCBzdG9yZS5saXN0Sm9icyh7am9iTmFtZSwgbGltaXQ6IHBlclBhZ2UsIG9mZnNldDogKHBhZ2UgLSAxKSAqIHBlclBhZ2UsIHNvcnRDb2x1bW4sIHNvcnREaXJlY3Rpb24sIHN0YXR1c30pXG4gICAgICBjb25zdCB0b3RhbCA9IGF3YWl0IHN0b3JlLmNvdW50Sm9icyh7am9iTmFtZSwgc3RhdHVzfSlcblxuICAgICAgYXdhaXQgdGhpcy5yZW5kZXIoe2pzb246IHtcbiAgICAgICAgam9iczogam9icy5tYXAoKGpvYikgPT4gdGhpcy5fc2VyaWFsaXplSm9iKGpvYikpLFxuICAgICAgICBwYWdpbmF0aW9uOiB7cGFnZSwgcGVyUGFnZSwgdG90YWwsIHRvdGFsUGFnZXM6IHBlclBhZ2UgPiAwID8gTWF0aC5jZWlsKHRvdGFsIC8gcGVyUGFnZSkgOiAwfVxuICAgICAgfX0pXG4gICAgfSlcbiAgfVxuXG4gIC8qKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNvbXBsZXRlLiAqL1xuICBhc3luYyBzaG93KCkge1xuICAgIGF3YWl0IHRoaXMuX3Jlc3BvbmQoYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3Qgam9iID0gYXdhaXQgdGhpcy5fc3RvcmUoKS5nZXRKb2IodGhpcy5wYXJhbXMoKS5pZClcblxuICAgICAgaWYgKCFqb2IpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5yZW5kZXIoe2pzb246IHtlcnJvcjogXCJub3RfZm91bmRcIn0sIHN0YXR1czogNDA0fSlcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHRoaXMucmVuZGVyKHtqc29uOiB7am9iOiB0aGlzLl9zZXJpYWxpemVKb2Ioam9iKX19KVxuICAgIH0pXG4gIH1cblxuICAvKiogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgd2hlbiBjb21wbGV0ZS4gKi9cbiAgYXN5bmMgc2NoZWR1bGUoKSB7XG4gICAgYXdhaXQgdGhpcy5fcmVzcG9uZChhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBzY2hlZHVsZWQgPSBhd2FpdCB0aGlzLmdldENvbmZpZ3VyYXRpb24oKS5nZXRTY2hlZHVsZWRCYWNrZ3JvdW5kSm9ic0NvbmZpZygpXG5cbiAgICAgIGF3YWl0IHRoaXMucmVuZGVyKHtqc29uOiB7c2NoZWR1bGU6IHRoaXMuX3NlcmlhbGl6ZVNjaGVkdWxlKHNjaGVkdWxlZCl9fSlcbiAgICB9KVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYlJvd30gam9iIC0gSm9iIHJvdy5cbiAgICogQHJldHVybnMge1JlY29yZDxzdHJpbmcsIGFueT59IC0gU2VyaWFsaXplZCBqb2IgZm9yIHRoZSBBUEkuXG4gICAqL1xuICBfc2VyaWFsaXplSm9iKGpvYikge1xuICAgIGNvbnN0IHJlZGFjdEFyZ3MgPSBCb29sZWFuKHRoaXMuX21vdW50T3B0aW9ucygpLnJlZGFjdEFyZ3MpXG5cbiAgICByZXR1cm4ge1xuICAgICAgYXJnczogcmVkYWN0QXJncyA/IHVuZGVmaW5lZCA6IGpvYi5hcmdzLFxuICAgICAgYXJnc1JlZGFjdGVkOiByZWRhY3RBcmdzLFxuICAgICAgYXR0ZW1wdHM6IGpvYi5hdHRlbXB0cyxcbiAgICAgIGNvbXBsZXRlZEF0TXM6IGpvYi5jb21wbGV0ZWRBdE1zLFxuICAgICAgY3JlYXRlZEF0TXM6IGpvYi5jcmVhdGVkQXRNcyxcbiAgICAgIGZhaWxlZEF0TXM6IGpvYi5mYWlsZWRBdE1zLFxuICAgICAgZm9ya2VkOiBqb2IuZm9ya2VkLFxuICAgICAgaGFuZGVkT2ZmQXRNczogam9iLmhhbmRlZE9mZkF0TXMsXG4gICAgICBpZDogam9iLmlkLFxuICAgICAgam9iTmFtZTogam9iLmpvYk5hbWUsXG4gICAgICBsYXN0RXJyb3I6IGpvYi5sYXN0RXJyb3IsXG4gICAgICBtYXhSZXRyaWVzOiBqb2IubWF4UmV0cmllcyxcbiAgICAgIG9ycGhhbmVkQXRNczogam9iLm9ycGhhbmVkQXRNcyxcbiAgICAgIHNjaGVkdWxlZEF0TXM6IGpvYi5zY2hlZHVsZWRBdE1zLFxuICAgICAgc3RhdHVzOiBqb2Iuc3RhdHVzLFxuICAgICAgd29ya2VySWQ6IGpvYi53b3JrZXJJZFxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uLy4uL2NvbmZpZ3VyYXRpb24tdHlwZXMuanNcIikuU2NoZWR1bGVkQmFja2dyb3VuZEpvYnNDb25maWd1cmF0aW9uIHwgdW5kZWZpbmVkfSBzY2hlZHVsZWQgLSBTY2hlZHVsZWQgam9icyBjb25maWcuXG4gICAqIEByZXR1cm5zIHtBcnJheTxSZWNvcmQ8c3RyaW5nLCBhbnk+Pn0gLSBTZXJpYWxpemVkIHJlY3VycmluZyBqb2JzLlxuICAgKi9cbiAgX3NlcmlhbGl6ZVNjaGVkdWxlKHNjaGVkdWxlZCkge1xuICAgIGNvbnN0IGpvYnMgPSBzY2hlZHVsZWQ/LmpvYnNcblxuICAgIGlmICgham9icyB8fCB0eXBlb2Ygam9icyAhPT0gXCJvYmplY3RcIikgcmV0dXJuIFtdXG5cbiAgICBjb25zdCByZWRhY3RBcmdzID0gQm9vbGVhbih0aGlzLl9tb3VudE9wdGlvbnMoKS5yZWRhY3RBcmdzKVxuXG4gICAgcmV0dXJuIE9iamVjdC5rZXlzKGpvYnMpLm1hcCgobmFtZSkgPT4ge1xuICAgICAgY29uc3QgZW50cnkgPSBqb2JzW25hbWVdIHx8IC8qKiBAdHlwZSB7YW55fSAqLyAoe30pXG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGFyZ3M6IHJlZGFjdEFyZ3MgPyB1bmRlZmluZWQgOiAoZW50cnkuYXJncyB8fCBbXSksXG4gICAgICAgIGNyb246IGVudHJ5LmNyb24sXG4gICAgICAgIGVuYWJsZWQ6IGVudHJ5LmVuYWJsZWQgIT09IGZhbHNlLFxuICAgICAgICBldmVyeTogZW50cnkuZXZlcnksXG4gICAgICAgIGpvYk5hbWU6IHR5cGVvZiBlbnRyeS5jbGFzcyA9PT0gXCJmdW5jdGlvblwiID8gZW50cnkuY2xhc3MubmFtZSA6IHVuZGVmaW5lZCxcbiAgICAgICAgbmFtZSxcbiAgICAgICAgb3B0aW9uczogZW50cnkub3B0aW9ucyB8fCB7fVxuICAgICAgfVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHt1bmtub3dufSB2YWx1ZSAtIFJhdyBzdGF0dXMgcGFyYW0uXG4gICAqIEByZXR1cm5zIHtzdHJpbmcgfCB1bmRlZmluZWR9IC0gVmFsaWQgc3RhdHVzIG9yIHVuZGVmaW5lZC5cbiAgICovXG4gIF9zYW5pdGl6ZVN0YXR1cyh2YWx1ZSkge1xuICAgIHJldHVybiB0eXBlb2YgdmFsdWUgPT09IFwic3RyaW5nXCIgJiYgREFTSEJPQVJEX1NUQVRVU0VTLmluY2x1ZGVzKHZhbHVlKSA/IHZhbHVlIDogdW5kZWZpbmVkXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHt1bmtub3dufSB2YWx1ZSAtIFJhdyBzb3J0IHBhcmFtIChlLmcuIFwiY3JlYXRlZEF0TXNcIiBvciBcIi1mYWlsZWRBdE1zXCIpLlxuICAgKiBAcmV0dXJucyB7e3NvcnRDb2x1bW46IHN0cmluZywgc29ydERpcmVjdGlvbjogXCJBU0NcIiB8IFwiREVTQ1wifX0gLSBOb3JtYWxpemVkIHNvcnQuXG4gICAqL1xuICBfc2FuaXRpemVTb3J0KHZhbHVlKSB7XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSAhPT0gXCJzdHJpbmdcIiB8fCB2YWx1ZS5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybiB7c29ydENvbHVtbjogXCJjcmVhdGVkQXRNc1wiLCBzb3J0RGlyZWN0aW9uOiBcIkRFU0NcIn1cbiAgICB9XG5cbiAgICBjb25zdCBkZXNjZW5kaW5nID0gdmFsdWUuc3RhcnRzV2l0aChcIi1cIilcbiAgICBjb25zdCBrZXkgPSBkZXNjZW5kaW5nID8gdmFsdWUuc2xpY2UoMSkgOiB2YWx1ZVxuICAgIGNvbnN0IHNvcnRDb2x1bW4gPSBTT1JUQUJMRV9LRVlTLmluY2x1ZGVzKGtleSkgPyBrZXkgOiBcImNyZWF0ZWRBdE1zXCJcblxuICAgIHJldHVybiB7c29ydENvbHVtbiwgc29ydERpcmVjdGlvbjogZGVzY2VuZGluZyA/IFwiREVTQ1wiIDogXCJBU0NcIn1cbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge3Vua25vd259IHZhbHVlIC0gUmF3IG51bWVyaWMgcGFyYW0uXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBmYWxsYmFjayAtIEZhbGxiYWNrIHdoZW4gaW52YWxpZC5cbiAgICogQHJldHVybnMge251bWJlcn0gLSBQb3NpdGl2ZSBpbnRlZ2VyLlxuICAgKi9cbiAgX3Bvc2l0aXZlSW50KHZhbHVlLCBmYWxsYmFjaykge1xuICAgIGNvbnN0IG51bWVyaWMgPSBOdW1iZXIoQXJyYXkuaXNBcnJheSh2YWx1ZSkgPyB2YWx1ZVswXSA6IHZhbHVlKVxuXG4gICAgaWYgKCFOdW1iZXIuaXNGaW5pdGUobnVtZXJpYykgfHwgbnVtZXJpYyA8IDEpIHJldHVybiBmYWxsYmFja1xuXG4gICAgcmV0dXJuIE1hdGguZmxvb3IobnVtZXJpYylcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mountable read-only background-jobs dashboard API. Include it in a routes file
|
|
3
|
+
* the way Sidekiq::Web is mounted in Rails:
|
|
4
|
+
*
|
|
5
|
+
* ```js
|
|
6
|
+
* routes.draw((route) => {
|
|
7
|
+
* route.mount(VelociousBackgroundJobsApi, {
|
|
8
|
+
* at: "/velocious/jobs",
|
|
9
|
+
* authorize: async ({request, ability}) => { ... },
|
|
10
|
+
* accessTokens: [process.env.VELOCIOUS_JOBS_TOKEN]
|
|
11
|
+
* })
|
|
12
|
+
* })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export default class VelociousBackgroundJobsApi {
|
|
16
|
+
/**
|
|
17
|
+
* Registers the jobs API under `at`. Implemented as a route-resolver hook so
|
|
18
|
+
* the controller can live inside the velocious package rather than the host
|
|
19
|
+
* app's `src/routes` directory. Invoked by the routing layer for each
|
|
20
|
+
* `route.mount(...)` registration.
|
|
21
|
+
* @param {object} args - Options.
|
|
22
|
+
* @param {import("../../configuration.js").default} args.configuration - Configuration instance.
|
|
23
|
+
* @param {string} args.at - Mount path prefix (e.g. "/velocious/jobs").
|
|
24
|
+
* @param {import("./registry.js").JobsMountOptions["authorize"]} [args.authorize] - Authorization callback.
|
|
25
|
+
* @param {string[]} [args.accessTokens] - Accepted bearer tokens for cross-origin/native access.
|
|
26
|
+
* @param {string[]} [args.allowedOrigins] - Allowed CORS origins for browser access.
|
|
27
|
+
* @param {boolean} [args.redactArgs] - When true, job arguments are omitted from responses.
|
|
28
|
+
* @param {string} [args.databaseIdentifier] - Database identifier the jobs store reads from.
|
|
29
|
+
* @returns {void} - No return value.
|
|
30
|
+
*/
|
|
31
|
+
static mountInto({ accessTokens, allowedOrigins, at, authorize, configuration, databaseIdentifier, redactArgs }: {
|
|
32
|
+
configuration: import("../../configuration.js").default;
|
|
33
|
+
at: string;
|
|
34
|
+
authorize?: import("./registry.js").JobsMountOptions["authorize"];
|
|
35
|
+
accessTokens?: string[] | undefined;
|
|
36
|
+
allowedOrigins?: string[] | undefined;
|
|
37
|
+
redactArgs?: boolean | undefined;
|
|
38
|
+
databaseIdentifier?: string | undefined;
|
|
39
|
+
}): void;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/background-jobs/web/index.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;GAaG;AACH;IACE;;;;;;;;;;;;;;OAcG;IACH,iHATG;QAAuD,aAAa,EAA5D,OAAO,wBAAwB,EAAE,OAAO;QAC3B,EAAE,EAAf,MAAM;QACuD,SAAS,GAAtE,OAAO,eAAe,EAAE,gBAAgB,CAAC,WAAW,CAAC;QACrC,YAAY;QACZ,cAAc;QACf,UAAU;QACX,kBAAkB;KACxC,GAAU,IAAI,CAqBhB;CACF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import VelociousBackgroundJobsWebController from "./controller.js";
|
|
3
|
+
import { matchJobsApiPath, normalizeMountPrefix } from "./path-matcher.js";
|
|
4
|
+
import { registerJobsMount } from "./registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Mountable read-only background-jobs dashboard API. Include it in a routes file
|
|
7
|
+
* the way Sidekiq::Web is mounted in Rails:
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* routes.draw((route) => {
|
|
11
|
+
* route.mount(VelociousBackgroundJobsApi, {
|
|
12
|
+
* at: "/velocious/jobs",
|
|
13
|
+
* authorize: async ({request, ability}) => { ... },
|
|
14
|
+
* accessTokens: [process.env.VELOCIOUS_JOBS_TOKEN]
|
|
15
|
+
* })
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default class VelociousBackgroundJobsApi {
|
|
20
|
+
/**
|
|
21
|
+
* Registers the jobs API under `at`. Implemented as a route-resolver hook so
|
|
22
|
+
* the controller can live inside the velocious package rather than the host
|
|
23
|
+
* app's `src/routes` directory. Invoked by the routing layer for each
|
|
24
|
+
* `route.mount(...)` registration.
|
|
25
|
+
* @param {object} args - Options.
|
|
26
|
+
* @param {import("../../configuration.js").default} args.configuration - Configuration instance.
|
|
27
|
+
* @param {string} args.at - Mount path prefix (e.g. "/velocious/jobs").
|
|
28
|
+
* @param {import("./registry.js").JobsMountOptions["authorize"]} [args.authorize] - Authorization callback.
|
|
29
|
+
* @param {string[]} [args.accessTokens] - Accepted bearer tokens for cross-origin/native access.
|
|
30
|
+
* @param {string[]} [args.allowedOrigins] - Allowed CORS origins for browser access.
|
|
31
|
+
* @param {boolean} [args.redactArgs] - When true, job arguments are omitted from responses.
|
|
32
|
+
* @param {string} [args.databaseIdentifier] - Database identifier the jobs store reads from.
|
|
33
|
+
* @returns {void} - No return value.
|
|
34
|
+
*/
|
|
35
|
+
static mountInto({ accessTokens, allowedOrigins, at, authorize, configuration, databaseIdentifier, redactArgs }) {
|
|
36
|
+
if (!configuration)
|
|
37
|
+
throw new Error("No configuration given");
|
|
38
|
+
const prefix = normalizeMountPrefix(at);
|
|
39
|
+
registerJobsMount(configuration, prefix, { accessTokens, allowedOrigins, authorize, databaseIdentifier, redactArgs });
|
|
40
|
+
configuration.addRouteResolverHook(({ currentPath, request }) => {
|
|
41
|
+
const match = matchJobsApiPath({ method: request.httpMethod(), path: currentPath, prefix });
|
|
42
|
+
if (!match)
|
|
43
|
+
return null;
|
|
44
|
+
return {
|
|
45
|
+
action: match.action,
|
|
46
|
+
controller: "velociousBackgroundJobsWeb",
|
|
47
|
+
controllerClass: VelociousBackgroundJobsWebController,
|
|
48
|
+
params: { ...match.params, velociousJobsMountAt: prefix }
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL3dlYi9pbmRleC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxvQ0FBb0MsTUFBTSxpQkFBaUIsQ0FBQTtBQUNsRSxPQUFPLEVBQUMsZ0JBQWdCLEVBQUUsb0JBQW9CLEVBQUMsTUFBTSxtQkFBbUIsQ0FBQTtBQUN4RSxPQUFPLEVBQUMsaUJBQWlCLEVBQUMsTUFBTSxlQUFlLENBQUE7QUFFL0M7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sMEJBQTBCO0lBQzdDOzs7Ozs7Ozs7Ozs7OztPQWNHO0lBQ0gsTUFBTSxDQUFDLFNBQVMsQ0FBQyxFQUFDLFlBQVksRUFBRSxjQUFjLEVBQUUsRUFBRSxFQUFFLFNBQVMsRUFBRSxhQUFhLEVBQUUsa0JBQWtCLEVBQUUsVUFBVSxFQUFDO1FBQzNHLElBQUksQ0FBQyxhQUFhO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyx3QkFBd0IsQ0FBQyxDQUFBO1FBRTdELE1BQU0sTUFBTSxHQUFHLG9CQUFvQixDQUFDLEVBQUUsQ0FBQyxDQUFBO1FBRXZDLGlCQUFpQixDQUFDLGFBQWEsRUFBRSxNQUFNLEVBQUUsRUFBQyxZQUFZLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxVQUFVLEVBQUMsQ0FBQyxDQUFBO1FBRW5ILGFBQWEsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLEVBQUMsV0FBVyxFQUFFLE9BQU8sRUFBQyxFQUFFLEVBQUU7WUFDNUQsTUFBTSxLQUFLLEdBQUcsZ0JBQWdCLENBQUMsRUFBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFDLENBQUMsQ0FBQTtZQUV6RixJQUFJLENBQUMsS0FBSztnQkFBRSxPQUFPLElBQUksQ0FBQTtZQUV2QixPQUFPO2dCQUNMLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTTtnQkFDcEIsVUFBVSxFQUFFLDRCQUE0QjtnQkFDeEMsZUFBZSxFQUFFLG9DQUFvQztnQkFDckQsTUFBTSxFQUFFLEVBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLG9CQUFvQixFQUFFLE1BQU0sRUFBQzthQUN4RCxDQUFBO1FBQ0gsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IFZlbG9jaW91c0JhY2tncm91bmRKb2JzV2ViQ29udHJvbGxlciBmcm9tIFwiLi9jb250cm9sbGVyLmpzXCJcbmltcG9ydCB7bWF0Y2hKb2JzQXBpUGF0aCwgbm9ybWFsaXplTW91bnRQcmVmaXh9IGZyb20gXCIuL3BhdGgtbWF0Y2hlci5qc1wiXG5pbXBvcnQge3JlZ2lzdGVySm9ic01vdW50fSBmcm9tIFwiLi9yZWdpc3RyeS5qc1wiXG5cbi8qKlxuICogTW91bnRhYmxlIHJlYWQtb25seSBiYWNrZ3JvdW5kLWpvYnMgZGFzaGJvYXJkIEFQSS4gSW5jbHVkZSBpdCBpbiBhIHJvdXRlcyBmaWxlXG4gKiB0aGUgd2F5IFNpZGVraXE6OldlYiBpcyBtb3VudGVkIGluIFJhaWxzOlxuICpcbiAqIGBgYGpzXG4gKiByb3V0ZXMuZHJhdygocm91dGUpID0+IHtcbiAqICAgcm91dGUubW91bnQoVmVsb2Npb3VzQmFja2dyb3VuZEpvYnNBcGksIHtcbiAqICAgICBhdDogXCIvdmVsb2Npb3VzL2pvYnNcIixcbiAqICAgICBhdXRob3JpemU6IGFzeW5jICh7cmVxdWVzdCwgYWJpbGl0eX0pID0+IHsgLi4uIH0sXG4gKiAgICAgYWNjZXNzVG9rZW5zOiBbcHJvY2Vzcy5lbnYuVkVMT0NJT1VTX0pPQlNfVE9LRU5dXG4gKiAgIH0pXG4gKiB9KVxuICogYGBgXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFZlbG9jaW91c0JhY2tncm91bmRKb2JzQXBpIHtcbiAgLyoqXG4gICAqIFJlZ2lzdGVycyB0aGUgam9icyBBUEkgdW5kZXIgYGF0YC4gSW1wbGVtZW50ZWQgYXMgYSByb3V0ZS1yZXNvbHZlciBob29rIHNvXG4gICAqIHRoZSBjb250cm9sbGVyIGNhbiBsaXZlIGluc2lkZSB0aGUgdmVsb2Npb3VzIHBhY2thZ2UgcmF0aGVyIHRoYW4gdGhlIGhvc3RcbiAgICogYXBwJ3MgYHNyYy9yb3V0ZXNgIGRpcmVjdG9yeS4gSW52b2tlZCBieSB0aGUgcm91dGluZyBsYXllciBmb3IgZWFjaFxuICAgKiBgcm91dGUubW91bnQoLi4uKWAgcmVnaXN0cmF0aW9uLlxuICAgKiBAcGFyYW0ge29iamVjdH0gYXJncyAtIE9wdGlvbnMuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBhcmdzLmNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uIGluc3RhbmNlLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5hdCAtIE1vdW50IHBhdGggcHJlZml4IChlLmcuIFwiL3ZlbG9jaW91cy9qb2JzXCIpLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4vcmVnaXN0cnkuanNcIikuSm9ic01vdW50T3B0aW9uc1tcImF1dGhvcml6ZVwiXX0gW2FyZ3MuYXV0aG9yaXplXSAtIEF1dGhvcml6YXRpb24gY2FsbGJhY2suXG4gICAqIEBwYXJhbSB7c3RyaW5nW119IFthcmdzLmFjY2Vzc1Rva2Vuc10gLSBBY2NlcHRlZCBiZWFyZXIgdG9rZW5zIGZvciBjcm9zcy1vcmlnaW4vbmF0aXZlIGFjY2Vzcy5cbiAgICogQHBhcmFtIHtzdHJpbmdbXX0gW2FyZ3MuYWxsb3dlZE9yaWdpbnNdIC0gQWxsb3dlZCBDT1JTIG9yaWdpbnMgZm9yIGJyb3dzZXIgYWNjZXNzLlxuICAgKiBAcGFyYW0ge2Jvb2xlYW59IFthcmdzLnJlZGFjdEFyZ3NdIC0gV2hlbiB0cnVlLCBqb2IgYXJndW1lbnRzIGFyZSBvbWl0dGVkIGZyb20gcmVzcG9uc2VzLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gW2FyZ3MuZGF0YWJhc2VJZGVudGlmaWVyXSAtIERhdGFiYXNlIGlkZW50aWZpZXIgdGhlIGpvYnMgc3RvcmUgcmVhZHMgZnJvbS5cbiAgICogQHJldHVybnMge3ZvaWR9IC0gTm8gcmV0dXJuIHZhbHVlLlxuICAgKi9cbiAgc3RhdGljIG1vdW50SW50byh7YWNjZXNzVG9rZW5zLCBhbGxvd2VkT3JpZ2lucywgYXQsIGF1dGhvcml6ZSwgY29uZmlndXJhdGlvbiwgZGF0YWJhc2VJZGVudGlmaWVyLCByZWRhY3RBcmdzfSkge1xuICAgIGlmICghY29uZmlndXJhdGlvbikgdGhyb3cgbmV3IEVycm9yKFwiTm8gY29uZmlndXJhdGlvbiBnaXZlblwiKVxuXG4gICAgY29uc3QgcHJlZml4ID0gbm9ybWFsaXplTW91bnRQcmVmaXgoYXQpXG5cbiAgICByZWdpc3RlckpvYnNNb3VudChjb25maWd1cmF0aW9uLCBwcmVmaXgsIHthY2Nlc3NUb2tlbnMsIGFsbG93ZWRPcmlnaW5zLCBhdXRob3JpemUsIGRhdGFiYXNlSWRlbnRpZmllciwgcmVkYWN0QXJnc30pXG5cbiAgICBjb25maWd1cmF0aW9uLmFkZFJvdXRlUmVzb2x2ZXJIb29rKCh7Y3VycmVudFBhdGgsIHJlcXVlc3R9KSA9PiB7XG4gICAgICBjb25zdCBtYXRjaCA9IG1hdGNoSm9ic0FwaVBhdGgoe21ldGhvZDogcmVxdWVzdC5odHRwTWV0aG9kKCksIHBhdGg6IGN1cnJlbnRQYXRoLCBwcmVmaXh9KVxuXG4gICAgICBpZiAoIW1hdGNoKSByZXR1cm4gbnVsbFxuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBhY3Rpb246IG1hdGNoLmFjdGlvbixcbiAgICAgICAgY29udHJvbGxlcjogXCJ2ZWxvY2lvdXNCYWNrZ3JvdW5kSm9ic1dlYlwiLFxuICAgICAgICBjb250cm9sbGVyQ2xhc3M6IFZlbG9jaW91c0JhY2tncm91bmRKb2JzV2ViQ29udHJvbGxlcixcbiAgICAgICAgcGFyYW1zOiB7Li4ubWF0Y2gucGFyYW1zLCB2ZWxvY2lvdXNKb2JzTW91bnRBdDogcHJlZml4fVxuICAgICAgfVxuICAgIH0pXG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} JobsApiMatch
|
|
3
|
+
* @property {string} action - Controller action to run.
|
|
4
|
+
* @property {Record<string, string>} params - Extra params extracted from the path.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes a mount prefix: ensures a leading slash and strips any trailing
|
|
8
|
+
* slash so `/velocious/jobs/` and `/velocious/jobs` behave identically.
|
|
9
|
+
* @param {string} at - Raw mount prefix.
|
|
10
|
+
* @returns {string} - Normalized prefix.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeMountPrefix(at: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Matches an incoming request against the read-only jobs API routes that live
|
|
15
|
+
* under the mount prefix. Returns the controller action plus any extracted
|
|
16
|
+
* params, or null when the path/method isn't part of the jobs API.
|
|
17
|
+
* @param {object} args - Options.
|
|
18
|
+
* @param {string} args.prefix - Normalized mount prefix.
|
|
19
|
+
* @param {string} args.path - Request path without query string.
|
|
20
|
+
* @param {string} args.method - HTTP method.
|
|
21
|
+
* @returns {JobsApiMatch | null} - Matched action or null.
|
|
22
|
+
*/
|
|
23
|
+
export function matchJobsApiPath({ prefix, path, method }: {
|
|
24
|
+
prefix: string;
|
|
25
|
+
path: string;
|
|
26
|
+
method: string;
|
|
27
|
+
}): JobsApiMatch | null;
|
|
28
|
+
export type JobsApiMatch = {
|
|
29
|
+
/**
|
|
30
|
+
* - Controller action to run.
|
|
31
|
+
*/
|
|
32
|
+
action: string;
|
|
33
|
+
/**
|
|
34
|
+
* - Extra params extracted from the path.
|
|
35
|
+
*/
|
|
36
|
+
params: Record<string, string>;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=path-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-matcher.d.ts","sourceRoot":"","sources":["../../../../src/background-jobs/web/path-matcher.js"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH;;;;;GAKG;AACH,yCAHW,MAAM,GACJ,MAAM,CAYlB;AAED;;;;;;;;;GASG;AACH,2DALG;IAAqB,MAAM,EAAnB,MAAM;IACO,IAAI,EAAjB,MAAM;IACO,MAAM,EAAnB,MAAM;CACd,GAAU,YAAY,GAAG,IAAI,CAuC/B;;;;;YArEa,MAAM;;;;YACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {object} JobsApiMatch
|
|
4
|
+
* @property {string} action - Controller action to run.
|
|
5
|
+
* @property {Record<string, string>} params - Extra params extracted from the path.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes a mount prefix: ensures a leading slash and strips any trailing
|
|
9
|
+
* slash so `/velocious/jobs/` and `/velocious/jobs` behave identically.
|
|
10
|
+
* @param {string} at - Raw mount prefix.
|
|
11
|
+
* @returns {string} - Normalized prefix.
|
|
12
|
+
*/
|
|
13
|
+
export function normalizeMountPrefix(at) {
|
|
14
|
+
if (typeof at !== "string" || !at.startsWith("/")) {
|
|
15
|
+
throw new Error(`mount requires an 'at' path starting with '/', got: ${String(at)}`);
|
|
16
|
+
}
|
|
17
|
+
if (at.length > 1 && at.endsWith("/")) {
|
|
18
|
+
return at.slice(0, -1);
|
|
19
|
+
}
|
|
20
|
+
return at;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Matches an incoming request against the read-only jobs API routes that live
|
|
24
|
+
* under the mount prefix. Returns the controller action plus any extracted
|
|
25
|
+
* params, or null when the path/method isn't part of the jobs API.
|
|
26
|
+
* @param {object} args - Options.
|
|
27
|
+
* @param {string} args.prefix - Normalized mount prefix.
|
|
28
|
+
* @param {string} args.path - Request path without query string.
|
|
29
|
+
* @param {string} args.method - HTTP method.
|
|
30
|
+
* @returns {JobsApiMatch | null} - Matched action or null.
|
|
31
|
+
*/
|
|
32
|
+
export function matchJobsApiPath({ prefix, path, method }) {
|
|
33
|
+
/** @type {string} */
|
|
34
|
+
let subPath;
|
|
35
|
+
if (prefix === "/") {
|
|
36
|
+
// Root mount: the whole path is the sub-path (avoid building a "//" guard).
|
|
37
|
+
subPath = path;
|
|
38
|
+
}
|
|
39
|
+
else if (path === prefix) {
|
|
40
|
+
subPath = "/";
|
|
41
|
+
}
|
|
42
|
+
else if (path.startsWith(`${prefix}/`)) {
|
|
43
|
+
subPath = path.slice(prefix.length);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (method === "GET" && subPath === "/api/health")
|
|
49
|
+
return { action: "health", params: {} };
|
|
50
|
+
if (method === "GET" && subPath === "/api/stats")
|
|
51
|
+
return { action: "stats", params: {} };
|
|
52
|
+
if (method === "GET" && subPath === "/api/schedule")
|
|
53
|
+
return { action: "schedule", params: {} };
|
|
54
|
+
if (method === "GET" && subPath === "/api/jobs")
|
|
55
|
+
return { action: "index", params: {} };
|
|
56
|
+
if (method === "GET") {
|
|
57
|
+
const jobMatch = subPath.match(/^\/api\/jobs\/([^/]+)$/);
|
|
58
|
+
if (jobMatch) {
|
|
59
|
+
let id;
|
|
60
|
+
try {
|
|
61
|
+
id = decodeURIComponent(jobMatch[1]);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return { action: "show", params: { id } };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aC1tYXRjaGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2JhY2tncm91bmQtam9icy93ZWIvcGF0aC1tYXRjaGVyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWjs7OztHQUlHO0FBRUg7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsb0JBQW9CLENBQUMsRUFBRTtJQUNyQyxJQUFJLE9BQU8sRUFBRSxLQUFLLFFBQVEsSUFBSSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUNsRCxNQUFNLElBQUksS0FBSyxDQUFDLHVEQUF1RCxNQUFNLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBQ3RGLENBQUM7SUFFRCxJQUFJLEVBQUUsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN0QyxPQUFPLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDeEIsQ0FBQztJQUVELE9BQU8sRUFBRSxDQUFBO0FBQ1gsQ0FBQztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxFQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFDO0lBQ3JELHFCQUFxQjtJQUNyQixJQUFJLE9BQU8sQ0FBQTtJQUVYLElBQUksTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDO1FBQ25CLDRFQUE0RTtRQUM1RSxPQUFPLEdBQUcsSUFBSSxDQUFBO0lBQ2hCLENBQUM7U0FBTSxJQUFJLElBQUksS0FBSyxNQUFNLEVBQUUsQ0FBQztRQUMzQixPQUFPLEdBQUcsR0FBRyxDQUFBO0lBQ2YsQ0FBQztTQUFNLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN6QyxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUE7SUFDckMsQ0FBQztTQUFNLENBQUM7UUFDTixPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFFRCxJQUFJLE1BQU0sS0FBSyxLQUFLLElBQUksT0FBTyxLQUFLLGFBQWE7UUFBRSxPQUFPLEVBQUMsTUFBTSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFDLENBQUE7SUFDeEYsSUFBSSxNQUFNLEtBQUssS0FBSyxJQUFJLE9BQU8sS0FBSyxZQUFZO1FBQUUsT0FBTyxFQUFDLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBQyxDQUFBO0lBQ3RGLElBQUksTUFBTSxLQUFLLEtBQUssSUFBSSxPQUFPLEtBQUssZUFBZTtRQUFFLE9BQU8sRUFBQyxNQUFNLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUMsQ0FBQTtJQUM1RixJQUFJLE1BQU0sS0FBSyxLQUFLLElBQUksT0FBTyxLQUFLLFdBQVc7UUFBRSxPQUFPLEVBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFDLENBQUE7SUFFckYsSUFBSSxNQUFNLEtBQUssS0FBSyxFQUFFLENBQUM7UUFDckIsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyx3QkFBd0IsQ0FBQyxDQUFBO1FBRXhELElBQUksUUFBUSxFQUFFLENBQUM7WUFDYixJQUFJLEVBQUUsQ0FBQTtZQUVOLElBQUksQ0FBQztnQkFDSCxFQUFFLEdBQUcsa0JBQWtCLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7WUFDdEMsQ0FBQztZQUFDLE1BQU0sQ0FBQztnQkFDUCxPQUFPLElBQUksQ0FBQTtZQUNiLENBQUM7WUFFRCxPQUFPLEVBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsRUFBQyxFQUFFLEVBQUMsRUFBQyxDQUFBO1FBQ3ZDLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxJQUFJLENBQUE7QUFDYixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbi8qKlxuICogQHR5cGVkZWYge29iamVjdH0gSm9ic0FwaU1hdGNoXG4gKiBAcHJvcGVydHkge3N0cmluZ30gYWN0aW9uIC0gQ29udHJvbGxlciBhY3Rpb24gdG8gcnVuLlxuICogQHByb3BlcnR5IHtSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+fSBwYXJhbXMgLSBFeHRyYSBwYXJhbXMgZXh0cmFjdGVkIGZyb20gdGhlIHBhdGguXG4gKi9cblxuLyoqXG4gKiBOb3JtYWxpemVzIGEgbW91bnQgcHJlZml4OiBlbnN1cmVzIGEgbGVhZGluZyBzbGFzaCBhbmQgc3RyaXBzIGFueSB0cmFpbGluZ1xuICogc2xhc2ggc28gYC92ZWxvY2lvdXMvam9icy9gIGFuZCBgL3ZlbG9jaW91cy9qb2JzYCBiZWhhdmUgaWRlbnRpY2FsbHkuXG4gKiBAcGFyYW0ge3N0cmluZ30gYXQgLSBSYXcgbW91bnQgcHJlZml4LlxuICogQHJldHVybnMge3N0cmluZ30gLSBOb3JtYWxpemVkIHByZWZpeC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5vcm1hbGl6ZU1vdW50UHJlZml4KGF0KSB7XG4gIGlmICh0eXBlb2YgYXQgIT09IFwic3RyaW5nXCIgfHwgIWF0LnN0YXJ0c1dpdGgoXCIvXCIpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBtb3VudCByZXF1aXJlcyBhbiAnYXQnIHBhdGggc3RhcnRpbmcgd2l0aCAnLycsIGdvdDogJHtTdHJpbmcoYXQpfWApXG4gIH1cblxuICBpZiAoYXQubGVuZ3RoID4gMSAmJiBhdC5lbmRzV2l0aChcIi9cIikpIHtcbiAgICByZXR1cm4gYXQuc2xpY2UoMCwgLTEpXG4gIH1cblxuICByZXR1cm4gYXRcbn1cblxuLyoqXG4gKiBNYXRjaGVzIGFuIGluY29taW5nIHJlcXVlc3QgYWdhaW5zdCB0aGUgcmVhZC1vbmx5IGpvYnMgQVBJIHJvdXRlcyB0aGF0IGxpdmVcbiAqIHVuZGVyIHRoZSBtb3VudCBwcmVmaXguIFJldHVybnMgdGhlIGNvbnRyb2xsZXIgYWN0aW9uIHBsdXMgYW55IGV4dHJhY3RlZFxuICogcGFyYW1zLCBvciBudWxsIHdoZW4gdGhlIHBhdGgvbWV0aG9kIGlzbid0IHBhcnQgb2YgdGhlIGpvYnMgQVBJLlxuICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MucHJlZml4IC0gTm9ybWFsaXplZCBtb3VudCBwcmVmaXguXG4gKiBAcGFyYW0ge3N0cmluZ30gYXJncy5wYXRoIC0gUmVxdWVzdCBwYXRoIHdpdGhvdXQgcXVlcnkgc3RyaW5nLlxuICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MubWV0aG9kIC0gSFRUUCBtZXRob2QuXG4gKiBAcmV0dXJucyB7Sm9ic0FwaU1hdGNoIHwgbnVsbH0gLSBNYXRjaGVkIGFjdGlvbiBvciBudWxsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbWF0Y2hKb2JzQXBpUGF0aCh7cHJlZml4LCBwYXRoLCBtZXRob2R9KSB7XG4gIC8qKiBAdHlwZSB7c3RyaW5nfSAqL1xuICBsZXQgc3ViUGF0aFxuXG4gIGlmIChwcmVmaXggPT09IFwiL1wiKSB7XG4gICAgLy8gUm9vdCBtb3VudDogdGhlIHdob2xlIHBhdGggaXMgdGhlIHN1Yi1wYXRoIChhdm9pZCBidWlsZGluZyBhIFwiLy9cIiBndWFyZCkuXG4gICAgc3ViUGF0aCA9IHBhdGhcbiAgfSBlbHNlIGlmIChwYXRoID09PSBwcmVmaXgpIHtcbiAgICBzdWJQYXRoID0gXCIvXCJcbiAgfSBlbHNlIGlmIChwYXRoLnN0YXJ0c1dpdGgoYCR7cHJlZml4fS9gKSkge1xuICAgIHN1YlBhdGggPSBwYXRoLnNsaWNlKHByZWZpeC5sZW5ndGgpXG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIG51bGxcbiAgfVxuXG4gIGlmIChtZXRob2QgPT09IFwiR0VUXCIgJiYgc3ViUGF0aCA9PT0gXCIvYXBpL2hlYWx0aFwiKSByZXR1cm4ge2FjdGlvbjogXCJoZWFsdGhcIiwgcGFyYW1zOiB7fX1cbiAgaWYgKG1ldGhvZCA9PT0gXCJHRVRcIiAmJiBzdWJQYXRoID09PSBcIi9hcGkvc3RhdHNcIikgcmV0dXJuIHthY3Rpb246IFwic3RhdHNcIiwgcGFyYW1zOiB7fX1cbiAgaWYgKG1ldGhvZCA9PT0gXCJHRVRcIiAmJiBzdWJQYXRoID09PSBcIi9hcGkvc2NoZWR1bGVcIikgcmV0dXJuIHthY3Rpb246IFwic2NoZWR1bGVcIiwgcGFyYW1zOiB7fX1cbiAgaWYgKG1ldGhvZCA9PT0gXCJHRVRcIiAmJiBzdWJQYXRoID09PSBcIi9hcGkvam9ic1wiKSByZXR1cm4ge2FjdGlvbjogXCJpbmRleFwiLCBwYXJhbXM6IHt9fVxuXG4gIGlmIChtZXRob2QgPT09IFwiR0VUXCIpIHtcbiAgICBjb25zdCBqb2JNYXRjaCA9IHN1YlBhdGgubWF0Y2goL15cXC9hcGlcXC9qb2JzXFwvKFteL10rKSQvKVxuXG4gICAgaWYgKGpvYk1hdGNoKSB7XG4gICAgICBsZXQgaWRcblxuICAgICAgdHJ5IHtcbiAgICAgICAgaWQgPSBkZWNvZGVVUklDb21wb25lbnQoam9iTWF0Y2hbMV0pXG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgcmV0dXJuIG51bGxcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHthY3Rpb246IFwic2hvd1wiLCBwYXJhbXM6IHtpZH19XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIG51bGxcbn1cbiJdfQ==
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import("../../configuration.js").default} configuration - Configuration instance.
|
|
3
|
+
* @param {string} at - Normalized mount path.
|
|
4
|
+
* @param {JobsMountOptions} options - Mount options.
|
|
5
|
+
* @returns {void} - No return value.
|
|
6
|
+
*/
|
|
7
|
+
export function registerJobsMount(configuration: import("../../configuration.js").default, at: string, options: JobsMountOptions): void;
|
|
8
|
+
/**
|
|
9
|
+
* @param {import("../../configuration.js").default} configuration - Configuration instance.
|
|
10
|
+
* @param {string} at - Normalized mount path.
|
|
11
|
+
* @returns {JobsMountOptions | undefined} - Mount options if registered.
|
|
12
|
+
*/
|
|
13
|
+
export function getJobsMount(configuration: import("../../configuration.js").default, at: string): JobsMountOptions | undefined;
|
|
14
|
+
export type JobsMountOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* - Authorization callback. Return true to allow the request.
|
|
17
|
+
*/
|
|
18
|
+
authorize?: ((args: {
|
|
19
|
+
request: import("../../http-server/client/request.js").default;
|
|
20
|
+
ability: (import("../../authorization/ability.js").default | undefined);
|
|
21
|
+
token: (string | null);
|
|
22
|
+
configuration: import("../../configuration.js").default;
|
|
23
|
+
}) => (boolean | void | Promise<boolean | void>)) | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* - Bearer tokens accepted for cross-origin/native access.
|
|
26
|
+
*/
|
|
27
|
+
accessTokens?: string[] | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* - Origins allowed for cross-origin browser access.
|
|
30
|
+
*/
|
|
31
|
+
allowedOrigins?: string[] | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* - When true, job arguments are omitted from API responses.
|
|
34
|
+
*/
|
|
35
|
+
redactArgs?: boolean | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* - Database identifier the jobs store reads from.
|
|
38
|
+
*/
|
|
39
|
+
databaseIdentifier?: string | undefined;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../src/background-jobs/web/registry.js"],"names":[],"mappings":"AAqBA;;;;;GAKG;AACH,iDALW,OAAO,wBAAwB,EAAE,OAAO,MACxC,MAAM,WACN,gBAAgB,GACd,IAAI,CAWhB;AAED;;;;GAIG;AACH,4CAJW,OAAO,wBAAwB,EAAE,OAAO,MACxC,MAAM,GACJ,gBAAgB,GAAG,SAAS,CAIxC;;;;;wBAzCoB;QAAC,OAAO,EAAE,OAAO,qCAAqC,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,CAAC,OAAO,gCAAgC,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC;QAAC,KAAK,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QAAC,aAAa,EAAE,OAAO,wBAAwB,EAAE,OAAO,CAAA;KAAC,KAAK,CAAC,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {object} JobsMountOptions
|
|
4
|
+
* @property {(args: {request: import("../../http-server/client/request.js").default, ability: (import("../../authorization/ability.js").default | undefined), token: (string | null), configuration: import("../../configuration.js").default}) => (boolean | void | Promise<boolean | void>)} [authorize] - Authorization callback. Return true to allow the request.
|
|
5
|
+
* @property {string[]} [accessTokens] - Bearer tokens accepted for cross-origin/native access.
|
|
6
|
+
* @property {string[]} [allowedOrigins] - Origins allowed for cross-origin browser access.
|
|
7
|
+
* @property {boolean} [redactArgs] - When true, job arguments are omitted from API responses.
|
|
8
|
+
* @property {string} [databaseIdentifier] - Database identifier the jobs store reads from.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Mount options are keyed by configuration so multiple configurations (e.g.
|
|
12
|
+
* across tests) never share state, and by mount path so a single configuration
|
|
13
|
+
* can mount the dashboard at more than one prefix. Functions in the options
|
|
14
|
+
* (the `authorize` callback) can't travel through route params, so the
|
|
15
|
+
* controller looks them up here using the plain `at` string it receives.
|
|
16
|
+
* @type {WeakMap<import("../../configuration.js").default, Map<string, JobsMountOptions>>}
|
|
17
|
+
*/
|
|
18
|
+
const registry = new WeakMap();
|
|
19
|
+
/**
|
|
20
|
+
* @param {import("../../configuration.js").default} configuration - Configuration instance.
|
|
21
|
+
* @param {string} at - Normalized mount path.
|
|
22
|
+
* @param {JobsMountOptions} options - Mount options.
|
|
23
|
+
* @returns {void} - No return value.
|
|
24
|
+
*/
|
|
25
|
+
export function registerJobsMount(configuration, at, options) {
|
|
26
|
+
let byPath = registry.get(configuration);
|
|
27
|
+
if (!byPath) {
|
|
28
|
+
byPath = new Map();
|
|
29
|
+
registry.set(configuration, byPath);
|
|
30
|
+
}
|
|
31
|
+
byPath.set(at, options);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @param {import("../../configuration.js").default} configuration - Configuration instance.
|
|
35
|
+
* @param {string} at - Normalized mount path.
|
|
36
|
+
* @returns {JobsMountOptions | undefined} - Mount options if registered.
|
|
37
|
+
*/
|
|
38
|
+
export function getJobsMount(configuration, at) {
|
|
39
|
+
return registry.get(configuration)?.get(at);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVnaXN0cnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL3dlYi9yZWdpc3RyeS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVo7Ozs7Ozs7R0FPRztBQUVIOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLFFBQVEsR0FBRyxJQUFJLE9BQU8sRUFBRSxDQUFBO0FBRTlCOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLGFBQWEsRUFBRSxFQUFFLEVBQUUsT0FBTztJQUMxRCxJQUFJLE1BQU0sR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFBO0lBRXhDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNaLE1BQU0sR0FBRyxJQUFJLEdBQUcsRUFBRSxDQUFBO1FBQ2xCLFFBQVEsQ0FBQyxHQUFHLENBQUMsYUFBYSxFQUFFLE1BQU0sQ0FBQyxDQUFBO0lBQ3JDLENBQUM7SUFFRCxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxPQUFPLENBQUMsQ0FBQTtBQUN6QixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxZQUFZLENBQUMsYUFBYSxFQUFFLEVBQUU7SUFDNUMsT0FBTyxRQUFRLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQTtBQUM3QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbi8qKlxuICogQHR5cGVkZWYge29iamVjdH0gSm9ic01vdW50T3B0aW9uc1xuICogQHByb3BlcnR5IHsoYXJnczoge3JlcXVlc3Q6IGltcG9ydChcIi4uLy4uL2h0dHAtc2VydmVyL2NsaWVudC9yZXF1ZXN0LmpzXCIpLmRlZmF1bHQsIGFiaWxpdHk6IChpbXBvcnQoXCIuLi8uLi9hdXRob3JpemF0aW9uL2FiaWxpdHkuanNcIikuZGVmYXVsdCB8IHVuZGVmaW5lZCksIHRva2VuOiAoc3RyaW5nIHwgbnVsbCksIGNvbmZpZ3VyYXRpb246IGltcG9ydChcIi4uLy4uL2NvbmZpZ3VyYXRpb24uanNcIikuZGVmYXVsdH0pID0+IChib29sZWFuIHwgdm9pZCB8IFByb21pc2U8Ym9vbGVhbiB8IHZvaWQ+KX0gW2F1dGhvcml6ZV0gLSBBdXRob3JpemF0aW9uIGNhbGxiYWNrLiBSZXR1cm4gdHJ1ZSB0byBhbGxvdyB0aGUgcmVxdWVzdC5cbiAqIEBwcm9wZXJ0eSB7c3RyaW5nW119IFthY2Nlc3NUb2tlbnNdIC0gQmVhcmVyIHRva2VucyBhY2NlcHRlZCBmb3IgY3Jvc3Mtb3JpZ2luL25hdGl2ZSBhY2Nlc3MuXG4gKiBAcHJvcGVydHkge3N0cmluZ1tdfSBbYWxsb3dlZE9yaWdpbnNdIC0gT3JpZ2lucyBhbGxvd2VkIGZvciBjcm9zcy1vcmlnaW4gYnJvd3NlciBhY2Nlc3MuXG4gKiBAcHJvcGVydHkge2Jvb2xlYW59IFtyZWRhY3RBcmdzXSAtIFdoZW4gdHJ1ZSwgam9iIGFyZ3VtZW50cyBhcmUgb21pdHRlZCBmcm9tIEFQSSByZXNwb25zZXMuXG4gKiBAcHJvcGVydHkge3N0cmluZ30gW2RhdGFiYXNlSWRlbnRpZmllcl0gLSBEYXRhYmFzZSBpZGVudGlmaWVyIHRoZSBqb2JzIHN0b3JlIHJlYWRzIGZyb20uXG4gKi9cblxuLyoqXG4gKiBNb3VudCBvcHRpb25zIGFyZSBrZXllZCBieSBjb25maWd1cmF0aW9uIHNvIG11bHRpcGxlIGNvbmZpZ3VyYXRpb25zIChlLmcuXG4gKiBhY3Jvc3MgdGVzdHMpIG5ldmVyIHNoYXJlIHN0YXRlLCBhbmQgYnkgbW91bnQgcGF0aCBzbyBhIHNpbmdsZSBjb25maWd1cmF0aW9uXG4gKiBjYW4gbW91bnQgdGhlIGRhc2hib2FyZCBhdCBtb3JlIHRoYW4gb25lIHByZWZpeC4gRnVuY3Rpb25zIGluIHRoZSBvcHRpb25zXG4gKiAodGhlIGBhdXRob3JpemVgIGNhbGxiYWNrKSBjYW4ndCB0cmF2ZWwgdGhyb3VnaCByb3V0ZSBwYXJhbXMsIHNvIHRoZVxuICogY29udHJvbGxlciBsb29rcyB0aGVtIHVwIGhlcmUgdXNpbmcgdGhlIHBsYWluIGBhdGAgc3RyaW5nIGl0IHJlY2VpdmVzLlxuICogQHR5cGUge1dlYWtNYXA8aW1wb3J0KFwiLi4vLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0LCBNYXA8c3RyaW5nLCBKb2JzTW91bnRPcHRpb25zPj59XG4gKi9cbmNvbnN0IHJlZ2lzdHJ5ID0gbmV3IFdlYWtNYXAoKVxuXG4vKipcbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBjb25maWd1cmF0aW9uIC0gQ29uZmlndXJhdGlvbiBpbnN0YW5jZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBhdCAtIE5vcm1hbGl6ZWQgbW91bnQgcGF0aC5cbiAqIEBwYXJhbSB7Sm9ic01vdW50T3B0aW9uc30gb3B0aW9ucyAtIE1vdW50IG9wdGlvbnMuXG4gKiBAcmV0dXJucyB7dm9pZH0gLSBObyByZXR1cm4gdmFsdWUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWdpc3RlckpvYnNNb3VudChjb25maWd1cmF0aW9uLCBhdCwgb3B0aW9ucykge1xuICBsZXQgYnlQYXRoID0gcmVnaXN0cnkuZ2V0KGNvbmZpZ3VyYXRpb24pXG5cbiAgaWYgKCFieVBhdGgpIHtcbiAgICBieVBhdGggPSBuZXcgTWFwKClcbiAgICByZWdpc3RyeS5zZXQoY29uZmlndXJhdGlvbiwgYnlQYXRoKVxuICB9XG5cbiAgYnlQYXRoLnNldChhdCwgb3B0aW9ucylcbn1cblxuLyoqXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4uLy4uL2NvbmZpZ3VyYXRpb24uanNcIikuZGVmYXVsdH0gY29uZmlndXJhdGlvbiAtIENvbmZpZ3VyYXRpb24gaW5zdGFuY2UuXG4gKiBAcGFyYW0ge3N0cmluZ30gYXQgLSBOb3JtYWxpemVkIG1vdW50IHBhdGguXG4gKiBAcmV0dXJucyB7Sm9ic01vdW50T3B0aW9ucyB8IHVuZGVmaW5lZH0gLSBNb3VudCBvcHRpb25zIGlmIHJlZ2lzdGVyZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRKb2JzTW91bnQoY29uZmlndXJhdGlvbiwgYXQpIHtcbiAgcmV0dXJuIHJlZ2lzdHJ5LmdldChjb25maWd1cmF0aW9uKT8uZ2V0KGF0KVxufVxuIl19
|
|
@@ -84,6 +84,8 @@ export default class VelociousConfiguration {
|
|
|
84
84
|
_logging: import("./configuration-types.js").LoggingConfiguration | undefined;
|
|
85
85
|
_mailerBackend: import("./configuration-types.js").MailerBackend | undefined;
|
|
86
86
|
_routeResolverHooks: import("./configuration-types.js").RouteResolverHookType[];
|
|
87
|
+
/** @type {WeakSet<object>} */
|
|
88
|
+
_appliedRouteMounts: WeakSet<object>;
|
|
87
89
|
_errorEvents: import("eventemitter3").EventEmitter<string | symbol, any>;
|
|
88
90
|
/** @type {{[key: string]: import("./database/pool/base.js").default}} */
|
|
89
91
|
databasePools: {
|
|
@@ -481,6 +483,15 @@ export default class VelociousConfiguration {
|
|
|
481
483
|
*/
|
|
482
484
|
setRoutes(newRoutes: import("./routes/index.js").default): void;
|
|
483
485
|
_routes: import("./routes/index.js").default | undefined;
|
|
486
|
+
/**
|
|
487
|
+
* Applies any `route.mount(...)` registrations from the routes file by letting
|
|
488
|
+
* each mountable register its routes (typically route-resolver hooks) against
|
|
489
|
+
* this configuration. Guarded so repeated setRoutes calls with the same routes
|
|
490
|
+
* don't register a mount more than once.
|
|
491
|
+
* @param {import("./routes/index.js").default} newRoutes - Routes instance.
|
|
492
|
+
* @returns {void} - No return value.
|
|
493
|
+
*/
|
|
494
|
+
_applyRouteMounts(newRoutes: import("./routes/index.js").default): void;
|
|
484
495
|
/**
|
|
485
496
|
* Adds plugin/library routes using a lightweight route DSL backed by route resolver hooks.
|
|
486
497
|
* @param {(routes: import("./routes/plugin-routes.js").default) => void} callback - Routes callback.
|