c8y-nitro 0.2.0 → 0.3.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 +118 -1
- package/dist/index.mjs +8 -5
- package/dist/package.mjs +1 -1
- package/dist/types.d.mts +9 -0
- package/dist/utils/logging.d.mts +3 -0
- package/dist/utils/logging.mjs +4 -0
- package/dist/utils.d.mts +2 -1
- package/dist/utils.mjs +2 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -103,6 +103,8 @@ C8Y_BOOTSTRAP_PASSWORD=<generated-password>
|
|
|
103
103
|
|
|
104
104
|
> **Manual Bootstrap**: For more control or troubleshooting, you can use the [CLI bootstrap command](#cli-commands) to manually register your microservice.
|
|
105
105
|
|
|
106
|
+
> **Disable Auto-Bootstrap**: Set `skipBootstrap: true` in your c8y config to disable auto-bootstrap entirely. This is useful in CI/CD pipelines or when you want to manage bootstrap manually.
|
|
107
|
+
|
|
106
108
|
## Automatic Zip Creation
|
|
107
109
|
|
|
108
110
|
`c8y-nitro` automatically generates a ready-to-deploy microservice zip package after each build. The process includes:
|
|
@@ -241,6 +243,109 @@ export class MyComponent {
|
|
|
241
243
|
|
|
242
244
|
> **Note**: The client regenerates automatically when routes change during development.
|
|
243
245
|
|
|
246
|
+
## Logging
|
|
247
|
+
|
|
248
|
+
`c8y-nitro` builds on [evlog](https://www.evlog.dev) to provide structured **wide-event logging**, one comprehensive log per request that accumulates all relevant context rather than scattering individual log lines throughout your code.
|
|
249
|
+
|
|
250
|
+
evlog is automatically configured, no extra setup required. The service name is derived from your package name.
|
|
251
|
+
|
|
252
|
+
### useLogger
|
|
253
|
+
|
|
254
|
+
Use `useLogger(event)` in your route handlers to get a request-scoped logger. The logger accumulates context throughout the request lifetime and emits a single wide event when the response is sent.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { defineHandler } from 'nitro/h3'
|
|
258
|
+
import { useLogger } from 'c8y-nitro/utils'
|
|
259
|
+
|
|
260
|
+
export default defineHandler(async (event) => {
|
|
261
|
+
const log = useLogger(event)
|
|
262
|
+
|
|
263
|
+
const user = await useUser(event)
|
|
264
|
+
log.set({ action: 'process-order', user: { id: user.userName } })
|
|
265
|
+
|
|
266
|
+
// Add more context as it becomes available
|
|
267
|
+
log.set({ order: { id: '42', total: 9999 } })
|
|
268
|
+
|
|
269
|
+
return { success: true }
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
> **Note**: `useLogger` requires the `event` parameter. If you enable `experimental.asyncContext: true` in your Nitro config, you can access the logger anywhere in the call stack via `useRequest()` from `nitro/context` — see the [evlog Nitro v3 setup](https://www.evlog.dev/getting-started/installation#nitro-v3) for details.
|
|
274
|
+
|
|
275
|
+
### createError
|
|
276
|
+
|
|
277
|
+
Use `createError` from `c8y-nitro/utils` instead of Nitro's built-in error helper to get richer, structured error responses. This adds `why`, `fix`, and `link` fields that are:
|
|
278
|
+
|
|
279
|
+
- Logged as part of the wide event so you can see exactly what went wrong without guessing
|
|
280
|
+
- Returned in the JSON response body so clients can display actionable context
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { defineHandler } from 'nitro/h3'
|
|
284
|
+
import { useLogger, createError } from 'c8y-nitro/utils'
|
|
285
|
+
|
|
286
|
+
export default defineHandler(async (event) => {
|
|
287
|
+
const log = useLogger(event)
|
|
288
|
+
log.set({ action: 'payment', userId: 'user_123' })
|
|
289
|
+
|
|
290
|
+
throw createError({
|
|
291
|
+
message: 'Payment failed',
|
|
292
|
+
status: 402,
|
|
293
|
+
why: 'Card declined by issuer (insufficient funds)',
|
|
294
|
+
fix: 'Try a different payment method or contact your bank',
|
|
295
|
+
link: 'https://docs.example.com/payments/declined',
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The error response returned to the client:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"message": "Payment failed",
|
|
305
|
+
"status": 402,
|
|
306
|
+
"data": {
|
|
307
|
+
"why": "Card declined by issuer (insufficient funds)",
|
|
308
|
+
"fix": "Try a different payment method or contact your bank",
|
|
309
|
+
"link": "https://docs.example.com/payments/declined"
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
> **Tip**: Always prefer `createError` from `c8y-nitro/utils`. It ensures the error is captured in the wide log event with full context, making investigation straightforward.
|
|
315
|
+
|
|
316
|
+
### createLogger (standalone)
|
|
317
|
+
|
|
318
|
+
For code that runs **outside a request handler** — background jobs, queue workers, event-driven workflows, scheduled tasks — use `createLogger()` to get the same wide-event logger interface without needing an HTTP event.
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import { createLogger } from 'c8y-nitro/utils'
|
|
322
|
+
|
|
323
|
+
export async function processSubscriptionRenewal(tenantId: string) {
|
|
324
|
+
const log = createLogger({ job: 'subscription-renewal', tenantId })
|
|
325
|
+
|
|
326
|
+
log.set({ subscription: { id: 'sub_123', plan: 'pro' } })
|
|
327
|
+
|
|
328
|
+
// ... do work ...
|
|
329
|
+
|
|
330
|
+
log.set({ result: 'renewed' })
|
|
331
|
+
log.emit() // Must call emit() manually outside request context
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
This is useful for Cumulocity notification workflows where your microservice reacts to platform events (device management, alarms, etc.) outside of the standard request/response cycle.
|
|
336
|
+
|
|
337
|
+
> **Note**: Unlike `useLogger`, `createLogger` does **not** auto-emit at request end. You must call `log.emit()` manually when the work is complete.
|
|
338
|
+
|
|
339
|
+
For more on wide events, structured errors, and advanced configuration (sampling, draining to Axiom/Loki, enrichers), see the [evlog documentation](https://www.evlog.dev/core-concepts/wide-events).
|
|
340
|
+
|
|
341
|
+
### Logging Utilities
|
|
342
|
+
|
|
343
|
+
| Function | Description | Request Context |
|
|
344
|
+
| ---------------------- | ------------------------------------------------------------- | :-------------: |
|
|
345
|
+
| `useLogger(event)` | Get the request-scoped wide-event logger | ✅ |
|
|
346
|
+
| `createLogger(ctx?)` | Create a standalone wide-event logger; call `emit()` manually | ❌ |
|
|
347
|
+
| `createError(options)` | Create a structured error with `why`, `fix`, `link` | ❌ |
|
|
348
|
+
|
|
244
349
|
## Utilities
|
|
245
350
|
|
|
246
351
|
`c8y-nitro` provides several utility functions to simplify common tasks in Cumulocity microservices.
|
|
@@ -401,10 +506,22 @@ pnpm dev
|
|
|
401
506
|
# Build for production
|
|
402
507
|
pnpm build
|
|
403
508
|
|
|
404
|
-
# Run tests
|
|
509
|
+
# Run tests (watch mode)
|
|
405
510
|
pnpm test
|
|
511
|
+
|
|
512
|
+
# Run tests once
|
|
513
|
+
pnpm test:run
|
|
406
514
|
```
|
|
407
515
|
|
|
516
|
+
### Testing
|
|
517
|
+
|
|
518
|
+
Tests are organized in two categories:
|
|
519
|
+
|
|
520
|
+
- **Unit tests** (`tests/unit/`) — Test individual functions in isolation
|
|
521
|
+
- **Server tests** (`tests/server/`) — Integration tests that spin up a Nitro dev server with the c8y-nitro module
|
|
522
|
+
|
|
523
|
+
Server tests use Nitro's virtual modules to mock `@c8y/client` at build time, allowing full integration testing without real Cumulocity API calls. See [AGENTS.md](AGENTS.md#server-integration-tests) for implementation details.
|
|
524
|
+
|
|
408
525
|
## License
|
|
409
526
|
|
|
410
527
|
MIT
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createC8yManifestFromNitro } from "./module/manifest.mjs";
|
|
1
2
|
import { writeAPIClient } from "./module/apiClient.mjs";
|
|
2
3
|
import { createC8yZip } from "./module/c8yzip.mjs";
|
|
3
4
|
import { setupRuntime } from "./module/runtime.mjs";
|
|
@@ -6,6 +7,7 @@ import { registerRuntime } from "./module/register.mjs";
|
|
|
6
7
|
import { checkProbes } from "./module/probeCheck.mjs";
|
|
7
8
|
import { autoBootstrap } from "./module/autoBootstrap.mjs";
|
|
8
9
|
import { name } from "./package.mjs";
|
|
10
|
+
import evlog from "evlog/nitro/v3";
|
|
9
11
|
|
|
10
12
|
//#region src/index.ts
|
|
11
13
|
function c8y() {
|
|
@@ -17,8 +19,8 @@ function c8y() {
|
|
|
17
19
|
nitro.options.typescript.tsConfig = {};
|
|
18
20
|
nitro.options.typescript.tsConfig.include = ["./**/*.d.ts"];
|
|
19
21
|
nitro.options.typescript.tsConfig.exclude = [];
|
|
20
|
-
nitro.options.noExternals = nitro.options.noExternals
|
|
21
|
-
...nitro.options.noExternals
|
|
22
|
+
nitro.options.noExternals = nitro.options.noExternals === true ? true : [
|
|
23
|
+
...Array.isArray(nitro.options.noExternals) ? nitro.options.noExternals : [],
|
|
22
24
|
name,
|
|
23
25
|
"@c8y/client"
|
|
24
26
|
];
|
|
@@ -26,7 +28,9 @@ function c8y() {
|
|
|
26
28
|
nitro.logger.error(`Unsupported preset "${nitro.options.preset}" for c8y-nitro module, only node presets are supported.`);
|
|
27
29
|
throw new Error("Unsupported preset for c8y-nitro module");
|
|
28
30
|
}
|
|
29
|
-
await
|
|
31
|
+
const { setup: setupEvlog } = evlog({ env: { service: (await createC8yManifestFromNitro(nitro)).name } });
|
|
32
|
+
await setupEvlog(nitro);
|
|
33
|
+
if (!options.skipBootstrap) await autoBootstrap(nitro);
|
|
30
34
|
await setupRuntimeConfig(nitro, options);
|
|
31
35
|
setupRuntime(nitro, options.manifest);
|
|
32
36
|
nitro.hooks.hook("dev:reload", async () => {
|
|
@@ -52,7 +56,6 @@ function c8y() {
|
|
|
52
56
|
}
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
|
-
var src_default = c8y;
|
|
56
59
|
|
|
57
60
|
//#endregion
|
|
58
|
-
export { c8y,
|
|
61
|
+
export { c8y, c8y as default };
|
package/dist/package.mjs
CHANGED
package/dist/types.d.mts
CHANGED
|
@@ -11,6 +11,15 @@ interface C8yNitroModuleOptions {
|
|
|
11
11
|
apiClient?: C8YAPIClientOptions;
|
|
12
12
|
zip?: C8YZipOptions;
|
|
13
13
|
cache?: C8yCacheOptions;
|
|
14
|
+
/**
|
|
15
|
+
* Disable auto-bootstrap during development.
|
|
16
|
+
* When true, the module will not automatically register the microservice
|
|
17
|
+
* or retrieve bootstrap credentials on startup.
|
|
18
|
+
*
|
|
19
|
+
* Useful for CI/CD pipelines or manual bootstrap management.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
skipBootstrap?: boolean;
|
|
14
23
|
}
|
|
15
24
|
//#endregion
|
|
16
25
|
export { C8YAPIClientOptions, type C8YManifestOptions, C8YRoles, C8YTenantOptionKey, C8YTenantOptionKeysCacheConfig, C8YZipOptions, type C8yCacheOptions, C8yNitroModuleOptions };
|
package/dist/utils.d.mts
CHANGED
|
@@ -3,4 +3,5 @@ import { hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant
|
|
|
3
3
|
import { useUser, useUserRoles } from "./utils/resources.mjs";
|
|
4
4
|
import { useDeployedTenantCredentials, useSubscribedTenantCredentials, useUserTenantCredentials } from "./utils/credentials.mjs";
|
|
5
5
|
import { useTenantOption } from "./utils/tenantOptions.mjs";
|
|
6
|
-
|
|
6
|
+
import { createError, createLogger, useLogger } from "./utils/logging.mjs";
|
|
7
|
+
export { createError, createLogger, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
|
package/dist/utils.mjs
CHANGED
|
@@ -3,5 +3,6 @@ import { useDeployedTenantClient, useSubscribedTenantClients, useUserClient, use
|
|
|
3
3
|
import { useUser, useUserRoles } from "./utils/resources.mjs";
|
|
4
4
|
import { hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant } from "./utils/middleware.mjs";
|
|
5
5
|
import { useTenantOption } from "./utils/tenantOptions.mjs";
|
|
6
|
+
import { createError, createLogger, useLogger } from "./utils/logging.mjs";
|
|
6
7
|
|
|
7
|
-
export { hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, useDeployedTenantClient, useDeployedTenantCredentials, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
|
|
8
|
+
export { createError, createLogger, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, 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.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightning fast Cumulocity IoT microservice development powered by Nitro",
|
|
6
6
|
"keywords": [
|
|
@@ -48,9 +48,10 @@
|
|
|
48
48
|
"dist"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"c12": "^
|
|
51
|
+
"c12": "^4.0.0-beta.2",
|
|
52
52
|
"citty": "^0.2.0",
|
|
53
53
|
"consola": "^3.4.2",
|
|
54
|
+
"evlog": "^2.1.0",
|
|
54
55
|
"jszip": "^3.10.1",
|
|
55
56
|
"pathe": "^2.0.3",
|
|
56
57
|
"pkg-types": "^2.3.0",
|
|
@@ -58,12 +59,12 @@
|
|
|
58
59
|
"tinyexec": "^1.0.2"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
|
-
"@schplitt/eslint-config": "^1.
|
|
62
|
+
"@schplitt/eslint-config": "^1.3.1",
|
|
62
63
|
"@types/spinnies": "^0.5.3",
|
|
63
|
-
"bumpp": "^10.4.
|
|
64
|
-
"eslint": "^
|
|
64
|
+
"bumpp": "^10.4.1",
|
|
65
|
+
"eslint": "^10.0.0",
|
|
65
66
|
"memfs": "^4.56.10",
|
|
66
|
-
"tsdown": "^0.20.
|
|
67
|
+
"tsdown": "^0.20.3",
|
|
67
68
|
"typescript": "^5.9.3",
|
|
68
69
|
"vitest": "^4.0.18"
|
|
69
70
|
},
|