@palantir/pack.state.core 0.0.1-beta.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.
Files changed (63) hide show
  1. package/.turbo/turbo-lint.log +4 -0
  2. package/.turbo/turbo-transpileBrowser.log +5 -0
  3. package/.turbo/turbo-transpileCjs.log +5 -0
  4. package/.turbo/turbo-transpileEsm.log +5 -0
  5. package/.turbo/turbo-transpileTypes.log +5 -0
  6. package/.turbo/turbo-typecheck.log +4 -0
  7. package/LICENSE.txt +13 -0
  8. package/README.md +55 -0
  9. package/build/browser/index.js +1257 -0
  10. package/build/browser/index.js.map +1 -0
  11. package/build/cjs/index.cjs +1298 -0
  12. package/build/cjs/index.cjs.map +1 -0
  13. package/build/cjs/index.d.cts +272 -0
  14. package/build/esm/index.js +1257 -0
  15. package/build/esm/index.js.map +1 -0
  16. package/build/types/DocumentServiceModule.d.ts +6 -0
  17. package/build/types/DocumentServiceModule.d.ts.map +1 -0
  18. package/build/types/__tests__/DocumentStatusTracking.test.d.ts +1 -0
  19. package/build/types/__tests__/DocumentStatusTracking.test.d.ts.map +1 -0
  20. package/build/types/__tests__/RefStability.test.d.ts +1 -0
  21. package/build/types/__tests__/RefStability.test.d.ts.map +1 -0
  22. package/build/types/__tests__/StateModule.integration.test.d.ts +1 -0
  23. package/build/types/__tests__/StateModule.integration.test.d.ts.map +1 -0
  24. package/build/types/__tests__/testUtils.d.ts +7 -0
  25. package/build/types/__tests__/testUtils.d.ts.map +1 -0
  26. package/build/types/index.d.ts +11 -0
  27. package/build/types/index.d.ts.map +1 -0
  28. package/build/types/service/BaseYjsDocumentService.d.ts +155 -0
  29. package/build/types/service/BaseYjsDocumentService.d.ts.map +1 -0
  30. package/build/types/service/InMemoryDocumentService.d.ts +12 -0
  31. package/build/types/service/InMemoryDocumentService.d.ts.map +1 -0
  32. package/build/types/service/YjsSchemaMapper.d.ts +9 -0
  33. package/build/types/service/YjsSchemaMapper.d.ts.map +1 -0
  34. package/build/types/types/DocumentRefImpl.d.ts +5 -0
  35. package/build/types/types/DocumentRefImpl.d.ts.map +1 -0
  36. package/build/types/types/DocumentService.d.ts +62 -0
  37. package/build/types/types/DocumentService.d.ts.map +1 -0
  38. package/build/types/types/DocumentServiceConfig.d.ts +5 -0
  39. package/build/types/types/DocumentServiceConfig.d.ts.map +1 -0
  40. package/build/types/types/RecordCollectionRefImpl.d.ts +5 -0
  41. package/build/types/types/RecordCollectionRefImpl.d.ts.map +1 -0
  42. package/build/types/types/RecordRefImpl.d.ts +5 -0
  43. package/build/types/types/RecordRefImpl.d.ts.map +1 -0
  44. package/build/types/types/StateModule.d.ts +59 -0
  45. package/build/types/types/StateModule.d.ts.map +1 -0
  46. package/package.json +71 -0
  47. package/src/DocumentServiceModule.ts +53 -0
  48. package/src/__tests__/DocumentStatusTracking.test.ts +229 -0
  49. package/src/__tests__/RefStability.test.ts +441 -0
  50. package/src/__tests__/StateModule.integration.test.ts +1187 -0
  51. package/src/__tests__/testUtils.ts +106 -0
  52. package/src/index.ts +38 -0
  53. package/src/service/BaseYjsDocumentService.ts +1277 -0
  54. package/src/service/InMemoryDocumentService.ts +162 -0
  55. package/src/service/YjsSchemaMapper.ts +194 -0
  56. package/src/types/DocumentRefImpl.ts +98 -0
  57. package/src/types/DocumentService.ts +210 -0
  58. package/src/types/DocumentServiceConfig.ts +22 -0
  59. package/src/types/RecordCollectionRefImpl.ts +124 -0
  60. package/src/types/RecordRefImpl.ts +106 -0
  61. package/src/types/StateModule.ts +329 -0
  62. package/tsconfig.json +21 -0
  63. package/vitest.config.mjs +26 -0
