@objectstack/platform-objects 0.1.0 → 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 -215
  4. package/dist/apps/index.js.map +1 -1
  5. package/dist/apps/index.mjs +140 -210
  6. package/dist/apps/index.mjs.map +1 -1
  7. package/dist/audit/index.d.mts +40249 -150
  8. package/dist/audit/index.d.ts +40249 -150
  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 +18792 -2520
  14. package/dist/identity/index.d.ts +18792 -2520
  15. package/dist/identity/index.js +1107 -6
  16. package/dist/identity/index.js.map +1 -1
  17. package/dist/identity/index.mjs +1106 -7
  18. package/dist/identity/index.mjs.map +1 -1
  19. package/dist/index.d.mts +9 -7
  20. package/dist/index.d.ts +9 -7
  21. package/dist/index.js +3939 -1504
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +3917 -1487
  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 +1426 -19092
  32. package/dist/metadata/index.d.ts +1426 -19092
  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 +10759 -60
  38. package/dist/security/index.d.ts +10759 -60
  39. package/dist/security/index.js +786 -0
  40. package/dist/security/index.js.map +1 -1
  41. package/dist/security/index.mjs +782 -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 -16454
  51. package/dist/tenant/index.d.ts +0 -16454
  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
@@ -7,10 +7,154 @@ var SysUser = ObjectSchema.create({
7
7
  pluralLabel: "Users",
8
8
  icon: "user",
9
9
  isSystem: true,
10
+ managedBy: "better-auth",
10
11
  description: "User accounts for authentication",
11
12
  displayNameField: "name",
12
13
  titleFormat: "{name}",
13
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
+ },
14
158
  fields: {
15
159
  // ── Identity (primary business fields) ───────────────────────
16
160
  name: Field.text({
@@ -37,6 +181,32 @@ var SysUser = ObjectSchema.create({
37
181
  group: "Identity",
38
182
  description: "Whether two-factor authentication is enabled for this user. Maintained by the better-auth `twoFactor` plugin."
39
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
+ }),
40
210
  // ── Profile ──────────────────────────────────────────────────
41
211
  image: Field.url({
42
212
  label: "Profile Image",
@@ -92,10 +262,67 @@ var SysSession = ObjectSchema.create({
92
262
  pluralLabel: "Sessions",
93
263
  icon: "key",
94
264
  isSystem: true,
265
+ managedBy: "better-auth",
95
266
  description: "Active user sessions",
96
267
  displayNameField: "user_id",
97
268
  titleFormat: "Session \u2014 {user_id}",
98
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
+ },
99
326
  fields: {
100
327
  // ── Session owner & expiry ──────────────────────────────────
101
328
  user_id: Field.lookup("sys_user", {
@@ -133,6 +360,13 @@ var SysSession = ObjectSchema.create({
133
360
  required: false,
134
361
  group: "Client"
135
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
+ }),
136
370
  // ── Secret (hidden by default) ──────────────────────────────
137
371
  token: Field.text({
138
372
  label: "Session Token",
@@ -183,9 +417,41 @@ var SysAccount = ObjectSchema.create({
183
417
  pluralLabel: "Accounts",
184
418
  icon: "link",
185
419
  isSystem: true,
420
+ managedBy: "better-auth",
186
421
  description: "OAuth and authentication provider accounts",
187
422
  titleFormat: "{provider_id} - {account_id}",
188
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
+ },
189
455
  fields: {
190
456
  id: Field.text({
191
457
  label: "Account ID",
@@ -266,6 +532,7 @@ var SysVerification = ObjectSchema.create({
266
532
  pluralLabel: "Verifications",
267
533
  icon: "shield-check",
268
534
  isSystem: true,
535
+ managedBy: "better-auth",
269
536
  description: "Email and phone verification tokens",
270
537
  titleFormat: "Verification for {identifier}",
271
538
  compactLayout: ["identifier", "expires_at", "created_at"],
@@ -320,10 +587,109 @@ var SysOrganization = ObjectSchema.create({
320
587
  pluralLabel: "Organizations",
321
588
  icon: "building-2",
322
589
  isSystem: true,
590
+ managedBy: "better-auth",
323
591
  description: "Organizations for multi-tenant grouping",
324
592
  displayNameField: "name",
325
593
  titleFormat: "{name}",
326
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
+ },
327
693
  fields: {
328
694
  // ── Identity ─────────────────────────────────────────────────
329
695
  name: Field.text({
@@ -393,9 +759,67 @@ var SysMember = ObjectSchema.create({
393
759
  pluralLabel: "Members",
394
760
  icon: "user-check",
395
761
  isSystem: true,
762
+ managedBy: "better-auth",
396
763
  description: "Organization membership records",
397
764
  titleFormat: "{user_id} in {organization_id}",
398
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
+ ],
399
823
  fields: {
400
824
  id: Field.text({
401
825
  label: "Member ID",
@@ -415,11 +839,16 @@ var SysMember = ObjectSchema.create({
415
839
  label: "User",
416
840
  required: true
417
841
  }),
418
- role: Field.text({
842
+ role: Field.select({
419
843
  label: "Role",
420
844
  required: false,
421
- description: "Member role within the organization (e.g. admin, member)",
422
- 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"
423
852
  })
424
853
  },
425
854
  indexes: [
@@ -441,9 +870,101 @@ var SysInvitation = ObjectSchema.create({
441
870
  pluralLabel: "Invitations",
442
871
  icon: "mail",
443
872
  isSystem: true,
873
+ managedBy: "better-auth",
444
874
  description: "Organization invitations for user onboarding",
445
875
  titleFormat: "Invitation to {organization_id}",
446
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
+ },
447
968
  fields: {
448
969
  id: Field.text({
449
970
  label: "Invitation ID",
@@ -464,11 +985,16 @@ var SysInvitation = ObjectSchema.create({
464
985
  required: true,
465
986
  description: "Email address of the invited user"
466
987
  }),
467
- role: Field.text({
988
+ role: Field.select({
468
989
  label: "Role",
469
990
  required: false,
470
- maxLength: 100,
471
- 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"
472
998
  }),
473
999
  status: Field.select(["pending", "accepted", "rejected", "expired", "canceled"], {
474
1000
  label: "Status",
@@ -510,10 +1036,89 @@ var SysTeam = ObjectSchema.create({
510
1036
  pluralLabel: "Teams",
511
1037
  icon: "users",
512
1038
  isSystem: true,
1039
+ managedBy: "better-auth",
513
1040
  description: "Teams within organizations for fine-grained grouping",
514
1041
  displayNameField: "name",
515
1042
  titleFormat: "{name}",
516
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
+ },
517
1122
  fields: {
518
1123
  // ── Identity ─────────────────────────────────────────────────
519
1124
  name: Field.text({
@@ -568,9 +1173,52 @@ var SysTeamMember = ObjectSchema.create({
568
1173
  pluralLabel: "Team Members",
569
1174
  icon: "user-plus",
570
1175
  isSystem: true,
1176
+ managedBy: "better-auth",
571
1177
  description: "Team membership records linking users to teams",
572
1178
  titleFormat: "{user_id} in {team_id}",
573
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
+ ],
574
1222
  fields: {
575
1223
  id: Field.text({
576
1224
  label: "Team Member ID",
@@ -604,16 +1252,338 @@ var SysTeamMember = ObjectSchema.create({
604
1252
  mru: false
605
1253
  }
606
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
+ });
607
1497
  var SysApiKey = ObjectSchema.create({
608
1498
  name: "sys_api_key",
609
1499
  label: "API Key",
610
1500
  pluralLabel: "API Keys",
611
1501
  icon: "key-round",
612
1502
  isSystem: true,
1503
+ managedBy: "better-auth",
613
1504
  description: "API keys for programmatic access",
614
1505
  displayNameField: "name",
615
1506
  titleFormat: "{name}",
616
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
+ },
617
1587
  fields: {
618
1588
  // ── Identity ─────────────────────────────────────────────────
619
1589
  name: Field.text({
@@ -712,9 +1682,66 @@ var SysTwoFactor = ObjectSchema.create({
712
1682
  pluralLabel: "Two Factor Credentials",
713
1683
  icon: "smartphone",
714
1684
  isSystem: true,
1685
+ managedBy: "better-auth",
715
1686
  description: "Two-factor authentication credentials",
716
1687
  titleFormat: "Two-factor for {user_id}",
717
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
+ ],
718
1745
  fields: {
719
1746
  id: Field.text({
720
1747
  label: "Two Factor ID",
@@ -764,6 +1791,7 @@ var SysDeviceCode = ObjectSchema.create({
764
1791
  pluralLabel: "Device Codes",
765
1792
  icon: "key-round",
766
1793
  isSystem: true,
1794
+ managedBy: "better-auth",
767
1795
  description: "OAuth 2.0 Device Authorization Grant (RFC 8628) pending requests",
768
1796
  titleFormat: "{user_code}",
769
1797
  compactLayout: ["user_code", "status", "client_id", "expires_at"],
@@ -853,9 +1881,44 @@ var SysUserPreference = ObjectSchema.create({
853
1881
  pluralLabel: "User Preferences",
854
1882
  icon: "settings",
855
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",
856
1888
  description: "Per-user key-value preferences (theme, locale, etc.)",
857
1889
  titleFormat: "{key}",
858
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
+ },
859
1922
  fields: {
860
1923
  id: Field.text({
861
1924
  label: "Preference ID",
@@ -907,10 +1970,42 @@ var SysOauthApplication = ObjectSchema.create({
907
1970
  pluralLabel: "OAuth Applications",
908
1971
  icon: "key-round",
909
1972
  isSystem: true,
1973
+ managedBy: "better-auth",
910
1974
  description: "Registered OAuth/OIDC client applications",
911
1975
  displayNameField: "name",
912
1976
  titleFormat: "{name}",
913
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
+ },
914
2009
  fields: {
915
2010
  // ── Identity ─────────────────────────────────────────────────
916
2011
  id: Field.text({
@@ -1122,6 +2217,7 @@ var SysOauthAccessToken = ObjectSchema.create({
1122
2217
  pluralLabel: "OAuth Access Tokens",
1123
2218
  icon: "ticket",
1124
2219
  isSystem: true,
2220
+ managedBy: "better-auth",
1125
2221
  description: "Opaque OAuth access tokens issued to client applications",
1126
2222
  compactLayout: ["client_id", "user_id", "expires_at"],
1127
2223
  fields: {
@@ -1199,6 +2295,7 @@ var SysOauthRefreshToken = ObjectSchema.create({
1199
2295
  pluralLabel: "OAuth Refresh Tokens",
1200
2296
  icon: "refresh-cw",
1201
2297
  isSystem: true,
2298
+ managedBy: "better-auth",
1202
2299
  description: "Opaque OAuth refresh tokens (linked to a session)",
1203
2300
  compactLayout: ["client_id", "user_id", "expires_at"],
1204
2301
  fields: {
@@ -1280,6 +2377,7 @@ var SysOauthConsent = ObjectSchema.create({
1280
2377
  pluralLabel: "OAuth Consents",
1281
2378
  icon: "shield-check",
1282
2379
  isSystem: true,
2380
+ managedBy: "better-auth",
1283
2381
  description: "User consent records for OAuth client applications",
1284
2382
  compactLayout: ["client_id", "user_id", "scopes"],
1285
2383
  fields: {
@@ -1339,6 +2437,7 @@ var SysJwks = ObjectSchema.create({
1339
2437
  pluralLabel: "JWKS Keys",
1340
2438
  icon: "key",
1341
2439
  isSystem: true,
2440
+ managedBy: "better-auth",
1342
2441
  description: "Asymmetric key pairs used to sign and verify issued JWTs",
1343
2442
  compactLayout: ["id", "created_at", "expires_at"],
1344
2443
  fields: {
@@ -1380,6 +2479,6 @@ var SysJwks = ObjectSchema.create({
1380
2479
  }
1381
2480
  });
1382
2481
 
1383
- 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 };
1384
2483
  //# sourceMappingURL=index.mjs.map
1385
2484
  //# sourceMappingURL=index.mjs.map