@rippling/rippling-sdk 0.2.0-alpha.32 → 0.2.0-alpha.34
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/client.d.mts +8 -2
- package/client.d.mts.map +1 -1
- package/client.d.ts +8 -2
- package/client.d.ts.map +1 -1
- package/client.js +17 -0
- package/client.js.map +1 -1
- package/client.mjs +17 -0
- package/client.mjs.map +1 -1
- package/internal/types.d.mts +6 -6
- package/internal/types.d.mts.map +1 -1
- package/internal/types.d.ts +6 -6
- package/internal/types.d.ts.map +1 -1
- package/lib/manifest/_helpers.d.mts +91 -0
- package/lib/manifest/_helpers.d.mts.map +1 -0
- package/lib/manifest/_helpers.d.ts +91 -0
- package/lib/manifest/_helpers.d.ts.map +1 -0
- package/lib/manifest/_helpers.js +65 -0
- package/lib/manifest/_helpers.js.map +1 -0
- package/lib/manifest/_helpers.mjs +61 -0
- package/lib/manifest/_helpers.mjs.map +1 -0
- package/lib/manifest/app.d.mts +45 -0
- package/lib/manifest/app.d.mts.map +1 -0
- package/lib/manifest/app.d.ts +45 -0
- package/lib/manifest/app.d.ts.map +1 -0
- package/lib/manifest/app.js +74 -0
- package/lib/manifest/app.js.map +1 -0
- package/lib/manifest/app.mjs +70 -0
- package/lib/manifest/app.mjs.map +1 -0
- package/lib/manifest/category.d.mts +23 -0
- package/lib/manifest/category.d.mts.map +1 -0
- package/lib/manifest/category.d.ts +23 -0
- package/lib/manifest/category.d.ts.map +1 -0
- package/lib/manifest/category.js +37 -0
- package/lib/manifest/category.js.map +1 -0
- package/lib/manifest/category.mjs +33 -0
- package/lib/manifest/category.mjs.map +1 -0
- package/lib/manifest/custom-object-field-section.d.mts +21 -0
- package/lib/manifest/custom-object-field-section.d.mts.map +1 -0
- package/lib/manifest/custom-object-field-section.d.ts +21 -0
- package/lib/manifest/custom-object-field-section.d.ts.map +1 -0
- package/lib/manifest/custom-object-field-section.js +42 -0
- package/lib/manifest/custom-object-field-section.js.map +1 -0
- package/lib/manifest/custom-object-field-section.mjs +38 -0
- package/lib/manifest/custom-object-field-section.mjs.map +1 -0
- package/lib/manifest/custom-object-page-layout.d.mts +158 -0
- package/lib/manifest/custom-object-page-layout.d.mts.map +1 -0
- package/lib/manifest/custom-object-page-layout.d.ts +158 -0
- package/lib/manifest/custom-object-page-layout.d.ts.map +1 -0
- package/lib/manifest/custom-object-page-layout.js +112 -0
- package/lib/manifest/custom-object-page-layout.js.map +1 -0
- package/lib/manifest/custom-object-page-layout.mjs +108 -0
- package/lib/manifest/custom-object-page-layout.mjs.map +1 -0
- package/lib/manifest/custom-object.d.mts +52 -0
- package/lib/manifest/custom-object.d.mts.map +1 -0
- package/lib/manifest/custom-object.d.ts +52 -0
- package/lib/manifest/custom-object.d.ts.map +1 -0
- package/lib/manifest/custom-object.js +219 -0
- package/lib/manifest/custom-object.js.map +1 -0
- package/lib/manifest/custom-object.mjs +215 -0
- package/lib/manifest/custom-object.mjs.map +1 -0
- package/lib/manifest/field.d.mts +276 -0
- package/lib/manifest/field.d.mts.map +1 -0
- package/lib/manifest/field.d.ts +276 -0
- package/lib/manifest/field.d.ts.map +1 -0
- package/lib/manifest/field.js +418 -0
- package/lib/manifest/field.js.map +1 -0
- package/lib/manifest/field.mjs +395 -0
- package/lib/manifest/field.mjs.map +1 -0
- package/lib/manifest/index.d.mts +15 -0
- package/lib/manifest/index.d.mts.map +1 -0
- package/lib/manifest/index.d.ts +15 -0
- package/lib/manifest/index.d.ts.map +1 -0
- package/lib/manifest/index.js +53 -0
- package/lib/manifest/index.js.map +1 -0
- package/lib/manifest/index.mjs +18 -0
- package/lib/manifest/index.mjs.map +1 -0
- package/lib/manifest/list-view.d.mts +47 -0
- package/lib/manifest/list-view.d.mts.map +1 -0
- package/lib/manifest/list-view.d.ts +47 -0
- package/lib/manifest/list-view.d.ts.map +1 -0
- package/lib/manifest/list-view.js +73 -0
- package/lib/manifest/list-view.js.map +1 -0
- package/lib/manifest/list-view.mjs +69 -0
- package/lib/manifest/list-view.mjs.map +1 -0
- package/lib/manifest/manifest-builder.d.mts +44 -0
- package/lib/manifest/manifest-builder.d.mts.map +1 -0
- package/lib/manifest/manifest-builder.d.ts +44 -0
- package/lib/manifest/manifest-builder.d.ts.map +1 -0
- package/lib/manifest/manifest-builder.js +69 -0
- package/lib/manifest/manifest-builder.js.map +1 -0
- package/lib/manifest/manifest-builder.mjs +65 -0
- package/lib/manifest/manifest-builder.mjs.map +1 -0
- package/lib/manifest/manifest-function.d.mts +39 -0
- package/lib/manifest/manifest-function.d.mts.map +1 -0
- package/lib/manifest/manifest-function.d.ts +39 -0
- package/lib/manifest/manifest-function.d.ts.map +1 -0
- package/lib/manifest/manifest-function.js +56 -0
- package/lib/manifest/manifest-function.js.map +1 -0
- package/lib/manifest/manifest-function.mjs +52 -0
- package/lib/manifest/manifest-function.mjs.map +1 -0
- package/lib/manifest/rule.d.mts +50 -0
- package/lib/manifest/rule.d.mts.map +1 -0
- package/lib/manifest/rule.d.ts +50 -0
- package/lib/manifest/rule.d.ts.map +1 -0
- package/lib/manifest/rule.js +84 -0
- package/lib/manifest/rule.js.map +1 -0
- package/lib/manifest/rule.mjs +80 -0
- package/lib/manifest/rule.mjs.map +1 -0
- package/lib/manifest/sdui-page.d.mts +33 -0
- package/lib/manifest/sdui-page.d.mts.map +1 -0
- package/lib/manifest/sdui-page.d.ts +33 -0
- package/lib/manifest/sdui-page.d.ts.map +1 -0
- package/lib/manifest/sdui-page.js +52 -0
- package/lib/manifest/sdui-page.js.map +1 -0
- package/lib/manifest/sdui-page.mjs +48 -0
- package/lib/manifest/sdui-page.mjs.map +1 -0
- package/lib/manifest/validation.d.mts +41 -0
- package/lib/manifest/validation.d.mts.map +1 -0
- package/lib/manifest/validation.d.ts +41 -0
- package/lib/manifest/validation.d.ts.map +1 -0
- package/lib/manifest/validation.js +57 -0
- package/lib/manifest/validation.js.map +1 -0
- package/lib/manifest/validation.mjs +53 -0
- package/lib/manifest/validation.mjs.map +1 -0
- package/package.json +11 -1
- package/resources/draft-hires.d.mts +22 -0
- package/resources/draft-hires.d.mts.map +1 -1
- package/resources/draft-hires.d.ts +22 -0
- package/resources/draft-hires.d.ts.map +1 -1
- package/resources/earnings-inputs.d.mts +104 -1
- package/resources/earnings-inputs.d.mts.map +1 -1
- package/resources/earnings-inputs.d.ts +104 -1
- package/resources/earnings-inputs.d.ts.map +1 -1
- package/resources/earnings-inputs.js +17 -0
- package/resources/earnings-inputs.js.map +1 -1
- package/resources/earnings-inputs.mjs +17 -0
- package/resources/earnings-inputs.mjs.map +1 -1
- package/resources/files.d.mts +149 -0
- package/resources/files.d.mts.map +1 -0
- package/resources/files.d.ts +149 -0
- package/resources/files.d.ts.map +1 -0
- package/resources/files.js +25 -0
- package/resources/files.js.map +1 -0
- package/resources/files.mjs +21 -0
- package/resources/files.mjs.map +1 -0
- package/resources/index.d.mts +2 -1
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +2 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +4 -2
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +1 -0
- package/resources/index.mjs.map +1 -1
- package/resources/object-categories.d.mts +1 -1
- package/resources/object-categories.d.ts +1 -1
- package/resources/object-categories.js +1 -1
- package/resources/object-categories.mjs +1 -1
- package/resources/report-runs.d.mts +11 -11
- package/resources/report-runs.d.ts +11 -11
- package/resources/workflow-action-executions.d.mts +17 -2
- package/resources/workflow-action-executions.d.mts.map +1 -1
- package/resources/workflow-action-executions.d.ts +17 -2
- package/resources/workflow-action-executions.d.ts.map +1 -1
- package/src/client.ts +29 -0
- package/src/internal/types.ts +6 -8
- package/src/lib/manifest/_helpers.ts +164 -0
- package/src/lib/manifest/app.ts +108 -0
- package/src/lib/manifest/category.ts +51 -0
- package/src/lib/manifest/custom-object-field-section.ts +54 -0
- package/src/lib/manifest/custom-object-page-layout.ts +292 -0
- package/src/lib/manifest/custom-object.ts +269 -0
- package/src/lib/manifest/field.ts +632 -0
- package/src/lib/manifest/index.ts +114 -0
- package/src/lib/manifest/list-view.ts +103 -0
- package/src/lib/manifest/manifest-builder.ts +97 -0
- package/src/lib/manifest/manifest-function.ts +81 -0
- package/src/lib/manifest/rule.ts +125 -0
- package/src/lib/manifest/sdui-page.ts +72 -0
- package/src/lib/manifest/validation.ts +87 -0
- package/src/resources/draft-hires.ts +26 -0
- package/src/resources/earnings-inputs.ts +140 -0
- package/src/resources/files.ts +186 -0
- package/src/resources/index.ts +3 -0
- package/src/resources/object-categories.ts +1 -1
- package/src/resources/report-runs.ts +32 -32
- package/src/resources/workflow-action-executions.ts +22 -1
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Fields for Rippling custom objects (CUSTOM_OBJECT_FIELD v2).
|
|
3
|
+
* Hand-written (not Stainless-generated); mirrors custom_object_field.py.
|
|
4
|
+
*
|
|
5
|
+
* Each concrete field type is its own class that extends the internal `Field`
|
|
6
|
+
* base. Construct like any other component:
|
|
7
|
+
*
|
|
8
|
+
* new TextField(customObject, { apiName, displayName, ... });
|
|
9
|
+
* new LookupField(customObject, { apiName, target: otherCo, ... });
|
|
10
|
+
* new SummaryField(customObject, { apiName, aggregates, using, lookup, ... });
|
|
11
|
+
*
|
|
12
|
+
* This mirrors AWS CDK's per-variant-class pattern (e.g.
|
|
13
|
+
* `new codepipeline_actions.GitHubSourceAction({...})` rather than a single
|
|
14
|
+
* Action class with 30 static factories). Every subclass serializes to the
|
|
15
|
+
* same `CUSTOM_OBJECT_FIELD` wire shape, discriminated by `data_type.field_type`.
|
|
16
|
+
*
|
|
17
|
+
* The parent CO is the first positional arg (the "scope") — its `api_name`
|
|
18
|
+
* becomes `custom_object_api_name` at serialization, and the field
|
|
19
|
+
* auto-registers with the manifest via the CO's scope chain.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
FILTER_OPERATORS_BY_TYPE,
|
|
24
|
+
type FieldType,
|
|
25
|
+
type FormulaFieldType,
|
|
26
|
+
type SummaryAggregation,
|
|
27
|
+
type SummaryFilterOperator,
|
|
28
|
+
} from './_helpers';
|
|
29
|
+
import type { CustomObject } from './custom-object';
|
|
30
|
+
import type { CustomObjectFieldSection } from './custom-object-field-section';
|
|
31
|
+
|
|
32
|
+
/** Summary filter. Lives here (not `_helpers.ts`) to avoid a circular import with `Field`. */
|
|
33
|
+
export interface SummaryFilter {
|
|
34
|
+
/** Filter field on the child CO. */
|
|
35
|
+
field: Field;
|
|
36
|
+
operator: SummaryFilterOperator;
|
|
37
|
+
/** Raw RQL value, e.g. `"'Active'"`, `"50"`, `"true"`, `"DATE(2026,1,1)"`. */
|
|
38
|
+
value: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface SummarySpec {
|
|
42
|
+
aggregates: Field;
|
|
43
|
+
using: SummaryAggregation;
|
|
44
|
+
lookup: Field;
|
|
45
|
+
filters: SummaryFilter[];
|
|
46
|
+
currencyCode: string | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateApiName(apiName: string): void {
|
|
50
|
+
if (!apiName.endsWith('__c')) {
|
|
51
|
+
throw new Error(`Field api_name must end with __c: ${apiName}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Props shared by every field variant. */
|
|
56
|
+
interface CommonFieldProps {
|
|
57
|
+
apiName: string;
|
|
58
|
+
displayName: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
section?: CustomObjectFieldSection | string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Low-level props for the base `Field`. Most users should use one of the
|
|
65
|
+
* concrete subclasses (`TextField`, `LookupField`, …) instead of constructing
|
|
66
|
+
* `Field` directly.
|
|
67
|
+
*/
|
|
68
|
+
export interface FieldProps {
|
|
69
|
+
apiName: string;
|
|
70
|
+
displayName: string;
|
|
71
|
+
// Optional members use `| undefined` so subclass constructors can spread
|
|
72
|
+
// props under `exactOptionalPropertyTypes`.
|
|
73
|
+
fieldType?: FieldType | undefined;
|
|
74
|
+
dataType?: Record<string, any> | undefined;
|
|
75
|
+
description?: string | null | undefined;
|
|
76
|
+
required?: boolean | null | undefined;
|
|
77
|
+
unique?: boolean | null | undefined;
|
|
78
|
+
indexed?: boolean | null | undefined;
|
|
79
|
+
rqlFormula?: string | null | undefined;
|
|
80
|
+
formulaAttrMetas?: Record<string, any> | null | undefined;
|
|
81
|
+
derivedAggregatedField?: Record<string, any> | null | undefined;
|
|
82
|
+
derivedFieldFormula?: string | null | undefined;
|
|
83
|
+
section?: CustomObjectFieldSection | string | null | undefined;
|
|
84
|
+
maxLength?: number | undefined;
|
|
85
|
+
decimalPlaces?: number | undefined;
|
|
86
|
+
currency?: string | undefined;
|
|
87
|
+
options?: string[] | undefined;
|
|
88
|
+
/** Internal — set by `SummaryField`; drives the `derived_aggregated_field` wire field. */
|
|
89
|
+
summarySpec?: SummarySpec | undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
93
|
+
// Base class
|
|
94
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Base class for every custom-object field. Prefer the concrete subclasses
|
|
98
|
+
* (`TextField`, `LookupField`, `SummaryField`, …). Construct `Field` directly
|
|
99
|
+
* only for wire-types the SDK doesn't yet model.
|
|
100
|
+
*/
|
|
101
|
+
export class Field {
|
|
102
|
+
private readonly _fieldApiName: string;
|
|
103
|
+
private readonly _fieldDisplayName: string;
|
|
104
|
+
private readonly _customObjectApiName: string;
|
|
105
|
+
private readonly _dataType: Record<string, any>;
|
|
106
|
+
private readonly _description: string | null;
|
|
107
|
+
private readonly _isRequired: boolean | null;
|
|
108
|
+
private readonly _isUnique: boolean | null;
|
|
109
|
+
private readonly _isIndexed: boolean | null;
|
|
110
|
+
private readonly _rqlFormula: string | null;
|
|
111
|
+
private readonly _formulaAttrMetas: Record<string, any> | null;
|
|
112
|
+
private readonly _derivedFieldFormula: string | null;
|
|
113
|
+
private readonly _section: string | null;
|
|
114
|
+
private readonly _summarySpec: SummarySpec | null;
|
|
115
|
+
private readonly _derivedAggregatedFieldStatic: Record<string, any> | null;
|
|
116
|
+
|
|
117
|
+
/** Build a new field; enforces `__c` suffix and folds props into `data_type`. */
|
|
118
|
+
constructor(customObject: CustomObject, props: FieldProps) {
|
|
119
|
+
validateApiName(props.apiName);
|
|
120
|
+
this._fieldApiName = props.apiName;
|
|
121
|
+
this._fieldDisplayName = props.displayName;
|
|
122
|
+
this._customObjectApiName = customObject.getApiName();
|
|
123
|
+
|
|
124
|
+
const dt: Record<string, any> = { ...(props.dataType ?? {}) };
|
|
125
|
+
if (props.fieldType != null) dt['field_type'] = props.fieldType;
|
|
126
|
+
if (props.maxLength != null) dt['max_length'] = props.maxLength;
|
|
127
|
+
if (props.decimalPlaces != null) dt['num_of_decimal_places'] = props.decimalPlaces;
|
|
128
|
+
if (props.currency != null) dt['currency'] = props.currency;
|
|
129
|
+
if (props.options != null) dt['options_list'] = [...props.options];
|
|
130
|
+
this._dataType = dt;
|
|
131
|
+
|
|
132
|
+
this._description = props.description ?? null;
|
|
133
|
+
this._isRequired = props.required ?? null;
|
|
134
|
+
this._isUnique = props.unique ?? null;
|
|
135
|
+
this._isIndexed = props.indexed ?? null;
|
|
136
|
+
this._rqlFormula = props.rqlFormula ?? null;
|
|
137
|
+
this._formulaAttrMetas = props.formulaAttrMetas ?? null;
|
|
138
|
+
this._derivedAggregatedFieldStatic = props.derivedAggregatedField ?? null;
|
|
139
|
+
this._derivedFieldFormula = props.derivedFieldFormula ?? null;
|
|
140
|
+
this._summarySpec = props.summarySpec ?? null;
|
|
141
|
+
|
|
142
|
+
const rawSection = props.section;
|
|
143
|
+
this._section =
|
|
144
|
+
rawSection == null ? null
|
|
145
|
+
: typeof rawSection === 'string' ? rawSection
|
|
146
|
+
: rawSection.getSectionId();
|
|
147
|
+
|
|
148
|
+
customObject._register(this);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Getters ────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/** Backend api_name for this field. */
|
|
154
|
+
getApiName(): string {
|
|
155
|
+
return this._fieldApiName;
|
|
156
|
+
}
|
|
157
|
+
/** Human-readable display name. */
|
|
158
|
+
getDisplayName(): string {
|
|
159
|
+
return this._fieldDisplayName;
|
|
160
|
+
}
|
|
161
|
+
/** api_name of the CustomObject this field lives on. */
|
|
162
|
+
getCustomObjectApiName(): string {
|
|
163
|
+
return this._customObjectApiName;
|
|
164
|
+
}
|
|
165
|
+
/** Resolved `data_type.field_type`, or null if unset. */
|
|
166
|
+
getFieldType(): FieldType | null {
|
|
167
|
+
return (this._dataType['field_type'] as FieldType | undefined) ?? null;
|
|
168
|
+
}
|
|
169
|
+
/** Raw `data_type` dict (read-only view). */
|
|
170
|
+
getDataType(): Readonly<Record<string, any>> {
|
|
171
|
+
return this._dataType;
|
|
172
|
+
}
|
|
173
|
+
/** True if this field is a simple formula field. */
|
|
174
|
+
isFormula(): boolean {
|
|
175
|
+
return this._derivedFieldFormula != null;
|
|
176
|
+
}
|
|
177
|
+
/** True if this field is a summary/rollup field. */
|
|
178
|
+
isSummary(): boolean {
|
|
179
|
+
return this._summarySpec != null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Serialization ──────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
/** Serialize to the backend `CUSTOM_OBJECT_FIELD` wire shape. */
|
|
185
|
+
toDict(): Record<string, any> {
|
|
186
|
+
return {
|
|
187
|
+
type: 'CUSTOM_OBJECT_FIELD',
|
|
188
|
+
custom_object_api_name: this._customObjectApiName,
|
|
189
|
+
field_api_name: this._fieldApiName,
|
|
190
|
+
field_display_name: this._fieldDisplayName,
|
|
191
|
+
data_type: { options_list: [], supported_types: [], ...this._dataType },
|
|
192
|
+
description: this._description,
|
|
193
|
+
is_required: this._isRequired,
|
|
194
|
+
is_unique: this._isUnique,
|
|
195
|
+
is_indexed: this._isIndexed,
|
|
196
|
+
rql_formula: this._rqlFormula ?? null,
|
|
197
|
+
formula_attr_metas: this._formulaAttrMetas,
|
|
198
|
+
// User-authored package fields are never standard (backend invariant).
|
|
199
|
+
is_standard: false,
|
|
200
|
+
derived_aggregated_field: this._resolveAggregatedField(),
|
|
201
|
+
derived_field_formula: this._derivedFieldFormula ?? null,
|
|
202
|
+
section: this._section,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Build the `derived_aggregated_field` dict from `_summarySpec` with cross-CO checks. */
|
|
207
|
+
private _resolveAggregatedField(): Record<string, any> | null {
|
|
208
|
+
if (this._summarySpec == null) return this._derivedAggregatedFieldStatic;
|
|
209
|
+
const spec = this._summarySpec;
|
|
210
|
+
|
|
211
|
+
const aggParent = spec.aggregates.getCustomObjectApiName();
|
|
212
|
+
const lookupParent = spec.lookup.getCustomObjectApiName();
|
|
213
|
+
if (lookupParent !== aggParent) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`SummaryField "${this._fieldApiName}": lookup lives on "${lookupParent}" ` +
|
|
216
|
+
`but aggregates lives on "${aggParent}" — both must live on the same ` +
|
|
217
|
+
`child CustomObject.`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Per-filter residency / back-ref / LONG_TEXT / formula checks: backend only.
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
aggregation_function: spec.using,
|
|
225
|
+
child_model_name: aggParent,
|
|
226
|
+
currency_code: spec.currencyCode,
|
|
227
|
+
field_to_aggregate: spec.aggregates.getApiName(),
|
|
228
|
+
filters: spec.filters.map((f) => ({
|
|
229
|
+
field_name: f.field.getApiName(),
|
|
230
|
+
filter_value: f.value,
|
|
231
|
+
operator: f.operator,
|
|
232
|
+
})),
|
|
233
|
+
lookup_field_api_name: spec.lookup.getApiName(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
239
|
+
// Concrete field subclasses — one per wire-level variant.
|
|
240
|
+
// Each sets its own `fieldType` / `dataType` and forwards the rest to
|
|
241
|
+
// `Field`'s constructor. Order matches the UI's "Data type" dropdown.
|
|
242
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
// ── Text-like ──────────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
export interface TextFieldProps extends CommonFieldProps {
|
|
247
|
+
required?: boolean;
|
|
248
|
+
unique?: boolean;
|
|
249
|
+
maxLength?: number;
|
|
250
|
+
}
|
|
251
|
+
/** Single-line text. */
|
|
252
|
+
export class TextField extends Field {
|
|
253
|
+
constructor(customObject: CustomObject, props: TextFieldProps) {
|
|
254
|
+
super(customObject, { ...props, fieldType: 'TEXT' });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface LongTextFieldProps extends CommonFieldProps {
|
|
259
|
+
required?: boolean;
|
|
260
|
+
maxLength?: number;
|
|
261
|
+
}
|
|
262
|
+
/** Multi-line text. */
|
|
263
|
+
export class LongTextField extends Field {
|
|
264
|
+
constructor(customObject: CustomObject, props: LongTextFieldProps) {
|
|
265
|
+
super(customObject, { ...props, fieldType: 'LONG_TEXT' });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface EmailFieldProps extends CommonFieldProps {
|
|
270
|
+
required?: boolean;
|
|
271
|
+
unique?: boolean;
|
|
272
|
+
}
|
|
273
|
+
/** Email address (format-validated by the UI). */
|
|
274
|
+
export class EmailField extends Field {
|
|
275
|
+
constructor(customObject: CustomObject, props: EmailFieldProps) {
|
|
276
|
+
super(customObject, { ...props, fieldType: 'EMAIL' });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface UrlFieldProps extends CommonFieldProps {
|
|
281
|
+
required?: boolean;
|
|
282
|
+
}
|
|
283
|
+
/** URL (format-validated by the UI). */
|
|
284
|
+
export class UrlField extends Field {
|
|
285
|
+
constructor(customObject: CustomObject, props: UrlFieldProps) {
|
|
286
|
+
super(customObject, { ...props, fieldType: 'URL' });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface PhoneFieldProps extends CommonFieldProps {
|
|
291
|
+
required?: boolean;
|
|
292
|
+
}
|
|
293
|
+
/** Phone number. */
|
|
294
|
+
export class PhoneField extends Field {
|
|
295
|
+
constructor(customObject: CustomObject, props: PhoneFieldProps) {
|
|
296
|
+
super(customObject, { ...props, fieldType: 'PHONE_NUMBER' });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── Numeric ────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
export interface NumberFieldProps extends CommonFieldProps {
|
|
303
|
+
required?: boolean;
|
|
304
|
+
unique?: boolean;
|
|
305
|
+
decimalPlaces?: number;
|
|
306
|
+
}
|
|
307
|
+
/** Number. */
|
|
308
|
+
export class NumberField extends Field {
|
|
309
|
+
constructor(customObject: CustomObject, props: NumberFieldProps) {
|
|
310
|
+
super(customObject, { ...props, fieldType: 'NUMBER' });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface CurrencyFieldProps extends CommonFieldProps {
|
|
315
|
+
required?: boolean;
|
|
316
|
+
currency?: string;
|
|
317
|
+
decimalPlaces?: number;
|
|
318
|
+
}
|
|
319
|
+
/** Currency amount; `currency` is forwarded only when set. */
|
|
320
|
+
export class CurrencyField extends Field {
|
|
321
|
+
constructor(customObject: CustomObject, props: CurrencyFieldProps) {
|
|
322
|
+
super(customObject, { ...props, fieldType: 'CURRENCY' });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface PercentageFieldProps extends CommonFieldProps {
|
|
327
|
+
required?: boolean;
|
|
328
|
+
decimalPlaces?: number;
|
|
329
|
+
}
|
|
330
|
+
/** Percentage. */
|
|
331
|
+
export class PercentageField extends Field {
|
|
332
|
+
constructor(customObject: CustomObject, props: PercentageFieldProps) {
|
|
333
|
+
super(customObject, { ...props, fieldType: 'PERCENTAGE' });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── Date/time ──────────────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
export interface DateFieldProps extends CommonFieldProps {
|
|
340
|
+
required?: boolean;
|
|
341
|
+
}
|
|
342
|
+
/** Date (no time). */
|
|
343
|
+
export class DateField extends Field {
|
|
344
|
+
constructor(customObject: CustomObject, props: DateFieldProps) {
|
|
345
|
+
super(customObject, { ...props, fieldType: 'DATE' });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export interface DatetimeFieldProps extends CommonFieldProps {
|
|
350
|
+
required?: boolean;
|
|
351
|
+
}
|
|
352
|
+
/** Date + time. */
|
|
353
|
+
export class DatetimeField extends Field {
|
|
354
|
+
constructor(customObject: CustomObject, props: DatetimeFieldProps) {
|
|
355
|
+
super(customObject, { ...props, fieldType: 'DATETIME' });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface TimeFieldProps extends CommonFieldProps {
|
|
360
|
+
required?: boolean;
|
|
361
|
+
}
|
|
362
|
+
/** Time of day. */
|
|
363
|
+
export class TimeField extends Field {
|
|
364
|
+
constructor(customObject: CustomObject, props: TimeFieldProps) {
|
|
365
|
+
super(customObject, { ...props, fieldType: 'TIME' });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ── Choice ─────────────────────────────────────────────────────────────
|
|
370
|
+
|
|
371
|
+
export interface SelectFieldProps extends CommonFieldProps {
|
|
372
|
+
/** Required choice list. */
|
|
373
|
+
options: string[];
|
|
374
|
+
required?: boolean;
|
|
375
|
+
}
|
|
376
|
+
/** Dropdown select. */
|
|
377
|
+
export class SelectField extends Field {
|
|
378
|
+
constructor(customObject: CustomObject, props: SelectFieldProps) {
|
|
379
|
+
super(customObject, { ...props, fieldType: 'SELECT' });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export interface RadioFieldProps extends CommonFieldProps {
|
|
384
|
+
/** Required choice list. */
|
|
385
|
+
options: string[];
|
|
386
|
+
required?: boolean;
|
|
387
|
+
}
|
|
388
|
+
/** Radio button group. */
|
|
389
|
+
export class RadioField extends Field {
|
|
390
|
+
constructor(customObject: CustomObject, props: RadioFieldProps) {
|
|
391
|
+
super(customObject, { ...props, fieldType: 'RADIO' });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export interface BooleanFieldProps extends CommonFieldProps {
|
|
396
|
+
required?: boolean;
|
|
397
|
+
}
|
|
398
|
+
/** Boolean (checkbox). */
|
|
399
|
+
export class BooleanField extends Field {
|
|
400
|
+
constructor(customObject: CustomObject, props: BooleanFieldProps) {
|
|
401
|
+
super(customObject, { ...props, fieldType: 'BOOLEAN' });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── File ───────────────────────────────────────────────────────────────
|
|
406
|
+
|
|
407
|
+
export interface FileFieldProps extends CommonFieldProps {
|
|
408
|
+
/**
|
|
409
|
+
* File-type restriction. Matches the UI's "File type" dropdown options.
|
|
410
|
+
* 'images' — jpg, jpeg, png, gif, bmp, svg
|
|
411
|
+
* 'documents' — doc, docx, pdf, csv, txt, rtf, ppt, pptx
|
|
412
|
+
* 'any' — any file type
|
|
413
|
+
*/
|
|
414
|
+
allowedType: 'images' | 'documents' | 'any';
|
|
415
|
+
required?: boolean;
|
|
416
|
+
}
|
|
417
|
+
/** File attachment; `allowedType` mirrors the UI's "File type" dropdown. */
|
|
418
|
+
export class FileField extends Field {
|
|
419
|
+
constructor(customObject: CustomObject, props: FileFieldProps) {
|
|
420
|
+
const { allowedType, ...rest } = props;
|
|
421
|
+
const supported = allowedType === 'any' ? ['*'] : [allowedType];
|
|
422
|
+
super(customObject, {
|
|
423
|
+
...rest,
|
|
424
|
+
fieldType: 'FILE',
|
|
425
|
+
dataType: { supported_types: supported },
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── Relational ─────────────────────────────────────────────────────────
|
|
431
|
+
|
|
432
|
+
export interface LookupFieldProps extends CommonFieldProps {
|
|
433
|
+
/** Target object. Live `CustomObject` (SDK reads api_name) or native-model api_name string (e.g. `'role'`). */
|
|
434
|
+
target: CustomObject | string;
|
|
435
|
+
relatedDataLabel?: string;
|
|
436
|
+
required?: boolean;
|
|
437
|
+
}
|
|
438
|
+
/** Many-to-one reference. */
|
|
439
|
+
export class LookupField extends Field {
|
|
440
|
+
constructor(customObject: CustomObject, props: LookupFieldProps) {
|
|
441
|
+
const targetApiName = typeof props.target === 'string' ? props.target : props.target.getApiName();
|
|
442
|
+
super(customObject, {
|
|
443
|
+
apiName: props.apiName,
|
|
444
|
+
displayName: props.displayName,
|
|
445
|
+
dataType: {
|
|
446
|
+
field_type: 'OG_REFERENCE_FIELD',
|
|
447
|
+
og_model_rql_name: targetApiName,
|
|
448
|
+
edge_type: 'LOOKUP',
|
|
449
|
+
...(props.relatedDataLabel != null ? { related_data_label: props.relatedDataLabel } : {}),
|
|
450
|
+
},
|
|
451
|
+
required: props.required,
|
|
452
|
+
description: props.description,
|
|
453
|
+
section: props.section,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export interface ParentChildFieldProps extends CommonFieldProps {
|
|
459
|
+
/** Target parent object. Must be a live `CustomObject`. */
|
|
460
|
+
target: CustomObject;
|
|
461
|
+
relatedDataLabel?: string;
|
|
462
|
+
parentChildType?: 'PRIMARY' | 'SECONDARY';
|
|
463
|
+
enableParentOwnerInheritance?: boolean;
|
|
464
|
+
}
|
|
465
|
+
/** Parent-child reference — always required on the wire. */
|
|
466
|
+
export class ParentChildField extends Field {
|
|
467
|
+
constructor(customObject: CustomObject, props: ParentChildFieldProps) {
|
|
468
|
+
super(customObject, {
|
|
469
|
+
apiName: props.apiName,
|
|
470
|
+
displayName: props.displayName,
|
|
471
|
+
dataType: {
|
|
472
|
+
field_type: 'OG_REFERENCE_FIELD',
|
|
473
|
+
og_model_rql_name: props.target.getApiName(),
|
|
474
|
+
edge_type: 'PARENT_CHILD',
|
|
475
|
+
...(props.relatedDataLabel != null ? { related_data_label: props.relatedDataLabel } : {}),
|
|
476
|
+
...(props.parentChildType != null ? { parent_child_type: props.parentChildType } : {}),
|
|
477
|
+
...(props.enableParentOwnerInheritance != null ?
|
|
478
|
+
{ enable_parent_owner_inheritance: props.enableParentOwnerInheritance }
|
|
479
|
+
: {}),
|
|
480
|
+
},
|
|
481
|
+
required: true,
|
|
482
|
+
description: props.description,
|
|
483
|
+
section: props.section,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ── Computed ───────────────────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
export interface FormulaFieldProps extends CommonFieldProps {
|
|
491
|
+
/** The computed RQL expression. */
|
|
492
|
+
formula: string;
|
|
493
|
+
/** Output type narrowed to the 7 variants the UI "Data type" dropdown exposes. */
|
|
494
|
+
fieldType: FormulaFieldType;
|
|
495
|
+
/** Required when `fieldType === 'TEXT'`. */
|
|
496
|
+
maxLength?: number;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Computed formula field (read-only). `fieldType` is required and narrowed
|
|
500
|
+
* to the 7 variants the UI "Data type" dropdown exposes. `maxLength` is
|
|
501
|
+
* required when `fieldType === 'TEXT'`.
|
|
502
|
+
*/
|
|
503
|
+
export class FormulaField extends Field {
|
|
504
|
+
constructor(customObject: CustomObject, props: FormulaFieldProps) {
|
|
505
|
+
if (props.fieldType === 'TEXT' && props.maxLength == null) {
|
|
506
|
+
throw new Error(`FormulaField "${props.apiName}": maxLength is required when fieldType is 'TEXT'.`);
|
|
507
|
+
}
|
|
508
|
+
const { formula, ...rest } = props;
|
|
509
|
+
super(customObject, {
|
|
510
|
+
...rest,
|
|
511
|
+
derivedFieldFormula: formula,
|
|
512
|
+
required: null,
|
|
513
|
+
unique: null,
|
|
514
|
+
indexed: null,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export interface SummaryFieldProps extends CommonFieldProps {
|
|
520
|
+
/** Child-CO field being aggregated. Must have a known fieldType. */
|
|
521
|
+
aggregates: Field;
|
|
522
|
+
using: SummaryAggregation;
|
|
523
|
+
/** Child-CO's parentChild field pointing back at this CO. */
|
|
524
|
+
lookup: Field;
|
|
525
|
+
filters?: SummaryFilter[];
|
|
526
|
+
/** Only valid when the output fieldType resolves to `'CURRENCY'`. */
|
|
527
|
+
currencyCode?: string;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Summary/rollup field — aggregates a child-object field (read-only).
|
|
531
|
+
* SDK enforces only what backend/UI won't: `lookup` edge_type, output-type
|
|
532
|
+
* inference, per-type filter operators, and same-child-CO residency for
|
|
533
|
+
* `aggregates` + `lookup`. Per-filter residency / LONG_TEXT / formula
|
|
534
|
+
* filter / currency pairing checks are left to the backend.
|
|
535
|
+
*/
|
|
536
|
+
export class SummaryField extends Field {
|
|
537
|
+
constructor(customObject: CustomObject, props: SummaryFieldProps) {
|
|
538
|
+
// lookup must be a parentChild field.
|
|
539
|
+
if (props.lookup.getDataType()['edge_type'] !== 'PARENT_CHILD') {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`SummaryField "${props.apiName}": 'lookup' must be a parentChild ` +
|
|
542
|
+
`field on the child CO (got edge_type=` +
|
|
543
|
+
`${props.lookup.getDataType()['edge_type'] ?? 'undefined'}).`,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Infer output fieldType: NUMBER for COUNT, else the aggregated field's type.
|
|
548
|
+
let outputFieldType: FieldType;
|
|
549
|
+
if (props.using === 'COUNT') {
|
|
550
|
+
outputFieldType = 'NUMBER';
|
|
551
|
+
} else {
|
|
552
|
+
const agg = props.aggregates.getFieldType();
|
|
553
|
+
if (agg == null) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`SummaryField "${props.apiName}": cannot infer output type — ` +
|
|
556
|
+
`aggregates field "${props.aggregates.getApiName()}" has no ` +
|
|
557
|
+
`known field_type. Construct it with a typed subclass (e.g. ` +
|
|
558
|
+
`CurrencyField / NumberField).`,
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
outputFieldType = agg;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Summary-filter field restrictions. Mirror the admin condition-builder
|
|
565
|
+
// picker so SDK-authored manifests are fully editable in the UI, and
|
|
566
|
+
// catch the backend's own hard rejects ahead of /validate.
|
|
567
|
+
for (const f of props.filters ?? []) {
|
|
568
|
+
const fname = f.field.getApiName();
|
|
569
|
+
const ft = f.field.getFieldType();
|
|
570
|
+
|
|
571
|
+
// (a) Can't filter on the parent-child lookup field itself
|
|
572
|
+
// (backend: parent_child_util.py rejects this explicitly).
|
|
573
|
+
if (f.field === props.lookup) {
|
|
574
|
+
throw new Error(
|
|
575
|
+
`SummaryField "${props.apiName}": filter field "${fname}" is the ` +
|
|
576
|
+
`parent-child lookup field — that's reserved as the aggregation ` +
|
|
577
|
+
`axis and can't be a filter. Pick a scalar field on the child CO.`,
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
// (b) Can't filter on a derived (formula or summary) field
|
|
581
|
+
// (backend rejects both).
|
|
582
|
+
if (f.field.isFormula() || f.field.isSummary()) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`SummaryField "${props.apiName}": filter field "${fname}" is ` +
|
|
585
|
+
`itself a derived field (formula/summary). The backend does not ` +
|
|
586
|
+
`allow derived fields as summary-filter fields. Filter on a ` +
|
|
587
|
+
`plain scalar field on the child CO instead.`,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
if (ft == null) continue;
|
|
591
|
+
// (c) FILTER_OPERATORS_BY_TYPE is the single source of truth for
|
|
592
|
+
// "which field types can be used as summary-filter fields".
|
|
593
|
+
// Absent key ⇒ UI picker hides this type ⇒ SDK rejects it.
|
|
594
|
+
const allowed = FILTER_OPERATORS_BY_TYPE[ft];
|
|
595
|
+
if (allowed == null) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
`SummaryField "${props.apiName}": filter field "${fname}" has type ` +
|
|
598
|
+
`${ft}, which the admin UI excludes from summary-filter pickers ` +
|
|
599
|
+
`(ambiguous semantics for aggregations). Model a plain BOOLEAN ` +
|
|
600
|
+
`field on the child CO (updated via a Rule) and filter on that.`,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
// (d) Per-type operator allow-list.
|
|
604
|
+
if (!allowed.has(f.operator)) {
|
|
605
|
+
throw new Error(
|
|
606
|
+
`SummaryField "${props.apiName}": operator "${f.operator}" is ` +
|
|
607
|
+
`not valid for ${ft} field "${fname}". ` +
|
|
608
|
+
`Allowed: ${[...allowed].join(', ')}.`,
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
super(customObject, {
|
|
614
|
+
apiName: props.apiName,
|
|
615
|
+
displayName: props.displayName,
|
|
616
|
+
fieldType: outputFieldType,
|
|
617
|
+
currency: outputFieldType === 'CURRENCY' ? props.currencyCode : undefined,
|
|
618
|
+
required: null,
|
|
619
|
+
unique: null,
|
|
620
|
+
indexed: null,
|
|
621
|
+
description: props.description,
|
|
622
|
+
section: props.section,
|
|
623
|
+
summarySpec: {
|
|
624
|
+
aggregates: props.aggregates,
|
|
625
|
+
using: props.using,
|
|
626
|
+
lookup: props.lookup,
|
|
627
|
+
filters: props.filters ?? [],
|
|
628
|
+
currencyCode: props.currencyCode ?? null,
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|