@camunda/ccma-api 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +8 -2
- package/dist/index.js +10 -0
- package/dist/index.js.map +2 -2
- package/dist/services/accounts/hdp.service.d.ts +7 -1
- package/dist/services/accounts/hdp.service.js +38 -0
- package/dist/services/accounts/hdp.service.js.map +2 -2
- package/dist/services/accounts/orgs.service.d.ts +28 -0
- package/dist/services/accounts/orgs.service.js +41 -1
- package/dist/services/accounts/orgs.service.js.map +2 -2
- package/dist/services/accounts/orgs.service.spec.d.ts +1 -0
- package/dist/services/accounts/queued-operations.service.d.ts +59 -0
- package/dist/services/accounts/queued-operations.service.js +49 -0
- package/dist/services/accounts/queued-operations.service.js.map +7 -0
- package/dist/services/accounts/trial-management.service.d.ts +27 -0
- package/dist/services/accounts/trial-management.service.js +101 -0
- package/dist/services/accounts/trial-management.service.js.map +7 -0
- package/dist/services/audit-logger/audit-logger.service.js +10 -1
- package/dist/services/audit-logger/audit-logger.service.js.map +2 -2
- package/dist/services/audit-logger/audit-logger.service.spec.d.ts +1 -0
- package/dist/services/auth0/auth0.service.d.ts +7 -0
- package/dist/services/auth0/auth0.service.js +2 -1
- package/dist/services/auth0/auth0.service.js.map +2 -2
- package/dist/services/auth0/auth0.service.spec.d.ts +1 -0
- package/dist/services/console/console.admin.service.js +13 -2
- package/dist/services/console/console.admin.service.js.map +2 -2
- package/dist/services/console/console.admin.service.spec.d.ts +1 -0
- package/dist/services/console/console.cluster.service.d.ts +9 -0
- package/dist/services/console/console.cluster.service.js +7 -0
- package/dist/services/console/console.cluster.service.js.map +2 -2
- package/dist/services/console/generation.service.d.ts +21 -1
- package/dist/services/console/generation.service.js +9 -0
- package/dist/services/console/generation.service.js.map +2 -2
- package/dist/services/github/github.service.d.ts +1 -0
- package/dist/services/github/github.service.js +32 -22
- package/dist/services/github/github.service.js.map +2 -2
- package/dist/services/github/github.service.spec.d.ts +1 -0
- package/dist/services/thanos/thanos.service.d.ts +49 -0
- package/dist/services/thanos/thanos.service.js +60 -0
- package/dist/services/thanos/thanos.service.js.map +7 -0
- package/dist/services/thanos/thanos.service.spec.d.ts +1 -0
- package/dist/services/token-issuer/token-issuer.jwks.service.js +5 -1
- package/dist/services/token-issuer/token-issuer.jwks.service.js.map +2 -2
- package/dist/services/token-issuer/token-issuer.jwks.service.spec.d.ts +1 -0
- package/dist/utils/api.js +8 -0
- package/dist/utils/api.js.map +2 -2
- package/dist/utils/cache.d.ts +24 -0
- package/dist/utils/cache.js +41 -0
- package/dist/utils/cache.js.map +2 -2
- package/dist/utils/cache.spec.d.ts +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { RequestBaseParams } from '../../types/general.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thanos' Prometheus-compatible HTTP query API.
|
|
4
|
+
*
|
|
5
|
+
* Console-backend used to call this with a bare `fetch()`, which is why its
|
|
6
|
+
* failure line had to hand-roll status extraction and body truncation — and got
|
|
7
|
+
* both wrong (it logged the `Response` object, which serialises to `{}`).
|
|
8
|
+
* Going through `request()` means the status, the capped error body, the
|
|
9
|
+
* `ccma` request metrics and the `error.*` log shape all come from the same
|
|
10
|
+
* place as every other outbound call.
|
|
11
|
+
*/
|
|
12
|
+
/** Endpoints of the query API we call. */
|
|
13
|
+
export declare enum ThanosApiEndpoint {
|
|
14
|
+
QUERY_RANGE = "query_range"
|
|
15
|
+
}
|
|
16
|
+
export interface ThanosQueryResult {
|
|
17
|
+
metric: any;
|
|
18
|
+
values?: any[];
|
|
19
|
+
value?: any[];
|
|
20
|
+
}
|
|
21
|
+
export interface ThanosQueryResponse {
|
|
22
|
+
status: string;
|
|
23
|
+
data?: {
|
|
24
|
+
resultType?: string;
|
|
25
|
+
result?: ThanosQueryResult[];
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface ThanosQueryRequest extends RequestBaseParams {
|
|
29
|
+
options: {
|
|
30
|
+
/** Host of the per-k8s-context Thanos query endpoint, without a scheme. */
|
|
31
|
+
thanosEndpoint: string;
|
|
32
|
+
endpoint: ThanosApiEndpoint | string;
|
|
33
|
+
/** Basic-auth credentials for the endpoint. */
|
|
34
|
+
username: string;
|
|
35
|
+
password: string;
|
|
36
|
+
queryParameter?: URLSearchParams;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare class ThanosService {
|
|
40
|
+
/**
|
|
41
|
+
* Run one query against a single Thanos endpoint.
|
|
42
|
+
*
|
|
43
|
+
* Throws `HttpError` on a non-2xx, per this package's convention. The status
|
|
44
|
+
* and the truncated response body ride along on it (`status`, `message`), so
|
|
45
|
+
* a caller that logs the caught error records what actually went wrong
|
|
46
|
+
* without reading the body itself.
|
|
47
|
+
*/
|
|
48
|
+
static query(params: ThanosQueryRequest): Promise<ThanosQueryResponse>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { request } from "../../utils/api.js";
|
|
2
|
+
import { HttpError } from "../../utils/errors.js";
|
|
3
|
+
var ThanosApiEndpoint = /* @__PURE__ */ ((ThanosApiEndpoint2) => {
|
|
4
|
+
ThanosApiEndpoint2["QUERY_RANGE"] = "query_range";
|
|
5
|
+
return ThanosApiEndpoint2;
|
|
6
|
+
})(ThanosApiEndpoint || {});
|
|
7
|
+
class ThanosService {
|
|
8
|
+
/**
|
|
9
|
+
* Run one query against a single Thanos endpoint.
|
|
10
|
+
*
|
|
11
|
+
* Throws `HttpError` on a non-2xx, per this package's convention. The status
|
|
12
|
+
* and the truncated response body ride along on it (`status`, `message`), so
|
|
13
|
+
* a caller that logs the caught error records what actually went wrong
|
|
14
|
+
* without reading the body itself.
|
|
15
|
+
*/
|
|
16
|
+
static async query(params) {
|
|
17
|
+
const queryParams = params.options.queryParameter ? `?${params.options.queryParameter.toString()}` : "";
|
|
18
|
+
const serviceContext = {
|
|
19
|
+
method: {
|
|
20
|
+
name: `ThanosService.query`,
|
|
21
|
+
url: `https://${params.options.thanosEndpoint}/api/v1/${params.options.endpoint}${queryParams}`,
|
|
22
|
+
// The endpoint host is per-k8s-context, so it stays out of the
|
|
23
|
+
// normalized URL — otherwise every region is its own metric series.
|
|
24
|
+
normalizedUrl: `https://:thanosEndpoint/api/v1/${params.options.endpoint}`,
|
|
25
|
+
type: "get"
|
|
26
|
+
},
|
|
27
|
+
source: params.config.metrics?.source || "unset",
|
|
28
|
+
target: {
|
|
29
|
+
name: "thanos",
|
|
30
|
+
type: "external"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const basicAuth = Buffer.from(
|
|
34
|
+
`${params.options.username}:${params.options.password}`
|
|
35
|
+
).toString("base64");
|
|
36
|
+
const response = await request({
|
|
37
|
+
config: params.config,
|
|
38
|
+
url: serviceContext.method.url,
|
|
39
|
+
method: serviceContext.method.type,
|
|
40
|
+
metricsDetails: serviceContext,
|
|
41
|
+
type: "json",
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Basic ${basicAuth}`
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new HttpError(
|
|
48
|
+
`${serviceContext.method.name}: ${response.status} ${response.statusText}`,
|
|
49
|
+
response.status,
|
|
50
|
+
response.statusText
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return response.body;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
ThanosApiEndpoint,
|
|
58
|
+
ThanosService
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=thanos.service.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/services/thanos/thanos.service.ts"],
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport type {\n RequestBaseParams,\n RequestBaseServiceContext,\n} from '../../types/general.types.js'\nimport { request } from '../../utils/api.js'\nimport { HttpError } from '../../utils/errors.js'\n\n/**\n * Thanos' Prometheus-compatible HTTP query API.\n *\n * Console-backend used to call this with a bare `fetch()`, which is why its\n * failure line had to hand-roll status extraction and body truncation \u2014 and got\n * both wrong (it logged the `Response` object, which serialises to `{}`).\n * Going through `request()` means the status, the capped error body, the\n * `ccma` request metrics and the `error.*` log shape all come from the same\n * place as every other outbound call.\n */\n\n/** Endpoints of the query API we call. */\nexport enum ThanosApiEndpoint {\n QUERY_RANGE = 'query_range',\n}\n\nexport interface ThanosQueryResult {\n metric: any\n values?: any[]\n value?: any[]\n}\n\nexport interface ThanosQueryResponse {\n status: string\n data?: {\n resultType?: string\n result?: ThanosQueryResult[]\n }\n}\n\nexport interface ThanosQueryRequest extends RequestBaseParams {\n options: {\n /** Host of the per-k8s-context Thanos query endpoint, without a scheme. */\n thanosEndpoint: string\n endpoint: ThanosApiEndpoint | string\n /** Basic-auth credentials for the endpoint. */\n username: string\n password: string\n queryParameter?: URLSearchParams\n }\n}\n\nexport class ThanosService {\n /**\n * Run one query against a single Thanos endpoint.\n *\n * Throws `HttpError` on a non-2xx, per this package's convention. The status\n * and the truncated response body ride along on it (`status`, `message`), so\n * a caller that logs the caught error records what actually went wrong\n * without reading the body itself.\n */\n public static async query(\n params: ThanosQueryRequest,\n ): Promise<ThanosQueryResponse> {\n const queryParams = params.options.queryParameter\n ? `?${params.options.queryParameter.toString()}`\n : ''\n const serviceContext: RequestBaseServiceContext = {\n method: {\n name: `ThanosService.query`,\n url: `https://${params.options.thanosEndpoint}/api/v1/${params.options.endpoint}${queryParams}`,\n // The endpoint host is per-k8s-context, so it stays out of the\n // normalized URL \u2014 otherwise every region is its own metric series.\n normalizedUrl: `https://:thanosEndpoint/api/v1/${params.options.endpoint}`,\n type: 'get',\n },\n source: params.config.metrics?.source || 'unset',\n target: {\n name: 'thanos',\n type: 'external',\n },\n }\n\n const basicAuth = Buffer.from(\n `${params.options.username}:${params.options.password}`,\n ).toString('base64')\n\n const response = await request<ThanosQueryResponse>({\n config: params.config,\n url: serviceContext.method.url,\n method: serviceContext.method.type,\n metricsDetails: serviceContext,\n type: 'json',\n headers: {\n Authorization: `Basic ${basicAuth}`,\n },\n })\n\n if (!response.ok) {\n throw new HttpError(\n `${serviceContext.method.name}: ${response.status} ${response.statusText}`,\n response.status,\n response.statusText,\n )\n }\n\n return response.body!\n }\n}\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAcnB,IAAK,oBAAL,kBAAKA,uBAAL;AACL,EAAAA,mBAAA,iBAAc;AADJ,SAAAA;AAAA,GAAA;AA8BL,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASzB,aAAoB,MAClB,QAC8B;AAC9B,UAAM,cAAc,OAAO,QAAQ,iBAC/B,IAAI,OAAO,QAAQ,eAAe,SAAS,CAAC,KAC5C;AACJ,UAAM,iBAA4C;AAAA,MAChD,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,KAAK,WAAW,OAAO,QAAQ,cAAc,WAAW,OAAO,QAAQ,QAAQ,GAAG,WAAW;AAAA;AAAA;AAAA,QAG7F,eAAe,kCAAkC,OAAO,QAAQ,QAAQ;AAAA,QACxE,MAAM;AAAA,MACR;AAAA,MACA,QAAQ,OAAO,OAAO,SAAS,UAAU;AAAA,MACzC,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAEA,UAAM,YAAY,OAAO;AAAA,MACvB,GAAG,OAAO,QAAQ,QAAQ,IAAI,OAAO,QAAQ,QAAQ;AAAA,IACvD,EAAE,SAAS,QAAQ;AAEnB,UAAM,WAAW,MAAM,QAA6B;AAAA,MAClD,QAAQ,OAAO;AAAA,MACf,KAAK,eAAe,OAAO;AAAA,MAC3B,QAAQ,eAAe,OAAO;AAAA,MAC9B,gBAAgB;AAAA,MAChB,MAAM;AAAA,MACN,SAAS;AAAA,QACP,eAAe,SAAS,SAAS;AAAA,MACnC;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,GAAG,eAAe,OAAO,IAAI,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,QACxE,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,IACF;AAEA,WAAO,SAAS;AAAA,EAClB;AACF;",
|
|
6
|
+
"names": ["ThanosApiEndpoint"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -23,7 +23,11 @@ class TokenIssuerJwksService {
|
|
|
23
23
|
config: params.config,
|
|
24
24
|
url: serviceContext.method.url,
|
|
25
25
|
method: serviceContext.method.type,
|
|
26
|
-
metricsDetails: serviceContext
|
|
26
|
+
metricsDetails: serviceContext,
|
|
27
|
+
// A read-only GET against an internal service, so retrying is free of
|
|
28
|
+
// side effects — and this feeds AuthChecker, so a brief token-issuer blip
|
|
29
|
+
// otherwise turns straight into failed auth for real requests.
|
|
30
|
+
retryTransientErrors: true
|
|
27
31
|
};
|
|
28
32
|
const response = await request(requestConfig);
|
|
29
33
|
if (!response.ok) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/services/token-issuer/token-issuer.jwks.service.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport { MINUTE } from '@camunda/ccma-shared-types'\nimport type {\n RequestBaseParams,\n RequestBaseServiceContext,\n} from '../../types/general.types.js'\nimport { log, request } from '../../utils/api.js'\nimport { Cache } from '../../utils/cache.js'\nimport { HttpError } from '../../utils/errors.js'\n\nconst CACHE_KEY = 'jwks'\nconst keysCache = new Cache<string, JwksResponse>(60 * MINUTE)\n\nexport interface JwksRequest extends RequestBaseParams {\n params: {\n uri: string\n }\n}\n\nexport interface JwksResponse {\n keys: any\n}\n\nexport class TokenIssuerJwksService {\n public static async get(params: JwksRequest): Promise<JwksResponse> {\n const serviceContext: RequestBaseServiceContext = {\n method: {\n name: `TokenIssuerJwksService.get`,\n url: params.params.uri,\n normalizedUrl: params.params.uri,\n type: 'get',\n },\n source: params.config.metrics?.source || 'unset',\n target: {\n name: 'token-issuer',\n type: 'internal',\n },\n }\n const requestConfig = {\n config: params.config,\n url: serviceContext.method.url,\n method: serviceContext.method.type,\n metricsDetails: serviceContext,\n }\n const response = await request<JwksResponse>(requestConfig)\n if (!response.ok) {\n const fromCache = keysCache.get(CACHE_KEY)\n if (fromCache && TokenIssuerJwksService.isValidKeysObject(fromCache)) {\n return fromCache\n }\n throw new HttpError(\n `${serviceContext.method.name}: ${response.status} ${response.statusText}`,\n response.status,\n response.statusText,\n )\n }\n\n if (TokenIssuerJwksService.isValidKeysObject(response.body)) {\n // valid response\n log(requestConfig, 'debug', 'jwks response valid, adding cache entry', {\n response,\n })\n keysCache.set(CACHE_KEY, response.body!)\n if (requestConfig.config.stage === 'dev') {\n const fromCache = keysCache.get(CACHE_KEY)\n log(requestConfig, 'debug', 'jwks value in cache', {\n response,\n fromCache,\n })\n }\n } else {\n log(requestConfig, 'debug', 'jwks response invalid', { response })\n // invalid response\n const fromCache = keysCache.get(CACHE_KEY)\n log(requestConfig, 'debug', 'checking cached entry', {\n response,\n fromCache,\n })\n if (fromCache && TokenIssuerJwksService.isValidKeysObject(fromCache)) {\n log(requestConfig, 'debug', 'using jwks from local cache', {\n response,\n fromCache,\n })\n return fromCache\n }\n }\n return response.body!\n }\n\n private static isValidKeysObject(response: any): boolean {\n return response?.keys && response?.keys.length > 0\n }\n}\n"],
|
|
5
|
-
"mappings": "AAMA,SAAS,cAAc;AAKvB,SAAS,KAAK,eAAe;AAC7B,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAE1B,MAAM,YAAY;AAClB,MAAM,YAAY,IAAI,MAA4B,KAAK,MAAM;AAYtD,MAAM,uBAAuB;AAAA,EAClC,aAAoB,IAAI,QAA4C;AAClE,UAAM,iBAA4C;AAAA,MAChD,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,KAAK,OAAO,OAAO;AAAA,QACnB,eAAe,OAAO,OAAO;AAAA,QAC7B,MAAM;AAAA,MACR;AAAA,MACA,QAAQ,OAAO,OAAO,SAAS,UAAU;AAAA,MACzC,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AACA,UAAM,gBAAgB;AAAA,MACpB,QAAQ,OAAO;AAAA,MACf,KAAK,eAAe,OAAO;AAAA,MAC3B,QAAQ,eAAe,OAAO;AAAA,MAC9B,gBAAgB;AAAA,
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport { MINUTE } from '@camunda/ccma-shared-types'\nimport type {\n RequestBaseParams,\n RequestBaseServiceContext,\n} from '../../types/general.types.js'\nimport { log, request } from '../../utils/api.js'\nimport { Cache } from '../../utils/cache.js'\nimport { HttpError } from '../../utils/errors.js'\n\nconst CACHE_KEY = 'jwks'\nconst keysCache = new Cache<string, JwksResponse>(60 * MINUTE)\n\nexport interface JwksRequest extends RequestBaseParams {\n params: {\n uri: string\n }\n}\n\nexport interface JwksResponse {\n keys: any\n}\n\nexport class TokenIssuerJwksService {\n public static async get(params: JwksRequest): Promise<JwksResponse> {\n const serviceContext: RequestBaseServiceContext = {\n method: {\n name: `TokenIssuerJwksService.get`,\n url: params.params.uri,\n normalizedUrl: params.params.uri,\n type: 'get',\n },\n source: params.config.metrics?.source || 'unset',\n target: {\n name: 'token-issuer',\n type: 'internal',\n },\n }\n const requestConfig = {\n config: params.config,\n url: serviceContext.method.url,\n method: serviceContext.method.type,\n metricsDetails: serviceContext,\n // A read-only GET against an internal service, so retrying is free of\n // side effects \u2014 and this feeds AuthChecker, so a brief token-issuer blip\n // otherwise turns straight into failed auth for real requests.\n retryTransientErrors: true,\n }\n const response = await request<JwksResponse>(requestConfig)\n if (!response.ok) {\n const fromCache = keysCache.get(CACHE_KEY)\n if (fromCache && TokenIssuerJwksService.isValidKeysObject(fromCache)) {\n return fromCache\n }\n throw new HttpError(\n `${serviceContext.method.name}: ${response.status} ${response.statusText}`,\n response.status,\n response.statusText,\n )\n }\n\n if (TokenIssuerJwksService.isValidKeysObject(response.body)) {\n // valid response\n log(requestConfig, 'debug', 'jwks response valid, adding cache entry', {\n response,\n })\n keysCache.set(CACHE_KEY, response.body!)\n if (requestConfig.config.stage === 'dev') {\n const fromCache = keysCache.get(CACHE_KEY)\n log(requestConfig, 'debug', 'jwks value in cache', {\n response,\n fromCache,\n })\n }\n } else {\n log(requestConfig, 'debug', 'jwks response invalid', { response })\n // invalid response\n const fromCache = keysCache.get(CACHE_KEY)\n log(requestConfig, 'debug', 'checking cached entry', {\n response,\n fromCache,\n })\n if (fromCache && TokenIssuerJwksService.isValidKeysObject(fromCache)) {\n log(requestConfig, 'debug', 'using jwks from local cache', {\n response,\n fromCache,\n })\n return fromCache\n }\n }\n return response.body!\n }\n\n private static isValidKeysObject(response: any): boolean {\n return response?.keys && response?.keys.length > 0\n }\n}\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,cAAc;AAKvB,SAAS,KAAK,eAAe;AAC7B,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAE1B,MAAM,YAAY;AAClB,MAAM,YAAY,IAAI,MAA4B,KAAK,MAAM;AAYtD,MAAM,uBAAuB;AAAA,EAClC,aAAoB,IAAI,QAA4C;AAClE,UAAM,iBAA4C;AAAA,MAChD,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,KAAK,OAAO,OAAO;AAAA,QACnB,eAAe,OAAO,OAAO;AAAA,QAC7B,MAAM;AAAA,MACR;AAAA,MACA,QAAQ,OAAO,OAAO,SAAS,UAAU;AAAA,MACzC,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AACA,UAAM,gBAAgB;AAAA,MACpB,QAAQ,OAAO;AAAA,MACf,KAAK,eAAe,OAAO;AAAA,MAC3B,QAAQ,eAAe,OAAO;AAAA,MAC9B,gBAAgB;AAAA;AAAA;AAAA;AAAA,MAIhB,sBAAsB;AAAA,IACxB;AACA,UAAM,WAAW,MAAM,QAAsB,aAAa;AAC1D,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,UAAU,IAAI,SAAS;AACzC,UAAI,aAAa,uBAAuB,kBAAkB,SAAS,GAAG;AACpE,eAAO;AAAA,MACT;AACA,YAAM,IAAI;AAAA,QACR,GAAG,eAAe,OAAO,IAAI,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,QACxE,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,uBAAuB,kBAAkB,SAAS,IAAI,GAAG;AAE3D,UAAI,eAAe,SAAS,2CAA2C;AAAA,QACrE;AAAA,MACF,CAAC;AACD,gBAAU,IAAI,WAAW,SAAS,IAAK;AACvC,UAAI,cAAc,OAAO,UAAU,OAAO;AACxC,cAAM,YAAY,UAAU,IAAI,SAAS;AACzC,YAAI,eAAe,SAAS,uBAAuB;AAAA,UACjD;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,OAAO;AACL,UAAI,eAAe,SAAS,yBAAyB,EAAE,SAAS,CAAC;AAEjE,YAAM,YAAY,UAAU,IAAI,SAAS;AACzC,UAAI,eAAe,SAAS,yBAAyB;AAAA,QACnD;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,aAAa,uBAAuB,kBAAkB,SAAS,GAAG;AACpE,YAAI,eAAe,SAAS,+BAA+B;AAAA,UACzD;AAAA,UACA;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,OAAe,kBAAkB,UAAwB;AACvD,WAAO,UAAU,QAAQ,UAAU,KAAK,SAAS;AAAA,EACnD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils/api.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getEnvParam } from "./environments.js";
|
|
2
2
|
import { frontendCache } from "./frontendCache.js";
|
|
3
3
|
const TRANSIENT_RETRY_STATUS = [502, 503, 504];
|
|
4
|
+
const MAX_ERROR_BODY_LENGTH = 500;
|
|
5
|
+
const ERROR_BODY_TRUNCATION_MARKER = "\u2026 [truncated]";
|
|
4
6
|
const MAX_TRANSIENT_RETRIES = 3;
|
|
5
7
|
function sendMetrics(payload, type, options) {
|
|
6
8
|
if (payload.config.metrics) {
|
|
@@ -358,6 +360,12 @@ async function request(payload) {
|
|
|
358
360
|
statusText = await fetchResponse.text();
|
|
359
361
|
} catch (_error) {
|
|
360
362
|
}
|
|
363
|
+
if (statusText.length > MAX_ERROR_BODY_LENGTH) {
|
|
364
|
+
statusText = `${statusText.slice(
|
|
365
|
+
0,
|
|
366
|
+
MAX_ERROR_BODY_LENGTH - ERROR_BODY_TRUNCATION_MARKER.length
|
|
367
|
+
)}${ERROR_BODY_TRUNCATION_MARKER}`;
|
|
368
|
+
}
|
|
361
369
|
response.statusText = statusText;
|
|
362
370
|
log(
|
|
363
371
|
payload,
|
package/dist/utils/api.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/api.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport type {\n RequestBaseCustomMetricsDetailsType,\n RequestBaseMethodType,\n RequestBaseParams,\n RequestBaseServiceContext,\n} from '../types/general.types.js'\nimport { getEnvParam, type ServiceName } from './environments.js'\nimport { frontendCache } from './frontendCache.js'\n\nexport interface RequestPayload extends RequestBaseParams {\n base?: {\n id: ServiceName\n }\n url: string\n method: RequestBaseMethodType\n headers?: { [key: string]: string }\n type?: 'json' | 'text' | 'form' | 'none'\n responseType?: 'json' | 'text' | 'blob' | 'none'\n retry?: {\n attempts: number\n remaining: number\n }\n /**\n * Opt-in: retry transient gateway failures (502/503/504 and network/fetch\n * errors) with backoff, mirroring the 429 retry. Retried attempts emit a\n * `transient-retry` metric instead of `error`, so a healed blip (e.g. an\n * envoy connection reset during a pod restart) doesn't count toward the\n * internal error-rate alert. Only enable for idempotent-safe calls \u2014 a\n * retried POST may execute twice.\n */\n retryTransientErrors?: boolean\n transientRetry?: {\n attempts: number\n remaining: number\n }\n body?: any\n metricsDetails: RequestBaseServiceContext\n}\n\nconst TRANSIENT_RETRY_STATUS = [502, 503, 504]\nconst MAX_TRANSIENT_RETRIES = 3\n\nexport interface Response<T> {\n body?: T\n status: number\n statusText?: string\n ok: boolean\n meta: {\n duration: number\n }\n}\n\nfunction sendMetrics(\n payload: RequestPayload,\n type: RequestBaseCustomMetricsDetailsType,\n options?: {\n duration?: number\n statusCode?: string\n error?: string\n },\n) {\n if (payload.config.metrics) {\n if (payload.config.metrics.types.includes(type)) {\n let durationBucket: string | undefined\n if (options?.duration) {\n if (options.duration < 100) {\n durationBucket = '0 | <100ms'\n } else if (options.duration < 200) {\n durationBucket = '1 | <200ms'\n } else if (options.duration < 500) {\n durationBucket = '2 | <500ms'\n } else if (options.duration < 1000) {\n durationBucket = '3 | <1s'\n } else {\n durationBucket = '4 | >=1s'\n }\n }\n\n // only add normalized errors as labels\n let error = 'other'\n if (options?.error) {\n try {\n const errorMessage = options.error\n if (errorMessage.startsWith('<!DOCTYPE html>')) {\n error = 'html-response'\n } else if (errorMessage.includes('UND_ERR_CONNECT_TIMEOUT')) {\n error = 'connect-timeout'\n }\n } catch (_error) {\n // ignore it\n }\n } else {\n error = 'unset'\n }\n\n const metrics: RequestBaseServiceContext = {\n ...payload.metricsDetails,\n eventType: type,\n durationBucket,\n statusCode: options?.statusCode,\n error,\n }\n try {\n payload.config.metrics.callback(metrics)\n } catch (metricsCallbackError) {\n log(payload, 'debug', `Failed to send metrics`, {\n error: metricsCallbackError,\n metrics,\n })\n }\n }\n }\n}\n\nexport function log(\n payload: RequestPayload,\n type: 'debug' | 'info' | 'error',\n message: string,\n additionalData?: any,\n) {\n if (payload.config.logger) {\n const messageParts: string[] = []\n messageParts.push(payload.metricsDetails?.method?.name)\n messageParts.push(\n `${payload.metricsDetails?.target?.name} (${payload.metricsDetails?.target?.type})`,\n )\n messageParts.push(message)\n let body: any\n if (payload.body) {\n body = JSON.parse(JSON.stringify(payload.body))\n for (const key of Object.keys(body)) {\n if (\n key.toLowerCase().includes('password') ||\n key.toLowerCase().includes('secret') ||\n key.toLowerCase().includes('token')\n ) {\n body[key] = '********'\n }\n }\n }\n\n let fullUrl = 'unset'\n\n if (payload.base) {\n try {\n const base = getEnvParam(payload.base.id, payload.config)\n fullUrl = base ? `${base}/${payload.url}` : payload.url\n } catch (_error) {\n fullUrl = `failed to get base url for ${payload.base.id}`\n }\n }\n\n let url = `${payload.url}`.toLowerCase()\n if (url.includes('secret') || url.includes('password')) {\n url = url.split('?')[0]\n }\n\n const data = {\n base: payload.base,\n method: payload.method,\n url,\n fullUrl,\n retry: payload.retry,\n body,\n context: payload.metricsDetails,\n ...additionalData,\n }\n payload.config.logger[type](messageParts.join(' > '), { data })\n }\n}\n\nfunction rateLimitWaitingTime(attempt: number): number {\n const noise = Math.random() * 100\n const waitingTime = 2 ** attempt * 1000 + noise\n return Math.floor(waitingTime)\n}\n\nfunction transientRetryWaitingTime(attempt: number): number {\n const noise = Math.random() * 100\n // 500ms, 1s, 2s (+jitter) \u2014 bounded so a fire-and-forget call heals fast.\n return Math.floor(2 ** attempt * 250 + noise)\n}\n\nfunction canRetryTransient(payload: RequestPayload): boolean {\n if (!payload.retryTransientErrors) {\n return false\n }\n const remaining = payload.transientRetry?.remaining ?? MAX_TRANSIENT_RETRIES\n return remaining > 0\n}\n\nasync function scheduleTransientRetry(payload: RequestPayload): Promise<void> {\n const attempts = payload.transientRetry?.attempts ?? 1\n const remaining = payload.transientRetry?.remaining ?? MAX_TRANSIENT_RETRIES\n const waitingTime = transientRetryWaitingTime(attempts)\n log(\n payload,\n 'debug',\n `transient error, attempt ${attempts}, remaining ${remaining}, waiting ${waitingTime}ms`,\n )\n await new Promise((resolve) => {\n setTimeout(resolve, waitingTime)\n })\n // eslint-disable-next-line require-atomic-updates\n payload.transientRetry = {\n attempts: attempts + 1,\n remaining: remaining - 1,\n }\n}\n\nexport async function request<T>(\n payload: RequestPayload,\n): Promise<Response<T>> {\n log(payload, 'debug', 'starting request')\n sendMetrics(payload, 'request')\n // configure frontend cache if enabled (browser only)\n try {\n if (payload.config.frontendCaching?.enabled) {\n frontendCache.configure(payload.config.frontendCaching)\n }\n } catch (_cacheConfigError) {\n // ignore configuration errors silently\n }\n let token\n if (payload.config.auth) {\n token = await payload.config.auth.getToken()\n }\n // Dynamic (e.g. correlation) headers first, then explicit per-request headers\n // override them, then auth/content-type below take final precedence.\n //\n // Correlation headers (traceparent, x-cc-session-id) identify our internal\n // request/session, so they must only ever reach Camunda-owned systems. Every\n // third-party target is marked `type: 'external'`, so we gate on that here:\n // external targets never receive dynamic headers even if the caller's config\n // sets getDynamicHeaders. This makes propagation safe-by-default and removes\n // the footgun of a shared config that is used for both internal and\n // third-party calls (e.g. BASIC_API_CONFIG \u2192 token-issuer vs Slack).\n let dynamicHeaders: Record<string, string> = {}\n if (payload.metricsDetails?.target?.type !== 'external') {\n try {\n dynamicHeaders = payload.config.getDynamicHeaders?.() ?? {}\n } catch (_error) {\n // never let header evaluation break a request\n }\n }\n const headers: { [key: string]: string } = {\n ...dynamicHeaders,\n ...(payload.headers ?? {}),\n }\n if (token) {\n headers.Authorization = `Bearer ${token}`\n }\n const type = payload.type ?? 'json'\n let body\n if (type) {\n switch (type) {\n case 'json':\n headers['Content-Type'] = 'application/json'\n body = payload.body ? JSON.stringify(payload.body) : undefined\n break\n case 'text':\n headers['Content-Type'] = 'text/plain'\n body = payload.body ? JSON.stringify(payload.body) : undefined\n break\n case 'form':\n if (payload.body) {\n const formData = new URLSearchParams()\n Object.keys(payload.body).forEach((key) => {\n formData.append(key, payload.body[key])\n })\n body = formData\n }\n break\n }\n }\n let base\n if (payload.base) {\n try {\n base = getEnvParam(payload.base.id, payload.config)\n } catch (error) {\n log(payload, 'error', `Failed to get base url for ${payload.base.id}`, {\n error,\n })\n throw error\n }\n }\n const url = base ? `${base}/${payload.url}` : payload.url\n\n // Attempt cache read for GET requests (stale-while-revalidate when callback supplied)\n let serveCachedAndReturnAfterNetwork = false\n if (payload.method === 'get' && payload.config.frontendCaching?.enabled) {\n try {\n const cached = await frontendCache.get<T>('get', url)\n if (cached) {\n const fresh = frontendCache.isFresh(cached)\n if (payload.onCachedResponse) {\n payload.onCachedResponse(cached.payload, { fresh, source: 'cache' })\n if (!fresh) {\n log(payload, 'debug', 'cache stale, refreshing in background')\n serveCachedAndReturnAfterNetwork = true\n } else {\n log(payload, 'debug', 'cache fresh, returning early')\n sendMetrics(payload, 'success', { duration: 0, statusCode: '200' })\n return {\n // eslint-disable-next-line id-length\n ok: true,\n status: 200,\n statusText: 'cached',\n body: cached.payload,\n meta: { duration: 0 },\n }\n }\n } else if (fresh) {\n // no callback but fresh -> return directly\n log(payload, 'debug', 'cache hit, returning cached response')\n sendMetrics(payload, 'success', { duration: 0, statusCode: '200' })\n return {\n // eslint-disable-next-line id-length\n ok: true,\n status: 200,\n statusText: 'cached',\n body: cached.payload,\n meta: { duration: 0 },\n }\n } else {\n log(payload, 'debug', 'cache stale, proceeding with network request')\n }\n }\n } catch (_cacheGetError) {\n // ignore cache errors\n }\n }\n const start = performance.now()\n const proxyAgent = payload.config.proxyAgent ?? {}\n try {\n const fetchResponse = await fetch(url, {\n signal: payload.config.timeout\n ? AbortSignal.timeout(payload.config.timeout)\n : undefined,\n method: payload.method.toUpperCase(),\n headers,\n body,\n ...proxyAgent,\n })\n const end = performance.now()\n const duration = end - start\n\n if ([429].includes(fetchResponse.status)) {\n // rate limiting, retry\n log(payload, 'info', 'rate limited', { durationMs: Math.floor(duration) })\n sendMetrics(payload, 'rate-limit-retry', {\n duration,\n statusCode: String(fetchResponse.status),\n })\n const remaining = payload.retry?.remaining ?? 8\n const attempts = payload.retry?.attempts ?? 1\n if (remaining > 0) {\n let waitingTime = rateLimitWaitingTime(attempts)\n if (fetchResponse.headers.has('x-ratelimit-reset')) {\n const rateLimitHeader = fetchResponse.headers.get('x-ratelimit-reset')\n log(\n payload,\n 'debug',\n `rate limited, found x-ratelimit-reset header: ${rateLimitHeader}`,\n )\n const rateLimitHeaderNumber = Number(rateLimitHeader)\n if (Number.isNaN(rateLimitHeaderNumber)) {\n waitingTime = Date.parse(rateLimitHeader ?? '0') - Date.now()\n } else {\n waitingTime = rateLimitHeaderNumber - Date.now()\n }\n }\n if (waitingTime < 0 || waitingTime > 180000) {\n waitingTime = rateLimitWaitingTime(attempts)\n }\n log(\n payload,\n 'debug',\n `rate limited, attempt ${attempts}, remaining ${remaining}, waiting ${waitingTime}ms`,\n )\n await new Promise((resolve) => {\n setTimeout(resolve, waitingTime)\n })\n // eslint-disable-next-line require-atomic-updates\n payload.retry = {\n attempts: attempts + 1,\n remaining: remaining - 1,\n }\n return request(payload)\n }\n log(payload, 'debug', `rate limited, no more retries`, {\n durationMs: Math.floor(duration),\n })\n }\n\n const response: Response<T> = {\n // eslint-disable-next-line id-length\n ok: fetchResponse.ok,\n status: fetchResponse.status,\n statusText: fetchResponse.statusText,\n meta: {\n duration,\n },\n }\n\n const responseType = payload.responseType ?? type\n\n if (response.ok) {\n log(payload, 'debug', `request successful in ${Math.floor(duration)}ms`, {\n baseUrl: base,\n durationMs: Math.floor(duration),\n })\n sendMetrics(payload, 'success', {\n duration,\n statusCode: String(fetchResponse.status),\n })\n switch (responseType) {\n case 'json':\n try {\n response.body = await fetchResponse.json()\n } catch (_error) {\n log(\n payload,\n 'debug',\n `Failed to parse json body. Returning empty object.`,\n {\n baseUrl: base,\n },\n )\n response.body = {} as T\n try {\n const responseBodyText = await fetchResponse.text()\n log(payload, 'debug', `Response body text`, {\n baseUrl: base,\n responseBodyText,\n status: fetchResponse.status,\n })\n } catch (textError) {\n // this is fine\n log(payload, 'debug', `Failed to parse response body text.`, {\n baseUrl: base,\n status: fetchResponse.status,\n error: textError,\n })\n }\n }\n break\n case 'text':\n response.body = (await fetchResponse.text()) as T\n break\n case 'blob':\n // Binary payloads (file downloads, e.g. database dumps). Not cached.\n response.body = (await fetchResponse.blob()) as T\n break\n default:\n response.body = (await fetchResponse.text()) as T\n }\n // store in cache after parsing\n if (\n payload.method === 'get' &&\n payload.config.frontendCaching?.enabled &&\n response.body !== undefined &&\n responseType !== 'blob'\n ) {\n try {\n await frontendCache.set('get', url, response.body as any)\n log(payload, 'debug', 'cache store successful')\n } catch (_cacheSetError) {\n // ignore cache set error\n }\n }\n } else {\n let statusText = fetchResponse.statusText\n try {\n statusText = await fetchResponse.text()\n } catch (_error) {\n // this is fine\n }\n response.statusText = statusText\n log(\n payload,\n 'debug',\n `request failed with status ${response.status}: ${statusText}`,\n {\n baseUrl: base,\n durationMs: Math.floor(duration),\n },\n )\n\n // Transient gateway failure (e.g. envoy \"connection termination\" 503\n // during a pod restart). When opted in, retry with backoff and record it\n // as `transient-retry` rather than `error`, so a healed blip stays off\n // the internal error-rate alert.\n if (\n TRANSIENT_RETRY_STATUS.includes(response.status) &&\n canRetryTransient(payload)\n ) {\n sendMetrics(payload, 'transient-retry', {\n duration,\n statusCode: String(fetchResponse.status),\n error: statusText,\n })\n await scheduleTransientRetry(payload)\n return request(payload)\n }\n\n // Special Handling for dedicated target systems to not count special status codes as errors\n // eslint-disable-next-line no-shadow\n let type: RequestBaseCustomMetricsDetailsType = 'error'\n const noErrorStatusCodes: number[] = []\n switch (payload.metricsDetails.target.name) {\n case 'auth0':\n noErrorStatusCodes.push(...[400, 404, 409])\n break\n case 'accounts':\n case 'console':\n noErrorStatusCodes.push(...[404, 409])\n if (payload.metricsDetails.method.name === 'createTemporaryCluster') {\n // case: cannot create temporary cluster\n noErrorStatusCodes.push(...[400, 403, 412])\n }\n if (\n [\n 'ConsoleM2MService.verifyZeebeClient',\n 'ConsoleM2MService.verifyApiClient',\n ].includes(payload.metricsDetails.method.name)\n ) {\n // case: api/zeebe client credentials are invalid\n noErrorStatusCodes.push(...[401])\n }\n if (\n payload.metricsDetails.method.name ===\n 'OrgsService.createAndReturnSocialLoginUser'\n ) {\n noErrorStatusCodes.push(\n ...[\n // onboard user, user already has an org\n 400,\n // existing extSSO-connection\n 412,\n ],\n )\n }\n break\n }\n\n if (noErrorStatusCodes.includes(response.status)) {\n type = 'success'\n }\n\n sendMetrics(payload, type, {\n duration,\n statusCode: String(fetchResponse.status),\n error: statusText,\n })\n }\n if (serveCachedAndReturnAfterNetwork && response.ok) {\n // when we served stale via callback earlier, we now also return the fresh response\n log(payload, 'debug', 'stale cache updated, returning new data')\n }\n return response\n } catch (error: any) {\n const duration = performance.now() - start\n let errorMessage: string | undefined\n if (error?.cause?.code) {\n errorMessage = error?.cause?.code\n } else if (error?.message) {\n errorMessage = error?.message\n }\n // Network/fetch error with no HTTP status (e.g. connection reset, timeout).\n // Retry-on-opt-in before treating it as an error (see else branch above).\n if (canRetryTransient(payload)) {\n log(payload, 'debug', `request failed with no status, retrying`, {\n baseUrl: base,\n error,\n durationMs: Math.floor(duration),\n })\n sendMetrics(payload, 'transient-retry', {\n statusCode: 'fetch-error',\n duration,\n error: errorMessage,\n })\n await scheduleTransientRetry(payload)\n return request(payload)\n }\n log(payload, 'error', `request failed with no status`, {\n baseUrl: base,\n error,\n durationMs: Math.floor(duration),\n })\n sendMetrics(payload, 'error', {\n statusCode: 'fetch-error',\n duration,\n error: errorMessage,\n })\n const response: Response<T> = {\n // eslint-disable-next-line id-length\n ok: false,\n status: 500,\n statusText: 'fetch error',\n meta: {\n duration,\n },\n }\n return response\n }\n}\n"],
|
|
5
|
-
"mappings": "AAYA,SAAS,mBAAqC;AAC9C,SAAS,qBAAqB;AAgC9B,MAAM,yBAAyB,CAAC,KAAK,KAAK,GAAG;
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport type {\n RequestBaseCustomMetricsDetailsType,\n RequestBaseMethodType,\n RequestBaseParams,\n RequestBaseServiceContext,\n} from '../types/general.types.js'\nimport { getEnvParam, type ServiceName } from './environments.js'\nimport { frontendCache } from './frontendCache.js'\n\nexport interface RequestPayload extends RequestBaseParams {\n base?: {\n id: ServiceName\n }\n url: string\n method: RequestBaseMethodType\n headers?: { [key: string]: string }\n type?: 'json' | 'text' | 'form' | 'none'\n responseType?: 'json' | 'text' | 'blob' | 'none'\n retry?: {\n attempts: number\n remaining: number\n }\n /**\n * Opt-in: retry transient gateway failures (502/503/504 and network/fetch\n * errors) with backoff, mirroring the 429 retry. Retried attempts emit a\n * `transient-retry` metric instead of `error`, so a healed blip (e.g. an\n * envoy connection reset during a pod restart) doesn't count toward the\n * internal error-rate alert. Only enable for idempotent-safe calls \u2014 a\n * retried POST may execute twice.\n */\n retryTransientErrors?: boolean\n transientRetry?: {\n attempts: number\n remaining: number\n }\n body?: any\n metricsDetails: RequestBaseServiceContext\n}\n\nconst TRANSIENT_RETRY_STATUS = [502, 503, 504]\n/**\n * Cap on the error body kept as `statusText`. It ends up in the thrown error's\n * message, the log line, and the `ccma.errors` payload, and some upstreams\n * (LiteLLM, GCP load balancers) answer failures with a full HTML page \u2014 one\n * such response is kilobytes of markup standing in for a reason phrase. The\n * head is what carries the actual reason.\n */\nconst MAX_ERROR_BODY_LENGTH = 500\n/** Appended in place of what was cut, and counted against the cap above. */\nconst ERROR_BODY_TRUNCATION_MARKER = '\u2026 [truncated]'\nconst MAX_TRANSIENT_RETRIES = 3\n\nexport interface Response<T> {\n body?: T\n status: number\n statusText?: string\n ok: boolean\n meta: {\n duration: number\n }\n}\n\nfunction sendMetrics(\n payload: RequestPayload,\n type: RequestBaseCustomMetricsDetailsType,\n options?: {\n duration?: number\n statusCode?: string\n error?: string\n },\n) {\n if (payload.config.metrics) {\n if (payload.config.metrics.types.includes(type)) {\n let durationBucket: string | undefined\n if (options?.duration) {\n if (options.duration < 100) {\n durationBucket = '0 | <100ms'\n } else if (options.duration < 200) {\n durationBucket = '1 | <200ms'\n } else if (options.duration < 500) {\n durationBucket = '2 | <500ms'\n } else if (options.duration < 1000) {\n durationBucket = '3 | <1s'\n } else {\n durationBucket = '4 | >=1s'\n }\n }\n\n // only add normalized errors as labels\n let error = 'other'\n if (options?.error) {\n try {\n const errorMessage = options.error\n if (errorMessage.startsWith('<!DOCTYPE html>')) {\n error = 'html-response'\n } else if (errorMessage.includes('UND_ERR_CONNECT_TIMEOUT')) {\n error = 'connect-timeout'\n }\n } catch (_error) {\n // ignore it\n }\n } else {\n error = 'unset'\n }\n\n const metrics: RequestBaseServiceContext = {\n ...payload.metricsDetails,\n eventType: type,\n durationBucket,\n statusCode: options?.statusCode,\n error,\n }\n try {\n payload.config.metrics.callback(metrics)\n } catch (metricsCallbackError) {\n log(payload, 'debug', `Failed to send metrics`, {\n error: metricsCallbackError,\n metrics,\n })\n }\n }\n }\n}\n\nexport function log(\n payload: RequestPayload,\n type: 'debug' | 'info' | 'error',\n message: string,\n additionalData?: any,\n) {\n if (payload.config.logger) {\n const messageParts: string[] = []\n messageParts.push(payload.metricsDetails?.method?.name)\n messageParts.push(\n `${payload.metricsDetails?.target?.name} (${payload.metricsDetails?.target?.type})`,\n )\n messageParts.push(message)\n let body: any\n if (payload.body) {\n body = JSON.parse(JSON.stringify(payload.body))\n for (const key of Object.keys(body)) {\n if (\n key.toLowerCase().includes('password') ||\n key.toLowerCase().includes('secret') ||\n key.toLowerCase().includes('token')\n ) {\n body[key] = '********'\n }\n }\n }\n\n let fullUrl = 'unset'\n\n if (payload.base) {\n try {\n const base = getEnvParam(payload.base.id, payload.config)\n fullUrl = base ? `${base}/${payload.url}` : payload.url\n } catch (_error) {\n fullUrl = `failed to get base url for ${payload.base.id}`\n }\n }\n\n let url = `${payload.url}`.toLowerCase()\n if (url.includes('secret') || url.includes('password')) {\n url = url.split('?')[0]\n }\n\n const data = {\n base: payload.base,\n method: payload.method,\n url,\n fullUrl,\n retry: payload.retry,\n body,\n context: payload.metricsDetails,\n ...additionalData,\n }\n payload.config.logger[type](messageParts.join(' > '), { data })\n }\n}\n\nfunction rateLimitWaitingTime(attempt: number): number {\n const noise = Math.random() * 100\n const waitingTime = 2 ** attempt * 1000 + noise\n return Math.floor(waitingTime)\n}\n\nfunction transientRetryWaitingTime(attempt: number): number {\n const noise = Math.random() * 100\n // 500ms, 1s, 2s (+jitter) \u2014 bounded so a fire-and-forget call heals fast.\n return Math.floor(2 ** attempt * 250 + noise)\n}\n\nfunction canRetryTransient(payload: RequestPayload): boolean {\n if (!payload.retryTransientErrors) {\n return false\n }\n const remaining = payload.transientRetry?.remaining ?? MAX_TRANSIENT_RETRIES\n return remaining > 0\n}\n\nasync function scheduleTransientRetry(payload: RequestPayload): Promise<void> {\n const attempts = payload.transientRetry?.attempts ?? 1\n const remaining = payload.transientRetry?.remaining ?? MAX_TRANSIENT_RETRIES\n const waitingTime = transientRetryWaitingTime(attempts)\n log(\n payload,\n 'debug',\n `transient error, attempt ${attempts}, remaining ${remaining}, waiting ${waitingTime}ms`,\n )\n await new Promise((resolve) => {\n setTimeout(resolve, waitingTime)\n })\n // eslint-disable-next-line require-atomic-updates\n payload.transientRetry = {\n attempts: attempts + 1,\n remaining: remaining - 1,\n }\n}\n\nexport async function request<T>(\n payload: RequestPayload,\n): Promise<Response<T>> {\n log(payload, 'debug', 'starting request')\n sendMetrics(payload, 'request')\n // configure frontend cache if enabled (browser only)\n try {\n if (payload.config.frontendCaching?.enabled) {\n frontendCache.configure(payload.config.frontendCaching)\n }\n } catch (_cacheConfigError) {\n // ignore configuration errors silently\n }\n let token\n if (payload.config.auth) {\n token = await payload.config.auth.getToken()\n }\n // Dynamic (e.g. correlation) headers first, then explicit per-request headers\n // override them, then auth/content-type below take final precedence.\n //\n // Correlation headers (traceparent, x-cc-session-id) identify our internal\n // request/session, so they must only ever reach Camunda-owned systems. Every\n // third-party target is marked `type: 'external'`, so we gate on that here:\n // external targets never receive dynamic headers even if the caller's config\n // sets getDynamicHeaders. This makes propagation safe-by-default and removes\n // the footgun of a shared config that is used for both internal and\n // third-party calls (e.g. BASIC_API_CONFIG \u2192 token-issuer vs Slack).\n let dynamicHeaders: Record<string, string> = {}\n if (payload.metricsDetails?.target?.type !== 'external') {\n try {\n dynamicHeaders = payload.config.getDynamicHeaders?.() ?? {}\n } catch (_error) {\n // never let header evaluation break a request\n }\n }\n const headers: { [key: string]: string } = {\n ...dynamicHeaders,\n ...(payload.headers ?? {}),\n }\n if (token) {\n headers.Authorization = `Bearer ${token}`\n }\n const type = payload.type ?? 'json'\n let body\n if (type) {\n switch (type) {\n case 'json':\n headers['Content-Type'] = 'application/json'\n body = payload.body ? JSON.stringify(payload.body) : undefined\n break\n case 'text':\n headers['Content-Type'] = 'text/plain'\n body = payload.body ? JSON.stringify(payload.body) : undefined\n break\n case 'form':\n if (payload.body) {\n const formData = new URLSearchParams()\n Object.keys(payload.body).forEach((key) => {\n formData.append(key, payload.body[key])\n })\n body = formData\n }\n break\n }\n }\n let base\n if (payload.base) {\n try {\n base = getEnvParam(payload.base.id, payload.config)\n } catch (error) {\n log(payload, 'error', `Failed to get base url for ${payload.base.id}`, {\n error,\n })\n throw error\n }\n }\n const url = base ? `${base}/${payload.url}` : payload.url\n\n // Attempt cache read for GET requests (stale-while-revalidate when callback supplied)\n let serveCachedAndReturnAfterNetwork = false\n if (payload.method === 'get' && payload.config.frontendCaching?.enabled) {\n try {\n const cached = await frontendCache.get<T>('get', url)\n if (cached) {\n const fresh = frontendCache.isFresh(cached)\n if (payload.onCachedResponse) {\n payload.onCachedResponse(cached.payload, { fresh, source: 'cache' })\n if (!fresh) {\n log(payload, 'debug', 'cache stale, refreshing in background')\n serveCachedAndReturnAfterNetwork = true\n } else {\n log(payload, 'debug', 'cache fresh, returning early')\n sendMetrics(payload, 'success', { duration: 0, statusCode: '200' })\n return {\n // eslint-disable-next-line id-length\n ok: true,\n status: 200,\n statusText: 'cached',\n body: cached.payload,\n meta: { duration: 0 },\n }\n }\n } else if (fresh) {\n // no callback but fresh -> return directly\n log(payload, 'debug', 'cache hit, returning cached response')\n sendMetrics(payload, 'success', { duration: 0, statusCode: '200' })\n return {\n // eslint-disable-next-line id-length\n ok: true,\n status: 200,\n statusText: 'cached',\n body: cached.payload,\n meta: { duration: 0 },\n }\n } else {\n log(payload, 'debug', 'cache stale, proceeding with network request')\n }\n }\n } catch (_cacheGetError) {\n // ignore cache errors\n }\n }\n const start = performance.now()\n const proxyAgent = payload.config.proxyAgent ?? {}\n try {\n const fetchResponse = await fetch(url, {\n signal: payload.config.timeout\n ? AbortSignal.timeout(payload.config.timeout)\n : undefined,\n method: payload.method.toUpperCase(),\n headers,\n body,\n ...proxyAgent,\n })\n const end = performance.now()\n const duration = end - start\n\n if ([429].includes(fetchResponse.status)) {\n // rate limiting, retry\n log(payload, 'info', 'rate limited', { durationMs: Math.floor(duration) })\n sendMetrics(payload, 'rate-limit-retry', {\n duration,\n statusCode: String(fetchResponse.status),\n })\n const remaining = payload.retry?.remaining ?? 8\n const attempts = payload.retry?.attempts ?? 1\n if (remaining > 0) {\n let waitingTime = rateLimitWaitingTime(attempts)\n if (fetchResponse.headers.has('x-ratelimit-reset')) {\n const rateLimitHeader = fetchResponse.headers.get('x-ratelimit-reset')\n log(\n payload,\n 'debug',\n `rate limited, found x-ratelimit-reset header: ${rateLimitHeader}`,\n )\n const rateLimitHeaderNumber = Number(rateLimitHeader)\n if (Number.isNaN(rateLimitHeaderNumber)) {\n waitingTime = Date.parse(rateLimitHeader ?? '0') - Date.now()\n } else {\n waitingTime = rateLimitHeaderNumber - Date.now()\n }\n }\n if (waitingTime < 0 || waitingTime > 180000) {\n waitingTime = rateLimitWaitingTime(attempts)\n }\n log(\n payload,\n 'debug',\n `rate limited, attempt ${attempts}, remaining ${remaining}, waiting ${waitingTime}ms`,\n )\n await new Promise((resolve) => {\n setTimeout(resolve, waitingTime)\n })\n // eslint-disable-next-line require-atomic-updates\n payload.retry = {\n attempts: attempts + 1,\n remaining: remaining - 1,\n }\n return request(payload)\n }\n log(payload, 'debug', `rate limited, no more retries`, {\n durationMs: Math.floor(duration),\n })\n }\n\n const response: Response<T> = {\n // eslint-disable-next-line id-length\n ok: fetchResponse.ok,\n status: fetchResponse.status,\n statusText: fetchResponse.statusText,\n meta: {\n duration,\n },\n }\n\n const responseType = payload.responseType ?? type\n\n if (response.ok) {\n log(payload, 'debug', `request successful in ${Math.floor(duration)}ms`, {\n baseUrl: base,\n durationMs: Math.floor(duration),\n })\n sendMetrics(payload, 'success', {\n duration,\n statusCode: String(fetchResponse.status),\n })\n switch (responseType) {\n case 'json':\n try {\n response.body = await fetchResponse.json()\n } catch (_error) {\n log(\n payload,\n 'debug',\n `Failed to parse json body. Returning empty object.`,\n {\n baseUrl: base,\n },\n )\n response.body = {} as T\n try {\n const responseBodyText = await fetchResponse.text()\n log(payload, 'debug', `Response body text`, {\n baseUrl: base,\n responseBodyText,\n status: fetchResponse.status,\n })\n } catch (textError) {\n // this is fine\n log(payload, 'debug', `Failed to parse response body text.`, {\n baseUrl: base,\n status: fetchResponse.status,\n error: textError,\n })\n }\n }\n break\n case 'text':\n response.body = (await fetchResponse.text()) as T\n break\n case 'blob':\n // Binary payloads (file downloads, e.g. database dumps). Not cached.\n response.body = (await fetchResponse.blob()) as T\n break\n default:\n response.body = (await fetchResponse.text()) as T\n }\n // store in cache after parsing\n if (\n payload.method === 'get' &&\n payload.config.frontendCaching?.enabled &&\n response.body !== undefined &&\n responseType !== 'blob'\n ) {\n try {\n await frontendCache.set('get', url, response.body as any)\n log(payload, 'debug', 'cache store successful')\n } catch (_cacheSetError) {\n // ignore cache set error\n }\n }\n } else {\n let statusText = fetchResponse.statusText\n try {\n statusText = await fetchResponse.text()\n } catch (_error) {\n // this is fine\n }\n if (statusText.length > MAX_ERROR_BODY_LENGTH) {\n // The marker is part of the budget, so the stored value never exceeds\n // MAX_ERROR_BODY_LENGTH \u2014 appending it after slicing to the full cap\n // would put the result over the bound the constant advertises.\n statusText = `${statusText.slice(\n 0,\n MAX_ERROR_BODY_LENGTH - ERROR_BODY_TRUNCATION_MARKER.length,\n )}${ERROR_BODY_TRUNCATION_MARKER}`\n }\n response.statusText = statusText\n log(\n payload,\n 'debug',\n `request failed with status ${response.status}: ${statusText}`,\n {\n baseUrl: base,\n durationMs: Math.floor(duration),\n },\n )\n\n // Transient gateway failure (e.g. envoy \"connection termination\" 503\n // during a pod restart). When opted in, retry with backoff and record it\n // as `transient-retry` rather than `error`, so a healed blip stays off\n // the internal error-rate alert.\n if (\n TRANSIENT_RETRY_STATUS.includes(response.status) &&\n canRetryTransient(payload)\n ) {\n sendMetrics(payload, 'transient-retry', {\n duration,\n statusCode: String(fetchResponse.status),\n error: statusText,\n })\n await scheduleTransientRetry(payload)\n return request(payload)\n }\n\n // Special Handling for dedicated target systems to not count special status codes as errors\n // eslint-disable-next-line no-shadow\n let type: RequestBaseCustomMetricsDetailsType = 'error'\n const noErrorStatusCodes: number[] = []\n switch (payload.metricsDetails.target.name) {\n case 'auth0':\n noErrorStatusCodes.push(...[400, 404, 409])\n break\n case 'accounts':\n case 'console':\n noErrorStatusCodes.push(...[404, 409])\n if (payload.metricsDetails.method.name === 'createTemporaryCluster') {\n // case: cannot create temporary cluster\n noErrorStatusCodes.push(...[400, 403, 412])\n }\n if (\n [\n 'ConsoleM2MService.verifyZeebeClient',\n 'ConsoleM2MService.verifyApiClient',\n ].includes(payload.metricsDetails.method.name)\n ) {\n // case: api/zeebe client credentials are invalid\n noErrorStatusCodes.push(...[401])\n }\n if (\n payload.metricsDetails.method.name ===\n 'OrgsService.createAndReturnSocialLoginUser'\n ) {\n noErrorStatusCodes.push(\n ...[\n // onboard user, user already has an org\n 400,\n // existing extSSO-connection\n 412,\n ],\n )\n }\n break\n }\n\n if (noErrorStatusCodes.includes(response.status)) {\n type = 'success'\n }\n\n sendMetrics(payload, type, {\n duration,\n statusCode: String(fetchResponse.status),\n error: statusText,\n })\n }\n if (serveCachedAndReturnAfterNetwork && response.ok) {\n // when we served stale via callback earlier, we now also return the fresh response\n log(payload, 'debug', 'stale cache updated, returning new data')\n }\n return response\n } catch (error: any) {\n const duration = performance.now() - start\n let errorMessage: string | undefined\n if (error?.cause?.code) {\n errorMessage = error?.cause?.code\n } else if (error?.message) {\n errorMessage = error?.message\n }\n // Network/fetch error with no HTTP status (e.g. connection reset, timeout).\n // Retry-on-opt-in before treating it as an error (see else branch above).\n if (canRetryTransient(payload)) {\n log(payload, 'debug', `request failed with no status, retrying`, {\n baseUrl: base,\n error,\n durationMs: Math.floor(duration),\n })\n sendMetrics(payload, 'transient-retry', {\n statusCode: 'fetch-error',\n duration,\n error: errorMessage,\n })\n await scheduleTransientRetry(payload)\n return request(payload)\n }\n log(payload, 'error', `request failed with no status`, {\n baseUrl: base,\n error,\n durationMs: Math.floor(duration),\n })\n sendMetrics(payload, 'error', {\n statusCode: 'fetch-error',\n duration,\n error: errorMessage,\n })\n const response: Response<T> = {\n // eslint-disable-next-line id-length\n ok: false,\n status: 500,\n statusText: 'fetch error',\n meta: {\n duration,\n },\n }\n return response\n }\n}\n"],
|
|
5
|
+
"mappings": "AAYA,SAAS,mBAAqC;AAC9C,SAAS,qBAAqB;AAgC9B,MAAM,yBAAyB,CAAC,KAAK,KAAK,GAAG;AAQ7C,MAAM,wBAAwB;AAE9B,MAAM,+BAA+B;AACrC,MAAM,wBAAwB;AAY9B,SAAS,YACP,SACA,MACA,SAKA;AACA,MAAI,QAAQ,OAAO,SAAS;AAC1B,QAAI,QAAQ,OAAO,QAAQ,MAAM,SAAS,IAAI,GAAG;AAC/C,UAAI;AACJ,UAAI,SAAS,UAAU;AACrB,YAAI,QAAQ,WAAW,KAAK;AAC1B,2BAAiB;AAAA,QACnB,WAAW,QAAQ,WAAW,KAAK;AACjC,2BAAiB;AAAA,QACnB,WAAW,QAAQ,WAAW,KAAK;AACjC,2BAAiB;AAAA,QACnB,WAAW,QAAQ,WAAW,KAAM;AAClC,2BAAiB;AAAA,QACnB,OAAO;AACL,2BAAiB;AAAA,QACnB;AAAA,MACF;AAGA,UAAI,QAAQ;AACZ,UAAI,SAAS,OAAO;AAClB,YAAI;AACF,gBAAM,eAAe,QAAQ;AAC7B,cAAI,aAAa,WAAW,iBAAiB,GAAG;AAC9C,oBAAQ;AAAA,UACV,WAAW,aAAa,SAAS,yBAAyB,GAAG;AAC3D,oBAAQ;AAAA,UACV;AAAA,QACF,SAAS,QAAQ;AAAA,QAEjB;AAAA,MACF,OAAO;AACL,gBAAQ;AAAA,MACV;AAEA,YAAM,UAAqC;AAAA,QACzC,GAAG,QAAQ;AAAA,QACX,WAAW;AAAA,QACX;AAAA,QACA,YAAY,SAAS;AAAA,QACrB;AAAA,MACF;AACA,UAAI;AACF,gBAAQ,OAAO,QAAQ,SAAS,OAAO;AAAA,MACzC,SAAS,sBAAsB;AAC7B,YAAI,SAAS,SAAS,0BAA0B;AAAA,UAC9C,OAAO;AAAA,UACP;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,IACd,SACA,MACA,SACA,gBACA;AACA,MAAI,QAAQ,OAAO,QAAQ;AACzB,UAAM,eAAyB,CAAC;AAChC,iBAAa,KAAK,QAAQ,gBAAgB,QAAQ,IAAI;AACtD,iBAAa;AAAA,MACX,GAAG,QAAQ,gBAAgB,QAAQ,IAAI,KAAK,QAAQ,gBAAgB,QAAQ,IAAI;AAAA,IAClF;AACA,iBAAa,KAAK,OAAO;AACzB,QAAI;AACJ,QAAI,QAAQ,MAAM;AAChB,aAAO,KAAK,MAAM,KAAK,UAAU,QAAQ,IAAI,CAAC;AAC9C,iBAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,YACE,IAAI,YAAY,EAAE,SAAS,UAAU,KACrC,IAAI,YAAY,EAAE,SAAS,QAAQ,KACnC,IAAI,YAAY,EAAE,SAAS,OAAO,GAClC;AACA,eAAK,GAAG,IAAI;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU;AAEd,QAAI,QAAQ,MAAM;AAChB,UAAI;AACF,cAAM,OAAO,YAAY,QAAQ,KAAK,IAAI,QAAQ,MAAM;AACxD,kBAAU,OAAO,GAAG,IAAI,IAAI,QAAQ,GAAG,KAAK,QAAQ;AAAA,MACtD,SAAS,QAAQ;AACf,kBAAU,8BAA8B,QAAQ,KAAK,EAAE;AAAA,MACzD;AAAA,IACF;AAEA,QAAI,MAAM,GAAG,QAAQ,GAAG,GAAG,YAAY;AACvC,QAAI,IAAI,SAAS,QAAQ,KAAK,IAAI,SAAS,UAAU,GAAG;AACtD,YAAM,IAAI,MAAM,GAAG,EAAE,CAAC;AAAA,IACxB;AAEA,UAAM,OAAO;AAAA,MACX,MAAM,QAAQ;AAAA,MACd,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,MACf;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,GAAG;AAAA,IACL;AACA,YAAQ,OAAO,OAAO,IAAI,EAAE,aAAa,KAAK,KAAK,GAAG,EAAE,KAAK,CAAC;AAAA,EAChE;AACF;AAEA,SAAS,qBAAqB,SAAyB;AACrD,QAAM,QAAQ,KAAK,OAAO,IAAI;AAC9B,QAAM,cAAc,KAAK,UAAU,MAAO;AAC1C,SAAO,KAAK,MAAM,WAAW;AAC/B;AAEA,SAAS,0BAA0B,SAAyB;AAC1D,QAAM,QAAQ,KAAK,OAAO,IAAI;AAE9B,SAAO,KAAK,MAAM,KAAK,UAAU,MAAM,KAAK;AAC9C;AAEA,SAAS,kBAAkB,SAAkC;AAC3D,MAAI,CAAC,QAAQ,sBAAsB;AACjC,WAAO;AAAA,EACT;AACA,QAAM,YAAY,QAAQ,gBAAgB,aAAa;AACvD,SAAO,YAAY;AACrB;AAEA,eAAe,uBAAuB,SAAwC;AAC5E,QAAM,WAAW,QAAQ,gBAAgB,YAAY;AACrD,QAAM,YAAY,QAAQ,gBAAgB,aAAa;AACvD,QAAM,cAAc,0BAA0B,QAAQ;AACtD;AAAA,IACE;AAAA,IACA;AAAA,IACA,4BAA4B,QAAQ,eAAe,SAAS,aAAa,WAAW;AAAA,EACtF;AACA,QAAM,IAAI,QAAQ,CAAC,YAAY;AAC7B,eAAW,SAAS,WAAW;AAAA,EACjC,CAAC;AAED,UAAQ,iBAAiB;AAAA,IACvB,UAAU,WAAW;AAAA,IACrB,WAAW,YAAY;AAAA,EACzB;AACF;AAEA,eAAsB,QACpB,SACsB;AACtB,MAAI,SAAS,SAAS,kBAAkB;AACxC,cAAY,SAAS,SAAS;AAE9B,MAAI;AACF,QAAI,QAAQ,OAAO,iBAAiB,SAAS;AAC3C,oBAAc,UAAU,QAAQ,OAAO,eAAe;AAAA,IACxD;AAAA,EACF,SAAS,mBAAmB;AAAA,EAE5B;AACA,MAAI;AACJ,MAAI,QAAQ,OAAO,MAAM;AACvB,YAAQ,MAAM,QAAQ,OAAO,KAAK,SAAS;AAAA,EAC7C;AAWA,MAAI,iBAAyC,CAAC;AAC9C,MAAI,QAAQ,gBAAgB,QAAQ,SAAS,YAAY;AACvD,QAAI;AACF,uBAAiB,QAAQ,OAAO,oBAAoB,KAAK,CAAC;AAAA,IAC5D,SAAS,QAAQ;AAAA,IAEjB;AAAA,EACF;AACA,QAAM,UAAqC;AAAA,IACzC,GAAG;AAAA,IACH,GAAI,QAAQ,WAAW,CAAC;AAAA,EAC1B;AACA,MAAI,OAAO;AACT,YAAQ,gBAAgB,UAAU,KAAK;AAAA,EACzC;AACA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,MAAI;AACJ,MAAI,MAAM;AACR,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,gBAAQ,cAAc,IAAI;AAC1B,eAAO,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AACrD;AAAA,MACF,KAAK;AACH,gBAAQ,cAAc,IAAI;AAC1B,eAAO,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AACrD;AAAA,MACF,KAAK;AACH,YAAI,QAAQ,MAAM;AAChB,gBAAM,WAAW,IAAI,gBAAgB;AACrC,iBAAO,KAAK,QAAQ,IAAI,EAAE,QAAQ,CAAC,QAAQ;AACzC,qBAAS,OAAO,KAAK,QAAQ,KAAK,GAAG,CAAC;AAAA,UACxC,CAAC;AACD,iBAAO;AAAA,QACT;AACA;AAAA,IACJ;AAAA,EACF;AACA,MAAI;AACJ,MAAI,QAAQ,MAAM;AAChB,QAAI;AACF,aAAO,YAAY,QAAQ,KAAK,IAAI,QAAQ,MAAM;AAAA,IACpD,SAAS,OAAO;AACd,UAAI,SAAS,SAAS,8BAA8B,QAAQ,KAAK,EAAE,IAAI;AAAA,QACrE;AAAA,MACF,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AACA,QAAM,MAAM,OAAO,GAAG,IAAI,IAAI,QAAQ,GAAG,KAAK,QAAQ;AAGtD,MAAI,mCAAmC;AACvC,MAAI,QAAQ,WAAW,SAAS,QAAQ,OAAO,iBAAiB,SAAS;AACvE,QAAI;AACF,YAAM,SAAS,MAAM,cAAc,IAAO,OAAO,GAAG;AACpD,UAAI,QAAQ;AACV,cAAM,QAAQ,cAAc,QAAQ,MAAM;AAC1C,YAAI,QAAQ,kBAAkB;AAC5B,kBAAQ,iBAAiB,OAAO,SAAS,EAAE,OAAO,QAAQ,QAAQ,CAAC;AACnE,cAAI,CAAC,OAAO;AACV,gBAAI,SAAS,SAAS,uCAAuC;AAC7D,+CAAmC;AAAA,UACrC,OAAO;AACL,gBAAI,SAAS,SAAS,8BAA8B;AACpD,wBAAY,SAAS,WAAW,EAAE,UAAU,GAAG,YAAY,MAAM,CAAC;AAClE,mBAAO;AAAA;AAAA,cAEL,IAAI;AAAA,cACJ,QAAQ;AAAA,cACR,YAAY;AAAA,cACZ,MAAM,OAAO;AAAA,cACb,MAAM,EAAE,UAAU,EAAE;AAAA,YACtB;AAAA,UACF;AAAA,QACF,WAAW,OAAO;AAEhB,cAAI,SAAS,SAAS,sCAAsC;AAC5D,sBAAY,SAAS,WAAW,EAAE,UAAU,GAAG,YAAY,MAAM,CAAC;AAClE,iBAAO;AAAA;AAAA,YAEL,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,YAAY;AAAA,YACZ,MAAM,OAAO;AAAA,YACb,MAAM,EAAE,UAAU,EAAE;AAAA,UACtB;AAAA,QACF,OAAO;AACL,cAAI,SAAS,SAAS,8CAA8C;AAAA,QACtE;AAAA,MACF;AAAA,IACF,SAAS,gBAAgB;AAAA,IAEzB;AAAA,EACF;AACA,QAAM,QAAQ,YAAY,IAAI;AAC9B,QAAM,aAAa,QAAQ,OAAO,cAAc,CAAC;AACjD,MAAI;AACF,UAAM,gBAAgB,MAAM,MAAM,KAAK;AAAA,MACrC,QAAQ,QAAQ,OAAO,UACnB,YAAY,QAAQ,QAAQ,OAAO,OAAO,IAC1C;AAAA,MACJ,QAAQ,QAAQ,OAAO,YAAY;AAAA,MACnC;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL,CAAC;AACD,UAAM,MAAM,YAAY,IAAI;AAC5B,UAAM,WAAW,MAAM;AAEvB,QAAI,CAAC,GAAG,EAAE,SAAS,cAAc,MAAM,GAAG;AAExC,UAAI,SAAS,QAAQ,gBAAgB,EAAE,YAAY,KAAK,MAAM,QAAQ,EAAE,CAAC;AACzE,kBAAY,SAAS,oBAAoB;AAAA,QACvC;AAAA,QACA,YAAY,OAAO,cAAc,MAAM;AAAA,MACzC,CAAC;AACD,YAAM,YAAY,QAAQ,OAAO,aAAa;AAC9C,YAAM,WAAW,QAAQ,OAAO,YAAY;AAC5C,UAAI,YAAY,GAAG;AACjB,YAAI,cAAc,qBAAqB,QAAQ;AAC/C,YAAI,cAAc,QAAQ,IAAI,mBAAmB,GAAG;AAClD,gBAAM,kBAAkB,cAAc,QAAQ,IAAI,mBAAmB;AACrE;AAAA,YACE;AAAA,YACA;AAAA,YACA,iDAAiD,eAAe;AAAA,UAClE;AACA,gBAAM,wBAAwB,OAAO,eAAe;AACpD,cAAI,OAAO,MAAM,qBAAqB,GAAG;AACvC,0BAAc,KAAK,MAAM,mBAAmB,GAAG,IAAI,KAAK,IAAI;AAAA,UAC9D,OAAO;AACL,0BAAc,wBAAwB,KAAK,IAAI;AAAA,UACjD;AAAA,QACF;AACA,YAAI,cAAc,KAAK,cAAc,MAAQ;AAC3C,wBAAc,qBAAqB,QAAQ;AAAA,QAC7C;AACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,yBAAyB,QAAQ,eAAe,SAAS,aAAa,WAAW;AAAA,QACnF;AACA,cAAM,IAAI,QAAQ,CAAC,YAAY;AAC7B,qBAAW,SAAS,WAAW;AAAA,QACjC,CAAC;AAED,gBAAQ,QAAQ;AAAA,UACd,UAAU,WAAW;AAAA,UACrB,WAAW,YAAY;AAAA,QACzB;AACA,eAAO,QAAQ,OAAO;AAAA,MACxB;AACA,UAAI,SAAS,SAAS,iCAAiC;AAAA,QACrD,YAAY,KAAK,MAAM,QAAQ;AAAA,MACjC,CAAC;AAAA,IACH;AAEA,UAAM,WAAwB;AAAA;AAAA,MAE5B,IAAI,cAAc;AAAA,MAClB,QAAQ,cAAc;AAAA,MACtB,YAAY,cAAc;AAAA,MAC1B,MAAM;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAEA,UAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAI,SAAS,IAAI;AACf,UAAI,SAAS,SAAS,yBAAyB,KAAK,MAAM,QAAQ,CAAC,MAAM;AAAA,QACvE,SAAS;AAAA,QACT,YAAY,KAAK,MAAM,QAAQ;AAAA,MACjC,CAAC;AACD,kBAAY,SAAS,WAAW;AAAA,QAC9B;AAAA,QACA,YAAY,OAAO,cAAc,MAAM;AAAA,MACzC,CAAC;AACD,cAAQ,cAAc;AAAA,QACpB,KAAK;AACH,cAAI;AACF,qBAAS,OAAO,MAAM,cAAc,KAAK;AAAA,UAC3C,SAAS,QAAQ;AACf;AAAA,cACE;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,gBACE,SAAS;AAAA,cACX;AAAA,YACF;AACA,qBAAS,OAAO,CAAC;AACjB,gBAAI;AACF,oBAAM,mBAAmB,MAAM,cAAc,KAAK;AAClD,kBAAI,SAAS,SAAS,sBAAsB;AAAA,gBAC1C,SAAS;AAAA,gBACT;AAAA,gBACA,QAAQ,cAAc;AAAA,cACxB,CAAC;AAAA,YACH,SAAS,WAAW;AAElB,kBAAI,SAAS,SAAS,uCAAuC;AAAA,gBAC3D,SAAS;AAAA,gBACT,QAAQ,cAAc;AAAA,gBACtB,OAAO;AAAA,cACT,CAAC;AAAA,YACH;AAAA,UACF;AACA;AAAA,QACF,KAAK;AACH,mBAAS,OAAQ,MAAM,cAAc,KAAK;AAC1C;AAAA,QACF,KAAK;AAEH,mBAAS,OAAQ,MAAM,cAAc,KAAK;AAC1C;AAAA,QACF;AACE,mBAAS,OAAQ,MAAM,cAAc,KAAK;AAAA,MAC9C;AAEA,UACE,QAAQ,WAAW,SACnB,QAAQ,OAAO,iBAAiB,WAChC,SAAS,SAAS,UAClB,iBAAiB,QACjB;AACA,YAAI;AACF,gBAAM,cAAc,IAAI,OAAO,KAAK,SAAS,IAAW;AACxD,cAAI,SAAS,SAAS,wBAAwB;AAAA,QAChD,SAAS,gBAAgB;AAAA,QAEzB;AAAA,MACF;AAAA,IACF,OAAO;AACL,UAAI,aAAa,cAAc;AAC/B,UAAI;AACF,qBAAa,MAAM,cAAc,KAAK;AAAA,MACxC,SAAS,QAAQ;AAAA,MAEjB;AACA,UAAI,WAAW,SAAS,uBAAuB;AAI7C,qBAAa,GAAG,WAAW;AAAA,UACzB;AAAA,UACA,wBAAwB,6BAA6B;AAAA,QACvD,CAAC,GAAG,4BAA4B;AAAA,MAClC;AACA,eAAS,aAAa;AACtB;AAAA,QACE;AAAA,QACA;AAAA,QACA,8BAA8B,SAAS,MAAM,KAAK,UAAU;AAAA,QAC5D;AAAA,UACE,SAAS;AAAA,UACT,YAAY,KAAK,MAAM,QAAQ;AAAA,QACjC;AAAA,MACF;AAMA,UACE,uBAAuB,SAAS,SAAS,MAAM,KAC/C,kBAAkB,OAAO,GACzB;AACA,oBAAY,SAAS,mBAAmB;AAAA,UACtC;AAAA,UACA,YAAY,OAAO,cAAc,MAAM;AAAA,UACvC,OAAO;AAAA,QACT,CAAC;AACD,cAAM,uBAAuB,OAAO;AACpC,eAAO,QAAQ,OAAO;AAAA,MACxB;AAIA,UAAIA,QAA4C;AAChD,YAAM,qBAA+B,CAAC;AACtC,cAAQ,QAAQ,eAAe,OAAO,MAAM;AAAA,QAC1C,KAAK;AACH,6BAAmB,KAAK,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC;AAC1C;AAAA,QACF,KAAK;AAAA,QACL,KAAK;AACH,6BAAmB,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC;AACrC,cAAI,QAAQ,eAAe,OAAO,SAAS,0BAA0B;AAEnE,+BAAmB,KAAK,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC;AAAA,UAC5C;AACA,cACE;AAAA,YACE;AAAA,YACA;AAAA,UACF,EAAE,SAAS,QAAQ,eAAe,OAAO,IAAI,GAC7C;AAEA,+BAAmB,KAAK,GAAG,CAAC,GAAG,CAAC;AAAA,UAClC;AACA,cACE,QAAQ,eAAe,OAAO,SAC9B,8CACA;AACA,+BAAmB;AAAA,cACjB,GAAG;AAAA;AAAA,gBAED;AAAA;AAAA,gBAEA;AAAA,cACF;AAAA,YACF;AAAA,UACF;AACA;AAAA,MACJ;AAEA,UAAI,mBAAmB,SAAS,SAAS,MAAM,GAAG;AAChD,QAAAA,QAAO;AAAA,MACT;AAEA,kBAAY,SAASA,OAAM;AAAA,QACzB;AAAA,QACA,YAAY,OAAO,cAAc,MAAM;AAAA,QACvC,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA,QAAI,oCAAoC,SAAS,IAAI;AAEnD,UAAI,SAAS,SAAS,yCAAyC;AAAA,IACjE;AACA,WAAO;AAAA,EACT,SAAS,OAAY;AACnB,UAAM,WAAW,YAAY,IAAI,IAAI;AACrC,QAAI;AACJ,QAAI,OAAO,OAAO,MAAM;AACtB,qBAAe,OAAO,OAAO;AAAA,IAC/B,WAAW,OAAO,SAAS;AACzB,qBAAe,OAAO;AAAA,IACxB;AAGA,QAAI,kBAAkB,OAAO,GAAG;AAC9B,UAAI,SAAS,SAAS,2CAA2C;AAAA,QAC/D,SAAS;AAAA,QACT;AAAA,QACA,YAAY,KAAK,MAAM,QAAQ;AAAA,MACjC,CAAC;AACD,kBAAY,SAAS,mBAAmB;AAAA,QACtC,YAAY;AAAA,QACZ;AAAA,QACA,OAAO;AAAA,MACT,CAAC;AACD,YAAM,uBAAuB,OAAO;AACpC,aAAO,QAAQ,OAAO;AAAA,IACxB;AACA,QAAI,SAAS,SAAS,iCAAiC;AAAA,MACrD,SAAS;AAAA,MACT;AAAA,MACA,YAAY,KAAK,MAAM,QAAQ;AAAA,IACjC,CAAC;AACD,gBAAY,SAAS,SAAS;AAAA,MAC5B,YAAY;AAAA,MACZ;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AACD,UAAM,WAAwB;AAAA;AAAA,MAE5B,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,MAAM;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": ["type"]
|
|
7
7
|
}
|
package/dist/utils/cache.d.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
export declare class Cache<K, V> {
|
|
2
2
|
private expirationInMilliseconds;
|
|
3
3
|
private data;
|
|
4
|
+
/**
|
|
5
|
+
* Loads that have started but not finished, keyed the same as `data`.
|
|
6
|
+
* `get`/`set` never touch this — only `getOrLoad` does — so the plain
|
|
7
|
+
* cache-aside callers are unaffected.
|
|
8
|
+
*/
|
|
9
|
+
private inFlight;
|
|
4
10
|
constructor(expirationInMilliseconds: number);
|
|
5
11
|
set(key: K, value: V): void;
|
|
6
12
|
get(key: K): V | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Cache-aside with single-flight: concurrent callers that miss the same key
|
|
15
|
+
* wait on one `load()` instead of each running their own.
|
|
16
|
+
*
|
|
17
|
+
* Without this, a cold key multiplies the upstream request count by the
|
|
18
|
+
* number of waiting callers. That is what made `GET
|
|
19
|
+
* /api/releases/release-train/:trainName` return 500s: Control asks for
|
|
20
|
+
* several trains at once, every lookup shares one cache key, and each
|
|
21
|
+
* `listMajorMinorReleases` fans out 20 parallel GitHub pages — so three
|
|
22
|
+
* simultaneous cache misses became ~120 concurrent GitHub calls and tripped
|
|
23
|
+
* GitHub's *secondary* (concurrency) rate limit.
|
|
24
|
+
*
|
|
25
|
+
* A rejected load is removed from the in-flight map and not stored, so the
|
|
26
|
+
* next caller retries rather than inheriting a wedged key.
|
|
27
|
+
*/
|
|
28
|
+
getOrLoad(key: K, load: () => Promise<V>, options?: {
|
|
29
|
+
ignoreCache?: boolean;
|
|
30
|
+
}): Promise<V>;
|
|
7
31
|
}
|
package/dist/utils/cache.js
CHANGED
|
@@ -2,6 +2,12 @@ class Cache {
|
|
|
2
2
|
constructor(expirationInMilliseconds) {
|
|
3
3
|
this.expirationInMilliseconds = expirationInMilliseconds;
|
|
4
4
|
this.data = /* @__PURE__ */ new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Loads that have started but not finished, keyed the same as `data`.
|
|
7
|
+
* `get`/`set` never touch this — only `getOrLoad` does — so the plain
|
|
8
|
+
* cache-aside callers are unaffected.
|
|
9
|
+
*/
|
|
10
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
5
11
|
}
|
|
6
12
|
set(key, value) {
|
|
7
13
|
this.data.set(key, {
|
|
@@ -18,6 +24,41 @@ class Cache {
|
|
|
18
24
|
}
|
|
19
25
|
return void 0;
|
|
20
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Cache-aside with single-flight: concurrent callers that miss the same key
|
|
29
|
+
* wait on one `load()` instead of each running their own.
|
|
30
|
+
*
|
|
31
|
+
* Without this, a cold key multiplies the upstream request count by the
|
|
32
|
+
* number of waiting callers. That is what made `GET
|
|
33
|
+
* /api/releases/release-train/:trainName` return 500s: Control asks for
|
|
34
|
+
* several trains at once, every lookup shares one cache key, and each
|
|
35
|
+
* `listMajorMinorReleases` fans out 20 parallel GitHub pages — so three
|
|
36
|
+
* simultaneous cache misses became ~120 concurrent GitHub calls and tripped
|
|
37
|
+
* GitHub's *secondary* (concurrency) rate limit.
|
|
38
|
+
*
|
|
39
|
+
* A rejected load is removed from the in-flight map and not stored, so the
|
|
40
|
+
* next caller retries rather than inheriting a wedged key.
|
|
41
|
+
*/
|
|
42
|
+
getOrLoad(key, load, options) {
|
|
43
|
+
if (options?.ignoreCache !== true) {
|
|
44
|
+
const cached = this.get(key);
|
|
45
|
+
if (cached !== void 0) {
|
|
46
|
+
return Promise.resolve(cached);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const pending = this.inFlight.get(key);
|
|
50
|
+
if (pending) {
|
|
51
|
+
return pending;
|
|
52
|
+
}
|
|
53
|
+
const loading = load().then((value) => {
|
|
54
|
+
this.set(key, value);
|
|
55
|
+
return value;
|
|
56
|
+
}).finally(() => {
|
|
57
|
+
this.inFlight.delete(key);
|
|
58
|
+
});
|
|
59
|
+
this.inFlight.set(key, loading);
|
|
60
|
+
return loading;
|
|
61
|
+
}
|
|
21
62
|
}
|
|
22
63
|
export {
|
|
23
64
|
Cache
|
package/dist/utils/cache.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/cache.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nexport class Cache<K, V> {\n private data: Map<K, { value: V; expiresAt: number }> = new Map()\n\n constructor(private expirationInMilliseconds: number) {}\n\n public set(key: K, value: V) {\n this.data.set(key, {\n value,\n expiresAt: Date.now() + this.expirationInMilliseconds,\n })\n }\n public get(key: K): V | undefined {\n const cached = this.data.get(key)\n if (cached && cached.expiresAt > Date.now()) {\n return cached.value\n } else if (cached) {\n this.data.delete(key)\n }\n return undefined\n }\n}\n"],
|
|
5
|
-
"mappings": "AAMO,MAAM,MAAY;AAAA,
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nexport class Cache<K, V> {\n private data: Map<K, { value: V; expiresAt: number }> = new Map()\n /**\n * Loads that have started but not finished, keyed the same as `data`.\n * `get`/`set` never touch this \u2014 only `getOrLoad` does \u2014 so the plain\n * cache-aside callers are unaffected.\n */\n private inFlight: Map<K, Promise<V>> = new Map()\n\n constructor(private expirationInMilliseconds: number) {}\n\n public set(key: K, value: V) {\n this.data.set(key, {\n value,\n expiresAt: Date.now() + this.expirationInMilliseconds,\n })\n }\n public get(key: K): V | undefined {\n const cached = this.data.get(key)\n if (cached && cached.expiresAt > Date.now()) {\n return cached.value\n } else if (cached) {\n this.data.delete(key)\n }\n return undefined\n }\n\n /**\n * Cache-aside with single-flight: concurrent callers that miss the same key\n * wait on one `load()` instead of each running their own.\n *\n * Without this, a cold key multiplies the upstream request count by the\n * number of waiting callers. That is what made `GET\n * /api/releases/release-train/:trainName` return 500s: Control asks for\n * several trains at once, every lookup shares one cache key, and each\n * `listMajorMinorReleases` fans out 20 parallel GitHub pages \u2014 so three\n * simultaneous cache misses became ~120 concurrent GitHub calls and tripped\n * GitHub's *secondary* (concurrency) rate limit.\n *\n * A rejected load is removed from the in-flight map and not stored, so the\n * next caller retries rather than inheriting a wedged key.\n */\n public getOrLoad(\n key: K,\n load: () => Promise<V>,\n options?: { ignoreCache?: boolean },\n ): Promise<V> {\n if (options?.ignoreCache !== true) {\n const cached = this.get(key)\n if (cached !== undefined) {\n return Promise.resolve(cached)\n }\n }\n\n const pending = this.inFlight.get(key)\n if (pending) {\n return pending\n }\n\n const loading = load()\n .then((value) => {\n this.set(key, value)\n return value\n })\n .finally(() => {\n this.inFlight.delete(key)\n })\n this.inFlight.set(key, loading)\n return loading\n }\n}\n"],
|
|
5
|
+
"mappings": "AAMO,MAAM,MAAY;AAAA,EASvB,YAAoB,0BAAkC;AAAlC;AARpB,SAAQ,OAAgD,oBAAI,IAAI;AAMhE;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,WAA+B,oBAAI,IAAI;AAAA,EAEQ;AAAA,EAEhD,IAAI,KAAQ,OAAU;AAC3B,SAAK,KAAK,IAAI,KAAK;AAAA,MACjB;AAAA,MACA,WAAW,KAAK,IAAI,IAAI,KAAK;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA,EACO,IAAI,KAAuB;AAChC,UAAM,SAAS,KAAK,KAAK,IAAI,GAAG;AAChC,QAAI,UAAU,OAAO,YAAY,KAAK,IAAI,GAAG;AAC3C,aAAO,OAAO;AAAA,IAChB,WAAW,QAAQ;AACjB,WAAK,KAAK,OAAO,GAAG;AAAA,IACtB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBO,UACL,KACA,MACA,SACY;AACZ,QAAI,SAAS,gBAAgB,MAAM;AACjC,YAAM,SAAS,KAAK,IAAI,GAAG;AAC3B,UAAI,WAAW,QAAW;AACxB,eAAO,QAAQ,QAAQ,MAAM;AAAA,MAC/B;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,SAAS,IAAI,GAAG;AACrC,QAAI,SAAS;AACX,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,KAAK,EAClB,KAAK,CAAC,UAAU;AACf,WAAK,IAAI,KAAK,KAAK;AACnB,aAAO;AAAA,IACT,CAAC,EACA,QAAQ,MAAM;AACb,WAAK,SAAS,OAAO,GAAG;AAAA,IAC1B,CAAC;AACH,SAAK,SAAS,IAAI,KAAK,OAAO;AAC9B,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camunda/ccma-api",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Centralized API service layer for Camunda Cloud Management Apps",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@camunda/ccma-shared-types": "0.
|
|
44
|
+
"@camunda/ccma-shared-types": "0.6.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "node esbuild.mjs && tsc --emitDeclarationOnly --outDir dist",
|