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