Skip to content

Commit c126294

Browse files
committed
feat(zoho-desk): let the service account pick its data center
Zoho's accounts server is per region, and the integration pinned every call to the US host. For the interactive OAuth flow that is currently unavoidable - better-auth's authorize/token URLs are static per provider - but the service account mints its own token, so the region can simply be chosen. This makes the Self Client the only way a non-US Zoho org can connect. Adds an optional `dataCenter` field to the client-credential framework. Optional matters: ClientCredentialAccountFieldId and ClientCredentialAccountFields are shared with Zoom, Box and Salesforce, whose descriptors and minters are unchanged. Blank keeps the previous behavior (US), so existing credentials are unaffected. Only us/eu/in/au are offered - the four regions where both the accounts server and the Desk REST host are confirmed. CA is deliberately absent: Zoho's accounts docs say accounts.zohocloud.ca while Zoho's own Desk SDK says accounts.zoho.ca, and the two cannot both be right. JP/SA/CN/UK lack a confirmed Desk host. The Desk base is now derived from the selected region rather than inferred from the mint response, which also removes a dependency on `api_domain` being populated for Desk (Zoho documents it for CRM only). When `api_domain` IS present and disagrees with the region, it wins - it is authoritative about where the token actually works - and the mismatch is logged so a mis-selected region is diagnosable. deriveZohoDeskBaseFromApiDomain gains a `try` variant returning undefined so an untrusted api_domain can no longer masquerade as an authoritative US answer and silently override a correct region. A wrong region fails loudly rather than silently: the minter runs as verification on both create and reconnect, so the credential is never persisted in a broken state. Because Zoho reports it as `invalid_client` - a Self Client only exists on its own region's accounts server - the operator hint for that code now names the data center as a candidate cause. Copy is scoped per path rather than blanket "US only": the OAuth service description, trigger setup instructions, and the docs intro now say which path each limitation applies to, and the service-account page documents the four regions with a sign-in-domain to region-code table.
1 parent f9f5d3e commit c126294

18 files changed

Lines changed: 413 additions & 49 deletions

File tree

apps/docs/content/docs/en/integrations/zoho-desk-service-account.mdx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ This is the recommended way to use Zoho Desk in production workflows: the creden
1515

1616
You need a Zoho account with access to the [Zoho API Console](https://api-console.zoho.com) for the same organization your Zoho Desk portal belongs to, and the Zoho Desk **organization ID** for that portal.
1717

18-
<Callout type="warn">
19-
Sim's Zoho Desk integration authenticates against the US accounts server (`accounts.zoho.com`). Organizations hosted in another Zoho data center (EU, IN, AU, JP, CA, SA, CN, UK) cannot mint tokens through this flow. Once a token is minted, API calls are routed to the Desk host for the data center Zoho reports, so data residency is honored for the calls themselves.
18+
<Callout type="info">
19+
A Self Client can authenticate against the **US, EU, IN, or AU** accounts server — pick your region with the **Data center** field when you add the credential, or leave it blank for US. Organizations in the JP, CA, SA, CN, or UK data centers are not supported yet. API calls are then routed to the Desk host for that same region, so data residency is honored end to end.
20+
21+
The interactive **OAuth** connection is a separate path and remains **US-only** (`accounts.zoho.com`), so a non-US organization can connect only through a Self Client.
2022
</Callout>
2123

2224
<Callout type="warn">
@@ -64,7 +66,20 @@ You do **not** need the **Generate Code** tab. That tab produces a one-time auth
6466
This must be the organization ID of the Desk portal you want the workflows to act on. If your Zoho account has more than one Desk portal, using the wrong ID makes Zoho either reject the token request or issue a token scoped to the wrong portal.
6567
</Callout>
6668

67-
### 3. Scopes
69+
### 3. Know Your Data Center
70+
71+
Zoho hosts each organization in one data center, and the accounts server that issues tokens is per region. Look at the URL you use to sign in to Zoho Desk and pick the matching code:
72+
73+
| Data center | Sign-in domain | Code to enter |
74+
| --- | --- | --- |
75+
| United States | `zoho.com` | `us` (or leave blank) |
76+
| Europe | `zoho.eu` | `eu` |
77+
| India | `zoho.in` | `in` |
78+
| Australia | `zoho.com.au` | `au` |
79+
80+
Organizations in the JP, CA, SA, CN, and UK data centers cannot be connected yet.
81+
82+
### 4. Scopes
6883

6984
Sim requests exactly the scopes its Zoho Desk tools and trigger exercise:
7085

@@ -82,7 +97,7 @@ Sim sends this list on every token request, so there is nothing to pre-configure
8297

8398
A scope that is granted but insufficient surfaces at run time as a `4xx` from the Zoho Desk API naming the scope problem.
8499

85-
### 4. Protect the Client Secret
100+
### 5. Protect the Client Secret
86101

87102
The client secret is bearer material for your Zoho Desk organization, limited only by the scopes above. Treat it like a password — do not commit it to source control or share it publicly. Sim encrypts it at rest.
88103

@@ -102,9 +117,9 @@ Regenerating or revoking the Self Client in the Zoho API Console invalidates the
102117
{/* TODO(screenshot): Zoho Desk integration page with the Add Self Client connect option */}
103118
</Step>
104119
<Step>
105-
In the **Add Zoho Desk Self Client** dialog, paste the **Client ID**, the **Client secret**, and the numeric **Organization ID**, and optionally set a display name and description
120+
In the **Add Zoho Desk Self Client** dialog, paste the **Client ID**, the **Client secret**, and the numeric **Organization ID**. Set **Data center** to your region (`us`, `eu`, `in`, or `au`) — leave it blank for US. Optionally set a display name and description
106121

107-
{/* TODO(screenshot): Add Zoho Desk Self Client dialog with all three fields filled in */}
122+
{/* TODO(screenshot): Add Zoho Desk Self Client dialog with all fields filled in */}
108123
</Step>
109124
<Step>
110125
Click **Add Self Client**. Sim verifies the credentials by minting a real access token from Zoho — if it fails, the error tells you whether Zoho rejected the credentials or couldn't be reached.
@@ -123,6 +138,8 @@ The block calls the Zoho Desk REST API with a freshly minted access token — th
123138

124139
The Zoho Desk **trigger** provisions and tears down its own webhook subscription against your Desk organization, and that provisioning path currently runs only against a personal OAuth connection. Connect a Zoho Desk account through OAuth for triggers, and use the Self Client for the blocks that read and update tickets.
125140

141+
Because the OAuth flow is US-only, an organization outside the US data center can use Zoho Desk blocks through a Self Client but cannot use the Zoho Desk trigger.
142+
126143
## Token Behavior
127144

128145
Access tokens minted from a Self Client live for one hour and there is **no refresh token** — Sim mints a new token whenever one is needed, and caches the current one until it is close to expiry. Two events invalidate the stored credential:
@@ -135,7 +152,8 @@ Access tokens minted from a Self Client live for one hour and there is **no refr
135152
{ question: "Where do I find the Organization ID?", answer: "In Zoho Desk, go to Setup (gear icon) → Developer Space → API. The numeric Organization ID shown there is the value to paste. It is the same ID that Zoho Desk API calls send in the orgId header." },
136153
{ question: "Zoho rejects my credentials with invalid_client — why?", answer: "Either the client ID or secret was mistyped, or the client you created is not a Self Client. Only Self Clients support the client-credentials grant — in the Zoho API Console, Add Client → Self Client. Copy both values from the client's Client Secret tab." },
137154
{ question: "Zoho returns missing_org_info or rejects the organization — why?", answer: "Zoho could not resolve a Desk organization from the ID you pasted. Re-copy the numeric Organization ID from Setup → Developer Space → API in the Desk portal you want to use. If your Zoho account has multiple Desk portals, make sure it is the ID of the right one." },
138-
{ question: "Can I use a Self Client with a non-US Zoho account?", answer: "Not currently. Sim's Zoho Desk integration authenticates against the US accounts server, accounts.zoho.com. An organization in another Zoho data center must authenticate against its own accounts server, which this flow does not support." },
155+
{ question: "Can I use a Self Client with a non-US Zoho account?", answer: "Yes, for the US, EU, IN, and AU data centers. Set the Data center field to us, eu, in, or au when you add the credential, and Sim mints tokens against that region's accounts server and calls the Desk host in the same region. Leaving it blank means US. The JP, CA, SA, CN, and UK data centers are not supported yet, and the interactive OAuth connection remains US-only." },
156+
{ question: "I picked the wrong data center — what happens?", answer: "The region's accounts server does not know your organization, so Zoho rejects the token request and Sim reports that it could not authenticate. Edit the credential and set the Data center to the region whose domain you sign in to Zoho Desk with." },
139157
{ question: "Why doesn't my Zoho Desk trigger work with the Self Client?", answer: "The trigger provisions a webhook subscription in your Desk organization, and that path runs against a personal OAuth connection only. Connect Zoho Desk through OAuth for triggers. Separately, Zoho Desk webhooks require a Professional-edition plan or above — they are unavailable on Free and Standard." },
140158
{ question: "How do I rotate the credentials?", answer: "Regenerate the client secret on the Self Client's Client Secret tab in the Zoho API Console, then update the credential in Sim with the new secret. The old secret stops working as soon as it's regenerated, so update Sim promptly." },
141159
]} />

apps/docs/content/docs/en/integrations/zoho_desk.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ To trigger on Zoho Desk activity instead, use the block's trigger mode. Sim crea
3131

3232
> Zoho Desk webhooks require a Zoho Desk edition of **Professional or higher** — Free and Standard plans cannot create webhook subscriptions, so the trigger will fail to deploy on those plans.
3333
>
34-
> Sim currently supports Zoho accounts hosted in the **US data center** (`accounts.zoho.com`). Accounts in the EU, India, Australia, or Japan data centers cannot be connected yet.
34+
> Connecting a Zoho account with **OAuth** — which the trigger requires — works only for the **US data center** (`accounts.zoho.com`). To use Zoho Desk blocks from the EU, India, or Australia data centers, connect a [Self Client](/integrations/zoho-desk-service-account) instead and set its data center. The Japan, Canada, Saudi Arabia, China, and UK data centers are not supported by either path.
3535
{/* MANUAL-CONTENT-END */}
3636

3737

apps/sim/app/api/auth/oauth/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ async function resolveClientCredentialAccountToken(
525525
clientId: blob.clientId,
526526
clientSecret: blob.clientSecret,
527527
orgId: blob.orgId,
528+
dataCenter: blob.dataCenter,
528529
},
529530
{ skipIdentity: true }
530531
)

apps/sim/app/api/credentials/[id]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export const PUT = withRouteHandler(
8989
clientId: body.clientId,
9090
clientSecret: body.clientSecret,
9191
orgId: body.orgId,
92+
dataCenter: body.dataCenter,
9293
request,
9394
})
9495
if (!result.success) {

apps/sim/app/api/credentials/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
318318
clientId,
319319
clientSecret,
320320
orgId,
321+
dataCenter,
321322
} = parsed.data.body
322323

323324
const workspaceAccess = await checkWorkspaceAccess(workspaceId, session.user.id)
@@ -378,6 +379,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
378379
clientId,
379380
clientSecret,
380381
orgId,
382+
dataCenter,
381383
})
382384
resolvedProviderId = secret.providerId
383385
resolvedAccountId = null

apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/client-credential-account-modal.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ function messageForClientCredentialError(
3535
descriptor: ClientCredentialAccountDescriptor
3636
): string {
3737
if (isApiClientError(err) && err.code) {
38-
const fieldLabels = descriptor.fields.map((field) => field.label).join(', ')
38+
const fieldLabels = descriptor.fields
39+
.filter((field) => !field.optional)
40+
.map((field) => field.label)
41+
.join(', ')
3942
switch (err.code) {
4043
case 'invalid_credentials':
4144
return `We couldn't authenticate with those credentials. Check that the ${fieldLabels} all belong to the same ${descriptor.serviceLabel} app and that the app is authorized.`
@@ -92,6 +95,7 @@ export function ClientCredentialAccountModal({
9295
const [clientId, setClientId] = useState('')
9396
const [clientSecret, setClientSecret] = useState('')
9497
const [orgId, setOrgId] = useState('')
98+
const [dataCenter, setDataCenter] = useState('')
9599
const [displayName, setDisplayName] = useState(initialDisplayName ?? '')
96100
const [description, setDescription] = useState(initialDescription ?? '')
97101
const [error, setError] = useState<string | null>(null)
@@ -104,6 +108,7 @@ export function ClientCredentialAccountModal({
104108
setClientId('')
105109
setClientSecret('')
106110
setOrgId('')
111+
setDataCenter('')
107112
setDisplayName(initialDisplayName ?? '')
108113
setDescription(initialDescription ?? '')
109114
setError(null)
@@ -112,10 +117,12 @@ export function ClientCredentialAccountModal({
112117
const clientIdField = descriptor.fields.find((field) => field.id === 'clientId')
113118
const clientSecretField = descriptor.fields.find((field) => field.id === 'clientSecret')
114119
const orgIdField = descriptor.fields.find((field) => field.id === 'orgId')
120+
const dataCenterField = descriptor.fields.find((field) => field.id === 'dataCenter')
115121

116122
const trimmedClientId = clientId.trim()
117123
const trimmedClientSecret = clientSecret.trim()
118124
const trimmedOrgId = orgId.trim()
125+
const trimmedDataCenter = dataCenter.trim()
119126
const isPending = createCredential.isPending || updateCredential.isPending
120127
const isDisabled = !trimmedClientId || !trimmedClientSecret || !trimmedOrgId || isPending
121128

@@ -136,6 +143,7 @@ export function ClientCredentialAccountModal({
136143
clientId: trimmedClientId,
137144
clientSecret: trimmedClientSecret,
138145
orgId: trimmedOrgId,
146+
dataCenter: trimmedDataCenter || undefined,
139147
}
140148
if (credentialId) {
141149
await updateCredential.mutateAsync({
@@ -227,6 +235,21 @@ export function ClientCredentialAccountModal({
227235
/>
228236
)}
229237

238+
{dataCenterField && (
239+
<ChipModalField
240+
type='input'
241+
title={dataCenterField.label}
242+
value={dataCenter}
243+
onChange={(value) => {
244+
setDataCenter(value)
245+
if (error) setError(null)
246+
}}
247+
placeholder={dataCenterField.placeholder}
248+
autoComplete='off'
249+
hint={hintFor(dataCenterField, trimmedDataCenter) ?? dataCenterField.hintMessage}
250+
/>
251+
)}
252+
230253
<ChipModalField
231254
type='input'
232255
title='Display name'

apps/sim/hooks/queries/credentials.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export function useUpdateWorkspaceCredential() {
147147
clientId: payload.clientId,
148148
clientSecret: payload.clientSecret,
149149
orgId: payload.orgId,
150+
dataCenter: payload.dataCenter,
150151
},
151152
})
152153
},

apps/sim/lib/api/contracts/credentials.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ export const createCredentialBodySchema = z
133133
clientId: z.string().trim().min(1).max(512).optional(),
134134
clientSecret: z.string().trim().min(1).max(1024).optional(),
135135
orgId: z.string().trim().min(1).max(255).optional(),
136+
/** Optional provider region selector (Zoho Desk data center). */
137+
dataCenter: z.string().trim().min(1).max(32).optional(),
136138
})
137139
.superRefine((data, ctx) => {
138140
if (data.type === 'oauth') {
@@ -207,6 +209,7 @@ export const updateCredentialByIdBodySchema = z
207209
clientId: z.string().trim().min(1).max(512).optional(),
208210
clientSecret: z.string().trim().min(1).max(1024).optional(),
209211
orgId: z.string().trim().min(1).max(255).optional(),
212+
dataCenter: z.string().trim().min(1).max(32).optional(),
210213
})
211214
.strict()
212215
.refine(
@@ -220,7 +223,8 @@ export const updateCredentialByIdBodySchema = z
220223
data.domain !== undefined ||
221224
data.clientId !== undefined ||
222225
data.clientSecret !== undefined ||
223-
data.orgId !== undefined,
226+
data.orgId !== undefined ||
227+
data.dataCenter !== undefined,
224228
{
225229
message: 'At least one field must be provided',
226230
path: ['displayName'],

apps/sim/lib/credentials/client-credential-accounts/descriptors.ts

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,20 @@
1515
export const CLIENT_CREDENTIAL_ACCOUNT_SECRET_TYPE = 'client_credential_account' as const
1616

1717
/** Contract field ids a client-credential connect modal collects. */
18-
export type ClientCredentialAccountFieldId = 'clientId' | 'clientSecret' | 'orgId'
18+
export type ClientCredentialAccountFieldId = 'clientId' | 'clientSecret' | 'orgId' | 'dataCenter'
1919

2020
export interface ClientCredentialAccountField {
2121
id: ClientCredentialAccountFieldId
2222
label: string
2323
placeholder: string
2424
/** Rendered with SecretInput and never echoed back. */
2525
secret: boolean
26+
/**
27+
* Field the connect modal may submit empty; excluded from
28+
* {@link CLIENT_CREDENTIAL_ACCOUNT_REQUIRED_FIELDS} so create/reconnect
29+
* validation never demands it. Omitted (default) means required.
30+
*/
31+
optional?: boolean
2632
/** Soft-format hint shown while the current value doesn't match `hintPattern`. */
2733
hintPattern?: RegExp
2834
hintMessage?: string
@@ -116,6 +122,66 @@ export function normalizeZohoDeskSoid(rawOrgId: string): string {
116122
/** A normalized `soid`: a service-name prefix plus a numeric Zoho org id. */
117123
export const ZOHO_DESK_SOID_REGEX = /^[A-Za-z]+\.\d+$/
118124

125+
/**
126+
* Zoho data centers the Self Client (client-credentials) flow supports. Each
127+
* entry pairs the accounts server that mints the token with the Desk REST host
128+
* in the same region, so the minter never has to guess either one.
129+
*
130+
* Only regions where BOTH hosts are confirmed are listed. CA, SA, JP, CN, and
131+
* UK are deliberately absent: Zoho's accounts documentation and Zoho's own Desk
132+
* SDK disagree on Canada (`accounts.zohocloud.ca` vs `accounts.zoho.ca`), and
133+
* the Desk hosts for the others are not confirmed. More regions can be added
134+
* once both the accounts server and the Desk host are confirmed for them.
135+
*
136+
* This applies to the service account only — the interactive OAuth flow's
137+
* authorize/token URLs are static per provider and remain US-only.
138+
*/
139+
export const ZOHO_DESK_DATA_CENTERS = {
140+
us: { accountsBase: 'https://accounts.zoho.com', deskBase: 'https://desk.zoho.com' },
141+
eu: { accountsBase: 'https://accounts.zoho.eu', deskBase: 'https://desk.zoho.eu' },
142+
in: { accountsBase: 'https://accounts.zoho.in', deskBase: 'https://desk.zoho.in' },
143+
au: { accountsBase: 'https://accounts.zoho.com.au', deskBase: 'https://desk.zoho.com.au' },
144+
} as const satisfies Record<string, { accountsBase: string; deskBase: string }>
145+
146+
export type ZohoDeskDataCenterId = keyof typeof ZOHO_DESK_DATA_CENTERS
147+
148+
/** Region used when the admin leaves the field blank, so existing credentials are unaffected. */
149+
export const DEFAULT_ZOHO_DESK_DATA_CENTER: ZohoDeskDataCenterId = 'us'
150+
151+
export const ZOHO_DESK_DATA_CENTER_IDS = Object.keys(
152+
ZOHO_DESK_DATA_CENTERS
153+
) as ZohoDeskDataCenterId[]
154+
155+
/** Accepts exactly the supported region codes, case-insensitively after normalization. */
156+
export const ZOHO_DESK_DATA_CENTER_REGEX = new RegExp(`^(${ZOHO_DESK_DATA_CENTER_IDS.join('|')})$`)
157+
158+
/**
159+
* Normalizes a pasted data-center value to its lowercase region code. Shared by
160+
* the connect modal's format hint and the server-side minter so both judge the
161+
* same normalized value.
162+
*/
163+
export function normalizeZohoDeskDataCenter(rawDataCenter: string): string {
164+
return rawDataCenter.trim().toLowerCase()
165+
}
166+
167+
/**
168+
* Resolves a stored data-center value to its accounts/Desk host pair. A blank,
169+
* absent, or unrecognized value falls back to {@link DEFAULT_ZOHO_DESK_DATA_CENTER}
170+
* so credentials created before this field existed keep minting against the US
171+
* accounts server exactly as they did.
172+
*/
173+
export function resolveZohoDeskDataCenter(rawDataCenter?: string): {
174+
id: ZohoDeskDataCenterId
175+
accountsBase: string
176+
deskBase: string
177+
} {
178+
const normalized = normalizeZohoDeskDataCenter(rawDataCenter ?? '')
179+
const id: ZohoDeskDataCenterId = Object.hasOwn(ZOHO_DESK_DATA_CENTERS, normalized)
180+
? (normalized as ZohoDeskDataCenterId)
181+
: DEFAULT_ZOHO_DESK_DATA_CENTER
182+
return { id, ...ZOHO_DESK_DATA_CENTERS[id] }
183+
}
184+
119185
export const CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS: Record<
120186
ClientCredentialAccountProviderId,
121187
ClientCredentialAccountDescriptor
@@ -237,25 +303,36 @@ export const CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS: Record<
237303
hintMessage:
238304
'Paste the numeric Zoho Desk organization ID from Setup → Developer Space → API, or the full ZohoDesk.<orgId> value.',
239305
},
306+
{
307+
id: 'dataCenter',
308+
label: 'Data center',
309+
placeholder: 'us',
310+
secret: false,
311+
optional: true,
312+
hintPattern: ZOHO_DESK_DATA_CENTER_REGEX,
313+
hintNormalize: normalizeZohoDeskDataCenter,
314+
hintMessage: `Enter one of ${ZOHO_DESK_DATA_CENTER_IDS.join(', ')}. Leave blank for us (accounts.zoho.com).`,
315+
},
240316
],
241317
docsUrl: 'https://docs.sim.ai/integrations/zoho-desk-service-account',
242318
helpText:
243-
'Create the Self Client in the Zoho API Console, add the Zoho Desk scopes, and use the organization ID from Setup → Developer Space → API. Only Zoho accounts in the US data center (accounts.zoho.com) are supported, and Zoho Desk triggers still require an OAuth connection.',
319+
'Create the Self Client in the Zoho API Console, add the Zoho Desk scopes, and use the organization ID from Setup → Developer Space → API. Self Clients work with the US, EU, IN, and AU data centers — leave the data center blank for US. Zoho Desk triggers still require an OAuth connection, which is US-only.',
244320
},
245321
}
246322

247323
/**
248324
* Required contract fields per client-credential provider, consumed by the
249325
* `createCredentialBodySchema` superRefine so validation errors name the exact
250-
* missing field. Derived from each descriptor's field list.
326+
* missing field. Derived from each descriptor's field list, minus the fields
327+
* marked `optional`.
251328
*/
252329
export const CLIENT_CREDENTIAL_ACCOUNT_REQUIRED_FIELDS: Record<
253330
string,
254331
ClientCredentialAccountFieldId[]
255332
> = Object.fromEntries(
256333
Object.values(CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS).map((descriptor) => [
257334
descriptor.providerId,
258-
descriptor.fields.map((field) => field.id),
335+
descriptor.fields.filter((field) => !field.optional).map((field) => field.id),
259336
])
260337
)
261338

0 commit comments

Comments
 (0)