c8y-nitro 0.10.3 → 0.11.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.
Files changed (41) hide show
  1. package/README.md +2 -0
  2. package/dist/{apiClient-kxv5KHYJ.mjs → apiClient-GMQqcgQe.mjs} +2 -3
  3. package/dist/{autoBootstrap-DUjHuHe_.mjs → autoBootstrap-D4vEytWW.mjs} +6 -8
  4. package/dist/{bootstrap-DrMjty-k.mjs → bootstrap-DpYAhM7C.mjs} +6 -7
  5. package/dist/cli/index.mjs +6 -7
  6. package/dist/{config-BVQHqlgV.mjs → config-Dozrdw3A.mjs} +11 -8
  7. package/dist/{credentials-eKnJ7m9Y.d.mts → credentials-C_3m4LVX.d.mts} +0 -2
  8. package/dist/defaults-CrrCinRJ.mjs +20 -0
  9. package/dist/{c8y-api-DSs1xIIh.mjs → env-fUAhIWsI.mjs} +76 -3
  10. package/dist/{env-file-BrOTmVjA.mjs → env-file-DWbaFlIp.mjs} +1 -2
  11. package/dist/{index-BEFoDy-y.d.mts → index-CmIQfBpj.d.mts} +56 -22
  12. package/dist/index.d.mts +4 -6
  13. package/dist/index.mjs +20 -21
  14. package/dist/{logging-BUYT8Z2V.mjs → logging-B1PuWdiF.mjs} +1 -2
  15. package/dist/{manifest-DV3UW9Dl.mjs → manifest-BhrHDm8Y.mjs} +4 -6
  16. package/dist/{options-54uJmuUV.mjs → options-CgTptIpI.mjs} +5 -6
  17. package/dist/{package-Dqlz6AoZ.mjs → package-y3p_br3i.mjs} +2 -3
  18. package/dist/{roles-CNCObfa3.mjs → roles-DEIHe2Xt.mjs} +4 -5
  19. package/dist/runtime/handlers/invalidateTenantOptions.mjs +2 -408
  20. package/dist/runtime/handlers/liveness-readiness.mjs +1 -2
  21. package/dist/runtime/handlers/openapi.mjs +4 -5
  22. package/dist/runtime/handlers/rewriteRequestUrl.mjs +3 -4
  23. package/dist/runtime/middlewares/c8y-client.mjs +3 -4
  24. package/dist/runtime/middlewares/dev-user.dev.mjs +2 -3
  25. package/dist/runtime/plugins/c8y-variables.dev.mjs +2 -3
  26. package/dist/runtime/plugins/enrich-logs.mjs +2 -3
  27. package/dist/scope-CxEPZ74p.mjs +2763 -0
  28. package/dist/{client-BmSKIcuq.mjs → tenant-0MBgBY_o.mjs} +115 -139
  29. package/dist/testing/setup.d.mts +1 -0
  30. package/dist/testing/setup.mjs +62 -0
  31. package/dist/testing/vite.d.mts +26 -0
  32. package/dist/testing/vite.mjs +58 -0
  33. package/dist/testing-D5VjeD-F.mjs +14 -0
  34. package/dist/testing.d.mts +521 -0
  35. package/dist/testing.mjs +494 -0
  36. package/dist/{typegen-BO_AkyNc.mjs → typegen-Bw4VxFmA.mjs} +4 -5
  37. package/dist/types-D663gk80.d.mts +140 -0
  38. package/dist/types.d.mts +3 -3
  39. package/dist/utils.d.mts +48 -66
  40. package/dist/utils.mjs +16 -27
  41. package/package.json +36 -19
package/dist/utils.d.mts CHANGED
@@ -1,11 +1,10 @@
1
- import { t as TenantCredentials } from "./credentials-eKnJ7m9Y.mjs";
1
+ import { t as TenantCredentials } from "./credentials-C_3m4LVX.mjs";
2
2
  import { RealtimeClient } from "c8y-realtime";
3
3
  import { Client, ICredentials, ICurrentUser, IResultList } from "@c8y/client";
4
4
  import { createError as createError$1, createLogger } from "evlog";
5
5
  import { EventHandler, H3Event, HTTPEvent } from "nitro/h3";
6
6
  import { ServerRequest, TaskContext, TaskPayload } from "nitro/types";
7
7
  import { C8YRole, C8YRoles, C8YSettingsCategory, C8YTenantOptionKey } from "c8y-nitro/types";
8
- //#region src/utils/client.d.ts
9
8
  /**
10
9
  * Creates a Cumulocity client authenticated with the current user's credentials.\
11
10
  * Extracts credentials from the Authorization header of the current request.
@@ -16,7 +15,7 @@ import { C8YRole, C8YRoles, C8YSettingsCategory, C8YTenantOptionKey } from "c8y-
16
15
  * const client = useUserClient(event)
17
16
  * const { data } = await client.inventory.list()
18
17
  */
