@objectstack/platform-objects 4.0.5 → 4.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 (57) hide show
  1. package/dist/apps/index.d.mts +16 -48
  2. package/dist/apps/index.d.ts +16 -48
  3. package/dist/apps/index.js +139 -217
  4. package/dist/apps/index.js.map +1 -1
  5. package/dist/apps/index.mjs +140 -212
  6. package/dist/apps/index.mjs.map +1 -1
  7. package/dist/audit/index.d.mts +38990 -51
  8. package/dist/audit/index.d.ts +38990 -51
  9. package/dist/audit/index.js +1428 -0
  10. package/dist/audit/index.js.map +1 -1
  11. package/dist/audit/index.mjs +1417 -1
  12. package/dist/audit/index.mjs.map +1 -1
  13. package/dist/identity/index.d.mts +14869 -2802
  14. package/dist/identity/index.d.ts +14869 -2802
  15. package/dist/identity/index.js +1090 -6
  16. package/dist/identity/index.js.map +1 -1
  17. package/dist/identity/index.mjs +1089 -7
  18. package/dist/identity/index.mjs.map +1 -1
  19. package/dist/index.d.mts +8 -7
  20. package/dist/index.d.ts +8 -7
  21. package/dist/index.js +3652 -1482
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +3633 -1465
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/integration/index.d.mts +2905 -0
  26. package/dist/integration/index.d.ts +2905 -0
  27. package/dist/integration/index.js +140 -0
  28. package/dist/integration/index.js.map +1 -0
  29. package/dist/integration/index.mjs +138 -0
  30. package/dist/integration/index.mjs.map +1 -0
  31. package/dist/metadata/index.d.mts +577 -21181
  32. package/dist/metadata/index.d.ts +577 -21181
  33. package/dist/metadata/index.js +29 -619
  34. package/dist/metadata/index.js.map +1 -1
  35. package/dist/metadata/index.mjs +30 -615
  36. package/dist/metadata/index.mjs.map +1 -1
  37. package/dist/security/index.d.mts +7278 -46
  38. package/dist/security/index.d.ts +7278 -46
  39. package/dist/security/index.js +540 -0
  40. package/dist/security/index.js.map +1 -1
  41. package/dist/security/index.mjs +539 -1
  42. package/dist/security/index.mjs.map +1 -1
  43. package/dist/system/index.d.mts +8409 -0
  44. package/dist/system/index.d.ts +8409 -0
  45. package/dist/system/index.js +395 -0
  46. package/dist/system/index.js.map +1 -0
  47. package/dist/system/index.mjs +391 -0
  48. package/dist/system/index.mjs.map +1 -0
  49. package/package.json +13 -8
  50. package/dist/tenant/index.d.mts +0 -18464
  51. package/dist/tenant/index.d.ts +0 -18464
  52. package/dist/tenant/index.js +0 -741
  53. package/dist/tenant/index.js.map +0 -1
  54. package/dist/tenant/index.mjs +0 -733
  55. package/dist/tenant/index.mjs.map +0 -1
  56. /package/dist/{state-machine.zod-BFg-VE0M.d-Ek3_yo9P.d.mts → state-machine.zod-BNanU03M.d-Ek3_yo9P.d.mts} +0 -0
  57. /package/dist/{state-machine.zod-BFg-VE0M.d-Ek3_yo9P.d.ts → state-machine.zod-BNanU03M.d-Ek3_yo9P.d.ts} +0 -0
