@rippling/rippling-sdk 0.2.0-alpha.109 → 0.2.0-alpha.110

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 (250) hide show
  1. package/client.d.mts +11 -0
  2. package/client.d.mts.map +1 -1
  3. package/client.d.ts +11 -0
  4. package/client.d.ts.map +1 -1
  5. package/client.js +15 -0
  6. package/client.js.map +1 -1
  7. package/client.mjs +15 -0
  8. package/client.mjs.map +1 -1
  9. package/index.d.mts +5 -0
  10. package/index.d.mts.map +1 -1
  11. package/index.d.ts +5 -0
  12. package/index.d.ts.map +1 -1
  13. package/index.js +11 -1
  14. package/index.js.map +1 -1
  15. package/index.mjs +5 -0
  16. package/index.mjs.map +1 -1
  17. package/lib/manifest/composition.d.mts +21 -0
  18. package/lib/manifest/composition.d.mts.map +1 -0
  19. package/lib/manifest/composition.d.ts +21 -0
  20. package/lib/manifest/composition.d.ts.map +1 -0
  21. package/lib/manifest/composition.js +103 -0
  22. package/lib/manifest/composition.js.map +1 -0
  23. package/lib/manifest/composition.mjs +99 -0
  24. package/lib/manifest/composition.mjs.map +1 -0
  25. package/lib/manifest/definitions/_object-records.d.mts +23 -0
  26. package/lib/manifest/definitions/_object-records.d.mts.map +1 -0
  27. package/lib/manifest/definitions/_object-records.d.ts +23 -0
  28. package/lib/manifest/definitions/_object-records.d.ts.map +1 -0
  29. package/lib/manifest/definitions/_object-records.js +151 -0
  30. package/lib/manifest/definitions/_object-records.js.map +1 -0
  31. package/lib/manifest/definitions/_object-records.mjs +147 -0
  32. package/lib/manifest/definitions/_object-records.mjs.map +1 -0
  33. package/lib/manifest/definitions/action.d.mts +7 -0
  34. package/lib/manifest/definitions/action.d.mts.map +1 -0
  35. package/lib/manifest/definitions/action.d.ts +7 -0
  36. package/lib/manifest/definitions/action.d.ts.map +1 -0
  37. package/lib/manifest/definitions/action.js +25 -0
  38. package/lib/manifest/definitions/action.js.map +1 -0
  39. package/lib/manifest/definitions/action.mjs +22 -0
  40. package/lib/manifest/definitions/action.mjs.map +1 -0
  41. package/lib/manifest/definitions/agent.d.mts +51 -0
  42. package/lib/manifest/definitions/agent.d.mts.map +1 -0
  43. package/lib/manifest/definitions/agent.d.ts +51 -0
  44. package/lib/manifest/definitions/agent.d.ts.map +1 -0
  45. package/lib/manifest/definitions/agent.js +65 -0
  46. package/lib/manifest/definitions/agent.js.map +1 -0
  47. package/lib/manifest/definitions/agent.mjs +59 -0
  48. package/lib/manifest/definitions/agent.mjs.map +1 -0
  49. package/lib/manifest/definitions/app.d.mts +3 -0
  50. package/lib/manifest/definitions/app.d.mts.map +1 -0
  51. package/lib/manifest/definitions/app.d.ts +3 -0
  52. package/lib/manifest/definitions/app.d.ts.map +1 -0
  53. package/lib/manifest/definitions/app.js +14 -0
  54. package/lib/manifest/definitions/app.js.map +1 -0
  55. package/lib/manifest/definitions/app.mjs +11 -0
  56. package/lib/manifest/definitions/app.mjs.map +1 -0
  57. package/lib/manifest/definitions/category.d.mts +9 -0
  58. package/lib/manifest/definitions/category.d.mts.map +1 -0
  59. package/lib/manifest/definitions/category.d.ts +9 -0
  60. package/lib/manifest/definitions/category.d.ts.map +1 -0
  61. package/lib/manifest/definitions/category.js +27 -0
  62. package/lib/manifest/definitions/category.js.map +1 -0
  63. package/lib/manifest/definitions/category.mjs +23 -0
  64. package/lib/manifest/definitions/category.mjs.map +1 -0
  65. package/lib/manifest/definitions/component.d.mts +101 -0
  66. package/lib/manifest/definitions/component.d.mts.map +1 -0
  67. package/lib/manifest/definitions/component.d.ts +101 -0
  68. package/lib/manifest/definitions/component.d.ts.map +1 -0
  69. package/lib/manifest/definitions/component.js +106 -0
  70. package/lib/manifest/definitions/component.js.map +1 -0
  71. package/lib/manifest/definitions/component.mjs +97 -0
  72. package/lib/manifest/definitions/component.mjs.map +1 -0
  73. package/lib/manifest/definitions/custom-field-section.d.mts +13 -0
  74. package/lib/manifest/definitions/custom-field-section.d.mts.map +1 -0
  75. package/lib/manifest/definitions/custom-field-section.d.ts +13 -0
  76. package/lib/manifest/definitions/custom-field-section.d.ts.map +1 -0
  77. package/lib/manifest/definitions/custom-field-section.js +15 -0
  78. package/lib/manifest/definitions/custom-field-section.js.map +1 -0
  79. package/lib/manifest/definitions/custom-field-section.mjs +12 -0
  80. package/lib/manifest/definitions/custom-field-section.mjs.map +1 -0
  81. package/lib/manifest/definitions/custom-field.d.mts +10 -0
  82. package/lib/manifest/definitions/custom-field.d.mts.map +1 -0
  83. package/lib/manifest/definitions/custom-field.d.ts +10 -0
  84. package/lib/manifest/definitions/custom-field.d.ts.map +1 -0
  85. package/lib/manifest/definitions/custom-field.js +18 -0
  86. package/lib/manifest/definitions/custom-field.js.map +1 -0
  87. package/lib/manifest/definitions/custom-field.mjs +15 -0
  88. package/lib/manifest/definitions/custom-field.mjs.map +1 -0
  89. package/lib/manifest/definitions/custom-object.d.mts +162 -0
  90. package/lib/manifest/definitions/custom-object.d.mts.map +1 -0
  91. package/lib/manifest/definitions/custom-object.d.ts +162 -0
  92. package/lib/manifest/definitions/custom-object.d.ts.map +1 -0
  93. package/lib/manifest/definitions/custom-object.js +412 -0
  94. package/lib/manifest/definitions/custom-object.js.map +1 -0
  95. package/lib/manifest/definitions/custom-object.mjs +386 -0
  96. package/lib/manifest/definitions/custom-object.mjs.map +1 -0
  97. package/lib/manifest/definitions/custom-skill.d.mts +8 -0
  98. package/lib/manifest/definitions/custom-skill.d.mts.map +1 -0
  99. package/lib/manifest/definitions/custom-skill.d.ts +8 -0
  100. package/lib/manifest/definitions/custom-skill.d.ts.map +1 -0
  101. package/lib/manifest/definitions/custom-skill.js +15 -0
  102. package/lib/manifest/definitions/custom-skill.js.map +1 -0
  103. package/lib/manifest/definitions/custom-skill.mjs +12 -0
  104. package/lib/manifest/definitions/custom-skill.mjs.map +1 -0
  105. package/lib/manifest/definitions/field-section.d.mts +7 -0
  106. package/lib/manifest/definitions/field-section.d.mts.map +1 -0
  107. package/lib/manifest/definitions/field-section.d.ts +7 -0
  108. package/lib/manifest/definitions/field-section.d.ts.map +1 -0
  109. package/lib/manifest/definitions/field-section.js +16 -0
  110. package/lib/manifest/definitions/field-section.js.map +1 -0
  111. package/lib/manifest/definitions/field-section.mjs +13 -0
  112. package/lib/manifest/definitions/field-section.mjs.map +1 -0
  113. package/lib/manifest/definitions/field.d.mts +38 -0
  114. package/lib/manifest/definitions/field.d.mts.map +1 -0
  115. package/lib/manifest/definitions/field.d.ts +38 -0
  116. package/lib/manifest/definitions/field.d.ts.map +1 -0
  117. package/lib/manifest/definitions/field.js +50 -0
  118. package/lib/manifest/definitions/field.js.map +1 -0
  119. package/lib/manifest/definitions/field.mjs +45 -0
  120. package/lib/manifest/definitions/field.mjs.map +1 -0
  121. package/lib/manifest/definitions/function.d.mts +61 -0
  122. package/lib/manifest/definitions/function.d.mts.map +1 -0
  123. package/lib/manifest/definitions/function.d.ts +61 -0
  124. package/lib/manifest/definitions/function.d.ts.map +1 -0
  125. package/lib/manifest/definitions/function.js +79 -0
  126. package/lib/manifest/definitions/function.js.map +1 -0
  127. package/lib/manifest/definitions/function.mjs +73 -0
  128. package/lib/manifest/definitions/function.mjs.map +1 -0
  129. package/lib/manifest/definitions/index.d.mts +22 -0
  130. package/lib/manifest/definitions/index.d.mts.map +1 -0
  131. package/lib/manifest/definitions/index.d.ts +22 -0
  132. package/lib/manifest/definitions/index.d.ts.map +1 -0
  133. package/lib/manifest/definitions/index.js +64 -0
  134. package/lib/manifest/definitions/index.js.map +1 -0
  135. package/lib/manifest/definitions/index.mjs +22 -0
  136. package/lib/manifest/definitions/index.mjs.map +1 -0
  137. package/lib/manifest/definitions/list-view.d.mts +9 -0
  138. package/lib/manifest/definitions/list-view.d.mts.map +1 -0
  139. package/lib/manifest/definitions/list-view.d.ts +9 -0
  140. package/lib/manifest/definitions/list-view.d.ts.map +1 -0
  141. package/lib/manifest/definitions/list-view.js +25 -0
  142. package/lib/manifest/definitions/list-view.js.map +1 -0
  143. package/lib/manifest/definitions/list-view.mjs +22 -0
  144. package/lib/manifest/definitions/list-view.mjs.map +1 -0
  145. package/lib/manifest/definitions/object-search-config.d.mts +13 -0
  146. package/lib/manifest/definitions/object-search-config.d.mts.map +1 -0
  147. package/lib/manifest/definitions/object-search-config.d.ts +13 -0
  148. package/lib/manifest/definitions/object-search-config.d.ts.map +1 -0
  149. package/lib/manifest/definitions/object-search-config.js +25 -0
  150. package/lib/manifest/definitions/object-search-config.js.map +1 -0
  151. package/lib/manifest/definitions/object-search-config.mjs +22 -0
  152. package/lib/manifest/definitions/object-search-config.mjs.map +1 -0
  153. package/lib/manifest/definitions/object-settings.d.mts +24 -0
  154. package/lib/manifest/definitions/object-settings.d.mts.map +1 -0
  155. package/lib/manifest/definitions/object-settings.d.ts +24 -0
  156. package/lib/manifest/definitions/object-settings.d.ts.map +1 -0
  157. package/lib/manifest/definitions/object-settings.js +32 -0
  158. package/lib/manifest/definitions/object-settings.js.map +1 -0
  159. package/lib/manifest/definitions/object-settings.mjs +29 -0
  160. package/lib/manifest/definitions/object-settings.mjs.map +1 -0
  161. package/lib/manifest/definitions/page-layout.d.mts +21 -0
  162. package/lib/manifest/definitions/page-layout.d.mts.map +1 -0
  163. package/lib/manifest/definitions/page-layout.d.ts +21 -0
  164. package/lib/manifest/definitions/page-layout.d.ts.map +1 -0
  165. package/lib/manifest/definitions/page-layout.js +155 -0
  166. package/lib/manifest/definitions/page-layout.js.map +1 -0
  167. package/lib/manifest/definitions/page-layout.mjs +152 -0
  168. package/lib/manifest/definitions/page-layout.mjs.map +1 -0
  169. package/lib/manifest/definitions/page.d.mts +8 -0
  170. package/lib/manifest/definitions/page.d.mts.map +1 -0
  171. package/lib/manifest/definitions/page.d.ts +8 -0
  172. package/lib/manifest/definitions/page.d.ts.map +1 -0
  173. package/lib/manifest/definitions/page.js +15 -0
  174. package/lib/manifest/definitions/page.js.map +1 -0
  175. package/lib/manifest/definitions/page.mjs +12 -0
  176. package/lib/manifest/definitions/page.mjs.map +1 -0
  177. package/lib/manifest/definitions/record-triggered-flow.d.mts +24 -0
  178. package/lib/manifest/definitions/record-triggered-flow.d.mts.map +1 -0
  179. package/lib/manifest/definitions/record-triggered-flow.d.ts +24 -0
  180. package/lib/manifest/definitions/record-triggered-flow.d.ts.map +1 -0
  181. package/lib/manifest/definitions/record-triggered-flow.js +28 -0
  182. package/lib/manifest/definitions/record-triggered-flow.js.map +1 -0
  183. package/lib/manifest/definitions/record-triggered-flow.mjs +25 -0
  184. package/lib/manifest/definitions/record-triggered-flow.mjs.map +1 -0
  185. package/lib/manifest/definitions/validation.d.mts +8 -0
  186. package/lib/manifest/definitions/validation.d.mts.map +1 -0
  187. package/lib/manifest/definitions/validation.d.ts +8 -0
  188. package/lib/manifest/definitions/validation.d.ts.map +1 -0
  189. package/lib/manifest/definitions/validation.js +24 -0
  190. package/lib/manifest/definitions/validation.js.map +1 -0
  191. package/lib/manifest/definitions/validation.mjs +21 -0
  192. package/lib/manifest/definitions/validation.mjs.map +1 -0
  193. package/lib/manifest/definitions/workflow.d.mts +10 -0
  194. package/lib/manifest/definitions/workflow.d.mts.map +1 -0
  195. package/lib/manifest/definitions/workflow.d.ts +10 -0
  196. package/lib/manifest/definitions/workflow.d.ts.map +1 -0
  197. package/lib/manifest/definitions/workflow.js +33 -0
  198. package/lib/manifest/definitions/workflow.js.map +1 -0
  199. package/lib/manifest/definitions/workflow.mjs +29 -0
  200. package/lib/manifest/definitions/workflow.mjs.map +1 -0
  201. package/lib/manifest/index.d.mts +3 -0
  202. package/lib/manifest/index.d.mts.map +1 -1
  203. package/lib/manifest/index.d.ts +3 -0
  204. package/lib/manifest/index.d.ts.map +1 -1
  205. package/lib/manifest/index.js +4 -0
  206. package/lib/manifest/index.js.map +1 -1
  207. package/lib/manifest/index.mjs +3 -0
  208. package/lib/manifest/index.mjs.map +1 -1
  209. package/lib/manifest/package-manifest.d.mts +17 -0
  210. package/lib/manifest/package-manifest.d.mts.map +1 -0
  211. package/lib/manifest/package-manifest.d.ts +17 -0
  212. package/lib/manifest/package-manifest.d.ts.map +1 -0
  213. package/lib/manifest/package-manifest.js +26 -0
  214. package/lib/manifest/package-manifest.js.map +1 -0
  215. package/lib/manifest/package-manifest.mjs +23 -0
  216. package/lib/manifest/package-manifest.mjs.map +1 -0
  217. package/package.json +1 -1
  218. package/src/client.ts +24 -0
  219. package/src/index.ts +17 -0
  220. package/src/lib/manifest/composition.ts +133 -0
  221. package/src/lib/manifest/definitions/README.md +89 -0
  222. package/src/lib/manifest/definitions/_object-records.ts +218 -0
  223. package/src/lib/manifest/definitions/action.ts +31 -0
  224. package/src/lib/manifest/definitions/agent.ts +149 -0
  225. package/src/lib/manifest/definitions/app.ts +12 -0
  226. package/src/lib/manifest/definitions/category.ts +35 -0
  227. package/src/lib/manifest/definitions/component.ts +241 -0
  228. package/src/lib/manifest/definitions/custom-field-section.ts +39 -0
  229. package/src/lib/manifest/definitions/custom-field.ts +32 -0
  230. package/src/lib/manifest/definitions/custom-object.ts +786 -0
  231. package/src/lib/manifest/definitions/custom-skill.ts +28 -0
  232. package/src/lib/manifest/definitions/field-section.ts +31 -0
  233. package/src/lib/manifest/definitions/field.ts +119 -0
  234. package/src/lib/manifest/definitions/function.ts +198 -0
  235. package/src/lib/manifest/definitions/index.ts +91 -0
  236. package/src/lib/manifest/definitions/list-view.ts +48 -0
  237. package/src/lib/manifest/definitions/object-search-config.ts +44 -0
  238. package/src/lib/manifest/definitions/object-settings.ts +61 -0
  239. package/src/lib/manifest/definitions/page-layout.ts +218 -0
  240. package/src/lib/manifest/definitions/page.ts +28 -0
  241. package/src/lib/manifest/definitions/record-triggered-flow.ts +71 -0
  242. package/src/lib/manifest/definitions/validation.ts +46 -0
  243. package/src/lib/manifest/definitions/workflow.ts +62 -0
  244. package/src/lib/manifest/index.ts +4 -0
  245. package/src/lib/manifest/package-manifest.ts +40 -0
  246. package/src/version.ts +1 -1
  247. package/version.d.mts +1 -1
  248. package/version.d.ts +1 -1
  249. package/version.js +1 -1
  250. package/version.mjs +1 -1