19
- declare function useUserClient(requestOrEvent: ServerRequest | H3Event): Client;
18
+ export declare function useUserClient(requestOrEvent: ServerRequest | H3Event): Client;
20
19
  /**
21
20
  * Creates a Cumulocity client for the tenant of the current user.\
22
21
  * Uses the tenant's service user credentials rather than the user's own credentials.
@@ -27,7 +26,7 @@ declare function useUserClient(requestOrEvent: ServerRequest | H3Event): Client;
27
26
  * const tenantClient = await useUserTenantClient(event)
28
27
  * const { data } = await tenantClient.inventory.list()
29
28
  */
30
- declare function useUserTenantClient(requestOrEvent: ServerRequest | H3Event): Promise<Client>;
29
+ export declare function useUserTenantClient(requestOrEvent: ServerRequest | H3Event): Promise<Client>;
31
30
  /**
32
31
  * Creates Cumulocity clients for all tenants subscribed to this microservice.\
33
32
  * Each client is authenticated with that tenant's service user credentials.\
@@ -40,7 +39,7 @@ declare function useUserTenantClient(requestOrEvent: ServerRequest | H3Event): P
40
39
  * console.log(`Tenant ${tenant} has ${data.length} inventory items`)
41
40
  * }
42
41
  */
43
- declare function useSubscribedTenantClients(): Promise<Record<string, Client>>;
42
+ export declare function useSubscribedTenantClients(): Promise<Record<string, Client>>;
44
43
  /**
45
44
  * Creates a Cumulocity client for the tenant where this microservice is deployed.\
46
45
  * Uses the bootstrap tenant ID from runtime config to identify the deployed tenant.\
@@ -50,9 +49,7 @@ declare function useSubscribedTenantClients(): Promise<Record<string, Client>>;
50
49
  * const client = await useDeployedTenantClient()
51
50
  * const { data } = await client.application.list()
52
51
  */
53
- declare function useDeployedTenantClient(): Promise<Client>;
54
- //#endregion
55
- //#region src/utils/paging.d.ts
52
+ export declare function useDeployedTenantClient(): Promise<Client>;
56
53
  /**
57
54
  * The first page of a Cumulocity list result, in any of the shapes a caller
58
55
  * naturally has on hand:
@@ -66,11 +63,11 @@ declare function useDeployedTenantClient(): Promise<Client>;
66
63
  * filter that produced the first page — so there is no client to pass and no
67
64
  * ambiguity between user, tenant, or microservice clients.
68
65
  */
69
- type PageSource<T> = IResultList<T> | Promise<IResultList<T>> | (() => IResultList<T> | Promise<IResultList<T>>);
66
+ export type PageSource<T> = IResultList<T> | Promise<IResultList<T>> | (() => IResultList<T> | Promise<IResultList<T>>);
70
67
  /**
71
68
  * Optional safety guards for walking large or unbounded Cumulocity collections.
72
69
  */
