c8y-nitro 0.6.0 → 0.7.1

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,11 +1,9 @@
1
- import { t as TenantCredentials } from "./credentials-9FO7rTIR.mjs";
2
- import { useLogger } from "evlog/nitro/v3";
1
+ import { t as TenantCredentials } from "./credentials-eKnJ7m9Y.mjs";
3
2
  import { Client, ICredentials, ICurrentUser } from "@c8y/client";
4
- import { EventHandler, H3Event } from "nitro/h3";
5
- import { createError, createLogger } from "evlog";
3
+ import { createError as createError$1, createLogger } from "evlog";
4
+ import { EventHandler, H3Event, HTTPEvent } from "nitro/h3";
6
5
  import { ServerRequest, TaskContext, TaskPayload } from "nitro/types";
7
- import { C8YRoles, C8YTenantOptionKey } from "c8y-nitro/types";
8
-
6
+ import { C8YRoles, C8YSettingsCategory, C8YTenantOptionKey } from "c8y-nitro/types";
9
7
  //#region src/utils/client.d.ts
10
8
  /**
11
9
  * Creates a Cumulocity client authenticated with the current user's credentials.\
@@ -173,7 +171,7 @@ declare const useSubscribedTenantCredentials: (() => Promise<TenantCredentials>)
173
171
  * Uses the C8Y_BOOTSTRAP_TENANT environment variable to identify the deployed tenant.\
174
172
  * Returns credentials from the subscribed tenant credentials cache (cached based on configured TTL, default: 10 minutes).
175
173
  * @returns Credentials for the deployed tenant
176
- * @throws {HTTPError} If no credentials found for the deployed tenant
174
+ * @throws {EvlogError} If no credentials found for the deployed tenant
177
175
  * @example
178
176
  * // Get deployed tenant credentials:
179
177
  * const creds = await useDeployedTenantCredentials()
@@ -196,7 +194,7 @@ declare const useDeployedTenantCredentials: (() => Promise<ICredentials>) & {
196
194
  * Results are cached in the request context for subsequent calls within the same request.
197
195
  * @param requestOrEvent - The H3Event or ServerRequest from the current request
198
196
  * @returns Credentials for the user's tenant
199
- * @throws {HTTPError} If no subscribed tenant credentials found for the user's tenant
197
+ * @throws {EvlogError} If no subscribed tenant credentials found for the user's tenant
200
198
  * @example
201
199
  * // In a request handler:
202
200
  * const userCreds = await useUserTenantCredentials(event)
@@ -209,12 +207,148 @@ declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Even
209
207
  //#endregion
210
208
  //#region src/utils/tenantOptions.d.ts
211
209
  /**
212
- * Fetches a tenant option value by key.\
213
- * Uses the deployed tenant's service user credentials to access the Options API.\
214
- * Results are cached based on the configured TTL (default: 10 minutes).
210
+ * A tenant option key for the microservice's own settings category.
215
211
  *
216
- * @param key - The tenant option key to fetch (as defined in `manifest.settings`)
217
- * @returns The option value as a string
212
+ * Manifest-declared keys are offered via autocomplete (from the generated
213
+ * {@link C8YTenantOptionKey} union) while any other string is still accepted, so
214
+ * runtime-computed keys such as `encrypted.password.${hash}` work too.
215
+ */
216
+ type TenantOptionKeyInput = C8YTenantOptionKey | (string & {});
217
+ /**
218
+ * Guards a key used against a *foreign* settings category.
219
+ *
220
+ * `credentials.*` options are stored encrypted and can only be decrypted within
221
+ * the owning microservice's category, so they can never be read from a different
222
+ * category. Such keys collapse to `never`, turning the misuse into a compile error.
223
+ */
224
+ type ForeignTenantOptionKeyInput<K extends string> = K & (K extends `credentials.${string}` ? never : unknown);
225
+ /**
226
+ * Options for {@link TenantOption.setEditable}.
227
+ */
228
+ interface SetEditableOptions {
229
+ /**
230
+ * The tenant whose option `editable` flag is updated.
231
+ *
232
+ * Required by Cumulocity: the editable-flag endpoint is only permitted from
233
+ * the management tenant, and the target tenant must be given explicitly.
234
+ */
235
+ targetTenant: string;
236
+ }
237
+ /**
238
+ * Handle for a single tenant option (a `category` + `key` pair).
239
+ *
240
+ * Obtained via {@link useTenantOption} (own category) or
241
+ * `useTenantOptions(category).option(key)`.
242
+ */
243
+ interface TenantOption {
244
+ /**
245
+ * Read the current value. Returns `undefined` when the option is not set
246
+ * (Cumulocity responds `404`). The value is cached per the configured TTL.
247
+ */
248
+ read: () => Promise<string | undefined>;
249
+ /**
250
+ * Create or update the option value (upsert), then invalidate the cached read.
251
+ */
252
+ set: (value: string) => Promise<void>;
253
+ /**
254
+ * Read the value; if it is not set, write `fallback` and return it.
255
+ */
256
+ getOrInsert: (fallback: string) => Promise<string>;
257
+ /**
258
+ * Delete the option. Idempotent — a missing option resolves without error.
259
+ * Invalidates the cached read afterwards.
260
+ */
261
+ delete: () => Promise<void>;
262
+ /**
263
+ * Update the option's `editable` flag.
264
+ *
265
+ * @remarks Cumulocity only allows this from the **management tenant** (role
266
+ * `ROLE_OPTION_MANAGEMENT_ADMIN`). It will fail with `403` when called from a
267
+ * regular subscribed-tenant microservice.
268
+ */
269
+ setEditable: (editable: boolean, options: SetEditableOptions) => Promise<void>;
270
+ /**
271
+ * Invalidate the cached value, then re-read it.
272
+ */
273
+ refresh: () => Promise<string | undefined>;
274
+ /**
275
+ * Invalidate the cached value (no-op if it was never read).
276
+ */
277
+ invalidate: () => Promise<void>;
278
+ }
279
+ /**
280
+ * Handle for all options within the microservice's own settings category.
281
+ */
282
+ interface TenantOptionCategory {
283
+ /**
284
+ * Get a handle for a single option in this category.
285
+ */
286
+ option: (key: TenantOptionKeyInput) => TenantOption;
287
+ /**
288
+ * List all options set in this category as a key/value map.
289
+ */
290
+ list: () => Promise<Record<string, string>>;
291
+ /**
292
+ * Create or update multiple options in this category, then invalidate their caches.
293
+ */
294
+ setAll: (values: Record<string, string>) => Promise<void>;
295
+ /**
296
+ * Invalidate every already-read option cache in this category.
297
+ */
298
+ invalidateAll: () => Promise<void>;
299
+ /**
300
+ * Refresh every already-read option cache in this category.
301
+ * @returns A map of the refreshed keys to their new values.
302
+ */
303
+ refreshAll: () => Promise<Record<string, string | undefined>>;
304
+ }
305
+ /**
306
+ * Handle for a *foreign* settings category (one other than the microservice's own).
307
+ *
308
+ * Identical to {@link TenantOptionCategory} except that:
309
+ * - `credentials.*` keys are rejected — see {@link ForeignTenantOptionKeyInput};
310
+ * - the optional type parameter `K` sets the known key set for autocomplete
311
+ * (`useTenantOptions<'a' | 'b'>(client, 'other')`). It defaults to `string`,
312
+ * which accepts any key (still minus `credentials.*`).
313
+ */
314
+ interface ForeignTenantOptionCategory<K extends string = string> {
315
+ /**
316
+ * Get a handle for a single non-`credentials.*` option in this category.
317
+ * Completes to `K` when a key set was provided.
318
+ */
319
+ option: <Key extends K>(key: ForeignTenantOptionKeyInput<Key>) => TenantOption;
320
+ /**
321
+ * List all options set in this category as a key/value map.
322
+ */
323
+ list: () => Promise<Record<string, string>>;
324
+ /**
325
+ * Create or update multiple options in this category, then invalidate their caches.
326
+ */
327
+ setAll: (values: Record<string, string>) => Promise<void>;
328
+ /**
329
+ * Invalidate every already-read option cache in this category.
330
+ */
331
+ invalidateAll: () => Promise<void>;
332
+ /**
333
+ * Refresh every already-read option cache in this category.
334
+ * @returns A map of the refreshed keys to their new values.
335
+ */
336
+ refreshAll: () => Promise<Record<string, string | undefined>>;
337
+ }
338
+ /**
339
+ * Gets a handle for a single tenant option in the microservice's **own** settings
340
+ * category, targeting the tenant of the given `client`.\
341
+ * Reads are cached per tenant, using the client's service user credentials.
342
+ *
343
+ * This is a shortcut for `useTenantOptions(client).option(key)` — it only drops
344
+ * the category argument. For a single option in a **foreign** category, use
345
+ * `useTenantOptions(client, category).option(key)`.
346
+ *
347
+ * Manifest-declared keys are offered via autocomplete, but any dynamic string
348
+ * is accepted too (e.g. `encrypted.password.${hash}`).
349
+ *
350
+ * @param client - The Cumulocity client that determines the target tenant
351
+ * @param key - The tenant option key
218
352
  *
219
353
  * @config Cache TTL can be configured via:
220
354
  * - `c8y.cache.defaultTenantOptionsTTL` — Default TTL for all keys (in seconds)
@@ -222,47 +356,151 @@ declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Even
222
356
  * - `NITRO_C8Y_DEFAULT_TENANT_OPTIONS_TTL` — Environment variable for default TTL
223
357
  *
224
358
  * @example
225
- * // Fetch a tenant option:
226
- * const value = await useTenantOption('myOption')
227
- *
228
- * // Fetch an encrypted secret:
229
- * const secret = await useTenantOption('credentials.apiKey')
230
- *
231
- * // Invalidate cache for a specific key:
232
- * await useTenantOption.invalidate('myOption')
359
+ * // Read the owner tenant's config:
360
+ * const value = await useTenantOption(await useDeployedTenantClient(), 'featureMode').read()
233
361
  *
234
- * // Force refresh a specific key:
235
- * const fresh = await useTenantOption.refresh('myOption')
362
+ * // Read the current request tenant's config (multi-tenant):
363
+ * const value = await useTenantOption(await useUserTenantClient(event), 'featureMode').read()
236
364
  *
237
- * // Invalidate all tenant option caches:
238
- * await useTenantOption.invalidateAll()
239
- *
240
- * // Refresh all tenant options:
241
- * const all = await useTenantOption.refreshAll()
365
+ * // Create or update, or seed a dynamic key:
366
+ * await useTenantOption(client, 'featureMode').set('advanced')
367
+ * const token = await useTenantOption(client, `encrypted.password.${hash}`).getOrInsert('')
368
+ */
369
+ declare function useTenantOption(client: Client, key: TenantOptionKeyInput): TenantOption;
370
+ /**
371
+ * @internal
242
372
  */
