@tldraw/tlschema 4.1.0-next.1b89b40eff1c → 4.1.0-next.9f145d10c7d0

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 (212) hide show
  1. package/dist-cjs/TLStore.js +3 -10
  2. package/dist-cjs/TLStore.js.map +2 -2
  3. package/dist-cjs/assets/TLBaseAsset.js.map +2 -2
  4. package/dist-cjs/assets/TLBookmarkAsset.js.map +2 -2
  5. package/dist-cjs/assets/TLImageAsset.js.map +2 -2
  6. package/dist-cjs/assets/TLVideoAsset.js.map +2 -2
  7. package/dist-cjs/bindings/TLArrowBinding.js.map +2 -2
  8. package/dist-cjs/bindings/TLBaseBinding.js.map +2 -2
  9. package/dist-cjs/createPresenceStateDerivation.js.map +2 -2
  10. package/dist-cjs/createTLSchema.js.map +2 -2
  11. package/dist-cjs/index.d.ts +4416 -223
  12. package/dist-cjs/index.js +1 -1
  13. package/dist-cjs/index.js.map +2 -2
  14. package/dist-cjs/misc/TLColor.js.map +2 -2
  15. package/dist-cjs/misc/TLCursor.js.map +2 -2
  16. package/dist-cjs/misc/TLHandle.js.map +2 -2
  17. package/dist-cjs/misc/TLOpacity.js.map +2 -2
  18. package/dist-cjs/misc/TLRichText.js.map +2 -2
  19. package/dist-cjs/misc/TLScribble.js.map +2 -2
  20. package/dist-cjs/misc/geometry-types.js.map +2 -2
  21. package/dist-cjs/misc/id-validator.js.map +2 -2
  22. package/dist-cjs/records/TLAsset.js.map +2 -2
  23. package/dist-cjs/records/TLBinding.js.map +2 -2
  24. package/dist-cjs/records/TLCamera.js.map +2 -2
  25. package/dist-cjs/records/TLDocument.js.map +2 -2
  26. package/dist-cjs/records/TLInstance.js.map +2 -2
  27. package/dist-cjs/records/TLPage.js.map +2 -2
  28. package/dist-cjs/records/TLPageState.js.map +2 -2
  29. package/dist-cjs/records/TLPointer.js.map +2 -2
  30. package/dist-cjs/records/TLPresence.js.map +2 -2
  31. package/dist-cjs/records/TLRecord.js.map +1 -1
  32. package/dist-cjs/records/TLShape.js.map +2 -2
  33. package/dist-cjs/recordsWithProps.js.map +2 -2
  34. package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
  35. package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
  36. package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
  37. package/dist-cjs/shapes/TLBookmarkShape.js.map +2 -2
  38. package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
  39. package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
  40. package/dist-cjs/shapes/TLFrameShape.js.map +2 -2
  41. package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
  42. package/dist-cjs/shapes/TLGroupShape.js.map +2 -2
  43. package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
  44. package/dist-cjs/shapes/TLImageShape.js.map +2 -2
  45. package/dist-cjs/shapes/TLLineShape.js.map +2 -2
  46. package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
  47. package/dist-cjs/shapes/TLTextShape.js.map +2 -2
  48. package/dist-cjs/shapes/TLVideoShape.js.map +2 -2
  49. package/dist-cjs/store-migrations.js.map +2 -2
  50. package/dist-cjs/styles/TLColorStyle.js.map +2 -2
  51. package/dist-cjs/styles/TLDashStyle.js.map +2 -2
  52. package/dist-cjs/styles/TLFillStyle.js.map +2 -2
  53. package/dist-cjs/styles/TLFontStyle.js.map +2 -2
  54. package/dist-cjs/styles/TLHorizontalAlignStyle.js.map +2 -2
  55. package/dist-cjs/styles/TLSizeStyle.js.map +2 -2
  56. package/dist-cjs/styles/TLTextAlignStyle.js.map +2 -2
  57. package/dist-cjs/styles/TLVerticalAlignStyle.js.map +2 -2
  58. package/dist-cjs/translations/translations.js +1 -1
  59. package/dist-cjs/translations/translations.js.map +2 -2
  60. package/dist-cjs/util-types.js.map +1 -1
  61. package/dist-esm/TLStore.mjs +3 -10
  62. package/dist-esm/TLStore.mjs.map +2 -2
  63. package/dist-esm/assets/TLBaseAsset.mjs.map +2 -2
  64. package/dist-esm/assets/TLBookmarkAsset.mjs.map +2 -2
  65. package/dist-esm/assets/TLImageAsset.mjs.map +2 -2
  66. package/dist-esm/assets/TLVideoAsset.mjs.map +2 -2
  67. package/dist-esm/bindings/TLArrowBinding.mjs.map +2 -2
  68. package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
  69. package/dist-esm/createPresenceStateDerivation.mjs.map +2 -2
  70. package/dist-esm/createTLSchema.mjs.map +2 -2
  71. package/dist-esm/index.d.mts +4416 -223
  72. package/dist-esm/index.mjs +1 -1
  73. package/dist-esm/index.mjs.map +2 -2
  74. package/dist-esm/misc/TLColor.mjs.map +2 -2
  75. package/dist-esm/misc/TLCursor.mjs.map +2 -2
  76. package/dist-esm/misc/TLHandle.mjs.map +2 -2
  77. package/dist-esm/misc/TLOpacity.mjs.map +2 -2
  78. package/dist-esm/misc/TLRichText.mjs.map +2 -2
  79. package/dist-esm/misc/TLScribble.mjs.map +2 -2
  80. package/dist-esm/misc/geometry-types.mjs.map +2 -2
  81. package/dist-esm/misc/id-validator.mjs.map +2 -2
  82. package/dist-esm/records/TLAsset.mjs.map +2 -2
  83. package/dist-esm/records/TLBinding.mjs.map +2 -2
  84. package/dist-esm/records/TLCamera.mjs.map +2 -2
  85. package/dist-esm/records/TLDocument.mjs.map +2 -2
  86. package/dist-esm/records/TLInstance.mjs.map +2 -2
  87. package/dist-esm/records/TLPage.mjs.map +2 -2
  88. package/dist-esm/records/TLPageState.mjs.map +2 -2
  89. package/dist-esm/records/TLPointer.mjs.map +2 -2
  90. package/dist-esm/records/TLPresence.mjs.map +2 -2
  91. package/dist-esm/records/TLShape.mjs.map +2 -2
  92. package/dist-esm/recordsWithProps.mjs.map +2 -2
  93. package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
  94. package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
  95. package/dist-esm/shapes/TLBookmarkShape.mjs.map +2 -2
  96. package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
  97. package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
  98. package/dist-esm/shapes/TLFrameShape.mjs.map +2 -2
  99. package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
  100. package/dist-esm/shapes/TLGroupShape.mjs.map +2 -2
  101. package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
  102. package/dist-esm/shapes/TLImageShape.mjs.map +2 -2
  103. package/dist-esm/shapes/TLLineShape.mjs.map +2 -2
  104. package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
  105. package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
  106. package/dist-esm/shapes/TLVideoShape.mjs.map +2 -2
  107. package/dist-esm/store-migrations.mjs.map +2 -2
  108. package/dist-esm/styles/TLColorStyle.mjs.map +2 -2
  109. package/dist-esm/styles/TLDashStyle.mjs.map +2 -2
  110. package/dist-esm/styles/TLFillStyle.mjs.map +2 -2
  111. package/dist-esm/styles/TLFontStyle.mjs.map +2 -2
  112. package/dist-esm/styles/TLHorizontalAlignStyle.mjs.map +2 -2
  113. package/dist-esm/styles/TLSizeStyle.mjs.map +2 -2
  114. package/dist-esm/styles/TLTextAlignStyle.mjs.map +2 -2
  115. package/dist-esm/styles/TLVerticalAlignStyle.mjs.map +2 -2
  116. package/dist-esm/translations/translations.mjs +1 -1
  117. package/dist-esm/translations/translations.mjs.map +2 -2
  118. package/package.json +5 -5
  119. package/src/TLStore.test.ts +644 -0
  120. package/src/TLStore.ts +205 -20
  121. package/src/assets/TLBaseAsset.ts +90 -7
  122. package/src/assets/TLBookmarkAsset.test.ts +96 -0
  123. package/src/assets/TLBookmarkAsset.ts +52 -2
  124. package/src/assets/TLImageAsset.test.ts +213 -0
  125. package/src/assets/TLImageAsset.ts +60 -2
  126. package/src/assets/TLVideoAsset.test.ts +105 -0
  127. package/src/assets/TLVideoAsset.ts +93 -4
  128. package/src/bindings/TLArrowBinding.test.ts +55 -0
  129. package/src/bindings/TLArrowBinding.ts +132 -10
  130. package/src/bindings/TLBaseBinding.ts +140 -3
  131. package/src/createPresenceStateDerivation.test.ts +158 -0
  132. package/src/createPresenceStateDerivation.ts +71 -2
  133. package/src/createTLSchema.test.ts +181 -0
  134. package/src/createTLSchema.ts +164 -7
  135. package/src/index.ts +32 -0
  136. package/src/misc/TLColor.ts +50 -6
  137. package/src/misc/TLCursor.ts +110 -8
  138. package/src/misc/TLHandle.ts +86 -6
  139. package/src/misc/TLOpacity.ts +51 -2
  140. package/src/misc/TLRichText.ts +56 -3
  141. package/src/misc/TLScribble.ts +105 -5
  142. package/src/misc/geometry-types.ts +30 -2
  143. package/src/misc/id-validator.test.ts +50 -0
  144. package/src/misc/id-validator.ts +20 -1
  145. package/src/records/TLAsset.test.ts +234 -0
  146. package/src/records/TLAsset.ts +165 -8
  147. package/src/records/TLBinding.test.ts +22 -0
  148. package/src/records/TLBinding.ts +277 -11
  149. package/src/records/TLCamera.test.ts +19 -0
  150. package/src/records/TLCamera.ts +118 -7
  151. package/src/records/TLDocument.test.ts +35 -0
  152. package/src/records/TLDocument.ts +148 -8
  153. package/src/records/TLInstance.test.ts +201 -0
  154. package/src/records/TLInstance.ts +117 -9
  155. package/src/records/TLPage.test.ts +110 -0
  156. package/src/records/TLPage.ts +106 -8
  157. package/src/records/TLPageState.test.ts +228 -0
  158. package/src/records/TLPageState.ts +88 -7
  159. package/src/records/TLPointer.test.ts +63 -0
  160. package/src/records/TLPointer.ts +105 -7
  161. package/src/records/TLPresence.test.ts +190 -0
  162. package/src/records/TLPresence.ts +99 -5
  163. package/src/records/TLRecord.test.ts +70 -0
  164. package/src/records/TLRecord.ts +43 -1
  165. package/src/records/TLShape.test.ts +232 -0
  166. package/src/records/TLShape.ts +289 -12
  167. package/src/recordsWithProps.test.ts +188 -0
  168. package/src/recordsWithProps.ts +131 -2
  169. package/src/shapes/ShapeWithCrop.test.ts +18 -0
  170. package/src/shapes/ShapeWithCrop.ts +64 -2
  171. package/src/shapes/TLArrowShape.test.ts +505 -0
  172. package/src/shapes/TLArrowShape.ts +188 -10
  173. package/src/shapes/TLBaseShape.test.ts +142 -0
  174. package/src/shapes/TLBaseShape.ts +103 -4
  175. package/src/shapes/TLBookmarkShape.test.ts +122 -0
  176. package/src/shapes/TLBookmarkShape.ts +58 -4
  177. package/src/shapes/TLDrawShape.test.ts +177 -0
  178. package/src/shapes/TLDrawShape.ts +97 -6
  179. package/src/shapes/TLEmbedShape.test.ts +286 -0
  180. package/src/shapes/TLEmbedShape.ts +57 -4
  181. package/src/shapes/TLFrameShape.test.ts +71 -0
  182. package/src/shapes/TLFrameShape.ts +59 -4
  183. package/src/shapes/TLGeoShape.test.ts +247 -0
  184. package/src/shapes/TLGeoShape.ts +103 -7
  185. package/src/shapes/TLGroupShape.test.ts +59 -0
  186. package/src/shapes/TLGroupShape.ts +52 -4
  187. package/src/shapes/TLHighlightShape.test.ts +325 -0
  188. package/src/shapes/TLHighlightShape.ts +79 -4
  189. package/src/shapes/TLImageShape.test.ts +534 -0
  190. package/src/shapes/TLImageShape.ts +105 -5
  191. package/src/shapes/TLLineShape.test.ts +269 -0
  192. package/src/shapes/TLLineShape.ts +128 -8
  193. package/src/shapes/TLNoteShape.test.ts +1568 -0
  194. package/src/shapes/TLNoteShape.ts +97 -4
  195. package/src/shapes/TLTextShape.test.ts +407 -0
  196. package/src/shapes/TLTextShape.ts +94 -4
  197. package/src/shapes/TLVideoShape.test.ts +112 -0
  198. package/src/shapes/TLVideoShape.ts +99 -4
  199. package/src/store-migrations.test.ts +88 -0
  200. package/src/store-migrations.ts +47 -1
  201. package/src/styles/TLColorStyle.test.ts +439 -0
  202. package/src/styles/TLColorStyle.ts +228 -10
  203. package/src/styles/TLDashStyle.ts +54 -2
  204. package/src/styles/TLFillStyle.ts +54 -2
  205. package/src/styles/TLFontStyle.ts +72 -3
  206. package/src/styles/TLHorizontalAlignStyle.ts +55 -2
  207. package/src/styles/TLSizeStyle.ts +54 -2
  208. package/src/styles/TLTextAlignStyle.ts +52 -2
  209. package/src/styles/TLVerticalAlignStyle.ts +52 -2
  210. package/src/translations/translations.test.ts +378 -35
  211. package/src/translations/translations.ts +157 -10
  212. package/src/util-types.ts +51 -1
