Skip to content

Commit fc489f5

Browse files
authored
Merge pull request #13 from javrrr/override-makeRequired-and-quirks
overrides: add makeRequired DSL + 6 input-shape quirks + 5 service-method JSDocs
2 parents 02973d5 + 99c046b commit fc489f5

7 files changed

Lines changed: 253 additions & 28 deletions

File tree

scripts/generate-types.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ interface SchemaOverride {
3535
note: string;
3636
/** Fields to relax from required to optional (spec marks them required but the API doesn't). */
3737
makeOptional?: string[];
38+
/** Fields to promote from optional to required (spec marks them optional but the API requires them). */
39+
makeRequired?: string[];
3840
/** Fields whose type should be replaced. Use type names from schemas.ts (not Schemas["..."]). */
3941
fieldTypes?: Record<string, string>;
4042
/** Optional fields to add when runtime responses include undocumented properties. */
@@ -56,7 +58,7 @@ interface SchemaOverride {
5658
*/
5759
const SCHEMA_OVERRIDES: Record<string, SchemaOverride> = {
5860
DataStreamInputRepresentation: {
59-
note: "Spec bugs: dataLakeObjectInfo should accept single or array; mappings and sourceFields are not required for all connector types",
61+
note: "Spec bugs: dataLakeObjectInfo should accept single or array; mappings and sourceFields are not required for all connector types. Routing note: dataAccessMode='Direct_Access' is required for federated/BYOL connectors (Snowflake, Databricks, BigQuery, Iceberg) — without it the server returns `400 INTERNAL_ERROR: Unable to post Data Stream: DATA_CONNECTORS is not supported` even when the connector is GA. Direct_Access streams must also OMIT the top-level `datasource` field (otherwise: `DataSource name should be empty for External data streams`); the connection binding is established via connectorInfo.connectorDetails.name instead.",
6062
makeOptional: ["mappings", "sourceFields"],
6163
fieldTypes: {
6264
dataLakeObjectInfo: "DataLakeObjectInputRepresentation | DataLakeObjectInputRepresentation[]",
@@ -81,7 +83,7 @@ const SCHEMA_OVERRIDES: Record<string, SchemaOverride> = {
8183
},
8284
},
8385
SemanticSearchInputRepresentation: {
84-
note: "Spec bugs: processingType missing from spec but required by API; attachment/transcribe fields are only required for document/PDF search indexes, not structured DMO search",
86+
note: "Spec bugs: processingType missing from spec but required by API; attachment/transcribe fields are only required for document/PDF search indexes, not structured DMO search. Input rejects output-only display-name fields (sourceDmoName, sourceDmoFieldName, relatedDmoName, relatedDmoFieldName) that GET responses include — round-tripping a GET shape into a POST returns `500 UNKNOWN_EXCEPTION` with no diagnostic body. Pass developer-name fields only.",
8587
makeOptional: [
8688
"attachmentDmoDeveloperName",
8789
"transcribeDmoDeveloperName",
@@ -95,6 +97,21 @@ const SCHEMA_OVERRIDES: Record<string, SchemaOverride> = {
9597
processingType: '"NEAR_REALTIME" | "REALTIME"',
9698
},
9799
},
100+
ConnectionSchemaFieldInputRepresentation: {
101+
note: "Server NPEs (`Cannot invoke java.lang.CharSequence.length() because this.text is null`) when `label` is omitted from any field. Spec marks it optional but the upsert handler dereferences it unconditionally.",
102+
makeRequired: ["label"],
103+
},
104+
DataStreamFieldMappingInputRepresentation: {
105+
note: "Asymmetric input/output: POST input uses `sourceFieldLabel`, but GET responses echo `sourceFieldName` for the same field. Round-tripping GET→POST without renaming fails with JSON_PARSER_ERROR. Also: targetFieldReturntype is required by the create handler — when omitted, the mapping is silently dropped from the saved data stream with no error.",
106+
makeRequired: ["targetFieldReturntype"],
107+
},
108+
DataStreamSourceFieldInputRepresentation: {
109+
note: "Asymmetric input/output: POST input uses `dataType` (camelCase), but GET responses echo `datatype` (lowercase) for the same field. Round-tripping GET→POST without renaming fails with `JSON_PARSER_ERROR: Unrecognized field 'datatype'`.",
110+
},
111+
VectorEmbeddingInputRepresentation: {
112+
note: "Server NPEs when vectorEmbeddingRelatedFields is omitted, empty, or null. The list must be non-empty (typical minimum: a single entry pointing at the source DMO's primary key). Spec marks it optional.",
113+
makeRequired: ["vectorEmbeddingRelatedFields"],
114+
},
98115
};
99116

100117
// ────────────────────────────────────────────────────────────────────────────
@@ -725,13 +742,23 @@ async function main() {
725742

726743
// Step 4: Flatten schemas, apply overrides, and generate discriminated unions
727744

728-
// Validate overrides reference real schemas
729-
for (const name of Object.keys(SCHEMA_OVERRIDES)) {
745+
// Validate overrides reference real schemas, and reject contradictions
746+
// between makeOptional/makeRequired (a single field appearing in both is
747+
// a config error and should fail loudly rather than silently last-wins).
748+
for (const [name, override] of Object.entries(SCHEMA_OVERRIDES)) {
730749
if (!schemas[name]) {
731750
throw new Error(
732751
`SCHEMA_OVERRIDES: schema "${name}" not found in spec — remove stale override`,
733752
);
734753
}
754+
const optional = new Set(override.makeOptional ?? []);
755+
for (const field of override.makeRequired ?? []) {
756+
if (optional.has(field)) {
757+
throw new Error(
758+
`SCHEMA_OVERRIDES.${name}: field "${field}" is in both makeOptional and makeRequired`,
759+
);
760+
}
761+
}
735762
}
736763

737764
// Flatten allOf schemas + schemas with overrides
@@ -741,13 +768,30 @@ async function main() {
741768
const props = collectFlatProperties(name, schemas, !!override);
742769
if (!props) continue;
743770

744-
// Apply overrides
771+
// Apply overrides. Each list referencing an existing-field name (the
772+
// make* and fieldTypes families) is validated against the flattened
773+
// property set — a stale field name fails generation, mirroring the
774+
// schema-name validation above. The add*Fields families are skipped
775+
// here because they're explicitly for fields NOT yet present.
745776
if (override) {
777+
const validateField = (kind: string, field: string): void => {
778+
if (!props[field]) {
779+
throw new Error(
780+
`SCHEMA_OVERRIDES.${name}.${kind}: field "${field}" not found on schema — remove stale override`,
781+
);
782+
}
783+
};
746784
for (const field of override.makeOptional ?? []) {
747-
if (props[field]) props[field].required = false;
785+
validateField("makeOptional", field);
786+
props[field]!.required = false;
787+
}
788+
for (const field of override.makeRequired ?? []) {
789+
validateField("makeRequired", field);
790+
props[field]!.required = true;
748791
}
749792
for (const [field, type] of Object.entries(override.fieldTypes ?? {})) {
750-
if (props[field]) props[field].tsType = type;
793+
validateField("fieldTypes", field);
794+
props[field]!.tsType = type;
751795
}
752796
for (const [field, type] of Object.entries(override.addOptionalFields ?? {})) {
753797
if (!props[field]) {

src/generated/openapi.d.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13215,6 +13215,13 @@ export type components = {
1321513215
* **Available Version:** 60.0
1321613216
*/
1321713217
activationTargetSubjectConfig?: components["schemas"]["ActivationTargetSubjectConfigInputRepresentation"];
13218+
/**
13219+
* @description Type of activation. If unspecified, defaults to `Segment`.
13220+
*
13221+
* **Available Version:** 66.0
13222+
* @enum {string}
13223+
*/
13224+
activationType?: "ApiTriggered" | "Segment";
1321813225
/**
1321913226
* @description Limiting expression configuration for the activation.
1322013227
*
@@ -13283,7 +13290,7 @@ export type components = {
1328313290
*/
1328413291
limitValue?: number;
1328513292
/**
13286-
* @description Segment ID of the segment the activation needs to be created against. Either marketSegmentId or segmentApiName must be present.
13293+
* @description Segment ID of the segment the activation needs to be created against. Either marketSegmentId or segmentApiName must be present. Exclude this property for `ApiTriggered` activation types.
1328713294
*
1328813295
* **Available Version:** 60.0
1328913296
*/
@@ -13307,7 +13314,7 @@ export type components = {
1330713314
*/
1330813315
relatedDmoFiltersConfig?: components["schemas"]["DMOFilterConfigInputRepresentation"][];
1330913316
/**
13310-
* @description Developer name of the segment. Either marketSegmentId or segmentApiName must be present.
13317+
* @description Developer name of the segment. Either marketSegmentId or segmentApiName must be present. Exclude this property for `ApiTriggered` activation types.
1331113318
*
1331213319
* **Available Version:** 60.0
1331313320
*/
@@ -13324,6 +13331,12 @@ export type components = {
1332413331
* **Available Version:** 60.0
1332513332
*/
1332613333
shouldExcludeUpdates?: boolean;
13334+
/**
13335+
* @description Developer name of the source DMO. Required for `ApiTriggered` activation types. Exclude this property for `Segment` activation types.
13336+
*
13337+
* **Available Version:** 66.0
13338+
*/
13339+
sourceDmoName?: string;
1332713340
/**
1332813341
* @description Configuration of static data, which adds metadata or campaign details in the output. For example, `campaignId` or `campaignName`.
1332913342
*
@@ -28525,6 +28538,15 @@ export type components = {
2852528538
* **Available Version:** 60.0
2852628539
*/
2852728540
activationTargetSubjectConfig: components["schemas"]["ActivationTargetSubjectRepresentation"];
28541+
/**
28542+
* @description Type of activation.
28543+
*
28544+
* **Filter Group:** Small
28545+
*
28546+
* **Available Version:** 66.0
28547+
* @enum {string}
28548+
*/
28549+
activationType?: "ApiTriggered" | "Segment";
2852828550
/**
2852928551
* @description Limiting expression configuration for the activation.
2853028552
*
@@ -28696,7 +28718,7 @@ export type components = {
2869628718
*/
2869728719
limitValue?: number;
2869828720
/**
28699-
* @description Segment ID of the activation.
28721+
* @description Segment ID of the activation. Returned for `Segment` type activations.
2870028722
*
2870128723
* **Filter Group:** Small
2870228724
*
@@ -28737,15 +28759,15 @@ export type components = {
2873728759
*/
2873828760
relatedDmoFiltersConfig?: components["schemas"]["DmoFiltersConfigRepresentation"];
2873928761
/**
28740-
* @description Segment API name.
28762+
* @description Segment API name. Returned for `Segment` type activations.
2874128763
*
2874228764
* **Filter Group:** Small
2874328765
*
2874428766
* **Available Version:** 60.0
2874528767
*/
2874628768
segmentApiName?: string;
2874728769
/**
28748-
* @description Segment ID of the activation.
28770+
* @description Segment ID of the activation. Returned for `Segment` type activations.
2874928771
*
2875028772
* **Filter Group:** Small
2875128773
*
@@ -28768,6 +28790,14 @@ export type components = {
2876828790
* **Available Version:** 60.0
2876928791
*/
2877028792
shouldExcludeUpdates?: boolean;
28793+
/**
28794+
* @description Developer name of the source DMO.
28795+
*
28796+
* **Filter Group:** Small
28797+
*
28798+
* **Available Version:** 66.0
28799+
*/
28800+
sourceDmoName?: string;
2877128801
/**
2877228802
* @description Static data configuration for the activation.
2877328803
*

src/generated/openapi.yaml

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8746,6 +8746,15 @@ components:
87468746
**Available Version:** 60.0
87478747
allOf:
87488748
- $ref: "#/components/schemas/ActivationTargetSubjectConfigInputRepresentation"
8749+
activationType:
8750+
description: |
8751+
Type of activation. If unspecified, defaults to `Segment`.
8752+
8753+
**Available Version:** 66.0
8754+
type: string
8755+
enum:
8756+
- ApiTriggered
8757+
- Segment
87498758
attributeLimitingExpressionConfig:
87508759
description: |
87518760
Limiting expression configuration for the activation.
@@ -8828,7 +8837,7 @@ components:
88288837
type: integer
88298838
marketSegmentId:
88308839
description: |
8831-
Segment ID of the segment the activation needs to be created against. Either marketSegmentId or segmentApiName must be present.
8840+
Segment ID of the segment the activation needs to be created against. Either marketSegmentId or segmentApiName must be present. Exclude this property for `ApiTriggered` activation types.
88328841

88338842
**Available Version:** 60.0
88348843
type: string
@@ -8854,7 +8863,7 @@ components:
88548863
$ref: "#/components/schemas/DMOFilterConfigInputRepresentation"
88558864
segmentApiName:
88568865
description: |
8857-
Developer name of the segment. Either marketSegmentId or segmentApiName must be present.
8866+
Developer name of the segment. Either marketSegmentId or segmentApiName must be present. Exclude this property for `ApiTriggered` activation types.
88588867

88598868
**Available Version:** 60.0
88608869
type: string
@@ -8870,6 +8879,12 @@ components:
88708879

88718880
**Available Version:** 60.0
88728881
type: boolean
8882+
sourceDmoName:
8883+
description: |
8884+
Developer name of the source DMO. Required for `ApiTriggered` activation types. Exclude this property for `Segment` activation types.
8885+
8886+
**Available Version:** 66.0
8887+
type: string
88738888
staticDataConfig:
88748889
description: |
88758890
Configuration of static data, which adds metadata or campaign details in the output. For example, `campaignId` or `campaignName`.
@@ -27004,6 +27019,17 @@ components:
2700427019
**Available Version:** 60.0
2700527020
allOf:
2700627021
- $ref: "#/components/schemas/ActivationTargetSubjectRepresentation"
27022+
activationType:
27023+
description: |
27024+
Type of activation.
27025+
27026+
**Filter Group:** Small
27027+
27028+
**Available Version:** 66.0
27029+
type: string
27030+
enum:
27031+
- ApiTriggered
27032+
- Segment
2700727033
attributeLimitingExpressionConfig:
2700827034
description: |
2700927035
Limiting expression configuration for the activation.
@@ -27193,7 +27219,7 @@ components:
2719327219
type: integer
2719427220
marketSegmentId:
2719527221
description: |
27196-
Segment ID of the activation.
27222+
Segment ID of the activation. Returned for `Segment` type activations.
2719727223

2719827224
**Filter Group:** Small
2719927225

@@ -27238,15 +27264,15 @@ components:
2723827264
- $ref: "#/components/schemas/DmoFiltersConfigRepresentation"
2723927265
segmentApiName:
2724027266
description: |
27241-
Segment API name.
27267+
Segment API name. Returned for `Segment` type activations.
2724227268

2724327269
**Filter Group:** Small
2724427270

2724527271
**Available Version:** 60.0
2724627272
type: string
2724727273
segmentId:
2724827274
description: |
27249-
Segment ID of the activation.
27275+
Segment ID of the activation. Returned for `Segment` type activations.
2725027276

2725127277
**Filter Group:** Small
2725227278

@@ -27268,6 +27294,14 @@ components:
2726827294

2726927295
**Available Version:** 60.0
2727027296
type: boolean
27297+
sourceDmoName:
27298+
description: |
27299+
Developer name of the source DMO.
27300+
27301+
**Filter Group:** Small
27302+
27303+
**Available Version:** 66.0
27304+
type: string
2727127305
staticDataConfig:
2727227306
description: |
2727327307
Static data configuration for the activation.
Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,43 @@
1-
export { CalculatedInsightsServiceBase as CalculatedInsightsService } from "../generated/services/calculated-insights.base.js";
1+
import { CalculatedInsightsServiceBase } from "../generated/services/calculated-insights.base.js";
2+
import type { RequestOptions } from "../core/types.js";
3+
import type {
4+
CdpCalculatedInsightInputRepresentation,
5+
CdpCalculatedInsightRepresentation,
6+
} from "../schemas.js";
7+
8+
export class CalculatedInsightsService extends CalculatedInsightsServiceBase {
9+
/**
10+
* POST /ssot/calculated-insights — Create calculated insight.
11+
*
12+
* Server-side timing: this endpoint runs SQL validation, schedule
13+
* registration, and dependency-graph updates synchronously, and
14+
* commonly takes 30–60 seconds for non-trivial expressions. The
15+
* SDK's default 30s HTTP timeout aborts before the response arrives;
16+
* pass `{ timeout: 120_000 }` (or longer) via `options` for any CI
17+
* whose `expression` references multiple DMOs or large fact tables.
18+
*/
19+
override async create(
20+
body: CdpCalculatedInsightInputRepresentation,
21+
options?: RequestOptions,
22+
): Promise<CdpCalculatedInsightRepresentation> {
23+
return super.create(body, options);
24+
}
25+
26+
/**
27+
* DELETE /ssot/calculated-insights/{apiName} — Delete calculated insight.
28+
*
29+
* Asynchronous teardown: the response is 204 immediately, but the
30+
* record sits at `status = "DELETING"` for several minutes before
31+
* becoming truly gone. During that window:
32+
* - GET still returns the CI (with the DELETING status)
33+
* - A re-create attempt with the same apiName 400s with
34+
* DUPLICATES_DETECTED until teardown completes
35+
*
36+
* Callers performing a delete-then-recreate flow should poll the
37+
* CI's `status` until either (a) GET returns 404 or (b) the status
38+
* is no longer `DELETING`, before re-creating with the same apiName.
39+
*/
40+
override async delete(apiName: string, options?: RequestOptions): Promise<void> {
41+
return super.delete(apiName, options);
42+
}
43+
}

0 commit comments

Comments
 (0)