@tldraw/tlschema 4.6.0-next.1f489710ee41 → 4.6.0-next.4dde09fa17ab

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 (38) hide show
  1. package/dist-cjs/TLStore.js +13 -0
  2. package/dist-cjs/TLStore.js.map +2 -2
  3. package/dist-cjs/createPresenceStateDerivation.js +6 -4
  4. package/dist-cjs/createPresenceStateDerivation.js.map +2 -2
  5. package/dist-cjs/createTLSchema.js +8 -1
  6. package/dist-cjs/createTLSchema.js.map +2 -2
  7. package/dist-cjs/index.d.ts +211 -28
  8. package/dist-cjs/index.js +9 -1
  9. package/dist-cjs/index.js.map +2 -2
  10. package/dist-cjs/records/TLRecord.js.map +1 -1
  11. package/dist-cjs/records/TLUser.js +101 -0
  12. package/dist-cjs/records/TLUser.js.map +7 -0
  13. package/dist-cjs/shapes/TLNoteShape.js +13 -2
  14. package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
  15. package/dist-esm/TLStore.mjs +13 -0
  16. package/dist-esm/TLStore.mjs.map +2 -2
  17. package/dist-esm/createPresenceStateDerivation.mjs +6 -4
  18. package/dist-esm/createPresenceStateDerivation.mjs.map +2 -2
  19. package/dist-esm/createTLSchema.mjs +8 -1
  20. package/dist-esm/createTLSchema.mjs.map +2 -2
  21. package/dist-esm/index.d.mts +211 -28
  22. package/dist-esm/index.mjs +17 -1
  23. package/dist-esm/index.mjs.map +2 -2
  24. package/dist-esm/records/TLUser.mjs +85 -0
  25. package/dist-esm/records/TLUser.mjs.map +7 -0
  26. package/dist-esm/shapes/TLNoteShape.mjs +13 -2
  27. package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
  28. package/package.json +6 -6
  29. package/src/TLStore.test.ts +5 -0
  30. package/src/TLStore.ts +95 -1
  31. package/src/createPresenceStateDerivation.test.ts +33 -20
  32. package/src/createPresenceStateDerivation.ts +20 -25
  33. package/src/createTLSchema.ts +52 -0
  34. package/src/index.ts +13 -1
  35. package/src/migrations.test.ts +22 -0
  36. package/src/records/TLRecord.ts +2 -0
  37. package/src/records/TLUser.ts +148 -0
  38. package/src/shapes/TLNoteShape.ts +13 -0
@@ -15,6 +15,7 @@ import { instancePageStateVersions } from './records/TLPageState'
15
15
  import { pointerVersions } from './records/TLPointer'
16
16
  import { instancePresenceVersions } from './records/TLPresence'
17
17
  import { TLShape, rootShapeVersions } from './records/TLShape'
18
+ import { userVersions } from './records/TLUser'
18
19
  import { arrowShapeVersions } from './shapes/TLArrowShape'
19
20
  import { bookmarkShapeVersions } from './shapes/TLBookmarkShape'
20
21
  import { drawShapeVersions } from './shapes/TLDrawShape'
@@ -2603,6 +2604,27 @@ describe('LegacyPointsConversion migration for highlight shape', () => {
2603
2604
  })
2604
2605
  })
2605
2606
 
