@palantir/pack.document-schema.model-types 0.1.0-beta.3 → 0.1.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 (49) hide show
  1. package/.turbo/turbo-lint.log +2 -2
  2. package/.turbo/turbo-transpileBrowser.log +1 -1
  3. package/.turbo/turbo-transpileCjs.log +1 -1
  4. package/.turbo/turbo-transpileEsm.log +1 -1
  5. package/.turbo/turbo-transpileTypes.log +1 -1
  6. package/.turbo/turbo-typecheck.log +1 -1
  7. package/CHANGELOG.md +27 -0
  8. package/build/browser/index.js +29 -5
  9. package/build/browser/index.js.map +1 -1
  10. package/build/cjs/index.cjs +30 -4
  11. package/build/cjs/index.cjs.map +1 -1
  12. package/build/cjs/index.d.cts +517 -39
  13. package/build/esm/index.js +29 -5
  14. package/build/esm/index.js.map +1 -1
  15. package/build/types/__tests__/Metadata.test.d.ts +1 -0
  16. package/build/types/__tests__/Metadata.test.d.ts.map +1 -0
  17. package/build/types/index.d.ts +6 -2
  18. package/build/types/index.d.ts.map +1 -1
  19. package/build/types/types/ActivityEvent.d.ts +64 -0
  20. package/build/types/types/ActivityEvent.d.ts.map +1 -0
  21. package/build/types/types/DocumentRef.d.ts +199 -5
  22. package/build/types/types/DocumentRef.d.ts.map +1 -1
  23. package/build/types/types/DocumentSchema.d.ts +6 -0
  24. package/build/types/types/DocumentSchema.d.ts.map +1 -1
  25. package/build/types/types/Model.d.ts +13 -0
  26. package/build/types/types/Model.d.ts.map +1 -1
  27. package/build/types/types/PresenceEvent.d.ts +73 -0
  28. package/build/types/types/PresenceEvent.d.ts.map +1 -0
  29. package/build/types/types/RecordCollectionRef.d.ts +54 -0
  30. package/build/types/types/RecordCollectionRef.d.ts.map +1 -1
  31. package/build/types/types/RecordRef.d.ts +71 -0
  32. package/build/types/types/RecordRef.d.ts.map +1 -1
  33. package/build/types/types/Unsubscribe.d.ts +3 -0
  34. package/build/types/types/Unsubscribe.d.ts.map +1 -1
  35. package/build/types/types/UserRef.d.ts +5 -0
  36. package/build/types/types/UserRef.d.ts.map +1 -1
  37. package/package.json +5 -5
  38. package/src/__tests__/Metadata.test.ts +43 -0
  39. package/src/index.ts +19 -2
  40. package/src/types/ActivityEvent.ts +88 -0
  41. package/src/types/DocumentRef.ts +218 -8
  42. package/src/types/DocumentSchema.ts +6 -0
  43. package/src/types/Metadata.ts +20 -5
  44. package/src/types/Model.ts +14 -0
  45. package/src/types/PresenceEvent.ts +102 -0
  46. package/src/types/RecordCollectionRef.ts +54 -0
  47. package/src/types/RecordRef.ts +74 -0
  48. package/src/types/Unsubscribe.ts +3 -0
  49. package/src/types/UserRef.ts +5 -0
@@ -4,13 +4,84 @@ import type { Model, ModelData } from "./Model.js";
4
4
  import type { Unsubscribe } from "./Unsubscribe.js";
5
5
  export type RecordId = Flavored<"RecordId">;
6
6
  export declare const RecordRefBrand: unique symbol;
7
+ /**
8
+ * A reference providing an API to interact with a specific record in a
9
+ * document. This is the main interface for accessing and updating individual
10
+ * records.
11
+ *
12
+ * These will be created by docRef or recordCollectionRef APIs for example and
13
+ * should not be created manually. RecordRefs are stable objects that can be
14
+ * used for reference equality checks.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { DocumentRef, DocumentSchema, MyModel } from "@myapp/schema";
19
+ * import { app } from "./appInstance";
20
+ *
21
+ * const docRef = app.getDocRef<DocumentSchema>(someDocumentId);
22
+ *
23
+ * const recordRef = docRef.getRecords(MyModel).set("my-record-id", { myFieldName: "some value", foo: 42 });
24
+ *
25
+ * // Or get a specific record.
26
+ * const recordRef = docRef.getRecords(MyModel).get("my-record-id");
27
+ */
7
28
  export interface RecordRef<M extends Model = Model> {
8
29
  readonly docRef: DocumentRef;
9
30
  readonly id: RecordId;
10
31
  readonly model: M;
11
32
  readonly [RecordRefBrand]: typeof RecordRefBrand;
33
+ /**
34
+ * Get the current state of the record in a plain object.
35
+ * If there is an active subscription to the document this is the current state of the record in memory.
36
+ * Otherwise, this will fetch the latest state from the server.
37
+ */
12
38
  getSnapshot(): Promise<ModelData<M>>;
39
+ /**
40
+ * Subscribe to changes in the record.
41
+ * @param callback The callback to invoke when the record changes.
42
+ * @returns An unsubscribe function.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // Subscribe to changes
47
+ * recordRef.onChange((newSnapshot, recordRef) => {
48
+ * console.log("Record changed:", newSnapshot);
49
+ * });
50
+ *
51
+ * // Submit a change
52
+ * recordRef.set({ myFieldName: "new value" });
53
+ * ```
54
+ */
13
55
  onChange(callback: (snapshot: ModelData<M>, recordRef: RecordRef<M>) => void): Unsubscribe;
56
+ /**
57
+ * Subscribe to deletion of the record.
58
+ * @param callback The callback to invoke when the record is deleted.
59
+ * @returns An unsubscribe function.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * // Subscribe to deletion
64
+ * recordRef.onDeleted((recordRef) => {
65
+ * console.log("Record deleted:", recordRef.id);
66
+ * });
67
+ *
68
+ * // Trigger the deletion
69
+ * docRef.getRecords(MyModel).delete(recordRef.id);
70
+ * ```
71
+ */
14
72
  onDeleted(callback: (recordRef: RecordRef<M>) => void): Unsubscribe;
73
+ /**
74
+ * Set the data for the record (creating it if it doesn't exist).
75
+ *
76
+ * @see {onChange} to subscribe to changes to the record.
77
+ *
78
+ * @param record - The plain object data to set for the record, corresponding to the model's schema.
79
+ * @returns An ignorable promise that resolves when the record is published.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * await recordRef.set({ field: "value" });
84
+ * ```
85
+ */
15
86
  set(record: ModelData<M>): Promise<void>;
16
87
  }
@@ -1 +1 @@
1
- {"mappings":"AAgBA,cAAc,gBAAgB;AAC9B,cAAc,mBAAmB;AACjC,cAAc,OAAO,iBAAiB;AACtC,cAAc,mBAAmB;AAEjC,YAAY,WAAW,SAAS;AAEhC,OAAO,cAAMA;AAEb,iBAAiB,UAAU,UAAU,QAAQ,OAAO;UACzC,QAAQ;UACR,IAAI;UACJ,OAAO;WACN,wBAAwB;CAElC,eAAe,QAAQ,UAAU;CACjC,SAAS,WAAW,UAAU,UAAU,IAAI,WAAW,UAAU,cAAc;CAC/E,UAAU,WAAW,WAAW,UAAU,cAAc;CACxD,IAAI,QAAQ,UAAU,KAAK","names":["RecordRefBrand: unique symbol"],"sources":["../../../src/types/RecordRef.ts"],"version":3,"file":"RecordRef.d.ts"}
1
+ {"mappings":"AAgBA,cAAc,gBAAgB;AAC9B,cAAc,mBAAmB;AACjC,cAAc,OAAO,iBAAiB;AACtC,cAAc,mBAAmB;AAEjC,YAAY,WAAW,SAAS;AAEhC,OAAO,cAAMA;;;;;;;;;;;;;;;;;;;;;;AAuBb,iBAAiB,UAAU,UAAU,QAAQ,OAAO;UACzC,QAAQ;UACR,IAAI;UACJ,OAAO;WACN,wBAAwB;;;;;;CAOlC,eAAe,QAAQ,UAAU;;;;;;;;;;;;;;;;;CAkBjC,SAAS,WAAW,UAAU,UAAU,IAAI,WAAW,UAAU,cAAc;;;;;;;;;;;;;;;;;CAkB/E,UAAU,WAAW,WAAW,UAAU,cAAc;;;;;;;;;;;;;;CAexD,IAAI,QAAQ,UAAU,KAAK","names":["RecordRefBrand: unique symbol"],"sources":["../../../src/types/RecordRef.ts"],"version":3,"file":"RecordRef.d.ts"}
@@ -1 +1,4 @@
1
+ /**
2
+ * A function that can be called to unsubscribe from a previously subscribed event interface.
3
+ */
1
4
  export type Unsubscribe = () => void;
