c8y-nitro 0.4.1 → 0.5.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 +22 -2
- package/dist/{bootstrap-BqWPkH8q.mjs → bootstrap-BSdVwFWO.mjs} +3 -3
- package/dist/{c8y-api-BBSKRwKs.mjs → c8y-api-BgTNTqHd.mjs} +57 -2
- package/dist/cli/index.mjs +4 -4
- package/dist/{config-Dqi-ttQi.mjs → config-BRnvtthI.mjs} +4 -1
- package/dist/{index-CzUqbp5C.d.mts → index-Bvu7DqDt.d.mts} +26 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +38 -6
- package/dist/logging-CVlaRS4O.mjs +3 -0
- package/dist/{options-BDDJWdph.mjs → options-BkWL1FOa.mjs} +7 -5
- package/dist/{package-CobwNpP9.mjs → package-bfMasPPg.mjs} +1 -1
- package/dist/{roles-DJxp2d8p.mjs → roles-CoBPqvDv.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 +2 -2
- package/dist/utils.d.mts +0 -5
- package/dist/utils.mjs +70 -15
- package/package.json +14 -22
package/README.md
CHANGED
|
@@ -143,6 +143,7 @@ Credential caching can be configured to optimize performance. By default, subscr
|
|
|
143
143
|
```ts
|
|
144
144
|
export default defineNitroConfig({
|
|
145
145
|
c8y: {
|
|
146
|
+
enableTenantOptionsInvalidationRoute: false,
|
|
146
147
|
cache: {
|
|
147
148
|
credentialsTTL: 300, // Cache credentials for 5 minutes (in seconds)
|
|
148
149
|
defaultTenantOptionsTTL: 600, // Default cache for tenant options (in seconds)
|
|
@@ -179,6 +180,21 @@ C8Y_DEVELOPMENT_PASSWORD=your-password
|
|
|
179
180
|
|
|
180
181
|
This enables testing of access control middlewares like `hasUserRequiredRole()` and `isUserFromAllowedTenant()` without needing to manually set authorization headers.
|
|
181
182
|
|
|
183
|
+
If you run a local proxy that already forwards a user session or authorization header, disable this middleware:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
export default defineNitroConfig({
|
|
187
|
+
c8y: {
|
|
188
|
+
dev: {
|
|
189
|
+
injectUser: false,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
modules: [c8y()],
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
When disabled, `c8y-nitro` does not register the development user injection middleware, so incoming auth headers stay untouched.
|
|
197
|
+
|
|
182
198
|
### Managing Development User Roles
|
|
183
199
|
|
|
184
200
|
Use the [CLI roles command](#cli-commands) to assign or remove your microservice's custom roles to your development user:
|
|
@@ -440,7 +456,7 @@ export default defineNitroConfig({
|
|
|
440
456
|
manifest: {
|
|
441
457
|
settings: [
|
|
442
458
|
{ key: 'myOption', defaultValue: 'default' },
|
|
443
|
-
{ key: 'credentials.secret' }, // Encrypted option
|
|
459
|
+
{ key: 'credentials.secret', defaultValue: 'change-me' }, // Encrypted option
|
|
444
460
|
],
|
|
445
461
|
settingsCategory: 'my-service', // Optional, defaults to contextPath/name
|
|
446
462
|
requiredRoles: ['ROLE_OPTION_MANAGEMENT_READ'], // Required for reading tenant options
|
|
@@ -450,9 +466,11 @@ export default defineNitroConfig({
|
|
|
450
466
|
})
|
|
451
467
|
```
|
|
452
468
|
|
|
469
|
+
`manifest.settings[].defaultValue` is required and must be a non-empty string. `''` is rejected during manifest generation so invalid settings fail early during development/build.
|
|
470
|
+
|
|
453
471
|
> **Important**: To read tenant options, your microservice **must** have the `ROLE_OPTION_MANAGEMENT_READ` role in `manifest.requiredRoles`. Without this role, API calls will fail with a 403 Forbidden error.
|
|
454
472
|
|
|
455
|
-
> **Note on Encrypted Options**: Keys prefixed with `credentials.` are stored encrypted by Cumulocity.
|
|
473
|
+
> **Note on Encrypted Options**: Keys prefixed with `credentials.` are stored encrypted by Cumulocity. See more details [here](https://cumulocity.com/api/core/#operation/postOptionCollectionResource).
|
|
456
474
|
|
|
457
475
|
> **Note on Missing Options**: If a tenant option is not set (404 Not Found), `useTenantOption()` returns `undefined` instead of throwing an error. Other errors (e.g., 403 Forbidden) are thrown normally.
|
|
458
476
|
|
|
@@ -480,6 +498,8 @@ export default defineNitroConfig({
|
|
|
480
498
|
| `isUserFromAllowedTenant(tenant\|tenants)` | Check if user is from allowed tenant(s) | ✅ |
|
|
481
499
|
| `isUserFromDeployedTenant()` | Check if user is from the deployed tenant | ✅ |
|
|
482
500
|
|
|
501
|
+
Probe requests targeting the manifest-defined `livenessProbe.httpGet.path` or `readinessProbe.httpGet.path` bypass these auth helpers so orchestration health checks are not blocked by route-wide access control.
|
|
502
|
+
|
|
483
503
|
## CLI Commands
|
|
484
504
|
|
|
485
505
|
| Command | Description |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as findMicroserviceByName, d as updateMicroservice, l as subscribeToApplication, n as createBasicAuthHeader, o as getBootstrapCredentials, p as createC8yManifest, r as createMicroservice } from "./c8y-api-
|
|
1
|
+
import { a as findMicroserviceByName, d as updateMicroservice, l as subscribeToApplication, n as createBasicAuthHeader, o as getBootstrapCredentials, p as createC8yManifest, r as createMicroservice } from "./c8y-api-BgTNTqHd.mjs";
|
|
2
2
|
import { t as writeBootstrapCredentials } from "./env-file-B0BK-uZW.mjs";
|
|
3
|
-
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-
|
|
3
|
+
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BRnvtthI.mjs";
|
|
4
4
|
import { defineCommand, runCommand } from "citty";
|
|
5
5
|
import { consola } from "consola";
|
|
6
6
|
//#region src/cli/commands/bootstrap.ts
|
|
@@ -52,7 +52,7 @@ var bootstrap_default = defineCommand({
|
|
|
52
52
|
});
|
|
53
53
|
consola.success(`Bootstrap credentials written to ${envFileName}`);
|
|
54
54
|
if (manifest.roles && manifest.roles.length > 0) {
|
|
55
|
-
if (await consola.prompt("Do you want to manage microservice roles for your development user?", { type: "confirm" })) await runCommand(await import("./roles-
|
|
55
|
+
if (await consola.prompt("Do you want to manage microservice roles for your development user?", { type: "confirm" })) await runCommand(await import("./roles-CoBPqvDv.mjs").then((r) => r.default), { rawArgs: [] });
|
|
56
56
|
}
|
|
57
57
|
consola.success("Bootstrap complete!");
|
|
58
58
|
}
|
|
@@ -3,8 +3,15 @@ import { Buffer } from "node:buffer";
|
|
|
3
3
|
//#region src/module/constants.ts
|
|
4
4
|
const GENERATED_LIVENESS_ROUTE = "/_c8y_nitro/liveness";
|
|
5
5
|
const GENERATED_READINESS_ROUTE = "/_c8y_nitro/readiness";
|
|
6
|
+
const GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE = "/_c8y_nitro/invalidate-tenant-options";
|
|
6
7
|
//#endregion
|
|
7
8
|
//#region src/module/manifest.ts
|
|
9
|
+
function validateManifestSettings(options) {
|
|
10
|
+
const invalidSettings = options.settings?.filter((setting) => typeof setting.defaultValue !== "string" || setting.defaultValue.length === 0) ?? [];
|
|
11
|
+
if (invalidSettings.length === 0) return;
|
|
12
|
+
const invalidKeys = invalidSettings.map((setting) => `"${setting.key}"`).join(", ");
|
|
13
|
+
throw new Error(`manifest.settings entries must define a non-empty defaultValue. Invalid keys: ${invalidKeys}`);
|
|
14
|
+
}
|
|
8
15
|
async function readPackageJsonFieldsForManifest(rootDir, logger) {
|
|
9
16
|
logger?.debug(`Reading package file from ${rootDir}`);
|
|
10
17
|
const pkg = await readPackage(rootDir);
|
|
@@ -37,6 +44,7 @@ async function readPackageJsonFieldsForManifest(rootDir, logger) {
|
|
|
37
44
|
* @param logger - Optional logger for debug output
|
|
38
45
|
*/
|
|
39
46
|
async function createC8yManifest(rootDir, options = {}, logger) {
|
|
47
|
+
validateManifestSettings(options);
|
|
40
48
|
const { name, version, provider, ...restManifestFields } = await readPackageJsonFieldsForManifest(rootDir, logger);
|
|
41
49
|
const probeFields = {};
|
|
42
50
|
if (!options.livenessProbe?.httpGet) probeFields.livenessProbe = {
|
|
@@ -286,7 +294,35 @@ async function getTenantOption(baseUrl, category, key, authHeader) {
|
|
|
286
294
|
return (await response.json()).value;
|
|
287
295
|
}
|
|
288
296
|
/**
|
|
289
|
-
*
|
|
297
|
+
* Creates a tenant option.
|
|
298
|
+
* @param baseUrl - The Cumulocity base URL
|
|
299
|
+
* @param category - The category of the option
|
|
300
|
+
* @param key - The option key
|
|
301
|
+
* @param value - The value to set
|
|
302
|
+
* @param authHeader - The Basic Auth header
|
|
303
|
+
*/
|
|
304
|
+
async function createTenantOption(baseUrl, category, key, value, authHeader) {
|
|
305
|
+
const url = `${baseUrl}/tenant/options`;
|
|
306
|
+
const response = await fetch(url, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: {
|
|
309
|
+
"Authorization": authHeader,
|
|
310
|
+
"Content-Type": "application/vnd.com.nsn.cumulocity.option+json",
|
|
311
|
+
"Accept": "application/json"
|
|
312
|
+
},
|
|
313
|
+
body: JSON.stringify({
|
|
314
|
+
category,
|
|
315
|
+
key,
|
|
316
|
+
value
|
|
317
|
+
})
|
|
318
|
+
});
|
|
319
|
+
if (!response.ok) {
|
|
320
|
+
const errorText = await response.text();
|
|
321
|
+
throw new Error(`Failed to create tenant option ${category}/${key}: ${response.status} ${response.statusText}\n${errorText}`, { cause: response });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Updates an existing tenant option.
|
|
290
326
|
* @param baseUrl - The Cumulocity base URL
|
|
291
327
|
* @param category - The category of the option
|
|
292
328
|
* @param key - The option key
|
|
@@ -310,6 +346,25 @@ async function updateTenantOption(baseUrl, category, key, value, authHeader) {
|
|
|
310
346
|
}
|
|
311
347
|
}
|
|
312
348
|
/**
|
|
349
|
+
* Updates a tenant option if it exists, otherwise creates it.
|
|
350
|
+
* @param baseUrl - The Cumulocity base URL
|
|
351
|
+
* @param category - The category of the option
|
|
352
|
+
* @param key - The option key
|
|
353
|
+
* @param value - The new value to set
|
|
354
|
+
* @param authHeader - The Basic Auth header
|
|
355
|
+
*/
|
|
356
|
+
async function upsertTenantOption(baseUrl, category, key, value, authHeader) {
|
|
357
|
+
try {
|
|
358
|
+
await updateTenantOption(baseUrl, category, key, value, authHeader);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (error.cause?.status === 404) {
|
|
361
|
+
await createTenantOption(baseUrl, category, key, value, authHeader);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
313
368
|
* Deletes a tenant option.
|
|
314
369
|
* @param baseUrl - The Cumulocity base URL
|
|
315
370
|
* @param category - The category of the option
|
|
@@ -329,4 +384,4 @@ async function deleteTenantOption(baseUrl, category, key, authHeader) {
|
|
|
329
384
|
}
|
|
330
385
|
}
|
|
331
386
|
//#endregion
|
|
332
|
-
export { findMicroserviceByName as a, getTenantOptionsByCategory as c, updateMicroservice as d,
|
|
387
|
+
export { GENERATED_READINESS_ROUTE as _, findMicroserviceByName as a, getTenantOptionsByCategory as c, updateMicroservice as d, upsertTenantOption as f, GENERATED_LIVENESS_ROUTE as g, GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE as h, deleteTenantOption as i, subscribeToApplication as l, createC8yManifestFromNitro as m, createBasicAuthHeader as n, getBootstrapCredentials as o, createC8yManifest as p, createMicroservice as r, getTenantOption as s, assignUserRole as t, unassignUserRole as u };
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as name, r as version, t as description } from "../package-
|
|
1
|
+
import { n as name, r as version, t as description } from "../package-bfMasPPg.mjs";
|
|
2
2
|
import { defineCommand, runMain } from "citty";
|
|
3
3
|
//#region src/cli/index.ts
|
|
4
4
|
runMain(defineCommand({
|
|
@@ -8,9 +8,9 @@ runMain(defineCommand({
|
|
|
8
8
|
description
|
|
9
9
|
},
|
|
10
10
|
subCommands: {
|
|
11
|
-
bootstrap: () => import("../bootstrap-
|
|
12
|
-
roles: () => import("../roles-
|
|
13
|
-
options: () => import("../options-
|
|
11
|
+
bootstrap: () => import("../bootstrap-BSdVwFWO.mjs").then((r) => r.default),
|
|
12
|
+
roles: () => import("../roles-CoBPqvDv.mjs").then((r) => r.default),
|
|
13
|
+
options: () => import("../options-BkWL1FOa.mjs").then((r) => r.default)
|
|
14
14
|
}
|
|
15
15
|
}));
|
|
16
16
|
//#endregion
|
|
@@ -2,6 +2,9 @@ import { dirname } from "pathe";
|
|
|
2
2
|
import { loadConfig, loadDotenv } from "c12";
|
|
3
3
|
import process from "process";
|
|
4
4
|
//#region src/cli/utils/config.ts
|
|
5
|
+
function normalizeBaseUrl(baseUrl) {
|
|
6
|
+
return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
7
|
+
}
|
|
5
8
|
/**
|
|
6
9
|
* Loads c8y configuration from nitro.config and .env files.
|
|
7
10
|
* Searches in cwd.
|
|
@@ -45,7 +48,7 @@ function validateBootstrapEnv(env) {
|
|
|
45
48
|
const missing = REQUIRED_BOOTSTRAP_ENV_VARS.filter((key) => !env[key]);
|
|
46
49
|
if (missing.length > 0) throw new Error(`Missing required environment variables:\n${missing.map((k) => ` - ${k}`).join("\n")}\n\nPlease set these in your .env or .env.local file.`);
|
|
47
50
|
return {
|
|
48
|
-
C8Y_BASEURL:
|
|
51
|
+
C8Y_BASEURL: normalizeBaseUrl(env.C8Y_BASEURL),
|
|
49
52
|
C8Y_DEVELOPMENT_TENANT: env.C8Y_DEVELOPMENT_TENANT,
|
|
50
53
|
C8Y_DEVELOPMENT_USER: env.C8Y_DEVELOPMENT_USER,
|
|
51
54
|
C8Y_DEVELOPMENT_PASSWORD: env.C8Y_DEVELOPMENT_PASSWORD
|
|
@@ -193,10 +193,10 @@ interface Option {
|
|
|
193
193
|
*/
|
|
194
194
|
key: string;
|
|
195
195
|
/**
|
|
196
|
-
* Initial value if not overridden.
|
|
196
|
+
* Initial non-empty value if not overridden.
|
|
197
197
|
* @example "1234"
|
|
198
198
|
*/
|
|
199
|
-
defaultValue
|
|
199
|
+
defaultValue: string;
|
|
200
200
|
/**
|
|
201
201
|
* Allow tenants to modify at runtime.
|
|
202
202
|
* @default false
|
|
@@ -389,11 +389,34 @@ type C8YTenantOptionKeysCacheConfig$1 = Partial<Record<C8YTenantOptionKey$1, num
|
|
|
389
389
|
type C8YTenantOptionKey$1 = string;
|
|
390
390
|
//#endregion
|
|
391
391
|
//#region src/types/index.d.ts
|
|
392
|
+
interface C8yDevOptions {
|
|
393
|
+
/**
|
|
394
|
+
* Automatically inject the configured development user into incoming requests
|
|
395
|
+
* during local Nitro dev mode.
|
|
396
|
+
*
|
|
397
|
+
* Disable this when a local proxy already forwards the desired user context.
|
|
398
|
+
* @default true
|
|
399
|
+
*/
|
|
400
|
+
injectUser?: boolean;
|
|
401
|
+
}
|
|
392
402
|
interface C8yNitroModuleOptions {
|
|
403
|
+
dev?: C8yDevOptions;
|
|
393
404
|
manifest?: C8YManifestOptions;
|
|
394
405
|
apiClient?: C8YAPIClientOptions;
|
|
395
406
|
zip?: C8YZipOptions;
|
|
396
407
|
cache?: C8yCacheOptions;
|
|
408
|
+
/**
|
|
409
|
+
* Adds a debug route for invalidating already-created tenant option caches.
|
|
410
|
+
* Exposes `GET /_c8y_nitro/invalidate-tenant-options`.
|
|
411
|
+
*
|
|
412
|
+
* Query params:
|
|
413
|
+
* - `all`: invalidate all created tenant option fetchers
|
|
414
|
+
* - `key`: invalidate a single manifest-defined tenant option key if its fetcher exists
|
|
415
|
+
*
|
|
416
|
+
* `all` takes priority over `key`.
|
|
417
|
+
* @default false
|
|
418
|
+
*/
|
|
419
|
+
enableTenantOptionsInvalidationRoute?: boolean;
|
|
397
420
|
/**
|
|
398
421
|
* Disable auto-bootstrap during development.
|
|
399
422
|
* When true, the module will not automatically register the microservice
|
|
@@ -405,4 +428,4 @@ interface C8yNitroModuleOptions {
|
|
|
405
428
|
skipBootstrap?: boolean;
|
|
406
429
|
}
|
|
407
430
|
//#endregion
|
|
408
|
-
export {
|
|
431
|
+
export { C8YRoles$1 as a, C8YManifest as c, C8YTenantOptionKeysCacheConfig$1 as i, C8YManifestOptions as l, C8yNitroModuleOptions as n, C8yCacheOptions as o, C8YTenantOptionKey$1 as r, C8YZipOptions as s, C8yDevOptions as t, C8YAPIClientOptions as u };
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as findMicroserviceByName, g as
|
|
1
|
+
import { _ as GENERATED_READINESS_ROUTE, a as findMicroserviceByName, g as GENERATED_LIVENESS_ROUTE, h as GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE, l as subscribeToApplication, m as createC8yManifestFromNitro, n as createBasicAuthHeader, o as getBootstrapCredentials, p as createC8yManifest, r as createMicroservice } from "./c8y-api-BgTNTqHd.mjs";
|
|
2
2
|
import { t as writeBootstrapCredentials } from "./env-file-B0BK-uZW.mjs";
|
|
3
|
-
import { n as name } from "./package-
|
|
3
|
+
import { n as name } from "./package-bfMasPPg.mjs";
|
|
4
4
|
import { basename, dirname, join, relative } from "node:path";
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import { x } from "tinyexec";
|
|
@@ -427,11 +427,21 @@ async function setupRuntimeConfig(nitro, options) {
|
|
|
427
427
|
*/
|
|
428
428
|
function registerRuntime(nitro, options = {}) {
|
|
429
429
|
const thisFilePath = fileURLToPath(new URL(".", import.meta.url));
|
|
430
|
+
const isNitroDev = nitro.options.preset === "nitro-dev";
|
|
431
|
+
const shouldIncludeRuntimeFile = (relativePath) => {
|
|
432
|
+
if (relativePath.endsWith(".dev.ts") && !isNitroDev) return false;
|
|
433
|
+
if (relativePath.endsWith("/dev-user.dev.ts") && options.dev?.injectUser === false) return false;
|
|
434
|
+
return true;
|
|
435
|
+
};
|
|
436
|
+
const toRuntimePath = (relativePath) => join$1(thisFilePath, relativePath.replace(/\.ts$/, ""));
|
|
430
437
|
const allPlugins = Object.keys({
|
|
431
|
-
"./runtime/plugins/c8y-variables.ts": 0,
|
|
438
|
+
"./runtime/plugins/c8y-variables.dev.ts": 0,
|
|
432
439
|
"./runtime/plugins/enrich-logs.ts": 0
|
|
433
|
-
}).
|
|
434
|
-
const allMiddlewares = Object.keys({
|
|
440
|
+
}).filter(shouldIncludeRuntimeFile).map(toRuntimePath);
|
|
441
|
+
const allMiddlewares = Object.keys({
|
|
442
|
+
"./runtime/middlewares/c8y-client.ts": 0,
|
|
443
|
+
"./runtime/middlewares/dev-user.dev.ts": 0
|
|
444
|
+
}).filter(shouldIncludeRuntimeFile).map(toRuntimePath);
|
|
435
445
|
/**
|
|
436
446
|
* Plugins (auto scanned)
|
|
437
447
|
*/
|
|
@@ -465,6 +475,15 @@ function registerRuntime(nitro, options = {}) {
|
|
|
465
475
|
});
|
|
466
476
|
nitro.logger.debug(`Generated readiness probe at ${GENERATED_READINESS_ROUTE}`);
|
|
467
477
|
} else nitro.logger.debug("Readiness probe httpGet defined by user; skipping generation");
|
|
478
|
+
if (options.enableTenantOptionsInvalidationRoute) {
|
|
479
|
+
const invalidateTenantOptionsHandlerPath = join$1(thisFilePath, "./runtime/handlers/invalidateTenantOptions");
|
|
480
|
+
handlers.push({
|
|
481
|
+
route: GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE,
|
|
482
|
+
handler: invalidateTenantOptionsHandlerPath,
|
|
483
|
+
method: "GET"
|
|
484
|
+
});
|
|
485
|
+
nitro.logger.debug(`Generated tenant option invalidation route at ${GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE}`);
|
|
486
|
+
}
|
|
468
487
|
nitro.options.handlers.push(...handlers);
|
|
469
488
|
}
|
|
470
489
|
//#endregion
|
|
@@ -558,12 +577,25 @@ function c8y() {
|
|
|
558
577
|
name,
|
|
559
578
|
"@c8y/client"
|
|
560
579
|
];
|
|
580
|
+
nitro.options.rolldownConfig = {
|
|
581
|
+
...nitro.options.rolldownConfig,
|
|
582
|
+
resolve: {
|
|
583
|
+
...nitro.options.rolldownConfig?.resolve,
|
|
584
|
+
alias: {
|
|
585
|
+
...nitro.options.rolldownConfig?.resolve?.alias,
|
|
586
|
+
tslib: "tslib/tslib.es6.mjs"
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
};
|
|
561
590
|
if (!nitro.options.preset.startsWith("nitro") && !nitro.options.preset.startsWith("node")) {
|
|
562
591
|
nitro.logger.error(`Unsupported preset "${nitro.options.preset}" for c8y-nitro module, only node presets are supported.`);
|
|
563
592
|
throw new Error("Unsupported preset for c8y-nitro module");
|
|
564
593
|
}
|
|
565
594
|
let manifest = await createC8yManifestFromNitro(nitro);
|
|
566
|
-
const { setup: setupEvlog } = evlog({
|
|
595
|
+
const { setup: setupEvlog } = evlog({
|
|
596
|
+
env: { service: manifest.name },
|
|
597
|
+
exclude: [manifest.livenessProbe?.httpGet?.path, manifest.readinessProbe?.httpGet?.path].filter(Boolean)
|
|
598
|
+
});
|
|
567
599
|
await setupEvlog(nitro);
|
|
568
600
|
if (!options.skipBootstrap) await autoBootstrap(nitro);
|
|
569
601
|
await setupRuntimeConfig(nitro, options);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as getTenantOptionsByCategory, f as
|
|
2
|
-
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-
|
|
1
|
+
import { c as getTenantOptionsByCategory, f as upsertTenantOption, i as deleteTenantOption, n as createBasicAuthHeader, p as createC8yManifest, s as getTenantOption } from "./c8y-api-BgTNTqHd.mjs";
|
|
2
|
+
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BRnvtthI.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
//#region src/cli/commands/options.ts
|
|
@@ -62,6 +62,7 @@ async function handleRead(baseUrl, category, authHeader, availableKeys, currentO
|
|
|
62
62
|
consola.warn("No options are currently set");
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
if (credentialsKeys.length > 0) consola.warn("Encrypted credentials.* options cannot be read in decrypted form with the development user in this CLI.");
|
|
65
66
|
const key = await consola.prompt("Select option to read:", {
|
|
66
67
|
type: "select",
|
|
67
68
|
options: allKeys.map((k) => ({
|
|
@@ -71,7 +72,7 @@ async function handleRead(baseUrl, category, authHeader, availableKeys, currentO
|
|
|
71
72
|
cancel: "reject"
|
|
72
73
|
});
|
|
73
74
|
consola.info(`Reading option: ${key}`);
|
|
74
|
-
const value = await getTenantOption(baseUrl, category, key
|
|
75
|
+
const value = await getTenantOption(baseUrl, category, key, authHeader);
|
|
75
76
|
if (value === void 0) consola.warn(`Option '${key}' is not set`);
|
|
76
77
|
else consola.success(`Value: ${value}`);
|
|
77
78
|
}
|
|
@@ -101,7 +102,7 @@ async function handleUpdate(baseUrl, category, authHeader, availableKeys, curren
|
|
|
101
102
|
cancel: "reject"
|
|
102
103
|
});
|
|
103
104
|
consola.info(`Updating option: ${key}`);
|
|
104
|
-
await
|
|
105
|
+
await upsertTenantOption(baseUrl, category, key, newValue, authHeader);
|
|
105
106
|
currentOptions[key] = newValue;
|
|
106
107
|
consola.success(`Option '${key}' updated successfully`);
|
|
107
108
|
if (!await consola.prompt("Update another option?", {
|
|
@@ -136,7 +137,8 @@ async function handleDelete(baseUrl, category, authHeader, availableKeys, curren
|
|
|
136
137
|
consola.info(`Deleting ${keysToDelete.length} option(s)...`);
|
|
137
138
|
for (const key of keysToDelete) {
|
|
138
139
|
consola.info(`Deleting option: ${key}`);
|
|
139
|
-
await deleteTenantOption(baseUrl, category, key
|
|
140
|
+
await deleteTenantOption(baseUrl, category, key, authHeader);
|
|
141
|
+
delete currentOptions[key];
|
|
140
142
|
consola.success(`✓ Deleted: ${key}`);
|
|
141
143
|
}
|
|
142
144
|
consola.success("Delete operation completed");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as createBasicAuthHeader, p as createC8yManifest, t as assignUserRole, u as unassignUserRole } from "./c8y-api-
|
|
2
|
-
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-
|
|
1
|
+
import { n as createBasicAuthHeader, p as createC8yManifest, t as assignUserRole, u as unassignUserRole } from "./c8y-api-BgTNTqHd.mjs";
|
|
2
|
+
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BRnvtthI.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
//#region src/cli/commands/roles.ts
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { t as createError } from "../../logging-CVlaRS4O.mjs";
|
|
2
|
+
import { c8yTenantOptionKeys } from "c8y-nitro/runtime";
|
|
3
|
+
import { defineEventHandler, getQuery } from "nitro/h3";
|
|
4
|
+
//#region src/utils/internal/tenantOptionFetchers.ts
|
|
5
|
+
/**
|
|
6
|
+
* Internal storage for cached functions per key
|
|
7
|
+
*/
|
|
8
|
+
const tenantOptionFetchers = {};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/module/runtime/handlers/invalidateTenantOptions.ts
|
|
11
|
+
var invalidateTenantOptions_default = defineEventHandler(async (event) => {
|
|
12
|
+
const query = getQuery(event);
|
|
13
|
+
if ("all" in query) {
|
|
14
|
+
await Promise.all(Object.values(tenantOptionFetchers).map((fetcher) => fetcher?.invalidate()));
|
|
15
|
+
return { message: "success" };
|
|
16
|
+
}
|
|
17
|
+
const key = Array.isArray(query.key) ? query.key[0] : query.key;
|
|
18
|
+
if (!key) throw createError({
|
|
19
|
+
status: 400,
|
|
20
|
+
message: "Provide either the all or key query parameter",
|
|
21
|
+
why: "The tenant option invalidation route requires an explicit target",
|
|
22
|
+
fix: "Use ?all to invalidate all created tenant option caches or ?key=<manifest setting key> to invalidate one created cache entry"
|
|
23
|
+
});
|
|
24
|
+
if (!c8yTenantOptionKeys.includes(key)) throw createError({
|
|
25
|
+
status: 400,
|
|
26
|
+
message: "Invalid tenant option invalidation request",
|
|
27
|
+
why: "Only tenant option keys declared in manifest.settings can be invalidated via this route",
|
|
28
|
+
fix: "Use ?all to invalidate all created tenant option caches or provide a valid manifest-defined key in ?key"
|
|
29
|
+
});
|
|
30
|
+
const fetcher = tenantOptionFetchers[key];
|
|
31
|
+
if (fetcher) await fetcher.invalidate();
|
|
32
|
+
return { message: "success" };
|
|
33
|
+
});
|
|
34
|
+
//#endregion
|
|
35
|
+
export { invalidateTenantOptions_default as default };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { t as createError } from "../../logging-CVlaRS4O.mjs";
|
|
2
|
+
import { defineMiddleware } from "nitro";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
//#region src/module/runtime/middlewares/c8y-client.ts
|
|
5
|
+
function isC8yClientThrownResponse(error) {
|
|
6
|
+
if (!error || typeof error !== "object") return false;
|
|
7
|
+
if (!("res" in error) || !error.res || typeof error.res !== "object") return false;
|
|
8
|
+
const response = error.res;
|
|
9
|
+
if (typeof response.status !== "number") return false;
|
|
10
|
+
const responseUrl = typeof response.url === "string" ? response.url : void 0;
|
|
11
|
+
const baseUrl = process.env.C8Y_BASEURL?.replace(/\/+$/, "");
|
|
12
|
+
const matchesConfiguredTenant = Boolean(baseUrl && responseUrl?.startsWith(baseUrl));
|
|
13
|
+
if ("data" in error && error.data && typeof error.data === "object") {
|
|
14
|
+
const data = error.data;
|
|
15
|
+
if (typeof data.info === "string" && data.info.startsWith("https://cumulocity.com/")) return true;
|
|
16
|
+
}
|
|
17
|
+
return matchesConfiguredTenant;
|
|
18
|
+
}
|
|
19
|
+
var c8y_client_default = defineMiddleware(async (_event, next) => {
|
|
20
|
+
try {
|
|
21
|
+
return await next();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (!isC8yClientThrownResponse(error)) throw error;
|
|
24
|
+
const upstreamMessage = error.data?.message || error.res.statusText || "Cumulocity request failed";
|
|
25
|
+
const upstreamCode = error.data?.error;
|
|
26
|
+
throw createError({
|
|
27
|
+
message: "Internal Server Error",
|
|
28
|
+
status: 500,
|
|
29
|
+
internal: {
|
|
30
|
+
help: "An error occurred while processing a request to Cumulocity from the @c8y/client library.",
|
|
31
|
+
upstream: "@c8y/client",
|
|
32
|
+
message: upstreamMessage,
|
|
33
|
+
code: upstreamCode,
|
|
34
|
+
status: error.res.status,
|
|
35
|
+
statusText: error.res.statusText,
|
|
36
|
+
url: error.res.url,
|
|
37
|
+
data: error.data
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
//#endregion
|
|
43
|
+
export { c8y_client_default as default };
|
|
@@ -2,8 +2,8 @@ import { defineHandler } from "nitro/h3";
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import consola from "consola";
|
|
5
|
-
//#region src/module/runtime/middlewares/dev-user.ts
|
|
6
|
-
var
|
|
5
|
+
//#region src/module/runtime/middlewares/dev-user.dev.ts
|
|
6
|
+
var dev_user_dev_default = defineHandler((event) => {
|
|
7
7
|
if (import.meta.dev) {
|
|
8
8
|
const missingDevVars = [
|
|
9
9
|
"C8Y_DEVELOPMENT_TENANT",
|
|
@@ -20,4 +20,4 @@ var dev_user_default = defineHandler((event) => {
|
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
//#endregion
|
|
23
|
-
export {
|
|
23
|
+
export { dev_user_dev_default as default };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { definePlugin } from "nitro";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
//#region src/module/runtime/plugins/c8y-variables.dev.ts
|
|
4
|
+
var c8y_variables_dev_default = definePlugin(() => {
|
|
5
5
|
if (import.meta.dev) {
|
|
6
6
|
const env = process.env;
|
|
7
7
|
const missingVars = [
|
|
@@ -14,4 +14,4 @@ var c8y_variables_default = definePlugin(() => {
|
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
16
|
//#endregion
|
|
17
|
-
export {
|
|
17
|
+
export { c8y_variables_dev_default as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { definePlugin } from "nitro";
|
|
2
1
|
import { c8yManifest } from "c8y-nitro/runtime";
|
|
2
|
+
import { definePlugin } from "nitro";
|
|
3
3
|
//#region src/module/runtime/plugins/enrich-logs.ts
|
|
4
4
|
var enrich_logs_default = definePlugin(async (nitroApp) => {
|
|
5
5
|
nitroApp.hooks.hook("evlog:enrich", (enrichContext) => {
|
package/dist/types.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { C8YAPIClientOptions, C8YManifest, C8YManifestOptions, C8YRoles, C8YTenantOptionKey, C8YTenantOptionKeysCacheConfig, C8YZipOptions, C8yCacheOptions, C8yNitroModuleOptions };
|
|
1
|
+
import { a as C8YRoles, c as C8YManifest, i as C8YTenantOptionKeysCacheConfig, l as C8YManifestOptions, n as C8yNitroModuleOptions, o as C8yCacheOptions, r as C8YTenantOptionKey, s as C8YZipOptions, t as C8yDevOptions, u as C8YAPIClientOptions } from "./index-Bvu7DqDt.mjs";
|
|
2
|
+
export { C8YAPIClientOptions, C8YManifest, C8YManifestOptions, C8YRoles, C8YTenantOptionKey, C8YTenantOptionKeysCacheConfig, C8YZipOptions, C8yCacheOptions, C8yDevOptions, C8yNitroModuleOptions };
|
package/dist/utils.d.mts
CHANGED
|
@@ -242,11 +242,6 @@ declare function useUserTenantCredentials(requestOrEvent: ServerRequest | H3Even
|
|
|
242
242
|
* - `c8y.cache.tenantOptions` — Per-key TTL overrides
|
|
243
243
|
* - `NITRO_C8Y_DEFAULT_TENANT_OPTIONS_TTL` — Environment variable for default TTL
|
|
244
244
|
*
|
|
245
|
-
* @note For encrypted options (keys starting with `credentials.`), the value is automatically
|
|
246
|
-
* decrypted by Cumulocity if this microservice is the owner of the option (category matches
|
|
247
|
-
* the microservice's settingsCategory/contextPath/name). The `credentials.` prefix is
|
|
248
|
-
* automatically stripped when calling the API.
|
|
249
|
-
*
|
|
250
245
|
* @example
|
|
251
246
|
* // Fetch a tenant option:
|
|
252
247
|
* const value = await useTenantOption('myOption')
|
package/dist/utils.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
1
2
|
import process from "node:process";
|
|
2
3
|
import { useLogger } from "evlog/nitro/v3";
|
|
3
4
|
import { BasicAuth, Client, MicroserviceClientRequestAuth } from "@c8y/client";
|
|
4
5
|
import { defineCachedFunction } from "nitro/cache";
|
|
6
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
7
|
import { HTTPError, defineHandler } from "nitro/h3";
|
|
6
8
|
import { useStorage } from "nitro/storage";
|
|
7
9
|
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
10
|
+
import { c8yManifest } from "c8y-nitro/runtime";
|
|
8
11
|
import { createError, createLogger } from "evlog";
|
|
9
12
|
//#region src/utils/internal/common.ts
|
|
10
13
|
/**
|
|
@@ -30,6 +33,55 @@ function convertRequestHeadersToC8yFormat(request) {
|
|
|
30
33
|
return headers;
|
|
31
34
|
}
|
|
32
35
|
//#endregion
|
|
36
|
+
//#region src/utils/internal/tenant.ts
|
|
37
|
+
const USER_TENANT_CACHE_SALT = randomBytes(32).toString("hex");
|
|
38
|
+
function getCookieValue(cookieHeader, name) {
|
|
39
|
+
try {
|
|
40
|
+
const value = cookieHeader?.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`);
|
|
41
|
+
return value ? value.pop() : void 0;
|
|
42
|
+
} catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getCurrentUserTenantCacheKeyMaterial(requestOrEvent) {
|
|
47
|
+
const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
|
|
48
|
+
const cookieAuth = getCookieValue(request.headers.get("cookie"), "authorization");
|
|
49
|
+
if (cookieAuth) return `cookie:${cookieAuth}`;
|
|
50
|
+
const authorization = request.headers.get("authorization");
|
|
51
|
+
if (authorization) return `header:${authorization}`;
|
|
52
|
+
}
|
|
53
|
+
function createCurrentUserTenantCacheKey(requestOrEvent) {
|
|
54
|
+
const material = getCurrentUserTenantCacheKeyMaterial(requestOrEvent);
|
|
55
|
+
if (!material) throw new Error("Cannot create current user tenant cache key without auth material");
|
|
56
|
+
return createHash("sha256").update(USER_TENANT_CACHE_SALT).update(":").update(material).digest("hex");
|
|
57
|
+
}
|
|
58
|
+
function tryGetTenantFromBasicAuth(requestOrEvent) {
|
|
59
|
+
const authorization = ("req" in requestOrEvent ? requestOrEvent.req : requestOrEvent).headers.get("authorization");
|
|
60
|
+
if (!authorization?.startsWith("Basic ")) return;
|
|
61
|
+
try {
|
|
62
|
+
const decoded = Buffer.from(authorization.slice(6), "base64").toString("utf8");
|
|
63
|
+
const separatorIndex = decoded.indexOf(":");
|
|
64
|
+
const userPart = separatorIndex === -1 ? decoded : decoded.slice(0, separatorIndex);
|
|
65
|
+
const slashIndex = userPart.indexOf("/");
|
|
66
|
+
if (slashIndex <= 0) return;
|
|
67
|
+
return userPart.slice(0, slashIndex);
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const getCurrentUserTenantId = defineCachedFunction(async (requestOrEvent) => {
|
|
73
|
+
const basicTenant = tryGetTenantFromBasicAuth(requestOrEvent);
|
|
74
|
+
if (basicTenant) return basicTenant;
|
|
75
|
+
return (await useUserClient(requestOrEvent).tenant.current()).data.name;
|
|
76
|
+
}, {
|
|
77
|
+
maxAge: 60,
|
|
78
|
+
name: "_c8y_nitro_get_current_user_tenant_id",
|
|
79
|
+
group: "c8y_nitro",
|
|
80
|
+
swr: false,
|
|
81
|
+
getKey: (requestOrEvent) => createCurrentUserTenantCacheKey(requestOrEvent),
|
|
82
|
+
shouldBypassCache: (requestOrEvent) => !getCurrentUserTenantCacheKeyMaterial(requestOrEvent)
|
|
83
|
+
});
|
|
84
|
+
//#endregion
|
|
33
85
|
//#region src/utils/credentials.ts
|
|
34
86
|
/**
|
|
35
87
|
* Fetches credentials for all tenants subscribed to this microservice.\
|
|
@@ -128,7 +180,7 @@ const useDeployedTenantCredentials = Object.assign(async () => {
|
|
|
128
180
|
async function useUserTenantCredentials(requestOrEvent) {
|
|
129
181
|
const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
|
|
130
182
|
if (request.context?.["c8y_user_tenant_credentials"]) return request.context["c8y_user_tenant_credentials"];
|
|
131
|
-
const tenantId =
|
|
183
|
+
const tenantId = await getCurrentUserTenantId(requestOrEvent);
|
|
132
184
|
const userTenantCreds = (await useSubscribedTenantCredentials())[tenantId];
|
|
133
185
|
if (!userTenantCreds) throw new HTTPError({
|
|
134
186
|
message: `No subscribed tenant credentials found for user tenant '${tenantId}'`,
|
|
@@ -172,7 +224,7 @@ function useUserClient(requestOrEvent) {
|
|
|
172
224
|
async function useUserTenantClient(requestOrEvent) {
|
|
173
225
|
const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
|
|
174
226
|
if (request.context?.["c8y_user_tenant_client"]) return request.context["c8y_user_tenant_client"];
|
|
175
|
-
const tenantId =
|
|
227
|
+
const tenantId = await getCurrentUserTenantId(requestOrEvent);
|
|
176
228
|
const creds = await useSubscribedTenantCredentials();
|
|
177
229
|
if (!creds[tenantId]) throw new HTTPError({
|
|
178
230
|
message: `No subscribed tenant credentials found for user tenant '${tenantId}'`,
|
|
@@ -267,8 +319,13 @@ async function useUserRoles(requestOrEvent) {
|
|
|
267
319
|
}
|
|
268
320
|
//#endregion
|
|
269
321
|
//#region src/utils/middleware.ts
|
|
322
|
+
const probePaths = [c8yManifest.livenessProbe?.httpGet?.path, c8yManifest.readinessProbe?.httpGet?.path].filter((path) => Boolean(path));
|
|
323
|
+
function isProbeRequest(pathname) {
|
|
324
|
+
return probePaths.some((probePath) => pathname.startsWith(probePath));
|
|
325
|
+
}
|
|
270
326
|
function hasUserRequiredRole(roleOrRoles) {
|
|
271
327
|
return defineHandler(async (event) => {
|
|
328
|
+
if (isProbeRequest(event.url.pathname)) return;
|
|
272
329
|
const requiredRoles = Array.isArray(roleOrRoles) ? roleOrRoles : [roleOrRoles];
|
|
273
330
|
const userRoles = await useUserRoles(event);
|
|
274
331
|
if (!requiredRoles.some((role) => userRoles.includes(role))) throw new HTTPError({
|
|
@@ -280,8 +337,9 @@ function hasUserRequiredRole(roleOrRoles) {
|
|
|
280
337
|
}
|
|
281
338
|
function isUserFromAllowedTenant(tenantIdOrIds) {
|
|
282
339
|
return defineHandler(async (event) => {
|
|
340
|
+
if (isProbeRequest(event.url.pathname)) return;
|
|
283
341
|
const allowedTenants = Array.isArray(tenantIdOrIds) ? tenantIdOrIds : [tenantIdOrIds];
|
|
284
|
-
const userTenantId =
|
|
342
|
+
const userTenantId = await getCurrentUserTenantId(event);
|
|
285
343
|
if (!allowedTenants.includes(userTenantId)) throw new HTTPError({
|
|
286
344
|
status: 403,
|
|
287
345
|
statusText: "Forbidden",
|
|
@@ -306,7 +364,8 @@ function isUserFromAllowedTenant(tenantIdOrIds) {
|
|
|
306
364
|
*/
|
|
307
365
|
function isUserFromDeployedTenant() {
|
|
308
366
|
return defineHandler(async (event) => {
|
|
309
|
-
|
|
367
|
+
if (isProbeRequest(event.url.pathname)) return;
|
|
368
|
+
const userTenantId = await getCurrentUserTenantId(event);
|
|
310
369
|
const deployedTenantId = process.env.C8Y_BOOTSTRAP_TENANT;
|
|
311
370
|
if (!deployedTenantId) throw new HTTPError({
|
|
312
371
|
status: 500,
|
|
@@ -321,6 +380,12 @@ function isUserFromDeployedTenant() {
|
|
|
321
380
|
});
|
|
322
381
|
}
|
|
323
382
|
//#endregion
|
|
383
|
+
//#region src/utils/internal/tenantOptionFetchers.ts
|
|
384
|
+
/**
|
|
385
|
+
* Internal storage for cached functions per key
|
|
386
|
+
*/
|
|
387
|
+
const tenantOptionFetchers = {};
|
|
388
|
+
//#endregion
|
|
324
389
|
//#region src/utils/tenantOptions.ts
|
|
325
390
|
/**
|
|
326
391
|
* Gets the cache TTL for a specific tenant option key.
|
|
@@ -332,10 +397,6 @@ function getTenantOptionCacheTTL(key) {
|
|
|
332
397
|
return config.c8yTenantOptionsPerKeyTTL?.[key] ?? config.c8yDefaultTenantOptionsTTL ?? 600;
|
|
333
398
|
}
|
|
334
399
|
/**
|
|
335
|
-
* Internal storage for cached functions per key
|
|
336
|
-
*/
|
|
337
|
-
const tenantOptionFetchers = {};
|
|
338
|
-
/**
|
|
339
400
|
* Factory function that creates a cached fetcher for a specific tenant option key.
|
|
340
401
|
* @param key - The tenant option key
|
|
341
402
|
*/
|
|
@@ -344,10 +405,9 @@ function createCachedTenantOptionFetcher(key) {
|
|
|
344
405
|
const fetcher = defineCachedFunction(async () => {
|
|
345
406
|
const client = await useDeployedTenantClient();
|
|
346
407
|
const category = useRuntimeConfig().c8ySettingsCategory;
|
|
347
|
-
const apiKey = key.replace(/^credentials\./, "");
|
|
348
408
|
try {
|
|
349
409
|
return (await client.options.tenant.detail({
|
|
350
|
-
key
|
|
410
|
+
key,
|
|
351
411
|
category
|
|
352
412
|
})).data.value;
|
|
353
413
|
} catch (error) {
|
|
@@ -397,11 +457,6 @@ function getOrCreateFetcher(key) {
|
|
|
397
457
|
* - `c8y.cache.tenantOptions` — Per-key TTL overrides
|
|
398
458
|
* - `NITRO_C8Y_DEFAULT_TENANT_OPTIONS_TTL` — Environment variable for default TTL
|
|
399
459
|
*
|
|
400
|
-
* @note For encrypted options (keys starting with `credentials.`), the value is automatically
|
|
401
|
-
* decrypted by Cumulocity if this microservice is the owner of the option (category matches
|
|
402
|
-
* the microservice's settingsCategory/contextPath/name). The `credentials.` prefix is
|
|
403
|
-
* automatically stripped when calling the API.
|
|
404
|
-
*
|
|
405
460
|
* @example
|
|
406
461
|
* // Fetch a tenant option:
|
|
407
462
|
* const value = await useTenantOption('myOption')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8y-nitro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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
|
},
|
|
@@ -49,25 +39,26 @@
|
|
|
49
39
|
],
|
|
50
40
|
"dependencies": {
|
|
51
41
|
"c12": "^4.0.0-beta.4",
|
|
52
|
-
"citty": "^0.2.
|
|
42
|
+
"citty": "^0.2.2",
|
|
53
43
|
"consola": "^3.4.2",
|
|
54
|
-
"evlog": "^2.
|
|
44
|
+
"evlog": "^2.11.1",
|
|
55
45
|
"jszip": "^3.10.1",
|
|
56
46
|
"pathe": "^2.0.3",
|
|
57
47
|
"pkg-types": "^2.3.0",
|
|
58
48
|
"spinnies": "^0.5.1",
|
|
59
|
-
"tinyexec": "^1.
|
|
49
|
+
"tinyexec": "^1.1.1",
|
|
50
|
+
"tsnapi": "^0.1.1"
|
|
60
51
|
},
|
|
61
52
|
"devDependencies": {
|
|
62
53
|
"@schplitt/eslint-config": "^1.3.1",
|
|
63
54
|
"@types/spinnies": "^0.5.3",
|
|
64
55
|
"bumpp": "^11.0.1",
|
|
65
56
|
"changelogithub": "^14.0.0",
|
|
66
|
-
"eslint": "^10.
|
|
57
|
+
"eslint": "^10.2.0",
|
|
67
58
|
"memfs": "^4.57.1",
|
|
68
|
-
"tsdown": "^0.21.
|
|
59
|
+
"tsdown": "^0.21.7",
|
|
69
60
|
"typescript": "^5.9.3",
|
|
70
|
-
"vitest": "^4.1.
|
|
61
|
+
"vitest": "^4.1.4"
|
|
71
62
|
},
|
|
72
63
|
"peerDependencies": {
|
|
73
64
|
"@c8y/client": ">=1021",
|
|
@@ -79,6 +70,7 @@
|
|
|
79
70
|
"scripts": {
|
|
80
71
|
"dev": "tsdown --watch",
|
|
81
72
|
"build": "tsdown",
|
|
73
|
+
"build:update": "tsdown --update-snapshot",
|
|
82
74
|
"lint": "eslint",
|
|
83
75
|
"lint:fix": "eslint --fix",
|
|
84
76
|
"typecheck": "tsc --noEmit",
|