2607
+ describe('Adding textFirstEditedBy to note shape', () => {
2608
+ const { up, down } = getTestMigration(noteShapeVersions.AddFirstEditedBy)
2609
+
2610
+ test('up works as expected', () => {
2611
+ expect(up({ props: {} })).toEqual({ props: { textFirstEditedBy: null } })
2612
+ })
2613
+
2614
+ test('down works as expected', () => {
2615
+ expect(down({ props: { textFirstEditedBy: null } })).toEqual({ props: {} })
2616
+ })
2617
+ })
2618
+
2619
+ describe('TLUser initial migration', () => {
2620
+ const { up } = getTestMigration(userVersions.Initial)
2621
+
2622
+ test('up is a no-op', () => {
2623
+ const record = { id: 'user:123', name: 'Test', color: '#000', imageUrl: '', meta: {} }
2624
+ expect(up(record)).toEqual(record)
2625
+ })
2626
+ })
2627
+
2606
2628
  /* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
2607
2629
 
2608
2630
  // check that all migrator fns were called at least once
@@ -8,6 +8,7 @@ import { TLInstancePageState } from './TLPageState'
8
8
  import { TLPointer } from './TLPointer'
9
9
  import { TLInstancePresence } from './TLPresence'
10
10
  import { TLShape } from './TLShape'
11
+ import { TLUser } from './TLUser'
11
12
 
12
13
  /**
13
14
  * Interface for extending tldraw with custom record types via TypeScript module augmentation.
@@ -63,6 +64,7 @@ export type TLDefaultRecord =
63
64
  | TLShape
64
65
  | TLInstancePresence
65
66
  | TLPointer
67
+ | TLUser
66
68
 
67
69
  /**
68
70
  * Index type that maps custom record type names to their record types.
@@ -0,0 +1,148 @@
1
+ import {
2
+ BaseRecord,
3
+ createMigrationIds,
4
+ createRecordMigrationSequence,
5
+ createRecordType,
6
+ RecordId,
7
+ } from '@tldraw/store'
8
+ import { JsonObject } from '@tldraw/utils'
9
+ import { T } from '@tldraw/validate'
10
+ import { idValidator } from '../misc/id-validator'
11
+
12
+ /**
13
+ * A user record in a tldraw store. User records are document-scoped and
14
+ * persist alongside shapes, assets, and pages. They are automatically
15
+ * included in snapshots, clipboard content, and `.tldr` files so that
16
+ * attribution display names survive across boards and sessions.
17
+ *
18
+ * User records are populated from the {@link @tldraw/tlschema#TLUserStore}
19
+ * when the editor stamps attribution metadata onto shapes.
20
+ *
21
+ * Extend user records with custom metadata by passing validators to
22
+ * {@link @tldraw/tlschema#createTLSchema} or {@link createUserRecordType}.
23
+ *
24
+ * @public
25
+ */
26
+ export interface TLUser extends BaseRecord<'user', TLUserId> {
27
+ name: string
28
+ color: string
29
+ imageUrl: string
30
+ meta: JsonObject
31
+ }
32
+
33
+ /** @public */
34
+ export type TLUserId = RecordId<TLUser>
35
+
36
+ /** @public */
37
+ export const userIdValidator = idValidator<TLUserId>('user')
38
+
39
+ /** @public */
40
+ export const userVersions = createMigrationIds('com.tldraw.user', {
41
+ Initial: 1,
42
+ })
43
+
44
+ /** @public */
45
+ export const userMigrations = createRecordMigrationSequence({
46
+ sequenceId: 'com.tldraw.user',
47
+ recordType: 'user',
48
+ sequence: [
49
+ {
50
+ id: userVersions.Initial,
51
+ up: (_record: any) => {
52
+ // initial version — nothing to migrate
53
+ },
54
+ },
55
+ ],
56
+ })
57
+
58
+ /**
59
+ * Creates a user record type with optional custom meta validation.
60
+ *
61
+ * When `meta` validators are provided, the user record's `meta` field will
62
+ * validate those specific fields (when present) while still allowing
63
+ * arbitrary additional JSON properties. Custom meta fields are treated as
64
+ * optional so that user records created without them remain valid.
65
+ *
66
+ * @param config - Optional configuration for custom meta validators
67
+ * @returns A configured user record type
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { createUserRecordType } from '@tldraw/tlschema'
72
+ * import { T } from '@tldraw/validate'
73
+ *
74
+ * const CustomUserRecordType = createUserRecordType({
75
+ * meta: {
76
+ * isAdmin: T.boolean,
77
+ * department: T.string,
78
+ * },
79
+ * })
80
+ * ```
81
+ *
82
+ * @public
83
+ */
84
+ export function createUserRecordType(config?: { meta?: Record<string, T.Validatable<any>> }) {
85
+ const metaConfig = config?.meta
86
+
87
+ const metaValidator = metaConfig
88
+ ? T.object(
89
+ Object.fromEntries(
90
+ Object.entries(metaConfig).map(([key, v]) => [
91
+ key,
92
+ new T.Validator((value) => {
93
+ if (value === undefined) return undefined
94
+ return (v as T.Validator<unknown>).validate(value)
95
+ }),
96
+ ])
97
+ )
98
+ ).allowUnknownProperties()
99
+ : (T.jsonValue as T.ObjectValidator<JsonObject>)
100
+
101
+ const validator: T.Validator<TLUser> = T.model(
102
+ 'user',
103
+ T.object({
104
+ typeName: T.literal('user'),
105
+ id: userIdValidator,
106
+ name: T.string,
107
+ color: T.string,
108
+ imageUrl: T.string,
109
+ meta: metaValidator as T.ObjectValidator<JsonObject>,
110
+ })
111
+ )
112
+
113
+ return createRecordType<TLUser>('user', {
114
+ validator,
115
+ scope: 'document',
116
+ }).withDefaultProperties(() => ({
117
+ name: '',
118
+ color: '',
119
+ imageUrl: '',
120
+ meta: {},
121
+ }))
122
+ }
123
+
124
+ /** @public */
125
+ export const userValidator: T.Validator<TLUser> = T.model(
126
+ 'user',
127
+ T.object({
128
+ typeName: T.literal('user'),
129
+ id: userIdValidator,
130
+ name: T.string,
131
+ color: T.string,
132
+ imageUrl: T.string,
133
+ meta: T.jsonValue as T.ObjectValidator<JsonObject>,
134
+ })
135
+ )
136
+
137
+ /** @public */
138
+ export const UserRecordType = createUserRecordType()
139
+
140
+ /** @public */
141
+ export function isUserId(id: string): id is TLUserId {
142
+ return UserRecordType.isId(id)
143
+ }
144
+
145
+ /** @public */
146
+ export function createUserId(id: string): TLUserId {
147
+ return UserRecordType.createId(id)
148
+ }
@@ -64,6 +64,8 @@ export interface TLNoteShapeProps {
64
64
  richText: TLRichText
65
65
  /** Scale factor applied to the note shape for display */
66
66
  scale: number
67
+ /** User ID of the person who first edited the note text */
68
+ textFirstEditedBy: string | null
67
69
  }
68
70
 
69
71
  /**
@@ -130,6 +132,7 @@ export const noteShapeProps: RecordProps<TLNoteShape> = {
130
132
  url: T.linkUrl,
131
133
  richText: richTextValidator,
132
134
  scale: T.nonZeroNumber,
135
+ textFirstEditedBy: T.string.nullable(),
133
136
  }
134
137
 
135
138
  const Versions = createShapePropsMigrationIds('note', {
@@ -143,6 +146,7 @@ const Versions = createShapePropsMigrationIds('note', {
143
146
  AddLabelColor: 8,
144
147
  AddRichText: 9,
145
148
  AddRichTextAttrs: 10,
149
+ AddFirstEditedBy: 11,
146
150
  })
147
151
 
148
152
  /**
@@ -265,5 +269,14 @@ export const noteShapeMigrations = createShapePropsMigrationSequence({
265
269
  }
266
270
  },
267
271
  },
272
+ {
273
+ id: Versions.AddFirstEditedBy,
274
+ up: (props) => {
275
+ props.textFirstEditedBy = null
276
+ },
277
+ down: (props) => {
278
+ delete props.textFirstEditedBy
279
+ },
280
+ },
268
281
  ],
269
282
  })