@cronvello/sdk 0.2.1 → 0.4.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.
- package/CHANGELOG.md +56 -0
- package/README.md +102 -15
- package/dist/cli.cjs +990 -216
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +990 -216
- package/dist/cli.js.map +1 -1
- package/dist/dev.cjs +1001 -217
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +5 -0
- package/dist/dev.d.ts +5 -0
- package/dist/dev.js +1001 -217
- package/dist/dev.js.map +1 -1
- package/dist/{dispatch-handler-BoadpsL9.d.cts → dispatch-handler-yHIFEnNG.d.cts} +17 -5
- package/dist/{dispatch-handler-BoadpsL9.d.ts → dispatch-handler-yHIFEnNG.d.ts} +17 -5
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.cjs +191 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +280 -10
- package/dist/index.d.ts +280 -10
- package/dist/index.js +191 -26
- package/dist/index.js.map +1 -1
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/docs/dashboard.png +0 -0
- package/package.json +2 -1
|
@@ -434,16 +434,28 @@ interface CronvelloAppConfig {
|
|
|
434
434
|
/**
|
|
435
435
|
* Public base URL of THIS app, where Cronvello delivers callbacks.
|
|
436
436
|
* e.g. "https://app.example.com". The dispatch handler is mounted under it.
|
|
437
|
+
*
|
|
438
|
+
* Only needed for the hosted side (`sync()`, `dispatchUrl`). Local runs — `cronvello dev`,
|
|
439
|
+
* `trigger()` — never leave the machine, so you can leave it out entirely.
|
|
437
440
|
*/
|
|
438
|
-
appUrl
|
|
439
|
-
/**
|
|
440
|
-
|
|
441
|
+
appUrl?: string;
|
|
442
|
+
/**
|
|
443
|
+
* Cronvello account API key (`crn_live_…`).
|
|
444
|
+
*
|
|
445
|
+
* Only needed for the hosted side (`sync()`, `run()`, `client`). Leave it out to run
|
|
446
|
+
* locally with no account.
|
|
447
|
+
*/
|
|
448
|
+
apiKey?: string;
|
|
441
449
|
/**
|
|
442
450
|
* Shared secret. Cronvello sends it back as `Authorization: Bearer <secret>` on every
|
|
443
451
|
* dispatch; the mounted handler verifies it in constant time. Generate a strong random
|
|
444
452
|
* value and store it in your env (e.g. `openssl rand -hex 32`).
|
|
453
|
+
*
|
|
454
|
+
* Only needed to accept dispatches from the hosted side. Without it there is no HTTP
|
|
455
|
+
* entry point to protect, so local runs don't require one — and any mounted handler
|
|
456
|
+
* refuses every request rather than running unauthenticated.
|
|
445
457
|
*/
|
|
446
|
-
dispatchSecret
|
|
458
|
+
dispatchSecret?: string;
|
|
447
459
|
/** Path the dispatch handler is mounted at. Default "/cronvello/dispatch". */
|
|
448
460
|
dispatchPath?: string;
|
|
449
461
|
/** Cronvello API base URL. Default "https://api.cronvello.com". */
|
|
@@ -550,4 +562,4 @@ interface DispatchHandler {
|
|
|
550
562
|
handle(req: DispatchRequest): Promise<DispatchResponse>;
|
|
551
563
|
}
|
|
552
564
|
|
|
553
|
-
export { type AccountTasksQuery as A, type
|
|
565
|
+
export { type AccountTasksQuery as A, type ReconcileTaskChange as B, type CronvelloAppConfig as C, type DispatchRequest as D, type ExecutionMode as E, type FetchLike as F, type RegistryReconcileChange as G, type HttpMethod as H, type RegistryTaskInput as I, type JobCreateBody as J, type RunStatus as K, type RunType as L, type MeResponse as M, type SuccessCriteria as N, type DispatchHandler as O, type PublicJob as P, type RunNowResult as R, type SyncOptions as S, Transport as T, type UsageResponse as U, type JobUpdateBody as a, type PublicTask as b, type TaskCreateBody as c, type TasksPage as d, type TaskUpdateBody as e, type RunsQuery as f, type RunsPage as g, type AccountRunsQuery as h, type PublicRun as i, type RegistryReconcileRequest as j, type RegistryReconcileResponse as k, type ResolvedJob as l, type ReconcileResult as m, type DispatchResponse as n, type Urgency as o, type CallbackStatus as p, type CronvelloHooks as q, type CronvelloJobConfig as r, type CronvelloJobConfigWithKey as s, type CronvelloJobContext as t, type CronvelloJobHandler as u, type CronvelloJobsInput as v, type CronvelloLogger as w, type CronvelloRunSource as x, type JobStatus as y, type Pagination as z };
|
|
@@ -434,16 +434,28 @@ interface CronvelloAppConfig {
|
|
|
434
434
|
/**
|
|
435
435
|
* Public base URL of THIS app, where Cronvello delivers callbacks.
|
|
436
436
|
* e.g. "https://app.example.com". The dispatch handler is mounted under it.
|
|
437
|
+
*
|
|
438
|
+
* Only needed for the hosted side (`sync()`, `dispatchUrl`). Local runs — `cronvello dev`,
|
|
439
|
+
* `trigger()` — never leave the machine, so you can leave it out entirely.
|
|
437
440
|
*/
|
|
438
|
-
appUrl
|
|
439
|
-
/**
|
|
440
|
-
|
|
441
|
+
appUrl?: string;
|
|
442
|
+
/**
|
|
443
|
+
* Cronvello account API key (`crn_live_…`).
|
|
444
|
+
*
|
|
445
|
+
* Only needed for the hosted side (`sync()`, `run()`, `client`). Leave it out to run
|
|
446
|
+
* locally with no account.
|
|
447
|
+
*/
|
|
448
|
+
apiKey?: string;
|
|
441
449
|
/**
|
|
442
450
|
* Shared secret. Cronvello sends it back as `Authorization: Bearer <secret>` on every
|
|
443
451
|
* dispatch; the mounted handler verifies it in constant time. Generate a strong random
|
|
444
452
|
* value and store it in your env (e.g. `openssl rand -hex 32`).
|
|
453
|
+
*
|
|
454
|
+
* Only needed to accept dispatches from the hosted side. Without it there is no HTTP
|
|
455
|
+
* entry point to protect, so local runs don't require one — and any mounted handler
|
|
456
|
+
* refuses every request rather than running unauthenticated.
|
|
445
457
|
*/
|
|
446
|
-
dispatchSecret
|
|
458
|
+
dispatchSecret?: string;
|
|
447
459
|
/** Path the dispatch handler is mounted at. Default "/cronvello/dispatch". */
|
|
448
460
|
dispatchPath?: string;
|
|
449
461
|
/** Cronvello API base URL. Default "https://api.cronvello.com". */
|
|
@@ -550,4 +562,4 @@ interface DispatchHandler {
|
|
|
550
562
|
handle(req: DispatchRequest): Promise<DispatchResponse>;
|
|
551
563
|
}
|
|
552
564
|
|
|
553
|
-
export { type AccountTasksQuery as A, type
|
|
565
|
+
export { type AccountTasksQuery as A, type ReconcileTaskChange as B, type CronvelloAppConfig as C, type DispatchRequest as D, type ExecutionMode as E, type FetchLike as F, type RegistryReconcileChange as G, type HttpMethod as H, type RegistryTaskInput as I, type JobCreateBody as J, type RunStatus as K, type RunType as L, type MeResponse as M, type SuccessCriteria as N, type DispatchHandler as O, type PublicJob as P, type RunNowResult as R, type SyncOptions as S, Transport as T, type UsageResponse as U, type JobUpdateBody as a, type PublicTask as b, type TaskCreateBody as c, type TasksPage as d, type TaskUpdateBody as e, type RunsQuery as f, type RunsPage as g, type AccountRunsQuery as h, type PublicRun as i, type RegistryReconcileRequest as j, type RegistryReconcileResponse as k, type ResolvedJob as l, type ReconcileResult as m, type DispatchResponse as n, type Urgency as o, type CallbackStatus as p, type CronvelloHooks as q, type CronvelloJobConfig as r, type CronvelloJobConfigWithKey as s, type CronvelloJobContext as t, type CronvelloJobHandler as u, type CronvelloJobsInput as v, type CronvelloLogger as w, type CronvelloRunSource as x, type JobStatus as y, type Pagination as z };
|
package/dist/express.d.cts
CHANGED
package/dist/express.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -545,8 +545,13 @@ function createDispatcher(state) {
|
|
|
545
545
|
if (req.method.toUpperCase() !== "POST") {
|
|
546
546
|
return resp(405, { ok: false, error: "Method not allowed" });
|
|
547
547
|
}
|
|
548
|
+
const secret = state.dispatchSecret;
|
|
549
|
+
if (!secret) {
|
|
550
|
+
log.error?.("[cronvello] dispatch refused: no `dispatchSecret` configured on this app");
|
|
551
|
+
return resp(500, { ok: false, error: "Dispatch is not configured" });
|
|
552
|
+
}
|
|
548
553
|
const token = parseBearer(req.authorization);
|
|
549
|
-
if (!token || !timingSafeEqual(token,
|
|
554
|
+
if (!token || !timingSafeEqual(token, secret)) {
|
|
550
555
|
log.warn?.("[cronvello] dispatch rejected: bad or missing bearer token");
|
|
551
556
|
return resp(401, { ok: false, error: "Unauthorized" });
|
|
552
557
|
}
|
|
@@ -577,7 +582,7 @@ function createDispatcher(state) {
|
|
|
577
582
|
const isAsync = callback !== null;
|
|
578
583
|
const ctx = buildContext(key, job, req, body, isAsync, "dispatch");
|
|
579
584
|
if (callback) {
|
|
580
|
-
const work = runAndReportCallback(job, ctx, callback,
|
|
585
|
+
const work = runAndReportCallback(job, ctx, callback, secret, log, invokeHandler);
|
|
581
586
|
if (req.waitUntil) req.waitUntil(work);
|
|
582
587
|
return resp(202, { ok: true, job: key, accepted: true });
|
|
583
588
|
}
|
|
@@ -1189,6 +1194,7 @@ var LocalEngine = class {
|
|
|
1189
1194
|
started = false;
|
|
1190
1195
|
stopped = false;
|
|
1191
1196
|
signalCleanup = null;
|
|
1197
|
+
startedAtMs = null;
|
|
1192
1198
|
constructor(jobs, runner, options = {}) {
|
|
1193
1199
|
this.clock = options.clock ?? realClock;
|
|
1194
1200
|
if (options.onEvent) this.listeners.add(options.onEvent);
|
|
@@ -1237,7 +1243,8 @@ var LocalEngine = class {
|
|
|
1237
1243
|
if (this.started) return this;
|
|
1238
1244
|
this.started = true;
|
|
1239
1245
|
this.stopped = false;
|
|
1240
|
-
this.
|
|
1246
|
+
this.startedAtMs = this.clock.now();
|
|
1247
|
+
this.emit({ type: "engine-start", jobs: this.states.length, at: this.startedAtMs });
|
|
1241
1248
|
for (const state of this.states) {
|
|
1242
1249
|
if (state.isReboot) {
|
|
1243
1250
|
this.launch(state);
|
|
@@ -1301,6 +1308,14 @@ var LocalEngine = class {
|
|
|
1301
1308
|
get activeRuns() {
|
|
1302
1309
|
return this.inFlight.size;
|
|
1303
1310
|
}
|
|
1311
|
+
/** Epoch ms of the `start()` call, or null while the engine has never been started. */
|
|
1312
|
+
get startedAt() {
|
|
1313
|
+
return this.startedAtMs;
|
|
1314
|
+
}
|
|
1315
|
+
/** True between `start()` and `stop()`. */
|
|
1316
|
+
get running() {
|
|
1317
|
+
return this.started && !this.stopped;
|
|
1318
|
+
}
|
|
1304
1319
|
// ── Scheduling ─────────────────────────────────────────────────────────────
|
|
1305
1320
|
scheduleNext(state) {
|
|
1306
1321
|
if (this.stopped) return;
|
|
@@ -1495,14 +1510,20 @@ function defineCronvello(config) {
|
|
|
1495
1510
|
}
|
|
1496
1511
|
const jobs = normalizeJobs(config.jobs, validate);
|
|
1497
1512
|
const dispatchPath = normalizePath(config.dispatchPath ?? DEFAULT_DISPATCH_PATH);
|
|
1498
|
-
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1513
|
+
let clientInstance;
|
|
1514
|
+
const getClient = () => {
|
|
1515
|
+
if (!clientInstance) {
|
|
1516
|
+
requireCloud(config, ["apiKey"], "Talking to the Cronvello API");
|
|
1517
|
+
clientInstance = new CronvelloClient({
|
|
1518
|
+
apiKey: config.apiKey,
|
|
1519
|
+
baseUrl: config.baseUrl ?? CRONVELLO_DEFAULT_BASE_URL,
|
|
1520
|
+
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
|
|
1521
|
+
...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {},
|
|
1522
|
+
...config.fetch ? { fetch: config.fetch } : {}
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
return clientInstance;
|
|
1526
|
+
};
|
|
1506
1527
|
const dispatcher = createDispatcher({
|
|
1507
1528
|
jobs,
|
|
1508
1529
|
dispatchSecret: config.dispatchSecret,
|
|
@@ -1511,17 +1532,29 @@ function defineCronvello(config) {
|
|
|
1511
1532
|
...config.maxBodyBytes !== void 0 ? { maxBodyBytes: config.maxBodyBytes } : {}
|
|
1512
1533
|
});
|
|
1513
1534
|
const app = {
|
|
1514
|
-
client
|
|
1535
|
+
get client() {
|
|
1536
|
+
return getClient();
|
|
1537
|
+
},
|
|
1515
1538
|
jobs,
|
|
1516
1539
|
appName: config.appName,
|
|
1517
1540
|
dispatchPath,
|
|
1518
|
-
dispatchUrl
|
|
1541
|
+
get dispatchUrl() {
|
|
1542
|
+
requireCloud(config, ["appUrl"], "Building the dispatch URL");
|
|
1543
|
+
return joinUrl(config.appUrl, dispatchPath);
|
|
1544
|
+
},
|
|
1545
|
+
get isCloudConfigured() {
|
|
1546
|
+
return missingCloudFields(config, CLOUD_FIELDS).length === 0;
|
|
1547
|
+
},
|
|
1519
1548
|
async sync(options) {
|
|
1549
|
+
requireCloud(config, CLOUD_FIELDS, "Syncing your jobs to Cronvello");
|
|
1550
|
+
const client = getClient();
|
|
1551
|
+
const dispatchUrl = joinUrl(config.appUrl, dispatchPath);
|
|
1552
|
+
const dispatchSecret = config.dispatchSecret;
|
|
1520
1553
|
const opts = options ?? {};
|
|
1521
1554
|
if (!opts.dryRun) {
|
|
1522
1555
|
try {
|
|
1523
1556
|
const res = await client.reconcileRegistry(
|
|
1524
|
-
buildRegistryRequest(config, dispatchUrl, defaultTimeZone, [...jobs.values()], opts)
|
|
1557
|
+
buildRegistryRequest(config, dispatchSecret, dispatchUrl, defaultTimeZone, [...jobs.values()], opts)
|
|
1525
1558
|
);
|
|
1526
1559
|
return {
|
|
1527
1560
|
jobId: res.job.id,
|
|
@@ -1549,7 +1582,7 @@ function defineCronvello(config) {
|
|
|
1549
1582
|
client,
|
|
1550
1583
|
appName: config.appName,
|
|
1551
1584
|
dispatchUrl,
|
|
1552
|
-
dispatchSecret
|
|
1585
|
+
dispatchSecret,
|
|
1553
1586
|
defaultTimeZone,
|
|
1554
1587
|
jobs: [...jobs.values()]
|
|
1555
1588
|
},
|
|
@@ -1560,6 +1593,7 @@ function defineCronvello(config) {
|
|
|
1560
1593
|
if (!jobs.has(key)) {
|
|
1561
1594
|
throw new CronvelloConfigError(`Unknown job '${key}'. Known: ${[...jobs.keys()].join(", ") || "(none)"}`);
|
|
1562
1595
|
}
|
|
1596
|
+
const client = getClient();
|
|
1563
1597
|
const containers = await client.jobs.list();
|
|
1564
1598
|
const container = containers.find((j) => j.name === config.appName);
|
|
1565
1599
|
if (!container) {
|
|
@@ -1574,8 +1608,16 @@ function defineCronvello(config) {
|
|
|
1574
1608
|
},
|
|
1575
1609
|
trigger: (key, payload) => dispatcher.runLocal(key, payload),
|
|
1576
1610
|
handle: dispatcher.handle,
|
|
1577
|
-
|
|
1578
|
-
|
|
1611
|
+
// Mounting a dispatch route that could only ever answer "not configured" hides the real
|
|
1612
|
+
// mistake behind a runtime 500, so both adapters fail at mount time instead.
|
|
1613
|
+
expressHandler: () => {
|
|
1614
|
+
requireCloud(config, ["dispatchSecret"], "Mounting the dispatch handler");
|
|
1615
|
+
return expressHandler(app);
|
|
1616
|
+
},
|
|
1617
|
+
nextHandler: () => {
|
|
1618
|
+
requireCloud(config, ["dispatchSecret"], "Mounting the dispatch handler");
|
|
1619
|
+
return nextHandler(app);
|
|
1620
|
+
},
|
|
1579
1621
|
dev(options) {
|
|
1580
1622
|
const { autoStart = true, dashboard, ...engineOptions } = options ?? {};
|
|
1581
1623
|
const engineJobs = buildEngineJobs([...jobs.values()], defaultTimeZone);
|
|
@@ -1625,7 +1667,9 @@ function buildEngineJobs(jobs, defaultTimeZone) {
|
|
|
1625
1667
|
!appUrl && "CRONVELLO_APP_URL (or PUBLIC_URL)"
|
|
1626
1668
|
].filter(Boolean);
|
|
1627
1669
|
if (missing.length) {
|
|
1628
|
-
throw new CronvelloConfigError(
|
|
1670
|
+
throw new CronvelloConfigError(
|
|
1671
|
+
`defineCronvello.fromEnv() is missing required env: ${missing.join(", ")}. To run locally with no account, use defineCronvello({ appName, jobs }) instead \u2014 it needs none of these.`
|
|
1672
|
+
);
|
|
1629
1673
|
}
|
|
1630
1674
|
return defineCronvello2({
|
|
1631
1675
|
...config,
|
|
@@ -1637,17 +1681,32 @@ function buildEngineJobs(jobs, defaultTimeZone) {
|
|
|
1637
1681
|
}
|
|
1638
1682
|
defineCronvello2.fromEnv = fromEnv;
|
|
1639
1683
|
})(defineCronvello || (defineCronvello = {}));
|
|
1684
|
+
var CLOUD_FIELDS = ["apiKey", "appUrl", "dispatchSecret"];
|
|
1685
|
+
var CLOUD_FIELD_HINT = {
|
|
1686
|
+
apiKey: "`apiKey` (crn_live_\u2026, from your Cronvello account)",
|
|
1687
|
+
appUrl: "`appUrl` (the public https URL of THIS app, where Cronvello delivers callbacks)",
|
|
1688
|
+
dispatchSecret: "`dispatchSecret` (a random 32-byte value: run `npx cronvello secret`)"
|
|
1689
|
+
};
|
|
1690
|
+
function missingCloudFields(config, fields) {
|
|
1691
|
+
return fields.filter((f) => !config[f]);
|
|
1692
|
+
}
|
|
1693
|
+
function requireCloud(config, fields, purpose) {
|
|
1694
|
+
const missing = missingCloudFields(config, fields);
|
|
1695
|
+
if (!missing.length) return;
|
|
1696
|
+
throw new CronvelloConfigError(
|
|
1697
|
+
`${purpose} needs config this app doesn't have: ${missing.map((f) => CLOUD_FIELD_HINT[f]).join(", ")}. Local runs (\`cronvello dev\`, \`trigger()\`) work without any of it.`
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1640
1700
|
function validateConfig(config) {
|
|
1641
1701
|
if (!config) throw new CronvelloConfigError("defineCronvello requires a config object.");
|
|
1642
1702
|
if (!config.appName || !config.appName.trim()) throw new CronvelloConfigError("`appName` is required.");
|
|
1643
|
-
if (!config.
|
|
1703
|
+
if (!config.jobs) throw new CronvelloConfigError("`jobs` is required.");
|
|
1704
|
+
if (config.appUrl !== void 0 && !/^https?:\/\//i.test(config.appUrl)) {
|
|
1644
1705
|
throw new CronvelloConfigError("`appUrl` must be an absolute http(s) URL (the public URL of THIS app).");
|
|
1645
1706
|
}
|
|
1646
|
-
if (
|
|
1647
|
-
|
|
1648
|
-
throw new CronvelloConfigError("`dispatchSecret` is required and must be at least 16 chars (use a random 32-byte value).");
|
|
1707
|
+
if (config.dispatchSecret !== void 0 && config.dispatchSecret.length < 16) {
|
|
1708
|
+
throw new CronvelloConfigError("`dispatchSecret` must be at least 16 chars (use a random 32-byte value).");
|
|
1649
1709
|
}
|
|
1650
|
-
if (!config.jobs) throw new CronvelloConfigError("`jobs` is required.");
|
|
1651
1710
|
}
|
|
1652
1711
|
function normalizeJobs(input, validate) {
|
|
1653
1712
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -1684,14 +1743,14 @@ function normalizePath(path) {
|
|
|
1684
1743
|
function joinUrl(base, path) {
|
|
1685
1744
|
return `${base.replace(/\/+$/, "")}${path}`;
|
|
1686
1745
|
}
|
|
1687
|
-
function buildRegistryRequest(config, dispatchUrl, defaultTimeZone, jobs, opts) {
|
|
1746
|
+
function buildRegistryRequest(config, dispatchSecret, dispatchUrl, defaultTimeZone, jobs, opts) {
|
|
1688
1747
|
const tasks = jobs.map((j) => {
|
|
1689
1748
|
const cfg = j.config;
|
|
1690
1749
|
const task = {
|
|
1691
1750
|
key: j.key,
|
|
1692
1751
|
schedule: cfg.schedule,
|
|
1693
1752
|
targetUrl: dispatchUrl,
|
|
1694
|
-
targetToken:
|
|
1753
|
+
targetToken: dispatchSecret,
|
|
1695
1754
|
method: "POST",
|
|
1696
1755
|
timeZone: cfg.timeZone ?? defaultTimeZone,
|
|
1697
1756
|
requestBody: JSON.stringify({ job: j.key, ...cfg.payload ?? {} })
|
|
@@ -1857,6 +1916,112 @@ function field(value, min, max, label) {
|
|
|
1857
1916
|
return value;
|
|
1858
1917
|
}
|
|
1859
1918
|
|
|
1919
|
+
// src/client/admin-client.ts
|
|
1920
|
+
var CronvelloAdminClient = class {
|
|
1921
|
+
transport;
|
|
1922
|
+
/** The resolved base URL in use. */
|
|
1923
|
+
baseUrl;
|
|
1924
|
+
/** Registration, status, key rotation and removal of external apps. */
|
|
1925
|
+
externalApps;
|
|
1926
|
+
constructor(options) {
|
|
1927
|
+
if (!options || !options.serviceKey) {
|
|
1928
|
+
throw new CronvelloConfigError(
|
|
1929
|
+
"CronvelloAdminClient requires a `serviceKey` (Cronvello's backend-to-backend key, not an account apiKey)."
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
this.baseUrl = (options.baseUrl ?? CRONVELLO_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1933
|
+
this.transport = new Transport({
|
|
1934
|
+
baseUrl: this.baseUrl,
|
|
1935
|
+
apiKey: options.serviceKey,
|
|
1936
|
+
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
1937
|
+
...options.maxRetries !== void 0 ? { maxRetries: options.maxRetries } : {},
|
|
1938
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
1939
|
+
...options.onRequest ? { onRequest: options.onRequest } : {}
|
|
1940
|
+
});
|
|
1941
|
+
this.externalApps = new ExternalAppsResource(this.transport);
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
var ExternalAppsResource = class {
|
|
1945
|
+
constructor(t) {
|
|
1946
|
+
this.t = t;
|
|
1947
|
+
}
|
|
1948
|
+
t;
|
|
1949
|
+
/**
|
|
1950
|
+
* Idempotent upsert, keyed on the string `appId`: creates when unknown, updates credentials
|
|
1951
|
+
* and URL when it already exists. Safe to retry and to re-run on every provisioning pass.
|
|
1952
|
+
*
|
|
1953
|
+
* When the server mints the token (`generateApiKey: true` on a *new* app) it comes back as
|
|
1954
|
+
* `generatedApiKey` — plaintext, exactly once. On an update it is `null`.
|
|
1955
|
+
*/
|
|
1956
|
+
register(input) {
|
|
1957
|
+
assertRegisterInput(input);
|
|
1958
|
+
return this.t.request({ method: "POST", path: "/external-apps/service/register", body: input });
|
|
1959
|
+
}
|
|
1960
|
+
/** Registration status, last-sync info and job count for one app, by its string `appId`. */
|
|
1961
|
+
status(appId) {
|
|
1962
|
+
assertAppId(appId, "status");
|
|
1963
|
+
return this.t.request({ method: "GET", path: `/external-apps/service/status/${enc2(appId)}` });
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Mint a fresh per-app token, by string `appId`. The new token is returned once as `newApiKey`
|
|
1967
|
+
* and must be written into the app's environment — the previous one stops working.
|
|
1968
|
+
*
|
|
1969
|
+
* Use this for drift recovery when the current token is no longer known.
|
|
1970
|
+
*/
|
|
1971
|
+
rotateKey(appId) {
|
|
1972
|
+
assertAppId(appId, "rotateKey");
|
|
1973
|
+
return this.t.request({ method: "POST", path: `/external-apps/service/rotate-key/${enc2(appId)}` });
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Delete a registration, cascading its jobs and tasks.
|
|
1977
|
+
*
|
|
1978
|
+
* ⚠ This one takes the **numeric** {@link ExternalApp.id}, not the string `appId` the other
|
|
1979
|
+
* three methods take — an asymmetry in the server's route contract. Read the id off a
|
|
1980
|
+
* `register()` result (or a prior lookup); passing a string `appId` here is rejected locally.
|
|
1981
|
+
*/
|
|
1982
|
+
delete(id) {
|
|
1983
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
1984
|
+
throw new CronvelloConfigError(
|
|
1985
|
+
`externalApps.delete() takes the numeric app id (ExternalApp.id), not the string appId \u2014 received ${JSON.stringify(id)}.`
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
return this.t.request({ method: "DELETE", path: `/external-apps/service/delete/${id}` });
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
function assertRegisterInput(input) {
|
|
1992
|
+
if (!input || typeof input !== "object") {
|
|
1993
|
+
throw new CronvelloConfigError("externalApps.register() requires an input object.");
|
|
1994
|
+
}
|
|
1995
|
+
if (!input.appId || !input.appId.trim()) {
|
|
1996
|
+
throw new CronvelloConfigError("externalApps.register() requires a non-empty `appId`.");
|
|
1997
|
+
}
|
|
1998
|
+
if (!input.name || !input.name.trim()) {
|
|
1999
|
+
throw new CronvelloConfigError("externalApps.register() requires a non-empty `name`.");
|
|
2000
|
+
}
|
|
2001
|
+
if (!input.base_url && input.targetUrl === void 0) {
|
|
2002
|
+
throw new CronvelloConfigError("externalApps.register() requires either `base_url` or `targetUrl`.");
|
|
2003
|
+
}
|
|
2004
|
+
const authMethod = input.authMethod ?? (input.oauthClientId || input.oauthClientSecret ? "oauth" : "api_key");
|
|
2005
|
+
if (authMethod === "api_key" && !input.apiKey && !input.generateApiKey) {
|
|
2006
|
+
throw new CronvelloConfigError(
|
|
2007
|
+
"externalApps.register() with api_key auth requires either `apiKey` or `generateApiKey: true`."
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
if (authMethod === "oauth" && (!input.oauthClientId || !input.oauthClientSecret)) {
|
|
2011
|
+
throw new CronvelloConfigError(
|
|
2012
|
+
"externalApps.register() with oauth auth requires both `oauthClientId` and `oauthClientSecret`."
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
function assertAppId(appId, method) {
|
|
2017
|
+
if (typeof appId !== "string" || !appId.trim()) {
|
|
2018
|
+
throw new CronvelloConfigError(`externalApps.${method}() requires a non-empty string appId.`);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
function enc2(segment) {
|
|
2022
|
+
return encodeURIComponent(segment);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1860
2025
|
// src/index.ts
|
|
1861
2026
|
function generateDispatchSecret(bytes = 32) {
|
|
1862
2027
|
const buf = new Uint8Array(bytes);
|
|
@@ -1865,6 +2030,7 @@ function generateDispatchSecret(bytes = 32) {
|
|
|
1865
2030
|
}
|
|
1866
2031
|
|
|
1867
2032
|
exports.CRONVELLO_DEFAULT_BASE_URL = CRONVELLO_DEFAULT_BASE_URL;
|
|
2033
|
+
exports.CronvelloAdminClient = CronvelloAdminClient;
|
|
1868
2034
|
exports.CronvelloApiError = CronvelloApiError;
|
|
1869
2035
|
exports.CronvelloClient = CronvelloClient;
|
|
1870
2036
|
exports.CronvelloConfigError = CronvelloConfigError;
|