@objectstack/platform-objects 4.0.5 → 4.1.0

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