@@ -0,0 +1,218 @@
1
+ import {
2
+ createCustomObjectRecordAdapter,
3
+ CustomObjectRecordValidationError,
4
+ } from '../../../core/platform/custom-objects/manager';
5
+ import {
6
+ validateCreateInput,
7
+ validateUpdateInput,
8
+ type ValidationIssue,
9
+ } from '../../../core/platform/custom-objects/validation';
10
+ import type {
11
+ AnyCustomObjectDefinition,
12
+ AnyFieldDefinition,
13
+ CreateInput,
14
+ UpdateInput,
15
+ FieldValue,
16
+ RecordShape,
17
+ } from '../../../core/platform/custom-objects/types';
18
+ import type { CustomObjectDataRow } from '../../../resources/custom-objects/records';
19
+ import type { ObjectRecords, ObjectRecordsContext } from './custom-object';
20
+
21
+ type NullableFields<D extends AnyCustomObjectDefinition> = {
22
+ [K in keyof D['fields']]: D['fields'][K] extends { writable: true; required: false } ? K : never;
23
+ }[keyof D['fields']];
24
+
25
+ /** Definition-based writes accept null for writable, non-required custom fields. */
26
+ export type ObjectCreateInput<D extends AnyCustomObjectDefinition> = Omit<
27
+ CreateInput<D>,
28
+ NullableFields<D>
29
+ > & {
30
+ [K in NullableFields<D>]?: FieldValue<D['fields'][K]> | null;
31
+ };
32
+
33
+ export type ObjectUpdateInput<D extends AnyCustomObjectDefinition> = Omit<
34
+ UpdateInput<D>,
35
+ NullableFields<D>
36
+ > & {
37
+ [K in NullableFields<D>]?: FieldValue<D['fields'][K]> | null;
38
+ };
39
+
40
+ export type ObjectBulkUpdateInput<D extends AnyCustomObjectDefinition> = {
41
+ readonly id: string;
42
+ } & ObjectUpdateInput<D>;
43
+
44
+ /** Reuse the existing value checks, excluding only explicitly allowed null custom-field values. */
45
+ function inputIssues(
46
+ definition: AnyCustomObjectDefinition,
47
+ input: Record<string, unknown>,
48
+ operation: 'create' | 'update',
49
+ ): readonly ValidationIssue[] {
50
+ if (input == null || typeof input !== 'object' || Array.isArray(input))
51
+ return [{ path: '', message: 'Expected a record object.' }];
52
+ const checked = { ...input };
53
+ for (const [key, field] of Object.entries(definition.fields)) {
54
+ if (field.writable && !field.required && checked[key] === null) delete checked[key];
55
+ }
56
+ return (operation === 'create' ? validateCreateInput : validateUpdateInput)(definition, checked).issues;
57
+ }
58
+
59
+ export function assertObjectInput(
60
+ definition: AnyCustomObjectDefinition,
61
+ input: Record<string, unknown>,
62
+ operation: 'create' | 'update',
63
+ ): void {
64
+ const issues = inputIssues(definition, input, operation);
65
+ if (issues.length > 0) throw new CustomObjectRecordValidationError(definition, operation, issues);
66
+ }
67
+
68
+ function referenceID(value: unknown): unknown {
69
+ return value != null && typeof value === 'object' && 'id' in value ? value.id : value;
70
+ }
71
+
72
+ function serializeInput(
73
+ definition: AnyCustomObjectDefinition,
74
+ input: Record<string, unknown>,
75
+ ): Record<string, unknown> {
76
+ const wire: Record<string, unknown> = {};
77
+ for (const key of ['name', 'external_id', 'owner_role']) {
78
+ if (Object.prototype.hasOwnProperty.call(input, key) && input[key] !== undefined)
79
+ wire[key] = key === 'owner_role' ? referenceID(input[key]) : input[key];
80
+ }
81
+ for (const [key, field] of Object.entries(definition.fields)) {
82
+ if (!Object.prototype.hasOwnProperty.call(input, key) || input[key] === undefined) continue;
83
+ wire[field.apiName] =
84
+ field.kind === 'lookup' || field.kind === 'parent-child' || field.kind === 'file' ?
85
+ referenceID(input[key])
86
+ : input[key];
87
+ }
88
+ return wire;
89
+ }
90
+
91
+ function lookup(value: unknown, apiName: string) {
92
+ if (typeof value === 'string') return { id: value, apiName };
93
+ if (value != null && typeof value === 'object' && 'id' in value && typeof value.id === 'string') {
94
+ return {
95
+ id: value.id,
96
+ apiName: 'apiName' in value && typeof value.apiName === 'string' ? value.apiName : apiName,
97
+ };
98
+ }
99
+ return null;
100
+ }
101
+
102
+ function fieldValue(field: AnyFieldDefinition, value: unknown): unknown {
103
+ if (value == null) return null;
104
+ switch (field.kind) {
105
+ case 'lookup':
106
+ case 'parent-child':
107
+ return lookup(value, field.targetApiName ?? 'unknown');
108
+ case 'file':
109
+ return (
110
+ typeof value === 'string' ? value
111
+ : value != null && typeof value === 'object' && 'id' in value && typeof value.id === 'string' ?
112
+ { id: value.id }
113
+ : null
114
+ );
115
+ case 'number':
116
+ case 'currency':
117
+ case 'percentage':
118
+ case 'formula-number':
119
+ case 'formula-currency':
120
+ case 'formula-percentage': {
121
+ const numeric =
122
+ typeof value === 'number' ? value
123
+ : typeof value === 'string' && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(value) ?
124
+ Number(value)
125
+ : NaN;
126
+ if (!Number.isFinite(numeric))
127
+ throw new TypeError(`Invalid numeric value for ${field.apiName}: expected a finite number.`);
128
+ return numeric;
129
+ }
130
+ default:
131
+ return value;
132
+ }
133
+ }
134
+
135
+ function decodeRecord<D extends AnyCustomObjectDefinition>(
136
+ definition: D,
137
+ row: CustomObjectDataRow,
138
+ ): RecordShape<D> {
139
+ const record: Record<string, unknown> = { id: row.id, name: row.name };
140
+ for (const key of ['created_at', 'updated_at', 'system_updated_at', 'external_id'])
141
+ record[key] = typeof row[key] === 'string' ? row[key] : null;
142
+ for (const key of ['owner_role', 'created_by', 'last_modified_by'])
143
+ record[key] = lookup(row[key], 'Employee');
144
+ for (const [key, field] of Object.entries(definition.fields))
145
+ record[key] = fieldValue(field as AnyFieldDefinition, row[field.apiName]);
146
+ return record as RecordShape<D>;
147
+ }
148
+
149
+ /** Definition-specific nullability and decoding; the shared manager and legacy constructors are unchanged. */
150
+ export function createObjectRecords<D extends AnyCustomObjectDefinition>(
151
+ definition: D,
152
+ ): Readonly<ObjectRecords<D>> {
153
+ const adapter = (ctx: ObjectRecordsContext) => createCustomObjectRecordAdapter(ctx.customObjects.records);
154
+ const decode = (row: CustomObjectDataRow) => decodeRecord(definition, row);
155
+ const serializeBatch = (rows: readonly Record<string, unknown>[], operation: 'create' | 'update') => {
156
+ const issues = rows.flatMap((row, index) => {
157
+ if (row == null || typeof row !== 'object' || Array.isArray(row))
158
+ return [{ path: `[${index}]`, message: 'Expected a record object.' }];
159
+ const { id, ...patch } = row;
160
+ return [
161
+ ...(operation === 'update' && (typeof id !== 'string' || id.length === 0) ?
162
+ [{ path: `[${index}].id`, message: 'Expected a record id string.' }]
163
+ : []),
164
+ ...inputIssues(definition, operation === 'update' ? patch : row, operation).map((issue) => ({
165
+ ...issue,
166
+ path: `[${index}].${issue.path}`,
167
+ })),
168
+ ];
169
+ });
170
+ if (issues.length > 0)
171
+ throw new CustomObjectRecordValidationError(
172
+ definition,
173
+ operation === 'create' ? 'bulk-create' : 'bulk-update',
174
+ issues,
175
+ );
176
+ return rows.map((row) =>
177
+ operation === 'create' ?
178
+ serializeInput(definition, row)
179
+ : { id: row['id'], ...serializeInput(definition, row) },
180
+ );
181
+ };
182
+ const query = async (ctx: ObjectRecordsContext, options: Parameters<ObjectRecords<D>['query']>[1]) => {
183
+ const page = await adapter(ctx).query(definition.apiName, options ?? {});
184
+ return { cursor: page.cursor, results: page.results.map(decode) };
185
+ };
186
+ return Object.freeze<ObjectRecords<D>>({
187
+ async create(ctx, input) {
188
+ assertObjectInput(definition, input, 'create');
189
+ return decode(await adapter(ctx).create(definition.apiName, serializeInput(definition, input)));
190
+ },
191
+ async createMany(ctx, rows, options) {
192
+ const rowsToWrite = serializeBatch(rows, 'create');
193
+ return (await adapter(ctx).bulkCreate(definition.apiName, { ...options, rowsToWrite })).map(decode);
194
+ },
195
+ async get(ctx, recordID) {
196
+ return decode(await adapter(ctx).retrieve(definition.apiName, recordID));
197
+ },
198
+ async getByExternalID(ctx, externalID) {
199
+ return decode(await adapter(ctx).retrieveByExternalID(definition.apiName, externalID));
200
+ },
201
+ async update(ctx, recordID, patch) {
202
+ assertObjectInput(definition, patch, 'update');
203
+ return decode(
204
+ await adapter(ctx).update(definition.apiName, recordID, serializeInput(definition, patch)),
205
+ );
206
+ },
207
+ async updateMany(ctx, rows, options) {
208
+ const rowsToUpdate = serializeBatch(rows, 'update');
209
+ return (await adapter(ctx).bulkUpdate(definition.apiName, { ...options, rowsToUpdate })).map(decode);
210
+ },
211
+ delete: (ctx, recordID) => adapter(ctx).delete(definition.apiName, recordID),
212
+ deleteMany: (ctx, recordIDs, options) =>
213
+ adapter(ctx).bulkDelete(definition.apiName, { ...options, rowsToDelete: recordIDs }),
214
+ list: (ctx, { where, ...options } = {}) =>
215
+ query(ctx, { ...options, ...(where === undefined ? {} : { query: where.text }) }),
216
+ query,
217
+ });
218
+ }
@@ -0,0 +1,31 @@
1
+ import { defineComponent, withApiName, validateCustomApiName, type ComponentDefinition } from './component';
2
+ import { CustomObjectAction, type CustomObjectActionProps } from '../custom-object-action';
3
+ import { manifestObject, type ObjectReference } from './custom-object';
4
+
5
+ export type DefineActionProps = Omit<CustomObjectActionProps, 'actionId' | 'apiName'> & {
6
+ apiName: `${string}__c`;
7
+ };
8
+
9
+ export function defineAction(object: ObjectReference, props: DefineActionProps): CustomObjectAction {
10
+ validateCustomApiName(props.apiName);
11
+ const action = withApiName(
12
+ new CustomObjectAction(manifestObject(object), { ...props, actionId: props.apiName }),
13
+ props.apiName,
14
+ );
15
+ currentFunction.set(action, props.function);
16
+ const setFunction = action.setFunction.bind(action);
17
+ action.setFunction = (definition) => {
18
+ setFunction(definition);
19
+ currentFunction.set(action, definition);
20
+ return action;
21
+ };
22
+ return actionComponent.register(action, object, props);
23
+ }
24
+
25
+ const currentFunction = new WeakMap<CustomObjectAction, CustomObjectActionProps['function']>();
26
+
27
+ const actionComponent: ComponentDefinition<typeof defineAction> = defineComponent({
28
+ type: 'CUSTOM_OBJECT_ACTION',
29
+ define: defineAction,
30
+ references: (definition, object) => () => [object, currentFunction.get(definition)!],
31
+ });
@@ -0,0 +1,149 @@
1
+ import { ACPAgent, type ACPAgentProps, type ACPAgentSourceFile } from '../acp-agent';
2
+ import { detachedManifestScope, defineComponent, sourceEntry, validateCustomApiName } from './component';
3
+ import type { RipplingSDK } from '../../../client';
4
+ import type { RequestOptions } from '../../../internal/request-options';
5
+ import type {
6
+ SessionCreateParams,
7
+ SessionListParams,
8
+ SessionCreateResponse,
9
+ SessionListResponse,
10
+ SessionListResponsesPageCursorURL,
11
+ } from '../../../resources/agents/sessions/sessions';
12
+ import type { AgentRetrieveResponse } from '../../../resources/agents/agents';
13
+ import type { APIPromise } from '../../../core/api-promise';
14
+ import type { PagePromise } from '../../../core/pagination';
15
+
16
+ export type DefinedAgentProps = { apiName: `${string}__c` } & (
17
+ | (ACPAgentProps & { source?: never })
18
+ | (Omit<ACPAgentProps, 'files' | 'entrypoint'> & { source: URL; files?: never; entrypoint?: never })
19
+ );
20
+
21
+ /** Authenticated agent resources supplied per call. */
22
+ export interface AgentOperationsContext {
23
+ readonly agents: {
24
+ readonly retrieve: RipplingSDK['agents']['retrieve'];
25
+ readonly sessions: Pick<RipplingSDK['agents']['sessions'], 'create' | 'list'>;
26
+ };
27
+ }
28
+
29
+ export interface AgentSessions {
30
+ create(
31
+ ctx: AgentOperationsContext,
32
+ params: Omit<SessionCreateParams, 'agent_id'>,
33
+ options?: RequestOptions,
34
+ ): APIPromise<SessionCreateResponse>;
35
+ list(
36
+ ctx: AgentOperationsContext,
37
+ params?: SessionListParams,
38
+ options?: RequestOptions,
39
+ ): PagePromise<SessionListResponsesPageCursorURL, SessionListResponse>;
40
+ }
41
+
42
+ export interface AgentOperations {
43
+ retrieve(ctx: AgentOperationsContext, options?: RequestOptions): APIPromise<AgentRetrieveResponse>;
44
+ sessions: AgentSessions;
45
+ }
46
+
47
+ export interface AgentClient {
48
+ retrieve(options?: RequestOptions): APIPromise<AgentRetrieveResponse>;
49
+ sessions: {
50
+ create(
51
+ params: Omit<SessionCreateParams, 'agent_id'>,
52
+ options?: RequestOptions,
53
+ ): APIPromise<SessionCreateResponse>;
54
+ list(
55
+ params?: SessionListParams,
56
+ options?: RequestOptions,
57
+ ): PagePromise<SessionListResponsesPageCursorURL, SessionListResponse>;
58
+ };
59
+ }
60
+
61
+ export function defineAgent(props: DefinedAgentProps): DefinedAgent {
62
+ if (props.source !== undefined && (props.files !== undefined || props.entrypoint !== undefined))
63
+ throw new Error('Define an agent with either source or files/entrypoint, not both.');
64
+ return agentComponent.register(new DefinedAgent(props), props);
65
+ }
66
+
67
+ export class DefinedAgent extends ACPAgent {
68
+ private _source: URL | undefined;
69
+ readonly retrieve: (
70
+ ctx: AgentOperationsContext,
71
+ options?: RequestOptions,
72
+ ) => APIPromise<AgentRetrieveResponse>;
73
+ readonly sessions: AgentSessions;
74
+ constructor(props: DefinedAgentProps) {
75
+ validateCustomApiName(props.apiName);
76
+ if (props.source !== undefined && props.source.protocol !== 'file:')
77
+ throw new Error('Component source must be a file URL.');
78
+ super(
79
+ detachedManifestScope,
80
+ props.source !== undefined ? { ...props, files: [], entrypoint: 'index.ts' } : (props as ACPAgentProps),
81
+ );
82
+ this._source = props.source === undefined ? undefined : new URL(props.source.href);
83
+ const operations = createAgentOperations(this);
84
+ this.retrieve = operations.retrieve;
85
+ this.sessions = operations.sessions;
86
+ }
87
+
88
+ get source(): URL | undefined {
89
+ return this._source;
90
+ }
91
+
92
+ /** Replaces inline files or a file-backed source with the supplied files and entrypoint. */
93
+ override setSource(files: ACPAgentSourceFile[], entrypoint: string): this {
94
+ super.setSource(files, entrypoint);
95
+ this._source = undefined;
96
+ return this;
97
+ }
98
+ }
99
+
100
+ interface AgentMetadata {
101
+ type: 'ACP_AGENT';
102
+ api_name: string;
103
+ source_agent_id: string;
104
+ name: string;
105
+ entrypoint: string;
106
+ files: { type: 'ACP_AGENT'; path: string; content: string; kind: string }[];
107
+ }
108
+
109
+ /** @internal Shared with the legacy client helper, including constructor-based agent references. */
110
+ function createAgentOperations(definition: ACPAgent): AgentOperations {
111
+ return {
112
+ retrieve: (ctx: AgentOperationsContext, options?: RequestOptions) =>
113
+ ctx.agents.retrieve(definition.getApiName(), options),
114
+ sessions: {
115
+ create: (
116
+ ctx: AgentOperationsContext,
117
+ params: Omit<SessionCreateParams, 'agent_id'>,
118
+ options?: RequestOptions,
119
+ ) => ctx.agents.sessions.create({ ...params, agent_id: definition.getApiName() }, options),
120
+ list: (ctx: AgentOperationsContext, params?: SessionListParams, options?: RequestOptions) =>
121
+ ctx.agents.sessions.list(definition.getApiName(), params, options),
122
+ },
123
+ };
124
+ }
125
+
126
+ const agentComponent = defineComponent({
127
+ type: 'ACP_AGENT',
128
+ define: defineAgent,
129
+ serialize: (definition) =>
130
+ sourceEntry(definition.toDict() as AgentMetadata, definition.source, (config, content) => ({
131
+ ...config,
132
+ files: [{ type: 'ACP_AGENT' as const, path: 'index.ts', content, kind: 'typescript' }],
133
+ entrypoint: 'index.ts',
134
+ })),
135
+ });
136
+
137
+ /** @internal Forward client helpers to the definition operations. */
138
+ export function createAgentClient(ctx: AgentOperationsContext, definition: ACPAgent): AgentClient {
139
+ const operations = createAgentOperations(definition);
140
+ return {
141
+ retrieve: (options?: RequestOptions) => operations.retrieve(ctx, options),
142
+ sessions: {
143
+ create: (params: Omit<SessionCreateParams, 'agent_id'>, options?: RequestOptions) =>
144
+ operations.sessions.create(ctx, params, options),
145
+ list: (params?: SessionListParams, options?: RequestOptions) =>
146
+ operations.sessions.list(ctx, params, options),
147
+ },
148
+ };
149
+ }
@@ -0,0 +1,12 @@
1
+ import { defineComponent, detachedManifestScope, type ComponentDefinition } from './component';
2
+ import { App, type AppProps } from '../app';
3
+
4
+ export function defineApp(props: AppProps): App {
5
+ return appComponent.register(new App(detachedManifestScope, props), props);
6
+ }
7
+
8
+ const appComponent: ComponentDefinition<typeof defineApp> = defineComponent({
9
+ type: 'CUSTOM_APP',
10
+ define: defineApp,
11
+ references: (_definition, props) => props.pages,
12
+ });
@@ -0,0 +1,35 @@
1
+ import type { CustomObjectCategoryDefinition } from '../../../core/platform/custom-objects/category';
2
+ import { Category, type CategoryProps } from '../category';
3
+ import { detachedManifestScope, defineComponent, validateCustomApiName } from './component';
4
+
5
+ export type DefineCustomObjectCategoryProps<TApiName extends `${string}__c` = `${string}__c`> =
6
+ CategoryProps & { apiName: TApiName };
7
+
8
+ export function defineCategory<const TApiName extends `${string}__c`>(
9
+ props: DefineCustomObjectCategoryProps<TApiName>,
10
+ ): Category & CustomObjectCategoryDefinition<TApiName extends `${infer N}__c` ? N : never>;
11
+ export function defineCategory(props: CategoryProps): Category | CustomObjectCategoryDefinition {
12
+ validateCustomApiName(props.apiName);
13
+ return categoryComponent.register(
14
+ Object.assign(new Category(detachedManifestScope, props), {
15
+ apiName: props.apiName,
16
+ name: props.apiName.replace(/__c$/, ''),
17
+ displayName: props.name,
18
+ description: props.description,
19
+ }),
20
+ props,
21
+ );
22
+ }
23
+
24
+ export const defineCustomObjectCategory = defineCategory;
25
+
26
+ export const categoryComponent = defineComponent({
27
+ type: 'CUSTOM_CATEGORY',
28
+ define: defineCategory,
29
+ serialize: (definition) => ({
30
+ type: 'CUSTOM_CATEGORY',
31
+ api_name: definition.apiName,
32
+ name: definition.displayName,
33
+ description: definition.description ?? '',
34
+ }),
35
+ });
@@ -0,0 +1,241 @@
1
+ import type { ManifestComponentType, ManifestScope, ManifestComponent } from '../manifest-builder';
2
+
3
+ export type ComponentType = ManifestComponentType | 'CUSTOM_OBJECT_SETTINGS' | 'CUSTOM_OBJECT_SEARCH_CONFIG';
4
+
5
+ /** New definitions compose explicitly; legacy constructors still register through their original scopes. */
6
+ export const detachedManifestScope: ManifestScope = { _register() {} };
7
+
8
+ /** Shared by components whose backend writers require the custom API-name suffix. */
9
+ export function validateCustomApiName(apiName: string): void {
10
+ if (typeof apiName !== 'string' || !apiName.endsWith('__c'))
11
+ throw new Error(`apiName must end with __c: ${apiName}`);
12
+ }
13
+
14
+ /** Decorate only a newly created instance; the existing class and serializer stay unchanged. */
15
+ export function withApiName<D extends ManifestComponent>(
16
+ definition: D,
17
+ apiName: string,
18
+ extra: Record<string, unknown> = {},
19
+ ) {
20
+ const serialize = definition.toDict.bind(definition);
21
+ return Object.assign(definition, {
22
+ getApiName: () => apiName,
23
+ toDict: () => ({ ...serialize(), ...extra, api_name: apiName, identity_format_version: 2 as const }),
24
+ });
25
+ }
26
+
27
+ /** Identifying fields belong to the component's serialized metadata. */
28
+ export type MetadataConfig =
29
+ | {
30
+ readonly type: Exclude<
31
+ ComponentType,
32
+ | 'CUSTOM_OBJECT_FIELD'
33
+ | 'CUSTOM_OBJECT_FIELD_SECTION'
34
+ | 'CUSTOM_OBJECT_ACTION'
35
+ | 'CUSTOM_OBJECT_LIST_VIEW'
36
+ | 'CUSTOM_OBJECT_PAGE_LAYOUT'
37
+ | 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW'
38
+ | 'CUSTOM_OBJECT_VALIDATION_RULE'
39
+ | 'CUSTOM_FIELD_SECTION'
40
+ | 'CUSTOM_FIELD_V2'
41
+ | 'CUSTOM_OBJECT_SETTINGS'
42
+ | 'CUSTOM_OBJECT_SEARCH_CONFIG'
43
+ >;
44
+ readonly api_name: string;
45
+ }
46
+ | {
47
+ readonly type: 'CUSTOM_OBJECT_FIELD';
48
+ readonly custom_object_api_name: string;
49
+ readonly field_api_name: string;
50
+ }
51
+ | {
52
+ readonly type: 'CUSTOM_OBJECT_FIELD_SECTION';
53
+ readonly api_name: string;
54
+ readonly model_name: string;
55
+ }
56
+ | {
57
+ readonly type: 'CUSTOM_OBJECT_ACTION' | 'CUSTOM_OBJECT_LIST_VIEW' | 'CUSTOM_OBJECT_PAGE_LAYOUT';
58
+ readonly api_name: string;
59
+ readonly object_rql_name: string;
60
+ }
61
+ | {
62
+ readonly type:
63
+ | 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW'
64
+ | 'CUSTOM_OBJECT_VALIDATION_RULE'
65
+ | 'CUSTOM_FIELD_SECTION'
66
+ | 'CUSTOM_FIELD_V2';
67
+ readonly api_name: string;
68
+ readonly model_rql_name: string;
69
+ }
70
+ | { readonly type: 'CUSTOM_OBJECT_SETTINGS'; readonly object_rql_name: string; readonly setting_id: string }
71
+ | { readonly type: 'CUSTOM_OBJECT_SEARCH_CONFIG'; readonly model_rql_name: string };
72
+
73
+ export interface BuildContext {
74
+ bundle(entrypoint: URL): Promise<string>;
75
+ }
76
+
77
+ export interface MetadataEntry<W extends MetadataConfig = MetadataConfig> {
78
+ readonly config: W;
79
+ /** Generated defaults yield to authored metadata with the same type, parent, and API name. */
80
+ readonly fallback?: boolean;
81
+ readonly buildKey?: string;
82
+ readonly build?: (context: BuildContext) => Promise<W>;
83
+ }
84
+
85
+ export interface CompileContext {
86
+ readonly definitions: readonly object[];
87
+ }
88
+
89
+ type DefinitionFactory = (...args: never[]) => object;
90
+
91
+ interface ComponentFactory<F extends DefinitionFactory> {
92
+ /** Wire kind used to register this adapter; does not register an API endpoint. */
93
+ readonly type: ComponentType;
94
+ /** Creates the public definition. The wrapper registers the result without adding public properties. */
95
+ readonly define: F;
96
+ /**
97
+ * Receives the factory result and its original arguments. Return fixed references
98
+ * or a callback that reads current dependencies when setters can change them.
99
+ */
100
+ references?(
101
+ definition: ReturnType<F>,
102
+ ...args: Parameters<F>
103
+ ): readonly object[] | (() => readonly object[]);
104
+ }
105
+
106
+ /** Use a single-definition serializer normally; batch compilers handle schema expansion and merging. */
107
+ export type ComponentImplementation<
108
+ F extends DefinitionFactory,
109
+ W extends MetadataConfig,
110
+ > = ComponentFactory<F> &
111
+ (
112
+ | {
113
+ serialize(definition: ReturnType<F>): W | MetadataEntry<W>;
114
+ compileBatch?: never;
115
+ }
116
+ | {
117
+ serialize?: never;
118
+ compileBatch(
119
+ definitions: readonly ReturnType<F>[],
120
+ context: CompileContext,
121
+ ): readonly (W | MetadataEntry<W>)[];
122
+ }
123
+ | (ReturnType<F> extends ManifestComponent ? { serialize?: never; compileBatch?: never } : never)
124
+ );
125
+
126
+ /** Keep public declarations compact without expanding generic factories into their field unions. */
127
+ export interface ComponentDefinition<F extends DefinitionFactory> {
128
+ readonly define: F;
129
+ /** @internal Register a constructed value, preserving its concrete generic type. */
130
+ register<D extends object>(definition: D, ...args: unknown[]): D;
131
+ }
132
+
133
+ interface RegisteredComponent {
134
+ readonly type: ComponentType;
135
+ references(): readonly object[];
136
+ readonly implementation: {
137
+ readonly batchKey: object;
138
+ compileBatch(
139
+ definitions: readonly object[],
140
+ context: CompileContext,
141
+ ): readonly (MetadataConfig | MetadataEntry)[];
142
+ };
143
+ }
144
+
145
+ // Shared across SDK copies, and works for frozen schema definitions without mutating them.
146
+ const registryKey = Symbol.for('@rippling/sdk/component-registry/v3');
147
+ const shared = globalThis as typeof globalThis & { [registryKey]?: WeakMap<object, RegisteredComponent> };
148
+ const registry = (shared[registryKey] ??= new WeakMap());
149
+
150
+ export function registeredComponent(definition: object): RegisteredComponent | undefined {
151
+ return registry.get(definition);
152
+ }
153
+
154
+ /** Retains the exact factory signature, including its generic type parameters. */
155
+ export function defineComponent<F extends DefinitionFactory, W extends MetadataConfig = MetadataConfig>(
156
+ implementation: ComponentImplementation<F, W>,
157
+ ): ComponentDefinition<F> {
158
+ const adapter = {
159
+ batchKey: implementation.compileBatch ?? implementation,
160
+ compileBatch(
161
+ definitions: readonly object[],
162
+ context: CompileContext,
163
+ ): readonly (MetadataConfig | MetadataEntry)[] {
164
+ // Type erasure is confined to registry dispatch; authoring and each implementation stay typed.
165
+ const typed = definitions as ReturnType<F>[];
166
+ if (implementation.compileBatch != null) return implementation.compileBatch(typed, context);
167
+ return typed.map((definition) =>
168
+ implementation.serialize != null ?
169
+ implementation.serialize(definition)
170
+ : ((definition as ManifestComponent).toDict() as W),
171
+ );
172
+ },
173
+ };
174
+ const register = <D extends object>(definition: D, ...args: unknown[]): D => {
175
+ const references =
176
+ implementation.references?.(definition as ReturnType<F>, ...(args as Parameters<F>)) ?? [];
177
+ const snapshot = typeof references === 'function' ? references : Object.freeze([...references]);
178
+ registry.set(definition, {
179
+ type: implementation.type,
180
+ references: typeof snapshot === 'function' ? snapshot : () => snapshot,
181
+ implementation: adapter,
182
+ });
183
+ return definition;
184
+ };
185
+ const define = new Proxy(implementation.define, {
186
+ apply(target, receiver, args) {
187
+ return register(Reflect.apply(target, receiver, args) as ReturnType<F>, ...(args as Parameters<F>));
188
+ },
189
+ });
190
+ return { define, register };
191
+ }
192
+
193
+ /** Mirror backend identity and installation-map scopes, using wire fields rather than source IDs. */
194
+ export function entryKey({ config }: MetadataEntry): string {
195
+ switch (config.type) {
196
+ case 'CUSTOM_OBJECT_FIELD':
197
+ return JSON.stringify([config.type, config.custom_object_api_name, config.field_api_name]);
198
+ case 'CUSTOM_OBJECT_FIELD_SECTION':
199
+ case 'CUSTOM_OBJECT_ACTION':
200
+ case 'CUSTOM_OBJECT_LIST_VIEW':
201
+ case 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW':
202
+ case 'CUSTOM_OBJECT_VALIDATION_RULE':
203
+ // These components still use company-wide API-name lookups or installation maps.
204
+ return JSON.stringify([config.type, config.api_name]);
205
+ case 'CUSTOM_OBJECT_PAGE_LAYOUT':
206
+ return JSON.stringify([config.type, config.object_rql_name, config.api_name]);
207
+ case 'CUSTOM_FIELD_SECTION':
208
+ case 'CUSTOM_FIELD_V2':
209
+ return JSON.stringify([config.type, config.model_rql_name, config.api_name]);
210
+ case 'CUSTOM_OBJECT_SETTINGS':
211
+ return JSON.stringify([config.type, config.object_rql_name]);
212
+ case 'CUSTOM_OBJECT_SEARCH_CONFIG':
213
+ return JSON.stringify([config.type, config.model_rql_name]);
214
+ default:
215
+ return JSON.stringify([config.type, config.api_name]);
216
+ }
217
+ }
218
+
219
+ /** Deferred builds must retain the same detached configuration snapshot as manifest composition. */
220
+ function snapshotMetadata<W extends MetadataConfig>(config: W): W {
221
+ return JSON.parse(JSON.stringify(config)) as W;
222
+ }
223
+
224
+ /** Shared source-build plumbing; each component supplies its own wire format. */
225
+ export function sourceEntry<W extends MetadataConfig>(
226
+ metadata: W,
227
+ source: URL | undefined,
228
+ build: (config: W, content: string) => W,
229
+ ): MetadataEntry<W> {
230
+ const config = snapshotMetadata(metadata);
231
+ const href = source?.href;
232
+ return {
233
+ config,
234
+ ...(href === undefined ?
235
+ {}
236
+ : {
237
+ buildKey: href,
238
+ build: async ({ bundle }: BuildContext) => build(config, await bundle(new URL(href))),
239
+ }),
240
+ };
241
+ }