@@ -10,7 +10,25 @@ import { T } from '@tldraw/validate'
10
10
  import { idValidator } from '../misc/id-validator'
11
11
 
12
12
  /**
13
- * TLPointer
13
+ * Represents the current pointer/cursor position and activity state.
14
+ * This record tracks the mouse or touch pointer coordinates and when
15
+ * the pointer was last active, useful for cursor synchronization in
16
+ * collaborative environments.
17
+ *
18
+ * There is typically one pointer record per browser tab that gets updated
19
+ * as the user moves their mouse or touches the screen.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const pointer: TLPointer = {
24
+ * id: 'pointer:pointer',
25
+ * typeName: 'pointer',
26
+ * x: 150,
27
+ * y: 200,
28
+ * lastActivityTimestamp: Date.now(),
29
+ * meta: {}
30
+ * }
31
+ * ```
14
32
  *
15
33
  * @public
16
34
  */
@@ -21,10 +39,40 @@ export interface TLPointer extends BaseRecord<'pointer', TLPointerId> {
21
39
  meta: JsonObject
22
40
  }
23
41
 
24
- /** @public */
42
+ /**
43
+ * A unique identifier for TLPointer records.
44
+ *
45
+ * Pointer IDs follow the format 'pointer:' followed by a unique identifier.
46
+ * Typically there is one pointer record with a constant ID per session.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const pointerId: TLPointerId = 'pointer:pointer'
51
+ * ```
52
+ *
53
+ * @public
54
+ */
25
55
  export type TLPointerId = RecordId<TLPointer>
