c8y-nitro 0.5.0 → 0.7.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/dist/utils.d.mts CHANGED
@@ -1,10 +1,10 @@
1
+ import { t as TenantCredentials } from "./credentials-eKnJ7m9Y.mjs";
1
2
  import { useLogger } from "evlog/nitro/v3";
2
3
  import { Client, ICredentials, ICurrentUser } from "@c8y/client";
3
- import { EventHandler, H3Event } from "nitro/h3";
4
4
  import { createError, createLogger } from "evlog";
5
- import { ServerRequest } from "nitro/types";
6
- import { C8YRoles, C8YTenantOptionKey } from "c8y-nitro/types";
7
-
5
+ import { EventHandler, H3Event } from "nitro/h3";
6
+ import { ServerRequest, TaskContext, TaskPayload } from "nitro/types";
7
+ import { C8YRoles, C8YSettingsCategory, C8YTenantOptionKey } from "c8y-nitro/types";
8
8
  //#region src/utils/client.d.ts
9
9
  /**
10
10
  * Creates a Cumulocity client authenticated with the current user's credentials.\
@@ -163,38 +163,16 @@ declare function useUser(requestOrEvent: ServerRequest | H3Event): Promise<ICurr
163
163
  declare function useUserRoles(requestOrEvent: ServerRequest | H3Event): Promise<string[]>;
164
164
  //#endregion
165
165
  //#region src/utils/credentials.d.ts
166
- /**
167
- * Fetches credentials for all tenants subscribed to this microservice.\
168
- * Uses bootstrap credentials from runtime config to query the microservice subscriptions API.\
169
- * Results are cached based on the configured TTL (default: 10 minutes).\
170
- * @returns Object mapping tenant IDs to their respective credentials
171
- * @config Cache TTL can be configured via:
172
- * - `c8y.cache.credentialsTTL` in the Nitro config (value in seconds)
173
- * - `NITRO_C8Y_CACHE_CREDENTIALS_TTL` environment variable
174
- * @example
175
- * // Get all subscribed tenant credentials:
176
- * const credentials = await useSubscribedTenantCredentials()
177
- * console.log(Object.keys(credentials)) // ['t12345', 't67890']
178
- *
179
- * // Access specific tenant:
180
- * const tenant1Creds = credentials['t12345']
181
- *
182
- * // Invalidate cache:
183
- * await useSubscribedTenantCredentials.invalidate()
184
- *
185
- * // Force refresh:
186
- * const freshCreds = await useSubscribedTenantCredentials.refresh()
187
- */
188
- declare const useSubscribedTenantCredentials: (() => Promise<Record<string, ICredentials>>) & {
166
+ declare const useSubscribedTenantCredentials: (() => Promise<TenantCredentials>) & {
189
167
  invalidate: () => Promise<void>;
190
- refresh: () => Promise<Record<string, ICredentials>>;
168
+ refresh: () => Promise<TenantCredentials>;
191
169
  };
192
170
  /**
193
171
  * Fetches credentials for the tenant where this microservice is deployed.\
194
172
  * Uses the C8Y_BOOTSTRAP_TENANT environment variable to identify the deployed tenant.\
195
173
  * Returns credentials from the subscribed tenant credentials cache (cached based on configured TTL, default: 10 minutes).
196
174
  * @returns Credentials for the deployed tenant
197
- * @throws {HTTPError} If no credentials found for the deployed tenant
175
+ * @throws {EvlogError} If no credentials found for the deployed tenant
198
176
  * @example
199
177
  * // Get deployed tenant credentials:
200
178
  * const creds = await useDeployedTenantCredentials()
@@ -217,7 +195,7 @@ declare const useDeployedTenantCredentials: (() => Promise<ICredentials>) & {
217
195
  * Results are cached in the request context for subsequent calls within the same request.
218
196
  * @param requestOrEvent - The H3Event or ServerRequest from the current request
219
197
  * @returns Credentials for the user's tenant
220
- * @throws {HTTPError} If no subscribed tenant credentials found for the user's tenant
198
+ * @throws {EvlogError} If no subscribed tenant credentials found for the user's tenant
221
199
  * @example
222
200
  * // In a request handler:
223
201
  * const userCreds = await useUserTenantCredentials(event)
@@ -230,12 +208,148 @@ declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Even
230
208
  //#endregion
231
209
  //#region src/utils/tenantOptions.d.ts
232
210
  /**
233
- * Fetches a tenant option value by key.\
234
- * Uses the deployed tenant's service user credentials to access the Options API.\
235
- * Results are cached based on the configured TTL (default: 10 minutes).
211
+ * A tenant option key for the microservice's own settings category.
236
212
  *
237
- * @param key - The tenant option key to fetch (as defined in `manifest.settings`)
238
- * @returns The option value as a string
213
+ * Manifest-declared keys are offered via autocomplete (from the generated
214
+ * {@link C8YTenantOptionKey} union) while any other string is still accepted, so
215
+ * runtime-computed keys such as `encrypted.password.${hash}` work too.
216
+ */
217
+ type TenantOptionKeyInput = C8YTenantOptionKey | (string & {});
218
+ /**
219
+ * Guards a key used against a *foreign* settings category.
220
+ *
221
+ * `credentials.*` options are stored encrypted and can only be decrypted within
222
+ * the owning microservice's category, so they can never be read from a different
223
+ * category. Such keys collapse to `never`, turning the misuse into a compile error.
224
+ */
225
+ type ForeignTenantOptionKeyInput<K extends string> = K & (K extends `credentials.${string}` ? never : unknown);
226
+ /**
227
+ * Options for {@link TenantOption.setEditable}.
228
+ */
229
+ interface SetEditableOptions {
230
+ /**
231
+ * The tenant whose option `editable` flag is updated.
232
+ *
233
+ * Required by Cumulocity: the editable-flag endpoint is only permitted from
234
+ * the management tenant, and the target tenant must be given explicitly.
235
+ */
236
+ targetTenant: string;
237
+ }
238
+ /**
239
+ * Handle for a single tenant option (a `category` + `key` pair).
240
+ *
241
+ * Obtained via {@link useTenantOption} (own category) or
242
+ * `useTenantOptions(category).option(key)`.
243
+ */
244
+ interface TenantOption {
245
+ /**
246
+ * Read the current value. Returns `undefined` when the option is not set
247
+ * (Cumulocity responds `404`). The value is cached per the configured TTL.
248
+ */
249
+ read: () => Promise<string | undefined>;
250
+ /**
251
+ * Create or update the option value (upsert), then invalidate the cached read.
252
+ */
253
+ set: (value: string) => Promise<void>;
254
+ /**
255
+ * Read the value; if it is not set, write `fallback` and return it.
256
+ */
257
+ getOrInsert: (fallback: string) => Promise<string>;
258
+ /**
259
+ * Delete the option. Idempotent — a missing option resolves without error.
260
+ * Invalidates the cached read afterwards.
261
+ */
262
+ delete: () => Promise<void>;
263
+ /**
264
+ * Update the option's `editable` flag.
265
+ *
266
+ * @remarks Cumulocity only allows this from the **management tenant** (role
267
+ * `ROLE_OPTION_MANAGEMENT_ADMIN`). It will fail with `403` when called from a
268
+ * regular subscribed-tenant microservice.
269
+ */
270
+ setEditable: (editable: boolean, options: SetEditableOptions) => Promise<void>;
271
+ /**
272
+ * Invalidate the cached value, then re-read it.
273
+ */
274
+ refresh: () => Promise<string | undefined>;
275
+ /**
276
+ * Invalidate the cached value (no-op if it was never read).
277
+ */
278
+ invalidate: () => Promise<void>;
279
+ }
280
+ /**
281
+ * Handle for all options within the microservice's own settings category.
282
+ */
283
+ interface TenantOptionCategory {
284
+ /**
285
+ * Get a handle for a single option in this category.
286
+ */
287
+ option: (key: TenantOptionKeyInput) => TenantOption;
288
+ /**
289
+ * List all options set in this category as a key/value map.
290
+ */
291
+ list: () => Promise<Record<string, string>>;
292
+ /**
293
+ * Create or update multiple options in this category, then invalidate their caches.
294
+ */
295
+ setAll: (values: Record<string, string>) => Promise<void>;
296
+ /**
297
+ * Invalidate every already-read option cache in this category.
298
+ */
299
+ invalidateAll: () => Promise<void>;
300
+ /**
301
+ * Refresh every already-read option cache in this category.
302
+ * @returns A map of the refreshed keys to their new values.
303
+ */
304
+ refreshAll: () => Promise<Record<string, string | undefined>>;
305
+ }
306
+ /**
307
+ * Handle for a *foreign* settings category (one other than the microservice's own).
308
+ *
309
+ * Identical to {@link TenantOptionCategory} except that:
310
+ * - `credentials.*` keys are rejected — see {@link ForeignTenantOptionKeyInput};
311
+ * - the optional type parameter `K` sets the known key set for autocomplete
312
+ * (`useTenantOptions<'a' | 'b'>(client, 'other')`). It defaults to `string`,
313
+ * which accepts any key (still minus `credentials.*`).
314
+ */
315
+ interface ForeignTenantOptionCategory<K extends string = string> {
316
+ /**
317
+ * Get a handle for a single non-`credentials.*` option in this category.
318
+ * Completes to `K` when a key set was provided.
319
+ */
320
+ option: <Key extends K>(key: ForeignTenantOptionKeyInput<Key>) => TenantOption;
321
+ /**
322
+ * List all options set in this category as a key/value map.
323
+ */
324
+ list: () => Promise<Record<string, string>>;
325
+ /**
326
+ * Create or update multiple options in this category, then invalidate their caches.
327
+ */
328
+ setAll: (values: Record<string, string>) => Promise<void>;
329
+ /**
330
+ * Invalidate every already-read option cache in this category.
331
+ */
332
+ invalidateAll: () => Promise<void>;
333
+ /**
334
+ * Refresh every already-read option cache in this category.
335
+ * @returns A map of the refreshed keys to their new values.
336
+ */
337
+ refreshAll: () => Promise<Record<string, string | undefined>>;
338
+ }
339
+ /**
340
+ * Gets a handle for a single tenant option in the microservice's **own** settings
341
+ * category, targeting the tenant of the given `client`.\
342
+ * Reads are cached per tenant, using the client's service user credentials.
343
+ *
344
+ * This is a shortcut for `useTenantOptions(client).option(key)` — it only drops
345
+ * the category argument. For a single option in a **foreign** category, use
346
+ * `useTenantOptions(client, category).option(key)`.
347
+ *
348
+ * Manifest-declared keys are offered via autocomplete, but any dynamic string
349
+ * is accepted too (e.g. `encrypted.password.${hash}`).
350
+ *
351
+ * @param client - The Cumulocity client that determines the target tenant
352
+ * @param key - The tenant option key
239
353
  *
240
354
  * @config Cache TTL can be configured via:
241
355
  * - `c8y.cache.defaultTenantOptionsTTL` — Default TTL for all keys (in seconds)
@@ -243,46 +357,178 @@ declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Even
243
357
  * - `NITRO_C8Y_DEFAULT_TENANT_OPTIONS_TTL` — Environment variable for default TTL
244
358
  *
245
359
  * @example
246
- * // Fetch a tenant option:
247
- * const value = await useTenantOption('myOption')
360
+ * // Read the owner tenant's config:
361
+ * const value = await useTenantOption(await useDeployedTenantClient(), 'featureMode').read()
362
+ *
363
+ * // Read the current request tenant's config (multi-tenant):
364
+ * const value = await useTenantOption(await useUserTenantClient(event), 'featureMode').read()
365
+ *
366
+ * // Create or update, or seed a dynamic key:
367
+ * await useTenantOption(client, 'featureMode').set('advanced')
368
+ * const token = await useTenantOption(client, `encrypted.password.${hash}`).getOrInsert('')
369
+ */
370
+ declare function useTenantOption(client: Client, key: TenantOptionKeyInput): TenantOption;
371
+ /**
372
+ * @internal
373
+ */
374
+ interface UseTenantOptions {
375
+ /**
376
+ * Handle for the client tenant's own settings category.
377
+ */
378
+ (client: Client): TenantOptionCategory;
379
+ /**
380
+ * Handle for the client tenant's own settings category (given explicitly).
381
+ */
382
+ (client: Client, category: C8YSettingsCategory): TenantOptionCategory;
383
+ /**
384
+ * Handle for a foreign settings category (`credentials.*` keys are rejected).
385
+ *
386
+ * Provide the key set as a type argument for autocomplete, e.g.
387
+ * `useTenantOptions<'featureA' | 'featureB'>(client, 'other-service')`.
388
+ * Supplying a type argument also explicitly selects this overload.
389
+ */
390
+ <K extends string = string>(client: Client, category: string): ForeignTenantOptionCategory<K>;
391
+ /**
392
+ * Invalidate every already-read option cache, across all tenants and categories.
393
+ */
394
+ invalidateAll: () => Promise<void>;
395
+ /**
396
+ * Refresh every already-read option cache, across all tenants and categories.
397
+ * @returns Map of `${tenant}::${category}::${key}` store keys to their refreshed values.
398
+ */
399
+ refreshAll: () => Promise<Record<string, string | undefined>>;
400
+ }
401
+ /**
402
+ * Gets a handle for a whole settings category, targeting the tenant of the given
403
+ * `client`.\
404
+ * Omit the category (or pass the own {@link C8YSettingsCategory}) for the
405
+ * microservice's own category; pass any other category to read/write options in
406
+ * a foreign category (where `credentials.*` keys are rejected).
407
+ *
408
+ * @param client - The Cumulocity client that determines the target tenant
248
409
  *
249
- * // Fetch an encrypted secret:
250
- * const secret = await useTenantOption('credentials.apiKey')
410
+ * @example
411
+ * const client = await useUserTenantClient(event)
251
412
  *
252
- * // Invalidate cache for a specific key:
253
- * await useTenantOption.invalidate('myOption')
413
+ * // Own category:
414
+ * const values = await useTenantOptions(client).list()
415
+ * await useTenantOptions(client).option('featureMode').set('advanced')
254
416
  *
255
- * // Force refresh a specific key:
256
- * const fresh = await useTenantOption.refresh('myOption')
417
+ * // Foreign category:
418
+ * const value = await useTenantOptions(client, 'other-service').option('someKey').read()
257
419
  *
258
- * // Invalidate all tenant option caches:
259
- * await useTenantOption.invalidateAll()
420
+ * // Invalidate all cached options of one tenant + category:
421
+ * await useTenantOptions(client, 'other-service').invalidateAll()
260
422
  *
261
- * // Refresh all tenant options:
262
- * const all = await useTenantOption.refreshAll()
423
+ * // Invalidate everything, across all tenants and categories:
424
+ * await useTenantOptions.invalidateAll()
425
+ */
426
+ declare const useTenantOptions: UseTenantOptions;
427
+ //#endregion
428
+ //#region src/utils/schedule.d.ts
429
+ type ScheduledTaskPayload = TaskPayload;
430
+ type ScheduledTaskContext = TaskContext;
431
+ /**
432
+ * A one-shot schedule definition for `scheduleTask()`.\
433
+ * Numbers are treated as seconds from now, strings are parsed as human-readable durations
434
+ * such as `"10 minutes"`, and dates are used as exact run times.
263
435
  */
264
- declare const useTenantOption: ((key: C8YTenantOptionKey) => Promise<string | undefined>) & {
436
+ type ScheduledTaskInput = Date | number | string;
437
+ /**
438
+ * Options for scheduling a Nitro task to run once in the future.\
439
+ * `payload` and `context` are forwarded to the task handler's `run()` method.
440
+ */
441
+ interface ScheduleTaskOptions {
265
442
  /**
266
- * Invalidate the cache for a specific tenant option key.
267
- * @param key - The tenant option key to invalidate
443
+ * Payload forwarded to the task handler's `TaskEvent.payload`.
268
444
  */
269
- invalidate: (key: C8YTenantOptionKey) => Promise<void>;
445
+ payload?: ScheduledTaskPayload;
270
446
  /**
271
- * Force refresh a specific tenant option key (invalidates and re-fetches).
272
- * @param key - The tenant option key to refresh
447
+ * Context forwarded to the task handler's `TaskEvent.context`.
273
448
  */
274
- refresh: (key: C8YTenantOptionKey) => Promise<string | undefined>;
449
+ context?: ScheduledTaskContext;
275
450
  /**
276
- * Invalidate all tenant option caches that have been accessed.
277
- * Only invalidates keys that have been fetched at least once.
451
+ * When the task should run. Numbers are seconds from now.
278
452
  */
279
- invalidateAll: () => Promise<void>;
453
+ schedule: ScheduledTaskInput;
454
+ }
455
+ /**
456
+ * Public information about a pending scheduled task.\
457
+ * Returned by `scheduleTask()` and by `listScheduledTasks()`.
458
+ */
459
+ interface ScheduledTaskInfo {
280
460
  /**
281
- * Refresh all tenant options that have been accessed.
282
- * Only refreshes keys that have been fetched at least once.
283
- * @returns Object mapping keys to their refreshed values
461
+ * Stable UUID used to list or cancel the scheduled task.
284
462
  */
285
- refreshAll: () => Promise<Record<string, string | undefined>>;
286
- };
463
+ id: string;
464
+ /**
465
+ * Nitro task name that will be passed to `runTask()`.
466
+ */
467
+ task: string;
468
+ /**
469
+ * Exact execution time as an ISO date string.
470
+ */
471
+ runAt: string;
472
+ }
473
+ /**
474
+ * Schedules a Nitro task to run once in the future.\
475
+ * Resolves and calls the task handler directly from Nitro's virtual task registry\
476
+ * Numbers are treated as seconds, strings are parsed as human-readable durations, and dates are used as exact run times.
477
+ *
478
+ * @param taskName - The Nitro task name to run (from `tasks/*.ts`)
479
+ * @param options - Task payload, context, and the schedule time
480
+ * @returns Information about the scheduled task
481
+ *
482
+ * @example
483
+ * // Run a task in 30 seconds:
484
+ * const scheduled = await scheduleTask('emails:send', {
485
+ * payload: { messageId: 'abc123' },
486
+ * schedule: 30,
487
+ * })
488
+ *
489
+ * @example
490
+ * // Run a task using a human-readable duration:
491
+ * await scheduleTask('reports:generate', {
492
+ * payload: { reportId: 'report-1' },
493
+ * schedule: '1 hour',
494
+ * })
495
+ *
496
+ * @example
497
+ * // Run a task at an exact time:
498
+ * await scheduleTask('cleanup:tenant', {
499
+ * payload: { tenant: 't12345' },
500
+ * schedule: new Date('2026-05-01T12:00:00Z'),
501
+ * })
502
+ */
503
+ declare function scheduleTask(taskName: string, options: ScheduleTaskOptions): Promise<ScheduledTaskInfo>;
504
+ /**
505
+ * Lists all tasks that are currently scheduled and have not started yet.\
506
+ * The returned object is keyed by the scheduled task UUID for easy lookup and cancellation.
507
+ *
508
+ * @returns Object mapping scheduled task IDs to their public task information
509
+ *
510
+ * @example
511
+ * const tasks = await listScheduledTasks()
512
+ * for (const [id, task] of Object.entries(tasks)) {
513
+ * console.log(id, task.task, task.runAt)
514
+ * }
515
+ */
516
+ declare function listScheduledTasks(): Promise<Record<string, ScheduledTaskInfo>>;
517
+ /**
518
+ * Cancels a scheduled task before it starts running.\
519
+ * Once the underlying Nitro task has started, it cannot be cancelled with this utility.
520
+ *
521
+ * @param id - The scheduled task UUID returned by `scheduleTask()` or `listScheduledTasks()`
522
+ * @returns `true` when a pending task was cancelled, otherwise `false`
523
+ *
524
+ * @example
525
+ * const scheduled = await scheduleTask('emails:send', {
526
+ * payload: { messageId: 'abc123' },
527
+ * schedule: '10 minutes',
528
+ * })
529
+ *
530
+ * const cancelled = await cancelScheduledTask(scheduled.id)
531
+ */
532
+ declare function cancelScheduledTask(id: string): Promise<boolean>;
287
533
  //#endregion
288
- export { createError, createLogger, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
534
+ export { ForeignTenantOptionCategory, ForeignTenantOptionKeyInput, ScheduleTaskOptions, ScheduledTaskContext, ScheduledTaskInfo, ScheduledTaskInput, ScheduledTaskPayload, SetEditableOptions, TenantOption, TenantOptionCategory, TenantOptionKeyInput, cancelScheduledTask, createError, createLogger, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, listScheduledTasks, scheduleTask, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useTenantOptions, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };