@objectstack/platform-objects 6.9.0 → 7.1.0

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 (51) hide show
  1. package/dist/apps/index.d.mts +30 -1
  2. package/dist/apps/index.d.ts +30 -1
  3. package/dist/apps/index.js +987 -37
  4. package/dist/apps/index.js.map +1 -1
  5. package/dist/apps/index.mjs +987 -38
  6. package/dist/apps/index.mjs.map +1 -1
  7. package/dist/audit/index.d.mts +240 -64
  8. package/dist/audit/index.d.ts +240 -64
  9. package/dist/identity/index.d.mts +900 -82
  10. package/dist/identity/index.d.ts +900 -82
  11. package/dist/identity/index.js +384 -8
  12. package/dist/identity/index.js.map +1 -1
  13. package/dist/identity/index.mjs +384 -8
  14. package/dist/identity/index.mjs.map +1 -1
  15. package/dist/index.d.mts +4 -1
  16. package/dist/index.d.ts +4 -1
  17. package/dist/index.js +6823 -99
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +6818 -100
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/integration/index.d.mts +15 -4
  22. package/dist/integration/index.d.ts +15 -4
  23. package/dist/metadata/index.d.mts +30 -8
  24. package/dist/metadata/index.d.ts +30 -8
  25. package/dist/metadata-translations/index.d.mts +20 -0
  26. package/dist/metadata-translations/index.d.ts +20 -0
  27. package/dist/metadata-translations/index.js +4777 -0
  28. package/dist/metadata-translations/index.js.map +1 -0
  29. package/dist/metadata-translations/index.mjs +4775 -0
  30. package/dist/metadata-translations/index.mjs.map +1 -0
  31. package/dist/pages/index.d.mts +73 -0
  32. package/dist/pages/index.d.ts +73 -0
  33. package/dist/pages/index.js +371 -0
  34. package/dist/pages/index.js.map +1 -0
  35. package/dist/pages/index.mjs +368 -0
  36. package/dist/pages/index.mjs.map +1 -0
  37. package/dist/plugin.d.mts +35 -0
  38. package/dist/plugin.d.ts +35 -0
  39. package/dist/plugin.js +17562 -0
  40. package/dist/plugin.js.map +1 -0
  41. package/dist/plugin.mjs +17559 -0
  42. package/dist/plugin.mjs.map +1 -0
  43. package/dist/security/index.d.mts +806 -204
  44. package/dist/security/index.d.ts +806 -204
  45. package/dist/security/index.js +208 -1
  46. package/dist/security/index.js.map +1 -1
  47. package/dist/security/index.mjs +208 -1
  48. package/dist/security/index.mjs.map +1 -1
  49. package/dist/system/index.d.mts +45 -12
  50. package/dist/system/index.d.ts +45 -12
  51. package/package.json +17 -2
@@ -112,9 +112,166 @@ var SysUser = ObjectSchema.create({
112
112
  successMessage: "Now impersonating user",
113
113
  refreshAfter: true,
114
114
  confirmText: "Start an impersonation session for this user? Use only for legitimate support cases \u2014 actions will be logged."
115
+ },
116
+ // ── Self-service actions (the row owner only) ─────────────────────
117
+ //
118
+ // These four actions are the "account settings" surfaces the standalone
119
+ // Account SPA used to own (`/account/profile`, `/account/security`).
120
+ // They are visible only when the current row is the signed-in user —
121
+ // i.e. opened from the user's own detail page or a "My Account" view —
122
+ // via the `visible` CEL predicate. Admin equivalents (set_user_password
123
+ // for any account) are above and stay separate.
124
+ {
125
+ name: "update_my_profile",
126
+ label: "Update Profile",
127
+ icon: "user-pen",
128
+ variant: "primary",
129
+ mode: "edit",
130
+ locations: ["record_header"],
131
+ type: "api",
132
+ target: "/api/v1/auth/update-user",
133
+ visible: "record.id == ctx.user.id",
134
+ successMessage: "Profile updated",
135
+ refreshAfter: true,
136
+ params: [
137
+ { field: "name", required: false, defaultFromRow: true },
138
+ { field: "image", required: false, defaultFromRow: true }
139
+ ]
140
+ },
141
+ {
142
+ name: "change_my_password",
143
+ label: "Change Password",
144
+ icon: "key",
145
+ variant: "secondary",
146
+ locations: ["record_header", "record_more", "record_section"],
147
+ type: "api",
148
+ target: "/api/v1/auth/change-password",
149
+ visible: "record.id == ctx.user.id",
150
+ successMessage: "Password changed",
151
+ refreshAfter: false,
152
+ params: [
153
+ { name: "currentPassword", label: "Current Password", type: "text", required: true },
154
+ { name: "newPassword", label: "New Password", type: "text", required: true },
155
+ { name: "revokeOtherSessions", label: "Sign out other devices", type: "boolean", required: false, defaultValue: true }
156
+ ]
157
+ },
158
+ {
159
+ name: "change_my_email",
160
+ label: "Change Email",
161
+ icon: "mail",
162
+ variant: "secondary",
163
+ locations: ["record_header", "record_more", "record_section"],
164
+ type: "api",
165
+ target: "/api/v1/auth/change-email",
166
+ visible: "record.id == ctx.user.id",
167
+ successMessage: "Verification email sent \u2014 check the new address to confirm.",
168
+ refreshAfter: false,
169
+ params: [
170
+ { name: "newEmail", label: "New Email", type: "email", required: true }
171
+ ]
172
+ },
173
+ {
174
+ name: "resend_verification_email",
175
+ label: "Resend Verification Email",
176
+ icon: "mail-check",
177
+ variant: "secondary",
178
+ locations: ["record_header", "record_more", "record_section"],
179
+ type: "api",
180
+ target: "/api/v1/auth/send-verification-email",
181
+ // Only render for the row owner AND when their email is still
182
+ // unverified — there's nothing to resend once verified.
183
+ visible: "record.id == ctx.user.id && record.email_verified == false",
184
+ successMessage: "Verification email sent \u2014 check your inbox.",
185
+ refreshAfter: false,
186
+ params: []
187
+ },
188
+ {
189
+ name: "delete_my_account",
190
+ label: "Delete My Account",
191
+ icon: "user-x",
192
+ variant: "danger",
193
+ mode: "delete",
194
+ locations: ["record_more", "record_section"],
195
+ type: "api",
196
+ target: "/api/v1/auth/delete-user",
197
+ visible: "record.id == ctx.user.id",
198
+ confirmText: "Permanently delete your account? This cannot be undone \u2014 all your sessions will be terminated and all data you own will be removed per the configured retention policy.",
199
+ successMessage: "Account deleted",
200
+ refreshAfter: false,
201
+ params: [
202
+ { name: "password", label: "Current Password", type: "text", required: true }
203
+ ]
204
+ },
205
+ // ── Two-factor authentication ─────────────────────────────────
206
+ // Enable flow returns { totpURI, backupCodes } — surfacing those
207
+ // safely needs a QR + verify UI that the generic action engine
208
+ // can't render yet. We still expose it so the API call works
209
+ // and the success toast displays the otpauth:// URI that users
210
+ // can manually add to an authenticator app as a fallback.
211
+ {
212
+ name: "enable_two_factor",
213
+ label: "Enable Two-Factor Auth",
214
+ icon: "shield-plus",
215
+ variant: "primary",
216
+ locations: ["record_section"],
217
+ type: "api",
218
+ target: "/api/v1/auth/two-factor/enable",
219
+ visible: "record.id == ctx.user.id && record.two_factor_enabled != true",
220
+ successMessage: "Two-factor authentication enabled. Scan the QR code or paste the otpauth URI into your authenticator app, then verify a code to complete setup.",
221
+ refreshAfter: true,
222
+ params: [
223
+ { name: "password", label: "Current Password", type: "text", required: true }
224
+ ]
225
+ },
226
+ {
227
+ name: "disable_two_factor",
228
+ label: "Disable Two-Factor Auth",
229
+ icon: "shield-off",
230
+ variant: "danger",
231
+ locations: ["record_section"],
232
+ type: "api",
233
+ target: "/api/v1/auth/two-factor/disable",
234
+ visible: "record.id == ctx.user.id && record.two_factor_enabled == true",
235
+ confirmText: "Turn off two-factor authentication? Your account will be less secure.",
236
+ successMessage: "Two-factor authentication disabled.",
237
+ refreshAfter: true,
238
+ params: [
239
+ { name: "password", label: "Current Password", type: "text", required: true }
240
+ ]
241
+ },
242
+ {
243
+ name: "generate_backup_codes",
244
+ label: "Regenerate Backup Codes",
245
+ icon: "list-restart",
246
+ variant: "secondary",
247
+ locations: ["record_section"],
248
+ type: "api",
249
+ target: "/api/v1/auth/two-factor/generate-backup-codes",
250
+ visible: "record.id == ctx.user.id && record.two_factor_enabled == true",
251
+ confirmText: "Generate a new set of backup codes? Any previously generated codes will stop working.",
252
+ successMessage: "New backup codes generated \u2014 save them somewhere safe.",
253
+ refreshAfter: false,
254
+ params: [
255
+ { name: "password", label: "Current Password", type: "text", required: true }
256
+ ]
115
257
  }
116
258
  ],
117
259
  listViews: {
260
+ // Self-service profile entry — surfaced by the Account App so every
261
+ // authenticated user can view / edit their own basic profile (name,
262
+ // email, avatar). Filtered to a single row (the caller) via the
263
+ // `{current_user_id}` template variable; RLS additionally enforces
264
+ // that non-admins cannot read other users' rows.
265
+ me: {
266
+ type: "grid",
267
+ name: "me",
268
+ label: "My Profile",
269
+ data: { provider: "object", object: "sys_user" },
270
+ columns: ["name", "email", "email_verified", "two_factor_enabled", "updated_at"],
271
+ filter: [{ field: "id", operator: "equals", value: "{current_user_id}" }],
272
+ sort: [{ field: "name", order: "asc" }],
273
+ pagination: { pageSize: 1 }
274
+ },
118
275
  all_users: {
119
276
  type: "grid",
120
277
  name: "all_users",
@@ -426,7 +583,41 @@ var SysAccount = ObjectSchema.create({
426
583
  // requests it). Better-auth exposes `/unlink-account { providerId,
427
584
  // accountId }` for this. The form is locked to the row's values so
428
585
  // it acts as a one-click confirmation rather than a free-form edit.
586
+ //
587
+ // `link_social` is the self-service counterpart — a toolbar action
588
+ // that redirects the browser to better-auth's social sign-in endpoint
589
+ // with a callbackURL pointing back to the linked-accounts view. The
590
+ // endpoint sets the link cookie and OAuth-dances through the provider,
591
+ // which is why it's `type: 'url'` (full page navigation) rather than
592
+ // `type: 'api'` (XHR — would block on CORS / 302).
429
593
  actions: [
594
+ {
595
+ name: "link_social",
596
+ label: "Link Social Account",
597
+ icon: "link-2",
598
+ variant: "primary",
599
+ mode: "create",
600
+ locations: ["list_toolbar"],
601
+ type: "url",
602
+ target: "/api/v1/auth/sign-in/social?provider=${param.provider}&callbackURL=${ctx.origin}/apps/account/sys_account",
603
+ params: [
604
+ {
605
+ name: "provider",
606
+ label: "Provider",
607
+ type: "select",
608
+ required: true,
609
+ options: [
610
+ { label: "Google", value: "google" },
611
+ { label: "GitHub", value: "github" },
612
+ { label: "Microsoft", value: "microsoft" },
613
+ { label: "Apple", value: "apple" },
614
+ { label: "Facebook", value: "facebook" },
615
+ { label: "GitLab", value: "gitlab" },
616
+ { label: "Discord", value: "discord" }
617
+ ]
618
+ }
619
+ ]
620
+ },
430
621
  {
431
622
  name: "unlink_account",
432
623
  label: "Unlink Account",
@@ -707,6 +898,30 @@ var SysOrganization = ObjectSchema.create({
707
898
  confirmText: "Leave this organization? You will lose access to all of its resources.",
708
899
  successMessage: "You have left the organization",
709
900
  refreshAfter: true
901
+ },
902
+ {
903
+ // Rename the organization slug (URL prefix). Backed by the cloud
904
+ // orchestrator at /api/v1/cloud/organizations/{id}/change-slug,
905
+ // which atomically updates sys_organization.slug, rewrites
906
+ // platform_subdomain sys_domain rows under the new slug, soft-
907
+ // retires the old rows with a redirect window, parks the old
908
+ // slug in sys_slug_reservation, and refreshes the registry
909
+ // mirror. See cloud `docs/design/sys-domain.md` §6.
910
+ name: "change_slug",
911
+ label: "Change Slug",
912
+ icon: "edit-3",
913
+ variant: "secondary",
914
+ mode: "custom",
915
+ locations: ["record_header"],
916
+ type: "api",
917
+ target: "/api/v1/cloud/organizations/{id}/change-slug",
918
+ method: "POST",
919
+ confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?",
920
+ successMessage: "Organization slug changed",
921
+ refreshAfter: true,
922
+ params: [
923
+ { field: "slug", required: true, defaultFromRow: true }
924
+ ]
710
925
  }
711
926
  ],
712
927
  listViews: {
@@ -848,8 +1063,46 @@ var SysMember = ObjectSchema.create({
848
1063
  confirmText: "Remove this member from the organization? They will lose access to all org resources.",
849
1064
  successMessage: "Member removed",
850
1065
  refreshAfter: true
1066
+ },
1067
+ // Transfer ownership is modeled as `update-member-role` with role=owner
1068
+ // (better-auth's organization plugin auto-demotes the previous owner
1069
+ // to admin). Kept as a separate action so the row menu can present a
1070
+ // distinct destructive-style affordance with the right confirm copy —
1071
+ // mixing it into `update_member_role` would hide the ownership-handoff
1072
+ // semantics behind a generic role dropdown.
1073
+ {
1074
+ name: "transfer_ownership",
1075
+ label: "Transfer Ownership",
1076
+ icon: "crown",
1077
+ variant: "danger",
1078
+ mode: "custom",
1079
+ locations: ["list_item"],
1080
+ type: "api",
1081
+ target: "/api/v1/auth/organization/update-member-role",
1082
+ recordIdParam: "memberId",
1083
+ bodyExtra: { role: "owner" },
1084
+ visible: "record.role != 'owner'",
1085
+ confirmText: "Transfer ownership of this organization to the selected member? You will be demoted to admin and lose owner-only privileges.",
1086
+ successMessage: "Ownership transferred",
1087
+ refreshAfter: true
851
1088
  }
852
1089
  ],
1090
+ listViews: {
1091
+ mine: {
1092
+ type: "grid",
1093
+ name: "mine",
1094
+ label: "My Memberships",
1095
+ data: { provider: "object", object: "sys_member" },
1096
+ columns: ["organization_id", "role", "created_at"],
1097
+ filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
1098
+ sort: [{ field: "created_at", order: "desc" }],
1099
+ pagination: { pageSize: 50 },
1100
+ emptyState: {
1101
+ title: "No organizations yet",
1102
+ message: "You haven't joined any organizations."
1103
+ }
1104
+ }
1105
+ },
853
1106
  fields: {
854
1107
  id: Field.text({
855
1108
  label: "Member ID",
@@ -952,6 +1205,40 @@ var SysInvitation = ObjectSchema.create({
952
1205
  { field: "email", required: true, defaultFromRow: true },
953
1206
  { field: "role", required: true, defaultFromRow: true }
954
1207
  ]
1208
+ },
1209
+ // ── Recipient-side actions (the invited user) ────────────────────
1210
+ //
1211
+ // These two are the counterpart to invite/cancel/resend: they are
1212
+ // visible only on invitations addressed to the current user. Used
1213
+ // by an "Inbox / Pending invitations" list opened from the user's
1214
+ // own account page. The recipient-only `visible` predicate keeps
1215
+ // them out of the admin org-management view.
1216
+ {
1217
+ name: "accept_invitation",
1218
+ label: "Accept Invitation",
1219
+ icon: "check",
1220
+ variant: "primary",
1221
+ locations: ["list_item", "record_header"],
1222
+ type: "api",
1223
+ target: "/api/v1/auth/organization/accept-invitation",
1224
+ recordIdParam: "invitationId",
1225
+ visible: "record.email == ctx.user.email && record.status == 'pending'",
1226
+ successMessage: "Invitation accepted",
1227
+ refreshAfter: true
1228
+ },
1229
+ {
1230
+ name: "reject_invitation",
1231
+ label: "Decline Invitation",
1232
+ icon: "x",
1233
+ variant: "ghost",
1234
+ locations: ["list_item", "record_header"],
1235
+ type: "api",
1236
+ target: "/api/v1/auth/organization/reject-invitation",
1237
+ recordIdParam: "invitationId",
1238
+ visible: "record.email == ctx.user.email && record.status == 'pending'",
1239
+ confirmText: "Decline this invitation? The inviter will be notified and you will need a new invitation to join.",
1240
+ successMessage: "Invitation declined",
1241
+ refreshAfter: true
955
1242
  }
956
1243
  ],
957
1244
  listViews: {
@@ -1737,10 +2024,12 @@ var SysTwoFactor = ObjectSchema.create({
1737
2024
  pagination: { pageSize: 50 }
1738
2025
  }
1739
2026
  },
1740
- // Toolbar actions for self-service 2FA enrollment. The actual TOTP secret
1741
- // and backup codes returned by better-auth must be shown in the response
1742
- // toast / dialog the action runner surfaces successMessage; the raw
1743
- // payload is logged client-side for now (TODO: dedicated 2FA setup wizard).
2027
+ // Toolbar actions for self-service 2FA enrollment. Better-auth's
2028
+ // `/two-factor/enable` returns `{ totpURI, backupCodes }` the user must
2029
+ // scan the URI into an authenticator app and save the backup codes NOW;
2030
+ // they are never recoverable afterward. The `resultDialog` field tells
2031
+ // the renderer to open a one-shot reveal dialog instead of toasting the
2032
+ // success message. Same shape used by `regenerate_backup_codes`.
1744
2033
  actions: [
1745
2034
  {
1746
2035
  name: "enable_two_factor",
@@ -1750,11 +2039,19 @@ var SysTwoFactor = ObjectSchema.create({
1750
2039
  locations: ["list_toolbar"],
1751
2040
  type: "api",
1752
2041
  target: "/api/v1/auth/two-factor/enable",
1753
- successMessage: "2FA enrollment started \u2014 check response for TOTP URI and backup codes",
1754
2042
  refreshAfter: true,
1755
2043
  params: [
1756
2044
  { name: "password", label: "Current Password", type: "text", required: true }
1757
- ]
2045
+ ],
2046
+ resultDialog: {
2047
+ title: "Two-factor authentication enabled",
2048
+ description: "Scan the QR code with your authenticator app, then save the backup codes somewhere safe. The backup codes are shown only once.",
2049
+ acknowledge: "I have saved my backup codes",
2050
+ fields: [
2051
+ { path: "totpURI", label: "Authenticator URI", format: "qrcode" },
2052
+ { path: "backupCodes", label: "Backup Codes", format: "code-list" }
2053
+ ]
2054
+ }
1758
2055
  },
1759
2056
  {
1760
2057
  name: "disable_two_factor",
@@ -1770,6 +2067,28 @@ var SysTwoFactor = ObjectSchema.create({
1770
2067
  params: [
1771
2068
  { name: "password", label: "Current Password", type: "text", required: true }
1772
2069
  ]
2070
+ },
2071
+ {
2072
+ name: "regenerate_backup_codes",
2073
+ label: "Regenerate Backup Codes",
2074
+ icon: "refresh-cw",
2075
+ variant: "secondary",
2076
+ locations: ["list_toolbar", "list_item"],
2077
+ type: "api",
2078
+ target: "/api/v1/auth/two-factor/generate-backup-codes",
2079
+ confirmText: "Regenerate backup codes? All previous backup codes will stop working immediately.",
2080
+ refreshAfter: true,
2081
+ params: [
2082
+ { name: "password", label: "Current Password", type: "text", required: true }
2083
+ ],
2084
+ resultDialog: {
2085
+ title: "New backup codes generated",
2086
+ description: "Previous backup codes are now invalid. Save these new codes somewhere safe \u2014 they are shown only once.",
2087
+ acknowledge: "I have saved the new codes",
2088
+ fields: [
2089
+ { path: "backupCodes", label: "Backup Codes", format: "code-list" }
2090
+ ]
2091
+ }
1773
2092
  }
1774
2093
  ],
1775
2094
  fields: {
@@ -1801,6 +2120,11 @@ var SysTwoFactor = ObjectSchema.create({
1801
2120
  label: "Backup Codes",
1802
2121
  required: false,
1803
2122
  description: "JSON-serialized backup recovery codes"
2123
+ }),
2124
+ verified: Field.boolean({
2125
+ label: "Verified",
2126
+ defaultValue: true,
2127
+ description: "Whether the enrollment was confirmed with a valid TOTP code (managed by better-auth)"
1804
2128
  })
1805
2129
  },
1806
2130
  indexes: [
@@ -2060,6 +2384,37 @@ var SysOauthApplication = ObjectSchema.create({
2060
2384
  { name: "client_id", field: "client_id", defaultFromRow: true, required: true }
2061
2385
  ]
2062
2386
  },
2387
+ {
2388
+ name: "create_oauth_application",
2389
+ label: "Register OAuth Application",
2390
+ icon: "plus-circle",
2391
+ variant: "primary",
2392
+ mode: "create",
2393
+ locations: ["list_toolbar"],
2394
+ type: "api",
2395
+ method: "POST",
2396
+ target: "/api/v1/auth/sys-oauth-application/register",
2397
+ refreshAfter: true,
2398
+ params: [
2399
+ { name: "name", label: "Application Name", type: "text", required: true },
2400
+ { name: "redirectURLs", label: "Redirect URLs", type: "textarea", required: true, helpText: "One URL per line. Must use https:// in production." },
2401
+ { name: "type", label: "Application Type", type: "select", required: true, defaultValue: "web", options: [
2402
+ { label: "Web", value: "web" },
2403
+ { label: "Native", value: "native" },
2404
+ { label: "User-agent based", value: "user-agent-based" },
2405
+ { label: "Public", value: "public" }
2406
+ ] }
2407
+ ],
2408
+ resultDialog: {
2409
+ title: "OAuth application registered",
2410
+ description: "Save the client_secret now \u2014 it is shown only once and cannot be recovered. You can rotate it later if it leaks.",
2411
+ acknowledge: "I have saved the client secret",
2412
+ fields: [
2413
+ { path: "client.client_id", label: "Client ID", format: "text" },
2414
+ { path: "client.client_secret", label: "Client Secret", format: "secret" }
2415
+ ]
2416
+ }
2417
+ },
2063
2418
  {
2064
2419
  name: "rotate_client_secret",
2065
2420
  label: "Rotate Client Secret",
@@ -2071,11 +2426,18 @@ var SysOauthApplication = ObjectSchema.create({
2071
2426
  method: "POST",
2072
2427
  target: "/api/v1/auth/oauth2/client/rotate-secret",
2073
2428
  confirmText: "Rotate this OAuth client's secret? The previous secret will stop working immediately and any integrations using it will break until they are updated with the new secret. The new secret is shown only once.",
2074
- successMessage: "Client secret rotated \u2014 copy the new value from the response now.",
2075
2429
  refreshAfter: true,
2076
2430
  params: [
2077
2431
  { name: "client_id", field: "client_id", defaultFromRow: true, required: true }
2078
- ]
2432
+ ],
2433
+ resultDialog: {
2434
+ title: "Client secret rotated",
2435
+ description: "Save the new secret now \u2014 it is shown only once. Update every integration before the previous secret's grace period ends.",
2436
+ acknowledge: "I have updated my integrations",
2437
+ fields: [
2438
+ { path: "client_secret", label: "New Client Secret", format: "secret" }
2439
+ ]
2440
+ }
2079
2441
  },
2080
2442
  {
2081
2443
  name: "delete_oauth_application",
@@ -2096,6 +2458,20 @@ var SysOauthApplication = ObjectSchema.create({
2096
2458
  }
2097
2459
  ],
2098
2460
  listViews: {
2461
+ mine: {
2462
+ type: "grid",
2463
+ name: "mine",
2464
+ label: "My Applications",
2465
+ data: { provider: "object", object: "sys_oauth_application" },
2466
+ columns: ["name", "client_id", "type", "disabled", "created_at"],
2467
+ // Self-service Account view — scope to the signed-in user's own
2468
+ // registrations so they don't see other developers' apps. Admins
2469
+ // get the unfiltered `active` / `disabled_apps` / `all_apps` views
2470
+ // via the Setup → OAuth Applications nav.
2471
+ filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
2472
+ sort: [{ field: "created_at", order: "desc" }],
2473
+ pagination: { pageSize: 50 }
2474
+ },
2099
2475
  active: {
2100
2476
  type: "grid",
2101
2477
  name: "active",