@palantir/pack.state.foundry 0.9.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
+ });
@@ -29,7 +29,6 @@ import type {
29
29
  ActivityEventDataDocumentMandatorySecurityUpdate,
30
30
  ActivityEventDataDocumentRename,
31
31
  DocumentSchema,
32
- DocumentSecurityDiscretionary,
33
32
  Model,
34
33
  ModelData,
35
34
  PresenceEvent,
@@ -64,136 +63,79 @@ export function getActivityEvent(
64
63
  };
65
64
  }
66
65
 
67
- /**
68
- * Platform event type names as sent by backpack.
69
- * Note: Only DOCUMENT_CREATE is currently emitted by the backend.
70
- * Remaining types are included for future use.
71
- */
72
- const PlatformEventType = {
73
- DOCUMENT_CREATE: "DocumentCreateEvent",
74
- DOCUMENT_DESCRIPTION_UPDATE: "DocumentDescriptionUpdateEvent",
75
- DOCUMENT_RENAME: "DocumentRenameEvent",
76
- DOCUMENT_MANDATORY_SECURITY_UPDATE: "DocumentMandatorySecurityUpdateEvent",
77
- DOCUMENT_DISCRETIONARY_SECURITY_UPDATE: "DocumentDiscretionarySecurityUpdateEvent",
78
- } as const;
79
-
80
- interface WireDocumentCreateEvent {
81
- readonly name?: string;
82
- readonly initialMandatorySecurity?: {
83
- readonly classification?: readonly string[];
84
- readonly markings?: readonly string[];
85
- };
86
- }
87
-
88
- interface WireDocumentRenameEvent {
89
- readonly previousName?: string;
90
- readonly newName?: string;
91
- }
92
-
93
- interface WireDocumentDescriptionUpdateEvent {
94
- readonly newDescription?: string;
95
- readonly isInitial?: boolean;
96
- }
97
-
98
- interface WireDocumentMandatorySecurityUpdateEvent {
99
- readonly newClassification?: readonly string[];
100
- readonly newMarkings?: readonly string[];
101
- }
102
-
103
- interface WireDocumentDiscretionarySecurityUpdateEvent {
104
- readonly principalType: "ALL_PRINCIPAL" | "USER";
105
- readonly previousDiscretionarySecurity?: DocumentSecurityDiscretionary;
106
- readonly newDiscretionarySecurity: DocumentSecurityDiscretionary;
107
- }
108
-
109
66
  function getActivityEventData(
110
67
  docSchema: DocumentSchema,
111
- { eventData, eventType }: FoundryActivityEvent,
68
+ { eventData }: FoundryActivityEvent,
112
69
  ): ActivityEventData {
113
- const platformEventData = getPlatformActivityEventData(
114
- eventType,
115
- eventData.data,
116
- );
117
- if (platformEventData != null) {
118
- return platformEventData;
119
- }
120
-
121
- // Handle custom application-defined activity events
122
- // TODO: validate model is valid for activity events
123
- const model = docSchema[eventType];
124
- if (model == null) {
125
- return {
126
- rawData: eventData.data,
127
- rawType: eventType,
128
- type: ActivityEventDataType.UNKNOWN,
129
- };
130
- }
131
-
132
- // TODO: validate data against model schema
133
-
134
- return {
135
- eventData: eventData.data as ModelData<Model>,
136
- model,
137
- type: ActivityEventDataType.CUSTOM_EVENT,
138
- };
139
- }
140
-
141
- function getPlatformActivityEventData(
142
- eventType: string,
143
- data: unknown,
144
- ): ActivityEventData | undefined {
145
- switch (eventType) {
146
- case PlatformEventType.DOCUMENT_CREATE: {
147
- const wireData = data as WireDocumentCreateEvent;
70
+ switch (eventData.type) {
71
+ case "documentCreate":
148
72
  return {
149
73
  initialMandatorySecurity: {
150
- classification: wireData.initialMandatorySecurity?.classification,
151
- markings: wireData.initialMandatorySecurity?.markings,
74
+ classification: eventData.initialMandatorySecurity?.classification,
75
+ markings: eventData.initialMandatorySecurity?.markings,
152
76
  },
153
- name: wireData.name ?? "",
77
+ name: eventData.name ?? "",
154
78
  type: ActivityEventDataType.DOCUMENT_CREATE,
155
79
  } satisfies ActivityEventDataDocumentCreate;
156
- }
157
80
 
158
- case PlatformEventType.DOCUMENT_RENAME: {
159
- const wireData = data as WireDocumentRenameEvent;
81
+ case "documentRename":
160
82
  return {
161
- newName: wireData.newName ?? "",
162
- previousName: wireData.previousName ?? "",
83
+ newName: eventData.newName ?? "",
84
+ previousName: eventData.previousName ?? "",
163
85
  type: ActivityEventDataType.DOCUMENT_RENAME,
164
86
  } satisfies ActivityEventDataDocumentRename;
165
- }
166
87
 
167
- case PlatformEventType.DOCUMENT_DESCRIPTION_UPDATE: {
168
- const wireData = data as WireDocumentDescriptionUpdateEvent;
88
+ case "documentDescriptionUpdate":
169
89
  return {
170
- isInitial: wireData.isInitial ?? false,
171
- newDescription: wireData.newDescription ?? "",
90
+ isInitial: eventData.isInitial ?? false,
91
+ newDescription: eventData.newDescription ?? "",
172
92
  type: ActivityEventDataType.DOCUMENT_DESCRIPTION_UPDATE,
173
93
  } satisfies ActivityEventDataDocumentDescriptionUpdate;
174
- }
175
94
 
176
- case PlatformEventType.DOCUMENT_MANDATORY_SECURITY_UPDATE: {
177
- const wireData = data as WireDocumentMandatorySecurityUpdateEvent;
95
+ case "documentMandatorySecurityUpdate":
178
96
  return {
179
- newClassification: wireData.newClassification ?? [],
180
- newMarkings: wireData.newMarkings ?? [],
97
+ newClassification: eventData.newClassification ?? [],
98
+ newMarkings: eventData.newMarkings ?? [],
181
99
  type: ActivityEventDataType.DOCUMENT_MANDATORY_SECURITY_UPDATE,
182
100
  } satisfies ActivityEventDataDocumentMandatorySecurityUpdate;
183
- }
184
101
 
185
- case PlatformEventType.DOCUMENT_DISCRETIONARY_SECURITY_UPDATE: {
186
- const wireData = data as WireDocumentDiscretionarySecurityUpdateEvent;
102
+ case "documentDiscretionarySecurityUpdate":
187
103
  return {
188
- principalType: wireData.principalType ?? "",
189
- previousDiscretionarySecurity: wireData.previousDiscretionarySecurity,
190
- newDiscretionarySecurity: wireData.newDiscretionarySecurity ?? [],
104
+ principalType: eventData.principalType ?? "",
105
+ previousDiscretionarySecurity: eventData.previousDiscretionarySecurity,
106
+ newDiscretionarySecurity: eventData.newDiscretionarySecurity ?? [],
191
107
  type: ActivityEventDataType.DOCUMENT_DISCRETIONARY_SECURITY_UPDATE,
192
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
+ };
193
129
  }
194
130
 
195
- default:
196
- 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
+ }
197
139
  }
198
140
  }
199
141