@azlib/scheduler 1.1.0 → 1.2.0

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.
@@ -22,9 +22,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  //#endregion
23
23
  const require_store_dashboard = require("./store-dashboard-Sg4BBWiJ.cjs");
24
24
  let _azlib_std = require("@azlib/std");
25
- let node_fs = require("node:fs");
26
25
  let node_http = require("node:http");
27
26
  node_http = __toESM(node_http, 1);
27
+ let node_fs = require("node:fs");
28
28
  let node_path = require("node:path");
29
29
  let node_url = require("node:url");
30
30
  let node_async_hooks = require("node:async_hooks");
@@ -102,6 +102,36 @@ function parseUpdateJobBody(body) {
102
102
  return patch;
103
103
  }
104
104
  //#endregion
105
+ //#region src/dashboard/http-path.ts
106
+ function normalizeDashboardBasePath(basePath) {
107
+ const trimmed = basePath?.trim();
108
+ if (!trimmed || trimmed === "/") return "";
109
+ return (trimmed.startsWith("/") ? trimmed : `/${trimmed}`).replace(/\/+$/, "");
110
+ }
111
+ function stripDashboardBasePath(pathname, basePath) {
112
+ const base = normalizeDashboardBasePath(basePath);
113
+ const path = pathname || "/";
114
+ if (!base) return path.startsWith("/") ? path : `/${path}`;
115
+ if (path === base || path === `${base}/`) return "/";
116
+ if (path.startsWith(`${base}/`)) {
117
+ const rest = path.slice(base.length);
118
+ return rest.startsWith("/") ? rest : `/${rest}`;
119
+ }
120
+ return null;
121
+ }
122
+ function resolveDashboardApiBase(pathname) {
123
+ const trimmed = (pathname.split("?")[0] || "/").replace(/\/index\.html$/i, "/").replace(/\/[^/]+\.[a-z0-9]+$/i, "/").replace(/\/+$/, "");
124
+ return trimmed === "" ? "/api" : `${trimmed}/api`;
125
+ }
126
+ function requestPathname(url, originalUrl) {
127
+ const raw = originalUrl || url || "/";
128
+ try {
129
+ return new URL(raw, "http://127.0.0.1").pathname;
130
+ } catch {
131
+ return raw.split("?")[0] || "/";
132
+ }
133
+ }
134
+ //#endregion
105
135
  //#region src/dashboard/public-dir.ts
