@palantir/pack.state.foundry 0.8.0 → 0.10.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.
@@ -0,0 +1,258 @@
1
+ /*
2
+ * Copyright 2026 Palantir Technologies, Inc. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import type { ActivityCollaborativeUpdate } from "@osdk/foundry.pack";
18
+ import type { DocumentSchema, Model } from "@palantir/pack.document-schema.model-types";
19
+ import { ActivityEventDataType, Metadata } from "@palantir/pack.document-schema.model-types";
20
+ import { describe, expect, it } from "vitest";
21
+ import { getActivityEvent } from "../eventMappers.js";
22
+
23
+ const mockModel = {
24
+ __type: {} as { someField: string },
25
+ zodSchema: {} as Model["zodSchema"],
26
+ [Metadata]: { name: "TestModel" },
27
+ } as unknown as Model;
28
+
29
+ const emptySchema = {
30
+ [Metadata]: { name: "TestSchema", version: 0, models: {} },
31
+ } as unknown as DocumentSchema;
32
+
33
+ const schemaWithModel = {
34
+ ...emptySchema,
35
+ TestModel: mockModel,
36
+ } as unknown as DocumentSchema;
37
+
38
+ function makeActivityUpdate(
39
+ eventData: unknown,
40
+ ): ActivityCollaborativeUpdate {
41
+ return {
42
+ type: "activityCreated",
43
+ activityEvent: {
44
+ eventId: "event-1",
45
+ eventData,
46
+ isRead: false,
47
+ aggregationKey: "agg-1",
48
+ createdBy: "user-1",
49
+ createdTime: "2026-01-01T00:00:00Z",
50
+ },
51
+ } as ActivityCollaborativeUpdate;
52
+ }
53
+
54
+ describe("getActivityEvent", () => {
55
+ it("returns undefined for activityDeleted events", () => {
56
+ const update = {
57
+ type: "activityDeleted",
58
+ eventId: "event-1",
59
+ aggregationKey: "agg-1",
60
+ } as ActivityCollaborativeUpdate;
61
+
62
+ expect(getActivityEvent(emptySchema, update)).toBeUndefined();
63
+ });
64
+
65
+ it("maps documentCreate events", () => {
66
+ const result = getActivityEvent(
67
+ emptySchema,
68
+ makeActivityUpdate({
69
+ type: "documentCreate",
70
+ name: "My Document",
71
+ initialMandatorySecurity: {
72
+ classification: ["SECRET"],
73
+ markings: ["marking-1"],
74
+ },
75
+ }),
76
+ );
77
+
78
+ expect(result).toBeDefined();
79
+ expect(result!.eventData).toEqual({
80
+ type: ActivityEventDataType.DOCUMENT_CREATE,
81
+ name: "My Document",
82
+ initialMandatorySecurity: {
83
+ classification: ["SECRET"],
84
+ markings: ["marking-1"],
85
+ },
86
+ });
87
+ });
88
+
89
+ it("maps documentCreate with missing fields to defaults", () => {
90
+ const result = getActivityEvent(
91
+ emptySchema,
92
+ makeActivityUpdate({
93
+ type: "documentCreate",
94
+ initialMandatorySecurity: undefined,
95
+ }),
96
+ );
97
+
98
+ expect(result!.eventData).toEqual({
99
+ type: ActivityEventDataType.DOCUMENT_CREATE,
100
+ name: "",
101
+ initialMandatorySecurity: {
102
+ classification: undefined,
103
+ markings: undefined,
104
+ },
105
+ });
106
+ });
107
+
108
+ it("maps documentRename events", () => {
109
+ const result = getActivityEvent(
110
+ emptySchema,
111
+ makeActivityUpdate({
112
+ type: "documentRename",
113
+ previousName: "Old Name",
114
+ newName: "New Name",
115
+ }),
116
+ );
117
+
118
+ expect(result!.eventData).toEqual({
119
+ type: ActivityEventDataType.DOCUMENT_RENAME,
120
+ previousName: "Old Name",
121
+ newName: "New Name",
122
+ });
123
+ });
124
+
125
+ it("maps documentDescriptionUpdate events", () => {
126
+ const result = getActivityEvent(
127
+ emptySchema,
128
+ makeActivityUpdate({
129
+ type: "documentDescriptionUpdate",
130
+ newDescription: "Updated description",
131
+ isInitial: false,
132
+ }),
133
+ );
134
+
135
+ expect(result!.eventData).toEqual({
136
+ type: ActivityEventDataType.DOCUMENT_DESCRIPTION_UPDATE,
137
+ newDescription: "Updated description",
138
+ isInitial: false,
139
+ });
140
+ });
141
+
142
+ it("maps documentMandatorySecurityUpdate events", () => {
143
+ const result = getActivityEvent(
144
+ emptySchema,
145
+ makeActivityUpdate({
146
+ type: "documentMandatorySecurityUpdate",
147
+ newClassification: ["test-classification"],
148
+ newMarkings: ["test-marking"],
149
+ }),
150
+ );
151
+
152
+ expect(result!.eventData).toEqual({
153
+ type: ActivityEventDataType.DOCUMENT_MANDATORY_SECURITY_UPDATE,
154
+ newClassification: ["test-classification"],
155
+ newMarkings: ["test-marking"],
156
+ });
157
+ });
158
+
159
+ it("maps documentDiscretionarySecurityUpdate events", () => {
160
+ const newSecurity = {
161
+ owners: [{ type: "all" as const }],
162
+ editors: [],
163
+ viewers: [],
164
+ };
165
+ const result = getActivityEvent(
166
+ emptySchema,
167
+ makeActivityUpdate({
168
+ type: "documentDiscretionarySecurityUpdate",
169
+ principalType: "ALL_PRINCIPAL",
170
+ previousDiscretionarySecurity: undefined,
171
+ newDiscretionarySecurity: newSecurity,
172
+ }),
173
+ );
174
+
175
+ expect(result!.eventData).toEqual({
176
+ type: ActivityEventDataType.DOCUMENT_DISCRETIONARY_SECURITY_UPDATE,
177
+ principalType: "ALL_PRINCIPAL",
178
+ previousDiscretionarySecurity: undefined,
179
+ newDiscretionarySecurity: newSecurity,
180
+ });
181
+ });
182
+
183
+ it("maps documentCustomEvent with known model to CUSTOM_EVENT", () => {
184
+ const customData = { eventType: "shapeAdd", nodeId: "node-1" };
185
+ const result = getActivityEvent(
186
+ schemaWithModel,
187
+ makeActivityUpdate({
188
+ type: "documentCustomEvent",
189
+ eventType: "TestModel",
190
+ data: customData,
191
+ version: 1,
192
+ }),
193
+ );
194
+
195
+ expect(result!.eventData).toEqual({
196
+ type: ActivityEventDataType.CUSTOM_EVENT,
197
+ model: mockModel,
198
+ eventType: "TestModel",
199
+ data: customData,
200
+ });
201
+ });
202
+
203
+ it("maps documentCustomEvent with unknown model to UNKNOWN", () => {
204
+ const customData = { eventType: "shapeAdd", nodeId: "node-1" };
205
+ const result = getActivityEvent(
206
+ emptySchema,
207
+ makeActivityUpdate({
208
+ type: "documentCustomEvent",
209
+ eventType: "NonExistentModel",
210
+ data: customData,
211
+ version: 1,
212
+ }),
213
+ );
214
+
215
+ expect(result!.eventData).toEqual({
216
+ type: ActivityEventDataType.UNKNOWN,
217
+ rawType: "NonExistentModel",
218
+ rawData: customData,
219
+ });
220
+ });
221
+
222
+ it("maps unrecognized event types to UNKNOWN", () => {
223
+ const result = getActivityEvent(
224
+ emptySchema,
225
+ makeActivityUpdate({
226
+ type: "someNewEventType",
227
+ someData: 123,
228
+ }),
229
+ );
230
+
231
+ expect(result!.eventData).toEqual({
232
+ type: ActivityEventDataType.UNKNOWN,
233
+ rawType: "someNewEventType",
234
+ rawData: { type: "someNewEventType", someData: 123 },
235
+ });
236
+ });
237
+
238
+ it("populates top-level event fields correctly", () => {
239
+ const result = getActivityEvent(
240
+ emptySchema,
241
+ makeActivityUpdate({
242
+ type: "documentCreate",
243
+ name: "Test",
244
+ initialMandatorySecurity: { classification: [], markings: [] },
245
+ }),
246
+ );
247
+
248
+ expect(result).toMatchObject({
249
+ eventId: "event-1",
250
+ aggregationKey: "agg-1",
251
+ createdBy: "user-1",
252
+ isRead: false,
253
+ });
254
+ expect(result!.createdInstant).toEqual(
255
+ new Date("2026-01-01T00:00:00Z").getTime(),
256
+ );
257
+ });
258
+ });
@@ -25,8 +25,9 @@ import type {
25
25
  ActivityEventData,
26
26
  ActivityEventDataDocumentCreate,
27
27
  ActivityEventDataDocumentDescriptionUpdate,
28
+ ActivityEventDataDocumentDiscretionarySecurityUpdate,
29
+ ActivityEventDataDocumentMandatorySecurityUpdate,
28
30
  ActivityEventDataDocumentRename,
29
- ActivityEventDataDocumentSecurityUpdate,
30
31
  DocumentSchema,
31
32
  Model,
32
33
  ModelData,
@@ -62,119 +63,79 @@ export function getActivityEvent(
62
63
  };
63
64
  }
64
65
 
65
- /**
66
- * Platform event type names as sent by backpack.
67
- * Note: Only DOCUMENT_CREATE is currently emitted by the backend.
68
- * Remaining types are included for future use.
69
- */
70
- const PlatformEventType = {
71
- DOCUMENT_CREATE: "DocumentCreateEvent",
72
- DOCUMENT_DESCRIPTION_UPDATE: "DocumentDescriptionUpdateEvent",
73
- DOCUMENT_RENAME: "DocumentRenameEvent",
74
- DOCUMENT_SECURITY_UPDATE: "DocumentMandatorySecurityUpdateEvent",
75
- } as const;
76
-
77
- interface WireDocumentCreateEvent {
78
- readonly name?: string;
79
- readonly initialMandatorySecurity?: {
80
- readonly classification?: readonly string[];
81
- readonly markings?: readonly string[];
82
- };
83
- }
84
-
85
- interface WireDocumentRenameEvent {
86
- readonly previousName?: string;
87
- readonly newName?: string;
88
- }
89
-
90
- interface WireDocumentDescriptionUpdateEvent {
91
- readonly newDescription?: string;
92
- readonly isInitial?: boolean;
93
- }
94
-
95
- interface WireDocumentSecurityUpdateEvent {
96
- readonly newClassification?: readonly string[];
97
- readonly newMarkings?: readonly string[];
98
- }
99
-
100
66
  function getActivityEventData(
101
67
  docSchema: DocumentSchema,
102
- { eventData, eventType }: FoundryActivityEvent,
68
+ { eventData }: FoundryActivityEvent,
103
69
  ): ActivityEventData {
104
- const platformEventData = getPlatformActivityEventData(
105
- eventType,
106
- eventData.data,
107
- );
108
- if (platformEventData != null) {
109
- return platformEventData;
110
- }
111
-
112
- // Handle custom application-defined activity events
113
- // TODO: validate model is valid for activity events
114
- const model = docSchema[eventType];
115
- if (model == null) {
116
- return {
117
- rawData: eventData.data,
118
- rawType: eventType,
119
- type: ActivityEventDataType.UNKNOWN,
120
- };
121
- }
122
-
123
- // TODO: validate data against model schema
124
-
125
- return {
126
- eventData: eventData.data as ModelData<Model>,
127
- model,
128
- type: ActivityEventDataType.CUSTOM_EVENT,
129
- };
130
- }
131
-
132
- function getPlatformActivityEventData(
133
- eventType: string,
134
- data: unknown,
135
- ): ActivityEventData | undefined {
136
- switch (eventType) {
137
- case PlatformEventType.DOCUMENT_CREATE: {
138
- const wireData = data as WireDocumentCreateEvent;
70
+ switch (eventData.type) {
71
+ case "documentCreate":
139
72
  return {
140
73
  initialMandatorySecurity: {
141
- classification: wireData.initialMandatorySecurity?.classification,
142
- markings: wireData.initialMandatorySecurity?.markings,
74
+ classification: eventData.initialMandatorySecurity?.classification,
75
+ markings: eventData.initialMandatorySecurity?.markings,
143
76
  },
144
- name: wireData.name ?? "",
77
+ name: eventData.name ?? "",
145
78
  type: ActivityEventDataType.DOCUMENT_CREATE,
146
79
  } satisfies ActivityEventDataDocumentCreate;
147
- }
148
80
 
149
- case PlatformEventType.DOCUMENT_RENAME: {
150
- const wireData = data as WireDocumentRenameEvent;
81
+ case "documentRename":
151
82
  return {
152
- newName: wireData.newName ?? "",
153
- previousName: wireData.previousName ?? "",
83
+ newName: eventData.newName ?? "",
84
+ previousName: eventData.previousName ?? "",
154
85
  type: ActivityEventDataType.DOCUMENT_RENAME,
155
86
  } satisfies ActivityEventDataDocumentRename;
156
- }
157
87
 
158
- case PlatformEventType.DOCUMENT_DESCRIPTION_UPDATE: {
159
- const wireData = data as WireDocumentDescriptionUpdateEvent;
88
+ case "documentDescriptionUpdate":
160
89
  return {
161
- isInitial: wireData.isInitial ?? false,
162
- newDescription: wireData.newDescription ?? "",
90
+ isInitial: eventData.isInitial ?? false,
91
+ newDescription: eventData.newDescription ?? "",
163
92
  type: ActivityEventDataType.DOCUMENT_DESCRIPTION_UPDATE,
164
93
  } satisfies ActivityEventDataDocumentDescriptionUpdate;
165
- }
166
94
 
167
- case PlatformEventType.DOCUMENT_SECURITY_UPDATE: {
168
- const wireData = data as WireDocumentSecurityUpdateEvent;
95
+ case "documentMandatorySecurityUpdate":
96
+ return {
97
+ newClassification: eventData.newClassification ?? [],
98
+ newMarkings: eventData.newMarkings ?? [],
99
+ type: ActivityEventDataType.DOCUMENT_MANDATORY_SECURITY_UPDATE,
100
+ } satisfies ActivityEventDataDocumentMandatorySecurityUpdate;
101
+
102
+ case "documentDiscretionarySecurityUpdate":
169
103
  return {
170
- newClassification: wireData.newClassification ?? [],
171
- newMarkings: wireData.newMarkings ?? [],
172
- type: ActivityEventDataType.DOCUMENT_SECURITY_UPDATE,
173
- } satisfies ActivityEventDataDocumentSecurityUpdate;
104
+ principalType: eventData.principalType ?? "",
105
+ previousDiscretionarySecurity: eventData.previousDiscretionarySecurity,
106
+ newDiscretionarySecurity: eventData.newDiscretionarySecurity ?? [],
107
+ type: ActivityEventDataType.DOCUMENT_DISCRETIONARY_SECURITY_UPDATE,
108
+ } satisfies ActivityEventDataDocumentDiscretionarySecurityUpdate;
109
+
110
+ case "documentCustomEvent": {
111
+ const { eventType, data } = eventData;
112
+ // TODO: validate model is valid for activity events
113
+ const model = docSchema[eventType];
114
+ if (model == null) {
115
+ return {
116
+ rawData: data,
117
+ rawType: eventType,
118
+ type: ActivityEventDataType.UNKNOWN,
119
+ };
120
+ }
121
+
122
+ // TODO: validate data against model schema
123
+ return {
124
+ data: data as ModelData<Model>,
125
+ eventType,
126
+ model,
127
+ type: ActivityEventDataType.CUSTOM_EVENT,
128
+ };
174
129
  }
175
130
 
176
- default:
177
- return undefined;
131
+ default: {
132
+ const unknownEventData = eventData as Record<string, unknown>;
133
+ return {
134
+ rawData: eventData,
135
+ rawType: typeof unknownEventData.type === "string" ? unknownEventData.type : "unknown",
136
+ type: ActivityEventDataType.UNKNOWN,
137
+ };
138
+ }
178
139
  }
179
140
  }
180
141