@@ -1 +1 @@
1
- {"mappings":"AAgBA,YAAY","names":[],"sources":["../../../src/types/Unsubscribe.ts"],"version":3,"file":"Unsubscribe.d.ts"}
1
+ {"mappings":";;;AAmBA,YAAY","names":[],"sources":["../../../src/types/Unsubscribe.ts"],"version":3,"file":"Unsubscribe.d.ts"}
@@ -1,6 +1,11 @@
1
1
  import type { Flavored } from "@palantir/pack.core";
2
2
  export type UserId = Flavored<"pack:UserId">;
3
3
  export declare const UserRefBrand: unique symbol;
4
+ /**
5
+ * A reference providing an API to interact with a user.
6
+ *
7
+ * @experimental
8
+ */
4
9
  export interface UserRef {
5
10
  readonly userId: UserId;
6
11
  readonly [UserRefBrand]: typeof UserRefBrand;
@@ -1 +1 @@
1
- {"mappings":"AAgBA,cAAc,gBAAgB;AAE9B,YAAY,SAAS,SAAS;AAE9B,OAAO,cAAMA;AAEb,iBAAiB,QAAQ;UACd,QAAQ;WACP,sBAAsB;UACvB,MAAM,oBAAoB","names":["UserRefBrand: unique symbol"],"sources":["../../../src/types/UserRef.ts"],"version":3,"file":"UserRef.d.ts"}
1
+ {"mappings":"AAgBA,cAAc,gBAAgB;AAE9B,YAAY,SAAS,SAAS;AAE9B,OAAO,cAAMA;;;;;;AAOb,iBAAiB,QAAQ;UACd,QAAQ;WACP,sBAAsB;UACvB,MAAM,oBAAoB","names":["UserRefBrand: unique symbol"],"sources":["../../../src/types/UserRef.ts"],"version":3,"file":"UserRef.d.ts"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palantir/pack.document-schema.model-types",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.1",
4
4
  "description": "Minimal types supporting generated PACK schemas",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  }
29
29
  },
30
30
  "dependencies": {
31
- "@palantir/pack.core": "~0.1.0-beta.2"
31
+ "@palantir/pack.core": "~0.1.1"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "zod": "^4.1.7"
@@ -37,7 +37,7 @@
37
37
  "rimraf": "^6.0.1",
38
38
  "tslib": "^2.8.1",
39
39
  "typescript": "^5.9.2",
40
- "@palantir/pack.monorepo.tsconfig": "~0.4.0-beta.1"
40
+ "@palantir/pack.monorepo.tsconfig": "~0.4.3"
41
41
  },
