@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,162 @@
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 { ModuleConfigTuple, PackAppInternal } from "@palantir/pack.core";
18
+ import { generateId } from "@palantir/pack.core";
19
+ import type {
20
+ DocumentId,
21
+ DocumentMetadata,
22
+ DocumentRef,
23
+ DocumentSchema,
24
+ } from "@palantir/pack.document-schema.model-types";
25
+ import type * as Y from "yjs";
26
+ import { createDocumentServiceConfig } from "../DocumentServiceModule.js";
27
+ import { createDocRef } from "../types/DocumentRefImpl.js";
28
+ import type { DocumentService } from "../types/DocumentService.js";
29
+ import { DocumentLoadStatus } from "../types/DocumentService.js";
30
+ import type { InternalYjsDoc } from "./BaseYjsDocumentService.js";
31
+ import { BaseYjsDocumentService } from "./BaseYjsDocumentService.js";
32
+
33
+ export interface InMemoryDocumentServiceOptions {
34
+ /**
35
+ * Automatically create documents when referenced
36
+ * @default true
37
+ */
38
+ readonly autoCreateDocuments?: boolean;
39
+ }
40
+
41
+ export function createInMemoryDocumentServiceConfig(
42
+ { autoCreateDocuments = true }: InMemoryDocumentServiceOptions = {},
43
+ ): ModuleConfigTuple<DocumentService> {
44
+ const config: InMemoryDocumentServiceOptions = {
45
+ autoCreateDocuments,
46
+ };
47
+
48
+ return createDocumentServiceConfig(internalCreateInMemoryDocumentService, config);
49
+ }
50
+
51
+ export function internalCreateInMemoryDocumentService(
52
+ app: PackAppInternal,
53
+ options: InMemoryDocumentServiceOptions,
54
+ ): BaseYjsDocumentService {
55
+ return new InMemoryDocumentService(app, options);
56
+ }
57
+
58
+ class InMemoryDocumentService extends BaseYjsDocumentService {
59
+ constructor(
60
+ app: PackAppInternal,
61
+ readonly config: InMemoryDocumentServiceOptions,
62
+ ) {
63
+ super(
64
+ app,
65
+ app.config.logger.child({}, { level: "debug", msgPrefix: "InMemoryDocumentService" }),
66
+ );
67
+ }
68
+
69
+ override createInternalDoc(
70
+ ref: DocumentRef,
71
+ metadata?: DocumentMetadata,
72
+ yDoc?: Y.Doc,
73
+ ): InternalYjsDoc {
74
+ return this.createBaseInternalDoc(ref, metadata, yDoc);
75
+ }
76
+
77
+ get hasMetadataSubscriptions() {
78
+ return Array.from(this.documents.values()).some(doc =>
79
+ this.hasSubscriptions(doc) && doc.metadataSubscribers.size > 0
80
+ );
81
+ }
82
+
83
+ get hasStateSubscriptions() {
84
+ return Array.from(this.documents.values()).some(doc =>
85
+ this.hasSubscriptions(doc) && doc.docStateSubscribers.size > 0
86
+ );
87
+ }
88
+
89
+ readonly createDocument = <T extends DocumentSchema>(
90
+ metadata: DocumentMetadata,
91
+ schema: T,
92
+ ): Promise<DocumentRef<T>> => {
93
+ const id = generateDocumentId();
94
+ const docRef = createDocRef(this.app, id, schema);
95
+
96
+ const yDoc = this.initializeYDoc(schema);
97
+ this.getCreateInternalDoc(docRef, metadata, yDoc);
98
+
99
+ return Promise.resolve(docRef);
100
+ };
101
+
102
+ // Lifecycle method implementations
103
+ protected onMetadataSubscriptionOpened(
104
+ internalDoc: InternalYjsDoc,
105
+ docRef: DocumentRef,
106
+ ): void {
107
+ this.updateMetadataStatus(internalDoc, docRef, {
108
+ load: DocumentLoadStatus.LOADING,
109
+ });
110
+
111
+ if (this.config.autoCreateDocuments === false && internalDoc.metadata == null) {
112
+ this.updateMetadataStatus(internalDoc, docRef, {
113
+ error: new Error("Document not found and autoCreateDocuments is disabled"),
114
+ load: DocumentLoadStatus.ERROR,
115
+ });
116
+ return;
117
+ }
118
+
119
+ this.updateMetadataStatus(internalDoc, docRef, {
120
+ load: DocumentLoadStatus.LOADED,
121
+ });
122
+ }
123
+
124
+ protected onDataSubscriptionOpened(
125
+ internalDoc: InternalYjsDoc,
126
+ docRef: DocumentRef,
127
+ ): void {
128
+ this.updateDataStatus(internalDoc, docRef, {
129
+ load: DocumentLoadStatus.LOADING,
130
+ });
131
+
132
+ if (this.config.autoCreateDocuments === false && internalDoc.metadata == null) {
133
+ this.updateDataStatus(internalDoc, docRef, {
134
+ error: new Error("Document not found and autoCreateDocuments is disabled"),
135
+ load: DocumentLoadStatus.ERROR,
136
+ });
137
+ return;
138
+ }
139
+
140
+ this.updateDataStatus(internalDoc, docRef, {
141
+ load: DocumentLoadStatus.LOADED,
142
+ });
143
+ }
144
+
145
+ protected onMetadataSubscriptionClosed(
146
+ _internalDoc: InternalYjsDoc,
147
+ _docRef: DocumentRef,
148
+ ): void {
149
+ // No cleanup needed for in-memory service
150
+ }
151
+
152
+ protected onDataSubscriptionClosed(
153
+ _internalDoc: InternalYjsDoc,
154
+ _docRef: DocumentRef,
155
+ ): void {
156
+ // No cleanup needed for in-memory service
157
+ }
158
+ }
159
+
160
+ function generateDocumentId(): DocumentId {
161
+ return generateId() as DocumentId;
162
+ }
@@ -0,0 +1,194 @@
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 {
18
+ type DocumentSchema,
19
+ getMetadata,
20
+ type Model,
21
+ type ModelData,
22
+ type RecordId,
23
+ } from "@palantir/pack.document-schema.model-types";
24
+ import * as Y from "yjs";
25
+
26
+ export function initializeDocumentStructure(
27
+ yDoc: Y.Doc,
28
+ schema: DocumentSchema,
29
+ ): void {
30
+ // Initialize storage for each model type using storageName directly on the root Y.Doc
31
+ Object.values(schema).forEach(modelEntry => {
32
+ yDoc.getMap(getMetadata(modelEntry).name);
33
+ });
34
+ }
35
+
36
+ export function getRecordsMap(
37
+ yDoc: Y.Doc,
38
+ storageName: string,
39
+ ): Y.Map<unknown> {
40
+ return yDoc.getMap(storageName);
41
+ }
42
+
43
+ export function getRecordData(
44
+ yDoc: Y.Doc,
45
+ storageName: string,
46
+ recordId: RecordId,
47
+ ): Y.Map<unknown> | undefined {
48
+ const recordsCollection = getRecordsMap(yDoc, storageName);
49
+ return recordsCollection.get(recordId as string) as Y.Map<unknown> | undefined;
50
+ }
51
+
52
+ export function setRecord(
53
+ yDoc: Y.Doc,
54
+ storageName: string,
55
+ recordId: RecordId,
56
+ state: ModelData<Model>,
57
+ ): boolean {
58
+ const recordsCollection = getRecordsMap(yDoc, storageName);
59
+ const currentRecord = recordsCollection.get(recordId as string) as Y.Map<unknown> | undefined;
60
+ const wasExisting = currentRecord != null;
61
+
62
+ // Use transaction for atomic update
63
+ yDoc.transact(() => {
64
+ if (currentRecord != null) {
65
+ // Clear existing record completely
66
+ currentRecord.clear();
67
+ populateYMapFromState(currentRecord, state);
68
+ } else {
69
+ // Create new record
70
+ const newRecord = new Y.Map();
71
+ populateYMapFromState(newRecord, state);
72
+ recordsCollection.set(recordId as string, newRecord);
73
+ }
74
+ });
75
+
76
+ return wasExisting;
77
+ }
78
+
79
+ export function getRecordSnapshot(
80
+ yDoc: Y.Doc,
81
+ storageName: string,
82
+ recordId: RecordId,
83
+ ): unknown {
84
+ const data = getRecordData(yDoc, storageName, recordId);
85
+ if (!data) {
86
+ return undefined;
87
+ }
88
+
89
+ return yMapToState(data);
90
+ }
91
+
92
+ export function updateRecord(
93
+ yDoc: Y.Doc,
94
+ storageName: string,
95
+ recordId: RecordId,
96
+ partialState: Partial<ModelData<Model>>,
97
+ ): boolean {
98
+ const recordsCollection = getRecordsMap(yDoc, storageName);
99
+ const currentRecord = recordsCollection.get(recordId as string) as Y.Map<unknown> | undefined;
100
+
101
+ if (currentRecord == null) {
102
+ return false; // Record doesn't exist, cannot update
103
+ }
104
+
105
+ // Use transaction for atomic partial update
106
+ yDoc.transact(() => {
107
+ updateYMapFromPartialState(currentRecord, partialState);
108
+ });
109
+
110
+ return true;
111
+ }
112
+
113
+ export function getAllRecordIds(yDoc: Y.Doc, storageName: string): RecordId[] {
114
+ const recordsCollection = getRecordsMap(yDoc, storageName);
115
+ return Array.from(recordsCollection.keys()).map(key => key as RecordId);
116
+ }
117
+
118
+ function populateYMapFromState(
119
+ yMap: Y.Map<unknown>,
120
+ state: unknown,
121
+ ): void {
122
+ if (state != null && typeof state === "object") {
123
+ Object.entries(state).forEach(([key, value]) => {
124
+ if (value === undefined) return;
125
+
126
+ if (Array.isArray(value)) {
127
+ const yArray = new Y.Array();
128
+ value.forEach(item => {
129
+ yArray.push([item]);
130
+ });
131
+ yMap.set(key, yArray);
132
+ } else if (typeof value === "object" && value != null) {
133
+ const nestedMap = new Y.Map();
134
+ populateYMapFromState(nestedMap, value as Model);
135
+ yMap.set(key, nestedMap);
136
+ } else {
137
+ yMap.set(key, value);
138
+ }
139
+ });
140
+ }
141
+ }
142
+
143
+ function updateYMapFromPartialState(
144
+ yMap: Y.Map<unknown>,
145
+ partialState: Partial<unknown>,
146
+ ): void {
147
+ if (typeof partialState === "object") {
148
+ Object.entries(partialState).forEach(([key, value]) => {
149
+ if (value === undefined) {
150
+ // Remove the field if undefined is explicitly provided
151
+ yMap.delete(key);
152
+ return;
153
+ }
154
+
155
+ if (Array.isArray(value)) {
156
+ const yArray = new Y.Array();
157
+ value.forEach(item => {
158
+ yArray.push([item]);
159
+ });
160
+ yMap.set(key, yArray);
161
+ } else if (typeof value === "object" && value != null) {
162
+ // For nested objects, we need to handle partial updates
163
+ const existingValue = yMap.get(key);
164
+ if (existingValue instanceof Y.Map) {
165
+ // Recursively update nested Y.Map
166
+ updateYMapFromPartialState(existingValue, value as Partial<unknown>);
167
+ } else {
168
+ // Replace with new nested map if existing value is not a Y.Map
169
+ const nestedMap = new Y.Map();
170
+ populateYMapFromState(nestedMap, value as Model);
171
+ yMap.set(key, nestedMap);
172
+ }
173
+ } else {
174
+ yMap.set(key, value);
175
+ }
176
+ });
177
+ }
178
+ }
179
+
180
+ function yMapToState(yMap: Y.Map<unknown>): ModelData<Model> {
181
+ const state: Record<string, unknown> = {};
182
+
183
+ yMap.forEach((value, key) => {
184
+ if (value instanceof Y.Array) {
185
+ state[key] = value.toArray();
186
+ } else if (value instanceof Y.Map) {
187
+ state[key] = yMapToState(value);
188
+ } else {
189
+ state[key] = value;
190
+ }
191
+ });
192
+
193
+ return state as ModelData<Model>;
194
+ }
@@ -0,0 +1,98 @@
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
+ DocumentRef,
22
+ DocumentSchema,
23
+ DocumentState,
24
+ Model,
25
+ RecordCollectionRef,
26
+ Unsubscribe,
27
+ } from "@palantir/pack.document-schema.model-types";
28
+ import { DocumentRefBrand } from "@palantir/pack.document-schema.model-types";
29
+ import type { StateModuleImpl } from "./StateModule.js";
30
+ import { getStateModule } from "./StateModule.js";
31
+
32
+ const INVALID_DOC_REF_ID: DocumentId = "INVALID_DOC_REF";
33
+ const INVALID_DOC_REF: DocumentRef = Object.freeze(
34
+ {
35
+ id: INVALID_DOC_REF_ID,
36
+ schema: {} as DocumentSchema,
37
+ [DocumentRefBrand]: DocumentRefBrand,
38
+ getDocSnapshot: () => Promise.reject(new Error("Invalid document reference")),
39
+ getRecords: () => {
40
+ throw new Error("Invalid document reference");
41
+ },
42
+ onMetadataChange: () => () => {},
43
+ onStateChange: () => () => {},
44
+ } as const,
45
+ );
46
+
47
+ export const createDocRef = <const D extends DocumentSchema>(
48
+ app: PackAppInternal,
49
+ id: DocumentId,
50
+ schema: D,
51
+ ): DocumentRef<D> => {
52
+ return new DocumentRefImpl(app, id, schema);
53
+ };
54
+
55
+ export function invalidDocRef<D extends DocumentSchema = DocumentSchema>(): DocumentRef<D> {
56
+ return INVALID_DOC_REF as DocumentRef<D>;
57
+ }
58
+
59
+ export function isValidDocRef<D extends DocumentSchema = DocumentSchema>(
60
+ docRef: DocumentRef<D>,
61
+ ): docRef is DocumentRef<D> {
62
+ return docRef.id !== INVALID_DOC_REF_ID && docRef.id !== "";
63
+ }
64
+
65
+ class DocumentRefImpl<T extends DocumentSchema> implements DocumentRef<T> {
66
+ readonly id: DocumentId;
67
+ readonly schema: T;
68
+ declare readonly [DocumentRefBrand]: typeof DocumentRefBrand;
69
+ readonly #stateModule: StateModuleImpl;
70
+
71
+ constructor(app: PackAppInternal, id: DocumentId, schema: T) {
72
+ this.#stateModule = getStateModule(app) as StateModuleImpl;
73
+ this.id = id;
74
+ this.schema = schema;
75
+ }
76
+
77
+ async getDocSnapshot(): Promise<DocumentState<T>> {
78
+ return this.#stateModule.getDocumentSnapshot(this);
79
+ }
80
+
81
+ getRecords<R extends Model>(
82
+ model: R,
83
+ ): RecordCollectionRef<R> {
84
+ return this.#stateModule.getCreateRecordCollectionRef(this, model);
85
+ }
86
+
87
+ onMetadataChange(
88
+ cb: (docRef: DocumentRef<T>, metadata: DocumentMetadata) => void,
89
+ ): Unsubscribe {
90
+ return this.#stateModule.onMetadataChange(this, cb);
91
+ }
92
+
93
+ onStateChange(
94
+ callback: (docRef: DocumentRef<T>) => void,
95
+ ): Unsubscribe {
96
+ return this.#stateModule.onStateChange(this, callback);
97
+ }
98
+ }
@@ -0,0 +1,210 @@
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 { Unsubscribe } from "@palantir/pack.core";
18
+ import type {
19
+ DocumentId,
20
+ DocumentMetadata,
21
+ DocumentRef,
22
+ DocumentSchema,
23
+ DocumentState,
24
+ Model,
25
+ ModelData,
26
+ RecordCollectionRef,
27
+ RecordId,
28
+ RecordRef,
29
+ } from "@palantir/pack.document-schema.model-types";
30
+
31
+ export const DocumentLoadStatus = {
32
+ UNLOADED: "unloaded", // Not yet loaded
33
+ LOADING: "loading", // Initial load in progress
34
+ LOADED: "loaded", // Successfully loaded
35
+ ERROR: "error", // Load failed
36
+ } as const;
37
+ export type DocumentLoadStatus = typeof DocumentLoadStatus[keyof typeof DocumentLoadStatus];
38
+
39
+ export const DocumentLiveStatus = {
40
+ DISCONNECTED: "disconnected", // Not syncing
41
+ CONNECTING: "connecting", // Establishing connection
42
+ CONNECTED: "connected", // Live syncing active
43
+ ERROR: "error", // Connection error
44
+ } as const;
45
+ export type DocumentLiveStatus = typeof DocumentLiveStatus[keyof typeof DocumentLiveStatus];
46
+
47
+ export type DocumentSyncStatus = {
48
+ readonly error?: unknown;
49
+ readonly live: DocumentLiveStatus;
50
+ readonly load: DocumentLoadStatus;
51
+ };
52
+
53
+ export type DocumentStatus = {
54
+ readonly metadata: DocumentSyncStatus;
55
+ readonly data: DocumentSyncStatus;
56
+ readonly metadataError?: unknown;
57
+ readonly dataError?: unknown;
58
+ };
59
+
60
+ export type DocumentStatusChangeCallback = (
61
+ docRef: DocumentRef,
62
+ status: DocumentStatus,
63
+ ) => void;
64
+
65
+ export type DocumentMetadataChangeCallback<
66
+ T extends DocumentSchema = DocumentSchema,
67
+ > = (docRef: DocumentRef<T>, metadata: DocumentMetadata) => void;
68
+
69
+ export type DocumentStateChangeCallback<
70
+ T extends DocumentSchema = DocumentSchema,
71
+ > = (docRef: DocumentRef<T>) => void;
72
+
73
+ export type RecordCollectionChangeCallback<M extends Model = Model> = (
74
+ items: readonly RecordRef<M>[],
75
+ ) => void;
76
+
77
+ export type RecordChangeCallback<M extends Model = Model> = (
78
+ snapshot: ModelData<M>,
79
+ record: RecordRef<M>,
80
+ ) => void;
81
+
82
+ export type RecordDeleteCallback<M extends Model = Model> = (
83
+ record: RecordRef<M>,
84
+ ) => void;
85
+
86
+ export interface DocumentService {
87
+ readonly hasMetadataSubscriptions: boolean;
88
+ readonly hasStateSubscriptions: boolean;
89
+
90
+ readonly createDocument: <T extends DocumentSchema>(
91
+ metadata: DocumentMetadata,
92
+ schema: T,
93
+ ) => Promise<DocumentRef<T>>;
94
+
95
+ readonly createDocRef: <const T extends DocumentSchema>(
96
+ id: DocumentId,
97
+ schema: T,
98
+ ) => DocumentRef<T>;
99
+
100
+ readonly getCreateRecordCollectionRef: <const M extends Model>(
101
+ docRef: DocumentRef,
102
+ model: M,
103
+ ) => RecordCollectionRef<M>;
104
+
105
+ readonly getCreateRecordRef: <const M extends Model>(
106
+ docRef: DocumentRef,
107
+ id: RecordId,
108
+ model: M,
109
+ ) => RecordRef<M>;
110
+
111
+ readonly getDocumentSnapshot: <T extends DocumentSchema>(
112
+ docRef: DocumentRef<T>,
113
+ ) => Promise<DocumentState<T>>;
114
+
115
+ readonly getRecordSnapshot: <R extends Model>(
116
+ record: RecordRef<R>,
117
+ ) => Promise<ModelData<R>>;
118
+
119
+ readonly setRecord: <R extends Model>(
120
+ record: RecordRef<R>,
121
+ state: ModelData<R>,
122
+ ) => Promise<void>;
123
+
124
+ readonly updateRecord: <R extends Model>(
125
+ record: RecordRef<R>,
126
+ partialState: Partial<ModelData<R>>,
127
+ ) => Promise<void>;
128
+
129
+ // Collection methods
130
+ readonly getRecord: <M extends Model>(
131
+ collection: RecordCollectionRef<M>,
132
+ id: RecordId,
133
+ ) => RecordRef<M> | undefined;
134
+
135
+ readonly hasRecord: <M extends Model>(
136
+ collection: RecordCollectionRef<M>,
137
+ id: RecordId,
138
+ ) => boolean;
139
+
140
+ readonly setCollectionRecord: <M extends Model>(
141
+ collection: RecordCollectionRef<M>,
142
+ id: RecordId,
143
+ state: ModelData<M>,
144
+ ) => Promise<void>;
145
+
146
+ readonly deleteRecord: <M extends Model>(
147
+ record: RecordRef<M>,
148
+ ) => Promise<void>;
149
+
150
+ readonly getCollectionSize: <M extends Model>(
151
+ collection: RecordCollectionRef<M>,
152
+ ) => number;
153
+
154
+ readonly getCollectionRecords: <M extends Model>(
155
+ collection: RecordCollectionRef<M>,
156
+ ) => RecordRef<M>[];
157
+
158
+ readonly onCollectionItemsAdded: <M extends Model>(
159
+ collection: RecordCollectionRef<M>,
160
+ callback: RecordCollectionChangeCallback<M>,
161
+ ) => Unsubscribe;
162
+
163
+ readonly onCollectionItemsChanged: <M extends Model>(
164
+ collection: RecordCollectionRef<M>,
165
+ callback: RecordCollectionChangeCallback<M>,
166
+ ) => Unsubscribe;
167
+
168
+ readonly onCollectionItemsDeleted: <M extends Model>(
169
+ collection: RecordCollectionRef<M>,
170
+ callback: RecordCollectionChangeCallback<M>,
171
+ ) => Unsubscribe;
172
+
173
+ readonly onMetadataChange: <T extends DocumentSchema>(
174
+ docRef: DocumentRef<T>,
175
+ callback: DocumentMetadataChangeCallback<T>,
176
+ ) => Unsubscribe;
177
+
178
+ readonly onStateChange: <T extends DocumentSchema>(
179
+ docRef: DocumentRef<T>,
180
+ callback: DocumentStateChangeCallback<T>,
181
+ ) => Unsubscribe;
182
+
183
+ readonly onRecordChanged: <M extends Model>(
184
+ record: RecordRef<M>,
185
+ callback: RecordChangeCallback<M>,
186
+ ) => Unsubscribe;
187
+
188
+ readonly onRecordDeleted: <M extends Model>(
189
+ record: RecordRef<M>,
190
+ callback: RecordDeleteCallback<M>,
191
+ ) => Unsubscribe;
192
+
193
+ // Status methods
194
+ readonly getDocumentStatus: <T extends DocumentSchema>(
195
+ docRef: DocumentRef<T>,
196
+ ) => DocumentStatus;
197
+
198
+ readonly onStatusChange: <T extends DocumentSchema>(
199
+ docRef: DocumentRef<T>,
200
+ callback: DocumentStatusChangeCallback,
201
+ ) => Unsubscribe;
202
+
203
+ readonly waitForMetadataLoad: <T extends DocumentSchema>(
204
+ docRef: DocumentRef<T>,
205
+ ) => Promise<void>;
206
+
207
+ readonly waitForDataLoad: <T extends DocumentSchema>(
208
+ docRef: DocumentRef<T>,
209
+ ) => Promise<void>;
210
+ }
@@ -0,0 +1,22 @@
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 { DocumentService } from "./DocumentService.js";
19
+
20
+ export type WithDocumentServiceInit = {
21
+ readonly init: (app: PackAppInternal, config: unknown) => DocumentService;
22
+ };