@rippling/rippling-sdk 0.2.0-alpha.37 → 0.2.0-alpha.39
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/examples/sdui/sdui-crud-todo/sdui-crud-todo.ts +3 -3
- package/examples/sdui/sdui-showcase/sdui-showcase.ts +6 -11
- package/examples/sdui/sdui-tab-multi-view/sdui-tab-multi-view.ts +14 -24
- package/examples/sdui/vote/vote.ts +7 -11
- package/lib/manifest/app.d.mts +11 -1
- package/lib/manifest/app.d.mts.map +1 -1
- package/lib/manifest/app.d.ts +11 -1
- package/lib/manifest/app.d.ts.map +1 -1
- package/lib/manifest/app.js +42 -1
- package/lib/manifest/app.js.map +1 -1
- package/lib/manifest/app.mjs +42 -1
- package/lib/manifest/app.mjs.map +1 -1
- package/lib/manifest/category.d.mts +14 -3
- package/lib/manifest/category.d.mts.map +1 -1
- package/lib/manifest/category.d.ts +14 -3
- package/lib/manifest/category.d.ts.map +1 -1
- package/lib/manifest/category.js +25 -3
- package/lib/manifest/category.js.map +1 -1
- package/lib/manifest/category.mjs +25 -3
- package/lib/manifest/category.mjs.map +1 -1
- package/lib/manifest/custom-object-field-section.d.mts +13 -0
- package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
- package/lib/manifest/custom-object-field-section.d.ts +13 -0
- package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
- package/lib/manifest/custom-object-field-section.js +22 -1
- package/lib/manifest/custom-object-field-section.js.map +1 -1
- package/lib/manifest/custom-object-field-section.mjs +22 -1
- package/lib/manifest/custom-object-field-section.mjs.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.mts +23 -0
- package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.ts +23 -0
- package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
- package/lib/manifest/custom-object-page-layout.js +45 -2
- package/lib/manifest/custom-object-page-layout.js.map +1 -1
- package/lib/manifest/custom-object-page-layout.mjs +45 -2
- package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
- package/lib/manifest/custom-object.d.mts +41 -2
- package/lib/manifest/custom-object.d.mts.map +1 -1
- package/lib/manifest/custom-object.d.ts +41 -2
- package/lib/manifest/custom-object.d.ts.map +1 -1
- package/lib/manifest/custom-object.js +70 -3
- package/lib/manifest/custom-object.js.map +1 -1
- package/lib/manifest/custom-object.mjs +70 -3
- package/lib/manifest/custom-object.mjs.map +1 -1
- package/lib/manifest/existing-manifest-context.d.mts +87 -0
- package/lib/manifest/existing-manifest-context.d.mts.map +1 -0
- package/lib/manifest/existing-manifest-context.d.ts +87 -0
- package/lib/manifest/existing-manifest-context.d.ts.map +1 -0
- package/lib/manifest/existing-manifest-context.js +511 -0
- package/lib/manifest/existing-manifest-context.js.map +1 -0
- package/lib/manifest/existing-manifest-context.mjs +506 -0
- package/lib/manifest/existing-manifest-context.mjs.map +1 -0
- package/lib/manifest/field.d.mts +44 -2
- package/lib/manifest/field.d.mts.map +1 -1
- package/lib/manifest/field.d.ts +44 -2
- package/lib/manifest/field.d.ts.map +1 -1
- package/lib/manifest/field.js +134 -3
- package/lib/manifest/field.js.map +1 -1
- package/lib/manifest/field.mjs +134 -3
- package/lib/manifest/field.mjs.map +1 -1
- package/lib/manifest/index.d.mts +8 -7
- package/lib/manifest/index.d.mts.map +1 -1
- package/lib/manifest/index.d.ts +8 -7
- package/lib/manifest/index.d.ts.map +1 -1
- package/lib/manifest/index.js +3 -1
- package/lib/manifest/index.js.map +1 -1
- package/lib/manifest/index.mjs +5 -4
- package/lib/manifest/index.mjs.map +1 -1
- package/lib/manifest/list-view.d.mts +17 -0
- package/lib/manifest/list-view.d.mts.map +1 -1
- package/lib/manifest/list-view.d.ts +17 -0
- package/lib/manifest/list-view.d.ts.map +1 -1
- package/lib/manifest/list-view.js +38 -1
- package/lib/manifest/list-view.js.map +1 -1
- package/lib/manifest/list-view.mjs +38 -1
- package/lib/manifest/list-view.mjs.map +1 -1
- package/lib/manifest/manifest-builder.d.mts +60 -7
- package/lib/manifest/manifest-builder.d.mts.map +1 -1
- package/lib/manifest/manifest-builder.d.ts +60 -7
- package/lib/manifest/manifest-builder.d.ts.map +1 -1
- package/lib/manifest/manifest-builder.js +62 -4
- package/lib/manifest/manifest-builder.js.map +1 -1
- package/lib/manifest/manifest-builder.mjs +62 -4
- package/lib/manifest/manifest-builder.mjs.map +1 -1
- package/lib/manifest/manifest-function.d.mts +18 -3
- package/lib/manifest/manifest-function.d.mts.map +1 -1
- package/lib/manifest/manifest-function.d.ts +18 -3
- package/lib/manifest/manifest-function.d.ts.map +1 -1
- package/lib/manifest/manifest-function.js +44 -3
- package/lib/manifest/manifest-function.js.map +1 -1
- package/lib/manifest/manifest-function.mjs +44 -3
- package/lib/manifest/manifest-function.mjs.map +1 -1
- package/lib/manifest/rule.d.mts +17 -0
- package/lib/manifest/rule.d.mts.map +1 -1
- package/lib/manifest/rule.d.ts +17 -0
- package/lib/manifest/rule.d.ts.map +1 -1
- package/lib/manifest/rule.js +45 -1
- package/lib/manifest/rule.js.map +1 -1
- package/lib/manifest/rule.mjs +45 -1
- package/lib/manifest/rule.mjs.map +1 -1
- package/lib/manifest/sdui-page.d.mts +10 -1
- package/lib/manifest/sdui-page.d.mts.map +1 -1
- package/lib/manifest/sdui-page.d.ts +10 -1
- package/lib/manifest/sdui-page.d.ts.map +1 -1
- package/lib/manifest/sdui-page.js +28 -1
- package/lib/manifest/sdui-page.js.map +1 -1
- package/lib/manifest/sdui-page.mjs +28 -1
- package/lib/manifest/sdui-page.mjs.map +1 -1
- package/lib/manifest/validation.d.mts +17 -0
- package/lib/manifest/validation.d.mts.map +1 -1
- package/lib/manifest/validation.d.ts +17 -0
- package/lib/manifest/validation.d.ts.map +1 -1
- package/lib/manifest/validation.js +31 -1
- package/lib/manifest/validation.js.map +1 -1
- package/lib/manifest/validation.mjs +31 -1
- package/lib/manifest/validation.mjs.map +1 -1
- package/lib/sdui/actions.d.mts.map +1 -1
- package/lib/sdui/actions.d.ts.map +1 -1
- package/lib/sdui/actions.js +23 -13
- package/lib/sdui/actions.js.map +1 -1
- package/lib/sdui/actions.mjs +23 -13
- package/lib/sdui/actions.mjs.map +1 -1
- package/lib/sdui/component.d.mts +28 -11
- package/lib/sdui/component.d.mts.map +1 -1
- package/lib/sdui/component.d.ts +28 -11
- package/lib/sdui/component.d.ts.map +1 -1
- package/lib/sdui/component.js +69 -36
- package/lib/sdui/component.js.map +1 -1
- package/lib/sdui/component.mjs +69 -36
- package/lib/sdui/component.mjs.map +1 -1
- package/lib/sdui/components.d.mts +1141 -132
- package/lib/sdui/components.d.mts.map +1 -1
- package/lib/sdui/components.d.ts +1141 -132
- package/lib/sdui/components.d.ts.map +1 -1
- package/lib/sdui/components.js +834 -206
- package/lib/sdui/components.js.map +1 -1
- package/lib/sdui/components.mjs +830 -203
- package/lib/sdui/components.mjs.map +1 -1
- package/lib/sdui/directives.d.mts.map +1 -1
- package/lib/sdui/directives.d.ts.map +1 -1
- package/lib/sdui/directives.js +4 -1
- package/lib/sdui/directives.js.map +1 -1
- package/lib/sdui/directives.mjs +4 -1
- package/lib/sdui/directives.mjs.map +1 -1
- package/lib/sdui/index.d.mts +314 -261
- package/lib/sdui/index.d.mts.map +1 -1
- package/lib/sdui/index.d.ts +314 -261
- package/lib/sdui/index.d.ts.map +1 -1
- package/lib/sdui/index.js +255 -204
- package/lib/sdui/index.js.map +1 -1
- package/lib/sdui/index.mjs +128 -103
- package/lib/sdui/index.mjs.map +1 -1
- package/lib/sdui/propTypes.d.mts +6330 -0
- package/lib/sdui/propTypes.d.mts.map +1 -0
- package/lib/sdui/propTypes.d.ts +6330 -0
- package/lib/sdui/propTypes.d.ts.map +1 -0
- package/lib/sdui/propTypes.js +14 -0
- package/lib/sdui/propTypes.js.map +1 -0
- package/lib/sdui/propTypes.mjs +13 -0
- package/lib/sdui/propTypes.mjs.map +1 -0
- package/lib/sdui/render.d.mts +1 -1
- package/lib/sdui/render.d.mts.map +1 -1
- package/lib/sdui/render.d.ts +1 -1
- package/lib/sdui/render.d.ts.map +1 -1
- package/lib/sdui/render.js +11 -3
- package/lib/sdui/render.js.map +1 -1
- package/lib/sdui/render.mjs +11 -3
- package/lib/sdui/render.mjs.map +1 -1
- package/lib/sdui/state.d.mts.map +1 -1
- package/lib/sdui/state.d.ts.map +1 -1
- package/lib/sdui/state.js +3 -1
- package/lib/sdui/state.js.map +1 -1
- package/lib/sdui/state.mjs +3 -1
- package/lib/sdui/state.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/manifest/app.ts +46 -2
- package/src/lib/manifest/category.ts +35 -7
- package/src/lib/manifest/custom-object-field-section.ts +37 -1
- package/src/lib/manifest/custom-object-page-layout.ts +67 -2
- package/src/lib/manifest/custom-object.ts +88 -4
- package/src/lib/manifest/existing-manifest-context.ts +673 -0
- package/src/lib/manifest/field.ts +160 -3
- package/src/lib/manifest/index.ts +39 -4
- package/src/lib/manifest/list-view.ts +50 -1
- package/src/lib/manifest/manifest-builder.ts +115 -5
- package/src/lib/manifest/manifest-function.ts +44 -4
- package/src/lib/manifest/rule.ts +60 -1
- package/src/lib/manifest/sdui-page.ts +37 -2
- package/src/lib/manifest/validation.ts +48 -1
- package/src/lib/sdui/actions.ts +23 -10
- package/src/lib/sdui/component.ts +84 -44
- package/src/lib/sdui/components.ts +1627 -212
- package/src/lib/sdui/directives.ts +4 -1
- package/src/lib/sdui/index.ts +565 -200
- package/src/lib/sdui/propTypes.ts +8470 -0
- package/src/lib/sdui/render.ts +13 -5
- package/src/lib/sdui/state.ts +8 -2
- 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
package/src/lib/manifest/app.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CustomObject } from './custom-object';
|
|
2
|
-
import type
|
|
2
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
3
|
+
import type { ManifestComponentDeletion, ManifestScope } from './manifest-builder';
|
|
3
4
|
import { SduiPage } from './sdui-page';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -82,6 +83,11 @@ function resolvePage(entry: AppPage): Record<string, any> {
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
function cloneJson<T>(value: T): T {
|
|
87
|
+
if (value == null) return value;
|
|
88
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
/**
|
|
86
92
|
* Defines a Rippling app and registers it with the manifest.
|
|
87
93
|
*
|
|
@@ -105,6 +111,15 @@ function resolvePage(entry: AppPage): Record<string, any> {
|
|
|
105
111
|
* @see {@link SduiPage} — SDUI surface tab source.
|
|
106
112
|
*/
|
|
107
113
|
export class App {
|
|
114
|
+
static readonly componentType = 'CUSTOM_APP' as const;
|
|
115
|
+
|
|
116
|
+
static toDeletionIdentifier(apiName: string): ManifestComponentDeletion {
|
|
117
|
+
return {
|
|
118
|
+
type: App.componentType,
|
|
119
|
+
api_name: apiName,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
private readonly _apiName: string;
|
|
109
124
|
private readonly _name: string;
|
|
110
125
|
private readonly _description: string | undefined;
|
|
@@ -127,6 +142,35 @@ export class App {
|
|
|
127
142
|
scope._register(this);
|
|
128
143
|
}
|
|
129
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Loads this app from the active existing-manifest JSON context.
|
|
147
|
+
*/
|
|
148
|
+
static loadFromExisting(apiName: string, options: ExistingManifestContextOptions = {}): App {
|
|
149
|
+
return getExistingManifestContext(options).loadApp(apiName);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @internal Hydrates an app from existing manifest wire JSON. */
|
|
153
|
+
static _fromExistingComponent(scope: ManifestScope, component: Record<string, any>): App {
|
|
154
|
+
const app = Object.create(App.prototype) as App;
|
|
155
|
+
Object.assign(app as Record<string, any>, {
|
|
156
|
+
_apiName: component['api_name'],
|
|
157
|
+
_name: component['name'] ?? component['api_name'],
|
|
158
|
+
_description: component['description'],
|
|
159
|
+
_appType: component['app_type'],
|
|
160
|
+
_icon: App.appIconFromExistingComponent(component['icon']),
|
|
161
|
+
_pages: cloneJson(component['pages'] ?? []),
|
|
162
|
+
});
|
|
163
|
+
scope._register(app);
|
|
164
|
+
return app;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private static appIconFromExistingComponent(value: unknown): AppIcon | undefined {
|
|
168
|
+
if (value == null || typeof value !== 'object') return undefined;
|
|
169
|
+
const wire = value as Record<string, any>;
|
|
170
|
+
if (typeof wire['s3_bucket'] !== 'string' || typeof wire['s3_key'] !== 'string') return undefined;
|
|
171
|
+
return { s3Bucket: wire['s3_bucket'], s3Key: wire['s3_key'] };
|
|
172
|
+
}
|
|
173
|
+
|
|
130
174
|
/**
|
|
131
175
|
* Returns the api_name of this app (e.g. `'gym_membership_management_app'`).
|
|
132
176
|
*/
|
|
@@ -148,7 +192,7 @@ export class App {
|
|
|
148
192
|
*/
|
|
149
193
|
toDict(): Record<string, any> {
|
|
150
194
|
const component: Record<string, any> = {
|
|
151
|
-
type:
|
|
195
|
+
type: App.componentType,
|
|
152
196
|
api_name: this._apiName,
|
|
153
197
|
name: this._name,
|
|
154
198
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
2
|
+
import type { ManifestComponentDeletion, ManifestScope } from './manifest-builder';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Initialization properties for {@link Category}.
|
|
@@ -19,9 +20,11 @@ export interface CategoryProps {
|
|
|
19
20
|
/**
|
|
20
21
|
* Human-readable description of what this category groups.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
+
* Required. The backend (`CustomCategoryConfig.description: str`) requires the
|
|
24
|
+
* key to be present and rejects `null`. Pass an empty string only if you truly
|
|
25
|
+
* have no meaningful copy.
|
|
23
26
|
*/
|
|
24
|
-
description
|
|
27
|
+
description: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -55,9 +58,18 @@ export interface CategoryProps {
|
|
|
55
58
|
* @see {@link CustomObject} — references this category via instance or api_name string.
|
|
56
59
|
*/
|
|
57
60
|
export class Category {
|
|
61
|
+
static readonly componentType = 'CUSTOM_CATEGORY' as const;
|
|
62
|
+
|
|
63
|
+
static toDeletionIdentifier(apiName: string): ManifestComponentDeletion {
|
|
64
|
+
return {
|
|
65
|
+
type: Category.componentType,
|
|
66
|
+
api_name: apiName,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
private readonly _apiName: string;
|
|
59
71
|
private readonly _name: string;
|
|
60
|
-
private readonly _description: string
|
|
72
|
+
private readonly _description: string;
|
|
61
73
|
|
|
62
74
|
/**
|
|
63
75
|
* @param scope - The manifest to register this category with.
|
|
@@ -66,10 +78,26 @@ export class Category {
|
|
|
66
78
|
constructor(scope: ManifestScope, props: CategoryProps) {
|
|
67
79
|
this._apiName = props.apiName;
|
|
68
80
|
this._name = props.name;
|
|
69
|
-
this._description = props.description
|
|
81
|
+
this._description = props.description;
|
|
70
82
|
scope._register(this);
|
|
71
83
|
}
|
|
72
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Loads this category from the active existing-manifest JSON context.
|
|
87
|
+
*/
|
|
88
|
+
static loadFromExisting(apiName: string, options: ExistingManifestContextOptions = {}): Category {
|
|
89
|
+
return getExistingManifestContext(options).loadCategory(apiName);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @internal Hydrates a category from existing manifest wire JSON. */
|
|
93
|
+
static _fromExistingComponent(scope: ManifestScope, component: Record<string, any>): Category {
|
|
94
|
+
return new Category(scope, {
|
|
95
|
+
apiName: component['api_name'],
|
|
96
|
+
name: component['name'] ?? component['api_name'],
|
|
97
|
+
description: component['description'] ?? '',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
73
101
|
/**
|
|
74
102
|
* Returns the api_name of this category (e.g. `'gym__c'`).
|
|
75
103
|
*/
|
|
@@ -91,10 +119,10 @@ export class Category {
|
|
|
91
119
|
*/
|
|
92
120
|
toDict(): Record<string, any> {
|
|
93
121
|
return {
|
|
94
|
-
type:
|
|
122
|
+
type: Category.componentType,
|
|
95
123
|
api_name: this._apiName,
|
|
96
124
|
name: this._name,
|
|
97
|
-
description: this._description
|
|
125
|
+
description: this._description,
|
|
98
126
|
};
|
|
99
127
|
}
|
|
100
128
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { generateId } from './_helpers';
|
|
2
2
|
import type { CustomObject } from './custom-object';
|
|
3
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
4
|
+
import type { ManifestComponentDeletion } from './manifest-builder';
|
|
5
|
+
|
|
6
|
+
export interface CustomObjectFieldSectionLoadFromExistingOptions extends ExistingManifestContextOptions {
|
|
7
|
+
customObjectApiName?: string;
|
|
8
|
+
}
|
|
3
9
|
|
|
4
10
|
/**
|
|
5
11
|
* Initialization properties for {@link CustomObjectFieldSection}.
|
|
@@ -45,6 +51,15 @@ export interface CustomObjectFieldSectionProps {
|
|
|
45
51
|
* @see {@link CustomObjectPageLayout} — controls which sections appear on which tab.
|
|
46
52
|
*/
|
|
47
53
|
export class CustomObjectFieldSection {
|
|
54
|
+
static readonly componentType = 'CUSTOM_OBJECT_FIELD_SECTION' as const;
|
|
55
|
+
|
|
56
|
+
static toDeletionIdentifier(sectionId: string): ManifestComponentDeletion {
|
|
57
|
+
return {
|
|
58
|
+
type: CustomObjectFieldSection.componentType,
|
|
59
|
+
section_id: sectionId,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
private readonly _sectionId: string;
|
|
49
64
|
private readonly _name: string;
|
|
50
65
|
private readonly _model: CustomObject;
|
|
@@ -60,6 +75,27 @@ export class CustomObjectFieldSection {
|
|
|
60
75
|
customObject._register(this);
|
|
61
76
|
}
|
|
62
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Loads this field section from the active existing-manifest JSON context.
|
|
80
|
+
*/
|
|
81
|
+
static loadFromExisting(
|
|
82
|
+
sectionId: string,
|
|
83
|
+
options: CustomObjectFieldSectionLoadFromExistingOptions = {},
|
|
84
|
+
): CustomObjectFieldSection {
|
|
85
|
+
return getExistingManifestContext(options).loadFieldSection(sectionId, options);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @internal Hydrates a field section from existing manifest wire JSON. */
|
|
89
|
+
static _fromExistingComponent(
|
|
90
|
+
customObject: CustomObject,
|
|
91
|
+
component: Record<string, any>,
|
|
92
|
+
): CustomObjectFieldSection {
|
|
93
|
+
return new CustomObjectFieldSection(customObject, {
|
|
94
|
+
sectionId: component['section_id'],
|
|
95
|
+
name: component['name'] ?? component['section_id'],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
63
99
|
/**
|
|
64
100
|
* Returns the stable identifier for this section (e.g. `'sec_gm_profile'`).
|
|
65
101
|
*
|
|
@@ -91,7 +127,7 @@ export class CustomObjectFieldSection {
|
|
|
91
127
|
*/
|
|
92
128
|
toDict(): Record<string, any> {
|
|
93
129
|
return {
|
|
94
|
-
type:
|
|
130
|
+
type: CustomObjectFieldSection.componentType,
|
|
95
131
|
section_id: this._sectionId,
|
|
96
132
|
model_name: this._model.getApiName(),
|
|
97
133
|
name: this._name,
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { generateId } from './_helpers';
|
|
2
2
|
import { CustomObjectFieldSection } from './custom-object-field-section';
|
|
3
3
|
import type { CustomObject } from './custom-object';
|
|
4
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
4
5
|
import type { Field } from './field';
|
|
6
|
+
import type { ManifestComponentDeletion } from './manifest-builder';
|
|
7
|
+
|
|
8
|
+
export interface PageLayoutDeletionIdentifier {
|
|
9
|
+
layoutId: string;
|
|
10
|
+
objectRqlName: string;
|
|
11
|
+
}
|
|
5
12
|
|
|
6
13
|
// ---------------------------------------------------------------------------
|
|
7
14
|
// header_edits — HeaderEditsDTO
|
|
@@ -96,6 +103,8 @@ export interface FieldsSection {
|
|
|
96
103
|
name?: string;
|
|
97
104
|
/** Fields to display in this section. Required. */
|
|
98
105
|
fields: SectionField[];
|
|
106
|
+
/** Column layout for the fields. Optional. @default `'responsive'` (backend default). */
|
|
107
|
+
layout?: SectionFieldsLayout;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
/**
|
|
@@ -265,6 +274,10 @@ export interface CustomObjectPageLayoutProps {
|
|
|
265
274
|
blueprintKey?: string;
|
|
266
275
|
}
|
|
267
276
|
|
|
277
|
+
export interface CustomObjectPageLayoutLoadFromExistingOptions extends ExistingManifestContextOptions {
|
|
278
|
+
customObjectApiName?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
268
281
|
interface SerializedSectionField extends SectionFieldOptions {
|
|
269
282
|
fieldRqlName: string;
|
|
270
283
|
}
|
|
@@ -274,6 +287,7 @@ interface SerializedFieldsSection {
|
|
|
274
287
|
key: string;
|
|
275
288
|
name: string;
|
|
276
289
|
fields: SerializedSectionField[];
|
|
290
|
+
layout?: SectionFieldsLayout;
|
|
277
291
|
}
|
|
278
292
|
|
|
279
293
|
interface SerializedTabWithSections {
|
|
@@ -380,12 +394,14 @@ function normalizeFieldsSection(
|
|
|
380
394
|
assertSectionBelongsTo(customObject, input.section);
|
|
381
395
|
const key = input.key ?? input.section.getSectionId();
|
|
382
396
|
const name = input.name ?? input.section.getName();
|
|
383
|
-
|
|
397
|
+
const normalized: SerializedFieldsSection = {
|
|
384
398
|
key,
|
|
385
399
|
name,
|
|
386
400
|
type: 'fields_section',
|
|
387
401
|
fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, key, field)),
|
|
388
402
|
};
|
|
403
|
+
if (input.layout != null) normalized.layout = input.layout;
|
|
404
|
+
return normalized;
|
|
389
405
|
}
|
|
390
406
|
|
|
391
407
|
function normalizeTab(customObject: CustomObject, tab: PageLayoutTab): SerializedPageLayoutTab {
|
|
@@ -453,6 +469,11 @@ function normalizeTabEdits(customObject: CustomObject, tabEdits: TabEdits): Seri
|
|
|
453
469
|
return normalized;
|
|
454
470
|
}
|
|
455
471
|
|
|
472
|
+
function cloneJson<T>(value: T): T {
|
|
473
|
+
if (value == null) return value;
|
|
474
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
475
|
+
}
|
|
476
|
+
|
|
456
477
|
// ---------------------------------------------------------------------------
|
|
457
478
|
// Class
|
|
458
479
|
// ---------------------------------------------------------------------------
|
|
@@ -498,6 +519,16 @@ function normalizeTabEdits(customObject: CustomObject, tabEdits: TabEdits): Seri
|
|
|
498
519
|
* @see {@link CustomObjectPageLayout.basic} — factory for simple single-tab layouts.
|
|
499
520
|
*/
|
|
500
521
|
export class CustomObjectPageLayout {
|
|
522
|
+
static readonly componentType = 'CUSTOM_OBJECT_PAGE_LAYOUT' as const;
|
|
523
|
+
|
|
524
|
+
static toDeletionIdentifier(identifier: PageLayoutDeletionIdentifier): ManifestComponentDeletion {
|
|
525
|
+
return {
|
|
526
|
+
type: CustomObjectPageLayout.componentType,
|
|
527
|
+
layout_id: identifier.layoutId,
|
|
528
|
+
object_rql_name: identifier.objectRqlName,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
501
532
|
private readonly _layoutId: string;
|
|
502
533
|
private readonly _apiName: string;
|
|
503
534
|
private readonly _customObjectApiName: string;
|
|
@@ -526,6 +557,40 @@ export class CustomObjectPageLayout {
|
|
|
526
557
|
customObject._register(this);
|
|
527
558
|
}
|
|
528
559
|
|
|
560
|
+
/**
|
|
561
|
+
* Loads this page layout from the active existing-manifest JSON context.
|
|
562
|
+
*
|
|
563
|
+
* `id` resolves against `layout_id` first, then `api_name`. When loading by
|
|
564
|
+
* api_name such as `'default'`, pass `customObjectApiName` if multiple objects
|
|
565
|
+
* have a layout with the same api_name.
|
|
566
|
+
*/
|
|
567
|
+
static loadFromExisting(
|
|
568
|
+
layoutIdOrApiName: string,
|
|
569
|
+
options: CustomObjectPageLayoutLoadFromExistingOptions = {},
|
|
570
|
+
): CustomObjectPageLayout {
|
|
571
|
+
return getExistingManifestContext(options).loadPageLayout(layoutIdOrApiName, options);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** @internal Hydrates a page layout from existing manifest wire JSON. */
|
|
575
|
+
static _fromExistingComponent(
|
|
576
|
+
customObject: CustomObject,
|
|
577
|
+
component: Record<string, any>,
|
|
578
|
+
): CustomObjectPageLayout {
|
|
579
|
+
const layout = Object.create(CustomObjectPageLayout.prototype) as CustomObjectPageLayout;
|
|
580
|
+
Object.assign(layout as Record<string, any>, {
|
|
581
|
+
_layoutId: component['layout_id'],
|
|
582
|
+
_apiName: component['api_name'],
|
|
583
|
+
_customObjectApiName: component['object_rql_name'],
|
|
584
|
+
_name: component['name'] ?? component['api_name'],
|
|
585
|
+
_tabEdits: cloneJson(component['tab_edits'] ?? {}),
|
|
586
|
+
_headerEdits: cloneJson(component['header_edits'] ?? {}),
|
|
587
|
+
_visibilityConditions: cloneJson(component['visibility_conditions'] ?? {}),
|
|
588
|
+
_blueprintKey: component['blueprint_key'],
|
|
589
|
+
});
|
|
590
|
+
customObject._register(layout);
|
|
591
|
+
return layout;
|
|
592
|
+
}
|
|
593
|
+
|
|
529
594
|
/**
|
|
530
595
|
* Returns the stable identifier for this layout record (e.g. `'layout_gym_member'`).
|
|
531
596
|
*/
|
|
@@ -554,7 +619,7 @@ export class CustomObjectPageLayout {
|
|
|
554
619
|
*/
|
|
555
620
|
toDict(): Record<string, any> {
|
|
556
621
|
const d: Record<string, any> = {
|
|
557
|
-
type:
|
|
622
|
+
type: CustomObjectPageLayout.componentType,
|
|
558
623
|
layout_id: this._layoutId,
|
|
559
624
|
api_name: this._apiName,
|
|
560
625
|
object_rql_name: this._customObjectApiName,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Category } from './category';
|
|
2
|
-
import
|
|
2
|
+
import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
|
|
3
|
+
import type { ManifestComponent, ManifestComponentDeletion, ManifestScope } from './manifest-builder';
|
|
3
4
|
|
|
4
5
|
// ────────────────────────────────────────────────────────────────────────
|
|
5
6
|
// Platform-managed standard fields — mirrors
|
|
@@ -113,6 +114,8 @@ const STANDARD_FIELD_SPECS: ReadonlyArray<{
|
|
|
113
114
|
* (`id`, `created_at`, …) don't carry the custom-field suffix.
|
|
114
115
|
*/
|
|
115
116
|
class _StandardField implements ManifestComponent {
|
|
117
|
+
static readonly componentType = 'CUSTOM_OBJECT_FIELD' as const;
|
|
118
|
+
|
|
116
119
|
constructor(
|
|
117
120
|
private readonly customObjectApiName: string,
|
|
118
121
|
private readonly spec: (typeof STANDARD_FIELD_SPECS)[number],
|
|
@@ -120,7 +123,7 @@ class _StandardField implements ManifestComponent {
|
|
|
120
123
|
|
|
121
124
|
toDict(): Record<string, any> {
|
|
122
125
|
return {
|
|
123
|
-
type:
|
|
126
|
+
type: _StandardField.componentType,
|
|
124
127
|
custom_object_api_name: this.customObjectApiName,
|
|
125
128
|
field_api_name: this.spec.apiName,
|
|
126
129
|
field_display_name: this.spec.displayName,
|
|
@@ -215,6 +218,21 @@ export interface CustomObjectProps {
|
|
|
215
218
|
* Overrides for the auto-number name field — controls the format string and starting number.
|
|
216
219
|
* Leave `null` to use the default name field behavior.
|
|
217
220
|
*
|
|
221
|
+
* Example:
|
|
222
|
+
* ```ts
|
|
223
|
+
* nameFieldDetails: {
|
|
224
|
+
* is_autonumber_field: true,
|
|
225
|
+
* starting_number: 1,
|
|
226
|
+
* autonumber_format: 'EXP-{0000}',
|
|
227
|
+
* }
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* @remarks
|
|
231
|
+
* The backend auto-appends `{0000}` to `autonumber_format` if the format string
|
|
232
|
+
* doesn't already contain a sequence token — so `'EXP-'` becomes `'EXP-{0000}'`
|
|
233
|
+
* on the wire. Include the token explicitly when you want a different width
|
|
234
|
+
* (e.g. `'EXP-{00000}'`).
|
|
235
|
+
*
|
|
218
236
|
* Optional. @default null
|
|
219
237
|
*/
|
|
220
238
|
nameFieldDetails?: Record<string, any> | null;
|
|
@@ -241,7 +259,7 @@ export interface CustomObjectProps {
|
|
|
241
259
|
* name: 'Gym Membership Management',
|
|
242
260
|
* });
|
|
243
261
|
*
|
|
244
|
-
* const gymCategory = new Category(manifest, { apiName: 'gym__c', name: 'Gym' });
|
|
262
|
+
* const gymCategory = new Category(manifest, { apiName: 'gym__c', name: 'Gym', description: 'Gym domain' });
|
|
245
263
|
*
|
|
246
264
|
* const memberObj = new CustomObject(manifest, {
|
|
247
265
|
* apiName: 'gym_member__c',
|
|
@@ -266,8 +284,31 @@ export interface CustomObjectProps {
|
|
|
266
284
|
* with `DEPENDENCY_ERROR: Page layout config for CO (api_name=...) not found in package
|
|
267
285
|
* configs. CO requires at least one page layout in the package.`
|
|
268
286
|
* Use {@link CustomObjectPageLayout.basic} for a minimal single-tab layout.
|
|
287
|
+
*
|
|
288
|
+
* **Consider defining at least one {@link ListViewDef}.** Without a list view,
|
|
289
|
+
* the records page has nothing to display by default — users land on an empty grid.
|
|
290
|
+
* Consider shipping a "default" view with the columns most users want to see
|
|
291
|
+
* (typically `name`, the status / category field, owner, and `created_at`), sorted
|
|
292
|
+
* however the records are most commonly browsed.
|
|
293
|
+
*
|
|
294
|
+
* **Consider system fields before authoring new ones.** Every CO ships with
|
|
295
|
+
* `owner_role`, `created_by`, and `last_modified_by` (all `Employee` references),
|
|
296
|
+
* plus `name`, `created_at`, `updated_at`, `external_id`, and `id`. For any
|
|
297
|
+
* "the employee responsible for this record" semantics, consider using
|
|
298
|
+
* `owner_role` rather than adding a new {@link LookupField} targeting `'role'`.
|
|
299
|
+
* `owner_role` drives Rippling's record-level permissions, so a parallel field
|
|
300
|
+
* can fragment ownership and complicate the permission story.
|
|
269
301
|
*/
|
|
270
302
|
export class CustomObject implements ManifestScope {
|
|
303
|
+
static readonly componentType = 'CUSTOM_OBJECT' as const;
|
|
304
|
+
|
|
305
|
+
static toDeletionIdentifier(apiName: string): ManifestComponentDeletion {
|
|
306
|
+
return {
|
|
307
|
+
type: CustomObject.componentType,
|
|
308
|
+
api_name: apiName,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
271
312
|
private readonly _scope: ManifestScope;
|
|
272
313
|
private readonly _apiName: string;
|
|
273
314
|
private readonly _name: string;
|
|
@@ -304,6 +345,49 @@ export class CustomObject implements ManifestScope {
|
|
|
304
345
|
}
|
|
305
346
|
}
|
|
306
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Loads this custom object from the active existing-manifest JSON context.
|
|
350
|
+
*/
|
|
351
|
+
static loadFromExisting(apiName: string, options: ExistingManifestContextOptions = {}): CustomObject {
|
|
352
|
+
return getExistingManifestContext(options).loadCustomObject(apiName);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** @internal Hydrates a custom object from existing manifest wire JSON. */
|
|
356
|
+
static _fromExistingComponent(
|
|
357
|
+
scope: ManifestScope,
|
|
358
|
+
component: Record<string, any>,
|
|
359
|
+
resolveCategory: (apiName: string) => Category | string,
|
|
360
|
+
): CustomObject {
|
|
361
|
+
const categoryApiName = component['category_api_name'];
|
|
362
|
+
const props: CustomObjectProps = {
|
|
363
|
+
apiName: component['api_name'],
|
|
364
|
+
name: component['name'] ?? component['api_name'],
|
|
365
|
+
category: typeof categoryApiName === 'string' ? resolveCategory(categoryApiName) : categoryApiName,
|
|
366
|
+
};
|
|
367
|
+
if (component['description'] !== undefined) props.description = component['description'];
|
|
368
|
+
if (component['plural_label'] !== undefined) props.pluralLabel = component['plural_label'];
|
|
369
|
+
if (component['name_field_details'] !== undefined) {
|
|
370
|
+
props.nameFieldDetails = component['name_field_details'];
|
|
371
|
+
}
|
|
372
|
+
if (component['default_field_section_name'] !== undefined) {
|
|
373
|
+
props.defaultFieldSectionName = component['default_field_section_name'];
|
|
374
|
+
}
|
|
375
|
+
const icon = CustomObject.iconFromExistingComponent(component['icon']);
|
|
376
|
+
if (icon != null) props.icon = icon;
|
|
377
|
+
return new CustomObject(scope, props);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private static iconFromExistingComponent(value: unknown): IconConfig | undefined {
|
|
381
|
+
if (value == null || typeof value !== 'object') return undefined;
|
|
382
|
+
const wire = value as Record<string, any>;
|
|
383
|
+
const icon: IconConfig = {};
|
|
384
|
+
if (wire['emoji'] !== undefined) icon.emoji = wire['emoji'];
|
|
385
|
+
if (wire['rippling_icon'] !== undefined) icon.ripplingIcon = wire['rippling_icon'];
|
|
386
|
+
if (wire['s3_bucket'] !== undefined) icon.s3Bucket = wire['s3_bucket'];
|
|
387
|
+
if (wire['s3_key'] !== undefined) icon.s3Key = wire['s3_key'];
|
|
388
|
+
return Object.keys(icon).length > 0 ? icon : undefined;
|
|
389
|
+
}
|
|
390
|
+
|
|
307
391
|
/** @internal Scope hook — forwards child registration up to the root builder. */
|
|
308
392
|
_register(component: ManifestComponent): void {
|
|
309
393
|
this._scope._register(component);
|
|
@@ -333,7 +417,7 @@ export class CustomObject implements ManifestScope {
|
|
|
333
417
|
*/
|
|
334
418
|
toDict(): Record<string, any> {
|
|
335
419
|
const component: Record<string, any> = {
|
|
336
|
-
type:
|
|
420
|
+
type: CustomObject.componentType,
|
|
337
421
|
api_name: this._apiName,
|
|
338
422
|
name: this._name,
|
|
339
423
|
description: this._description ?? null,
|