42
42
  "publishConfig": {
43
43
  "access": "public"
@@ -48,8 +48,8 @@
48
48
  "type": "module",
49
49
  "scripts": {
50
50
  "clean": "rimraf .turbo build dist lib test-output *.tgz tsconfig.tsbuildinfo",
51
- "lint": "eslint ./src ; dprint check --config $(find-up dprint.json) --allow-no-files",
52
- "lint:fix": "eslint ./src --fix ; dprint fmt --config $(find-up dprint.json) --allow-no-files",
51
+ "lint": "eslint ./src && dprint check --config $(find-up dprint.json) --allow-no-files",
52
+ "lint:fix": "eslint ./src --fix && dprint fmt --config $(find-up dprint.json) --allow-no-files",
53
53
  "test": "vitest run --passWithNoTests -u",
54
54
  "test:watch": "vitest --passWithNoTests",
55
55
  "transpileBrowser": "monorepo-transpile -f esm -m bundle -t browser",
@@ -0,0 +1,43 @@
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 { describe, expect, it } from "vitest";
18
+ import { getMetadata, Metadata, type WithMetadata } from "../types/Metadata.js";
19
+
20
+ describe("Metadata Lookup Tests", () => {
21
+ it("should retrieve metadata using direct symbol access", () => {
22
+ const obj: WithMetadata<{ name: string }> = {
23
+ [Metadata]: { name: "TestModel" },
24
+ };
25
+
26
+ const result = getMetadata(obj);
27
+
28
+ expect(result).toEqual({ name: "TestModel" });
29
+ });
30
+
31
+ it("should fallback to string-based symbol matching for cross-package scenarios", () => {
32
+ // Simulate a different copy of the Metadata symbol (as if from a different package instance)
33
+ const differentMetadataSymbol = Symbol("@palantir/pack.document-schema/metadata");
34
+
35
+ const obj = {
36
+ [differentMetadataSymbol]: { version: 2 },
37
+ } as WithMetadata<{ version: number }>;
38
+
39
+ const result = getMetadata(obj);
40
+
41
+ expect(result).toEqual({ version: 2 });
42
+ });
43
+ });
package/src/index.ts CHANGED
@@ -14,6 +14,14 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ export { ActivityEventDataType } from "./types/ActivityEvent.js";
18
+ export type {
19
+ ActivityEvent,
20
+ ActivityEventData,
21
+ ActivityEventDataCustom,
22
+ ActivityEventDataUnknown,
23
+ ActivityEventId,
24
+ } from "./types/ActivityEvent.js";
17
25
  export type {
18
26
  DiscretionaryPrincipal,
19
27
  DiscretionaryPrincipal_All,
@@ -22,7 +30,7 @@ export type {
22
30
  DocumentMetadata,
23
31
  } from "./types/DocumentMetadata.js";
24
32
  export { DocumentRefBrand } from "./types/DocumentRef.js";
25
- export type { DocumentId, DocumentRef } from "./types/DocumentRef.js";
33
+ export type { DocumentId, DocumentRef, PresenceSubscriptionOptions } from "./types/DocumentRef.js";
26
34
  export type {
27
35
  DocumentSchema,
28
36
  DocumentSchemaMetadata,
@@ -33,9 +41,18 @@ export type { MediaId, MediaRef } from "./types/MediaRef.js";
33
41
  export { getMetadata, Metadata } from "./types/Metadata.js";
34
42
  export type { WithMetadata } from "./types/Metadata.js";
35
43
  export { ExternalRefType } from "./types/Model.js";
36
- export type { Model, ModelData, ModelMetadata } from "./types/Model.js";
44
+ export type { EditDescription, Model, ModelData, ModelMetadata } from "./types/Model.js";
37
45
  export { ObjectRefBrand } from "./types/ObjectRef.js";
38
46
  export type { ObjectId, ObjectRef } from "./types/ObjectRef.js";
47
+ export { PresenceEventDataType } from "./types/PresenceEvent.js";
48
+ export type {
49
+ PresenceEvent,
50
+ PresenceEventData,
51
+ PresenceEventDataArrived,
52
+ PresenceEventDataCustom,
53
+ PresenceEventDataDeparted,
54
+ PresenceEventUnknown,
55
+ } from "./types/PresenceEvent.js";
39
56
  export { RecordCollectionRefBrand } from "./types/RecordCollectionRef.js";
40
57
  export type { RecordCollectionRef } from "./types/RecordCollectionRef.js";
41
58
  export { RecordRefBrand } from "./types/RecordRef.js";
@@ -0,0 +1,88 @@
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 { Flavored } from "@palantir/pack.core";
18
+ import type { Model, ModelData } from "./Model.js";
19
+ import type { UserId } from "./UserRef.js";
20
+
21
+ export type ActivityEventId = Flavored<"pack:EventId">;
22
+
23
+ export const ActivityEventDataType = {
24
+ CUSTOM_EVENT: "customEvent",
25
+ UNKNOWN: "unknown",
26
+ } as const;
27
+
28
+ /**
29
+ * Application specific custom activity event data, as described in a transaction edit,
30
+ * using an application sdk's generated model types.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const unsubscribe = docRef.onActivity((docRef, event) => {
35
+ * console.log("Activity event:", event);
36
+ * });
37
+ * // Submit an edit with a description to generate an activity event.
38
+ * docRef.withTransaction(() => {
39
+ * // make some edits to the document here
40
+ * }, {
41
+ * model: MyEventModel,
42
+ * data: {
43
+ * myDataField: "some value",
44
+ * foo: 42,
45
+ * },
46
+ * });
47
+ * ```
48
+ */
49
+ export interface ActivityEventDataCustom<M extends Model = Model> {
50
+ readonly type: typeof ActivityEventDataType.CUSTOM_EVENT;
51
+ readonly model: M;
52
+ readonly eventData: ModelData<M>;
53
+ }
54
+
55
+ // TODO: add standard document activity events (need to be added to api types)
56
+
57
+ /**
58
+ * Fallback for unrecognized activity event types.
59
+ *
60
+ * This allows some flexibility with new event types added to the platform.
61
+ * Likely unknown events represent a new platform event type and will be handled
62
+ * in a future release of pack libraries and can be safely ignored by
63
+ * applications.
64
+ */
65
+ export interface ActivityEventDataUnknown {
66
+ readonly type: "unknown";
67
+ readonly rawType: string;
68
+ readonly rawData: unknown;
69
+ }
70
+
71
+ export type ActivityEventData = ActivityEventDataCustom | ActivityEventDataUnknown;
72
+
73
+ /**
74
+ * An event representing an activity that has occurred on a document. This
75
+ * includes standard document events as well as custom application-specific
76
+ * events describing document edits.
77
+ *
78
+ * ActivityEvents are useful for building activity feeds, or notifications.
79
+ */
80
+ export interface ActivityEvent {
81
+ /** Multiple events with the same aggregationKey may be collapsed. */
82
+ readonly aggregationKey: string;
83
+ readonly createdBy: UserId;
84
+ readonly createdInstant: number;
85
+ readonly eventData: ActivityEventData;
86
+ readonly eventId: ActivityEventId;
87
+ readonly isRead: boolean;
88
+ }
@@ -15,27 +15,237 @@
15
15
  */
16
16
 
17
17
  import type { Flavored } from "@palantir/pack.core";
18
+ import type { ActivityEvent } from "./ActivityEvent.js";
18
19
  import type { DocumentMetadata } from "./DocumentMetadata.js";
19
20
  import type { DocumentSchema, DocumentState } from "./DocumentSchema.js";
20
- import type { Model } from "./Model.js";
21
+ import type { EditDescription, Model, ModelData } from "./Model.js";
22
+ import type { PresenceEvent } from "./PresenceEvent.js";
21
23
  import type { RecordCollectionRef } from "./RecordCollectionRef.js";
22
24
  import type { Unsubscribe } from "./Unsubscribe.js";
23
25
 
24
26
  export type DocumentId = Flavored<"DocumentId">;
25
27
 
28
+ /**
29
+ * Options for subscribing to presence events on a document.
30
+ */
31
+ export interface PresenceSubscriptionOptions {
32
+ /**
33
+ * If true, presence events originating from the local user will be ignored.
34
+ *
35
+ * @default true
36
+ */
37
+ readonly ignoreSelfUpdates?: boolean;
38
+ }
39
+
26
40
  export const DocumentRefBrand: unique symbol = Symbol("pack:DocumentRef");
27
41
 
42
+ /**
43
+ * A reference to a document in the Pack system.
44
+ *
45
+ * A documentRef returned by various interfaces from the pack app instance or
46
+ * utilities such as react hooks from @palantir/pack.state.react provides
47
+ * methods to interact with the document, such as subscribing to & making
48
+ * changes to the document state and also related activity or presence events.
49
+ *
50
+ * A stable documentRef object is guaranteed for the same document id within the
51
+ * same app instance.
52
+ */
28
53
  export interface DocumentRef<D extends DocumentSchema = DocumentSchema> {
29
54
  readonly id: DocumentId;
30
55
  readonly schema: D;
31
56
  readonly [DocumentRefBrand]: typeof DocumentRefBrand;
32
57
 
33
- readonly getDocSnapshot: () => Promise<DocumentState<D>>;
34
- readonly getRecords: <R extends Model>(model: R) => RecordCollectionRef<R>;
35
- readonly onMetadataChange: (
36
- callback: (docId: DocumentRef<D>, metadata: DocumentMetadata) => void,
37
- ) => Unsubscribe;
38
- readonly onStateChange: (
58
+ /**
59
+ * Get a snapshot of the current document state.
60
+ *
61
+ * This is largely for debugging and dumps a json view of the whole document state.
62
+ *
63
+ * @experimental
64
+ */
65
+ getDocSnapshot(): Promise<DocumentState<D>>;
66
+
67
+ /**
68
+ * Get or create a ref to the collection of all records for the specified
69
+ * model in this document.
70
+ *
71
+ * @param model The model type from the application's generated schema.
72
+ * @returns A stable {@link RecordCollectionRef} object providing an interface
73
+ * to interact with the records of the specified model type in this document.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { DocumentModel, MyModel } from "@myapp/schema";
78
+ * import { app } from "./appInstance";
79
+ *
80
+ * const docRef = app.state.createDocRef(DocumentModel, someDocumentId);
81
+ * const myRecordCollection = docRef.getRecords(MyModel);
82
+ *
83
+ * myRecordCollection.onItemsAdded((items) => {
84
+ * console.log("New records added:", items);
85
+ * })
86
+ * ```
87
+ */
88
+ getRecords<R extends Model>(model: R): RecordCollectionRef<R>;
89
+
90
+ /**
91
+ * Subscribe to activity events for the document.
92
+ *
93
+ * Activity is used by applications to describe changes to the document, for
94
+ * populating activity feeds, change histories, notifications and similar
95
+ * features.
96
+ *
97
+ * @see {withTransaction} for making edits that generate activity events.
98
+ *
99
+ * @returns An unsubscribe function.
100
+ * @example
101
+ * ```ts
102
+ * const unsubscribe = docRef.onActivity((docRef, event) => {
103
+ * console.log("Activity event:", event);
104
+ * });
105
+ *
106
+ * // Submit an edit with a description to generate an activity event.
107
+ * docRef.withTransaction(() => {
108
+ * // make some edits to the document here
109
+ * }, {
110
+ * model: MyEventModel,
111
+ * data: {
112
+ * myDataField: "some value",
113
+ * foo: 42,
114
+ * },
115
+ * });
116
+ *
117
+ * // Later, to unsubscribe:
118
+ * unsubscribe();
119
+ * ```
120
+ */
121
+ onActivity(
122
+ callback: (docRef: DocumentRef<D>, event: ActivityEvent) => void,
123
+ ): Unsubscribe;
124
+
125
+ /**
126
+ * Subscribe to metadata changes for the document.
127
+ *
128
+ * @returns An unsubscribe function.
129
+ * @example
130
+ * ```ts
131
+ * const unsubscribe = docRef.onMetadataChange((docRef, metadata) => {
132
+ * console.log("Metadata changed:", metadata);
133
+ * });
134
+ *
135
+ * // Later, to unsubscribe:
136
+ * unsubscribe();
137
+ * ```
138
+ */
139
+ onMetadataChange(
140
+ callback: (docRef: DocumentRef<D>, metadata: DocumentMetadata) => void,
141
+ ): Unsubscribe;
142
+
143
+ /**
144
+ * Subscribe to presence events for the document.
145
+ *
146
+ * Presence events are a way to broadcast and receive non-persisted awareness
147
+ * and presence events. For example, a user moving their cursor in a
148
+ * collaborative editor could be broadcast via custom presence events.
149
+ *
150
+ * @see {@link updateCustomPresence} to publish custom presence updates.
151
+ *
152
+ * @param callback The callback to invoke on presence events.
153
+ * @param options Options for the presence subscription.
154
+ * @returns An unsubscribe function.
155
+ * @example
156
+ * ```ts
157
+ * const unsubscribe = docRef.onPresence((docRef, event) => {
158
+ * console.log("Presence event:", event);
159
+ * if (event.model === MyPresenceModel) {
160
+ * updateCursor((event.eventData as ModelData<MyPresenceModel>).cursorPosition);
161
+ * }
162
+ * }, { ignoreSelfUpdates: true });
163
+ *
164
+ * // Broadcast a presence update
165
+ * docRef.updateCustomPresence(MyPresenceModel, {
166
+ * cursorPosition: [42, 7],
167
+ * });
168
+ *
169
+ * // Later, to unsubscribe:
170
+ * unsubscribe();
171
+ * ```
172
+ */
173
+ onPresence(
174
+ callback: (docRef: DocumentRef<D>, event: PresenceEvent) => void,
175
+ options?: PresenceSubscriptionOptions,
176
+ ): Unsubscribe;
177
+
178
+ /**
179
+ * Subscribe to be notified any time the document state changes.
180
+ * This is largely for testing purposes.
181
+ *
182
+ * @experimental
183
+ *
184
+ * @param callback The callback to invoke on state changes.
185
+ * @returns An unsubscribe function.
186
+ * @example
187
+ * ```ts
188
+ * const unsubscribe = docRef.onStateChange((docRef) => {
189
+ * console.log("Document state changed:", docRef);
190
+ * });
191
+ *
192
+ * // Later, to unsubscribe:
193
+ * unsubscribe();
194
+ * ```
195
+ */
196
+ onStateChange(
39
197
  callback: (docRef: DocumentRef<D>) => void,
40
- ) => Unsubscribe;
198
+ ): Unsubscribe;
199
+
200
+ /**
201
+ * Broadcasts an update for the specified model as presence data to other
202
+ * subscribers.
203
+ *
204
+ * Presence data is ephemeral and not stored as part of the document state. It
205
+ * is intended for broadcasting transient user presence and awareness
206
+ * information. Each different model type used for presence is expected to
207
+ * update the latest 'presence state' for that model type.
208
+ *
209
+ * @see {@link onPresence} to subscribe to presence updates.
210
+ *
211
+ * @param model The model type to update presence for.
212
+ * @param eventData The new presence data for the model.
213
+ */
214
+ updateCustomPresence<M extends Model = Model>(
215
+ model: M,
216
+ eventData: ModelData<M>,
217
+ ): void;
218
+
219
+ /**
220
+ * Execute one or more document edits within a transaction, optionally providing
221
+ * a description of the edit for activity tracking.
222
+ *
223
+ * All edits made within the provided function will be treated as a single
224
+ * atomic edit operation. If a description is provided, an activity event will
225
+ * be generated for the edit.
226
+ *
227
+ * If this is called within an existing transaction, the inner edits will be
228
+ * included in the outer transaction only, and the inner descriptions will be
229
+ * discarded.
230
+ *
231
+ * @see {@link onActivity} to subscribe to activity events on this document.
232
+ *
233
+ * @param fn A lambda including some document edits.
234
+ * @param description Optional description of the edit for activity tracking.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * docRef.withTransaction(() => {
239
+ * const myRecords = docRef.getRecords(MyModel);
240
+ * myRecords.set("record-1", { field: "new value" });
241
+ * myRecords.delete("record-2");
242
+ * }, {
243
+ * model: MyEditEventModel,
244
+ * data: {
245
+ * summary: "Updated record-1 and deleted record-2",
246
+ * },
247
+ * });
248
+ * ```
249
+ */
250
+ withTransaction(fn: () => void, description?: EditDescription): void;
41
251
  }
@@ -17,10 +17,16 @@
17
17
  import type { WithMetadata } from "./Metadata.js";
18
18
  import type { Model, ModelData } from "./Model.js";
19
19
 
20
+ /**
21
+ * The base type for an application sdk's generated document schema.
22
+ */
20
23
  export interface DocumentSchema extends WithMetadata<DocumentSchemaMetadata> {
21
24
  readonly [modelName: string]: Model;
22
25
  }
23
26
 
27
+ /**
28
+ * The plain object representation of the state of a document.
29
+ */
24
30
  export type DocumentState<S extends DocumentSchema> = {
25
31
  readonly [K in Exclude<keyof S, symbol>]: { readonly [key: string]: ModelData<S[K]> };
26
32
  };
@@ -21,10 +21,25 @@ export interface WithMetadata<T> {
21
21
  }
22
22
 
23
23
  export function getMetadata<T>(obj: WithMetadata<T>): T {
24
- // TS always treats symbol keys as optional
25
- const metadata = obj[Metadata];
26
- if (metadata == null) {
27
- throw new Error("Object does not have metadata");
24
+ // First try the direct symbol access
25
+ const directMetadata = obj[Metadata];
26
+ if (directMetadata != null) {
27
+ return directMetadata;
28
28
  }
29
- return metadata;
29
+
30
+ // Fallback: search for a symbol with matching string representation
31
+ // If the different copies of this package are used, the symbol references will not match directly
32
+ const metadataString = Metadata.toString();
33
+ const symbolKeys = Object.getOwnPropertySymbols(obj);
34
+
35
+ for (const symbolKey of symbolKeys) {
36
+ if (symbolKey.toString() === metadataString) {
37
+ const fallbackMetadata = (obj as any)[symbolKey];
38
+ if (fallbackMetadata != null) {
39
+ return fallbackMetadata;
40
+ }
41
+ }
42
+ }
43
+
44
+ throw new Error("Object does not have metadata");
30
45
  }
@@ -32,6 +32,14 @@ export interface Model<T = unknown, Z extends ZodType<T> = ZodType<T>>
32
32
 
33
33
  export type ModelData<M extends Model> = M["__type"];
34
34
 
35
+ /**
36
+ * Describes an edit made to a document.
37
+ */
38
+ export interface EditDescription<M extends Model = Model> {
39
+ readonly data: ModelData<M>;
40
+ readonly model: M;
41
+ }
42
+
35
43
  export const ExternalRefType = {
36
44
  DOC_REF: "docRef",
37
45
  MEDIA_REF: "mediaRef",
@@ -42,6 +50,12 @@ export const ExternalRefType = {
42
50
  export type ExternalRefType = typeof ExternalRefType[keyof typeof ExternalRefType];
43
51
 
44
52
  export interface ModelMetadata<T = unknown> {
53
+ /**
54
+ * Which fields in the model are external references (e.g. UserRef, DocumentRef, etc).
55
+ */
45
56
  readonly externalRefFieldTypes?: Readonly<Record<keyof T, ExternalRefType>>;
57
+ /**
58
+ * The name of the model (should match the typescript symbol).
59
+ */
46
60
  readonly name: string;
47
61
  }