@camunda/ccma-shared-types 7.2.0 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/_tests/types/ClusterTtl.type.spec.d.ts +1 -0
  2. package/dist/approvals/use-cases.const.d.ts +2 -2
  3. package/dist/approvals/use-cases.const.js +3 -3
  4. package/dist/approvals/use-cases.const.js.map +2 -2
  5. package/dist/constants/{customer-hardware-sizing.const.d.ts → custom-hardware-sizing.const.d.ts} +18 -18
  6. package/dist/constants/{customer-hardware-sizing.const.js → custom-hardware-sizing.const.js} +35 -35
  7. package/dist/constants/custom-hardware-sizing.const.js.map +7 -0
  8. package/dist/dtos/ApprovalJob.dto.d.ts +2 -2
  9. package/dist/dtos/ApprovalJob.dto.js.map +1 -1
  10. package/dist/dtos/CommonCluster.dto.d.ts +12 -0
  11. package/dist/dtos/ControlV2.dto.d.ts +20 -0
  12. package/dist/enums/AuditEntity.enum.d.ts +1 -0
  13. package/dist/enums/AuditEntity.enum.js +1 -0
  14. package/dist/enums/AuditEntity.enum.js.map +2 -2
  15. package/dist/index.d.ts +5 -3
  16. package/dist/index.js +63 -25
  17. package/dist/index.js.map +2 -2
  18. package/dist/search/cluster-search-fields.d.ts +11 -0
  19. package/dist/search/cluster-search-fields.js +29 -1
  20. package/dist/search/cluster-search-fields.js.map +2 -2
  21. package/dist/search/index.d.ts +1 -1
  22. package/dist/search/index.js +5 -1
  23. package/dist/search/index.js.map +2 -2
  24. package/dist/types/ClusterTtl.type.d.ts +96 -0
  25. package/dist/types/ClusterTtl.type.js +89 -0
  26. package/dist/types/ClusterTtl.type.js.map +7 -0
  27. package/package.json +1 -1
  28. package/dist/constants/customer-hardware-sizing.const.js.map +0 -7
  29. /package/dist/_tests/constants/{customer-hardware-sizing.const.spec.d.ts → custom-hardware-sizing.const.spec.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/search/index.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 {\n type AndNode,\n isLuceneQueryError,\n LuceneQueryError,\n type NotNode,\n type OrNode,\n type QueryNode,\n type RangeNode,\n type SearchTermOperator,\n type TermNode,\n} from './ast.js'\nexport {\n CLUSTER_DEFAULT_SEARCH_FIELDS,\n CLUSTER_SEARCH_FIELDS,\n} from './cluster-search-fields.js'\nexport {\n type CompileContext,\n type CompiledQuery,\n compileQuery,\n type FieldBinding,\n idSetPredicate,\n joinIdSet,\n likeArgument,\n type SqlDialect,\n} from './compiler.js'\nexport {\n findSearchField,\n type SearchFieldCapability,\n type SearchFieldSpec,\n type SearchFieldType,\n} from './fields.js'\nexport {\n ORG_DEFAULT_SEARCH_FIELDS,\n ORGANIZATION_SEARCH_FIELDS,\n} from './organization-search-fields.js'\nexport { parseLuceneQuery } from './parser.js'\n"],
5
- "mappings": "AAMA;AAAA,EAEE;AAAA,EACA;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EAGE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;",
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 {\n type AndNode,\n isLuceneQueryError,\n LuceneQueryError,\n type NotNode,\n type OrNode,\n type QueryNode,\n type RangeNode,\n type SearchTermOperator,\n type TermNode,\n} from './ast.js'\nexport {\n CLUSTER_DEFAULT_SEARCH_FIELDS,\n CLUSTER_SEARCH_FIELDS,\n CLUSTER_TTL_SEARCH_FIELDS,\n clusterSearchFieldsForStage,\n} from './cluster-search-fields.js'\nexport {\n type CompileContext,\n type CompiledQuery,\n compileQuery,\n type FieldBinding,\n idSetPredicate,\n joinIdSet,\n likeArgument,\n type SqlDialect,\n} from './compiler.js'\nexport {\n findSearchField,\n type SearchFieldCapability,\n type SearchFieldSpec,\n type SearchFieldType,\n} from './fields.js'\nexport {\n ORG_DEFAULT_SEARCH_FIELDS,\n ORGANIZATION_SEARCH_FIELDS,\n} from './organization-search-fields.js'\nexport { parseLuceneQuery } from './parser.js'\n"],
5
+ "mappings": "AAMA;AAAA,EAEE;AAAA,EACA;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EAGE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * CLUSTER_TTL_FEATURE_FLAG gates the TTL of a single cluster. It is not the
3
+ * org-level `preprodClusterTtlHours` limit: both apply, and the earlier one
4
+ * deletes the cluster.
5
+ */
6
+ export declare const CLUSTER_TTL_FEATURE_FLAG = "console.clusterTtl";
7
+ /**
8
+ * isClusterTtlStage tells if the cluster TTL can be on at the stage `stage`.
9
+ * That is only dev and int. On prod, and on each other stage, the feature is
10
+ * off for every reader, also when the flag is on.
11
+ */
12
+ export declare const isClusterTtlStage: (stage: string | undefined) => boolean;
13
+ /** CLUSTER_TTL_DURATIONS are the TTLs that end at a time. */
14
+ export declare const CLUSTER_TTL_DURATIONS: readonly ["24h", "3d", "1w", "4w"];
15
+ export type ClusterTtlDuration = (typeof CLUSTER_TTL_DURATIONS)[number];
16
+ /** CLUSTER_TTL_OPTIONS are the choices of the create form, in display order. */
17
+ export declare const CLUSTER_TTL_OPTIONS: readonly ["24h", "3d", "1w", "4w", "unlimited"];
18
+ export type ClusterTtlOption = (typeof CLUSTER_TTL_OPTIONS)[number];
19
+ /** DEFAULT_CLUSTER_TTL_OPTION applies when an eligible create sends no `ttl`. */
20
+ export declare const DEFAULT_CLUSTER_TTL_OPTION: ClusterTtlOption;
21
+ export declare const CLUSTER_TTL_DURATION_MS: Readonly<Record<ClusterTtlDuration, number>>;
22
+ export declare const CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH = 500;
23
+ export declare const CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH = 2048;
24
+ /**
25
+ * CLUSTER_TTL_BACKFILL_REASON is the reason that the schema migration wrote
26
+ * for each cluster that existed before the TTL. These rows are the only
27
+ * unlimited TTLs with a reason and no link.
28
+ */
29
+ export declare const CLUSTER_TTL_BACKFILL_REASON = "Created before cluster TTL existed";
30
+ /**
31
+ * ClusterTtlDto is the TTL of one cluster. A response carries it only when
32
+ * the reader may see the feature, thus an absent `ttl` means "not shown", not
33
+ * "unlimited".
34
+ */
35
+ export interface ClusterTtlDto {
36
+ /** ISO-8601. Absent when the TTL is unlimited. */
37
+ expiresAt?: string;
38
+ /**
39
+ * Why the TTL is unlimited. Absent for a cluster that got no TTL, for
40
+ * example one that a failover or a customer org created.
41
+ */
42
+ unlimitedReason?: string;
43
+ /** An http(s) URL, for example a GitHub issue. */
44
+ unlimitedLink?: string;
45
+ }
46
+ /** ClusterTtlUnlimited is the input that sets an unlimited TTL. */
47
+ export interface ClusterTtlUnlimited {
48
+ reason: string;
49
+ link: string;
50
+ }
51
+ /**
52
+ * UpdateClusterTtlBody is the body of `PATCH api/admin/clusters/:clusterId/ttl`.
53
+ * Give exactly one of the two fields.
54
+ */
55
+ export type UpdateClusterTtlBody = {
56
+ extend: ClusterTtlDuration;
57
+ unlimited?: never;
58
+ } | {
59
+ unlimited: ClusterTtlUnlimited;
60
+ extend?: never;
61
+ };
62
+ export declare const isClusterTtlDuration: (value: unknown) => value is ClusterTtlDuration;
63
+ export declare const isClusterTtlOption: (value: unknown) => value is ClusterTtlOption;
64
+ /** clusterTtlExpiresAt gives the expiry of a new TTL that starts at `now`. */
65
+ export declare function clusterTtlExpiresAt(duration: ClusterTtlDuration, now?: Date): Date;
66
+ /**
67
+ * extendClusterTtl gives the expiry after an extension. The extension starts
68
+ * at the current expiry, or at `now` when that is in the past or when the TTL
69
+ * is unlimited (`currentExpiresAt` undefined). Thus an extension never makes
70
+ * the TTL shorter.
71
+ */
72
+ export declare function extendClusterTtl({ currentExpiresAt, duration, now, }: {
73
+ currentExpiresAt: Date | string | undefined;
74
+ duration: ClusterTtlDuration;
75
+ now?: Date;
76
+ }): Date;
77
+ /**
78
+ * isPastClusterTtl tells if the TTL of a cluster ran out. An unlimited TTL
79
+ * (`expiresAt` undefined) or a value that is not a date never runs out.
80
+ */
81
+ export declare function isPastClusterTtl({ expiresAt, now, }: {
82
+ expiresAt: Date | string | undefined;
83
+ now?: Date;
84
+ }): boolean;
85
+ /** isHttpUrl accepts an absolute `http:` or `https:` URL with a host. */
86
+ export declare function isHttpUrl(value: unknown): value is string;
87
+ /**
88
+ * validateClusterTtlUnlimited gives the first problem of an unlimited TTL
89
+ * input as a message for the caller, or `undefined` when the input is valid.
90
+ * The reason must hold text, and the link must be an http(s) URL. The length
91
+ * limits apply to the trimmed values, because the server stores those.
92
+ */
93
+ export declare function validateClusterTtlUnlimited(input: {
94
+ reason?: unknown;
95
+ link?: unknown;
96
+ }): string | undefined;
@@ -0,0 +1,89 @@
1
+ import { DAY, WEEK } from "../constants/duration.const.js";
2
+ const CLUSTER_TTL_FEATURE_FLAG = "console.clusterTtl";
3
+ const isClusterTtlStage = (stage) => stage === "dev" || stage === "int";
4
+ const CLUSTER_TTL_DURATIONS = ["24h", "3d", "1w", "4w"];
5
+ const CLUSTER_TTL_OPTIONS = [
6
+ ...CLUSTER_TTL_DURATIONS,
7
+ "unlimited"
8
+ ];
9
+ const DEFAULT_CLUSTER_TTL_OPTION = "24h";
10
+ const CLUSTER_TTL_DURATION_MS = {
11
+ "24h": DAY,
12
+ "3d": 3 * DAY,
13
+ "1w": WEEK,
14
+ "4w": 4 * WEEK
15
+ };
16
+ const CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH = 500;
17
+ const CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH = 2048;
18
+ const CLUSTER_TTL_BACKFILL_REASON = "Created before cluster TTL existed";
19
+ const isClusterTtlDuration = (value) => CLUSTER_TTL_DURATIONS.includes(value);
20
+ const isClusterTtlOption = (value) => CLUSTER_TTL_OPTIONS.includes(value);
21
+ function clusterTtlExpiresAt(duration, now = /* @__PURE__ */ new Date()) {
22
+ return new Date(now.getTime() + CLUSTER_TTL_DURATION_MS[duration]);
23
+ }
24
+ function extendClusterTtl({
25
+ currentExpiresAt,
26
+ duration,
27
+ now = /* @__PURE__ */ new Date()
28
+ }) {
29
+ const currentMs = currentExpiresAt === void 0 ? Number.NaN : new Date(currentExpiresAt).getTime();
30
+ const startMs = Number.isNaN(currentMs) ? now.getTime() : Math.max(now.getTime(), currentMs);
31
+ return new Date(startMs + CLUSTER_TTL_DURATION_MS[duration]);
32
+ }
33
+ function isPastClusterTtl({
34
+ expiresAt,
35
+ now = /* @__PURE__ */ new Date()
36
+ }) {
37
+ if (expiresAt === void 0) return false;
38
+ const expiresMs = new Date(expiresAt).getTime();
39
+ if (Number.isNaN(expiresMs)) return false;
40
+ return expiresMs < now.getTime();
41
+ }
42
+ function isHttpUrl(value) {
43
+ if (typeof value !== "string" || /\s/.test(value)) return false;
44
+ try {
45
+ const url = new URL(value);
46
+ return (url.protocol === "http:" || url.protocol === "https:") && url.hostname.length > 0;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+ function validateClusterTtlUnlimited(input) {
52
+ const reason = typeof input.reason === "string" ? input.reason.trim() : void 0;
53
+ const link = typeof input.link === "string" ? input.link.trim() : void 0;
54
+ if (!reason) {
55
+ return "An unlimited TTL needs a reason.";
56
+ }
57
+ if (reason.length > CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH) {
58
+ return `The reason must not be longer than ${CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH} characters.`;
59
+ }
60
+ if (!link) {
61
+ return "An unlimited TTL needs a link.";
62
+ }
63
+ if (link.length > CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH) {
64
+ return `The link must not be longer than ${CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH} characters.`;
65
+ }
66
+ if (!isHttpUrl(link)) {
67
+ return "The link must be an http or https URL.";
68
+ }
69
+ return void 0;
70
+ }
71
+ export {
72
+ CLUSTER_TTL_BACKFILL_REASON,
73
+ CLUSTER_TTL_DURATIONS,
74
+ CLUSTER_TTL_DURATION_MS,
75
+ CLUSTER_TTL_FEATURE_FLAG,
76
+ CLUSTER_TTL_OPTIONS,
77
+ CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH,
78
+ CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH,
79
+ DEFAULT_CLUSTER_TTL_OPTION,
80
+ clusterTtlExpiresAt,
81
+ extendClusterTtl,
82
+ isClusterTtlDuration,
83
+ isClusterTtlOption,
84
+ isClusterTtlStage,
85
+ isHttpUrl,
86
+ isPastClusterTtl,
87
+ validateClusterTtlUnlimited
88
+ };
89
+ //# sourceMappingURL=ClusterTtl.type.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/types/ClusterTtl.type.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 { DAY, WEEK } from '../constants/duration.const.js'\n\n/**\n * CLUSTER_TTL_FEATURE_FLAG gates the TTL of a single cluster. It is not the\n * org-level `preprodClusterTtlHours` limit: both apply, and the earlier one\n * deletes the cluster.\n */\nexport const CLUSTER_TTL_FEATURE_FLAG = 'console.clusterTtl'\n\n/**\n * isClusterTtlStage tells if the cluster TTL can be on at the stage `stage`.\n * That is only dev and int. On prod, and on each other stage, the feature is\n * off for every reader, also when the flag is on.\n */\nexport const isClusterTtlStage = (stage: string | undefined): boolean =>\n stage === 'dev' || stage === 'int'\n\n/** CLUSTER_TTL_DURATIONS are the TTLs that end at a time. */\nexport const CLUSTER_TTL_DURATIONS = ['24h', '3d', '1w', '4w'] as const\n\nexport type ClusterTtlDuration = (typeof CLUSTER_TTL_DURATIONS)[number]\n\n/** CLUSTER_TTL_OPTIONS are the choices of the create form, in display order. */\nexport const CLUSTER_TTL_OPTIONS = [\n ...CLUSTER_TTL_DURATIONS,\n 'unlimited',\n] as const\n\nexport type ClusterTtlOption = (typeof CLUSTER_TTL_OPTIONS)[number]\n\n/** DEFAULT_CLUSTER_TTL_OPTION applies when an eligible create sends no `ttl`. */\nexport const DEFAULT_CLUSTER_TTL_OPTION: ClusterTtlOption = '24h'\n\nexport const CLUSTER_TTL_DURATION_MS: Readonly<\n Record<ClusterTtlDuration, number>\n> = {\n '24h': DAY,\n '3d': 3 * DAY,\n '1w': WEEK,\n '4w': 4 * WEEK,\n}\n\nexport const CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH = 500\nexport const CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH = 2048\n\n/**\n * CLUSTER_TTL_BACKFILL_REASON is the reason that the schema migration wrote\n * for each cluster that existed before the TTL. These rows are the only\n * unlimited TTLs with a reason and no link.\n */\nexport const CLUSTER_TTL_BACKFILL_REASON = 'Created before cluster TTL existed'\n\n/**\n * ClusterTtlDto is the TTL of one cluster. A response carries it only when\n * the reader may see the feature, thus an absent `ttl` means \"not shown\", not\n * \"unlimited\".\n */\nexport interface ClusterTtlDto {\n /** ISO-8601. Absent when the TTL is unlimited. */\n expiresAt?: string\n /**\n * Why the TTL is unlimited. Absent for a cluster that got no TTL, for\n * example one that a failover or a customer org created.\n */\n unlimitedReason?: string\n /** An http(s) URL, for example a GitHub issue. */\n unlimitedLink?: string\n}\n\n/** ClusterTtlUnlimited is the input that sets an unlimited TTL. */\nexport interface ClusterTtlUnlimited {\n reason: string\n link: string\n}\n\n/**\n * UpdateClusterTtlBody is the body of `PATCH api/admin/clusters/:clusterId/ttl`.\n * Give exactly one of the two fields.\n */\nexport type UpdateClusterTtlBody =\n | { extend: ClusterTtlDuration; unlimited?: never }\n | { unlimited: ClusterTtlUnlimited; extend?: never }\n\nexport const isClusterTtlDuration = (\n value: unknown,\n): value is ClusterTtlDuration =>\n (CLUSTER_TTL_DURATIONS as readonly unknown[]).includes(value)\n\nexport const isClusterTtlOption = (value: unknown): value is ClusterTtlOption =>\n (CLUSTER_TTL_OPTIONS as readonly unknown[]).includes(value)\n\n/** clusterTtlExpiresAt gives the expiry of a new TTL that starts at `now`. */\nexport function clusterTtlExpiresAt(\n duration: ClusterTtlDuration,\n now: Date = new Date(),\n): Date {\n return new Date(now.getTime() + CLUSTER_TTL_DURATION_MS[duration])\n}\n\n/**\n * extendClusterTtl gives the expiry after an extension. The extension starts\n * at the current expiry, or at `now` when that is in the past or when the TTL\n * is unlimited (`currentExpiresAt` undefined). Thus an extension never makes\n * the TTL shorter.\n */\nexport function extendClusterTtl({\n currentExpiresAt,\n duration,\n now = new Date(),\n}: {\n currentExpiresAt: Date | string | undefined\n duration: ClusterTtlDuration\n now?: Date\n}): Date {\n const currentMs =\n currentExpiresAt === undefined\n ? Number.NaN\n : new Date(currentExpiresAt).getTime()\n const startMs = Number.isNaN(currentMs)\n ? now.getTime()\n : Math.max(now.getTime(), currentMs)\n return new Date(startMs + CLUSTER_TTL_DURATION_MS[duration])\n}\n\n/**\n * isPastClusterTtl tells if the TTL of a cluster ran out. An unlimited TTL\n * (`expiresAt` undefined) or a value that is not a date never runs out.\n */\nexport function isPastClusterTtl({\n expiresAt,\n now = new Date(),\n}: {\n expiresAt: Date | string | undefined\n now?: Date\n}): boolean {\n if (expiresAt === undefined) return false\n const expiresMs = new Date(expiresAt).getTime()\n if (Number.isNaN(expiresMs)) return false\n return expiresMs < now.getTime()\n}\n\n/** isHttpUrl accepts an absolute `http:` or `https:` URL with a host. */\nexport function isHttpUrl(value: unknown): value is string {\n if (typeof value !== 'string' || /\\s/.test(value)) return false\n try {\n const url = new URL(value)\n return (\n (url.protocol === 'http:' || url.protocol === 'https:') &&\n url.hostname.length > 0\n )\n } catch {\n return false\n }\n}\n\n/**\n * validateClusterTtlUnlimited gives the first problem of an unlimited TTL\n * input as a message for the caller, or `undefined` when the input is valid.\n * The reason must hold text, and the link must be an http(s) URL. The length\n * limits apply to the trimmed values, because the server stores those.\n */\nexport function validateClusterTtlUnlimited(input: {\n reason?: unknown\n link?: unknown\n}): string | undefined {\n const reason =\n typeof input.reason === 'string' ? input.reason.trim() : undefined\n const link = typeof input.link === 'string' ? input.link.trim() : undefined\n if (!reason) {\n return 'An unlimited TTL needs a reason.'\n }\n if (reason.length > CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH) {\n return `The reason must not be longer than ${CLUSTER_TTL_UNLIMITED_REASON_MAX_LENGTH} characters.`\n }\n if (!link) {\n return 'An unlimited TTL needs a link.'\n }\n if (link.length > CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH) {\n return `The link must not be longer than ${CLUSTER_TTL_UNLIMITED_LINK_MAX_LENGTH} characters.`\n }\n if (!isHttpUrl(link)) {\n return 'The link must be an http or https URL.'\n }\n return undefined\n}\n"],
5
+ "mappings": "AAMA,SAAS,KAAK,YAAY;AAOnB,MAAM,2BAA2B;AAOjC,MAAM,oBAAoB,CAAC,UAChC,UAAU,SAAS,UAAU;AAGxB,MAAM,wBAAwB,CAAC,OAAO,MAAM,MAAM,IAAI;AAKtD,MAAM,sBAAsB;AAAA,EACjC,GAAG;AAAA,EACH;AACF;AAKO,MAAM,6BAA+C;AAErD,MAAM,0BAET;AAAA,EACF,OAAO;AAAA,EACP,MAAM,IAAI;AAAA,EACV,MAAM;AAAA,EACN,MAAM,IAAI;AACZ;AAEO,MAAM,0CAA0C;AAChD,MAAM,wCAAwC;AAO9C,MAAM,8BAA8B;AAiCpC,MAAM,uBAAuB,CAClC,UAEC,sBAA6C,SAAS,KAAK;AAEvD,MAAM,qBAAqB,CAAC,UAChC,oBAA2C,SAAS,KAAK;AAGrD,SAAS,oBACd,UACA,MAAY,oBAAI,KAAK,GACf;AACN,SAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,wBAAwB,QAAQ,CAAC;AACnE;AAQO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,MAAM,oBAAI,KAAK;AACjB,GAIS;AACP,QAAM,YACJ,qBAAqB,SACjB,OAAO,MACP,IAAI,KAAK,gBAAgB,EAAE,QAAQ;AACzC,QAAM,UAAU,OAAO,MAAM,SAAS,IAClC,IAAI,QAAQ,IACZ,KAAK,IAAI,IAAI,QAAQ,GAAG,SAAS;AACrC,SAAO,IAAI,KAAK,UAAU,wBAAwB,QAAQ,CAAC;AAC7D;AAMO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,MAAM,oBAAI,KAAK;AACjB,GAGY;AACV,MAAI,cAAc,OAAW,QAAO;AACpC,QAAM,YAAY,IAAI,KAAK,SAAS,EAAE,QAAQ;AAC9C,MAAI,OAAO,MAAM,SAAS,EAAG,QAAO;AACpC,SAAO,YAAY,IAAI,QAAQ;AACjC;AAGO,SAAS,UAAU,OAAiC;AACzD,MAAI,OAAO,UAAU,YAAY,KAAK,KAAK,KAAK,EAAG,QAAO;AAC1D,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,YACG,IAAI,aAAa,WAAW,IAAI,aAAa,aAC9C,IAAI,SAAS,SAAS;AAAA,EAE1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,4BAA4B,OAGrB;AACrB,QAAM,SACJ,OAAO,MAAM,WAAW,WAAW,MAAM,OAAO,KAAK,IAAI;AAC3D,QAAM,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,KAAK,KAAK,IAAI;AAClE,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AACA,MAAI,OAAO,SAAS,yCAAyC;AAC3D,WAAO,sCAAsC,uCAAuC;AAAA,EACtF;AACA,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AACA,MAAI,KAAK,SAAS,uCAAuC;AACvD,WAAO,oCAAoC,qCAAqC;AAAA,EAClF;AACA,MAAI,CAAC,UAAU,IAAI,GAAG;AACpB,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/ccma-shared-types",
3
- "version": "7.2.0",
3
+ "version": "7.4.0",
4
4
  "description": "Shared TypeScript types for Camunda Cloud Management Apps",
5
5
  "bugs": {
6
6
  "url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/constants/customer-hardware-sizing.const.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 { canonicalPlan } from '../utils/elasticsearchPlanPath.utils.js'\n\n/**\n * Customer hardware sizing: a cluster plan that a Camundi creates for an\n * organization of their own, with no admin permission. The approval use cases\n * `create-customer-hardware-sizing` and `remove-customer-hardware-sizing` run\n * on dev only, and the limits in this file keep one plan from asking for a\n * machine park.\n *\n * Every cap below is 10 times the largest value in the cluster plans of prod\n * (dump of 2026-10-01, 27 plans). The cap for storage is 10 times the largest\n * value of a plan that is NOT a load test: `Cisco XXL Year 1` and\n * `G3-M (adapted for Cisco load test)` ask for 10 TB on purpose, and a cap\n * from them would be 100 TB.\n */\n\n/** The stage where customer hardware sizing runs. */\nexport const isCustomerHardwareSizingStage = (stage: string): boolean =>\n stage === 'dev'\n\n/**\n * The most plans that one Camundi can own through customer hardware sizing.\n *\n * The count is the number of plans the Camundi created. A plan that the\n * Camundi deleted does not count.\n */\nexport const CUSTOMER_HARDWARE_SIZING_MAX_PLANS_PER_REQUESTER = 10\n\n/** The shortest and longest name of a customer hardware sizing plan. */\nexport const CUSTOMER_HARDWARE_SIZING_NAME_MIN_LENGTH = 3\nexport const CUSTOMER_HARDWARE_SIZING_NAME_MAX_LENGTH = 100\n\n/**\n * The longest plan, as JSON text. The largest plan of prod is about 6000\n * characters. The cap stops a request that carries a huge object.\n */\nexport const CUSTOMER_HARDWARE_SIZING_MAX_PLAN_JSON_LENGTH = 50_000\n\n/** One cap for a resource of one kind of component. `gi` is gibibytes. */\ninterface ResourceCaps {\n cpu: number\n memoryGi: number\n storageGi: number\n}\n\n/**\n * Caps for the requests and the limits of one component.\n *\n * `other` is every component that is not the Zeebe broker or Elasticsearch:\n * Operate, Tasklist, Optimize, the gateways, Identity and the others. The\n * largest of them in prod is Operate, with 6 CPU and 30 GiB.\n *\n * Prod maximum, before the factor of 10:\n * - broker: 16 CPU, 50 GiB, 500 GiB of storage (no load test)\n * - Elasticsearch: 30 CPU, 100 GiB, 3000 GiB of storage (no load test)\n * - other: 6 CPU, 30 GiB, no storage\n */\nexport const CUSTOMER_HARDWARE_SIZING_RESOURCE_CAPS = {\n broker: { cpu: 160, memoryGi: 500, storageGi: 5000 },\n elasticsearch: { cpu: 300, memoryGi: 1000, storageGi: 30_000 },\n other: { cpu: 60, memoryGi: 300, storageGi: 5000 },\n} as const satisfies Record<string, ResourceCaps>\n\n/** Caps for the count fields of a plan. Prod maximum is in the comments. */\nexport const CUSTOMER_HARDWARE_SIZING_COUNT_CAPS = {\n // 7\n clusterSize: 70,\n // 50\n partitionsCount: 500,\n // 3\n replicationFactor: 30,\n // 5\n nodesCount: 50,\n // `replicas` of any component. 3.\n replicas: 30,\n} as const\n\n/** `zeebe.broker.storage.autoResizing.increase`. Prod maximum is 18 GiB. */\nexport const CUSTOMER_HARDWARE_SIZING_MAX_AUTO_RESIZE_INCREASE_GI = 180\n\n/**\n * The caps over the whole broker set, which the caps for one broker do not\n * give. A plan with 70 brokers of 160 CPU passes every cap above, and it asks\n * for 11200 CPU. The prod maximum is 84 CPU (7 brokers, `Cisco XXL Year 1`),\n * 250 GiB of memory (5 brokers, `GLS HA`) and 2500 GiB of storage (5 brokers,\n * `GLS HA`, no load test).\n */\nexport const CUSTOMER_HARDWARE_SIZING_BROKER_TOTAL_CAPS = {\n cpu: 840,\n memoryGi: 2500,\n storageGi: 25_000,\n} as const\n\n/**\n * The same, for the whole Elasticsearch node set. Ten times the prod maximum:\n * 90 CPU and 300 GiB of memory (3 nodes, `GLS HA`), and 10000 GiB of storage\n * (5 nodes, `CCL G3L HA`). The storage of one node is a claim of one volume,\n * so the total is the claim times the number of nodes.\n */\nexport const CUSTOMER_HARDWARE_SIZING_ELASTICSEARCH_TOTAL_CAPS = {\n cpu: 900,\n memoryGi: 3000,\n storageGi: 100_000,\n} as const\n\n/**\n * The same, for the pods of one component that has `replicas`, such as\n * Operate or the gateway. Ten times the prod maximum: 12 CPU (the gateway, 3\n * replicas of 4 CPU) and 60 GiB of memory (Operate, 2 replicas of 30 GiB). No\n * component of prod has storage in its pod resources, so the storage total is\n * the cap for one pod, 10 times over. The check counts a storage request of a\n * pod in `resources` against it.\n */\nexport const CUSTOMER_HARDWARE_SIZING_REPLICA_SET_TOTAL_CAPS = {\n cpu: 120,\n memoryGi: 600,\n storageGi: 50_000,\n} as const\n\nconst GIBIBYTE = 1024 ** 3\n\nconst QUANTITY_MULTIPLIERS: Record<string, number> = {\n '': 1,\n m: 1e-3,\n k: 1e3,\n M: 1e6,\n G: 1e9,\n T: 1e12,\n P: 1e15,\n Ki: 1024,\n Mi: 1024 ** 2,\n Gi: 1024 ** 3,\n Ti: 1024 ** 4,\n Pi: 1024 ** 5,\n}\n\nconst QUANTITY_PATTERN = /^([0-9]+(?:\\.[0-9]+)?)(m|k|M|G|T|P|Ki|Mi|Gi|Ti|Pi)?$/u\n\n/**\n * Reads a Kubernetes quantity such as `500m`, `2`, `4Gi` or `1G`, and returns\n * its value in base units: cores for a CPU, bytes for memory and storage.\n *\n * Returns `undefined` for a text that this function cannot read, so the\n * caller can refuse it. The exponent form (`1e3`) is not read on purpose.\n */\nexport function parseKubernetesQuantity(value: unknown): number | undefined {\n if (typeof value === 'number') {\n return Number.isFinite(value) && value >= 0 ? value : undefined\n }\n if (typeof value !== 'string') {\n return undefined\n }\n const match = QUANTITY_PATTERN.exec(value)\n if (!match) {\n return undefined\n }\n return Number(match[1]) * (QUANTITY_MULTIPLIERS[match[2] ?? ''] ?? 1)\n}\n\ntype ComponentKind = keyof typeof CUSTOMER_HARDWARE_SIZING_RESOURCE_CAPS\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nfunction componentOf(path: string[]): ComponentKind {\n if (path[0] === 'zeebe' && path[1] === 'broker') {\n return 'broker'\n }\n if (path[0] === 'elasticsearch') {\n return 'elasticsearch'\n }\n return 'other'\n}\n\nfunction checkNumberCap(\n violations: string[],\n path: string[],\n value: unknown,\n cap: number,\n): void {\n const name = path.join('.')\n const parsed = parseKubernetesQuantity(value)\n if (parsed === undefined) {\n violations.push(`${name} is not a number that can be checked`)\n } else if (parsed > cap) {\n violations.push(`${name} is ${parsed}, and the most is ${cap}`)\n }\n}\n\nfunction checkResource(\n violations: string[],\n path: string[],\n value: unknown,\n kind: 'cpu' | 'memory' | 'storage',\n): void {\n const name = path.join('.')\n const caps = CUSTOMER_HARDWARE_SIZING_RESOURCE_CAPS[componentOf(path)]\n const parsed = parseKubernetesQuantity(value)\n if (parsed === undefined) {\n violations.push(`${name} is not a quantity that can be checked`)\n return\n }\n if (kind === 'cpu') {\n if (parsed > caps.cpu) {\n violations.push(`${name} is ${parsed} CPU, and the most is ${caps.cpu}`)\n }\n return\n }\n const cap = kind === 'memory' ? caps.memoryGi : caps.storageGi\n if (parsed / GIBIBYTE > cap) {\n violations.push(`${name} is over ${cap} GiB`)\n }\n}\n\nfunction walk(violations: string[], node: unknown, path: string[]): void {\n if (!isRecord(node)) {\n return\n }\n for (const [key, value] of Object.entries(node)) {\n // The shape check of console treats `null` like an absent key, so this\n // check does too. A `null` has nothing to cap.\n if (value === null || value === undefined) {\n continue\n }\n const next = [...path, key]\n const parent = path.at(-1)\n const grandParent = path.at(-2)\n if (\n Object.hasOwn(CUSTOMER_HARDWARE_SIZING_COUNT_CAPS, key) &&\n !isRecord(value)\n ) {\n checkNumberCap(\n violations,\n next,\n value,\n CUSTOMER_HARDWARE_SIZING_COUNT_CAPS[\n key as keyof typeof CUSTOMER_HARDWARE_SIZING_COUNT_CAPS\n ],\n )\n } else if (\n (parent === 'limits' || parent === 'requests') &&\n grandParent === 'resources' &&\n (key === 'cpu' || key === 'memory' || key === 'storage')\n ) {\n checkResource(violations, next, value, key)\n } else if (\n (parent === 'limits' || parent === 'requests') &&\n grandParent === 'resources'\n ) {\n // Any other resource name (`ephemeral-storage`, a GPU) has no cap.\n violations.push(`${next.join('.')} is not a resource that has a cap`)\n } else if (key === 'persistentStorageCapacity') {\n // The legacy size of the volume. The operator can still read it.\n checkResource(violations, next, value, 'storage')\n } else if (parent === 'autoResizing' && key === 'increase') {\n // Only the step is capped. The disk itself stops at\n // `storage.resources.limits.storage`: `Plan.type.ts` calls it \"the upper\n // limit the pvc autoscaler can scale up to\", and the operator hands it\n // to the autoscaler as the storage limit of the PVC. The caps for that\n // field, for one node and for all nodes together, bound the size that a\n // disk can reach. `threshold` has no effect on that size.\n const parsed = parseKubernetesQuantity(value)\n if (\n parsed === undefined ||\n parsed / GIBIBYTE > CUSTOMER_HARDWARE_SIZING_MAX_AUTO_RESIZE_INCREASE_GI\n ) {\n violations.push(\n `${next.join('.')} is not a quantity of at most ${CUSTOMER_HARDWARE_SIZING_MAX_AUTO_RESIZE_INCREASE_GI} GiB`,\n )\n }\n } else {\n walk(violations, value, next)\n }\n }\n}\n\nconst larger = (\n block: Record<string, unknown> | undefined,\n key: string,\n): number | undefined => {\n const values = [block?.limits, block?.requests].map((entry) =>\n isRecord(entry) ? parseKubernetesQuantity(entry[key]) : undefined,\n )\n const known = values.filter((value) => value !== undefined)\n return known.length > 0 ? Math.max(...known) : undefined\n}\n\n/**\n * Checks the whole node set of one component against its totals. The caps for\n * one node do not bound `count` nodes together.\n *\n * Kubernetes schedules on `requests`, and a plan can set only `requests`. The\n * total reads the larger of the two, so that neither one skips it. The legacy\n * `persistentStorageCapacity` counts as storage too.\n */\nfunction checkSetTotals(\n violations: string[],\n who: string,\n count: unknown,\n parts: {\n resources: unknown\n storageResources: unknown\n legacyStorage: unknown\n },\n caps: { cpu: number; memoryGi: number; storageGi: number },\n): void {\n const nodes = parseKubernetesQuantity(count)\n if (nodes === undefined) {\n return\n }\n const resources = isRecord(parts.resources) ? parts.resources : undefined\n const storageResources = isRecord(parts.storageResources)\n ? parts.storageResources\n : undefined\n const cpu = larger(resources, 'cpu')\n if (cpu !== undefined && cpu * nodes > caps.cpu) {\n violations.push(`${who} together ask for more than ${caps.cpu} CPU`)\n }\n const memory = larger(resources, 'memory')\n if (memory !== undefined && (memory * nodes) / GIBIBYTE > caps.memoryGi) {\n violations.push(\n `${who} together ask for more than ${caps.memoryGi} GiB of memory`,\n )\n }\n const disks = [\n larger(storageResources, 'storage'),\n parseKubernetesQuantity(parts.legacyStorage),\n ].filter((value) => value !== undefined)\n if (\n disks.length > 0 &&\n (Math.max(...disks) * nodes) / GIBIBYTE > caps.storageGi\n ) {\n violations.push(\n `${who} together ask for more than ${caps.storageGi} GiB of storage`,\n )\n }\n}\n\n/**\n * Walks the plan for every component that has `replicas`, and checks its pods\n * together. The pod resources are in `resources`, or in `backend.resources`.\n * The brokers (`clusterSize`) and Elasticsearch (`nodesCount`) have their own\n * count and their own totals.\n */\nfunction checkReplicaSets(\n violations: string[],\n node: unknown,\n path: string[],\n): void {\n if (!isRecord(node)) {\n return\n }\n if (\n node.replicas !== undefined &&\n node.replicas !== null &&\n !isRecord(node.replicas)\n ) {\n const backend = isRecord(node.backend) ? node.backend : undefined\n const podResources = node.resources ?? backend?.resources\n checkSetTotals(\n violations,\n `the replicas of ${path.join('.') || 'the plan'}`,\n node.replicas,\n {\n resources: podResources,\n // The same block: a pod can ask for storage in its resources too.\n storageResources: podResources,\n legacyStorage: undefined,\n },\n CUSTOMER_HARDWARE_SIZING_REPLICA_SET_TOTAL_CAPS,\n )\n }\n for (const [key, value] of Object.entries(node)) {\n checkReplicaSets(violations, value, [...path, key])\n }\n}\n\nfunction checkNodeSetTotals(\n violations: string[],\n plan: Record<string, unknown>,\n): void {\n const zeebe = isRecord(plan.zeebe) ? plan.zeebe : undefined\n const broker = isRecord(zeebe?.broker) ? zeebe.broker : undefined\n if (broker) {\n const storage = isRecord(broker.storage) ? broker.storage : undefined\n checkSetTotals(\n violations,\n 'the brokers',\n broker.clusterSize,\n {\n resources: broker.resources,\n storageResources: storage?.resources,\n legacyStorage: broker.persistentStorageCapacity,\n },\n CUSTOMER_HARDWARE_SIZING_BROKER_TOTAL_CAPS,\n )\n }\n checkReplicaSets(violations, plan, [])\n const elasticsearch = isRecord(plan.elasticsearch)\n ? plan.elasticsearch\n : undefined\n if (elasticsearch) {\n const config = isRecord(elasticsearch.config)\n ? elasticsearch.config\n : undefined\n const storage = isRecord(config?.storage) ? config.storage : undefined\n checkSetTotals(\n violations,\n 'the Elasticsearch nodes',\n config?.nodesCount,\n {\n resources: elasticsearch.resources,\n storageResources: storage?.resources,\n legacyStorage: elasticsearch.persistentStorageCapacity,\n },\n CUSTOMER_HARDWARE_SIZING_ELASTICSEARCH_TOTAL_CAPS,\n )\n }\n}\n\n/**\n * Checks a cluster plan against the caps of customer hardware sizing.\n *\n * Returns the list of violations, and an empty list when the plan is within\n * the caps. It checks the size only. The shape of the plan is the job of the\n * plan validator of console-backend.\n *\n * A value that this function cannot read is a violation too: a cap that the\n * plan can skip with a strange spelling is no cap.\n */\nexport function checkCustomerHardwareSizing(plan: unknown): string[] {\n if (!isRecord(plan)) {\n return ['the plan is not an object']\n }\n if (\n JSON.stringify(plan).length > CUSTOMER_HARDWARE_SIZING_MAX_PLAN_JSON_LENGTH\n ) {\n return [\n `the plan is longer than ${CUSTOMER_HARDWARE_SIZING_MAX_PLAN_JSON_LENGTH} characters`,\n ]\n }\n // `operate.elasticsearch` is the old place of the Elasticsearch block. The\n // caps for Elasticsearch must read it as one place, so the plan is moved\n // to the canonical form first.\n const canonical = canonicalPlan(plan)\n const violations: string[] = []\n walk(violations, canonical, [])\n checkNodeSetTotals(violations, canonical)\n return violations\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }