@grantjs/schema 1.17.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grantjs/schema",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@graphql-codegen/add": "^7.1.1",
|
|
26
26
|
"@graphql-codegen/cli": "^7.4.6",
|
|
27
|
-
"@graphql-codegen/typed-document-node": "^7.
|
|
27
|
+
"@graphql-codegen/typed-document-node": "^7.2.0",
|
|
28
28
|
"@graphql-codegen/typescript": "^6.1.2",
|
|
29
29
|
"@graphql-codegen/typescript-operations": "^6.1.10",
|
|
30
30
|
"@graphql-codegen/typescript-resolvers": "^6.1.1",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"graphql": "^16.14.2",
|
|
35
35
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
36
36
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
37
|
-
"vitest": "^5.0.
|
|
37
|
+
"vitest": "^5.0.5"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"dev": "graphql-codegen --config codegen.ts generate --watch",
|
|
@@ -83,6 +83,17 @@ const INTEGRATIONS_OWNERS: EventCatalogEntry = {
|
|
|
83
83
|
audienceRule: { primitives: ['owners'], excludeActor: true },
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* An event that exists for webhook consumers only: delivered transactionally to
|
|
88
|
+
* subscriptions, never turned into a notification. The empty audience is what
|
|
89
|
+
* keeps the notification generator from creating rows for it.
|
|
90
|
+
*/
|
|
91
|
+
const webhookOnly = (category: EventCategory): EventCatalogEntry => ({
|
|
92
|
+
category,
|
|
93
|
+
deliveryClass: 'transactional',
|
|
94
|
+
audienceRule: { primitives: [] },
|
|
95
|
+
});
|
|
96
|
+
|
|
86
97
|
export const EVENT_TYPES = [
|
|
87
98
|
// IAM mutations (Phase 1 slice)
|
|
88
99
|
'role.created',
|
|
@@ -135,6 +146,17 @@ export const EVENT_TYPES = [
|
|
|
135
146
|
// CDM / project sync job summaries (entity events suppressed during import)
|
|
136
147
|
'project_sync.completed',
|
|
137
148
|
'project_sync.failed',
|
|
149
|
+
// Webhook-only lifecycle events (no notification): users, project users, API keys, tags
|
|
150
|
+
'user.created',
|
|
151
|
+
'user.updated',
|
|
152
|
+
'user.deleted',
|
|
153
|
+
'project.user_updated',
|
|
154
|
+
'api_key.deleted',
|
|
155
|
+
'tag.created',
|
|
156
|
+
'tag.updated',
|
|
157
|
+
'tag.deleted',
|
|
158
|
+
'tag.linked',
|
|
159
|
+
'tag.unlinked',
|
|
138
160
|
] as const;
|
|
139
161
|
|
|
140
162
|
export type EventType = (typeof EVENT_TYPES)[number];
|
|
@@ -200,6 +222,16 @@ export const EVENT_CATALOG: Readonly<Record<EventType, EventCatalogEntry>> = {
|
|
|
200
222
|
'user.password_changed': SECURITY_SUBJECT,
|
|
201
223
|
'project_sync.completed': INTEGRATIONS_OWNERS,
|
|
202
224
|
'project_sync.failed': INTEGRATIONS_OWNERS,
|
|
225
|
+
'user.created': webhookOnly('membership'),
|
|
226
|
+
'user.updated': webhookOnly('membership'),
|
|
227
|
+
'user.deleted': webhookOnly('membership'),
|
|
228
|
+
'project.user_updated': webhookOnly('membership'),
|
|
229
|
+
'api_key.deleted': webhookOnly('security'),
|
|
230
|
+
'tag.created': webhookOnly('iam'),
|
|
231
|
+
'tag.updated': webhookOnly('iam'),
|
|
232
|
+
'tag.deleted': webhookOnly('iam'),
|
|
233
|
+
'tag.linked': webhookOnly('iam'),
|
|
234
|
+
'tag.unlinked': webhookOnly('iam'),
|
|
203
235
|
};
|
|
204
236
|
|
|
205
237
|
export function getEventCatalogEntry(type: EventType): EventCatalogEntry {
|
|
@@ -3,17 +3,62 @@ import type { EventCategory, EventType } from './event-catalog';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Payload carried inside an event. The internal model can be rich; the external
|
|
6
|
-
* webhook envelope projects `data`
|
|
6
|
+
* webhook envelope projects `data` (see `ExternalEventData`): `after` and `delta`
|
|
7
|
+
* are redacted, `before` is reduced to its identity fields.
|
|
7
8
|
*/
|
|
8
9
|
export interface EventData {
|
|
9
10
|
/** Snapshot of the entity after the change (redacted on external delivery). */
|
|
10
11
|
after?: Record<string, unknown> | null;
|
|
11
|
-
/** Snapshot before the change (optional). */
|
|
12
|
+
/** Snapshot before the change (optional). External delivery keeps only its identity fields. */
|
|
12
13
|
before?: Record<string, unknown> | null;
|
|
13
14
|
/** Changed-fields delta: field -> { from, to }. */
|
|
14
15
|
delta?: Record<string, { from: unknown; to: unknown }> | null;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Keys an external (webhook) `data.before` may carry. The projection copies these,
|
|
20
|
+
* and only these, from the internal `before` snapshot, and only when the value is a
|
|
21
|
+
* string, number, boolean or `null`: a subscriber learns which entity or pivot an
|
|
22
|
+
* event removed, never its profile fields, metadata or secrets.
|
|
23
|
+
*/
|
|
24
|
+
export const EVENT_IDENTITY_KEYS = [
|
|
25
|
+
'id',
|
|
26
|
+
'name',
|
|
27
|
+
'slug',
|
|
28
|
+
'kind',
|
|
29
|
+
'type',
|
|
30
|
+
'userId',
|
|
31
|
+
'roleId',
|
|
32
|
+
'groupId',
|
|
33
|
+
'permissionId',
|
|
34
|
+
'tagId',
|
|
35
|
+
'apiKeyId',
|
|
36
|
+
'resourceId',
|
|
37
|
+
'targetKind',
|
|
38
|
+
'targetId',
|
|
39
|
+
'projectId',
|
|
40
|
+
'roleName',
|
|
41
|
+
'groupName',
|
|
42
|
+
'permissionName',
|
|
43
|
+
'tagName',
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
export type EventIdentityKey = (typeof EVENT_IDENTITY_KEYS)[number];
|
|
47
|
+
|
|
48
|
+
/** Identity-only projection of an internal `before` snapshot (see `EVENT_IDENTITY_KEYS`). */
|
|
49
|
+
export type EventIdentity = Partial<Record<EventIdentityKey, string | number | boolean | null>>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `data` as delivered to webhook consumers: `after` and `delta` redacted, `before`
|
|
53
|
+
* reduced to the identity of the entity or pivot the event is about. Absent when
|
|
54
|
+
* the internal snapshot has no identity field.
|
|
55
|
+
*/
|
|
56
|
+
export interface ExternalEventData {
|
|
57
|
+
after?: Record<string, unknown> | null;
|
|
58
|
+
before?: EventIdentity | null;
|
|
59
|
+
delta?: Record<string, { from: unknown; to: unknown }> | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
17
62
|
/** Reference to the aggregate (entity) the event is about. */
|
|
18
63
|
export interface EventAggregateRef {
|
|
19
64
|
kind: string;
|
|
@@ -33,6 +78,13 @@ export interface DomainEventInput {
|
|
|
33
78
|
aggregate?: EventAggregateRef;
|
|
34
79
|
data: EventData;
|
|
35
80
|
occurredAt?: Date;
|
|
81
|
+
/**
|
|
82
|
+
* Projects this event is relevant to beyond the one named by its scope. The
|
|
83
|
+
* publisher adds the projects an entity-bound aggregate is linked to; a caller
|
|
84
|
+
* passes it only to capture the links before a write that destroys them (a
|
|
85
|
+
* delete). Never part of the external envelope.
|
|
86
|
+
*/
|
|
87
|
+
projectIds?: string[];
|
|
36
88
|
}
|
|
37
89
|
|
|
38
90
|
/**
|
|
@@ -51,12 +103,17 @@ export interface DomainEvent {
|
|
|
51
103
|
aggregate?: EventAggregateRef;
|
|
52
104
|
data: EventData;
|
|
53
105
|
occurredAt: Date;
|
|
106
|
+
/**
|
|
107
|
+
* Projects the event is relevant to in addition to the project its scope names
|
|
108
|
+
* (webhook fan-out, ADR 0012). Internal: not carried by the webhook envelope.
|
|
109
|
+
*/
|
|
110
|
+
projectIds?: string[];
|
|
54
111
|
}
|
|
55
112
|
|
|
56
113
|
/**
|
|
57
114
|
* External event envelope delivered to webhook consumers. CloudEvents 1.0 core
|
|
58
115
|
* fields plus Grant extension attributes (prefixed `grant`). `data` is the
|
|
59
|
-
* redacted projection of the internal payload.
|
|
116
|
+
* redacted projection of the internal payload (`ExternalEventData`).
|
|
60
117
|
*/
|
|
61
118
|
export interface CloudEventEnvelope {
|
|
62
119
|
specversion: '1.0';
|
|
@@ -68,6 +125,13 @@ export interface CloudEventEnvelope {
|
|
|
68
125
|
grantsequence: string;
|
|
69
126
|
grantscope: { tenant: string; id: string };
|
|
70
127
|
grantactor?: { userId: string } | null;
|
|
128
|
+
/**
|
|
129
|
+
* The user the event is about, when it has one (`user.role_revoked`,
|
|
130
|
+
* `project.user_removed`, `organization.invitation_sent`, ...). `null` when
|
|
131
|
+
* the event has no subject user. It names the user even when `data.before`
|
|
132
|
+
* is absent or carries no `userId`.
|
|
133
|
+
*/
|
|
134
|
+
grantsubjectuser?: { userId: string } | null;
|
|
71
135
|
grantcategory: EventCategory;
|
|
72
|
-
data:
|
|
136
|
+
data: ExternalEventData;
|
|
73
137
|
}
|