106
136
  function resolveDashboardPublicDir(fromFileUrl) {
107
137
  const dir = (0, node_path.dirname)((0, node_url.fileURLToPath)(fromFileUrl));
@@ -112,7 +142,7 @@ function resolveDashboardPublicDir(fromFileUrl) {
112
142
  return nested;
113
143
  }
114
144
  //#endregion
115
- //#region src/dashboard/http-server.ts
145
+ //#region src/dashboard/http-handler.ts
116
146
  const MIME_TYPES = {
117
147
  ".css": "text/css; charset=utf-8",
118
148
  ".html": "text/html; charset=utf-8",
@@ -126,7 +156,7 @@ function sendJson(res, status, body) {
126
156
  res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
127
157
  res.end(JSON.stringify(body));
128
158
  }
129
- function sendError(res, error) {
159
+ function sendDashboardError(res, error) {
130
160
  if (error instanceof DashboardHttpError) {
131
161
  sendJson(res, error.status, { error: error.message });
132
162
  return;
@@ -134,10 +164,17 @@ function sendError(res, error) {
134
164
  const message = error instanceof Error ? error.message : "Unknown error";
135
165
  sendJson(res, message.includes("not found") ? 404 : message.includes("Only failed") ? 400 : message.includes("Unknown handlerKey") ? 400 : 500, { error: message });
136
166
  }
167
+ function hasParsedBody(req) {
168
+ return Object.prototype.hasOwnProperty.call(req, "body");
169
+ }
137
170
  async function readJsonBody(req) {
171
+ if (req.readableEnded && hasParsedBody(req)) return req.body ?? {};
138
172
  const chunks = [];
139
173
  for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
140
- if (chunks.length === 0) return {};
174
+ if (chunks.length === 0) {
175
+ if (hasParsedBody(req)) return req.body ?? {};
176
+ return {};
177
+ }
141
178
  const raw = Buffer.concat(chunks).toString("utf8");
142
179
  if (!raw.trim()) return {};
143
180
  try {
@@ -175,115 +212,158 @@ function servePublicFile(publicDir, urlPath, res) {
175
212
  (0, node_fs.createReadStream)(filePath).pipe(res);
176
213
  return true;
177
214
  }
178
- function createSchedulerDashboardServer(options) {
179
- const port = options.port ?? 9100;
180
- const host = options.host ?? "127.0.0.1";
181
- const publicDir = options.publicDir ?? resolveDashboardPublicDir(require("url").pathToFileURL(__filename).href);
182
- const dashboard = options.dashboard;
183
- const server = node_http.createServer((req, res) => {
184
- handleRequest(req, res).catch((error) => {
185
- if (!res.headersSent) sendError(res, error);
215
+ async function authorizeApi(options, method, pathname, req) {
216
+ if (options.authorize) {
217
+ await options.authorize({
218
+ method,
219
+ pathname,
220
+ authorization: req.headers.authorization
186
221
  });
222
+ return;
223
+ }
224
+ authorizeDashboardRequest({
225
+ token: options.token,
226
+ authorization: req.headers.authorization
187
227
  });
188
- async function handleRequest(req, res) {
189
- const method = req.method ?? "GET";
190
- const pathname = new URL(req.url ?? "/", `http://${host}:${port}`).pathname;
191
- if (method === "GET" && pathname === "/health") {
192
- res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
193
- res.end("ok");
228
+ }
229
+ async function handleApi(dashboard, method, pathname, req, res) {
230
+ if (method === "GET" && pathname === "/api/health") {
231
+ sendJson(res, 200, await dashboard.getHealth());
232
+ return;
233
+ }
234
+ if (method === "GET" && pathname === "/api/handlers") {
235
+ sendJson(res, 200, { handlers: dashboard.listHandlerKeys() });
236
+ return;
237
+ }
238
+ if (method === "GET" && pathname === "/api/jobs") {
239
+ sendJson(res, 200, { items: await dashboard.listItems() });
240
+ return;
241
+ }
242
+ if (method === "POST" && pathname === "/api/jobs") {
243
+ sendJson(res, 201, await dashboard.createJob(parseCreateJobBody(await readJsonBody(req))));
244
+ return;
245
+ }
246
+ const jobExec = matchPath(pathname, "/api/jobs/:jobId/executions");
247
+ if (jobExec && method === "GET") {
248
+ sendJson(res, 200, { executions: await dashboard.listExecutions(jobExec.jobId ?? "") });
249
+ return;
250
+ }
251
+ const jobAction = matchPath(pathname, "/api/jobs/:jobId/:action");
252
+ if (jobAction && method === "POST") {
253
+ const jobId = jobAction.jobId ?? "";
254
+ if (jobAction.action === "pause") {
255
+ await dashboard.pauseJob(jobId);
256
+ res.writeHead(204);
257
+ res.end();
194
258
  return;
195
259
  }
196
- if (pathname === "/api" || pathname.startsWith("/api/")) {
197
- authorizeDashboardRequest({
198
- token: options.token,
199
- authorization: req.headers.authorization
200
- });
201
- await handleApi(method, pathname, req, res);
260
+ if (jobAction.action === "resume") {
261
+ await dashboard.resumeJob(jobId);
262
+ res.writeHead(204);
263
+ res.end();
264
+ return;
265
+ }
266
+ if (jobAction.action === "run") {
267
+ await dashboard.runJob(jobId);
268
+ res.writeHead(204);
269
+ res.end();
270
+ return;
271
+ }
272
+ }
273
+ const retry = matchPath(pathname, "/api/executions/:executionId/retry");
274
+ if (retry && method === "POST") {
275
+ await dashboard.retryExecution(retry.executionId ?? "");
276
+ res.writeHead(204);
277
+ res.end();
278
+ return;
279
+ }
280
+ const job = matchPath(pathname, "/api/jobs/:jobId");
281
+ if (job) {
282
+ const jobId = job.jobId ?? "";
283
+ if (method === "GET") {
284
+ const item = await dashboard.getJob(jobId);
285
+ if (!item) throw new DashboardHttpError(404, `Job not found: ${jobId}`);
286
+ sendJson(res, 200, item);
287
+ return;
288
+ }
289
+ if (method === "PATCH") {
290
+ await dashboard.updateJob(jobId, parseUpdateJobBody(await readJsonBody(req)));
291
+ res.writeHead(204);
292
+ res.end();
202
293
  return;
203
294
  }
204
- if (method === "GET" && servePublicFile(publicDir, pathname, res)) return;
295
+ if (method === "DELETE") {
296
+ await dashboard.deleteJob(jobId);
297
+ res.writeHead(204);
298
+ res.end();
299
+ return;
300
+ }
301
+ }
302
+ sendJson(res, 404, { error: "Not Found" });
303
+ }
304
+ async function handleSchedulerDashboardRequest(req, res, options) {
305
+ const method = req.method ?? "GET";
306
+ const originalUrl = "originalUrl" in req && typeof req.originalUrl === "string" ? req.originalUrl : void 0;
307
+ const pathname = requestPathname(req.url, originalUrl);
308
+ const basePath = normalizeDashboardBasePath(options.basePath);
309
+ const relative = stripDashboardBasePath(pathname, basePath);
310
+ if (relative === null) return false;
311
+ if (method === "GET" && basePath && pathname === basePath) {
312
+ res.writeHead(302, { Location: `${basePath}/` });
313
+ res.end();
314
+ return true;
315
+ }
316
+ const publicDir = options.publicDir ?? resolveDashboardPublicDir(require("url").pathToFileURL(__filename).href);
317
+ try {
318
+ if (method === "GET" && relative === "/health") {
319
+ res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
320
+ res.end("ok");
321
+ return true;
322
+ }
323
+ if (relative === "/api" || relative.startsWith("/api/")) {
324
+ await authorizeApi(options, method, relative, req);
325
+ await handleApi(options.dashboard, method, relative, req, res);
326
+ return true;
327
+ }
328
+ if (method === "GET" && servePublicFile(publicDir, relative, res)) return true;
205
329
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
206
330
  res.end("Not Found");
331
+ return true;
332
+ } catch (error) {
333
+ sendDashboardError(res, error);
334
+ return true;
207
335
  }
208
- async function handleApi(method, pathname, req, res) {
209
- try {
210
- if (method === "GET" && pathname === "/api/health") {
211
- sendJson(res, 200, await dashboard.getHealth());
212
- return;
213
- }
214
- if (method === "GET" && pathname === "/api/handlers") {
215
- sendJson(res, 200, { handlers: dashboard.listHandlerKeys() });
216
- return;
217
- }
218
- if (method === "GET" && pathname === "/api/jobs") {
219
- sendJson(res, 200, { items: await dashboard.listItems() });
220
- return;
221
- }
222
- if (method === "POST" && pathname === "/api/jobs") {
223
- sendJson(res, 201, await dashboard.createJob(parseCreateJobBody(await readJsonBody(req))));
224
- return;
225
- }
226
- const jobExec = matchPath(pathname, "/api/jobs/:jobId/executions");
227
- if (jobExec && method === "GET") {
228
- sendJson(res, 200, { executions: await dashboard.listExecutions(jobExec.jobId ?? "") });
229
- return;
230
- }
231
- const jobAction = matchPath(pathname, "/api/jobs/:jobId/:action");
232
- if (jobAction && method === "POST") {
233
- const jobId = jobAction.jobId ?? "";
234
- if (jobAction.action === "pause") {
235
- await dashboard.pauseJob(jobId);
236
- res.writeHead(204);
237
- res.end();
238
- return;
239
- }
240
- if (jobAction.action === "resume") {
241
- await dashboard.resumeJob(jobId);
242
- res.writeHead(204);
243
- res.end();
244
- return;
245
- }
246
- if (jobAction.action === "run") {
247
- await dashboard.runJob(jobId);
248
- res.writeHead(204);
249
- res.end();
250
- return;
251
- }
252
- }
253
- const retry = matchPath(pathname, "/api/executions/:executionId/retry");
254
- if (retry && method === "POST") {
255
- await dashboard.retryExecution(retry.executionId ?? "");
256
- res.writeHead(204);
257
- res.end();
336
+ }
337
+ function createSchedulerDashboardNodeHandler(options) {
338
+ return (req, res, next) => {
339
+ handleSchedulerDashboardRequest(req, res, options).then((handled) => {
340
+ if (!handled) next?.();
341
+ }).catch((error) => {
342
+ if (next) {
343
+ next(error);
258
344
  return;
259
345
  }
260
- const job = matchPath(pathname, "/api/jobs/:jobId");
261
- if (job) {
262
- const jobId = job.jobId ?? "";
263
- if (method === "GET") {
264
- const item = await dashboard.getJob(jobId);
265
- if (!item) throw new DashboardHttpError(404, `Job not found: ${jobId}`);
266
- sendJson(res, 200, item);
267
- return;
268
- }
269
- if (method === "PATCH") {
270
- await dashboard.updateJob(jobId, parseUpdateJobBody(await readJsonBody(req)));
271
- res.writeHead(204);
272
- res.end();
273
- return;
274
- }
275
- if (method === "DELETE") {
276
- await dashboard.deleteJob(jobId);
277
- res.writeHead(204);
278
- res.end();
279
- return;
280
- }
346
+ if (!res.headersSent) sendDashboardError(res, error);
347
+ });
348
+ };
349
+ }
350
+ //#endregion
351
+ //#region src/dashboard/http-server.ts
352
+ function createSchedulerDashboardServer(options) {
353
+ const port = options.port ?? 9100;
354
+ const host = options.host ?? "127.0.0.1";
355
+ const handler = createSchedulerDashboardNodeHandler({
356
+ ...options,
357
+ publicDir: options.publicDir ?? resolveDashboardPublicDir(require("url").pathToFileURL(__filename).href)
358
+ });
359
+ const server = node_http.createServer((req, res) => {
360
+ handler(req, res, () => {
361
+ if (!res.headersSent) {
362
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
363
+ res.end("Not Found");
281
364
  }
282
- sendJson(res, 404, { error: "Not Found" });
283
- } catch (error) {
284
- sendError(res, error);
285
- }
286
- }
365
+ });
366
+ });
287
367
  return {
288
368
  get port() {
289
369
  return port;
@@ -480,6 +560,12 @@ function parseDashboardCliEnv(env) {
480
560
  };
481
561
  }
482
562
  //#endregion
563
+ Object.defineProperty(exports, "DashboardHttpError", {
564
+ enumerable: true,
565
+ get: function() {
566
+ return DashboardHttpError;
567
+ }
568
+ });
483
569
  Object.defineProperty(exports, "createDashboardPersistenceConfig", {
484
570
  enumerable: true,
485
571
  get: function() {
@@ -492,21 +578,51 @@ Object.defineProperty(exports, "createDashboardSqlDriver", {
492
578
  return createDashboardSqlDriver;
493
579
  }
494
580
  });
581
+ Object.defineProperty(exports, "createSchedulerDashboardNodeHandler", {
582
+ enumerable: true,
583
+ get: function() {
584
+ return createSchedulerDashboardNodeHandler;
585
+ }
586
+ });
495
587
  Object.defineProperty(exports, "createSchedulerDashboardServer", {
496
588
  enumerable: true,
497
589
  get: function() {
498
590
  return createSchedulerDashboardServer;
499
591
  }
500
592
  });
593
+ Object.defineProperty(exports, "handleSchedulerDashboardRequest", {
594
+ enumerable: true,
595
+ get: function() {
596
+ return handleSchedulerDashboardRequest;
597
+ }
598
+ });
599
+ Object.defineProperty(exports, "normalizeDashboardBasePath", {
600
+ enumerable: true,
601
+ get: function() {
602
+ return normalizeDashboardBasePath;
603
+ }
604
+ });
501
605
  Object.defineProperty(exports, "parseDashboardCliEnv", {
502
606
  enumerable: true,
503
607
  get: function() {
504
608
  return parseDashboardCliEnv;
505
609
  }
506
610
  });
611
+ Object.defineProperty(exports, "resolveDashboardApiBase", {
612
+ enumerable: true,
613
+ get: function() {
614
+ return resolveDashboardApiBase;
615
+ }
616
+ });
507
617
  Object.defineProperty(exports, "resolveDashboardPublicDir", {
508
618
  enumerable: true,
509
619
  get: function() {
510
620
  return resolveDashboardPublicDir;
511
621
  }
512
622
  });
623
+ Object.defineProperty(exports, "stripDashboardBasePath", {
624
+ enumerable: true,
625
+ get: function() {
626
+ return stripDashboardBasePath;
627
+ }
628
+ });
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_cli_config = require("../cli-config-Cz8QP_4I.cjs");
3
+ const require_cli_config = require("../cli-config-CFBl-YOh.cjs");
4
4
  const require_store_dashboard = require("../store-dashboard-Sg4BBWiJ.cjs");
5
5
  let node_url = require("node:url");
6
6
  //#region src/dashboard/cli.ts
@@ -1,4 +1,4 @@
1
- import { t as SchedulerDashboardServer } from "../http-server-DN6Lg464.cjs";
1
+ import { t as SchedulerDashboardServer } from "../http-server-u1BnbkAK.cjs";
2
2
  //#region src/dashboard/cli.d.ts
3
3
  declare function startSchedulerDashboardCli(env?: Record<string, string | undefined>): Promise<SchedulerDashboardServer>;
4
4
  //#endregion
@@ -1,4 +1,4 @@
1
- import { t as SchedulerDashboardServer } from "../http-server-DPQ9HIK-.mjs";
1
+ import { t as SchedulerDashboardServer } from "../http-server-Cme4XKyH.mjs";
2
2
  //#region src/dashboard/cli.d.ts
3
3
  declare function startSchedulerDashboardCli(env?: Record<string, string | undefined>): Promise<SchedulerDashboardServer>;
4
4
  //#endregion
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as createSchedulerDashboardFromPersistence } from "../store-dashboard-Cyf_JILa.mjs";
3
- import { a as resolveDashboardPublicDir, i as createSchedulerDashboardServer, n as createDashboardPersistenceConfig, r as createDashboardSqlDriver, t as parseDashboardCliEnv } from "../cli-config-7F2U8qPM.mjs";
3
+ import { i as createSchedulerDashboardServer, n as createDashboardPersistenceConfig, r as createDashboardSqlDriver, s as resolveDashboardPublicDir, t as parseDashboardCliEnv } from "../cli-config-6u8AzSdw.mjs";
4
4
  import { pathToFileURL } from "node:url";
5
5
  //#region src/dashboard/cli.ts
6
6
  async function startSchedulerDashboardCli(env = process.env) {