243
- declare const useTenantOption: ((key: C8YTenantOptionKey) => Promise<string | undefined>) & {
373
+ interface UseTenantOptions {
244
374
  /**
245
- * Invalidate the cache for a specific tenant option key.
246
- * @param key - The tenant option key to invalidate
375
+ * Handle for the client tenant's own settings category.
247
376
  */
248
- invalidate: (key: C8YTenantOptionKey) => Promise<void>;
377
+ (client: Client): TenantOptionCategory;
249
378
  /**
250
- * Force refresh a specific tenant option key (invalidates and re-fetches).
251
- * @param key - The tenant option key to refresh
379
+ * Handle for the client tenant's own settings category (given explicitly).
252
380
  */
253
- refresh: (key: C8YTenantOptionKey) => Promise<string | undefined>;
381
+ (client: Client, category: C8YSettingsCategory): TenantOptionCategory;
254
382
  /**
255
- * Invalidate all tenant option caches that have been accessed.
256
- * Only invalidates keys that have been fetched at least once.
383
+ * Handle for a foreign settings category (`credentials.*` keys are rejected).
384
+ *
385
+ * Provide the key set as a type argument for autocomplete, e.g.
386
+ * `useTenantOptions<'featureA' | 'featureB'>(client, 'other-service')`.
387
+ * Supplying a type argument also explicitly selects this overload.
388
+ */
389
+ <K extends string = string>(client: Client, category: string): ForeignTenantOptionCategory<K>;
390
+ /**
391
+ * Invalidate every already-read option cache, across all tenants and categories.
257
392
  */
258
393
  invalidateAll: () => Promise<void>;
259
394
  /**
260
- * Refresh all tenant options that have been accessed.
261
- * Only refreshes keys that have been fetched at least once.
262
- * @returns Object mapping keys to their refreshed values
395
+ * Refresh every already-read option cache, across all tenants and categories.
396
+ * @returns Map of `${tenant}::${category}::${key}` store keys to their refreshed values.
263
397
  */
264
398
  refreshAll: () => Promise<Record<string, string | undefined>>;
265
- };
399
+ }
400
+ /**
401
+ * Gets a handle for a whole settings category, targeting the tenant of the given
402
+ * `client`.\
403
+ * Omit the category (or pass the own {@link C8YSettingsCategory}) for the
404
+ * microservice's own category; pass any other category to read/write options in
405
+ * a foreign category (where `credentials.*` keys are rejected).
406
+ *
407
+ * @param client - The Cumulocity client that determines the target tenant
408
+ *
409
+ * @example
410
+ * const client = await useUserTenantClient(event)
411
+ *
412
+ * // Own category:
413
+ * const values = await useTenantOptions(client).list()
414
+ * await useTenantOptions(client).option('featureMode').set('advanced')
415
+ *
416
+ * // Foreign category:
417
+ * const value = await useTenantOptions(client, 'other-service').option('someKey').read()
418
+ *
419
+ * // Invalidate all cached options of one tenant + category:
420
+ * await useTenantOptions(client, 'other-service').invalidateAll()
421
+ *
422
+ * // Invalidate everything, across all tenants and categories:
423
+ * await useTenantOptions.invalidateAll()
424
+ */
425
+ declare const useTenantOptions: UseTenantOptions;
426
+ //#endregion
427
+ //#region src/utils/logging.d.ts
428
+ /**
429
+ * Returns the request-scoped wide-event logger for the current request.
430
+ *
431
+ * Accepts **either** an `H3Event` (as received by a route handler) **or** the
432
+ * `ServerRequest` returned by Nitro's `useRequest()` — both carry the same
433
+ * request context, so `useLogger(event)` and `useLogger(useRequest())` are
434
+ * equivalent.
435
+ *
436
+ * evlog's own `useLogger` only accepts an `H3Event` and reads the logger from
437
+ * `event.req.context.log`; `useRequest()` returns that `ServerRequest` directly
438
+ * (no `.req`), which would throw. This wrapper normalizes both shapes to the
439
+ * `{ req }` event evlog expects, restoring the accept-both behavior.
440
+ *
441
+ * @param eventOrRequest - The current `H3Event` or `ServerRequest` (`useRequest()`).
442
+ * @param service - Optional service name to override the default.
443
+ * @example
444
+ * // In a route handler:
445
+ * const log = useLogger(event)
446
+ *
447
+ * // Deeper in the call stack with experimental.asyncContext enabled:
448
+ * import { useRequest } from 'nitro/context'
449
+ * const log = useLogger(useRequest())
450
+ */
451
+ declare function useLogger<T extends object = Record<string, unknown>>(eventOrRequest: HTTPEvent | ServerRequest, service?: string): import("evlog").AuditableLogger<T>;
452
+ /**
453
+ * Create a structured error for a Cumulocity microservice. Always prefer this
454
+ * over Nitro/h3's built-in `createError`: the returned error is captured in the
455
+ * wide log event **and** serialized into the JSON response under a `data` key.
456
+ *
457
+ * ## What the caller sees vs what stays on the server
458
+ *
459
+ * Every field **except `internal`** is sent to the client in the HTTP response.
460
+ * `internal` is stripped from the response and only ever appears in your logs.
461
+ *
462
+ * | Field | Sent to client? | Purpose |
463
+ * | ---------- | :-------------: | ------------------------------------------------------------- |
464
+ * | `message` | yes | Short, safe summary of what went wrong. |
465
+ * | `status` | yes | HTTP status code (default `500`). |
466
+ * | `code` | yes | Stable machine-readable id (e.g. `'PAYMENT_DECLINED'`). |
467
+ * | `why` | yes | Human-readable cause — safe to expose, no secrets. |
468
+ * | `fix` | yes | What the caller can do about it. |
469
+ * | `link` | yes | Docs URL with more detail. |
470
+ * | `cause` | no (logged) | The original `Error` you caught. |
471
+ * | `internal` | no (logged) | Backend-only diagnostics — never reaches the client. |
472
+ *
473
+ * ## Do NOT leak internal system errors
474
+ *
475
+ * Put anything sensitive or diagnostic (raw Cumulocity core responses, DB
476
+ * errors, tokens, tenant internals) in `internal`, **not** in `message`/`why`.
477
+ * `internal` is logged for debugging but never serialized to the response.
478
+ *
479
+ * @example
480
+ * // User-facing error — every field here is visible to the caller:
481
+ * throw createError({
482
+ * code: 'PAYMENT_DECLINED',
483
+ * message: 'Payment failed',
484
+ * status: 402,
485
+ * why: 'Card declined by issuer',
486
+ * fix: 'Try a different payment method',
487
+ * link: 'https://docs.example.com/payments/declined',
488
+ * })
489
+ *
490
+ * @example
491
+ * // Wrapping an upstream failure — keep the raw payload server-side only:
492
+ * catch (cause) {
493
+ * throw createError({
494
+ * status: 502,
495
+ * message: 'Upstream request failed', // safe, generic — what the caller sees
496
+ * cause: cause as Error,
497
+ * internal: { upstreamStatus: cause?.res?.status, upstreamBody: cause?.data },
498
+ * })
499
+ * }
500
+ *
501
+ * @see https://www.evlog.dev
502
+ */
503
+ declare const createError: typeof createError$1;
266
504
  //#endregion
267
505
  //#region src/utils/schedule.d.ts
268
506
  type ScheduledTaskPayload = TaskPayload;
@@ -370,4 +608,4 @@ declare function listScheduledTasks(): Promise<Record<string, ScheduledTaskInfo>
370
608
  */
371
609
  declare function cancelScheduledTask(id: string): Promise<boolean>;
372
610
  //#endregion
373
- export { ScheduleTaskOptions, ScheduledTaskContext, ScheduledTaskInfo, ScheduledTaskInput, ScheduledTaskPayload, cancelScheduledTask, createError, createLogger, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, listScheduledTasks, scheduleTask, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
611
+ 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 };