@@ -0,0 +1,441 @@
1
+ /*
2
+ * Copyright 2025 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 { PackAppInternal } from "@palantir/pack.core";
18
+ import type {
19
+ DocumentId,
20
+ DocumentMetadata,
21
+ DocumentSchema,
22
+ Model,
23
+ RecordId,
24
+ } from "@palantir/pack.document-schema.model-types";
25
+ import { Metadata } from "@palantir/pack.document-schema.model-types";
26
+ import { beforeEach, describe, expect, it } from "vitest";
27
+ import { z } from "zod";
28
+ import { createDocRef, invalidDocRef, isValidDocRef } from "../types/DocumentRefImpl.js";
29
+ import {
30
+ invalidRecordCollectionRef,
31
+ isValidRecordCollectionRef,
32
+ } from "../types/RecordCollectionRefImpl.js";
33
+ import { invalidRecordRef, isValidRecordRef } from "../types/RecordRefImpl.js";
34
+ import { getStateModule } from "../types/StateModule.js";
35
+ import { createTestApp } from "./testUtils.js";
36
+
37
+ const TEST_SECURITY = {
38
+ mandatory: {
39
+ classification: ["MU"],
40
+ markings: [],
41
+ },
42
+ discretionary: {
43
+ owners: [],
44
+ editors: [],
45
+ viewers: [],
46
+ },
47
+ };
48
+
49
+ // Test schema with User and Post models
50
+ const createTestSchema = () => {
51
+ const userSchema = z.object({
52
+ id: z.string(),
53
+ name: z.string(),
54
+ email: z.string(),
55
+ });
56
+
57
+ const postSchema = z.object({
58
+ id: z.string(),
59
+ title: z.string(),
60
+ content: z.string(),
61
+ authorId: z.string(),
62
+ });
63
+
64
+ type User = { id: string; name: string; email: string };
65
+ type Post = { id: string; title: string; content: string; authorId: string };
66
+
67
+ const User: Model<User, typeof userSchema> = {
68
+ __type: {} as User,
69
+ zodSchema: userSchema,
70
+ [Metadata]: { name: "User" },
71
+ };
72
+
73
+ const Post: Model<Post, typeof postSchema> = {
74
+ __type: {} as Post,
75
+ zodSchema: postSchema,
76
+ [Metadata]: { name: "Post" },
77
+ };
78
+
79
+ const schema: DocumentSchema = {
80
+ [Metadata]: { version: 1 },
81
+ User,
82
+ Post,
83
+ };
84
+
85
+ return { schema, User, Post };
86
+ };
87
+
88
+ describe("Ref Stability Tests", () => {
89
+ let app: PackAppInternal;
90
+ const { schema, User, Post } = createTestSchema();
91
+
92
+ beforeEach(() => {
93
+ app = createTestApp();
94
+ });
95
+
96
+ describe("DocumentRef Stability", () => {
97
+ it("should return the same DocumentRef instance for identical parameters", () => {
98
+ const stateModule = getStateModule(app);
99
+ const docId = "test-doc-1" as DocumentId;
100
+
101
+ // Create multiple refs with same parameters
102
+ const ref1 = stateModule.createDocRef(docId, schema);
103
+ const ref2 = stateModule.createDocRef(docId, schema);
104
+ const ref3 = stateModule.createDocRef(docId, schema);
105
+
106
+ // Should be the exact same instance (reference equality)
107
+ expect(ref1).toBe(ref2);
108
+ expect(ref2).toBe(ref3);
109
+ expect(ref1).toBe(ref3);
110
+ });
111
+
112
+ it("should return different instances for different document IDs", () => {
113
+ const stateModule = getStateModule(app);
114
+ const docId1 = "test-doc-1" as DocumentId;
115
+ const docId2 = "test-doc-2" as DocumentId;
116
+
117
+ const ref1 = stateModule.createDocRef(docId1, schema);
118
+ const ref2 = stateModule.createDocRef(docId2, schema);
119
+
120
+ // Should be different instances
121
+ expect(ref1).not.toBe(ref2);
122
+ expect(ref1.id).toBe(docId1);
123
+ expect(ref2.id).toBe(docId2);
124
+ });
125
+
126
+ it("should work with invalid refs", () => {
127
+ const invalidRef = invalidDocRef();
128
+ expect(isValidDocRef(invalidRef)).toBe(false);
129
+
130
+ const validRef = createDocRef(app, "valid-doc" as DocumentId, schema);
131
+ expect(isValidDocRef(validRef)).toBe(true);
132
+ });
133
+ });
134
+
135
+ describe("RecordCollectionRef Stability", () => {
136
+ it("should return the same RecordCollectionRef instance for identical parameters", async () => {
137
+ const stateModule = getStateModule(app);
138
+
139
+ // Create a document first
140
+ const metadata: DocumentMetadata = {
141
+ name: "Test Document",
142
+ documentTypeName: "TestType",
143
+ ontologyRid: "test-ontology-rid",
144
+ security: TEST_SECURITY,
145
+ };
146
+ const docRef = await stateModule.createDocument(metadata, schema);
147
+
148
+ // Get collection refs multiple times
149
+ const collection1 = docRef.getRecords(User);
150
+ const collection2 = docRef.getRecords(User);
151
+ const collection3 = docRef.getRecords(User);
152
+
153
+ // Should be the exact same instance (reference equality)
154
+ expect(collection1).toBe(collection2);
155
+ expect(collection2).toBe(collection3);
156
+ expect(collection1).toBe(collection3);
157
+ });
158
+
159
+ it("should return different instances for different models", async () => {
160
+ const stateModule = getStateModule(app);
161
+
162
+ const metadata: DocumentMetadata = {
163
+ name: "Test Document",
164
+ documentTypeName: "TestType",
165
+ ontologyRid: "test-ontology-rid",
166
+ security: TEST_SECURITY,
167
+ };
168
+ const docRef = await stateModule.createDocument(metadata, schema);
169
+
170
+ const userCollection = docRef.getRecords(User);
171
+ const postCollection = docRef.getRecords(Post);
172
+
173
+ // Should be different instances
174
+ expect(userCollection).not.toBe(postCollection);
175
+ expect(userCollection.model).toBe(User);
176
+ expect(postCollection.model).toBe(Post);
177
+ });
178
+
179
+ it("should return different instances for different documents", async () => {
180
+ const stateModule = getStateModule(app);
181
+
182
+ const metadata1: DocumentMetadata = {
183
+ name: "Test Document 1",
184
+ documentTypeName: "TestType",
185
+ ontologyRid: "test-ontology-rid",
186
+ security: TEST_SECURITY,
187
+ };
188
+ const metadata2: DocumentMetadata = {
189
+ name: "Test Document 2",
190
+ documentTypeName: "TestType",
191
+ ontologyRid: "test-ontology-rid",
192
+ security: TEST_SECURITY,
193
+ };
194
+
195
+ const docRef1 = await stateModule.createDocument(metadata1, schema);
196
+ const docRef2 = await stateModule.createDocument(metadata2, schema);
197
+
198
+ const collection1 = docRef1.getRecords(User);
199
+ const collection2 = docRef2.getRecords(User);
200
+
201
+ // Should be different instances
202
+ expect(collection1).not.toBe(collection2);
203
+ expect(collection1.docRef).toBe(docRef1);
204
+ expect(collection2.docRef).toBe(docRef2);
205
+ });
206
+
207
+ it("should work with invalid refs", async () => {
208
+ const invalidRef = invalidRecordCollectionRef();
209
+ expect(isValidRecordCollectionRef(invalidRef)).toBe(false);
210
+
211
+ // All methods should be safe to call
212
+ expect(invalidRef.get("any-id" as RecordId)).toBeUndefined();
213
+ expect(invalidRef.has("any-id" as RecordId)).toBe(false);
214
+ expect(invalidRef.size).toBe(0);
215
+
216
+ // Async methods should reject
217
+ await expect(invalidRef.set("id" as RecordId, {})).rejects.toThrow();
218
+ await expect(invalidRef.delete("id" as RecordId)).rejects.toThrow();
219
+
220
+ // Iterator should be empty
221
+ const iterator = invalidRef[Symbol.iterator]();
222
+ const result = iterator.next();
223
+ expect(result.done).toBe(true);
224
+ });
225
+ });
226
+
227
+ describe("RecordRef Stability", () => {
228
+ it("should return the same RecordRef instance for identical parameters", async () => {
229
+ const stateModule = getStateModule(app);
230
+
231
+ const metadata: DocumentMetadata = {
232
+ name: "Test Document",
233
+ documentTypeName: "TestType",
234
+ ontologyRid: "test-ontology-rid",
235
+ security: TEST_SECURITY,
236
+ };
237
+ const docRef = await stateModule.createDocument(metadata, schema);
238
+ const userCollection = docRef.getRecords(User);
239
+ const userId = "user-1" as RecordId;
240
+
241
+ // Create the record first
242
+ await userCollection.set(userId, {
243
+ id: "user-1",
244
+ name: "Test User",
245
+ email: "test@example.com",
246
+ });
247
+
248
+ // Get record refs multiple times
249
+ const record1 = userCollection.get(userId);
250
+ const record2 = userCollection.get(userId);
251
+ const record3 = userCollection.get(userId);
252
+
253
+ // Should be the exact same instance (reference equality)
254
+ expect(record1).toBe(record2);
255
+ expect(record2).toBe(record3);
256
+ expect(record1).toBe(record3);
257
+ });
258
+
259
+ it("should return stable refs even for non-existent records", async () => {
260
+ const stateModule = getStateModule(app);
261
+
262
+ const metadata: DocumentMetadata = {
263
+ name: "Test Document",
264
+ documentTypeName: "TestType",
265
+ ontologyRid: "test-ontology-rid",
266
+ security: TEST_SECURITY,
267
+ };
268
+ const docRef = await stateModule.createDocument(metadata, schema);
269
+
270
+ const nonExistentId = "non-existent" as RecordId;
271
+
272
+ const ref1 = stateModule.createRecordRef(docRef, nonExistentId, User);
273
+ const ref2 = stateModule.createRecordRef(docRef, nonExistentId, User);
274
+
275
+ expect(ref1).toBe(ref2);
276
+ });
277
+
278
+ it("should return different instances for different record IDs", async () => {
279
+ const stateModule = getStateModule(app);
280
+
281
+ const metadata: DocumentMetadata = {
282
+ name: "Test Document",
283
+ documentTypeName: "TestType",
284
+ ontologyRid: "test-ontology-rid",
285
+ security: TEST_SECURITY,
286
+ };
287
+ const docRef = await stateModule.createDocument(metadata, schema);
288
+ const userCollection = docRef.getRecords(User);
289
+
290
+ const userId1 = "user-1" as RecordId;
291
+ const userId2 = "user-2" as RecordId;
292
+
293
+ // Create both records
294
+ await userCollection.set(userId1, {
295
+ id: "user-1",
296
+ name: "User 1",
297
+ email: "user1@example.com",
298
+ });
299
+ await userCollection.set(userId2, {
300
+ id: "user-2",
301
+ name: "User 2",
302
+ email: "user2@example.com",
303
+ });
304
+
305
+ const record1 = userCollection.get(userId1);
306
+ const record2 = userCollection.get(userId2);
307
+
308
+ // Should be different instances
309
+ expect(record1).not.toBe(record2);
310
+ expect(record1?.id).toBe(userId1);
311
+ expect(record2?.id).toBe(userId2);
312
+ });
313
+
314
+ it("should return different instances for different models", async () => {
315
+ const stateModule = getStateModule(app);
316
+
317
+ const metadata: DocumentMetadata = {
318
+ name: "Test Document",
319
+ documentTypeName: "TestType",
320
+ ontologyRid: "test-ontology-rid",
321
+ security: TEST_SECURITY,
322
+ };
323
+ const docRef = await stateModule.createDocument(metadata, schema);
324
+
325
+ const userCollection = docRef.getRecords(User);
326
+ const postCollection = docRef.getRecords(Post);
327
+
328
+ const recordId = "same-id" as RecordId;
329
+
330
+ // Create records with same ID but different models
331
+ await userCollection.set(recordId, {
332
+ id: "same-id",
333
+ name: "Test User",
334
+ email: "test@example.com",
335
+ });
336
+ await postCollection.set(recordId, {
337
+ id: "same-id",
338
+ title: "Test Post",
339
+ content: "Test content",
340
+ authorId: "user-1",
341
+ });
342
+
343
+ const userRecord = userCollection.get(recordId);
344
+ const postRecord = postCollection.get(recordId);
345
+
346
+ // Should be different instances
347
+ expect(userRecord).not.toBe(postRecord);
348
+ expect(userRecord?.model).toBe(User);
349
+ expect(postRecord?.model).toBe(Post);
350
+ });
351
+
352
+ it("should work with invalid refs", async () => {
353
+ const invalidRef = invalidRecordRef();
354
+ expect(isValidRecordRef(invalidRef)).toBe(false);
355
+
356
+ // Async methods should reject
357
+ await expect(invalidRef.getSnapshot()).rejects.toThrow();
358
+ await expect(invalidRef.set({})).rejects.toThrow();
359
+
360
+ // Subscription methods should be safe
361
+ const unsubscribe1 = invalidRef.onChange(() => {});
362
+ const unsubscribe2 = invalidRef.onDeleted(() => {});
363
+
364
+ expect(typeof unsubscribe1).toBe("function");
365
+ expect(typeof unsubscribe2).toBe("function");
366
+
367
+ // Should be safe to call
368
+ unsubscribe1();
369
+ unsubscribe2();
370
+ });
371
+ });
372
+
373
+ describe("Cross-reference Stability", () => {
374
+ it("should maintain stable refs across collection iterations", async () => {
375
+ const stateModule = getStateModule(app);
376
+
377
+ const metadata: DocumentMetadata = {
378
+ name: "Test Document",
379
+ documentTypeName: "TestType",
380
+ ontologyRid: "test-ontology-rid",
381
+ security: TEST_SECURITY,
382
+ };
383
+ const docRef = await stateModule.createDocument(metadata, schema);
384
+ const userCollection = docRef.getRecords(User);
385
+
386
+ // Create multiple users
387
+ const userIds = ["user-1", "user-2", "user-3"] as RecordId[];
388
+ for (const userId of userIds) {
389
+ await userCollection.set(userId, {
390
+ id: userId,
391
+ name: `User ${userId}`,
392
+ email: `${userId}@example.com`,
393
+ });
394
+ }
395
+
396
+ // Get refs through iteration
397
+ const iterationRefs = Array.from(userCollection);
398
+
399
+ // Get refs through direct access
400
+ const directRefs = userIds.map(id => userCollection.get(id)!);
401
+
402
+ // Should be the same instances
403
+ expect(iterationRefs).toHaveLength(3);
404
+ expect(directRefs).toHaveLength(3);
405
+
406
+ for (let i = 0; i < userIds.length; i++) {
407
+ const iterationRef = iterationRefs.find(ref => ref.id === userIds[i]);
408
+ const directRef = directRefs[i];
409
+
410
+ expect(iterationRef).toBeDefined();
411
+ expect(iterationRef).toBe(directRef);
412
+ }
413
+ });
414
+
415
+ it("should maintain stable document refs when accessed through different paths", async () => {
416
+ const stateModule = getStateModule(app);
417
+
418
+ const metadata: DocumentMetadata = {
419
+ name: "Test Document",
420
+ documentTypeName: "TestType",
421
+ ontologyRid: "test-ontology-rid",
422
+ security: TEST_SECURITY,
423
+ };
424
+
425
+ // Create doc through state module
426
+ const docRef1 = await stateModule.createDocument(metadata, schema);
427
+
428
+ // Get doc through createDocRef using the actual document ID
429
+ const docRef2 = stateModule.createDocRef(docRef1.id, schema);
430
+
431
+ // Get doc through collection's docRef
432
+ const collection = docRef1.getRecords(User);
433
+ const docRef3 = collection.docRef;
434
+
435
+ // Should be the same instances
436
+ expect(docRef1).toBe(docRef2);
437
+ expect(docRef2).toBe(docRef3);
438
+ expect(docRef1).toBe(docRef3);
439
+ });
440
+ });
441
+ });