@objectstack/platform-objects 4.0.5 → 4.1.1

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 +66 -48
  2. package/dist/apps/index.d.ts +66 -48
  3. package/dist/apps/index.js +12158 -217
  4. package/dist/apps/index.js.map +1 -1
  5. package/dist/apps/index.mjs +12154 -212
  6. package/dist/apps/index.mjs.map +1 -1
  7. package/dist/audit/index.d.mts +39010 -55
  8. package/dist/audit/index.d.ts +39010 -55
  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 +14904 -2817
  14. package/dist/identity/index.d.ts +14904 -2817
  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 +9 -7
  20. package/dist/index.d.ts +9 -7
  21. package/dist/index.js +15839 -1650
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +15815 -1633
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/integration/index.d.mts +2906 -0
  26. package/dist/integration/index.d.ts +2906 -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 +581 -21183
  32. package/dist/metadata/index.d.ts +581 -21183
  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 +7288 -50
  38. package/dist/security/index.d.ts +7288 -50
  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 +8412 -0
  44. package/dist/system/index.d.ts +8412 -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
@@ -7,10 +7,62 @@ var SysAuditLog = ObjectSchema.create({
7
7
  pluralLabel: "Audit Logs",
8
8
  icon: "scroll-text",
9
9
  isSystem: true,
10
+ managedBy: "append-only",
10
11
  description: "Immutable audit trail for platform events",
11
12
  displayNameField: "action",
12
13
  titleFormat: "{action} \xB7 {object_name}",
13
14
  compactLayout: ["created_at", "action", "object_name", "record_id", "user_id"],
15
+ listViews: {
16
+ recent: {
17
+ type: "grid",
18
+ name: "recent",
19
+ label: "Recent",
20
+ data: { provider: "object", object: "sys_audit_log" },
21
+ columns: ["created_at", "action", "object_name", "record_id", "user_id"],
22
+ sort: [{ field: "created_at", order: "desc" }],
23
+ pagination: { pageSize: 50 },
24
+ emptyState: { title: "No audit events", message: "Activity will appear here as users interact with the platform." }
25
+ },
26
+ writes_only: {
27
+ type: "grid",
28
+ name: "writes_only",
29
+ label: "Writes",
30
+ data: { provider: "object", object: "sys_audit_log" },
31
+ columns: ["created_at", "action", "object_name", "record_id", "user_id"],
32
+ filter: [{ field: "action", operator: "in", value: ["create", "update", "delete", "restore"] }],
33
+ sort: [{ field: "created_at", order: "desc" }],
34
+ pagination: { pageSize: 50 }
35
+ },
36
+ auth_events: {
37
+ type: "grid",
38
+ name: "auth_events",
39
+ label: "Auth",
40
+ data: { provider: "object", object: "sys_audit_log" },
41
+ columns: ["created_at", "action", "user_id"],
42
+ filter: [{ field: "action", operator: "in", value: ["login", "logout", "permission_change"] }],
43
+ sort: [{ field: "created_at", order: "desc" }],
44
+ pagination: { pageSize: 50 }
45
+ },
46
+ config_changes: {
47
+ type: "grid",
48
+ name: "config_changes",
49
+ label: "Config",
50
+ data: { provider: "object", object: "sys_audit_log" },
51
+ columns: ["created_at", "action", "object_name", "user_id"],
52
+ filter: [{ field: "action", operator: "in", value: ["config_change", "export", "import"] }],
53
+ sort: [{ field: "created_at", order: "desc" }],
54
+ pagination: { pageSize: 50 }
55
+ },
56
+ all_events: {
57
+ type: "grid",
58
+ name: "all_events",
59
+ label: "All",
60
+ data: { provider: "object", object: "sys_audit_log" },
61
+ columns: ["created_at", "action", "object_name", "record_id", "user_id"],
62
+ sort: [{ field: "created_at", order: "desc" }],
63
+ pagination: { pageSize: 100 }
64
+ }
65
+ },
14
66
  fields: {
15
67
  // ── Event ────────────────────────────────────────────────────
16
68
  created_at: Field.datetime({
@@ -135,6 +187,7 @@ var SysPresence = ObjectSchema.create({
135
187
  pluralLabel: "Presences",
136
188
  icon: "wifi",
137
189
  isSystem: true,
190
+ managedBy: "append-only",
138
191
  description: "Real-time user presence and activity tracking",
139
192
  titleFormat: "{user_id} ({status})",
140
193
  compactLayout: ["user_id", "status", "last_seen"],
@@ -225,6 +278,7 @@ var SysActivity = ObjectSchema.create({
225
278
  pluralLabel: "Activities",
226
279
  icon: "activity",
227
280
  isSystem: true,
281
+ managedBy: "append-only",
228
282
  description: "Recent activity stream entries (lightweight, denormalized)",
229
283
  displayNameField: "summary",
230
284
  titleFormat: "{type} \xB7 {summary}",
@@ -366,6 +420,7 @@ var SysComment = ObjectSchema.create({
366
420
  pluralLabel: "Comments",
367
421
  icon: "message-square",
368
422
  isSystem: true,
423
+ managedBy: "platform",
369
424
  description: "Threaded comments attached to records via thread_id",
370
425
  displayNameField: "body",
371
426
  titleFormat: "{author_name}: {body}",
@@ -481,7 +536,1368 @@ var SysComment = ObjectSchema.create({
481
536
  clone: false
482
537
  }
483
538
  });
539
+ var SysAttachment = ObjectSchema.create({
540
+ name: "sys_attachment",
541
+ label: "Attachment",
542
+ pluralLabel: "Attachments",
543
+ icon: "paperclip",
544
+ isSystem: true,
545
+ managedBy: "platform",
546
+ description: "Polymorphic link between a sys_file and any other record",
547
+ titleFormat: "{file_name} \u2192 {parent_object}/{parent_id}",
548
+ compactLayout: ["created_at", "parent_object", "file_name", "mime_type", "size"],
549
+ fields: {
550
+ id: Field.text({
551
+ label: "Attachment ID",
552
+ required: true,
553
+ readonly: true,
554
+ group: "System"
555
+ }),
556
+ // ── Parent (polymorphic) ────────────────────────────────────
557
+ parent_object: Field.text({
558
+ label: "Parent Object",
559
+ required: true,
560
+ searchable: true,
561
+ maxLength: 128,
562
+ description: "Short object name of the attached-to record (e.g. account, lead)",
563
+ group: "Parent"
564
+ }),
565
+ parent_id: Field.text({
566
+ label: "Parent Record",
567
+ required: true,
568
+ searchable: true,
569
+ maxLength: 64,
570
+ description: "Primary key of the attached-to record",
571
+ group: "Parent"
572
+ }),
573
+ // ── File reference ──────────────────────────────────────────
574
+ file_id: Field.lookup("sys_file", {
575
+ label: "File",
576
+ required: true,
577
+ description: "The sys_file storage entry being attached",
578
+ group: "File"
579
+ }),
580
+ file_name: Field.text({
581
+ label: "File Name",
582
+ required: false,
583
+ searchable: true,
584
+ maxLength: 255,
585
+ description: "Denormalised copy of sys_file.name for fast list rendering",
586
+ group: "File"
587
+ }),
588
+ mime_type: Field.text({
589
+ label: "MIME Type",
590
+ required: false,
591
+ maxLength: 128,
592
+ group: "File"
593
+ }),
594
+ size: Field.number({
595
+ label: "Size (bytes)",
596
+ required: false,
597
+ group: "File"
598
+ }),
599
+ // ── Sharing ────────────────────────────────────────────────
600
+ share_type: Field.select(
601
+ ["viewer", "collaborator", "inferred"],
602
+ {
603
+ label: "Share Type",
604
+ defaultValue: "viewer",
605
+ description: "viewer | collaborator | inferred (inherited from parent record)",
606
+ group: "Sharing"
607
+ }
608
+ ),
609
+ visibility: Field.select(
610
+ ["internal", "all_users", "shared_users"],
611
+ {
612
+ label: "Visibility",
613
+ defaultValue: "internal",
614
+ group: "Sharing"
615
+ }
616
+ ),
617
+ // ── Authoring ──────────────────────────────────────────────
618
+ uploaded_by: Field.lookup("sys_user", {
619
+ label: "Uploaded By",
620
+ required: false,
621
+ group: "System"
622
+ }),
623
+ description: Field.textarea({
624
+ label: "Description",
625
+ required: false,
626
+ maxLength: 1024,
627
+ group: "File"
628
+ }),
629
+ created_at: Field.datetime({
630
+ label: "Created At",
631
+ required: true,
632
+ defaultValue: "NOW()",
633
+ readonly: true,
634
+ group: "System"
635
+ }),
636
+ updated_at: Field.datetime({
637
+ label: "Updated At",
638
+ required: false,
639
+ group: "System"
640
+ })
641
+ },
642
+ indexes: [
643
+ { fields: ["parent_object", "parent_id", "created_at"] },
644
+ { fields: ["file_id"] },
645
+ { fields: ["uploaded_by"] }
646
+ ],
647
+ enable: {
648
+ trackHistory: false,
649
+ searchable: true,
650
+ apiEnabled: true,
651
+ trash: true,
652
+ mru: false,
653
+ clone: false
654
+ }
655
+ });
656
+ var SysNotification = ObjectSchema.create({
657
+ name: "sys_notification",
658
+ label: "Notification",
659
+ pluralLabel: "Notifications",
660
+ icon: "bell",
661
+ isSystem: true,
662
+ managedBy: "system",
663
+ description: "Per-user notification inbox entries",
664
+ displayNameField: "title",
665
+ titleFormat: "{title}",
666
+ compactLayout: ["title", "type", "is_read", "created_at"],
667
+ listViews: {
668
+ unread: {
669
+ type: "grid",
670
+ name: "unread",
671
+ label: "Unread",
672
+ data: { provider: "object", object: "sys_notification" },
673
+ columns: ["type", "title", "recipient_id", "created_at"],
674
+ filter: [
675
+ { field: "recipient_id", operator: "equals", value: "{current_user_id}" },
676
+ { field: "is_read", operator: "equals", value: false }
677
+ ],
678
+ sort: [{ field: "created_at", order: "desc" }],
679
+ pagination: { pageSize: 50 },
680
+ emptyState: { title: "Inbox zero", message: "No unread notifications." }
681
+ },
682
+ mine: {
683
+ type: "grid",
684
+ name: "mine",
685
+ label: "Mine",
686
+ data: { provider: "object", object: "sys_notification" },
687
+ columns: ["type", "title", "is_read", "created_at"],
688
+ filter: [{ field: "recipient_id", operator: "equals", value: "{current_user_id}" }],
689
+ sort: [{ field: "created_at", order: "desc" }],
690
+ pagination: { pageSize: 50 }
691
+ },
692
+ all_notifications: {
693
+ type: "grid",
694
+ name: "all_notifications",
695
+ label: "All",
696
+ data: { provider: "object", object: "sys_notification" },
697
+ columns: ["type", "title", "recipient_id", "is_read", "created_at"],
698
+ sort: [{ field: "created_at", order: "desc" }],
699
+ pagination: { pageSize: 100 }
700
+ }
701
+ },
702
+ fields: {
703
+ id: Field.text({
704
+ label: "Notification ID",
705
+ required: true,
706
+ readonly: true,
707
+ group: "System"
708
+ }),
709
+ // ── Routing ──────────────────────────────────────────────────
710
+ recipient_id: Field.lookup("sys_user", {
711
+ label: "Recipient",
712
+ required: true,
713
+ searchable: true,
714
+ description: "User the notification is delivered to",
715
+ group: "Routing"
716
+ }),
717
+ // ── Content ──────────────────────────────────────────────────
718
+ type: Field.select(
719
+ ["mention", "assignment", "comment_reply", "lead_converted", "task_due", "system"],
720
+ {
721
+ label: "Type",
722
+ required: true,
723
+ defaultValue: "system",
724
+ description: "Notification category \u2014 drives icon + sort priority",
725
+ group: "Content"
726
+ }
727
+ ),
728
+ title: Field.text({
729
+ label: "Title",
730
+ required: true,
731
+ maxLength: 255,
732
+ searchable: true,
733
+ group: "Content"
734
+ }),
735
+ body: Field.textarea({
736
+ label: "Body",
737
+ required: false,
738
+ description: "Optional secondary text (one-line summary)",
739
+ group: "Content"
740
+ }),
741
+ // ── Source linkage ───────────────────────────────────────────
742
+ source_object: Field.text({
743
+ label: "Source Object",
744
+ required: false,
745
+ maxLength: 100,
746
+ description: "Object name of the related record (e.g. lead, opportunity)",
747
+ group: "Source"
748
+ }),
749
+ source_id: Field.text({
750
+ label: "Source Record",
751
+ required: false,
752
+ maxLength: 100,
753
+ description: "Record id within source_object",
754
+ group: "Source"
755
+ }),
756
+ url: Field.url({
757
+ label: "Deep Link",
758
+ required: false,
759
+ description: "Optional URL to navigate to when clicked",
760
+ group: "Source"
761
+ }),
762
+ actor_id: Field.lookup("sys_user", {
763
+ label: "Actor",
764
+ required: false,
765
+ description: "User who caused the notification (mentioner, assigner)",
766
+ group: "Source"
767
+ }),
768
+ actor_name: Field.text({
769
+ label: "Actor Name",
770
+ required: false,
771
+ group: "Source"
772
+ }),
773
+ // ── Read state ───────────────────────────────────────────────
774
+ is_read: Field.boolean({
775
+ label: "Read",
776
+ defaultValue: false,
777
+ description: "True once recipient acknowledges",
778
+ group: "State"
779
+ }),
780
+ read_at: Field.datetime({
781
+ label: "Read At",
782
+ required: false,
783
+ group: "State"
784
+ }),
785
+ // ── Lifecycle ────────────────────────────────────────────────
786
+ created_at: Field.datetime({
787
+ label: "Created At",
788
+ required: true,
789
+ defaultValue: "NOW()",
790
+ readonly: true,
791
+ group: "System"
792
+ }),
793
+ updated_at: Field.datetime({
794
+ label: "Updated At",
795
+ required: false,
796
+ group: "System"
797
+ })
798
+ },
799
+ indexes: [
800
+ { fields: ["recipient_id", "is_read", "created_at"] },
801
+ { fields: ["recipient_id", "created_at"] },
802
+ { fields: ["source_object", "source_id"] }
803
+ ]
804
+ });
805
+ var SysEmail = ObjectSchema.create({
806
+ name: "sys_email",
807
+ label: "Email",
808
+ pluralLabel: "Emails",
809
+ icon: "mail",
810
+ isSystem: true,
811
+ managedBy: "append-only",
812
+ description: "Outbound email delivery log",
813
+ displayNameField: "subject",
814
+ titleFormat: "{subject}",
815
+ compactLayout: ["subject", "to", "status", "sent_at"],
816
+ fields: {
817
+ id: Field.text({
818
+ label: "Email ID",
819
+ required: true,
820
+ readonly: true,
821
+ group: "System"
822
+ }),
823
+ // ── Envelope ─────────────────────────────────────────────────
824
+ message_id: Field.text({
825
+ label: "Message-ID",
826
+ required: false,
827
+ maxLength: 255,
828
+ description: "RFC-5322 Message-ID assigned by the transport",
829
+ group: "Envelope"
830
+ }),
831
+ from_address: Field.text({
832
+ label: "From",
833
+ required: true,
834
+ maxLength: 320,
835
+ searchable: true,
836
+ group: "Envelope"
837
+ }),
838
+ to_addresses: Field.text({
839
+ label: "To",
840
+ required: true,
841
+ maxLength: 4e3,
842
+ searchable: true,
843
+ description: "Comma-separated recipient addresses",
844
+ group: "Envelope"
845
+ }),
846
+ cc_addresses: Field.text({
847
+ label: "Cc",
848
+ required: false,
849
+ maxLength: 4e3,
850
+ group: "Envelope"
851
+ }),
852
+ bcc_addresses: Field.text({
853
+ label: "Bcc",
854
+ required: false,
855
+ maxLength: 4e3,
856
+ group: "Envelope"
857
+ }),
858
+ reply_to: Field.text({
859
+ label: "Reply-To",
860
+ required: false,
861
+ maxLength: 320,
862
+ group: "Envelope"
863
+ }),
864
+ // ── Content ──────────────────────────────────────────────────
865
+ subject: Field.text({
866
+ label: "Subject",
867
+ required: true,
868
+ maxLength: 998,
869
+ searchable: true,
870
+ group: "Content"
871
+ }),
872
+ body_text: Field.textarea({
873
+ label: "Body (text)",
874
+ required: false,
875
+ searchable: true,
876
+ group: "Content"
877
+ }),
878
+ body_html: Field.textarea({
879
+ label: "Body (HTML)",
880
+ required: false,
881
+ group: "Content"
882
+ }),
883
+ // ── Delivery state ───────────────────────────────────────────
884
+ status: Field.select(
885
+ ["queued", "sent", "failed"],
886
+ {
887
+ label: "Status",
888
+ required: true,
889
+ defaultValue: "queued",
890
+ description: "Lifecycle state \u2014 queued by IEmailService.send before transport call",
891
+ group: "State"
892
+ }
893
+ ),
894
+ error: Field.textarea({
895
+ label: "Error",
896
+ required: false,
897
+ description: "Transport error message when status=failed",
898
+ group: "State"
899
+ }),
900
+ attempt_count: Field.number({
901
+ label: "Attempts",
902
+ required: false,
903
+ defaultValue: 0,
904
+ description: "Number of delivery attempts performed by the service",
905
+ group: "State"
906
+ }),
907
+ sent_at: Field.datetime({
908
+ label: "Sent At",
909
+ required: false,
910
+ description: 'Set when status transitions to "sent"',
911
+ group: "State"
912
+ }),
913
+ // ── Source linkage ───────────────────────────────────────────
914
+ related_object: Field.text({
915
+ label: "Related Object",
916
+ required: false,
917
+ maxLength: 100,
918
+ description: "Object name of the related record (e.g. lead, opportunity)",
919
+ group: "Source"
920
+ }),
921
+ related_id: Field.text({
922
+ label: "Related Record",
923
+ required: false,
924
+ maxLength: 100,
925
+ description: "Record id within related_object",
926
+ group: "Source"
927
+ }),
928
+ sent_by: Field.lookup("sys_user", {
929
+ label: "Sent By",
930
+ required: false,
931
+ group: "Source"
932
+ }),
933
+ // ── Lifecycle ────────────────────────────────────────────────
934
+ created_at: Field.datetime({
935
+ label: "Created At",
936
+ required: true,
937
+ defaultValue: "NOW()",
938
+ readonly: true,
939
+ group: "System"
940
+ }),
941
+ updated_at: Field.datetime({
942
+ label: "Updated At",
943
+ required: false,
944
+ group: "System"
945
+ })
946
+ },
947
+ indexes: [
948
+ { fields: ["status", "created_at"] },
949
+ { fields: ["related_object", "related_id"] },
950
+ { fields: ["sent_by"] }
951
+ ]
952
+ });
953
+ var SysEmailTemplate = ObjectSchema.create({
954
+ name: "sys_email_template",
955
+ label: "Email Template",
956
+ pluralLabel: "Email Templates",
957
+ icon: "mail",
958
+ isSystem: true,
959
+ managedBy: "config",
960
+ description: "Outbound email template (subject + body + variables) resolved by name+locale",
961
+ displayNameField: "label",
962
+ titleFormat: "{label}",
963
+ compactLayout: ["name", "label", "category", "locale", "active"],
964
+ fields: {
965
+ id: Field.text({
966
+ label: "ID",
967
+ required: true,
968
+ readonly: true,
969
+ group: "System"
970
+ }),
971
+ // ── Identity ─────────────────────────────────────────────────
972
+ name: Field.text({
973
+ label: "Name",
974
+ required: true,
975
+ maxLength: 128,
976
+ description: "Dotted snake_case identifier (e.g. auth.password_reset)",
977
+ group: "Identity"
978
+ }),
979
+ label: Field.text({
980
+ label: "Label",
981
+ required: true,
982
+ maxLength: 200,
983
+ description: "Human-readable name shown in Studio",
984
+ group: "Identity"
985
+ }),
986
+ category: Field.select(
987
+ ["auth", "notification", "workflow", "marketing", "custom"],
988
+ {
989
+ label: "Category",
990
+ required: true,
991
+ defaultValue: "custom",
992
+ group: "Identity"
993
+ }
994
+ ),
995
+ locale: Field.text({
996
+ label: "Locale",
997
+ required: true,
998
+ defaultValue: "en-US",
999
+ maxLength: 16,
1000
+ description: "BCP-47 locale tag (en-US, zh-CN, \u2026)",
1001
+ group: "Identity"
1002
+ }),
1003
+ description: Field.textarea({
1004
+ label: "Description",
1005
+ required: false,
1006
+ group: "Identity"
1007
+ }),
1008
+ // ── Content ──────────────────────────────────────────────────
1009
+ subject: Field.text({
1010
+ label: "Subject",
1011
+ required: true,
1012
+ maxLength: 500,
1013
+ description: "Subject template; supports {{var.path}} placeholders",
1014
+ group: "Content"
1015
+ }),
1016
+ body_html: Field.textarea({
1017
+ label: "HTML Body",
1018
+ required: true,
1019
+ description: "HTML body template; supports {{var.path}} placeholders",
1020
+ group: "Content"
1021
+ }),
1022
+ body_text: Field.textarea({
1023
+ label: "Plain Text Body",
1024
+ required: false,
1025
+ description: "Optional plain-text alternative (auto-derived from HTML when blank)",
1026
+ group: "Content"
1027
+ }),
1028
+ // ── Envelope overrides ───────────────────────────────────────
1029
+ from_name: Field.text({
1030
+ label: "From Name",
1031
+ required: false,
1032
+ maxLength: 200,
1033
+ group: "Envelope"
1034
+ }),
1035
+ from_address: Field.text({
1036
+ label: "From Address",
1037
+ required: false,
1038
+ maxLength: 255,
1039
+ group: "Envelope"
1040
+ }),
1041
+ reply_to: Field.text({
1042
+ label: "Reply-To",
1043
+ required: false,
1044
+ maxLength: 255,
1045
+ group: "Envelope"
1046
+ }),
1047
+ // ── Lifecycle ────────────────────────────────────────────────
1048
+ active: Field.boolean({
1049
+ label: "Active",
1050
+ required: true,
1051
+ defaultValue: true,
1052
+ group: "Lifecycle"
1053
+ }),
1054
+ is_system: Field.boolean({
1055
+ label: "System Template",
1056
+ required: false,
1057
+ defaultValue: false,
1058
+ readonly: true,
1059
+ description: "Provided by a plugin / platform; tenants may edit but should not delete",
1060
+ group: "Lifecycle"
1061
+ }),
1062
+ /**
1063
+ * Variables declared by the template author. Stored as JSON text
1064
+ * (array of {name,type,required,description}). Used by the Studio
1065
+ * authoring UI to render form hints and by sendTemplate() to
1066
+ * validate required vars at runtime.
1067
+ */
1068
+ variables_json: Field.textarea({
1069
+ label: "Variables (JSON)",
1070
+ required: false,
1071
+ description: "JSON array of {name,type,required,description}",
1072
+ group: "Lifecycle"
1073
+ }),
1074
+ created_at: Field.datetime({
1075
+ label: "Created At",
1076
+ required: true,
1077
+ defaultValue: "NOW()",
1078
+ readonly: true,
1079
+ group: "System"
1080
+ }),
1081
+ updated_at: Field.datetime({ label: "Updated At", required: false, group: "System" })
1082
+ },
1083
+ indexes: [
1084
+ { fields: ["name", "locale"], unique: true },
1085
+ { fields: ["category"] },
1086
+ { fields: ["active"] }
1087
+ ]
1088
+ });
1089
+ var SysSavedReport = ObjectSchema.create({
1090
+ name: "sys_saved_report",
1091
+ label: "Saved Report",
1092
+ pluralLabel: "Saved Reports",
1093
+ icon: "bar-chart",
1094
+ isSystem: true,
1095
+ managedBy: "platform",
1096
+ description: "Persisted ObjectQL report definition \u2014 re-runnable and schedulable",
1097
+ displayNameField: "name",
1098
+ titleFormat: "{name}",
1099
+ compactLayout: ["name", "object_name", "format", "owner_id", "updated_at"],
1100
+ fields: {
1101
+ id: Field.text({
1102
+ label: "Report ID",
1103
+ required: true,
1104
+ readonly: true,
1105
+ group: "System"
1106
+ }),
1107
+ name: Field.text({
1108
+ label: "Name",
1109
+ required: true,
1110
+ maxLength: 200,
1111
+ searchable: true,
1112
+ group: "Definition"
1113
+ }),
1114
+ description: Field.textarea({
1115
+ label: "Description",
1116
+ required: false,
1117
+ group: "Definition"
1118
+ }),
1119
+ object_name: Field.text({
1120
+ label: "Object",
1121
+ required: true,
1122
+ maxLength: 100,
1123
+ description: "Short object name the report queries",
1124
+ group: "Definition"
1125
+ }),
1126
+ query_json: Field.textarea({
1127
+ label: "Query",
1128
+ required: true,
1129
+ description: "ObjectQL query envelope \u2014 { filter, fields, orderBy, limit, groupBy }",
1130
+ group: "Definition"
1131
+ }),
1132
+ format: Field.select(
1133
+ ["csv", "json", "html_table"],
1134
+ {
1135
+ label: "Format",
1136
+ required: true,
1137
+ defaultValue: "csv",
1138
+ description: "Rendering used by IReportService.run() and email digests",
1139
+ group: "Definition"
1140
+ }
1141
+ ),
1142
+ owner_id: Field.lookup("sys_user", {
1143
+ label: "Owner",
1144
+ required: false,
1145
+ description: "User that owns the report definition (drives sharing)",
1146
+ group: "Provenance"
1147
+ }),
1148
+ last_run_at: Field.datetime({
1149
+ label: "Last Run",
1150
+ required: false,
1151
+ description: "Stamped by IReportService.run() on successful execution",
1152
+ group: "State"
1153
+ }),
1154
+ last_row_count: Field.number({
1155
+ label: "Last Row Count",
1156
+ required: false,
1157
+ group: "State"
1158
+ }),
1159
+ created_at: Field.datetime({
1160
+ label: "Created At",
1161
+ required: true,
1162
+ defaultValue: "NOW()",
1163
+ readonly: true,
1164
+ group: "System"
1165
+ }),
1166
+ updated_at: Field.datetime({
1167
+ label: "Updated At",
1168
+ required: false,
1169
+ group: "System"
1170
+ })
1171
+ },
1172
+ indexes: [
1173
+ { fields: ["object_name"] },
1174
+ { fields: ["owner_id"] },
1175
+ { fields: ["name"] }
1176
+ ]
1177
+ });
1178
+ var SysReportSchedule = ObjectSchema.create({
1179
+ name: "sys_report_schedule",
1180
+ label: "Report Schedule",
1181
+ pluralLabel: "Report Schedules",
1182
+ icon: "clock",
1183
+ isSystem: true,
1184
+ managedBy: "platform",
1185
+ description: "Recurring delivery of a sys_saved_report via email",
1186
+ titleFormat: "{report_id} \u2192 {recipients}",
1187
+ compactLayout: ["report_id", "recipients", "interval_minutes", "active", "next_run_at"],
1188
+ fields: {
1189
+ id: Field.text({
1190
+ label: "Schedule ID",
1191
+ required: true,
1192
+ readonly: true,
1193
+ group: "System"
1194
+ }),
1195
+ report_id: Field.lookup("sys_saved_report", {
1196
+ label: "Report",
1197
+ required: true,
1198
+ group: "Schedule"
1199
+ }),
1200
+ name: Field.text({
1201
+ label: "Name",
1202
+ required: false,
1203
+ maxLength: 200,
1204
+ description: "Optional label for the digest \u2014 used in the email subject",
1205
+ group: "Schedule"
1206
+ }),
1207
+ interval_minutes: Field.number({
1208
+ label: "Interval (minutes)",
1209
+ required: false,
1210
+ defaultValue: 1440,
1211
+ description: "How often to send (1440 = daily, 10080 = weekly)",
1212
+ group: "Schedule"
1213
+ }),
1214
+ cron_expression: Field.text({
1215
+ label: "Cron Expression",
1216
+ required: false,
1217
+ maxLength: 100,
1218
+ description: "Optional 5/6-field cron \u2014 overrides interval_minutes when present",
1219
+ group: "Schedule"
1220
+ }),
1221
+ timezone: Field.text({
1222
+ label: "Timezone",
1223
+ required: false,
1224
+ maxLength: 64,
1225
+ defaultValue: "UTC",
1226
+ group: "Schedule"
1227
+ }),
1228
+ active: Field.boolean({
1229
+ label: "Active",
1230
+ required: true,
1231
+ defaultValue: true,
1232
+ group: "Schedule"
1233
+ }),
1234
+ recipients: Field.text({
1235
+ label: "Recipients",
1236
+ required: true,
1237
+ maxLength: 4e3,
1238
+ description: "Comma-separated email addresses",
1239
+ group: "Delivery"
1240
+ }),
1241
+ format: Field.select(
1242
+ ["csv", "html_table"],
1243
+ {
1244
+ label: "Format",
1245
+ required: false,
1246
+ defaultValue: "html_table",
1247
+ description: "Render format \u2014 csv is attached, html_table is inlined",
1248
+ group: "Delivery"
1249
+ }
1250
+ ),
1251
+ subject_template: Field.text({
1252
+ label: "Subject Template",
1253
+ required: false,
1254
+ maxLength: 200,
1255
+ description: "Email subject; {{name}} / {{date}} / {{rows}} are substituted",
1256
+ group: "Delivery"
1257
+ }),
1258
+ owner_id: Field.lookup("sys_user", {
1259
+ label: "Owner",
1260
+ required: false,
1261
+ group: "Provenance"
1262
+ }),
1263
+ next_run_at: Field.datetime({
1264
+ label: "Next Run",
1265
+ required: false,
1266
+ description: "Dispatcher loads schedules where next_run_at <= now",
1267
+ group: "State"
1268
+ }),
1269
+ last_sent_at: Field.datetime({
1270
+ label: "Last Sent",
1271
+ required: false,
1272
+ group: "State"
1273
+ }),
1274
+ last_status: Field.select(
1275
+ ["ok", "failed", "skipped"],
1276
+ {
1277
+ label: "Last Status",
1278
+ required: false,
1279
+ group: "State"
1280
+ }
1281
+ ),
1282
+ last_error: Field.textarea({
1283
+ label: "Last Error",
1284
+ required: false,
1285
+ group: "State"
1286
+ }),
1287
+ created_at: Field.datetime({
1288
+ label: "Created At",
1289
+ required: true,
1290
+ defaultValue: "NOW()",
1291
+ readonly: true,
1292
+ group: "System"
1293
+ }),
1294
+ updated_at: Field.datetime({
1295
+ label: "Updated At",
1296
+ required: false,
1297
+ group: "System"
1298
+ })
1299
+ },
1300
+ indexes: [
1301
+ // Hot path for the dispatch loop.
1302
+ { fields: ["active", "next_run_at"] },
1303
+ { fields: ["report_id"] },
1304
+ { fields: ["owner_id"] }
1305
+ ]
1306
+ });
1307
+ var SysApprovalProcess = ObjectSchema.create({
1308
+ name: "sys_approval_process",
1309
+ label: "Approval Process",
1310
+ pluralLabel: "Approval Processes",
1311
+ icon: "check-square",
1312
+ isSystem: true,
1313
+ managedBy: "config",
1314
+ // Authoring an approval process requires a visual step designer that
1315
+ // doesn't yet exist — the embedded `definition_json` textarea would
1316
+ // force admins to hand-write a multi-page ApprovalProcess envelope.
1317
+ // Suppress generic CRUD until the designer lands. Real authoring path:
1318
+ // call `defineApprovalProcess({...})` in code and seed via the
1319
+ // approvals service (`POST /api/v1/approvals/processes`) or commit the
1320
+ // definition as a fixture. Editing existing rows (e.g. toggling
1321
+ // `active`) is also suppressed for now because the same textarea would
1322
+ // appear; use the service API or a future designer instead.
1323
+ userActions: { create: false, edit: false, delete: false, import: false },
1324
+ description: "Persisted approval process definition. Authored via defineApprovalProcess() in code; visual designer is on the roadmap.",
1325
+ displayNameField: "name",
1326
+ titleFormat: "{label}",
1327
+ compactLayout: ["name", "object_name", "active", "updated_at"],
1328
+ listViews: {
1329
+ active: {
1330
+ type: "grid",
1331
+ name: "active",
1332
+ label: "Active",
1333
+ data: { provider: "object", object: "sys_approval_process" },
1334
+ columns: ["label", "object_name", "active", "updated_at"],
1335
+ filter: [{ field: "active", operator: "equals", value: true }],
1336
+ sort: [{ field: "label", order: "asc" }],
1337
+ pagination: { pageSize: 50 }
1338
+ },
1339
+ inactive: {
1340
+ type: "grid",
1341
+ name: "inactive",
1342
+ label: "Inactive",
1343
+ data: { provider: "object", object: "sys_approval_process" },
1344
+ columns: ["label", "object_name", "active", "updated_at"],
1345
+ filter: [{ field: "active", operator: "equals", value: false }],
1346
+ sort: [{ field: "label", order: "asc" }],
1347
+ pagination: { pageSize: 50 }
1348
+ },
1349
+ by_object: {
1350
+ type: "grid",
1351
+ name: "by_object",
1352
+ label: "By Object",
1353
+ data: { provider: "object", object: "sys_approval_process" },
1354
+ columns: ["object_name", "label", "active", "updated_at"],
1355
+ sort: [{ field: "object_name", order: "asc" }, { field: "label", order: "asc" }],
1356
+ grouping: { fields: [{ field: "object_name", order: "asc", collapsed: false }] },
1357
+ pagination: { pageSize: 100 }
1358
+ },
1359
+ all_processes: {
1360
+ type: "grid",
1361
+ name: "all_processes",
1362
+ label: "All",
1363
+ data: { provider: "object", object: "sys_approval_process" },
1364
+ columns: ["label", "object_name", "active", "updated_at"],
1365
+ sort: [{ field: "label", order: "asc" }],
1366
+ pagination: { pageSize: 50 }
1367
+ }
1368
+ },
1369
+ fields: {
1370
+ id: Field.text({ label: "Process ID", required: true, readonly: true, group: "System" }),
1371
+ name: Field.text({
1372
+ label: "Name",
1373
+ required: true,
1374
+ maxLength: 100,
1375
+ description: "Unique snake_case name \u2014 referenced by submitters and audit rows",
1376
+ group: "Definition"
1377
+ }),
1378
+ label: Field.text({
1379
+ label: "Display Label",
1380
+ required: true,
1381
+ maxLength: 200,
1382
+ group: "Definition"
1383
+ }),
1384
+ object_name: Field.text({
1385
+ label: "Object",
1386
+ required: true,
1387
+ maxLength: 100,
1388
+ description: "Short object name this process governs",
1389
+ group: "Definition"
1390
+ }),
1391
+ description: Field.textarea({ label: "Description", required: false, group: "Definition" }),
1392
+ active: Field.boolean({
1393
+ label: "Active",
1394
+ required: true,
1395
+ defaultValue: false,
1396
+ description: "Only active processes are dispatched on submission",
1397
+ group: "Definition"
1398
+ }),
1399
+ definition_json: Field.textarea({
1400
+ label: "Definition",
1401
+ required: true,
1402
+ description: "Serialised ApprovalProcess JSON (see @objectstack/spec/automation/approval)",
1403
+ group: "Definition"
1404
+ }),
1405
+ created_at: Field.datetime({
1406
+ label: "Created At",
1407
+ required: true,
1408
+ defaultValue: "NOW()",
1409
+ readonly: true,
1410
+ group: "System"
1411
+ }),
1412
+ updated_at: Field.datetime({ label: "Updated At", required: false, group: "System" })
1413
+ },
1414
+ indexes: [
1415
+ { fields: ["name"], unique: true },
1416
+ { fields: ["object_name"] },
1417
+ { fields: ["active", "object_name"] }
1418
+ ]
1419
+ });
1420
+ var SysApprovalRequest = ObjectSchema.create({
1421
+ name: "sys_approval_request",
1422
+ label: "Approval Request",
1423
+ pluralLabel: "Approval Requests",
1424
+ icon: "inbox",
1425
+ isSystem: true,
1426
+ managedBy: "system",
1427
+ description: "Live approval instance tracked per submission",
1428
+ displayNameField: "id",
1429
+ titleFormat: "{process_name} \xB7 {record_id}",
1430
+ compactLayout: ["process_name", "object_name", "record_id", "status", "current_step", "submitter_id", "updated_at"],
1431
+ // Curated built-in list views — render as segmented tabs in the console.
1432
+ // Filters use {current_user_id} substitution wired by the console.
1433
+ listViews: {
1434
+ my_pending: {
1435
+ type: "grid",
1436
+ name: "my_pending",
1437
+ label: "My Pending",
1438
+ data: { provider: "object", object: "sys_approval_request" },
1439
+ columns: ["process_name", "object_name", "record_id", "current_step", "submitter_id", "updated_at"],
1440
+ filter: [
1441
+ { field: "status", operator: "equals", value: "pending" },
1442
+ { field: "pending_approvers", operator: "contains", value: "{current_user_id}" }
1443
+ ],
1444
+ sort: [{ field: "updated_at", order: "desc" }],
1445
+ pagination: { pageSize: 25 },
1446
+ emptyState: { title: "No pending approvals", message: "You're all caught up." }
1447
+ },
1448
+ submitted_by_me: {
1449
+ type: "grid",
1450
+ name: "submitted_by_me",
1451
+ label: "I Submitted",
1452
+ data: { provider: "object", object: "sys_approval_request" },
1453
+ columns: ["process_name", "object_name", "record_id", "status", "current_step", "updated_at"],
1454
+ filter: [{ field: "submitter_id", operator: "equals", value: "{current_user_id}" }],
1455
+ sort: [{ field: "updated_at", order: "desc" }],
1456
+ pagination: { pageSize: 25 }
1457
+ },
1458
+ completed: {
1459
+ type: "grid",
1460
+ name: "completed",
1461
+ label: "Completed",
1462
+ data: { provider: "object", object: "sys_approval_request" },
1463
+ columns: ["process_name", "object_name", "record_id", "status", "submitter_id", "completed_at"],
1464
+ filter: [{ field: "status", operator: "in", value: ["approved", "rejected", "recalled"] }],
1465
+ sort: [{ field: "completed_at", order: "desc" }],
1466
+ pagination: { pageSize: 25 }
1467
+ },
1468
+ all_requests: {
1469
+ type: "grid",
1470
+ name: "all_requests",
1471
+ label: "All",
1472
+ data: { provider: "object", object: "sys_approval_request" },
1473
+ columns: ["process_name", "object_name", "record_id", "status", "current_step", "submitter_id", "updated_at"],
1474
+ sort: [{ field: "updated_at", order: "desc" }],
1475
+ pagination: { pageSize: 50 }
1476
+ }
1477
+ },
1478
+ fields: {
1479
+ id: Field.text({ label: "Request ID", required: true, readonly: true, group: "System" }),
1480
+ organization_id: Field.lookup("sys_organization", {
1481
+ label: "Organization",
1482
+ required: false,
1483
+ group: "System",
1484
+ description: "Tenant that owns this approval request (propagated from submitter context)"
1485
+ }),
1486
+ process_name: Field.text({
1487
+ label: "Process",
1488
+ required: true,
1489
+ maxLength: 100,
1490
+ description: "sys_approval_process.name this request was opened against",
1491
+ group: "Target"
1492
+ }),
1493
+ object_name: Field.text({
1494
+ label: "Object",
1495
+ required: true,
1496
+ maxLength: 100,
1497
+ group: "Target"
1498
+ }),
1499
+ record_id: Field.text({
1500
+ label: "Record ID",
1501
+ required: true,
1502
+ maxLength: 100,
1503
+ group: "Target"
1504
+ }),
1505
+ submitter_id: Field.lookup("sys_user", {
1506
+ label: "Submitter",
1507
+ required: false,
1508
+ group: "Target"
1509
+ }),
1510
+ submitter_comment: Field.textarea({
1511
+ label: "Submitter Comment",
1512
+ required: false,
1513
+ group: "Target"
1514
+ }),
1515
+ status: Field.select(
1516
+ ["pending", "approved", "rejected", "recalled"],
1517
+ {
1518
+ label: "Status",
1519
+ required: true,
1520
+ defaultValue: "pending",
1521
+ description: "Lifecycle state of the request",
1522
+ group: "State"
1523
+ }
1524
+ ),
1525
+ current_step: Field.text({
1526
+ label: "Current Step",
1527
+ required: false,
1528
+ maxLength: 100,
1529
+ description: "Machine name of the step awaiting approval",
1530
+ group: "State"
1531
+ }),
1532
+ current_step_index: Field.number({
1533
+ label: "Current Step Index",
1534
+ required: false,
1535
+ defaultValue: 0,
1536
+ group: "State"
1537
+ }),
1538
+ pending_approvers: Field.textarea({
1539
+ label: "Pending Approvers",
1540
+ required: false,
1541
+ description: "Comma-separated user ids who can act on the current step",
1542
+ group: "State"
1543
+ }),
1544
+ payload_json: Field.textarea({
1545
+ label: "Snapshot",
1546
+ required: false,
1547
+ description: "Record snapshot at submission time",
1548
+ group: "State"
1549
+ }),
1550
+ completed_at: Field.datetime({
1551
+ label: "Completed At",
1552
+ required: false,
1553
+ group: "State"
1554
+ }),
1555
+ created_at: Field.datetime({
1556
+ label: "Created At",
1557
+ required: true,
1558
+ defaultValue: "NOW()",
1559
+ readonly: true,
1560
+ group: "System"
1561
+ }),
1562
+ updated_at: Field.datetime({ label: "Updated At", required: false, group: "System" })
1563
+ },
1564
+ indexes: [
1565
+ // Look up "is there a pending request for this record?" — common
1566
+ // guard on submit and on edit-while-locked checks.
1567
+ { fields: ["object_name", "record_id"] },
1568
+ { fields: ["status", "object_name"] },
1569
+ // "My approvals" inbox — pending_approvers is a CSV string so this
1570
+ // index only helps with status pre-filtering; the engine does a
1571
+ // post-filter substring match per row.
1572
+ { fields: ["status", "updated_at"] },
1573
+ { fields: ["submitter_id", "status"] }
1574
+ ]
1575
+ });
1576
+ var SysApprovalAction = ObjectSchema.create({
1577
+ name: "sys_approval_action",
1578
+ label: "Approval Action",
1579
+ pluralLabel: "Approval Actions",
1580
+ icon: "check-circle",
1581
+ isSystem: true,
1582
+ managedBy: "append-only",
1583
+ description: "Append-only audit trail for approval actions",
1584
+ displayNameField: "id",
1585
+ titleFormat: "{action} \xB7 {step_name}",
1586
+ compactLayout: ["request_id", "step_name", "action", "actor_id", "created_at"],
1587
+ listViews: {
1588
+ recent: {
1589
+ type: "grid",
1590
+ name: "recent",
1591
+ label: "Recent",
1592
+ data: { provider: "object", object: "sys_approval_action" },
1593
+ columns: ["created_at", "request_id", "step_name", "action", "actor_id", "comment"],
1594
+ sort: [{ field: "created_at", order: "desc" }],
1595
+ pagination: { pageSize: 50 },
1596
+ emptyState: { title: "No approval actions yet", message: "Actions are logged automatically when approvals progress." }
1597
+ },
1598
+ by_actor: {
1599
+ type: "grid",
1600
+ name: "by_actor",
1601
+ label: "By Actor",
1602
+ data: { provider: "object", object: "sys_approval_action" },
1603
+ columns: ["actor_id", "created_at", "request_id", "step_name", "action"],
1604
+ sort: [{ field: "actor_id", order: "asc" }, { field: "created_at", order: "desc" }],
1605
+ grouping: { fields: [{ field: "actor_id", order: "asc", collapsed: false }] },
1606
+ pagination: { pageSize: 100 }
1607
+ },
1608
+ all_actions: {
1609
+ type: "grid",
1610
+ name: "all_actions",
1611
+ label: "All",
1612
+ data: { provider: "object", object: "sys_approval_action" },
1613
+ columns: ["created_at", "request_id", "step_name", "action", "actor_id", "comment"],
1614
+ sort: [{ field: "created_at", order: "desc" }],
1615
+ pagination: { pageSize: 100 }
1616
+ }
1617
+ },
1618
+ fields: {
1619
+ id: Field.text({ label: "Action ID", required: true, readonly: true, group: "System" }),
1620
+ organization_id: Field.lookup("sys_organization", {
1621
+ label: "Organization",
1622
+ required: false,
1623
+ group: "System",
1624
+ description: "Tenant that owns this action (mirrors the parent request)"
1625
+ }),
1626
+ request_id: Field.lookup("sys_approval_request", {
1627
+ label: "Request",
1628
+ required: true,
1629
+ group: "Target"
1630
+ }),
1631
+ step_name: Field.text({
1632
+ label: "Step",
1633
+ required: false,
1634
+ maxLength: 100,
1635
+ description: "Machine name of the step at the time of the action",
1636
+ group: "Target"
1637
+ }),
1638
+ step_index: Field.number({
1639
+ label: "Step Index",
1640
+ required: false,
1641
+ group: "Target"
1642
+ }),
1643
+ action: Field.select(
1644
+ ["submit", "approve", "reject", "recall", "escalate"],
1645
+ {
1646
+ label: "Action",
1647
+ required: true,
1648
+ group: "Action"
1649
+ }
1650
+ ),
1651
+ actor_id: Field.lookup("sys_user", {
1652
+ label: "Actor",
1653
+ required: false,
1654
+ group: "Action"
1655
+ }),
1656
+ comment: Field.textarea({ label: "Comment", required: false, group: "Action" }),
1657
+ created_at: Field.datetime({
1658
+ label: "Created At",
1659
+ required: true,
1660
+ defaultValue: "NOW()",
1661
+ readonly: true,
1662
+ group: "System"
1663
+ })
1664
+ },
1665
+ indexes: [
1666
+ { fields: ["request_id", "created_at"] },
1667
+ { fields: ["request_id", "step_index", "action"] }
1668
+ ]
1669
+ });
1670
+ var SysJob = ObjectSchema.create({
1671
+ name: "sys_job",
1672
+ label: "Background Job",
1673
+ pluralLabel: "Background Jobs",
1674
+ icon: "clock",
1675
+ isSystem: true,
1676
+ managedBy: "system",
1677
+ description: "Catalogue of registered background jobs",
1678
+ displayNameField: "name",
1679
+ titleFormat: "{name}",
1680
+ compactLayout: ["name", "schedule_type", "active", "last_run_at", "last_status"],
1681
+ fields: {
1682
+ id: Field.text({ label: "Job ID", required: true, readonly: true, group: "System" }),
1683
+ name: Field.text({
1684
+ label: "Job Name",
1685
+ required: true,
1686
+ maxLength: 255,
1687
+ searchable: true,
1688
+ description: "Unique job identifier (snake_case)",
1689
+ group: "Identity"
1690
+ }),
1691
+ schedule_type: Field.select(["cron", "interval", "once"], {
1692
+ label: "Schedule Type",
1693
+ required: true,
1694
+ group: "Schedule"
1695
+ }),
1696
+ schedule_expression: Field.text({
1697
+ label: "Expression",
1698
+ required: false,
1699
+ maxLength: 200,
1700
+ description: "Cron expression / interval ms / ISO datetime",
1701
+ group: "Schedule"
1702
+ }),
1703
+ timezone: Field.text({
1704
+ label: "Timezone",
1705
+ required: false,
1706
+ maxLength: 100,
1707
+ group: "Schedule"
1708
+ }),
1709
+ active: Field.boolean({
1710
+ label: "Active",
1711
+ required: true,
1712
+ defaultValue: true,
1713
+ description: "Whether the scheduler is currently running this job",
1714
+ group: "State"
1715
+ }),
1716
+ last_run_at: Field.datetime({ label: "Last Run At", required: false, group: "State" }),
1717
+ last_status: Field.select(
1718
+ ["success", "failed", "timeout", "running"],
1719
+ { label: "Last Status", required: false, group: "State" }
1720
+ ),
1721
+ last_error: Field.textarea({ label: "Last Error", required: false, group: "State" }),
1722
+ run_count: Field.number({ label: "Run Count", required: false, defaultValue: 0, group: "State" }),
1723
+ failure_count: Field.number({ label: "Failure Count", required: false, defaultValue: 0, group: "State" }),
1724
+ created_at: Field.datetime({
1725
+ label: "Created At",
1726
+ required: true,
1727
+ defaultValue: "NOW()",
1728
+ readonly: true,
1729
+ group: "System"
1730
+ }),
1731
+ updated_at: Field.datetime({ label: "Updated At", required: false, group: "System" })
1732
+ },
1733
+ indexes: [
1734
+ { fields: ["name"], unique: true },
1735
+ { fields: ["active"] }
1736
+ ]
1737
+ });
1738
+ var SysJobRun = ObjectSchema.create({
1739
+ name: "sys_job_run",
1740
+ label: "Job Run",
1741
+ pluralLabel: "Job Runs",
1742
+ icon: "play",
1743
+ isSystem: true,
1744
+ managedBy: "append-only",
1745
+ description: "Background job execution audit trail",
1746
+ displayNameField: "job_name",
1747
+ titleFormat: "{job_name} @ {started_at}",
1748
+ compactLayout: ["job_name", "status", "started_at", "duration_ms", "attempt"],
1749
+ fields: {
1750
+ id: Field.text({ label: "Run ID", required: true, readonly: true, group: "System" }),
1751
+ job_name: Field.text({
1752
+ label: "Job",
1753
+ required: true,
1754
+ maxLength: 255,
1755
+ searchable: true,
1756
+ group: "Identity"
1757
+ }),
1758
+ status: Field.select(
1759
+ ["running", "success", "failed", "timeout"],
1760
+ { label: "Status", required: true, defaultValue: "running", group: "State" }
1761
+ ),
1762
+ started_at: Field.datetime({ label: "Started At", required: true, group: "State" }),
1763
+ completed_at: Field.datetime({ label: "Completed At", required: false, group: "State" }),
1764
+ duration_ms: Field.number({ label: "Duration (ms)", required: false, group: "State" }),
1765
+ attempt: Field.number({
1766
+ label: "Attempt",
1767
+ required: false,
1768
+ defaultValue: 1,
1769
+ description: "1 for first run, >1 for retries/replays",
1770
+ group: "State"
1771
+ }),
1772
+ trigger: Field.select(
1773
+ ["schedule", "manual", "replay"],
1774
+ { label: "Trigger", required: false, defaultValue: "schedule", group: "State" }
1775
+ ),
1776
+ error: Field.textarea({ label: "Error", required: false, group: "State" }),
1777
+ created_at: Field.datetime({
1778
+ label: "Created At",
1779
+ required: true,
1780
+ defaultValue: "NOW()",
1781
+ readonly: true,
1782
+ group: "System"
1783
+ })
1784
+ },
1785
+ indexes: [
1786
+ { fields: ["job_name", "started_at"] },
1787
+ { fields: ["status", "started_at"] }
1788
+ ]
1789
+ });
1790
+ var SysJobQueue = ObjectSchema.create({
1791
+ name: "sys_job_queue",
1792
+ label: "Job Queue Message",
1793
+ pluralLabel: "Job Queue Messages",
1794
+ icon: "inbox",
1795
+ isSystem: true,
1796
+ managedBy: "system",
1797
+ description: "Durable job/message queue including dead letters",
1798
+ displayNameField: "queue",
1799
+ titleFormat: "{queue} #{id}",
1800
+ compactLayout: ["queue", "status", "attempts", "scheduled_for", "last_error"],
1801
+ fields: {
1802
+ id: Field.text({ label: "Message ID", required: true, readonly: true, group: "System" }),
1803
+ queue: Field.text({
1804
+ label: "Queue",
1805
+ required: true,
1806
+ maxLength: 255,
1807
+ searchable: true,
1808
+ description: "Logical queue name (snake_case)",
1809
+ group: "Identity"
1810
+ }),
1811
+ idempotency_key: Field.text({
1812
+ label: "Idempotency Key",
1813
+ required: false,
1814
+ maxLength: 255,
1815
+ description: "Deduplication key within (queue, window)",
1816
+ group: "Identity"
1817
+ }),
1818
+ payload_json: Field.textarea({
1819
+ label: "Payload (JSON)",
1820
+ required: false,
1821
+ description: "Serialized message body",
1822
+ group: "Content"
1823
+ }),
1824
+ metadata_json: Field.textarea({
1825
+ label: "Metadata (JSON)",
1826
+ required: false,
1827
+ description: "Serialized metadata bag (tenant_id, source_record, ...)",
1828
+ group: "Content"
1829
+ }),
1830
+ status: Field.select(
1831
+ ["pending", "running", "completed", "failed", "dlq"],
1832
+ {
1833
+ label: "Status",
1834
+ required: true,
1835
+ defaultValue: "pending",
1836
+ description: "Lifecycle state",
1837
+ group: "State"
1838
+ }
1839
+ ),
1840
+ priority: Field.number({
1841
+ label: "Priority",
1842
+ required: false,
1843
+ defaultValue: 100,
1844
+ description: "Lower = higher priority",
1845
+ group: "Schedule"
1846
+ }),
1847
+ attempts: Field.number({ label: "Attempts", required: false, defaultValue: 0, group: "State" }),
1848
+ max_attempts: Field.number({ label: "Max Attempts", required: false, defaultValue: 3, group: "State" }),
1849
+ backoff_type: Field.select(
1850
+ ["fixed", "exponential"],
1851
+ { label: "Backoff", required: false, defaultValue: "exponential", group: "Schedule" }
1852
+ ),
1853
+ backoff_delay_ms: Field.number({
1854
+ label: "Backoff Base (ms)",
1855
+ required: false,
1856
+ defaultValue: 1e3,
1857
+ group: "Schedule"
1858
+ }),
1859
+ backoff_max_delay_ms: Field.number({
1860
+ label: "Backoff Cap (ms)",
1861
+ required: false,
1862
+ group: "Schedule"
1863
+ }),
1864
+ scheduled_for: Field.datetime({
1865
+ label: "Scheduled For",
1866
+ required: false,
1867
+ description: "Earliest time a worker may lease this message",
1868
+ group: "Schedule"
1869
+ }),
1870
+ locked_by: Field.text({
1871
+ label: "Locked By",
1872
+ required: false,
1873
+ maxLength: 255,
1874
+ description: "Worker id holding the lease",
1875
+ group: "Lease"
1876
+ }),
1877
+ locked_until: Field.datetime({
1878
+ label: "Locked Until",
1879
+ required: false,
1880
+ description: "Lease expiry; if past, another worker may claim",
1881
+ group: "Lease"
1882
+ }),
1883
+ last_error: Field.textarea({ label: "Last Error", required: false, group: "State" }),
1884
+ completed_at: Field.datetime({ label: "Completed At", required: false, group: "State" }),
1885
+ created_at: Field.datetime({
1886
+ label: "Created At",
1887
+ required: true,
1888
+ defaultValue: "NOW()",
1889
+ readonly: true,
1890
+ group: "System"
1891
+ }),
1892
+ updated_at: Field.datetime({ label: "Updated At", required: false, group: "System" })
1893
+ },
1894
+ indexes: [
1895
+ { fields: ["queue", "status", "scheduled_for"] },
1896
+ { fields: ["idempotency_key", "queue"] },
1897
+ { fields: ["status"] }
1898
+ ]
1899
+ });
484
1900
 
485
- export { SysActivity, SysAuditLog, SysComment, SysPresence };
1901
+ export { SysActivity, SysApprovalAction, SysApprovalProcess, SysApprovalRequest, SysAttachment, SysAuditLog, SysComment, SysEmail, SysEmailTemplate, SysJob, SysJobQueue, SysJobRun, SysNotification, SysPresence, SysReportSchedule, SysSavedReport };
486
1902
  //# sourceMappingURL=index.mjs.map
487
1903
  //# sourceMappingURL=index.mjs.map