@@ -12,6 +12,149 @@ var SysUser = ObjectSchema.create({
12
12
  displayNameField: "name",
13
13
  titleFormat: "{name}",
14
14
  compactLayout: ["name", "email", "email_verified"],
15
+ // Custom actions — generic CRUD is suppressed because user accounts are
16
+ // managed by better-auth, but we still need first-class affordances for
17
+ // common operations. Each action delegates to a Console-side named script
18
+ // registered on the ActionRunner (see objectui `AppContent.tsx`). Adding
19
+ // new affordances (reset password, revoke session, …) is now a pure
20
+ // schema + script-registration change — no per-view code.
21
+ actions: [
22
+ {
23
+ name: "invite_user",
24
+ label: "Invite User",
25
+ icon: "user-plus",
26
+ variant: "primary",
27
+ locations: ["list_toolbar"],
28
+ type: "api",
29
+ target: "/api/v1/auth/organization/invite-member",
30
+ successMessage: "Invitation sent",
31
+ refreshAfter: true,
32
+ params: [
33
+ { field: "email", required: true },
34
+ { field: "role", objectOverride: "sys_member", required: true }
35
+ ]
36
+ },
37
+ // ── Platform admin operations (require better-auth `admin` plugin) ─
38
+ //
39
+ // These actions hit /api/v1/auth/admin/* endpoints that are only
40
+ // wired when `auth.plugins.admin` is enabled. When the plugin is
41
+ // disabled the actions still render (schema is static) but server
42
+ // returns 404. UI surfaces them under the row menu so platform
43
+ // admins can manage accounts without dropping to SQL or
44
+ // a custom Setup wizard.
45
+ {
46
+ name: "ban_user",
47
+ label: "Ban User",
48
+ icon: "ban",
49
+ variant: "danger",
50
+ locations: ["list_item"],
51
+ type: "api",
52
+ target: "/api/v1/auth/admin/ban-user",
53
+ recordIdParam: "userId",
54
+ successMessage: "User banned",
55
+ refreshAfter: true,
56
+ confirmText: "Ban this user? They will be signed out and unable to sign in until unbanned.",
57
+ params: [
58
+ { name: "banReason", label: "Ban Reason", type: "text", required: false }
59
+ ]
60
+ },
61
+ {
62
+ name: "unban_user",
63
+ label: "Unban User",
64
+ icon: "check-circle-2",
65
+ variant: "secondary",
66
+ locations: ["list_item"],
67
+ type: "api",
68
+ target: "/api/v1/auth/admin/unban-user",
69
+ recordIdParam: "userId",
70
+ successMessage: "User unbanned",
71
+ refreshAfter: true
72
+ },
73
+ {
74
+ name: "set_user_password",
75
+ label: "Set Password",
76
+ icon: "key-round",
77
+ variant: "secondary",
78
+ locations: ["list_item"],
79
+ type: "api",
80
+ target: "/api/v1/auth/admin/set-user-password",
81
+ recordIdParam: "userId",
82
+ successMessage: "Password updated",
83
+ refreshAfter: false,
84
+ params: [
85
+ { name: "newPassword", label: "New Password", type: "text", required: true }
86
+ ]
87
+ },
88
+ {
89
+ name: "set_user_role",
90
+ label: "Set Platform Role",
91
+ icon: "shield-check",
92
+ variant: "secondary",
93
+ locations: ["list_item"],
94
+ type: "api",
95
+ target: "/api/v1/auth/admin/set-role",
96
+ recordIdParam: "userId",
97
+ successMessage: "Role updated",
98
+ refreshAfter: true,
99
+ params: [
100
+ { name: "role", label: "Platform Role", type: "text", required: true }
101
+ ]
102
+ },
103
+ {
104
+ name: "impersonate_user",
105
+ label: "Impersonate User",
106
+ icon: "user-cog",
107
+ variant: "secondary",
108
+ locations: ["list_item"],
109
+ type: "api",
110
+ target: "/api/v1/auth/admin/impersonate-user",
111
+ recordIdParam: "userId",
112
+ successMessage: "Now impersonating user",
113
+ refreshAfter: true,
114
+ confirmText: "Start an impersonation session for this user? Use only for legitimate support cases \u2014 actions will be logged."
115
+ }
116
+ ],
117
+ listViews: {
118
+ all_users: {
119
+ type: "grid",
120
+ name: "all_users",
121
+ label: "All Users",
122
+ data: { provider: "object", object: "sys_user" },
123
+ columns: ["name", "email", "email_verified", "two_factor_enabled", "created_at"],
124
+ sort: [{ field: "name", order: "asc" }],
125
+ pagination: { pageSize: 50 }
126
+ },
127
+ unverified: {
128
+ type: "grid",
129
+ name: "unverified",
130
+ label: "Unverified",
131
+ data: { provider: "object", object: "sys_user" },
132
+ columns: ["name", "email", "created_at"],
133
+ filter: [{ field: "email_verified", operator: "equals", value: false }],
134
+ sort: [{ field: "created_at", order: "desc" }],
135
+ pagination: { pageSize: 50 }
136
+ },
137
+ two_factor: {
138
+ type: "grid",
139
+ name: "two_factor",
140
+ label: "2FA Enabled",
141
+ data: { provider: "object", object: "sys_user" },
142
+ columns: ["name", "email", "two_factor_enabled", "updated_at"],
143
+ filter: [{ field: "two_factor_enabled", operator: "equals", value: true }],
144
+ sort: [{ field: "name", order: "asc" }],
145
+ pagination: { pageSize: 50 }
146
+ },
147
+ banned: {
148
+ type: "grid",
149
+ name: "banned",
150
+ label: "Banned",
151
+ data: { provider: "object", object: "sys_user" },
152
+ columns: ["name", "email", "banned", "ban_reason", "ban_expires"],
153
+ filter: [{ field: "banned", operator: "equals", value: true }],
154
+ sort: [{ field: "updated_at", order: "desc" }],
155
+ pagination: { pageSize: 50 }
156
+ }
157
+ },
15
158
  fields: {
16
159
  // ── Identity (primary business fields) ───────────────────────
17
160
  name: Field.text({
@@ -38,6 +181,32 @@ var SysUser = ObjectSchema.create({
38
181
  group: "Identity",
39
182
  description: "Whether two-factor authentication is enabled for this user. Maintained by the better-auth `twoFactor` plugin."
40
183
  }),
184
+ // ── Admin (managed by better-auth `admin` plugin when enabled) ───
185
+ role: Field.text({
186
+ label: "Platform Role",
187
+ required: false,
188
+ maxLength: 64,
189
+ group: "Admin",
190
+ description: "Platform-level role (admin, user, \u2026). Set via the Set Platform Role action."
191
+ }),
192
+ banned: Field.boolean({
193
+ label: "Banned",
194
+ defaultValue: false,
195
+ group: "Admin",
196
+ description: "When true, the user cannot sign in. Toggle via Ban User / Unban User actions."
197
+ }),
198
+ ban_reason: Field.text({
199
+ label: "Ban Reason",
200
+ required: false,
201
+ maxLength: 255,
202
+ group: "Admin"
203
+ }),
204
+ ban_expires: Field.datetime({
205
+ label: "Ban Expires",
206
+ required: false,
207
+ group: "Admin",
208
+ description: "When set, the ban auto-clears at this time."
209
+ }),
41
210
  // ── Profile ──────────────────────────────────────────────────
42
211
  image: Field.url({
43
212
  label: "Profile Image",
@@ -98,6 +267,62 @@ var SysSession = ObjectSchema.create({
98
267
  displayNameField: "user_id",
99
268
  titleFormat: "Session \u2014 {user_id}",
100
269
  compactLayout: ["user_id", "ip_address", "expires_at"],
270
+ // Custom actions — sessions are managed by better-auth (generic CRUD
271
+ // suppressed). "Sign out other devices" is the high-value self-service
272
+ // affordance every IdP exposes. Maps to better-auth's
273
+ // `revoke-other-sessions` endpoint which terminates every session for
274
+ // the current user except the one making the request.
275
+ actions: [
276
+ {
277
+ name: "revoke_my_other_sessions",
278
+ label: "Sign out other devices",
279
+ icon: "log-out",
280
+ variant: "danger",
281
+ locations: ["list_toolbar"],
282
+ type: "api",
283
+ target: "/api/v1/auth/revoke-other-sessions",
284
+ confirmText: "Sign out of every other device where you're currently logged in? Your current session will remain active.",
285
+ successMessage: "All other sessions revoked",
286
+ refreshAfter: true
287
+ },
288
+ {
289
+ name: "revoke_session",
290
+ label: "Revoke Session",
291
+ icon: "log-out",
292
+ variant: "danger",
293
+ mode: "delete",
294
+ locations: ["list_item"],
295
+ type: "api",
296
+ target: "/api/v1/auth/revoke-session",
297
+ // better-auth `revoke-session` keys off the session token, not the id.
298
+ recordIdParam: "token",
299
+ recordIdField: "token",
300
+ confirmText: "Revoke this session? The user will be signed out from that device.",
301
+ successMessage: "Session revoked",
302
+ refreshAfter: true
303
+ }
304
+ ],
305
+ listViews: {
306
+ mine: {
307
+ type: "grid",
308
+ name: "mine",
309
+ label: "My Sessions",
310
+ data: { provider: "object", object: "sys_session" },
311
+ columns: ["ip_address", "active_organization_id", "created_at", "expires_at"],
312
+ filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
313
+ sort: [{ field: "created_at", order: "desc" }],
314
+ pagination: { pageSize: 50 }
315
+ },
316
+ all_sessions: {
317
+ type: "grid",
318
+ name: "all_sessions",
319
+ label: "All",
320
+ data: { provider: "object", object: "sys_session" },
321
+ columns: ["user_id", "ip_address", "active_organization_id", "created_at", "expires_at"],
322
+ sort: [{ field: "created_at", order: "desc" }],
323
+ pagination: { pageSize: 50 }
324
+ }
325
+ },
101
326
  fields: {
102
327
  // ── Session owner & expiry ──────────────────────────────────
103
328
  user_id: Field.lookup("sys_user", {
@@ -135,6 +360,13 @@ var SysSession = ObjectSchema.create({
135
360
  required: false,
136
361
  group: "Client"
137
362
  }),
363
+ // ── Admin (managed by better-auth `admin` plugin) ────────────
364
+ impersonated_by: Field.lookup("sys_user", {
365
+ label: "Impersonated By",
366
+ required: false,
367
+ group: "Admin",
368
+ description: "User id of the admin that started this impersonation session, if any."
369
+ }),
138
370
  // ── Secret (hidden by default) ──────────────────────────────
139
371
  token: Field.text({
140
372
  label: "Session Token",
@@ -189,6 +421,37 @@ var SysAccount = ObjectSchema.create({
189
421
  description: "OAuth and authentication provider accounts",
190
422
  titleFormat: "{provider_id} - {account_id}",
191
423
  compactLayout: ["provider_id", "user_id", "account_id"],
424
+ listViews: {
425
+ mine: {
426
+ type: "grid",
427
+ name: "mine",
428
+ label: "My Links",
429
+ data: { provider: "object", object: "sys_account" },
430
+ columns: ["provider_id", "account_id", "created_at", "updated_at"],
431
+ filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
432
+ sort: [{ field: "provider_id", order: "asc" }],
433
+ pagination: { pageSize: 50 }
434
+ },
435
+ by_provider: {
436
+ type: "grid",
437
+ name: "by_provider",
438
+ label: "By Provider",
439
+ data: { provider: "object", object: "sys_account" },
440
+ columns: ["provider_id", "user_id", "account_id", "created_at"],
441
+ sort: [{ field: "provider_id", order: "asc" }, { field: "created_at", order: "desc" }],
442
+ grouping: { fields: [{ field: "provider_id", order: "asc", collapsed: false }] },
443
+ pagination: { pageSize: 100 }
444
+ },
445
+ all_links: {
446
+ type: "grid",
447
+ name: "all_links",
448
+ label: "All",
449
+ data: { provider: "object", object: "sys_account" },
450
+ columns: ["provider_id", "user_id", "account_id", "created_at", "updated_at"],
451
+ sort: [{ field: "created_at", order: "desc" }],
452
+ pagination: { pageSize: 100 }
453
+ }
454
+ },
192
455
  fields: {
193
456
  id: Field.text({
194
457
  label: "Account ID",
@@ -329,6 +592,104 @@ var SysOrganization = ObjectSchema.create({
329
592
  displayNameField: "name",
330
593
  titleFormat: "{name}",
331
594
  compactLayout: ["name", "slug"],
595
+ // Custom actions — generic CRUD is suppressed (better-auth-managed),
596
+ // but admins still need to create new orgs from the Setup app.
597
+ actions: [
598
+ {
599
+ name: "create_organization",
600
+ label: "Create Organization",
601
+ icon: "plus",
602
+ variant: "primary",
603
+ locations: ["list_toolbar"],
604
+ type: "api",
605
+ target: "/api/v1/auth/organization/create",
606
+ successMessage: "Organization created",
607
+ refreshAfter: true,
608
+ params: [
609
+ { field: "name", required: true },
610
+ { field: "slug", required: true },
611
+ { field: "logo" }
612
+ ]
613
+ },
614
+ {
615
+ name: "update_organization",
616
+ label: "Edit Organization",
617
+ icon: "pencil",
618
+ mode: "edit",
619
+ locations: ["list_item"],
620
+ type: "api",
621
+ target: "/api/v1/auth/organization/update",
622
+ recordIdParam: "organizationId",
623
+ // better-auth `organization/update` nests editable fields under `data`.
624
+ bodyShape: { wrap: "data" },
625
+ successMessage: "Organization updated",
626
+ refreshAfter: true,
627
+ params: [
628
+ { field: "name", required: true, defaultFromRow: true },
629
+ { field: "slug", required: true, defaultFromRow: true },
630
+ { field: "logo", defaultFromRow: true }
631
+ ]
632
+ },
633
+ {
634
+ name: "delete_organization",
635
+ label: "Delete Organization",
636
+ icon: "trash-2",
637
+ variant: "danger",
638
+ mode: "delete",
639
+ locations: ["list_item"],
640
+ type: "api",
641
+ target: "/api/v1/auth/organization/delete",
642
+ recordIdParam: "organizationId",
643
+ confirmText: "Delete this organization? All members will lose access immediately. This cannot be undone.",
644
+ successMessage: "Organization deleted",
645
+ refreshAfter: true
646
+ },
647
+ {
648
+ // Switch the caller's active organization context. Standard
649
+ // better-auth endpoint; no extra params needed (org id ships as
650
+ // the row id). Used from the Setup list and the record header so
651
+ // admins can context-switch without leaving the page.
652
+ name: "set_active_organization",
653
+ label: "Set Active",
654
+ icon: "check-circle-2",
655
+ variant: "secondary",
656
+ mode: "custom",
657
+ locations: ["list_item", "record_header"],
658
+ type: "api",
659
+ target: "/api/v1/auth/organization/set-active",
660
+ recordIdParam: "organizationId",
661
+ successMessage: "Active organization switched",
662
+ refreshAfter: true
663
+ },
664
+ {
665
+ // Current user leaves the org. Distinct from `delete_organization`
666
+ // (admin-only, destroys the org) — `leave` only removes the caller's
667
+ // own membership. Better-auth: `organization/leave { organizationId }`.
668
+ name: "leave_organization",
669
+ label: "Leave Organization",
670
+ icon: "log-out",
671
+ variant: "danger",
672
+ mode: "custom",
673
+ locations: ["list_item", "record_header"],
674
+ type: "api",
675
+ target: "/api/v1/auth/organization/leave",
676
+ recordIdParam: "organizationId",
677
+ confirmText: "Leave this organization? You will lose access to all of its resources.",
678
+ successMessage: "You have left the organization",
679
+ refreshAfter: true
680
+ }
681
+ ],
682
+ listViews: {
683
+ all_orgs: {
684
+ type: "grid",
685
+ name: "all_orgs",
686
+ label: "All",
687
+ data: { provider: "object", object: "sys_organization" },
688
+ columns: ["name", "slug", "created_at", "updated_at"],
689
+ sort: [{ field: "name", order: "asc" }],
690
+ pagination: { pageSize: 50 }
691
+ }
692
+ },
332
693
  fields: {
333
694
  // ── Identity ─────────────────────────────────────────────────
334
695
  name: Field.text({
@@ -402,6 +763,63 @@ var SysMember = ObjectSchema.create({
402
763
  description: "Organization membership records",
403
764
  titleFormat: "{user_id} in {organization_id}",
404
765
  compactLayout: ["user_id", "organization_id", "role"],
766
+ // Row-level actions: better-auth `organization/update-member-role` and
767
+ // `organization/remove-member`. Generic CRUD is suppressed on better-auth
768
+ // managed tables, so these are the canonical edit/delete entry points.
769
+ // The `add_member` toolbar action covers the admin "attach an existing
770
+ // user directly without sending an invitation" flow.
771
+ actions: [
772
+ {
773
+ // Admin-only: directly attach an existing user to the active org,
774
+ // bypassing the invite-accept flow. Better-auth:
775
+ // `organization/add-member { userId, role, organizationId?, teamId? }`.
776
+ // organizationId/teamId default to the caller's active org/team when
777
+ // omitted, so we leave them as optional params.
778
+ name: "add_member",
779
+ label: "Add Member",
780
+ icon: "user-plus",
781
+ variant: "primary",
782
+ locations: ["list_toolbar"],
783
+ type: "api",
784
+ target: "/api/v1/auth/organization/add-member",
785
+ successMessage: "Member added",
786
+ refreshAfter: true,
787
+ params: [
788
+ { name: "userId", field: "user_id", required: true },
789
+ { field: "role", required: true },
790
+ { name: "organizationId", field: "organization_id" }
791
+ ]
792
+ },
793
+ {
794
+ name: "update_member_role",
795
+ label: "Change Role",
796
+ icon: "shield",
797
+ mode: "edit",
798
+ locations: ["list_item"],
799
+ type: "api",
800
+ target: "/api/v1/auth/organization/update-member-role",
801
+ recordIdParam: "memberId",
802
+ successMessage: "Member role updated",
803
+ refreshAfter: true,
804
+ params: [
805
+ { field: "role", required: true, defaultFromRow: true }
806
+ ]
807
+ },
808
+ {
809
+ name: "remove_member",
810
+ label: "Remove Member",
811
+ icon: "user-minus",
812
+ variant: "danger",
813
+ mode: "delete",
814
+ locations: ["list_item"],
815
+ type: "api",
816
+ target: "/api/v1/auth/organization/remove-member",
817
+ recordIdParam: "memberIdOrEmail",
818
+ confirmText: "Remove this member from the organization? They will lose access to all org resources.",
819
+ successMessage: "Member removed",
820
+ refreshAfter: true
821
+ }
822
+ ],
405
823
  fields: {
406
824
  id: Field.text({
407
825
  label: "Member ID",
@@ -421,11 +839,16 @@ var SysMember = ObjectSchema.create({
421
839
  label: "User",
422
840
  required: true
423
841
  }),
424
- role: Field.text({
842
+ role: Field.select({
425
843
  label: "Role",
426
844
  required: false,
427
- description: "Member role within the organization (e.g. admin, member)",
428
- maxLength: 100
845
+ description: "Member role within the organization",
846
+ options: [
847
+ { label: "Owner", value: "owner" },
848
+ { label: "Admin", value: "admin" },
849
+ { label: "Member", value: "member" }
850
+ ],
851
+ defaultValue: "member"
429
852
  })
430
853
  },
431
854
  indexes: [
@@ -451,6 +874,97 @@ var SysInvitation = ObjectSchema.create({
451
874
  description: "Organization invitations for user onboarding",
452
875
  titleFormat: "Invitation to {organization_id}",
453
876
  compactLayout: ["email", "organization_id", "status"],
877
+ // Custom actions — generic CRUD is suppressed (better-auth-managed).
878
+ // Mirror the `invite_user` toolbar action from sys_user here so admins
879
+ // landing on the Invitations page get an obvious entry point.
880
+ actions: [
881
+ {
882
+ name: "invite_user",
883
+ label: "Invite User",
884
+ icon: "user-plus",
885
+ variant: "primary",
886
+ locations: ["list_toolbar"],
887
+ type: "api",
888
+ target: "/api/v1/auth/organization/invite-member",
889
+ successMessage: "Invitation sent",
890
+ refreshAfter: true,
891
+ params: [
892
+ { field: "email", required: true },
893
+ { field: "role", required: true }
894
+ ]
895
+ },
896
+ {
897
+ name: "cancel_invitation",
898
+ label: "Cancel Invitation",
899
+ icon: "x-circle",
900
+ variant: "danger",
901
+ mode: "delete",
902
+ locations: ["list_item"],
903
+ type: "api",
904
+ target: "/api/v1/auth/organization/cancel-invitation",
905
+ recordIdParam: "invitationId",
906
+ confirmText: "Cancel this invitation? The recipient will no longer be able to accept it.",
907
+ successMessage: "Invitation canceled",
908
+ refreshAfter: true
909
+ },
910
+ {
911
+ name: "resend_invitation",
912
+ label: "Resend Invitation",
913
+ icon: "send",
914
+ variant: "secondary",
915
+ locations: ["list_item"],
916
+ type: "api",
917
+ target: "/api/v1/auth/organization/invite-member",
918
+ bodyExtra: { resend: true },
919
+ successMessage: "Invitation resent",
920
+ refreshAfter: true,
921
+ params: [
922
+ { field: "email", required: true, defaultFromRow: true },
923
+ { field: "role", required: true, defaultFromRow: true }
924
+ ]
925
+ }
926
+ ],
927
+ listViews: {
928
+ pending: {
929
+ type: "grid",
930
+ name: "pending",
931
+ label: "Pending",
932
+ data: { provider: "object", object: "sys_invitation" },
933
+ columns: ["email", "role", "organization_id", "inviter_id", "expires_at"],
934
+ filter: [{ field: "status", operator: "equals", value: "pending" }],
935
+ sort: [{ field: "expires_at", order: "asc" }],
936
+ pagination: { pageSize: 50 }
937
+ },
938
+ accepted: {
939
+ type: "grid",
940
+ name: "accepted",
941
+ label: "Accepted",
942
+ data: { provider: "object", object: "sys_invitation" },
943
+ columns: ["email", "role", "organization_id", "inviter_id", "created_at"],
944
+ filter: [{ field: "status", operator: "equals", value: "accepted" }],
945
+ sort: [{ field: "created_at", order: "desc" }],
946
+ pagination: { pageSize: 50 }
947
+ },
948
+ expired: {
949
+ type: "grid",
950
+ name: "expired",
951
+ label: "Expired / Canceled",
952
+ data: { provider: "object", object: "sys_invitation" },
953
+ columns: ["email", "status", "organization_id", "expires_at"],
954
+ filter: [{ field: "status", operator: "in", value: ["expired", "rejected", "canceled"] }],
955
+ sort: [{ field: "expires_at", order: "desc" }],
956
+ pagination: { pageSize: 50 }
957
+ },
958
+ all_invitations: {
959
+ type: "grid",
960
+ name: "all_invitations",
961
+ label: "All",
962
+ data: { provider: "object", object: "sys_invitation" },
963
+ columns: ["email", "status", "role", "organization_id", "inviter_id", "created_at"],
964
+ sort: [{ field: "created_at", order: "desc" }],
965
+ pagination: { pageSize: 50 }
966
+ }
967
+ },
454
968
  fields: {
455
969
  id: Field.text({
456
970
  label: "Invitation ID",
@@ -471,11 +985,16 @@ var SysInvitation = ObjectSchema.create({
471
985
  required: true,
472
986
  description: "Email address of the invited user"
473
987
  }),
474
- role: Field.text({
988
+ role: Field.select({
475
989
  label: "Role",
476
990
  required: false,
477
- maxLength: 100,
478
- description: "Role to assign upon acceptance"
991
+ description: "Role to assign upon acceptance",
992
+ options: [
993
+ { label: "Owner", value: "owner" },
994
+ { label: "Admin", value: "admin" },
995
+ { label: "Member", value: "member" }
996
+ ],
997
+ defaultValue: "member"
479
998
  }),
480
999
  status: Field.select(["pending", "accepted", "rejected", "expired", "canceled"], {
481
1000
  label: "Status",
@@ -522,6 +1041,84 @@ var SysTeam = ObjectSchema.create({
522
1041
  displayNameField: "name",
523
1042
  titleFormat: "{name}",
524
1043
  compactLayout: ["name", "organization_id"],
1044
+ // Custom actions calling better-auth's team endpoints. Generic CRUD is
1045
+ // suppressed (managedBy: 'better-auth'), so these are the canonical
1046
+ // entry points for create/update/delete.
1047
+ actions: [
1048
+ {
1049
+ // Better-auth: `organization/create-team { name, organizationId? }`.
1050
+ // organizationId defaults to the caller's active org when omitted.
1051
+ name: "create_team",
1052
+ label: "Create Team",
1053
+ icon: "plus",
1054
+ variant: "primary",
1055
+ locations: ["list_toolbar"],
1056
+ type: "api",
1057
+ target: "/api/v1/auth/organization/create-team",
1058
+ successMessage: "Team created",
1059
+ refreshAfter: true,
1060
+ params: [
1061
+ { field: "name", required: true },
1062
+ { name: "organizationId", field: "organization_id" }
1063
+ ]
1064
+ },
1065
+ {
1066
+ // Better-auth: `organization/update-team { teamId, data: { name } }`.
1067
+ // teamId stays flat (top-level); the user-editable params nest under
1068
+ // `data` via bodyShape.
1069
+ name: "update_team",
1070
+ label: "Edit Team",
1071
+ icon: "pencil",
1072
+ mode: "edit",
1073
+ locations: ["list_item"],
1074
+ type: "api",
1075
+ target: "/api/v1/auth/organization/update-team",
1076
+ recordIdParam: "teamId",
1077
+ bodyShape: { wrap: "data" },
1078
+ successMessage: "Team updated",
1079
+ refreshAfter: true,
1080
+ params: [
1081
+ { field: "name", required: true, defaultFromRow: true }
1082
+ ]
1083
+ },
1084
+ {
1085
+ // Better-auth: `organization/remove-team { teamId, organizationId? }`.
1086
+ // organizationId defaults to the caller's active org when omitted.
1087
+ name: "remove_team",
1088
+ label: "Delete Team",
1089
+ icon: "trash-2",
1090
+ variant: "danger",
1091
+ mode: "delete",
1092
+ locations: ["list_item"],
1093
+ type: "api",
1094
+ target: "/api/v1/auth/organization/remove-team",
1095
+ recordIdParam: "teamId",
1096
+ confirmText: "Delete this team? Members will lose any team-scoped access. This cannot be undone.",
1097
+ successMessage: "Team deleted",
1098
+ refreshAfter: true
1099
+ }
1100
+ ],
1101
+ listViews: {
1102
+ by_org: {
1103
+ type: "grid",
1104
+ name: "by_org",
1105
+ label: "By Organization",
1106
+ data: { provider: "object", object: "sys_team" },
1107
+ columns: ["organization_id", "name", "created_at", "updated_at"],
1108
+ sort: [{ field: "organization_id", order: "asc" }, { field: "name", order: "asc" }],
1109
+ grouping: { fields: [{ field: "organization_id", order: "asc", collapsed: false }] },
1110
+ pagination: { pageSize: 100 }
1111
+ },
1112
+ all_teams: {
1113
+ type: "grid",
1114
+ name: "all_teams",
1115
+ label: "All",
1116
+ data: { provider: "object", object: "sys_team" },
1117
+ columns: ["name", "organization_id", "created_at", "updated_at"],
1118
+ sort: [{ field: "name", order: "asc" }],
1119
+ pagination: { pageSize: 50 }
1120
+ }
1121
+ },
525
1122
  fields: {
526
1123
  // ── Identity ─────────────────────────────────────────────────
527
1124
  name: Field.text({
@@ -580,6 +1177,48 @@ var SysTeamMember = ObjectSchema.create({
580
1177
  description: "Team membership records linking users to teams",
581
1178
  titleFormat: "{user_id} in {team_id}",
582
1179
  compactLayout: ["user_id", "team_id", "created_at"],
1180
+ // Custom actions calling better-auth's team-member endpoints. Generic
1181
+ // CRUD is suppressed (managedBy: 'better-auth') so these are the
1182
+ // canonical add/remove entry points.
1183
+ actions: [
1184
+ {
1185
+ // Better-auth: `organization/add-team-member { teamId, userId }`.
1186
+ name: "add_team_member",
1187
+ label: "Add Member",
1188
+ icon: "user-plus",
1189
+ variant: "primary",
1190
+ locations: ["list_toolbar"],
1191
+ type: "api",
1192
+ target: "/api/v1/auth/organization/add-team-member",
1193
+ successMessage: "Team member added",
1194
+ refreshAfter: true,
1195
+ params: [
1196
+ { name: "teamId", field: "team_id", required: true },
1197
+ { name: "userId", field: "user_id", required: true }
1198
+ ]
1199
+ },
1200
+ {
1201
+ // Better-auth: `organization/remove-team-member { teamId, userId }`.
1202
+ // The endpoint identifies the membership by the (teamId, userId)
1203
+ // pair rather than the join-row id, so we pull both from the row
1204
+ // via `defaultFromRow` instead of using `recordIdParam`.
1205
+ name: "remove_team_member",
1206
+ label: "Remove from Team",
1207
+ icon: "user-minus",
1208
+ variant: "danger",
1209
+ mode: "delete",
1210
+ locations: ["list_item"],
1211
+ type: "api",
1212
+ target: "/api/v1/auth/organization/remove-team-member",
1213
+ confirmText: "Remove this user from the team? They will lose any team-scoped access.",
1214
+ successMessage: "Team member removed",
1215
+ refreshAfter: true,
1216
+ params: [
1217
+ { name: "teamId", field: "team_id", required: true, defaultFromRow: true },
1218
+ { name: "userId", field: "user_id", required: true, defaultFromRow: true }
1219
+ ]
1220
+ }
1221
+ ],
583
1222
  fields: {
584
1223
  id: Field.text({
585
1224
  label: "Team Member ID",
@@ -613,6 +1252,248 @@ var SysTeamMember = ObjectSchema.create({
613
1252
  mru: false
614
1253
  }
615
1254
  });
1255
+ var SysDepartment = ObjectSchema.create({
1256
+ name: "sys_department",
1257
+ label: "Department",
1258
+ pluralLabel: "Departments",
1259
+ icon: "building",
1260
+ isSystem: true,
1261
+ managedBy: "platform",
1262
+ description: "Hierarchical org-skeleton node (department / division / business unit / office).",
1263
+ displayNameField: "name",
1264
+ titleFormat: "{name}",
1265
+ compactLayout: ["name", "kind", "parent_department_id", "manager_user_id"],
1266
+ listViews: {
1267
+ active: {
1268
+ type: "grid",
1269
+ name: "active",
1270
+ label: "Active",
1271
+ data: { provider: "object", object: "sys_department" },
1272
+ columns: ["name", "code", "kind", "parent_department_id", "manager_user_id", "effective_from"],
1273
+ filter: [{ field: "active", operator: "equals", value: true }],
1274
+ sort: [{ field: "name", order: "asc" }],
1275
+ pagination: { pageSize: 100 }
1276
+ },
1277
+ inactive: {
1278
+ type: "grid",
1279
+ name: "inactive",
1280
+ label: "Inactive",
1281
+ data: { provider: "object", object: "sys_department" },
1282
+ columns: ["name", "code", "kind", "effective_to"],
1283
+ filter: [{ field: "active", operator: "equals", value: false }],
1284
+ sort: [{ field: "effective_to", order: "desc" }],
1285
+ pagination: { pageSize: 50 }
1286
+ },
1287
+ by_kind: {
1288
+ type: "grid",
1289
+ name: "by_kind",
1290
+ label: "By Kind",
1291
+ data: { provider: "object", object: "sys_department" },
1292
+ columns: ["kind", "name", "code", "parent_department_id", "manager_user_id", "active"],
1293
+ sort: [{ field: "kind", order: "asc" }, { field: "name", order: "asc" }],
1294
+ grouping: { fields: [{ field: "kind", order: "asc", collapsed: false }] },
1295
+ pagination: { pageSize: 100 }
1296
+ },
1297
+ all_departments: {
1298
+ type: "grid",
1299
+ name: "all_departments",
1300
+ label: "All",
1301
+ data: { provider: "object", object: "sys_department" },
1302
+ columns: ["name", "code", "kind", "parent_department_id", "manager_user_id", "active"],
1303
+ sort: [{ field: "name", order: "asc" }],
1304
+ pagination: { pageSize: 100 }
1305
+ }
1306
+ },
1307
+ fields: {
1308
+ // ── Identity ─────────────────────────────────────────────────
1309
+ name: Field.text({
1310
+ label: "Name",
1311
+ required: true,
1312
+ searchable: true,
1313
+ maxLength: 255,
1314
+ group: "Identity"
1315
+ }),
1316
+ code: Field.text({
1317
+ label: "Code",
1318
+ required: false,
1319
+ searchable: true,
1320
+ maxLength: 64,
1321
+ description: "Short stable code (e.g. EMEA-SALES). Unique within tenant.",
1322
+ group: "Identity"
1323
+ }),
1324
+ kind: Field.select(
1325
+ ["company", "division", "department", "team", "office", "cost_center"],
1326
+ {
1327
+ label: "Kind",
1328
+ required: true,
1329
+ defaultValue: "department",
1330
+ description: "Categorisation hint \u2014 does not change graph semantics.",
1331
+ group: "Identity"
1332
+ }
1333
+ ),
1334
+ // ── Hierarchy ────────────────────────────────────────────────
1335
+ parent_department_id: Field.lookup("sys_department", {
1336
+ label: "Parent Department",
1337
+ required: false,
1338
+ description: "Self-reference for the org tree. Null = root of tenant.",
1339
+ group: "Hierarchy"
1340
+ }),
1341
+ organization_id: Field.lookup("sys_organization", {
1342
+ label: "Organization",
1343
+ required: true,
1344
+ description: "Tenant scope.",
1345
+ group: "Hierarchy"
1346
+ }),
1347
+ // ── Leadership ───────────────────────────────────────────────
1348
+ manager_user_id: Field.lookup("sys_user", {
1349
+ label: "Department Head",
1350
+ required: false,
1351
+ description: "User responsible for this org unit (department head / lead).",
1352
+ group: "Leadership"
1353
+ }),
1354
+ // ── Lifecycle ────────────────────────────────────────────────
1355
+ active: Field.boolean({
1356
+ label: "Active",
1357
+ required: false,
1358
+ defaultValue: true,
1359
+ description: "When false, members are not expanded by graph queries.",
1360
+ group: "Lifecycle"
1361
+ }),
1362
+ effective_from: Field.datetime({
1363
+ label: "Effective From",
1364
+ required: false,
1365
+ description: "When this department came into existence (HRIS sync).",
1366
+ group: "Lifecycle"
1367
+ }),
1368
+ effective_to: Field.datetime({
1369
+ label: "Effective To",
1370
+ required: false,
1371
+ description: "When this department was retired (HRIS sync).",
1372
+ group: "Lifecycle"
1373
+ }),
1374
+ external_ref: Field.text({
1375
+ label: "External Reference",
1376
+ required: false,
1377
+ maxLength: 200,
1378
+ description: "ID in upstream HRIS (Workday / SAP HR / \u5317\u68EE).",
1379
+ group: "Lifecycle"
1380
+ }),
1381
+ // ── System ───────────────────────────────────────────────────
1382
+ id: Field.text({
1383
+ label: "Department ID",
1384
+ required: true,
1385
+ readonly: true,
1386
+ group: "System"
1387
+ }),
1388
+ created_at: Field.datetime({
1389
+ label: "Created At",
1390
+ defaultValue: "NOW()",
1391
+ readonly: true,
1392
+ group: "System"
1393
+ }),
1394
+ updated_at: Field.datetime({
1395
+ label: "Updated At",
1396
+ defaultValue: "NOW()",
1397
+ readonly: true,
1398
+ group: "System"
1399
+ })
1400
+ },
1401
+ indexes: [
1402
+ { fields: ["organization_id"] },
1403
+ { fields: ["parent_department_id"] },
1404
+ { fields: ["code", "organization_id"], unique: true },
1405
+ { fields: ["active"] }
1406
+ ],
1407
+ enable: {
1408
+ trackHistory: true,
1409
+ searchable: true,
1410
+ apiEnabled: true,
1411
+ apiMethods: ["get", "list", "create", "update", "delete"],
1412
+ trash: true,
1413
+ mru: false
1414
+ }
1415
+ });
1416
+ var SysDepartmentMember = ObjectSchema.create({
1417
+ name: "sys_department_member",
1418
+ label: "Department Member",
1419
+ pluralLabel: "Department Members",
1420
+ icon: "user-cog",
1421
+ isSystem: true,
1422
+ managedBy: "platform",
1423
+ description: "User assignment to a department (matrix-org friendly, effective-dated).",
1424
+ titleFormat: "{user_id} in {department_id}",
1425
+ compactLayout: ["user_id", "department_id", "role_in_department", "is_primary"],
1426
+ fields: {
1427
+ id: Field.text({
1428
+ label: "Member ID",
1429
+ required: true,
1430
+ readonly: true,
1431
+ group: "System"
1432
+ }),
1433
+ department_id: Field.lookup("sys_department", {
1434
+ label: "Department",
1435
+ required: true,
1436
+ group: "Assignment"
1437
+ }),
1438
+ user_id: Field.lookup("sys_user", {
1439
+ label: "User",
1440
+ required: true,
1441
+ group: "Assignment"
1442
+ }),
1443
+ role_in_department: Field.select(
1444
+ ["member", "lead", "deputy"],
1445
+ {
1446
+ label: "Role in Department",
1447
+ required: false,
1448
+ defaultValue: "member",
1449
+ description: "`lead` is the day-to-day head; `deputy` may stand in for the lead in approval routing.",
1450
+ group: "Assignment"
1451
+ }
1452
+ ),
1453
+ is_primary: Field.boolean({
1454
+ label: "Primary Assignment",
1455
+ required: false,
1456
+ defaultValue: true,
1457
+ description: "When the user is in multiple departments, this marks the canonical one for reporting.",
1458
+ group: "Assignment"
1459
+ }),
1460
+ effective_from: Field.datetime({
1461
+ label: "Effective From",
1462
+ required: false,
1463
+ group: "Lifecycle"
1464
+ }),
1465
+ effective_to: Field.datetime({
1466
+ label: "Effective To",
1467
+ required: false,
1468
+ group: "Lifecycle"
1469
+ }),
1470
+ created_at: Field.datetime({
1471
+ label: "Created At",
1472
+ defaultValue: "NOW()",
1473
+ readonly: true,
1474
+ group: "System"
1475
+ }),
1476
+ updated_at: Field.datetime({
1477
+ label: "Updated At",
1478
+ defaultValue: "NOW()",
1479
+ readonly: true,
1480
+ group: "System"
1481
+ })
1482
+ },
1483
+ indexes: [
1484
+ { fields: ["department_id", "user_id"], unique: true },
1485
+ { fields: ["user_id"] },
1486
+ { fields: ["is_primary"] }
1487
+ ],
1488
+ enable: {
1489
+ trackHistory: true,
1490
+ searchable: true,
1491
+ apiEnabled: true,
1492
+ apiMethods: ["get", "list", "create", "update", "delete"],
1493
+ trash: true,
1494
+ mru: false
1495
+ }
1496
+ });
616
1497
  var SysApiKey = ObjectSchema.create({
617
1498
  name: "sys_api_key",
618
1499
  label: "API Key",
@@ -624,6 +1505,85 @@ var SysApiKey = ObjectSchema.create({
624
1505
  displayNameField: "name",
625
1506
  titleFormat: "{name}",
626
1507
  compactLayout: ["name", "prefix", "user_id", "expires_at", "revoked"],
1508
+ // Custom actions — sys_api_key is managed-by 'better-auth' but the
1509
+ // `revoked` boolean is a column we control via the data API. These row
1510
+ // actions use the generic PATCH /api/v1/sys_api_key/{id} endpoint with
1511
+ // `bodyExtra` to set the `revoked` flag explicitly.
1512
+ actions: [
1513
+ {
1514
+ name: "revoke_api_key",
1515
+ label: "Revoke API Key",
1516
+ icon: "shield-off",
1517
+ variant: "danger",
1518
+ mode: "custom",
1519
+ locations: ["list_item"],
1520
+ type: "api",
1521
+ method: "PATCH",
1522
+ target: "/api/v1/data/sys_api_key/{id}",
1523
+ bodyExtra: { revoked: true },
1524
+ confirmText: "Revoke this API key? Any clients using it will immediately lose access.",
1525
+ successMessage: "API key revoked",
1526
+ refreshAfter: true
1527
+ },
1528
+ {
1529
+ name: "restore_api_key",
1530
+ label: "Restore API Key",
1531
+ icon: "shield-check",
1532
+ variant: "secondary",
1533
+ mode: "custom",
1534
+ locations: ["list_item"],
1535
+ type: "api",
1536
+ method: "PATCH",
1537
+ target: "/api/v1/data/sys_api_key/{id}",
1538
+ bodyExtra: { revoked: false },
1539
+ confirmText: "Restore this revoked API key? Existing clients holding the key will regain access.",
1540
+ successMessage: "API key restored",
1541
+ refreshAfter: true
1542
+ }
1543
+ ],
1544
+ listViews: {
1545
+ mine: {
1546
+ type: "grid",
1547
+ name: "mine",
1548
+ label: "My Keys",
1549
+ data: { provider: "object", object: "sys_api_key" },
1550
+ columns: ["name", "prefix", "expires_at", "last_used_at", "revoked"],
1551
+ filter: [
1552
+ { field: "user_id", operator: "equals", value: "{current_user_id}" }
1553
+ ],
1554
+ sort: [{ field: "created_at", order: "desc" }],
1555
+ pagination: { pageSize: 50 }
1556
+ },
1557
+ active: {
1558
+ type: "grid",
1559
+ name: "active",
1560
+ label: "Active",
1561
+ data: { provider: "object", object: "sys_api_key" },
1562
+ columns: ["name", "prefix", "user_id", "expires_at", "last_used_at"],
1563
+ filter: [{ field: "revoked", operator: "equals", value: false }],
1564
+ sort: [{ field: "last_used_at", order: "desc" }],
1565
+ pagination: { pageSize: 50 }
1566
+ },
1567
+ revoked: {
1568
+ type: "grid",
1569
+ name: "revoked",
1570
+ label: "Revoked",
1571
+ data: { provider: "object", object: "sys_api_key" },
1572
+ columns: ["name", "prefix", "user_id", "expires_at", "updated_at"],
1573
+ filter: [{ field: "revoked", operator: "equals", value: true }],
1574
+ sort: [{ field: "updated_at", order: "desc" }],
1575
+ pagination: { pageSize: 50 }
1576
+ },
1577
+ all_keys: {
1578
+ type: "grid",
1579
+ name: "all_keys",
1580
+ label: "All",
1581
+ data: { provider: "object", object: "sys_api_key" },
1582
+ columns: ["name", "prefix", "user_id", "expires_at", "last_used_at", "revoked"],
1583
+ sort: [{ field: "created_at", order: "desc" }],
1584
+ pagination: { pageSize: 50 }
1585
+ }
1586
+ },
627
1587
  fields: {
628
1588
  // ── Identity ─────────────────────────────────────────────────
629
1589
  name: Field.text({
@@ -726,6 +1686,62 @@ var SysTwoFactor = ObjectSchema.create({
726
1686
  description: "Two-factor authentication credentials",
727
1687
  titleFormat: "Two-factor for {user_id}",
728
1688
  compactLayout: ["user_id", "created_at"],
1689
+ listViews: {
1690
+ mine: {
1691
+ type: "grid",
1692
+ name: "mine",
1693
+ label: "My Enrollment",
1694
+ data: { provider: "object", object: "sys_two_factor" },
1695
+ columns: ["created_at", "updated_at"],
1696
+ filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
1697
+ sort: [{ field: "created_at", order: "desc" }],
1698
+ pagination: { pageSize: 50 }
1699
+ },
1700
+ all_enrollments: {
1701
+ type: "grid",
1702
+ name: "all_enrollments",
1703
+ label: "All",
1704
+ data: { provider: "object", object: "sys_two_factor" },
1705
+ columns: ["user_id", "created_at", "updated_at"],
1706
+ sort: [{ field: "created_at", order: "desc" }],
1707
+ pagination: { pageSize: 50 }
1708
+ }
1709
+ },
1710
+ // Toolbar actions for self-service 2FA enrollment. The actual TOTP secret
1711
+ // and backup codes returned by better-auth must be shown in the response
1712
+ // toast / dialog — the action runner surfaces successMessage; the raw
1713
+ // payload is logged client-side for now (TODO: dedicated 2FA setup wizard).
1714
+ actions: [
1715
+ {
1716
+ name: "enable_two_factor",
1717
+ label: "Enable 2FA",
1718
+ icon: "shield-check",
1719
+ variant: "primary",
1720
+ locations: ["list_toolbar"],
1721
+ type: "api",
1722
+ target: "/api/v1/auth/two-factor/enable",
1723
+ successMessage: "2FA enrollment started \u2014 check response for TOTP URI and backup codes",
1724
+ refreshAfter: true,
1725
+ params: [
1726
+ { name: "password", label: "Current Password", type: "text", required: true }
1727
+ ]
1728
+ },
1729
+ {
1730
+ name: "disable_two_factor",
1731
+ label: "Disable 2FA",
1732
+ icon: "shield-off",
1733
+ variant: "danger",
1734
+ locations: ["list_toolbar"],
1735
+ type: "api",
1736
+ target: "/api/v1/auth/two-factor/disable",
1737
+ confirmText: "Disable two-factor authentication on your account?",
1738
+ successMessage: "2FA disabled",
1739
+ refreshAfter: true,
1740
+ params: [
1741
+ { name: "password", label: "Current Password", type: "text", required: true }
1742
+ ]
1743
+ }
1744
+ ],
729
1745
  fields: {
730
1746
  id: Field.text({
731
1747
  label: "Two Factor ID",
@@ -865,9 +1881,44 @@ var SysUserPreference = ObjectSchema.create({
865
1881
  pluralLabel: "User Preferences",
866
1882
  icon: "settings",
867
1883
  isSystem: true,
1884
+ // managedBy: 'system' — preferences are per-user state authored from
1885
+ // the user's own settings page, never created by an admin. The list
1886
+ // surface in Setup is a support/diagnostic view only.
1887
+ managedBy: "system",
868
1888
  description: "Per-user key-value preferences (theme, locale, etc.)",
869
1889
  titleFormat: "{key}",
870
1890
  compactLayout: ["user_id", "key"],
1891
+ listViews: {
1892
+ mine: {
1893
+ type: "grid",
1894
+ name: "mine",
1895
+ label: "My Preferences",
1896
+ data: { provider: "object", object: "sys_user_preference" },
1897
+ columns: ["key", "updated_at"],
1898
+ filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
1899
+ sort: [{ field: "key", order: "asc" }],
1900
+ pagination: { pageSize: 100 }
1901
+ },
1902
+ by_user: {
1903
+ type: "grid",
1904
+ name: "by_user",
1905
+ label: "By User",
1906
+ data: { provider: "object", object: "sys_user_preference" },
1907
+ columns: ["user_id", "key", "updated_at"],
1908
+ sort: [{ field: "user_id", order: "asc" }, { field: "key", order: "asc" }],
1909
+ grouping: { fields: [{ field: "user_id", order: "asc", collapsed: true }] },
1910
+ pagination: { pageSize: 200 }
1911
+ },
1912
+ all_preferences: {
1913
+ type: "grid",
1914
+ name: "all_preferences",
1915
+ label: "All",
1916
+ data: { provider: "object", object: "sys_user_preference" },
1917
+ columns: ["user_id", "key", "created_at", "updated_at"],
1918
+ sort: [{ field: "updated_at", order: "desc" }],
1919
+ pagination: { pageSize: 100 }
1920
+ }
1921
+ },
871
1922
  fields: {
872
1923
  id: Field.text({
873
1924
  label: "Preference ID",
@@ -924,6 +1975,37 @@ var SysOauthApplication = ObjectSchema.create({
924
1975
  displayNameField: "name",
925
1976
  titleFormat: "{name}",
926
1977
  compactLayout: ["name", "client_id", "type", "disabled"],
1978
+ listViews: {
1979
+ active: {
1980
+ type: "grid",
1981
+ name: "active",
1982
+ label: "Active",
1983
+ data: { provider: "object", object: "sys_oauth_application" },
1984
+ columns: ["name", "client_id", "type", "updated_at"],
1985
+ filter: [{ field: "disabled", operator: "equals", value: false }],
1986
+ sort: [{ field: "name", order: "asc" }],
1987
+ pagination: { pageSize: 50 }
1988
+ },
1989
+ disabled_apps: {
1990
+ type: "grid",
1991
+ name: "disabled_apps",
1992
+ label: "Disabled",
1993
+ data: { provider: "object", object: "sys_oauth_application" },
1994
+ columns: ["name", "client_id", "type", "updated_at"],
1995
+ filter: [{ field: "disabled", operator: "equals", value: true }],
1996
+ sort: [{ field: "updated_at", order: "desc" }],
1997
+ pagination: { pageSize: 50 }
1998
+ },
1999
+ all_apps: {
2000
+ type: "grid",
2001
+ name: "all_apps",
2002
+ label: "All",
2003
+ data: { provider: "object", object: "sys_oauth_application" },
2004
+ columns: ["name", "client_id", "type", "disabled", "created_at"],
2005
+ sort: [{ field: "name", order: "asc" }],
2006
+ pagination: { pageSize: 50 }
2007
+ }
2008
+ },
927
2009
  fields: {
928
2010
  // ── Identity ─────────────────────────────────────────────────
929
2011
  id: Field.text({
@@ -1397,6 +2479,6 @@ var SysJwks = ObjectSchema.create({
1397
2479
  }
1398
2480
  });
1399
2481
 
1400
- export { SysAccount, SysApiKey, SysDeviceCode, SysInvitation, SysJwks, SysMember, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysSession, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserPreference, SysVerification };
2482
+ export { SysAccount, SysApiKey, SysDepartment, SysDepartmentMember, SysDeviceCode, SysInvitation, SysJwks, SysMember, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysSession, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserPreference, SysVerification };
1401
2483
  //# sourceMappingURL=index.mjs.map
1402
2484
  //# sourceMappingURL=index.mjs.map