73
- interface PagingOptions {
70
+ export interface PagingOptions {
74
71
  /**
75
72
  * Stop after fetching this many pages. Guards against runaway iteration over
76
73
  * unexpectedly large collections. Unlimited when omitted.
@@ -102,7 +99,7 @@ interface PagingOptions {
102
99
  * await bulkInsert(page)
103
100
  * }
104
101
  */
105
- declare function paginatePages<T>(source: PageSource<T>, options?: PagingOptions): AsyncGenerator<T[]>;
102
+ export declare function paginatePages<T>(source: PageSource<T>, options?: PagingOptions): AsyncGenerator<T[]>;
106
103
  /**
107
104
  * Lazily iterates every item across all pages of a Cumulocity list result,
108
105
  * yielding one entry at a time — ideal for streaming large collections with a
@@ -121,7 +118,7 @@ declare function paginatePages<T>(source: PageSource<T>, options?: PagingOptions
121
118
  * await handleAlarm(alarm)
122
119
  * }
123
120
  */
124
- declare function paginate<T>(source: PageSource<T>, options?: PagingOptions): AsyncGenerator<T>;
121
+ export declare function paginate<T>(source: PageSource<T>, options?: PagingOptions): AsyncGenerator<T>;
125
122
  /**
126
123
  * Collects every item across all pages of a Cumulocity list result into a single
127
124
  * array, awaiting until all pages have been fetched.
@@ -140,9 +137,7 @@ declare function paginate<T>(source: PageSource<T>, options?: PagingOptions): As
140
137
  * const client = useUserClient(event)
141
138
  * const all = await fetchAllPages(client.inventory.list({ pageSize: 2000, fragmentType: 'c8y_Sensor' }))
142
139
  */
143
- declare function fetchAllPages<T>(source: PageSource<T>, options?: PagingOptions): Promise<T[]>;
144
- //#endregion
145
- //#region src/utils/middleware.d.ts
140
+ export declare function fetchAllPages<T>(source: PageSource<T>, options?: PagingOptions): Promise<T[]>;
146
141
  type UserRole = keyof C8YRoles | C8YRole;
147
142
  /**
148
143
  * Middleware to check if the current user has the required role.\
@@ -160,7 +155,7 @@ type UserRole = keyof C8YRoles | C8YRole;
160
155
  * })
161
156
  *
162
157
  */
163
- declare function hasUserRequiredRole(role: UserRole): EventHandler;
158
+ export declare function hasUserRequiredRole(role: UserRole): EventHandler;
164
159
  /**
165
160
  * Middleware to check if the current user has at least one of the required roles.\
166
161
  * If the user doesn't have any of the required roles, throws a 403 Forbidden error.\
@@ -176,7 +171,7 @@ declare function hasUserRequiredRole(role: UserRole): EventHandler;
176
171
  * }
177
172
  * })
178
173
  */
179
- declare function hasUserRequiredRole(roles: UserRole[]): EventHandler;
174
+ export declare function hasUserRequiredRole(roles: UserRole[]): EventHandler;
180
175
  /**
181
176
  * Middleware to check if the current user belongs to a specific allowed tenant.\
182
177
  * If the user's tenant doesn't match, throws a 403 Forbidden error.\
@@ -193,7 +188,7 @@ declare function hasUserRequiredRole(roles: UserRole[]): EventHandler;
193
188
  * })
194
189
  *
195
190
  */
196
- declare function isUserFromAllowedTenant(tenantId: string): EventHandler;
191
+ export declare function isUserFromAllowedTenant(tenantId: string): EventHandler;
197
192
  /**
198
193
  * Middleware to check if the current user belongs to one of the allowed tenants.\
199
194
  * If the user's tenant doesn't match any of the allowed tenants, throws a 403 Forbidden error.\
@@ -209,7 +204,7 @@ declare function isUserFromAllowedTenant(tenantId: string): EventHandler;
209
204
  * }
210
205
  * })
211
206
  */
212
- declare function isUserFromAllowedTenant(tenantIds: string[]): EventHandler;
207
+ export declare function isUserFromAllowedTenant(tenantIds: string[]): EventHandler;
213
208
  /**
214
209
  * Middleware to check if the current user belongs to the deployed tenant.\
215
210
  * The deployed tenant is where this microservice is hosted (C8Y_BOOTSTRAP_TENANT).\
@@ -225,9 +220,7 @@ declare function isUserFromAllowedTenant(tenantIds: string[]): EventHandler;
225
220
  * }
226
221
  * })
227
222
  */
228
- declare function isUserFromDeployedTenant(): EventHandler;
229
- //#endregion
230
- //#region src/utils/resources.d.ts
223
+ export declare function isUserFromDeployedTenant(): EventHandler;
231
224
  /**
232
225
  * Fetches the current user from Cumulocity using credentials extracted from the current request's headers.
233
226
  * This is a non-cached version - fetches fresh data on every call.
@@ -238,7 +231,7 @@ declare function isUserFromDeployedTenant(): EventHandler;
238
231
  * const user = await useUser(event)
239
232
  * console.log(user.userName, user.email)
240
233
  */
241
- declare function useUser(requestOrEvent: ServerRequest | H3Event): Promise<ICurrentUser>;
234
+ export declare function useUser(requestOrEvent: ServerRequest | H3Event): Promise<ICurrentUser>;
242
235
  /**
243
236
  * Fetches the roles of the current user from Cumulocity.
244
237
  * Internally calls `useUser()` and extracts role IDs from the user object.
@@ -250,10 +243,8 @@ declare function useUser(requestOrEvent: ServerRequest | H3Event): Promise<ICurr
250
243
  * const roles = await useUserRoles(event)
251
244
  * console.log(roles) // ['ROLE_INVENTORY_READ', 'ROLE_INVENTORY_ADMIN']
252
245
  */
253
- declare function useUserRoles(requestOrEvent: ServerRequest | H3Event): Promise<string[]>;
254
- //#endregion
255
- //#region src/utils/credentials.d.ts
256
- declare const useSubscribedTenantCredentials: (() => Promise<TenantCredentials>) & {
246
+ export declare function useUserRoles(requestOrEvent: ServerRequest | H3Event): Promise<string[]>;
247
+ export declare const useSubscribedTenantCredentials: (() => Promise<TenantCredentials>) & {
257
248
  invalidate: () => Promise<void>;
258
249
  refresh: () => Promise<TenantCredentials>;
259
250
  };
@@ -275,7 +266,7 @@ declare const useSubscribedTenantCredentials: (() => Promise<TenantCredentials>)
275
266
  * const freshCreds = await useDeployedTenantCredentials.refresh()
276
267
  * @note This function is not cached separately. It uses the cache of `useSubscribedTenantCredentials()`. Invalidating or refreshing one will refresh `useDeployedTenantCredentials()`s cache.
277
268
  */
278
- declare const useDeployedTenantCredentials: (() => Promise<ICredentials>) & {
269
+ export declare const useDeployedTenantCredentials: (() => Promise<ICredentials>) & {
279
270
  invalidate: () => Promise<void>;
280
271
  refresh: () => Promise<ICredentials>;
281
272
  };
@@ -294,9 +285,7 @@ declare const useDeployedTenantCredentials: (() => Promise<ICredentials>) & {
294
285
  * // Credentials are automatically cached for the request duration
295
286
  * const sameCreds = await useUserTenantCredentials(event) // Uses cached value
296
287
  */
297
- declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Event): Promise<ICredentials>;
298
- //#endregion
299
- //#region src/utils/tenantOptions.d.ts
288
+ export declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Event): Promise<ICredentials>;
300
289
  /**
301
290
  * A tenant option key for the microservice's own settings category.
302
291
  *
@@ -304,7 +293,7 @@ declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Even
304
293
  * {@link C8YTenantOptionKey} union) while any other string is still accepted, so
305
294
  * runtime-computed keys such as `encrypted.password.${hash}` work too.
306
295
  */
307
- type TenantOptionKeyInput = C8YTenantOptionKey | (string & {});
296
+ export type TenantOptionKeyInput = C8YTenantOptionKey | (string & {});
308
297
  /**
309
298
  * Guards a key used against a *foreign* settings category.
310
299
  *
@@ -312,11 +301,11 @@ type TenantOptionKeyInput = C8YTenantOptionKey | (string & {});
312
301
  * the owning microservice's category, so they can never be read from a different
313
302
  * category. Such keys collapse to `never`, turning the misuse into a compile error.
314
303
  */
315
- type ForeignTenantOptionKeyInput<K extends string> = K & (K extends `credentials.${string}` ? never : unknown);
304
+ export type ForeignTenantOptionKeyInput<K extends string> = K & (K extends `credentials.${string}` ? never : unknown);
316
305
  /**
317
306
  * Options for {@link TenantOption.setEditable}.
318
307
  */
319
- interface SetEditableOptions {
308
+ export interface SetEditableOptions {
320
309
  /**
321
310
  * The tenant whose option `editable` flag is updated.
322
311
  *
@@ -331,7 +320,7 @@ interface SetEditableOptions {
331
320
  * Obtained via {@link useTenantOption} (own category) or
332
321
  * `useTenantOptions(category).option(key)`.
333
322
  */
334
- interface TenantOption {
323
+ export interface TenantOption {
335
324
  /**
336
325
  * Read the current value. Returns `undefined` when the option is not set
337
326
  * (Cumulocity responds `404`). The value is cached per the configured TTL.
@@ -370,7 +359,7 @@ interface TenantOption {
370
359
  /**
371
360
  * Handle for all options within the microservice's own settings category.
372
361
  */
373
- interface TenantOptionCategory {
362
+ export interface TenantOptionCategory {
374
363
  /**
375
364
  * Get a handle for a single option in this category.
376
365
  */
@@ -402,7 +391,7 @@ interface TenantOptionCategory {
402
391
  * (`useTenantOptions<'a' | 'b'>(client, 'other')`). It defaults to `string`,
403
392
  * which accepts any key (still minus `credentials.*`).
404
393
  */
405
- interface ForeignTenantOptionCategory<K extends string = string> {
394
+ export interface ForeignTenantOptionCategory<K extends string = string> {
406
395
  /**
407
396
  * Get a handle for a single non-`credentials.*` option in this category.
408
397
  * Completes to `K` when a key set was provided.
@@ -457,7 +446,7 @@ interface ForeignTenantOptionCategory<K extends string = string> {
457
446
  * await useTenantOption(client, 'featureMode').set('advanced')
458
447
  * const token = await useTenantOption(client, `encrypted.password.${hash}`).getOrInsert('')
459
448
  */
460
- declare function useTenantOption(client: Client, key: TenantOptionKeyInput): TenantOption;
449
+ export declare function useTenantOption(client: Client, key: TenantOptionKeyInput): TenantOption;
461
450
  /**
462
451
  * @internal
463
452
  */
@@ -513,9 +502,7 @@ interface UseTenantOptions {
513
502
  * // Invalidate everything, across all tenants and categories:
514
503
  * await useTenantOptions.invalidateAll()
515
504
  */
516
- declare const useTenantOptions: UseTenantOptions;
517
- //#endregion
518
- //#region src/utils/logging.d.ts
505
+ export declare const useTenantOptions: UseTenantOptions;
519
506
  /**
520
507
  * Returns the request-scoped wide-event logger for the current request.
521
508
  *
@@ -539,7 +526,7 @@ declare const useTenantOptions: UseTenantOptions;
539
526
  * import { useRequest } from 'nitro/context'
540
527
  * const log = useLogger(useRequest())
541
528
  */
542
- declare function useLogger<T extends object = Record<string, unknown>>(eventOrRequest: HTTPEvent | ServerRequest, service?: string): import("evlog").AuditableLogger<T>;
529
+ export declare function useLogger<T extends object = Record<string, unknown>>(eventOrRequest: HTTPEvent | ServerRequest, service?: string): import("evlog").AuditableLogger<T>;
543
530
  /**
544
531
  * Create a structured error for a Cumulocity microservice. Always prefer this
545
532
  * over Nitro/h3's built-in `createError`: the returned error is captured in the
@@ -591,9 +578,7 @@ declare function useLogger<T extends object = Record<string, unknown>>(eventOrRe
591
578
  *
592
579
  * @see https://www.evlog.dev
593
580
  */
594
- declare const createError: typeof createError$1;
595
- //#endregion
596
- //#region src/utils/tasks.d.ts
581
+ export declare const createError: typeof createError$1;
597
582
  /**
598
583
  * Concurrency policy for a single job.
599
584
  * - `'single'`: at most one run of the job is in flight at a time. A tick that
@@ -602,13 +587,13 @@ declare const createError: typeof createError$1;
602
587
  * - `'parallel'`: every tick (or trigger) starts a fresh run, even if a previous
603
588
  * run has not finished. Runs of the same job may overlap.
604
589
  */
605
- type JobConcurrency = 'single' | 'parallel';
606
- type TaskRegistryPayload = TaskPayload;
607
- type TaskRegistryContext = TaskContext;
590
+ export type JobConcurrency = 'single' | 'parallel';
591
+ export type TaskRegistryPayload = TaskPayload;
592
+ export type TaskRegistryContext = TaskContext;
608
593
  /**
609
594
  * The event passed to a task handler on every execution.
610
595
  */
611
- interface TaskEvent<TPayload = TaskRegistryPayload> {
596
+ export interface TaskEvent<TPayload = TaskRegistryPayload> {
612
597
  /**
613
598
  * Name of the task (function) being executed.
614
599
  */
@@ -629,7 +614,7 @@ interface TaskEvent<TPayload = TaskRegistryPayload> {
629
614
  /**
630
615
  * A task handler function registered via {@link TaskRegistry.createTask}.
631
616
  */
632
- type TaskHandler<TPayload = TaskRegistryPayload, TResult = unknown> = (event: TaskEvent<TPayload>) => TResult | Promise<TResult>;
617
+ export type TaskHandler<TPayload = TaskRegistryPayload, TResult = unknown> = (event: TaskEvent<TPayload>) => TResult | Promise<TResult>;
633
618
  /**
634
619
  * When a job should run.
635
620
  * - `{ cron }`: recurring on a cron expression. 5- or 6-field (with seconds) is
@@ -640,7 +625,7 @@ type TaskHandler<TPayload = TaskRegistryPayload, TResult = unknown> = (event: Ta
640
625
  * taken literally (never re-projected to another timezone). Times in the past
641
626
  * fire almost immediately.
642
627
  */
643
- type JobSchedule = {
628
+ export type JobSchedule = {
644
629
  cron: string;
645
630
  } | {
646
631
  in: number | string;
@@ -650,7 +635,7 @@ type JobSchedule = {
650
635
  /**
651
636
  * Options for scheduling a job via {@link TaskRegistry.scheduleJob}.
652
637
  */
653
- interface ScheduleJobOptions<TTask extends string = string, TPayload = TaskRegistryPayload, TSchedule extends JobSchedule = JobSchedule> {
638
+ export interface ScheduleJobOptions<TTask extends string = string, TPayload = TaskRegistryPayload, TSchedule extends JobSchedule = JobSchedule> {
654
639
  /**
655
640
  * Unique, caller-chosen job name. This is the handle used by `cancelJob()`, `getJob()` and `triggerJob()`.
656
641
  */
@@ -700,7 +685,7 @@ interface ScheduleJobOptions<TTask extends string = string, TPayload = TaskRegis
700
685
  /**
701
686
  * Fields common to every job, regardless of kind.
702
687
  */
703
- interface BaseJobInfo {
688
+ export interface BaseJobInfo {
704
689
  /**
705
690
  * The job's unique name.
706
691
  */
@@ -725,7 +710,7 @@ interface BaseJobInfo {
725
710
  /**
726
711
  * Information about a one-shot job (scheduled with `{ in }` or `{ at }`).
727
712
  */
728
- interface OnceJobInfo extends BaseJobInfo {
713
+ export interface OnceJobInfo extends BaseJobInfo {
729
714
  kind: 'once';
730
715
  /**
731
716
  * Always `null` for one-shot jobs.
@@ -735,7 +720,7 @@ interface OnceJobInfo extends BaseJobInfo {
735
720
  /**
736
721
  * Information about a recurring job (scheduled with `{ cron }`).
737
722
  */
738
- interface RecurringJobInfo extends BaseJobInfo {
723
+ export interface RecurringJobInfo extends BaseJobInfo {
739
724
  kind: 'recurring';
740
725
  /**
741
726
  * The cron expression the job runs on.
@@ -754,24 +739,24 @@ interface RecurringJobInfo extends BaseJobInfo {
754
739
  * Public information about a registered job. A discriminated union on `kind` —
755
740
  * returned by `scheduleJob()`, `getJob()` and `listJobs()`.
756
741
  */
757
- type JobInfo = OnceJobInfo | RecurringJobInfo;
742
+ export type JobInfo = OnceJobInfo | RecurringJobInfo;
758
743
  /**
759
744
  * Maps a {@link JobSchedule} to the {@link JobInfo} variant it produces.
760
745
  */
761
- type JobInfoFor<TSchedule extends JobSchedule> = TSchedule extends {
746
+ export type JobInfoFor<TSchedule extends JobSchedule> = TSchedule extends {
762
747
  cron: string;
763
748
  } ? RecurringJobInfo : OnceJobInfo;
764
749
  /**
765
750
  * Describes the payload and result type of a single registered task.
766
751
  */
767
- interface TaskShape<TPayload = TaskRegistryPayload, TResult = unknown> {
752
+ export interface TaskShape<TPayload = TaskRegistryPayload, TResult = unknown> {
768
753
  payload: TPayload;
769
754
  result: TResult;
770
755
  }
771
756
  /**
772
757
  * A map of task name to its {@link TaskShape}, accumulated by the builder.
773
758
  */
774
- type TaskMap = Record<string, TaskShape>;
759
+ export type TaskMap = Record<string, TaskShape>;
775
760
  /**
776
761
  * A type-charged registry of tasks (functions) and the jobs that schedule them.
777
762
  *
@@ -779,7 +764,7 @@ type TaskMap = Record<string, TaskShape>;
779
764
  * `run()`, `scheduleJob()` and `triggerJob()` autocomplete known task names and
780
765
  * reject typos at compile time.
781
766
  */
782
- interface TaskRegistry<TTasks extends TaskMap = {}> {
767
+ export interface TaskRegistry<TTasks extends TaskMap = {}> {
783
768
  /**
784
769
  * Registers a task (function) under a compile-time-known name.\
785
770
  * Passing a name that is already registered is a type error (and throws at runtime).
@@ -852,9 +837,7 @@ interface TaskRegistry<TTasks extends TaskMap = {}> {
852
837
  * that schedules them executes, so register them from a Nitro plugin (or another
853
838
  * startup path) if they must run from boot.
854
839
  */
855
- declare function c8yTasks(): TaskRegistry;
856
- //#endregion
857
- //#region src/utils/realtime.d.ts
840
+ export declare function c8yTasks(): TaskRegistry;
858
841
  /**
859
842
  * Anything a caller might already have on hand that identifies a tenant, from
860
843
  * which {@link useTenantRealtimeClient} resolves the tenant's service
@@ -875,6 +858,5 @@ type TenantRealtimeInput = string | ICredentials | Client | H3Event | ServerRequ
875
858
  * rt.measurements.onCreate('device42', m => log.info(m.type, m.time))
876
859
  * @param input - A tenant id, `ICredentials`, a `Client`, or an `H3Event`/`ServerRequest`.
877
860
  */
878
- declare function useTenantRealtimeClient(input: TenantRealtimeInput): Promise<RealtimeClient>;
879
- //#endregion
880
- export { BaseJobInfo, ForeignTenantOptionCategory, ForeignTenantOptionKeyInput, JobConcurrency, JobInfo, JobInfoFor, JobSchedule, OnceJobInfo, PageSource, PagingOptions, RecurringJobInfo, ScheduleJobOptions, SetEditableOptions, TaskEvent, TaskHandler, TaskMap, TaskRegistry, TaskRegistryContext, TaskRegistryPayload, TaskShape, TenantOption, TenantOptionCategory, TenantOptionKeyInput, type TenantRealtimeInput, c8yTasks, createError, createLogger, fetchAllPages, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, paginate, paginatePages, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useTenantOptions, useTenantRealtimeClient, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
861
+ export declare function useTenantRealtimeClient(input: TenantRealtimeInput): Promise<RealtimeClient>;
862
+ export { type TenantRealtimeInput, createLogger };
package/dist/utils.mjs CHANGED
@@ -1,7 +1,8 @@
1
+ import { n as useTestingHooks } from "./testing-D5VjeD-F.mjs";
2
+ import process from "node:process";
1
3
  import { createRealtimeClient } from "c8y-realtime";
2
4
  import { createHash, randomBytes } from "node:crypto";
3
5
  import { Buffer } from "node:buffer";
4
- import process from "node:process";
5
6
  import { useLogger as useLogger$1 } from "evlog/nitro/v3";
6
7
  import { BasicAuth, Client, MicroserviceClientRequestAuth } from "@c8y/client";
7
8
  import { defineCachedFunction } from "nitro/cache";
@@ -13,7 +14,7 @@ import { c8yManifest } from "c8y-nitro/runtime";
13
14
  import { Cron } from "croner";
14
15
  import { ms } from "itty-time";
15
16
  import { WebSocket } from "ws";
16
- //#region src/utils/internal/common.ts
17
+ // src/utils/internal/common.ts
17
18
  /**
18
19
  * Whether two sets of tenant credentials are equivalent for the purposes of
19
20
  * change detection: same tenant, user, and password. Used to detect a
@@ -48,8 +49,7 @@ function convertRequestHeadersToC8yFormat(request) {
48
49
  if (cookie) headers.cookie = cookie;
49
50
  return headers;
50
51
  }
51
- //#endregion
52
- //#region src/utils/internal/tenant.ts
52
+ // src/utils/internal/tenant.ts
53
53
  const USER_TENANT_CACHE_SALT = randomBytes(32).toString("hex");
54
54
  function getCookieValue(cookieHeader, name) {
55
55
  try {
@@ -106,8 +106,7 @@ defineCachedFunction(async (requestOrEvent, _tenantId) => {
106
106
  swr: false,
107
107
  getKey: (_requestOrEvent, tenantId) => tenantId
108
108
  });
109
- //#endregion
110
- //#region src/utils/logging.ts
109
+ // src/utils/logging.ts
111
110
  /**
112
111
  * Returns the request-scoped wide-event logger for the current request.
113
112
  *
@@ -187,8 +186,7 @@ function useLogger(eventOrRequest, service) {
187
186
  * @see https://www.evlog.dev
188
187
  */
189
188
  const createError = createError$1;
190
- //#endregion
191
- //#region src/utils/credentials.ts
189
+ // src/utils/credentials.ts
192
190
  let prevCredentials = null;
193
191
  function shouldEmitTenantCredentialsUpdated(prev, next) {
194
192
  if (!prev) return true;
@@ -310,8 +308,7 @@ async function useUserTenantCredentials(requestOrEvent) {
310
308
  request.context["c8y_user_tenant_credentials"] = userTenantCreds;
311
309
  return userTenantCreds;
312
310
  }
313
- //#endregion
314
- //#region src/utils/client.ts
311
+ // src/utils/client.ts
315
312
  /**
316
313
  * Creates a Cumulocity client authenticated with the current user's credentials.\
317
314
  * Extracts credentials from the Authorization header of the current request.
@@ -329,7 +326,7 @@ function useUserClient(requestOrEvent) {
329
326
  const auth = new MicroserviceClientRequestAuth(headers);
330
327
  const client = new Client(auth, process.env.C8Y_BASEURL);
331
328
  const forwardedHost = request.headers.get("x-forwarded-host")?.split(",")[0]?.trim();
332
- client.core.defaultHeaders["X-Forwarded-Host"] = forwardedHost || process.env.C8Y_BASEURL;
329
+ if (forwardedHost) client.core.defaultHeaders["X-Forwarded-Host"] = forwardedHost;
333
330
  request.context ??= {};
334
331
  request.context["c8y_user_client"] = client;
335
332
  return client;
@@ -401,8 +398,7 @@ async function useDeployedTenantClient() {
401
398
  client.core.tenant = tenant;
402
399
  return client;
403
400
  }
404
- //#endregion
405
- //#region src/utils/paging.ts
401
+ // src/utils/paging.ts
406
402
  async function resolvePageSource(source) {
407
403
  return await (typeof source === "function" ? source() : source);
408
404
  }
@@ -492,8 +488,7 @@ async function fetchAllPages(source, options) {
492
488
  for await (const page of paginatePages(source, options)) all.push(...page);
493
489
  return all;
494
490
  }
495
- //#endregion
496
- //#region src/utils/resources.ts
491
+ // src/utils/resources.ts
497
492
  /**
498
493
  * Fetches the current user from Cumulocity using credentials extracted from the current request's headers.
499
494
  * This is a non-cached version - fetches fresh data on every call.
@@ -535,8 +530,7 @@ async function useUserRoles(requestOrEvent) {
535
530
  request.context["c8y_user_roles"] = userRoles;
536
531
  return userRoles;
537
532
  }
538
- //#endregion
539
- //#region src/utils/middleware.ts
533
+ // src/utils/middleware.ts
540
534
  const probePaths = [c8yManifest.livenessProbe?.httpGet?.path, c8yManifest.readinessProbe?.httpGet?.path].filter((path) => Boolean(path));
541
535
  function isProbeRequest(pathname) {
542
536
  return probePaths.some((probePath) => pathname.startsWith(probePath));
@@ -590,8 +584,7 @@ function isUserFromDeployedTenant() {
590
584
  });
591
585
  });
592
586
  }
593
- //#endregion
594
- //#region src/utils/internal/tenantOptionFetchers.ts
587
+ // src/utils/internal/tenantOptionFetchers.ts
595
588
  /**
596
589
  * Internal storage for cached option fetchers, keyed by
597
590
  * `${tenant}::${category}::${key}`.
@@ -610,8 +603,7 @@ const tenantOptionFetchers = {};
610
603
  function fetcherStoreKey(tenant, category, key) {
611
604
  return `${tenant}::${category}::${key}`;
612
605
  }
613
- //#endregion
614
- //#region src/utils/tenantOptions.ts
606
+ // src/utils/tenantOptions.ts
615
607
  /**
616
608
  * The microservice's own settings category, resolved once from runtime config
617
609
  * (`manifest.settingsCategory` → `contextPath` → `name`). This is independent of
@@ -992,8 +984,7 @@ const useTenantOptions = Object.assign(useTenantOptionsBase, {
992
984
  invalidateAll: () => invalidateFetchers(() => true),
993
985
  refreshAll: () => refreshFetchers(() => true)
994
986
  });
995
- //#endregion
996
- //#region src/utils/tasks.ts
987
+ // src/utils/tasks.ts
997
988
  function resolveOnceDate(schedule) {
998
989
  if ("in" in schedule) {
999
990
  if (typeof schedule.in === "string") {
@@ -1160,8 +1151,7 @@ function c8yTasks() {
1160
1151
  };
1161
1152
  return api;
1162
1153
  }
1163
- //#endregion
1164
- //#region src/utils/realtime.ts
1154
+ // src/utils/realtime.ts
1165
1155
  /**
1166
1156
  * `ws` as the factory type c8y-realtime asks for.
1167
1157
  *
@@ -1212,7 +1202,7 @@ function createRealtimeClientForTenant(tenantId, creds) {
1212
1202
  return createRealtimeClient({
1213
1203
  ...realtimeConfig(),
1214
1204
  ...toLibCredentials(tenantId, creds),
1215
- webSocketImpl: wsWebSocketImpl,
1205
+ webSocketImpl: useTestingHooks()?.WebSocket ?? wsWebSocketImpl,
1216
1206
  logger: console
1217
1207
  });
1218
1208
  }
@@ -1339,5 +1329,4 @@ async function useTenantRealtimeClient(input) {
1339
1329
  }
1340
1330
  return client;
1341
1331
  }
1342
- //#endregion
1343
1332
  export { c8yTasks, createError, createLogger, fetchAllPages, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, paginate, paginatePages, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useTenantOptions, useTenantRealtimeClient, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8y-nitro",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "Lightning fast Cumulocity IoT microservice development powered by Nitro",
6
6
  "keywords": [
@@ -25,6 +25,9 @@
25
25
  "exports": {
26
26
  ".": "./dist/index.mjs",
27
27
  "./cli": "./dist/cli/index.mjs",
28
+ "./testing": "./dist/testing.mjs",
29
+ "./testing/setup": "./dist/testing/setup.mjs",
30
+ "./testing/vite": "./dist/testing/vite.mjs",
28
31
  "./types": "./dist/types.mjs",
29
32
  "./utils": "./dist/utils.mjs",
30
33
  "./package.json": "./package.json"
@@ -38,40 +41,52 @@
38
41
  "dist"
39
42
  ],
40
43
  "dependencies": {
41
- "c12": "^4.0.0-rc.1",
44
+ "c12": "^4.0.0-rc.4",
42
45
  "c8y-realtime": "^0.2.2",
43
46
  "citty": "^0.2.2",
44
47
  "consola": "^3.4.2",
45
- "croner": "^10.0.1",
46
- "evlog": "^2.28.1",
48
+ "croner": "^11.0.0",
49
+ "evlog": "^2.34.0",
47
50
  "itty-time": "^2.0.2",
48
- "jszip": "^3.10.1",
51
+ "jszip": "^3.10.2",
49
52
  "pathe": "^2.0.3",
50
- "pkg-types": "^2.3.2",
53
+ "pkg-types": "^2.4.0",
51
54
  "spinnies": "^0.5.1",
52
55
  "tinyexec": "^1.3.1",
53
- "ws": "^8.21.3"
56
+ "ws": "^8.22.1"
54
57
  },
55
58
  "devDependencies": {
56
- "@c8y/client": "^1023.97.0",
57
- "@schplitt/eslint-config": "^1.5.2",
58
- "@types/node": "^24.12.3",
59
+ "@c8y/client": "^1023.97.8",
60
+ "@schplitt/eslint-config": "^1.5.4",
61
+ "@types/node": "^24.19.2",
59
62
  "@types/spinnies": "^0.5.3",
60
- "@types/ws": "^8.18.1",
63
+ "@types/ws": "^8.18.2",
61
64
  "bumpp": "^12.3.0",
62
65
  "changelogithub": "^15.0.5",
63
- "eslint": "^10.9.1",
64
- "memfs": "^4.69.1",
65
- "tsdown": "^0.22.14",
66
- "tsnapi": "^1.4.2",
66
+ "eslint": "^10.14.0",
67
+ "memfs": "^4.80.0",
68
+ "rolldown": "^1.2.16",
69
+ "tsdown": "^0.23.4",
70
+ "tsnapi": "^1.6.0",
67
71
  "typescript": "^6.0.3",
68
- "vitepress": "2.0.0-alpha.17",
69
- "vitepress-plugin-llms": "1.13.3",
70
- "vitest": "^4.1.11"
72
+ "vite": "^8.3.5",
73
+ "vitepress": "2.0.0-alpha.21",
74
+ "vitepress-plugin-llms": "1.14.0",
75
+ "vitest": "^5.0.5"
71
76
  },
72
77
  "peerDependencies": {
73
78
  "@c8y/client": ">=1021",
74
- "nitro": "3.0.260903-beta"
79
+ "nitro": "3.0.261102-rc",
80
+ "vite": "^7.0.0 || ^8.0.0",
81
+ "vitest": "^5.0.0"
82
+ },
83
+ "peerDependenciesMeta": {
84
+ "vite": {
85
+ "optional": true
86
+ },
87
+ "vitest": {
88
+ "optional": true
89
+ }
75
90
  },
76
91
  "engines": {
77
92
  "node": ">=24.0.0"
@@ -79,6 +94,7 @@
79
94
  "scripts": {
80
95
  "dev": "tsdown --watch",
81
96
  "gen:roles": "node scripts/generate-roles.ts",
97
+ "gen:testing-capabilities": "node scripts/generate-testing-capabilities.ts",
82
98
  "build": "tsdown",
83
99
  "build:update": "tsdown --update-snapshot",
84
100
  "lint": "eslint",
@@ -88,6 +104,7 @@
88
104
  "release": "bumpp",
89
105
  "test": "vitest",
90
106
  "test:run": "vitest run",
107
+ "test:contract": "vitest run --config vitest.contract.config.ts",
91
108
  "docs:dev": "vitepress dev docs",
92
109
  "docs:build": "vitepress build docs",
93
110
  "docs:preview": "vitepress preview docs"