@objectstack/platform-objects 7.2.1 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/apps/index.d.mts +45 -19
  2. package/dist/apps/index.d.ts +45 -19
  3. package/dist/apps/index.js +1443 -3403
  4. package/dist/apps/index.js.map +1 -1
  5. package/dist/apps/index.mjs +1443 -3404
  6. package/dist/apps/index.mjs.map +1 -1
  7. package/dist/audit/index.d.mts +4595 -26176
  8. package/dist/audit/index.d.ts +4595 -26176
  9. package/dist/audit/index.js +63 -1063
  10. package/dist/audit/index.js.map +1 -1
  11. package/dist/audit/index.mjs +64 -1057
  12. package/dist/audit/index.mjs.map +1 -1
  13. package/dist/identity/index.d.mts +839 -1281
  14. package/dist/identity/index.d.ts +839 -1281
  15. package/dist/index.d.mts +3 -8
  16. package/dist/index.d.ts +3 -8
  17. package/dist/index.js +2350 -6776
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +2349 -6761
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/integration/index.d.mts +1 -2947
  22. package/dist/integration/index.d.ts +1 -2947
  23. package/dist/integration/index.js +0 -136
  24. package/dist/integration/index.js.map +1 -1
  25. package/dist/integration/index.mjs +0 -135
  26. package/dist/integration/index.mjs.map +1 -1
  27. package/dist/metadata/index.d.mts +3924 -261
  28. package/dist/metadata/index.d.ts +3924 -261
  29. package/dist/metadata/index.js +101 -0
  30. package/dist/metadata/index.js.map +1 -1
  31. package/dist/metadata/index.mjs +101 -1
  32. package/dist/metadata/index.mjs.map +1 -1
  33. package/dist/metadata-translations/index.js +597 -505
  34. package/dist/metadata-translations/index.js.map +1 -1
  35. package/dist/metadata-translations/index.mjs +597 -505
  36. package/dist/metadata-translations/index.mjs.map +1 -1
  37. package/dist/plugin.js +1790 -3614
  38. package/dist/plugin.js.map +1 -1
  39. package/dist/plugin.mjs +1790 -3614
  40. package/dist/plugin.mjs.map +1 -1
  41. package/dist/security/index.d.mts +1 -18850
  42. package/dist/security/index.d.ts +1 -18850
  43. package/dist/security/index.js +0 -1531
  44. package/dist/security/index.js.map +1 -1
  45. package/dist/security/index.mjs +0 -1523
  46. package/dist/security/index.mjs.map +1 -1
  47. package/dist/system/index.d.mts +144 -212
  48. package/dist/system/index.d.ts +144 -212
  49. package/package.json +2 -2
  50. package/dist/state-machine.zod-BNanU03M.d-Ek3_yo9P.d.mts +0 -41
  51. package/dist/state-machine.zod-BNanU03M.d-Ek3_yo9P.d.ts +0 -41
@@ -2,541 +2,115 @@
2
2
 
3
3
  var data = require('@objectstack/spec/data');
4
4
 
5
- // src/audit/sys-audit-log.object.ts
6
- var SysAuditLog = data.ObjectSchema.create({
7
- name: "sys_audit_log",
8
- label: "Audit Log",
9
- pluralLabel: "Audit Logs",
10
- icon: "scroll-text",
5
+ // src/audit/sys-notification.object.ts
6
+ var SysNotification = data.ObjectSchema.create({
7
+ name: "sys_notification",
8
+ label: "Notification Event",
9
+ pluralLabel: "Notification Events",
10
+ icon: "bell",
11
11
  isSystem: true,
12
- managedBy: "append-only",
13
- description: "Immutable audit trail for platform events",
14
- displayNameField: "action",
15
- titleFormat: "{action} \xB7 {object_name}",
16
- compactLayout: ["created_at", "action", "object_name", "record_id", "user_id"],
12
+ managedBy: "system",
13
+ description: "Notification events \u2014 one row per emit() (ADR-0030 Layer 2 ingress)",
14
+ displayNameField: "topic",
15
+ titleFormat: "{topic}",
16
+ compactLayout: ["topic", "severity", "source_object", "created_at"],
17
17
  listViews: {
18
18
  recent: {
19
19
  type: "grid",
20
20
  name: "recent",
21
21
  label: "Recent",
22
- data: { provider: "object", object: "sys_audit_log" },
23
- columns: ["created_at", "action", "object_name", "record_id", "user_id"],
22
+ data: { provider: "object", object: "sys_notification" },
23
+ columns: ["topic", "severity", "actor_id", "source_object", "created_at"],
24
24
  sort: [{ field: "created_at", order: "desc" }],
25
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 }
26
+ emptyState: { title: "No events", message: "No notification events have been emitted." }
47
27
  },
48
- config_changes: {
28
+ by_topic: {
49
29
  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"],
30
+ name: "by_topic",
31
+ label: "By Topic",
32
+ data: { provider: "object", object: "sys_notification" },
33
+ columns: ["topic", "severity", "source_object", "source_id", "created_at"],
64
34
  sort: [{ field: "created_at", order: "desc" }],
65
- pagination: { pageSize: 100 }
35
+ pagination: { pageSize: 100 },
36
+ grouping: { fields: [{ field: "topic", order: "asc", collapsed: false }] }
66
37
  }
67
38
  },
68
39
  fields: {
69
- // ── Event ────────────────────────────────────────────────────
70
- created_at: data.Field.datetime({
71
- label: "Timestamp",
72
- required: true,
73
- defaultValue: "NOW()",
74
- readonly: true,
75
- group: "Event"
76
- }),
77
- action: data.Field.select(
78
- ["create", "update", "delete", "restore", "login", "logout", "permission_change", "config_change", "export", "import"],
79
- {
80
- label: "Action",
81
- required: true,
82
- readonly: true,
83
- searchable: true,
84
- description: "Action type (snake_case)",
85
- group: "Event"
86
- }
87
- ),
88
- user_id: data.Field.lookup("sys_user", {
89
- label: "Actor",
90
- required: false,
91
- readonly: true,
92
- searchable: true,
93
- description: "User who performed the action (null for system actions)",
94
- group: "Event"
95
- }),
96
- // ── Target record ────────────────────────────────────────────
97
- object_name: data.Field.text({
98
- label: "Object",
99
- required: false,
100
- readonly: true,
101
- searchable: true,
102
- maxLength: 255,
103
- description: "Target object (e.g. sys_user, project_task)",
104
- group: "Target"
105
- }),
106
- record_id: data.Field.text({
107
- label: "Record ID",
108
- required: false,
109
- readonly: true,
110
- searchable: true,
111
- description: "ID of the affected record",
112
- group: "Target"
113
- }),
114
- // ── Change payload ───────────────────────────────────────────
115
- old_value: data.Field.textarea({
116
- label: "Old Value",
117
- required: false,
118
- readonly: true,
119
- description: "JSON-serialized previous state",
120
- group: "Changes"
121
- }),
122
- new_value: data.Field.textarea({
123
- label: "New Value",
124
- required: false,
125
- readonly: true,
126
- description: "JSON-serialized new state",
127
- group: "Changes"
128
- }),
129
- // ── Client fingerprint ───────────────────────────────────────
130
- ip_address: data.Field.text({
131
- label: "IP Address",
132
- required: false,
133
- readonly: true,
134
- maxLength: 45,
135
- group: "Client"
136
- }),
137
- user_agent: data.Field.textarea({
138
- label: "User Agent",
139
- required: false,
140
- readonly: true,
141
- group: "Client"
142
- }),
143
- // ── Context ──────────────────────────────────────────────────
144
- tenant_id: data.Field.lookup("sys_organization", {
145
- label: "Tenant",
146
- required: false,
147
- readonly: true,
148
- description: "Tenant context for multi-tenant isolation",
149
- group: "Context"
150
- }),
151
- metadata: data.Field.textarea({
152
- label: "Metadata",
153
- required: false,
154
- readonly: true,
155
- description: "JSON-serialized additional context",
156
- group: "Context"
157
- }),
158
- // ── System ───────────────────────────────────────────────────
159
40
  id: data.Field.text({
160
- label: "Audit Log ID",
161
- required: true,
162
- readonly: true,
163
- group: "System"
164
- })
165
- },
166
- indexes: [
167
- { fields: ["created_at"] },
168
- { fields: ["user_id"] },
169
- { fields: ["object_name", "record_id"] },
170
- { fields: ["action"] },
171
- { fields: ["tenant_id"] }
172
- ],
173
- enable: {
174
- trackHistory: false,
175
- // Audit logs are themselves the audit trail
176
- searchable: true,
177
- apiEnabled: true,
178
- apiMethods: ["get", "list"],
179
- // Read-only — creation happens via internal system hooks only
180
- trash: false,
181
- // Never soft-delete audit logs
182
- mru: false,
183
- clone: false
184
- }
185
- });
186
- var SysPresence = data.ObjectSchema.create({
187
- name: "sys_presence",
188
- label: "Presence",
189
- pluralLabel: "Presences",
190
- icon: "wifi",
191
- isSystem: true,
192
- managedBy: "append-only",
193
- description: "Real-time user presence and activity tracking",
194
- titleFormat: "{user_id} ({status})",
195
- compactLayout: ["user_id", "status", "last_seen"],
196
- fields: {
197
- id: data.Field.text({
198
- label: "Presence ID",
199
- required: true,
200
- readonly: true
201
- }),
202
- created_at: data.Field.datetime({
203
- label: "Created At",
204
- defaultValue: "NOW()",
205
- readonly: true
206
- }),
207
- updated_at: data.Field.datetime({
208
- label: "Updated At",
209
- defaultValue: "NOW()",
210
- readonly: true
211
- }),
212
- user_id: data.Field.lookup("sys_user", {
213
- label: "User",
214
- required: true,
215
- searchable: true
216
- }),
217
- session_id: data.Field.lookup("sys_session", {
218
- label: "Session",
219
- required: true
220
- }),
221
- status: data.Field.select({
222
- label: "Status",
223
- required: true,
224
- defaultValue: "online",
225
- options: [
226
- { value: "online", label: "Online" },
227
- { value: "away", label: "Away" },
228
- { value: "busy", label: "Busy" },
229
- { value: "offline", label: "Offline" }
230
- ]
231
- }),
232
- last_seen: data.Field.datetime({
233
- label: "Last Seen",
234
- required: true,
235
- defaultValue: "NOW()"
236
- }),
237
- current_location: data.Field.text({
238
- label: "Current Location",
239
- required: false,
240
- maxLength: 500
241
- }),
242
- device: data.Field.select({
243
- label: "Device",
244
- required: false,
245
- options: [
246
- { value: "desktop", label: "Desktop" },
247
- { value: "mobile", label: "Mobile" },
248
- { value: "tablet", label: "Tablet" },
249
- { value: "other", label: "Other" }
250
- ]
251
- }),
252
- custom_status: data.Field.text({
253
- label: "Custom Status",
254
- required: false,
255
- maxLength: 255
256
- }),
257
- metadata: data.Field.json({
258
- label: "Metadata",
259
- required: false,
260
- description: "Arbitrary JSON metadata associated with the presence state (matches PresenceStateSchema.metadata)."
261
- })
262
- },
263
- indexes: [
264
- { fields: ["user_id"], unique: false },
265
- { fields: ["session_id"], unique: true },
266
- { fields: ["status"], unique: false }
267
- ],
268
- enable: {
269
- trackHistory: false,
270
- searchable: false,
271
- apiEnabled: true,
272
- apiMethods: ["get", "list", "create", "update", "delete"],
273
- trash: false,
274
- mru: false
275
- }
276
- });
277
- var SysActivity = data.ObjectSchema.create({
278
- name: "sys_activity",
279
- label: "Activity",
280
- pluralLabel: "Activities",
281
- icon: "activity",
282
- isSystem: true,
283
- managedBy: "append-only",
284
- description: "Recent activity stream entries (lightweight, denormalized)",
285
- displayNameField: "summary",
286
- titleFormat: "{type} \xB7 {summary}",
287
- compactLayout: ["timestamp", "type", "actor_name", "summary"],
288
- fields: {
289
- id: data.Field.text({
290
- label: "Activity ID",
41
+ label: "Notification ID",
291
42
  required: true,
292
43
  readonly: true,
293
44
  group: "System"
294
45
  }),
295
- timestamp: data.Field.datetime({
296
- label: "Timestamp",
46
+ // ── Event identity ───────────────────────────────────────────
47
+ topic: data.Field.text({
48
+ label: "Topic",
297
49
  required: true,
298
- defaultValue: "NOW()",
299
- readonly: true,
300
- group: "Event"
301
- }),
302
- type: data.Field.select(
303
- [
304
- "created",
305
- "updated",
306
- "deleted",
307
- "commented",
308
- "mentioned",
309
- "shared",
310
- "assigned",
311
- "completed",
312
- "login",
313
- "logout",
314
- "system"
315
- ],
316
- {
317
- label: "Type",
318
- required: true,
319
- readonly: true,
320
- searchable: true,
321
- group: "Event"
322
- }
323
- ),
324
- summary: data.Field.text({
325
- label: "Summary",
326
- required: true,
327
- readonly: true,
328
- maxLength: 500,
50
+ maxLength: 200,
329
51
  searchable: true,
330
- description: "Human-readable one-line summary",
52
+ description: "Notification topic, e.g. task.assigned, collab.mention",
331
53
  group: "Event"
332
54
  }),
333
- // ── Actor ───────────────────────────────────────────────────
334
- actor_id: data.Field.lookup("sys_user", {
335
- label: "Actor",
336
- required: false,
337
- readonly: true,
338
- searchable: true,
339
- group: "Actor"
340
- }),
341
- actor_name: data.Field.text({
342
- label: "Actor Name",
343
- required: false,
344
- readonly: true,
345
- group: "Actor"
346
- }),
347
- actor_avatar_url: data.Field.url({
348
- label: "Actor Avatar",
349
- required: false,
350
- readonly: true,
351
- group: "Actor"
352
- }),
353
- // ── Target ───────────────────────────────────────────────────
354
- object_name: data.Field.text({
355
- label: "Object",
356
- required: false,
357
- readonly: true,
358
- searchable: true,
359
- maxLength: 255,
360
- description: "Target object short name (e.g. account, sys_user)",
361
- group: "Target"
362
- }),
363
- record_id: data.Field.text({
364
- label: "Record ID",
365
- required: false,
366
- readonly: true,
367
- searchable: true,
368
- group: "Target"
369
- }),
370
- record_label: data.Field.text({
371
- label: "Record Label",
55
+ payload: data.Field.json({
56
+ label: "Payload",
372
57
  required: false,
373
- readonly: true,
374
- maxLength: 255,
375
- description: "Display label of the target record at write time",
376
- group: "Target"
377
- }),
378
- url: data.Field.url({
379
- label: "URL",
380
- required: false,
381
- readonly: true,
382
- description: "Optional deep-link to the activity target",
383
- group: "Target"
58
+ description: "Template inputs carried to channels (title/body/url/actor/source/\u2026)",
59
+ group: "Event"
384
60
  }),
385
- // ── Context ──────────────────────────────────────────────────
386
- environment_id: data.Field.lookup("sys_environment", {
387
- label: "Environment",
61
+ severity: data.Field.select(["info", "warning", "critical"], {
62
+ label: "Severity",
388
63
  required: false,
389
- readonly: true,
390
- searchable: true,
391
- description: "Environment context (multi-environment deployments)",
392
- group: "Context"
64
+ defaultValue: "info",
65
+ description: "Severity hint for rendering / filtering",
66
+ group: "Event"
393
67
  }),
394
- metadata: data.Field.textarea({
395
- label: "Metadata",
68
+ dedup_key: data.Field.text({
69
+ label: "Dedup Key",
396
70
  required: false,
397
- readonly: true,
398
- description: "JSON-serialized additional context",
399
- group: "Context"
400
- })
401
- },
402
- indexes: [
403
- { fields: ["timestamp"] },
404
- { fields: ["actor_id"] },
405
- { fields: ["object_name", "record_id"] },
406
- { fields: ["type"] },
407
- { fields: ["environment_id"] }
408
- ],
409
- enable: {
410
- trackHistory: false,
411
- searchable: true,
412
- apiEnabled: true,
413
- apiMethods: ["get", "list"],
414
- trash: false,
415
- mru: false,
416
- clone: false
417
- }
418
- });
419
- var SysComment = data.ObjectSchema.create({
420
- name: "sys_comment",
421
- label: "Comment",
422
- pluralLabel: "Comments",
423
- icon: "message-square",
424
- isSystem: true,
425
- managedBy: "platform",
426
- description: "Threaded comments attached to records via thread_id",
427
- displayNameField: "body",
428
- titleFormat: "{author_name}: {body}",
429
- compactLayout: ["created_at", "author_name", "body"],
430
- fields: {
431
- id: data.Field.text({
432
- label: "Comment ID",
433
- required: true,
434
- readonly: true,
435
- group: "System"
436
- }),
437
- // ── Thread ───────────────────────────────────────────────────
438
- thread_id: data.Field.text({
439
- label: "Thread",
440
- required: true,
441
- searchable: true,
442
71
  maxLength: 255,
443
- description: "Thread identifier \u2014 conventionally `{object}:{record_id}` (e.g. `sys_user:abc123`)",
444
- group: "Thread"
445
- }),
446
- parent_id: data.Field.lookup("sys_comment", {
447
- label: "Parent Comment",
448
- required: false,
449
- description: "Optional parent comment for nested replies",
450
- group: "Thread"
451
- }),
452
- reply_count: data.Field.number({
453
- label: "Reply Count",
454
- defaultValue: 0,
455
- readonly: true,
456
- group: "Thread"
457
- }),
458
- // ── Author ───────────────────────────────────────────────────
459
- author_id: data.Field.lookup("sys_user", {
460
- label: "Author",
461
- required: true,
462
- searchable: true,
463
- group: "Author"
464
- }),
465
- author_name: data.Field.text({
466
- label: "Author Name",
467
- required: false,
468
- group: "Author"
72
+ description: "Idempotency key within a topic window; a repeat emit is a no-op",
73
+ group: "Event"
469
74
  }),
470
- author_avatar_url: data.Field.url({
471
- label: "Author Avatar",
75
+ // ── Source linkage ───────────────────────────────────────────
76
+ source_object: data.Field.text({
77
+ label: "Source Object",
472
78
  required: false,
473
- group: "Author"
474
- }),
475
- // ── Body ─────────────────────────────────────────────────────
476
- body: data.Field.textarea({
477
- label: "Body",
478
- required: true,
479
- searchable: true,
480
- description: "Comment text (Markdown supported)",
481
- group: "Body"
79
+ maxLength: 100,
80
+ description: "Object name of the related record (e.g. lead, opportunity)",
81
+ group: "Source"
482
82
  }),
483
- mentions: data.Field.textarea({
484
- label: "Mentions",
83
+ source_id: data.Field.text({
84
+ label: "Source Record",
485
85
  required: false,
486
- description: "JSON array of @mention objects",
487
- group: "Body"
86
+ maxLength: 100,
87
+ description: "Record id within source_object",
88
+ group: "Source"
488
89
  }),
489
- reactions: data.Field.textarea({
490
- label: "Reactions",
90
+ actor_id: data.Field.lookup("sys_user", {
91
+ label: "Actor",
491
92
  required: false,
492
- description: "JSON array of emoji reaction objects",
493
- group: "Body"
93
+ description: "User who caused the event (mentioner, assigner)",
94
+ group: "Source"
494
95
  }),
495
96
  // ── Lifecycle ────────────────────────────────────────────────
496
- is_edited: data.Field.boolean({
497
- label: "Edited",
498
- defaultValue: false,
499
- group: "Lifecycle"
500
- }),
501
- edited_at: data.Field.datetime({
502
- label: "Edited At",
503
- required: false,
504
- group: "Lifecycle"
505
- }),
506
- visibility: data.Field.select(
507
- ["public", "internal", "private"],
508
- {
509
- label: "Visibility",
510
- defaultValue: "public",
511
- group: "Lifecycle"
512
- }
513
- ),
514
97
  created_at: data.Field.datetime({
515
98
  label: "Created At",
516
99
  required: true,
517
100
  defaultValue: "NOW()",
518
101
  readonly: true,
519
102
  group: "System"
520
- }),
521
- updated_at: data.Field.datetime({
522
- label: "Updated At",
523
- required: false,
524
- group: "System"
525
103
  })
526
104
  },
527
105
  indexes: [
528
- { fields: ["thread_id", "created_at"] },
529
- { fields: ["parent_id"] },
530
- { fields: ["author_id"] }
531
- ],
532
- enable: {
533
- trackHistory: true,
534
- searchable: true,
535
- apiEnabled: true,
536
- trash: true,
537
- mru: false,
538
- clone: false
539
- }
106
+ { fields: ["topic", "created_at"] },
107
+ // Idempotency spine (ADR-0030). UNIQUE so `emit()` dedup is race-safe: a
108
+ // concurrent emit with the same dedup_key loses the insert and converges to
109
+ // the winner (mirrors the delivery outbox). SQL treats NULLs as distinct, so
110
+ // the (common) events with no dedup_key are unconstrained.
111
+ { fields: ["dedup_key"], unique: true },
112
+ { fields: ["source_object", "source_id"] }
113
+ ]
540
114
  });
541
115
  var SysAttachment = data.ObjectSchema.create({
542
116
  name: "sys_attachment",
@@ -655,202 +229,6 @@ var SysAttachment = data.ObjectSchema.create({
655
229
  clone: false
656
230
  }
657
231
  });
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
- /**
670
- * Row-level inbox actions. Use `visible` CEL expressions to ensure
671
- * `mark_read` only shows on unread rows and vice-versa, mirroring the
672
- * mark-as-read affordances in GitHub / Linear inboxes. The toolbar-level
673
- * `mark_all_read` is intentionally omitted server-side: it requires a
674
- * bulk update primitive that doesn't yet exist on the REST surface, and
675
- * the popover already handles the multi-row case client-side via N
676
- * single-row PATCHes (see `InboxPopover.tsx` -> AppHeader `markAllRead`).
677
- */
678
- actions: [
679
- {
680
- name: "mark_read",
681
- label: "Mark as Read",
682
- icon: "check",
683
- variant: "secondary",
684
- mode: "custom",
685
- locations: ["list_item"],
686
- type: "api",
687
- method: "PATCH",
688
- target: "/api/v1/data/sys_notification/{id}",
689
- bodyExtra: { is_read: true },
690
- visible: "!record.is_read",
691
- successMessage: "Notification marked as read",
692
- refreshAfter: true
693
- },
694
- {
695
- name: "mark_unread",
696
- label: "Mark as Unread",
697
- icon: "bell-dot",
698
- variant: "secondary",
699
- mode: "custom",
700
- locations: ["list_item"],
701
- type: "api",
702
- method: "PATCH",
703
- target: "/api/v1/data/sys_notification/{id}",
704
- bodyExtra: { is_read: false, read_at: null },
705
- visible: "record.is_read",
706
- successMessage: "Notification marked as unread",
707
- refreshAfter: true
708
- }
709
- ],
710
- listViews: {
711
- unread: {
712
- type: "grid",
713
- name: "unread",
714
- label: "Unread",
715
- data: { provider: "object", object: "sys_notification" },
716
- // Title + actor first (the "who/what" the user actually scans);
717
- // type stays as a categorising chip; created_at right-aligned.
718
- columns: ["title", "actor_name", "type", "created_at"],
719
- filter: [
720
- { field: "recipient_id", operator: "equals", value: "{current_user_id}" },
721
- { field: "is_read", operator: "equals", value: false }
722
- ],
723
- sort: [{ field: "created_at", order: "desc" }],
724
- pagination: { pageSize: 50 },
725
- emptyState: { title: "Inbox zero", message: "No unread notifications." }
726
- },
727
- mine: {
728
- type: "grid",
729
- name: "mine",
730
- label: "Mine",
731
- data: { provider: "object", object: "sys_notification" },
732
- columns: ["title", "actor_name", "type", "is_read", "created_at"],
733
- filter: [{ field: "recipient_id", operator: "equals", value: "{current_user_id}" }],
734
- sort: [{ field: "created_at", order: "desc" }],
735
- pagination: { pageSize: 50 },
736
- // Group by notification category so mention/assignment storms don't
737
- // hide system or task_due rows. Users still toggle to flat via the
738
- // toolbar Group control if they prefer chronology only.
739
- grouping: { fields: [{ field: "type", order: "asc", collapsed: false }] }
740
- },
741
- all_notifications: {
742
- type: "grid",
743
- name: "all_notifications",
744
- label: "All",
745
- data: { provider: "object", object: "sys_notification" },
746
- columns: ["title", "recipient_id", "actor_name", "type", "is_read", "created_at"],
747
- sort: [{ field: "created_at", order: "desc" }],
748
- pagination: { pageSize: 100 }
749
- }
750
- },
751
- fields: {
752
- id: data.Field.text({
753
- label: "Notification ID",
754
- required: true,
755
- readonly: true,
756
- group: "System"
757
- }),
758
- // ── Routing ──────────────────────────────────────────────────
759
- recipient_id: data.Field.lookup("sys_user", {
760
- label: "Recipient",
761
- required: true,
762
- searchable: true,
763
- description: "User the notification is delivered to",
764
- group: "Routing"
765
- }),
766
- // ── Content ──────────────────────────────────────────────────
767
- type: data.Field.select(
768
- ["mention", "assignment", "comment_reply", "lead_converted", "task_due", "system"],
769
- {
770
- label: "Type",
771
- required: true,
772
- defaultValue: "system",
773
- description: "Notification category \u2014 drives icon + sort priority",
774
- group: "Content"
775
- }
776
- ),
777
- title: data.Field.text({
778
- label: "Title",
779
- required: true,
780
- maxLength: 255,
781
- searchable: true,
782
- group: "Content"
783
- }),
784
- body: data.Field.textarea({
785
- label: "Body",
786
- required: false,
787
- description: "Optional secondary text (one-line summary)",
788
- group: "Content"
789
- }),
790
- // ── Source linkage ───────────────────────────────────────────
791
- source_object: data.Field.text({
792
- label: "Source Object",
793
- required: false,
794
- maxLength: 100,
795
- description: "Object name of the related record (e.g. lead, opportunity)",
796
- group: "Source"
797
- }),
798
- source_id: data.Field.text({
799
- label: "Source Record",
800
- required: false,
801
- maxLength: 100,
802
- description: "Record id within source_object",
803
- group: "Source"
804
- }),
805
- url: data.Field.url({
806
- label: "Deep Link",
807
- required: false,
808
- description: "Optional URL to navigate to when clicked",
809
- group: "Source"
810
- }),
811
- actor_id: data.Field.lookup("sys_user", {
812
- label: "Actor",
813
- required: false,
814
- description: "User who caused the notification (mentioner, assigner)",
815
- group: "Source"
816
- }),
817
- actor_name: data.Field.text({
818
- label: "Actor Name",
819
- required: false,
820
- group: "Source"
821
- }),
822
- // ── Read state ───────────────────────────────────────────────
823
- is_read: data.Field.boolean({
824
- label: "Read",
825
- defaultValue: false,
826
- description: "True once recipient acknowledges",
827
- group: "State"
828
- }),
829
- read_at: data.Field.datetime({
830
- label: "Read At",
831
- required: false,
832
- group: "State"
833
- }),
834
- // ── Lifecycle ────────────────────────────────────────────────
835
- created_at: data.Field.datetime({
836
- label: "Created At",
837
- required: true,
838
- defaultValue: "NOW()",
839
- readonly: true,
840
- group: "System"
841
- }),
842
- updated_at: data.Field.datetime({
843
- label: "Updated At",
844
- required: false,
845
- group: "System"
846
- })
847
- },
848
- indexes: [
849
- { fields: ["recipient_id", "is_read", "created_at"] },
850
- { fields: ["recipient_id", "created_at"] },
851
- { fields: ["source_object", "source_id"] }
852
- ]
853
- });
854
232
  var SysEmail = data.ObjectSchema.create({
855
233
  name: "sys_email",
856
234
  label: "Email",
@@ -1353,377 +731,6 @@ var SysReportSchedule = data.ObjectSchema.create({
1353
731
  { fields: ["owner_id"] }
1354
732
  ]
1355
733
  });
1356
- var SysApprovalProcess = data.ObjectSchema.create({
1357
- name: "sys_approval_process",
1358
- label: "Approval Process",
1359
- pluralLabel: "Approval Processes",
1360
- icon: "check-square",
1361
- isSystem: true,
1362
- managedBy: "config",
1363
- // Authoring an approval process requires a visual step designer that
1364
- // doesn't yet exist — the embedded `definition_json` textarea would
1365
- // force admins to hand-write a multi-page ApprovalProcess envelope.
1366
- // Suppress generic CRUD until the designer lands. Real authoring path:
1367
- // call `defineApprovalProcess({...})` in code and seed via the
1368
- // approvals service (`POST /api/v1/approvals/processes`) or commit the
1369
- // definition as a fixture. Editing existing rows (e.g. toggling
1370
- // `active`) is also suppressed for now because the same textarea would
1371
- // appear; use the service API or a future designer instead.
1372
- userActions: { create: false, edit: false, delete: false, import: false },
1373
- description: "Persisted approval process definition. Authored via defineApprovalProcess() in code; visual designer is on the roadmap.",
1374
- displayNameField: "name",
1375
- titleFormat: "{label}",
1376
- compactLayout: ["name", "object_name", "active", "updated_at"],
1377
- listViews: {
1378
- active: {
1379
- type: "grid",
1380
- name: "active",
1381
- label: "Active",
1382
- data: { provider: "object", object: "sys_approval_process" },
1383
- columns: ["label", "object_name", "active", "updated_at"],
1384
- filter: [{ field: "active", operator: "equals", value: true }],
1385
- sort: [{ field: "label", order: "asc" }],
1386
- pagination: { pageSize: 50 }
1387
- },
1388
- inactive: {
1389
- type: "grid",
1390
- name: "inactive",
1391
- label: "Inactive",
1392
- data: { provider: "object", object: "sys_approval_process" },
1393
- columns: ["label", "object_name", "active", "updated_at"],
1394
- filter: [{ field: "active", operator: "equals", value: false }],
1395
- sort: [{ field: "label", order: "asc" }],
1396
- pagination: { pageSize: 50 }
1397
- },
1398
- by_object: {
1399
- type: "grid",
1400
- name: "by_object",
1401
- label: "By Object",
1402
- data: { provider: "object", object: "sys_approval_process" },
1403
- columns: ["object_name", "label", "active", "updated_at"],
1404
- sort: [{ field: "object_name", order: "asc" }, { field: "label", order: "asc" }],
1405
- grouping: { fields: [{ field: "object_name", order: "asc", collapsed: false }] },
1406
- pagination: { pageSize: 100 }
1407
- },
1408
- all_processes: {
1409
- type: "grid",
1410
- name: "all_processes",
1411
- label: "All",
1412
- data: { provider: "object", object: "sys_approval_process" },
1413
- columns: ["label", "object_name", "active", "updated_at"],
1414
- sort: [{ field: "label", order: "asc" }],
1415
- pagination: { pageSize: 50 }
1416
- }
1417
- },
1418
- fields: {
1419
- id: data.Field.text({ label: "Process ID", required: true, readonly: true, group: "System" }),
1420
- name: data.Field.text({
1421
- label: "Name",
1422
- required: true,
1423
- maxLength: 100,
1424
- description: "Unique snake_case name \u2014 referenced by submitters and audit rows",
1425
- group: "Definition"
1426
- }),
1427
- label: data.Field.text({
1428
- label: "Display Label",
1429
- required: true,
1430
- maxLength: 200,
1431
- group: "Definition"
1432
- }),
1433
- object_name: data.Field.text({
1434
- label: "Object",
1435
- required: true,
1436
- maxLength: 100,
1437
- description: "Short object name this process governs",
1438
- group: "Definition"
1439
- }),
1440
- description: data.Field.textarea({ label: "Description", required: false, group: "Definition" }),
1441
- active: data.Field.boolean({
1442
- label: "Active",
1443
- required: true,
1444
- defaultValue: false,
1445
- description: "Only active processes are dispatched on submission",
1446
- group: "Definition"
1447
- }),
1448
- definition_json: data.Field.textarea({
1449
- label: "Definition",
1450
- required: true,
1451
- description: "Serialised ApprovalProcess JSON (see @objectstack/spec/automation/approval)",
1452
- group: "Definition"
1453
- }),
1454
- created_at: data.Field.datetime({
1455
- label: "Created At",
1456
- required: true,
1457
- defaultValue: "NOW()",
1458
- readonly: true,
1459
- group: "System"
1460
- }),
1461
- updated_at: data.Field.datetime({ label: "Updated At", required: false, group: "System" })
1462
- },
1463
- indexes: [
1464
- { fields: ["name"], unique: true },
1465
- { fields: ["object_name"] },
1466
- { fields: ["active", "object_name"] }
1467
- ]
1468
- });
1469
- var SysApprovalRequest = data.ObjectSchema.create({
1470
- name: "sys_approval_request",
1471
- label: "Approval Request",
1472
- pluralLabel: "Approval Requests",
1473
- icon: "inbox",
1474
- isSystem: true,
1475
- managedBy: "system",
1476
- description: "Live approval instance tracked per submission",
1477
- displayNameField: "id",
1478
- titleFormat: "{process_name} \xB7 {record_id}",
1479
- compactLayout: ["process_name", "object_name", "record_id", "status", "current_step", "submitter_id", "updated_at"],
1480
- // Curated built-in list views — render as segmented tabs in the console.
1481
- // Filters use {current_user_id} substitution wired by the console.
1482
- listViews: {
1483
- my_pending: {
1484
- type: "grid",
1485
- name: "my_pending",
1486
- label: "My Pending",
1487
- data: { provider: "object", object: "sys_approval_request" },
1488
- columns: ["process_name", "object_name", "record_id", "current_step", "submitter_id", "updated_at"],
1489
- filter: [
1490
- { field: "status", operator: "equals", value: "pending" },
1491
- { field: "pending_approvers", operator: "contains", value: "{current_user_id}" }
1492
- ],
1493
- sort: [{ field: "updated_at", order: "desc" }],
1494
- pagination: { pageSize: 25 },
1495
- emptyState: { title: "No pending approvals", message: "You're all caught up." }
1496
- },
1497
- submitted_by_me: {
1498
- type: "grid",
1499
- name: "submitted_by_me",
1500
- label: "I Submitted",
1501
- data: { provider: "object", object: "sys_approval_request" },
1502
- columns: ["process_name", "object_name", "record_id", "status", "current_step", "updated_at"],
1503
- filter: [{ field: "submitter_id", operator: "equals", value: "{current_user_id}" }],
1504
- sort: [{ field: "updated_at", order: "desc" }],
1505
- pagination: { pageSize: 25 }
1506
- },
1507
- completed: {
1508
- type: "grid",
1509
- name: "completed",
1510
- label: "Completed",
1511
- data: { provider: "object", object: "sys_approval_request" },
1512
- columns: ["process_name", "object_name", "record_id", "status", "submitter_id", "completed_at"],
1513
- filter: [{ field: "status", operator: "in", value: ["approved", "rejected", "recalled"] }],
1514
- sort: [{ field: "completed_at", order: "desc" }],
1515
- pagination: { pageSize: 25 }
1516
- },
1517
- all_requests: {
1518
- type: "grid",
1519
- name: "all_requests",
1520
- label: "All",
1521
- data: { provider: "object", object: "sys_approval_request" },
1522
- columns: ["process_name", "object_name", "record_id", "status", "current_step", "submitter_id", "updated_at"],
1523
- sort: [{ field: "updated_at", order: "desc" }],
1524
- pagination: { pageSize: 50 }
1525
- }
1526
- },
1527
- fields: {
1528
- id: data.Field.text({ label: "Request ID", required: true, readonly: true, group: "System" }),
1529
- organization_id: data.Field.lookup("sys_organization", {
1530
- label: "Organization",
1531
- required: false,
1532
- group: "System",
1533
- description: "Tenant that owns this approval request (propagated from submitter context)"
1534
- }),
1535
- process_name: data.Field.text({
1536
- label: "Process",
1537
- required: true,
1538
- maxLength: 100,
1539
- description: "sys_approval_process.name this request was opened against",
1540
- group: "Target"
1541
- }),
1542
- object_name: data.Field.text({
1543
- label: "Object",
1544
- required: true,
1545
- maxLength: 100,
1546
- group: "Target"
1547
- }),
1548
- record_id: data.Field.text({
1549
- label: "Record ID",
1550
- required: true,
1551
- maxLength: 100,
1552
- group: "Target"
1553
- }),
1554
- submitter_id: data.Field.lookup("sys_user", {
1555
- label: "Submitter",
1556
- required: false,
1557
- group: "Target"
1558
- }),
1559
- submitter_comment: data.Field.textarea({
1560
- label: "Submitter Comment",
1561
- required: false,
1562
- group: "Target"
1563
- }),
1564
- status: data.Field.select(
1565
- ["pending", "approved", "rejected", "recalled"],
1566
- {
1567
- label: "Status",
1568
- required: true,
1569
- defaultValue: "pending",
1570
- description: "Lifecycle state of the request",
1571
- group: "State"
1572
- }
1573
- ),
1574
- current_step: data.Field.text({
1575
- label: "Current Step",
1576
- required: false,
1577
- maxLength: 100,
1578
- description: "Machine name of the step awaiting approval",
1579
- group: "State"
1580
- }),
1581
- current_step_index: data.Field.number({
1582
- label: "Current Step Index",
1583
- required: false,
1584
- defaultValue: 0,
1585
- group: "State"
1586
- }),
1587
- pending_approvers: data.Field.textarea({
1588
- label: "Pending Approvers",
1589
- required: false,
1590
- description: "Comma-separated user ids who can act on the current step",
1591
- group: "State"
1592
- }),
1593
- payload_json: data.Field.textarea({
1594
- label: "Snapshot",
1595
- required: false,
1596
- description: "Record snapshot at submission time",
1597
- group: "State"
1598
- }),
1599
- process_hash: data.Field.text({
1600
- label: "Process Hash",
1601
- required: false,
1602
- maxLength: 80,
1603
- readonly: true,
1604
- description: "sha256 of the approval process body at submit time (ADR-0009 execution pinning). Resolved through sys_metadata_history so process upgrades do not affect in-flight requests.",
1605
- group: "State"
1606
- }),
1607
- completed_at: data.Field.datetime({
1608
- label: "Completed At",
1609
- required: false,
1610
- group: "State"
1611
- }),
1612
- created_at: data.Field.datetime({
1613
- label: "Created At",
1614
- required: true,
1615
- defaultValue: "NOW()",
1616
- readonly: true,
1617
- group: "System"
1618
- }),
1619
- updated_at: data.Field.datetime({ label: "Updated At", required: false, group: "System" })
1620
- },
1621
- indexes: [
1622
- // Look up "is there a pending request for this record?" — common
1623
- // guard on submit and on edit-while-locked checks.
1624
- { fields: ["object_name", "record_id"] },
1625
- { fields: ["status", "object_name"] },
1626
- // "My approvals" inbox — pending_approvers is a CSV string so this
1627
- // index only helps with status pre-filtering; the engine does a
1628
- // post-filter substring match per row.
1629
- { fields: ["status", "updated_at"] },
1630
- { fields: ["submitter_id", "status"] }
1631
- ]
1632
- });
1633
- var SysApprovalAction = data.ObjectSchema.create({
1634
- name: "sys_approval_action",
1635
- label: "Approval Action",
1636
- pluralLabel: "Approval Actions",
1637
- icon: "check-circle",
1638
- isSystem: true,
1639
- managedBy: "append-only",
1640
- description: "Append-only audit trail for approval actions",
1641
- displayNameField: "id",
1642
- titleFormat: "{action} \xB7 {step_name}",
1643
- compactLayout: ["request_id", "step_name", "action", "actor_id", "created_at"],
1644
- listViews: {
1645
- recent: {
1646
- type: "grid",
1647
- name: "recent",
1648
- label: "Recent",
1649
- data: { provider: "object", object: "sys_approval_action" },
1650
- columns: ["created_at", "request_id", "step_name", "action", "actor_id", "comment"],
1651
- sort: [{ field: "created_at", order: "desc" }],
1652
- pagination: { pageSize: 50 },
1653
- emptyState: { title: "No approval actions yet", message: "Actions are logged automatically when approvals progress." }
1654
- },
1655
- by_actor: {
1656
- type: "grid",
1657
- name: "by_actor",
1658
- label: "By Actor",
1659
- data: { provider: "object", object: "sys_approval_action" },
1660
- columns: ["actor_id", "created_at", "request_id", "step_name", "action"],
1661
- sort: [{ field: "actor_id", order: "asc" }, { field: "created_at", order: "desc" }],
1662
- grouping: { fields: [{ field: "actor_id", order: "asc", collapsed: false }] },
1663
- pagination: { pageSize: 100 }
1664
- },
1665
- all_actions: {
1666
- type: "grid",
1667
- name: "all_actions",
1668
- label: "All",
1669
- data: { provider: "object", object: "sys_approval_action" },
1670
- columns: ["created_at", "request_id", "step_name", "action", "actor_id", "comment"],
1671
- sort: [{ field: "created_at", order: "desc" }],
1672
- pagination: { pageSize: 100 }
1673
- }
1674
- },
1675
- fields: {
1676
- id: data.Field.text({ label: "Action ID", required: true, readonly: true, group: "System" }),
1677
- organization_id: data.Field.lookup("sys_organization", {
1678
- label: "Organization",
1679
- required: false,
1680
- group: "System",
1681
- description: "Tenant that owns this action (mirrors the parent request)"
1682
- }),
1683
- request_id: data.Field.lookup("sys_approval_request", {
1684
- label: "Request",
1685
- required: true,
1686
- group: "Target"
1687
- }),
1688
- step_name: data.Field.text({
1689
- label: "Step",
1690
- required: false,
1691
- maxLength: 100,
1692
- description: "Machine name of the step at the time of the action",
1693
- group: "Target"
1694
- }),
1695
- step_index: data.Field.number({
1696
- label: "Step Index",
1697
- required: false,
1698
- group: "Target"
1699
- }),
1700
- action: data.Field.select(
1701
- ["submit", "approve", "reject", "recall", "escalate"],
1702
- {
1703
- label: "Action",
1704
- required: true,
1705
- group: "Action"
1706
- }
1707
- ),
1708
- actor_id: data.Field.lookup("sys_user", {
1709
- label: "Actor",
1710
- required: false,
1711
- group: "Action"
1712
- }),
1713
- comment: data.Field.textarea({ label: "Comment", required: false, group: "Action" }),
1714
- created_at: data.Field.datetime({
1715
- label: "Created At",
1716
- required: true,
1717
- defaultValue: "NOW()",
1718
- readonly: true,
1719
- group: "System"
1720
- })
1721
- },
1722
- indexes: [
1723
- { fields: ["request_id", "created_at"] },
1724
- { fields: ["request_id", "step_index", "action"] }
1725
- ]
1726
- });
1727
734
  var SysJob = data.ObjectSchema.create({
1728
735
  name: "sys_job",
1729
736
  label: "Background Job",
@@ -1955,20 +962,13 @@ var SysJobQueue = data.ObjectSchema.create({
1955
962
  ]
1956
963
  });
1957
964
 
1958
- exports.SysActivity = SysActivity;
1959
- exports.SysApprovalAction = SysApprovalAction;
1960
- exports.SysApprovalProcess = SysApprovalProcess;
1961
- exports.SysApprovalRequest = SysApprovalRequest;
1962
965
  exports.SysAttachment = SysAttachment;
1963
- exports.SysAuditLog = SysAuditLog;
1964
- exports.SysComment = SysComment;
1965
966
  exports.SysEmail = SysEmail;
1966
967
  exports.SysEmailTemplate = SysEmailTemplate;
1967
968
  exports.SysJob = SysJob;
1968
969
  exports.SysJobQueue = SysJobQueue;
1969
970
  exports.SysJobRun = SysJobRun;
1970
971
  exports.SysNotification = SysNotification;
1971
- exports.SysPresence = SysPresence;
1972
972
  exports.SysReportSchedule = SysReportSchedule;
1973
973
  exports.SysSavedReport = SysSavedReport;
1974
974
  //# sourceMappingURL=index.js.map