26
56
 
27
- /** @public */
57
+ /**
58
+ * Runtime validator for TLPointer records. Validates the structure
59
+ * and types of all pointer properties to ensure data integrity.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const pointer = {
64
+ * id: 'pointer:pointer',
65
+ * typeName: 'pointer',
66
+ * x: 100,
67
+ * y: 200,
68
+ * lastActivityTimestamp: Date.now(),
69
+ * meta: {}
70
+ * }
71
+ * const isValid = pointerValidator.isValid(pointer) // true
72
+ * ```
73
+ *
74
+ * @public
75
+ */
28
76
  export const pointerValidator: T.Validator<TLPointer> = T.model(
29
77
  'pointer',
30
78
  T.object({
@@ -37,12 +85,30 @@ export const pointerValidator: T.Validator<TLPointer> = T.model(
37
85
  })
38
86
  )
39
87
 
40
- /** @public */
88
+ /**
89
+ * Migration version identifiers for TLPointer records. Each version
90
+ * represents a schema change that requires data transformation when
91
+ * loading older documents.
92
+ *
93
+ * @public
94
+ */
41
95
  export const pointerVersions = createMigrationIds('com.tldraw.pointer', {
42
96
  AddMeta: 1,
43
97
  })
44
98
 
45
- /** @public */
99
+ /**
100
+ * Migration sequence for TLPointer records. Defines how to transform
101
+ * pointer records between different schema versions, ensuring data
102
+ * compatibility when loading documents created with different versions.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * // Migrations are applied automatically when loading documents
107
+ * const migratedPointer = pointerMigrations.migrate(oldPointer, targetVersion)
108
+ * ```
109
+ *
110
+ * @public
111
+ */
46
112
  export const pointerMigrations = createRecordMigrationSequence({
47
113
  sequenceId: 'com.tldraw.pointer',
48
114
  recordType: 'pointer',
@@ -56,7 +122,24 @@ export const pointerMigrations = createRecordMigrationSequence({
56
122
  ],
57
123
  })
58
124
 
59
- /** @public */
125
+ /**
126
+ * The RecordType definition for TLPointer records. Defines validation,
127
+ * scope, and default properties for pointer records in the tldraw store.
128
+ *
129
+ * Pointer records are scoped to the session level, meaning they are
130
+ * specific to a browser tab and don't persist across sessions.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const pointer = PointerRecordType.create({
135
+ * id: 'pointer:pointer',
136
+ * x: 0,
137
+ * y: 0
138
+ * })
139
+ * ```
140
+ *
141
+ * @public
142
+ */
60
143
  export const PointerRecordType = createRecordType<TLPointer>('pointer', {
61
144
  validator: pointerValidator,
62
145
  scope: 'session',
@@ -69,5 +152,20 @@ export const PointerRecordType = createRecordType<TLPointer>('pointer', {
69
152
  })
70
153
  )
71
154
 
72
- /** @public */
155
+ /**
156
+ * The constant ID used for the singleton TLPointer record.
157
+ *
158
+ * Since each browser tab typically has one pointer, this constant ID
159
+ * is used universally across the application.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const pointer = store.get(TLPOINTER_ID)
164
+ * if (pointer) {
165
+ * console.log('Pointer at:', pointer.x, pointer.y)
166
+ * }
167
+ * ```
168
+ *
169
+ * @public
170
+ */
73
171
  export const TLPOINTER_ID = PointerRecordType.createId('pointer')
@@ -0,0 +1,190 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ instancePresenceMigrations,
4
+ instancePresenceValidator,
5
+ instancePresenceVersions,
6
+ TLInstancePresenceID,
7
+ } from './TLPresence'
8
+
9
+ describe('instancePresenceValidator', () => {
10
+ it('should validate valid instance presence records', () => {
11
+ const validPresence = {
12
+ typeName: 'instance_presence',
13
+ id: 'instance_presence:test' as TLInstancePresenceID,
14
+ userId: 'user123',
15
+ userName: 'Test User',
16
+ lastActivityTimestamp: null,
17
+ color: '#007AFF',
18
+ camera: null,
19
+ selectedShapeIds: [],
20
+ currentPageId: 'page:main' as any,
21
+ brush: null,
22
+ scribbles: [],
23
+ screenBounds: null,
24
+ followingUserId: null,
25
+ cursor: null,
26
+ chatMessage: '',
27
+ meta: {},
28
+ }
29
+
30
+ expect(() => instancePresenceValidator.validate(validPresence)).not.toThrow()
31
+ const validated = instancePresenceValidator.validate(validPresence)
32
+ expect(validated).toEqual(validPresence)
33
+ })
34
+
35
+ it('should validate presence with complete data', () => {
36
+ const complexPresence = {
37
+ typeName: 'instance_presence',
38
+ id: 'instance_presence:complex' as TLInstancePresenceID,
39
+ userId: 'user456',
40
+ userName: 'Complex User',
41
+ lastActivityTimestamp: Date.now(),
42
+ color: '#FF3B30',
43
+ camera: { x: -100, y: 200, z: 0.75 },
44
+ selectedShapeIds: ['shape:1' as any, 'shape:2' as any],
45
+ currentPageId: 'page:design' as any,
46
+ brush: { x: 50, y: 75, w: 150, h: 100 },
47
+ scribbles: [
48
+ {
49
+ id: 'scribble:1',
50
+ points: [{ x: 0, y: 0, z: 0.5 }],
51
+ size: 4,
52
+ color: 'black',
53
+ opacity: 1,
54
+ state: 'starting',
55
+ delay: 0,
56
+ shrink: 0,
57
+ taper: false,
58
+ },
59
+ ],
60
+ screenBounds: { x: 0, y: 0, w: 2560, h: 1440 },
61
+ followingUserId: 'leader123',
62
+ cursor: { x: 300, y: 400, type: 'pointer', rotation: 45 },
63
+ chatMessage: 'Working on design!',
64
+ meta: { team: 'design', role: 'designer' },
65
+ }
66
+
67
+ expect(() => instancePresenceValidator.validate(complexPresence)).not.toThrow()
68
+ })
69
+
70
+ it('should reject invalid typeName', () => {
71
+ const invalidPresence = {
72
+ typeName: 'not-instance-presence',
73
+ id: 'instance_presence:test' as TLInstancePresenceID,
74
+ userId: 'user123',
75
+ userName: 'Test',
76
+ lastActivityTimestamp: null,
77
+ color: '#000000',
78
+ camera: null,
79
+ selectedShapeIds: [],
80
+ currentPageId: 'page:main' as any,
81
+ brush: null,
82
+ scribbles: [],
83
+ screenBounds: null,
84
+ followingUserId: null,
85
+ cursor: null,
86
+ chatMessage: '',
87
+ meta: {},
88
+ }
89
+
90
+ expect(() => instancePresenceValidator.validate(invalidPresence)).toThrow()
91
+ })
92
+ })
93
+
94
+ describe('instancePresenceMigrations', () => {
95
+ it('should migrate AddScribbleDelay correctly', () => {
96
+ const migration = instancePresenceMigrations.sequence.find(
97
+ (m) => m.id === instancePresenceVersions.AddScribbleDelay
98
+ )!
99
+
100
+ const oldRecordWithScribble: any = {
101
+ scribble: { points: [], size: 4, color: 'black' },
102
+ }
103
+ migration.up(oldRecordWithScribble)
104
+ expect(oldRecordWithScribble.scribble.delay).toBe(0)
105
+
106
+ const oldRecordWithoutScribble: any = {
107
+ scribble: null,
108
+ }
109
+ migration.up(oldRecordWithoutScribble)
110
+ expect(oldRecordWithoutScribble.scribble).toBe(null)
111
+ })
112
+
113
+ it('should migrate RemoveInstanceId correctly', () => {
114
+ const migration = instancePresenceMigrations.sequence.find(
115
+ (m) => m.id === instancePresenceVersions.RemoveInstanceId
116
+ )!
117
+ const oldRecord: any = {
118
+ instanceId: 'instance:removed',
119
+ otherProp: 'keep-me',
120
+ }
121
+ migration.up(oldRecord)
122
+
123
+ expect(oldRecord.instanceId).toBeUndefined()
124
+ expect(oldRecord.otherProp).toBe('keep-me')
125
+ })
126
+
127
+ it('should migrate AddChatMessage correctly', () => {
128
+ const migration = instancePresenceMigrations.sequence.find(
129
+ (m) => m.id === instancePresenceVersions.AddChatMessage
130
+ )!
131
+ const oldRecord: any = { id: 'instance_presence:test' }
132
+ migration.up(oldRecord)
133
+
134
+ expect(oldRecord.chatMessage).toBe('')
135
+ })
136
+
137
+ it('should migrate AddMeta correctly', () => {
138
+ const migration = instancePresenceMigrations.sequence.find(
139
+ (m) => m.id === instancePresenceVersions.AddMeta
140
+ )!
141
+ const oldRecord: any = { id: 'instance_presence:test' }
142
+ migration.up(oldRecord)
143
+
144
+ expect(oldRecord.meta).toEqual({})
145
+ })
146
+
147
+ it('should handle RenameSelectedShapeIds migration (noop)', () => {
148
+ const migration = instancePresenceMigrations.sequence.find(
149
+ (m) => m.id === instancePresenceVersions.RenameSelectedShapeIds
150
+ )!
151
+ const oldRecord: any = { selectedShapeIds: ['shape:1'] }
152
+ const originalRecord = { ...oldRecord }
153
+ migration.up(oldRecord)
154
+
155
+ // Should be a noop
156
+ expect(oldRecord).toEqual(originalRecord)
157
+ })
158
+
159
+ it('should handle NullableCameraCursor migration up (noop)', () => {
160
+ const migration = instancePresenceMigrations.sequence.find(
161
+ (m) => m.id === instancePresenceVersions.NullableCameraCursor
162
+ )!
163
+ const record: any = { camera: null, cursor: null }
164
+ const originalRecord = { ...record }
165
+ migration.up(record)
166
+
167
+ // Should be a noop
168
+ expect(record).toEqual(originalRecord)
169
+ })
170
+
171
+ it('should handle NullableCameraCursor migration down', () => {
172
+ const migration = instancePresenceMigrations.sequence.find(
173
+ (m) => m.id === instancePresenceVersions.NullableCameraCursor
174
+ )!
175
+ expect(migration.down).toBeDefined()
176
+
177
+ const record: any = {
178
+ camera: null,
179
+ lastActivityTimestamp: null,
180
+ cursor: null,
181
+ screenBounds: null,
182
+ }
183
+ migration.down!(record)
184
+
185
+ expect(record.camera).toEqual({ x: 0, y: 0, z: 1 })
186
+ expect(record.lastActivityTimestamp).toBe(0)
187
+ expect(record.cursor).toEqual({ type: 'default', x: 0, y: 0, rotation: 0 })
188
+ expect(record.screenBounds).toEqual({ x: 0, y: 0, w: 1, h: 1 })
189
+ })
190
+ })
@@ -14,7 +14,30 @@ import { scribbleValidator, TLScribble } from '../misc/TLScribble'
14
14
  import { TLPageId } from './TLPage'
15
15
  import { TLShapeId } from './TLShape'
16
16
 
17
- /** @public */
17
+ /**
18
+ * Represents the presence state of a user in a collaborative tldraw session.
19
+ * This record tracks what another user is doing: their cursor position, selected
20
+ * shapes, current page, and other real-time activity indicators.
21
+ *
22
+ * Instance presence records are used in multiplayer environments to show
23
+ * where other collaborators are working and what they're doing.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const presence: TLInstancePresence = {
28
+ * id: 'instance_presence:user123',
29
+ * typeName: 'instance_presence',
30
+ * userId: 'user123',
31
+ * userName: 'Alice',
32
+ * color: '#FF6B6B',
33
+ * cursor: { x: 100, y: 150, type: 'default', rotation: 0 },
34
+ * currentPageId: 'page:main',
35
+ * selectedShapeIds: ['shape:rect1']
36
+ * }
37
+ * ```
38
+ *
39
+ * @public
40
+ */
18
41
  export interface TLInstancePresence extends BaseRecord<'instance_presence', TLInstancePresenceID> {
19
42
  userId: string
20
43
  userName: string
@@ -37,10 +60,42 @@ export interface TLInstancePresence extends BaseRecord<'instance_presence', TLIn
37
60
  meta: JsonObject
38
61
  }
39
62
 
40
- /** @public */
63
+ /**
64
+ * A unique identifier for TLInstancePresence records.
65
+ *
66
+ * Instance presence IDs follow the format 'instance_presence:' followed
67
+ * by a unique identifier, typically the user ID.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const presenceId: TLInstancePresenceID = 'instance_presence:user123'
72
+ * ```
73
+ *
74
+ * @public
75
+ */
41
76
  export type TLInstancePresenceID = RecordId<TLInstancePresence>
42
77
 
43
- /** @public */
78
+ /**
79
+ * Runtime validator for TLInstancePresence records. Validates the structure
80
+ * and types of all instance presence properties to ensure data integrity.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const presence = {
85
+ * id: 'instance_presence:user1',
86
+ * typeName: 'instance_presence',
87
+ * userId: 'user1',
88
+ * userName: 'John',
89
+ * color: '#007AFF',
90
+ * cursor: { x: 0, y: 0, type: 'default', rotation: 0 },
91
+ * currentPageId: 'page:main',
92
+ * selectedShapeIds: []
93
+ * }
94
+ * const isValid = instancePresenceValidator.isValid(presence) // true
95
+ * ```
96
+ *
97
+ * @public
98
+ */
44
99
  export const instancePresenceValidator: T.Validator<TLInstancePresence> = T.model(
45
100
  'instance_presence',
46
101
  T.object({
@@ -72,7 +127,13 @@ export const instancePresenceValidator: T.Validator<TLInstancePresence> = T.mode
72
127
  })
73
128
  )
74
129
 
75
- /** @public */
130
+ /**
131
+ * Migration version identifiers for TLInstancePresence records. Each version
132
+ * represents a schema change that requires data transformation when loading
133
+ * older documents.
134
+ *
135
+ * @public
136
+ */
76
137
  export const instancePresenceVersions = createMigrationIds('com.tldraw.instance_presence', {
77
138
  AddScribbleDelay: 1,
78
139
  RemoveInstanceId: 2,
@@ -82,6 +143,19 @@ export const instancePresenceVersions = createMigrationIds('com.tldraw.instance_
82
143
  NullableCameraCursor: 6,
83
144
  } as const)
84
145
 
146
+ /**
147
+ * Migration sequence for TLInstancePresence records. Defines how to transform
148
+ * instance presence records between different schema versions, ensuring data
149
+ * compatibility when loading documents created with different versions.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * // Migrations are applied automatically when loading documents
154
+ * const migrated = instancePresenceMigrations.migrate(oldPresence, targetVersion)
155
+ * ```
156
+ *
157
+ * @public
158
+ */
85
159
  export const instancePresenceMigrations = createRecordMigrationSequence({
86
160
  sequenceId: 'com.tldraw.instance_presence',
87
161
  recordType: 'instance_presence',
@@ -141,7 +215,27 @@ export const instancePresenceMigrations = createRecordMigrationSequence({
141
215
  ],
142
216
  })
143
217
 
144
- /** @public */
218
+ /**
219
+ * The RecordType definition for TLInstancePresence records. Defines validation,
220
+ * scope, and default properties for instance presence records.
221
+ *
222
+ * Instance presence records are scoped to the presence level, meaning they
223
+ * represent real-time collaborative state that is ephemeral and tied to
224
+ * active user sessions.
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * const presence = InstancePresenceRecordType.create({
229
+ * id: 'instance_presence:user1',
230
+ * userId: 'user1',
231
+ * userName: 'Alice',
232
+ * color: '#FF6B6B',
233
+ * currentPageId: 'page:main'
234
+ * })
235
+ * ```
236
+ *
237
+ * @public
238
+ */
145
239
  export const InstancePresenceRecordType = createRecordType<TLInstancePresence>(
146
240
  'instance_presence',
147
241
  {
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { TLRecord } from './TLRecord'
3
+
4
+ describe('TLRecord', () => {
5
+ it('should support type discrimination by typeName', () => {
6
+ function processRecord(record: TLRecord): string {
7
+ // TypeScript should be able to narrow types based on typeName
8
+ switch (record.typeName) {
9
+ case 'shape':
10
+ return `Shape at (${record.x}, ${record.y})`
11
+ case 'page':
12
+ return `Page: ${record.name}`
13
+ case 'asset':
14
+ return `Asset: ${record.type}`
15
+ case 'binding':
16
+ return `Binding from ${record.fromId} to ${record.toId}`
17
+ case 'camera':
18
+ return `Camera at (${record.x}, ${record.y}) zoom: ${record.z}`
19
+ case 'document':
20
+ return `Document: ${record.name}, grid: ${record.gridSize}`
21
+ case 'instance':
22
+ return `Instance on page ${record.currentPageId}`
23
+ case 'instance_page_state':
24
+ return `Page state for ${record.pageId}`
25
+ case 'instance_presence':
26
+ return `Presence of ${record.userName}`
27
+ case 'pointer':
28
+ return `Pointer at (${record.x}, ${record.y})`
29
+ default:
30
+ // This should never be reached if all cases are handled
31
+ return 'Unknown record type'
32
+ }
33
+ }
34
+
35
+ // Test that the discriminated union works correctly
36
+ const shapeRecord: TLRecord = {
37
+ id: 'shape:test' as any,
38
+ typeName: 'shape',
39
+ type: 'geo',
40
+ x: 50,
41
+ y: 100,
42
+ rotation: 0,
43
+ index: 'a1' as any,
44
+ parentId: 'page:main' as any,
45
+ isLocked: false,
46
+ opacity: 1,
47
+ props: {
48
+ geo: 'rectangle',
49
+ w: 80,
50
+ h: 80,
51
+ color: 'black',
52
+ fill: 'none',
53
+ dash: 'draw',
54
+ size: 'm',
55
+ },
56
+ meta: {},
57
+ }
58
+
59
+ const pageRecord: TLRecord = {
60
+ id: 'page:test' as any,
61
+ typeName: 'page',
62
+ name: 'Test Page',
63
+ index: 'a1' as any,
64
+ meta: {},
65
+ }
66
+
67
+ expect(processRecord(shapeRecord)).toBe('Shape at (50, 100)')
68
+ expect(processRecord(pageRecord)).toBe('Page: Test Page')
69
+ })
70
+ })
@@ -9,7 +9,49 @@ import { TLPointer } from './TLPointer'
9
9
  import { TLInstancePresence } from './TLPresence'
10
10
  import { TLShape } from './TLShape'
11
11
 
12
- /** @public */
12
+ /**
13
+ * Union type representing all possible record types in a tldraw store.
14
+ * This includes both persistent records (documents, pages, shapes, assets, bindings)
15
+ * and session/presence records (cameras, instances, pointers, page states).
16
+ *
17
+ * Records are organized by scope:
18
+ * - **document**: Persisted across sessions (shapes, pages, assets, bindings, documents)
19
+ * - **session**: Local to current session (cameras, instances, page states)
20
+ * - **presence**: Ephemeral user presence data (pointers, instance presence)
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // Function that works with any record type
25
+ * function processRecord(record: TLRecord) {
26
+ * switch (record.typeName) {
27
+ * case 'shape':
28
+ * console.log(`Shape: ${record.type} at (${record.x}, ${record.y})`)
29
+ * break
30
+ * case 'page':
31
+ * console.log(`Page: ${record.name}`)
32
+ * break
33
+ * case 'asset':
34
+ * console.log(`Asset: ${record.type}`)
35
+ * break
36
+ * case 'camera':
37
+ * console.log(`Camera at (${record.x}, ${record.y}) zoom: ${record.z}`)
38
+ * break
39
+ * // ... handle other record types
40
+ * }
41
+ * }
42
+ *
43
+ * // Get all records from store
44
+ * const allRecords: TLRecord[] = store.allRecords()
45
+ *
46
+ * // Filter by record type using type guards
47
+ * import { isShape, isPage, isAsset } from '@tldraw/tlschema'
48
+ * const shapes = allRecords.filter(isShape)
49
+ * const pages = allRecords.filter(isPage)
50
+ * const assets = allRecords.filter(isAsset)
51
+ * ```
52
+ *
53
+ * @public
54
+ */
13
55
  export type TLRecord =
14
56
  | TLAsset
15
57
  | TLBinding