c8y-nitro 0.4.2 → 0.6.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/README.md +17 -494
- package/dist/{bootstrap-BqWPkH8q.mjs → bootstrap-DUDpmXcU.mjs} +3 -3
- package/dist/{c8y-api-BBSKRwKs.mjs → c8y-api-BbRS1-Ls.mjs} +65 -2
- package/dist/cli/index.mjs +4 -4
- package/dist/{config-Dqi-ttQi.mjs → config-BRnvtthI.mjs} +4 -1
- package/dist/credentials-9FO7rTIR.d.mts +6 -0
- package/dist/{index-CzUqbp5C.d.mts → index-uOwpI6rD.d.mts} +26 -3
- package/dist/index.d.mts +7 -1
- package/dist/index.mjs +38 -6
- package/dist/logging-CVlaRS4O.mjs +3 -0
- package/dist/{options-BDDJWdph.mjs → options-Web5UyIU.mjs} +7 -5
- package/dist/{package-C4HtuVu_.mjs → package-DsLC9Mo3.mjs} +1 -1
- package/dist/{roles-DJxp2d8p.mjs → roles-D7rpbxPp.mjs} +2 -2
- package/dist/runtime/handlers/invalidateTenantOptions.mjs +35 -0
- package/dist/runtime/middlewares/c8y-client.mjs +43 -0
- package/dist/runtime/middlewares/{dev-user.mjs → dev-user.dev.mjs} +3 -3
- package/dist/runtime/plugins/{c8y-variables.mjs → c8y-variables.dev.mjs} +4 -4
- package/dist/runtime/plugins/enrich-logs.mjs +1 -1
- package/dist/types.d.mts +3 -2
- package/dist/utils.d.mts +111 -31
- package/dist/utils.mjs +232 -36
- package/package.json +28 -30
package/dist/utils.mjs
CHANGED
|
@@ -3,11 +3,14 @@ import process from "node:process";
|
|
|
3
3
|
import { useLogger } from "evlog/nitro/v3";
|
|
4
4
|
import { BasicAuth, Client, MicroserviceClientRequestAuth } from "@c8y/client";
|
|
5
5
|
import { defineCachedFunction } from "nitro/cache";
|
|
6
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
6
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { HTTPError, defineHandler } from "nitro/h3";
|
|
8
|
-
import { useStorage } from "nitro/storage";
|
|
9
8
|
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
9
|
+
import { useNitroHooks } from "nitro/app";
|
|
10
|
+
import { c8yManifest } from "c8y-nitro/runtime";
|
|
10
11
|
import { createError, createLogger } from "evlog";
|
|
12
|
+
import { ms } from "itty-time";
|
|
13
|
+
import { tasks } from "#nitro/virtual/tasks";
|
|
11
14
|
//#region src/utils/internal/common.ts
|
|
12
15
|
/**
|
|
13
16
|
* Converts undici Request headers to the format expected by MicroserviceClientRequestAuth.\
|
|
@@ -82,6 +85,14 @@ const getCurrentUserTenantId = defineCachedFunction(async (requestOrEvent) => {
|
|
|
82
85
|
});
|
|
83
86
|
//#endregion
|
|
84
87
|
//#region src/utils/credentials.ts
|
|
88
|
+
let prevCredentials = null;
|
|
89
|
+
function shouldEmitTenantCredentialsUpdated(prev, next) {
|
|
90
|
+
if (!prev) return true;
|
|
91
|
+
const prevTenantIds = Object.keys(prev);
|
|
92
|
+
const nextTenantIds = Object.keys(next);
|
|
93
|
+
if (prevTenantIds.length !== nextTenantIds.length) return true;
|
|
94
|
+
return new Set(nextTenantIds).symmetricDifference(new Set(prevTenantIds)).size > 0;
|
|
95
|
+
}
|
|
85
96
|
/**
|
|
86
97
|
* Fetches credentials for all tenants subscribed to this microservice.\
|
|
87
98
|
* Uses bootstrap credentials from runtime config to query the microservice subscriptions API.\
|
|
@@ -89,7 +100,7 @@ const getCurrentUserTenantId = defineCachedFunction(async (requestOrEvent) => {
|
|
|
89
100
|
* @returns Object mapping tenant IDs to their respective credentials
|
|
90
101
|
* @config Cache TTL can be configured via:
|
|
91
102
|
* - `c8y.cache.credentialsTTL` in the Nitro config (value in seconds)
|
|
92
|
-
* - `
|
|
103
|
+
* - `NITRO_C8Y_CREDENTIALS_CACHE_TTL` environment variable
|
|
93
104
|
* @example
|
|
94
105
|
* // Get all subscribed tenant credentials:
|
|
95
106
|
* const credentials = await useSubscribedTenantCredentials()
|
|
@@ -104,8 +115,8 @@ const getCurrentUserTenantId = defineCachedFunction(async (requestOrEvent) => {
|
|
|
104
115
|
* // Force refresh:
|
|
105
116
|
* const freshCreds = await useSubscribedTenantCredentials.refresh()
|
|
106
117
|
*/
|
|
107
|
-
const
|
|
108
|
-
|
|
118
|
+
const cachedSubscribedTenantCredentials = defineCachedFunction(async () => {
|
|
119
|
+
const newCredentials = (await Client.getMicroserviceSubscriptions({
|
|
109
120
|
tenant: process.env.C8Y_BOOTSTRAP_TENANT,
|
|
110
121
|
user: process.env.C8Y_BOOTSTRAP_USER,
|
|
111
122
|
password: process.env.C8Y_BOOTSTRAP_PASSWORD
|
|
@@ -113,18 +124,26 @@ const useSubscribedTenantCredentials = Object.assign(defineCachedFunction(async
|
|
|
113
124
|
if (cred.tenant) acc[cred.tenant] = cred;
|
|
114
125
|
return acc;
|
|
115
126
|
}, {});
|
|
127
|
+
if (shouldEmitTenantCredentialsUpdated(prevCredentials, newCredentials)) useNitroHooks().callHook("c8y:tenantCredentialsUpdated", prevCredentials, newCredentials);
|
|
128
|
+
prevCredentials = newCredentials;
|
|
129
|
+
return newCredentials;
|
|
116
130
|
}, {
|
|
117
131
|
maxAge: useRuntimeConfig().c8yCredentialsCacheTTL ?? 600,
|
|
118
132
|
name: "_c8y_nitro_get_subscribed_tenant_credentials",
|
|
119
133
|
group: "c8y_nitro",
|
|
120
134
|
swr: false
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
});
|
|
136
|
+
const useSubscribedTenantCredentials = Object.assign(async () => {
|
|
137
|
+
const credentials = await cachedSubscribedTenantCredentials();
|
|
138
|
+
prevCredentials = credentials;
|
|
139
|
+
return credentials;
|
|
140
|
+
}, {
|
|
141
|
+
invalidate: cachedSubscribedTenantCredentials.invalidate,
|
|
125
142
|
refresh: async () => {
|
|
126
|
-
await
|
|
127
|
-
|
|
143
|
+
await cachedSubscribedTenantCredentials.invalidate();
|
|
144
|
+
const credentials = await cachedSubscribedTenantCredentials();
|
|
145
|
+
prevCredentials = credentials;
|
|
146
|
+
return credentials;
|
|
128
147
|
}
|
|
129
148
|
});
|
|
130
149
|
/**
|
|
@@ -157,7 +176,7 @@ const useDeployedTenantCredentials = Object.assign(async () => {
|
|
|
157
176
|
}, {
|
|
158
177
|
invalidate: useSubscribedTenantCredentials.invalidate,
|
|
159
178
|
refresh: async () => {
|
|
160
|
-
await
|
|
179
|
+
await useSubscribedTenantCredentials.refresh();
|
|
161
180
|
return await useDeployedTenantCredentials();
|
|
162
181
|
}
|
|
163
182
|
});
|
|
@@ -318,8 +337,13 @@ async function useUserRoles(requestOrEvent) {
|
|
|
318
337
|
}
|
|
319
338
|
//#endregion
|
|
320
339
|
//#region src/utils/middleware.ts
|
|
340
|
+
const probePaths = [c8yManifest.livenessProbe?.httpGet?.path, c8yManifest.readinessProbe?.httpGet?.path].filter((path) => Boolean(path));
|
|
341
|
+
function isProbeRequest(pathname) {
|
|
342
|
+
return probePaths.some((probePath) => pathname.startsWith(probePath));
|
|
343
|
+
}
|
|
321
344
|
function hasUserRequiredRole(roleOrRoles) {
|
|
322
345
|
return defineHandler(async (event) => {
|
|
346
|
+
if (isProbeRequest(event.url.pathname)) return;
|
|
323
347
|
const requiredRoles = Array.isArray(roleOrRoles) ? roleOrRoles : [roleOrRoles];
|
|
324
348
|
const userRoles = await useUserRoles(event);
|
|
325
349
|
if (!requiredRoles.some((role) => userRoles.includes(role))) throw new HTTPError({
|
|
@@ -331,6 +355,7 @@ function hasUserRequiredRole(roleOrRoles) {
|
|
|
331
355
|
}
|
|
332
356
|
function isUserFromAllowedTenant(tenantIdOrIds) {
|
|
333
357
|
return defineHandler(async (event) => {
|
|
358
|
+
if (isProbeRequest(event.url.pathname)) return;
|
|
334
359
|
const allowedTenants = Array.isArray(tenantIdOrIds) ? tenantIdOrIds : [tenantIdOrIds];
|
|
335
360
|
const userTenantId = await getCurrentUserTenantId(event);
|
|
336
361
|
if (!allowedTenants.includes(userTenantId)) throw new HTTPError({
|
|
@@ -357,6 +382,7 @@ function isUserFromAllowedTenant(tenantIdOrIds) {
|
|
|
357
382
|
*/
|
|
358
383
|
function isUserFromDeployedTenant() {
|
|
359
384
|
return defineHandler(async (event) => {
|
|
385
|
+
if (isProbeRequest(event.url.pathname)) return;
|
|
360
386
|
const userTenantId = await getCurrentUserTenantId(event);
|
|
361
387
|
const deployedTenantId = process.env.C8Y_BOOTSTRAP_TENANT;
|
|
362
388
|
if (!deployedTenantId) throw new HTTPError({
|
|
@@ -372,6 +398,12 @@ function isUserFromDeployedTenant() {
|
|
|
372
398
|
});
|
|
373
399
|
}
|
|
374
400
|
//#endregion
|
|
401
|
+
//#region src/utils/internal/tenantOptionFetchers.ts
|
|
402
|
+
/**
|
|
403
|
+
* Internal storage for cached functions per key
|
|
404
|
+
*/
|
|
405
|
+
const tenantOptionFetchers = {};
|
|
406
|
+
//#endregion
|
|
375
407
|
//#region src/utils/tenantOptions.ts
|
|
376
408
|
/**
|
|
377
409
|
* Gets the cache TTL for a specific tenant option key.
|
|
@@ -383,22 +415,17 @@ function getTenantOptionCacheTTL(key) {
|
|
|
383
415
|
return config.c8yTenantOptionsPerKeyTTL?.[key] ?? config.c8yDefaultTenantOptionsTTL ?? 600;
|
|
384
416
|
}
|
|
385
417
|
/**
|
|
386
|
-
* Internal storage for cached functions per key
|
|
387
|
-
*/
|
|
388
|
-
const tenantOptionFetchers = {};
|
|
389
|
-
/**
|
|
390
418
|
* Factory function that creates a cached fetcher for a specific tenant option key.
|
|
391
419
|
* @param key - The tenant option key
|
|
392
420
|
*/
|
|
393
421
|
function createCachedTenantOptionFetcher(key) {
|
|
394
422
|
const cacheName = `_c8y_nitro_tenant_option_${key.replace(/\./g, "_")}`;
|
|
395
|
-
const
|
|
423
|
+
const cachedFetcher = defineCachedFunction(async () => {
|
|
396
424
|
const client = await useDeployedTenantClient();
|
|
397
425
|
const category = useRuntimeConfig().c8ySettingsCategory;
|
|
398
|
-
const apiKey = key.replace(/^credentials\./, "");
|
|
399
426
|
try {
|
|
400
427
|
return (await client.options.tenant.detail({
|
|
401
|
-
key
|
|
428
|
+
key,
|
|
402
429
|
category
|
|
403
430
|
})).data.value;
|
|
404
431
|
} catch (error) {
|
|
@@ -411,17 +438,10 @@ function createCachedTenantOptionFetcher(key) {
|
|
|
411
438
|
group: "c8y_nitro",
|
|
412
439
|
swr: false
|
|
413
440
|
});
|
|
414
|
-
return Object.assign(
|
|
415
|
-
invalidate
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
},
|
|
419
|
-
refresh: async () => {
|
|
420
|
-
const completeKey = `c8y_nitro:functions:${cacheName}.json`;
|
|
421
|
-
await useStorage("cache").removeItem(completeKey);
|
|
422
|
-
return await fetcher();
|
|
423
|
-
}
|
|
424
|
-
});
|
|
441
|
+
return Object.assign(cachedFetcher, { refresh: async () => {
|
|
442
|
+
await cachedFetcher.invalidate();
|
|
443
|
+
return await cachedFetcher();
|
|
444
|
+
} });
|
|
425
445
|
}
|
|
426
446
|
/**
|
|
427
447
|
* Gets or creates a cached fetcher for a specific tenant option key.
|
|
@@ -448,11 +468,6 @@ function getOrCreateFetcher(key) {
|
|
|
448
468
|
* - `c8y.cache.tenantOptions` — Per-key TTL overrides
|
|
449
469
|
* - `NITRO_C8Y_DEFAULT_TENANT_OPTIONS_TTL` — Environment variable for default TTL
|
|
450
470
|
*
|
|
451
|
-
* @note For encrypted options (keys starting with `credentials.`), the value is automatically
|
|
452
|
-
* decrypted by Cumulocity if this microservice is the owner of the option (category matches
|
|
453
|
-
* the microservice's settingsCategory/contextPath/name). The `credentials.` prefix is
|
|
454
|
-
* automatically stripped when calling the API.
|
|
455
|
-
*
|
|
456
471
|
* @example
|
|
457
472
|
* // Fetch a tenant option:
|
|
458
473
|
* const value = await useTenantOption('myOption')
|
|
@@ -475,16 +490,33 @@ function getOrCreateFetcher(key) {
|
|
|
475
490
|
const useTenantOption = Object.assign(async (key) => {
|
|
476
491
|
return await getOrCreateFetcher(key)();
|
|
477
492
|
}, {
|
|
493
|
+
/**
|
|
494
|
+
* Invalidate the cache for a specific tenant option key.
|
|
495
|
+
* @param key - The tenant option key to invalidate
|
|
496
|
+
*/
|
|
478
497
|
invalidate: async (key) => {
|
|
479
498
|
const fetcher = tenantOptionFetchers[key];
|
|
480
499
|
if (fetcher) await fetcher.invalidate();
|
|
481
500
|
},
|
|
501
|
+
/**
|
|
502
|
+
* Force refresh a specific tenant option key (invalidates and re-fetches).
|
|
503
|
+
* @param key - The tenant option key to refresh
|
|
504
|
+
*/
|
|
482
505
|
refresh: async (key) => {
|
|
483
506
|
return await getOrCreateFetcher(key).refresh();
|
|
484
507
|
},
|
|
508
|
+
/**
|
|
509
|
+
* Invalidate all tenant option caches that have been accessed.
|
|
510
|
+
* Only invalidates keys that have been fetched at least once.
|
|
511
|
+
*/
|
|
485
512
|
invalidateAll: async () => {
|
|
486
513
|
await Promise.all(Object.values(tenantOptionFetchers).map((fetcher) => fetcher?.invalidate()));
|
|
487
514
|
},
|
|
515
|
+
/**
|
|
516
|
+
* Refresh all tenant options that have been accessed.
|
|
517
|
+
* Only refreshes keys that have been fetched at least once.
|
|
518
|
+
* @returns Object mapping keys to their refreshed values
|
|
519
|
+
*/
|
|
488
520
|
refreshAll: async () => {
|
|
489
521
|
const entries = Object.entries(tenantOptionFetchers);
|
|
490
522
|
const values = await Promise.all(entries.map(([, fetcher]) => fetcher?.refresh()));
|
|
@@ -492,4 +524,168 @@ const useTenantOption = Object.assign(async (key) => {
|
|
|
492
524
|
}
|
|
493
525
|
});
|
|
494
526
|
//#endregion
|
|
495
|
-
|
|
527
|
+
//#region src/utils/schedule.ts
|
|
528
|
+
const MAX_TIMEOUT_MS = 2147483647;
|
|
529
|
+
const SCHEDULE_LOOKAHEAD_MS = 3600 * 1e3;
|
|
530
|
+
const SCHEDULER_TICK_MS = 100;
|
|
531
|
+
const scheduledTasks = /* @__PURE__ */ new Map();
|
|
532
|
+
const scheduledTaskTimers = /* @__PURE__ */ new Map();
|
|
533
|
+
const schedulerReady = Promise.resolve();
|
|
534
|
+
let schedulerInterval;
|
|
535
|
+
function createScheduledTaskId() {
|
|
536
|
+
return randomUUID();
|
|
537
|
+
}
|
|
538
|
+
function resolveScheduleTime(schedule) {
|
|
539
|
+
if (schedule instanceof Date) {
|
|
540
|
+
const timestamp = schedule.getTime();
|
|
541
|
+
if (Number.isNaN(timestamp)) throw new TypeError("schedule date must be valid");
|
|
542
|
+
return timestamp;
|
|
543
|
+
}
|
|
544
|
+
if (typeof schedule === "number") {
|
|
545
|
+
if (!Number.isFinite(schedule) || schedule < 0) throw new TypeError("schedule number must be a non-negative number of seconds");
|
|
546
|
+
return Date.now() + schedule * 1e3;
|
|
547
|
+
}
|
|
548
|
+
const delay = ms(schedule);
|
|
549
|
+
if (!Number.isFinite(delay) || delay < 0) throw new TypeError("schedule string must be a valid non-negative duration");
|
|
550
|
+
return Date.now() + delay;
|
|
551
|
+
}
|
|
552
|
+
async function executeScheduledTask(id) {
|
|
553
|
+
const record = scheduledTasks.get(id);
|
|
554
|
+
if (!record) return;
|
|
555
|
+
if (record.timeoutId !== void 0) scheduledTaskTimers.delete(record.timeoutId);
|
|
556
|
+
scheduledTasks.delete(id);
|
|
557
|
+
stopSchedulerIntervalIfIdle();
|
|
558
|
+
const taskDef = tasks[record.taskName];
|
|
559
|
+
if (!taskDef) throw new Error(`Task "${record.taskName}" is not available!`);
|
|
560
|
+
if (!taskDef.resolve) throw new Error(`Task "${record.taskName}" is not implemented!`);
|
|
561
|
+
await (await taskDef.resolve()).run({
|
|
562
|
+
name: record.taskName,
|
|
563
|
+
payload: record.payload,
|
|
564
|
+
context: record.context
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function armScheduledTaskTimeout(id) {
|
|
568
|
+
const record = scheduledTasks.get(id);
|
|
569
|
+
if (!record || record.timeoutId !== void 0) return;
|
|
570
|
+
const remainingMs = Math.max(record.runAt - Date.now(), 0);
|
|
571
|
+
if (remainingMs > SCHEDULE_LOOKAHEAD_MS) return;
|
|
572
|
+
const timeout = setTimeout(() => {
|
|
573
|
+
executeScheduledTask(id);
|
|
574
|
+
}, Math.min(remainingMs, MAX_TIMEOUT_MS));
|
|
575
|
+
record.timeoutId = Number(timeout);
|
|
576
|
+
scheduledTaskTimers.set(record.timeoutId, timeout);
|
|
577
|
+
}
|
|
578
|
+
function runSchedulerTick() {
|
|
579
|
+
for (const record of scheduledTasks.values()) armScheduledTaskTimeout(record.id);
|
|
580
|
+
}
|
|
581
|
+
function ensureSchedulerInterval() {
|
|
582
|
+
if (schedulerInterval) return;
|
|
583
|
+
schedulerInterval = setInterval(runSchedulerTick, SCHEDULER_TICK_MS);
|
|
584
|
+
}
|
|
585
|
+
function stopSchedulerIntervalIfIdle() {
|
|
586
|
+
if (!schedulerInterval || scheduledTasks.size > 0) return;
|
|
587
|
+
clearInterval(schedulerInterval);
|
|
588
|
+
schedulerInterval = void 0;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Schedules a Nitro task to run once in the future.\
|
|
592
|
+
* Resolves and calls the task handler directly from Nitro's virtual task registry\
|
|
593
|
+
* Numbers are treated as seconds, strings are parsed as human-readable durations, and dates are used as exact run times.
|
|
594
|
+
*
|
|
595
|
+
* @param taskName - The Nitro task name to run (from `tasks/*.ts`)
|
|
596
|
+
* @param options - Task payload, context, and the schedule time
|
|
597
|
+
* @returns Information about the scheduled task
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* // Run a task in 30 seconds:
|
|
601
|
+
* const scheduled = await scheduleTask('emails:send', {
|
|
602
|
+
* payload: { messageId: 'abc123' },
|
|
603
|
+
* schedule: 30,
|
|
604
|
+
* })
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* // Run a task using a human-readable duration:
|
|
608
|
+
* await scheduleTask('reports:generate', {
|
|
609
|
+
* payload: { reportId: 'report-1' },
|
|
610
|
+
* schedule: '1 hour',
|
|
611
|
+
* })
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* // Run a task at an exact time:
|
|
615
|
+
* await scheduleTask('cleanup:tenant', {
|
|
616
|
+
* payload: { tenant: 't12345' },
|
|
617
|
+
* schedule: new Date('2026-05-01T12:00:00Z'),
|
|
618
|
+
* })
|
|
619
|
+
*/
|
|
620
|
+
async function scheduleTask(taskName, options) {
|
|
621
|
+
if (!import.meta._tasks) throw new Error("scheduleTask() requires tasks to be enabled. Set `experimental: { tasks: true }` in your nitro.config.ts.");
|
|
622
|
+
if (!taskName) throw new TypeError("taskName is required");
|
|
623
|
+
const runAt = resolveScheduleTime(options.schedule);
|
|
624
|
+
const record = {
|
|
625
|
+
id: createScheduledTaskId(),
|
|
626
|
+
taskName,
|
|
627
|
+
payload: options.payload ?? {},
|
|
628
|
+
context: options.context ?? {},
|
|
629
|
+
runAt
|
|
630
|
+
};
|
|
631
|
+
scheduledTasks.set(record.id, record);
|
|
632
|
+
armScheduledTaskTimeout(record.id);
|
|
633
|
+
ensureSchedulerInterval();
|
|
634
|
+
return {
|
|
635
|
+
id: record.id,
|
|
636
|
+
task: record.taskName,
|
|
637
|
+
runAt: new Date(record.runAt).toISOString()
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Lists all tasks that are currently scheduled and have not started yet.\
|
|
642
|
+
* The returned object is keyed by the scheduled task UUID for easy lookup and cancellation.
|
|
643
|
+
*
|
|
644
|
+
* @returns Object mapping scheduled task IDs to their public task information
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* const tasks = await listScheduledTasks()
|
|
648
|
+
* for (const [id, task] of Object.entries(tasks)) {
|
|
649
|
+
* console.log(id, task.task, task.runAt)
|
|
650
|
+
* }
|
|
651
|
+
*/
|
|
652
|
+
async function listScheduledTasks() {
|
|
653
|
+
await schedulerReady;
|
|
654
|
+
return Object.fromEntries([...scheduledTasks.values()].map((record) => [record.id, {
|
|
655
|
+
id: record.id,
|
|
656
|
+
task: record.taskName,
|
|
657
|
+
runAt: new Date(record.runAt).toISOString()
|
|
658
|
+
}]));
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Cancels a scheduled task before it starts running.\
|
|
662
|
+
* Once the underlying Nitro task has started, it cannot be cancelled with this utility.
|
|
663
|
+
*
|
|
664
|
+
* @param id - The scheduled task UUID returned by `scheduleTask()` or `listScheduledTasks()`
|
|
665
|
+
* @returns `true` when a pending task was cancelled, otherwise `false`
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* const scheduled = await scheduleTask('emails:send', {
|
|
669
|
+
* payload: { messageId: 'abc123' },
|
|
670
|
+
* schedule: '10 minutes',
|
|
671
|
+
* })
|
|
672
|
+
*
|
|
673
|
+
* const cancelled = await cancelScheduledTask(scheduled.id)
|
|
674
|
+
*/
|
|
675
|
+
async function cancelScheduledTask(id) {
|
|
676
|
+
await schedulerReady;
|
|
677
|
+
const record = scheduledTasks.get(id);
|
|
678
|
+
if (!record) return false;
|
|
679
|
+
if (record.timeoutId !== void 0) {
|
|
680
|
+
const timeout = scheduledTaskTimers.get(record.timeoutId);
|
|
681
|
+
if (timeout) {
|
|
682
|
+
clearTimeout(timeout);
|
|
683
|
+
scheduledTaskTimers.delete(record.timeoutId);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const cancelled = scheduledTasks.delete(id);
|
|
687
|
+
stopSchedulerIntervalIfIdle();
|
|
688
|
+
return cancelled;
|
|
689
|
+
}
|
|
690
|
+
//#endregion
|
|
691
|
+
export { cancelScheduledTask, createError, createLogger, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, listScheduledTasks, scheduleTask, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8y-nitro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightning fast Cumulocity IoT microservice development powered by Nitro",
|
|
6
6
|
"keywords": [
|
|
@@ -23,22 +23,12 @@
|
|
|
23
23
|
"url": "https://github.com/schplitt/c8y-nitro/issues"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
|
-
".":
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"./
|
|
31
|
-
"types": "./dist/types.d.mts",
|
|
32
|
-
"import": "./dist/types.mjs"
|
|
33
|
-
},
|
|
34
|
-
"./utils": {
|
|
35
|
-
"types": "./dist/utils.d.mts",
|
|
36
|
-
"import": "./dist/utils.mjs"
|
|
37
|
-
}
|
|
26
|
+
".": "./dist/index.mjs",
|
|
27
|
+
"./cli": "./dist/cli/index.mjs",
|
|
28
|
+
"./types": "./dist/types.mjs",
|
|
29
|
+
"./utils": "./dist/utils.mjs",
|
|
30
|
+
"./package.json": "./package.json"
|
|
38
31
|
},
|
|
39
|
-
"main": "./dist/index.mjs",
|
|
40
|
-
"module": "./dist/index.mjs",
|
|
41
|
-
"types": "./dist/index.d.mts",
|
|
42
32
|
"bin": {
|
|
43
33
|
"c8y-nitro": "./dist/cli/index.mjs"
|
|
44
34
|
},
|
|
@@ -48,30 +38,34 @@
|
|
|
48
38
|
"dist"
|
|
49
39
|
],
|
|
50
40
|
"dependencies": {
|
|
51
|
-
"c12": "^4.0.0-beta.
|
|
52
|
-
"citty": "^0.2.
|
|
41
|
+
"c12": "^4.0.0-beta.5",
|
|
42
|
+
"citty": "^0.2.2",
|
|
53
43
|
"consola": "^3.4.2",
|
|
54
|
-
"evlog": "^2.
|
|
44
|
+
"evlog": "^2.17.0",
|
|
45
|
+
"itty-time": "^2.0.2",
|
|
55
46
|
"jszip": "^3.10.1",
|
|
56
47
|
"pathe": "^2.0.3",
|
|
57
|
-
"pkg-types": "^2.3.
|
|
48
|
+
"pkg-types": "^2.3.1",
|
|
58
49
|
"spinnies": "^0.5.1",
|
|
59
|
-
"tinyexec": "^1.
|
|
50
|
+
"tinyexec": "^1.1.2"
|
|
60
51
|
},
|
|
61
52
|
"devDependencies": {
|
|
62
|
-
"@schplitt/eslint-config": "^1.
|
|
53
|
+
"@schplitt/eslint-config": "^1.5.0",
|
|
54
|
+
"@types/node": "^24.12.3",
|
|
63
55
|
"@types/spinnies": "^0.5.3",
|
|
64
|
-
"bumpp": "^11.0
|
|
56
|
+
"bumpp": "^11.1.0",
|
|
65
57
|
"changelogithub": "^14.0.0",
|
|
66
|
-
"eslint": "^10.
|
|
67
|
-
"memfs": "^4.57.
|
|
68
|
-
"tsdown": "^0.
|
|
69
|
-
"
|
|
70
|
-
"
|
|
58
|
+
"eslint": "^10.3.0",
|
|
59
|
+
"memfs": "^4.57.2",
|
|
60
|
+
"tsdown": "^0.22.0",
|
|
61
|
+
"tsnapi": "^0.3.3",
|
|
62
|
+
"typescript": "^6.0.3",
|
|
63
|
+
"vitepress": "2.0.0-alpha.17",
|
|
64
|
+
"vitest": "^4.1.6"
|
|
71
65
|
},
|
|
72
66
|
"peerDependencies": {
|
|
73
67
|
"@c8y/client": ">=1021",
|
|
74
|
-
"nitro": "3.0.
|
|
68
|
+
"nitro": "3.0.260429-beta"
|
|
75
69
|
},
|
|
76
70
|
"engines": {
|
|
77
71
|
"node": ">=24.0.0"
|
|
@@ -79,12 +73,16 @@
|
|
|
79
73
|
"scripts": {
|
|
80
74
|
"dev": "tsdown --watch",
|
|
81
75
|
"build": "tsdown",
|
|
76
|
+
"build:update": "tsdown --update-snapshot",
|
|
82
77
|
"lint": "eslint",
|
|
83
78
|
"lint:fix": "eslint --fix",
|
|
84
79
|
"typecheck": "tsc --noEmit",
|
|
85
80
|
"prerelease": "eslint && tsc --noEmit && tsdown && vitest run",
|
|
86
81
|
"release": "bumpp",
|
|
87
82
|
"test": "vitest",
|
|
88
|
-
"test:run": "vitest run"
|
|
83
|
+
"test:run": "vitest run",
|
|
84
|
+
"docs:dev": "vitepress dev docs",
|
|
85
|
+
"docs:build": "vitepress build docs",
|
|
86
|
+
"docs:preview": "vitepress preview docs"
|
|
89
87
|
}
|
|
90
88
|
}
|