@tldraw/tlschema 5.2.0-next.b91d4a4551c9 → 5.2.0-next.cd4a35fc06d5
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.
- package/DOCS.md +682 -0
- package/README.md +5 -1
- package/dist-cjs/index.js +1 -1
- package/dist-esm/index.mjs +1 -1
- package/package.json +8 -7
package/DOCS.md
ADDED
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
# @tldraw/tlschema
|
|
2
|
+
|
|
3
|
+
The schema package defines the type system, data structures, validation, and migrations for tldraw's persisted data. It provides a complete, type-safe, and version-aware data model that powers the tldraw editor.
|
|
4
|
+
|
|
5
|
+
## 1. Introduction
|
|
6
|
+
|
|
7
|
+
**@tldraw/tlschema** is the foundational package that defines how tldraw stores and manages data. It contains:
|
|
8
|
+
|
|
9
|
+
- **Record types** for all persisted data (shapes, assets, pages, user state)
|
|
10
|
+
- **Validation schemas** that ensure data integrity at runtime
|
|
11
|
+
- **Migration sequences** that handle data evolution over time
|
|
12
|
+
- **Style properties** that enable consistent styling across shapes
|
|
13
|
+
|
|
14
|
+
You'll use this package when creating custom shapes, defining your own data schemas, or when you need to work with tldraw's data structures directly.
|
|
15
|
+
|
|
16
|
+
## 2. Core Concepts
|
|
17
|
+
|
|
18
|
+
### Schemas: The Foundation
|
|
19
|
+
|
|
20
|
+
A **schema** defines the structure of your tldraw store. It specifies what types of records can exist, how they're validated, and how they evolve over time.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createTLSchema, defaultShapeSchemas } from '@tldraw/tlschema'
|
|
24
|
+
|
|
25
|
+
// Create a schema with default shapes
|
|
26
|
+
const schema = createTLSchema({
|
|
27
|
+
shapes: defaultShapeSchemas,
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Records: The Data Units
|
|
32
|
+
|
|
33
|
+
**Records** are the individual pieces of data stored in tldraw. Every record has a type, an ID, and properties specific to that type:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { TLShape, TLPage, TLAsset } from '@tldraw/tlschema'
|
|
37
|
+
|
|
38
|
+
// All records extend BaseRecord
|
|
39
|
+
const shape: TLShape = {
|
|
40
|
+
id: 'shape:abc123',
|
|
41
|
+
typeName: 'shape',
|
|
42
|
+
type: 'geo',
|
|
43
|
+
x: 100,
|
|
44
|
+
y: 200,
|
|
45
|
+
rotation: 0,
|
|
46
|
+
// ... other properties
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Style Properties: Shared Styling
|
|
51
|
+
|
|
52
|
+
**Style properties** are special properties that can be applied across multiple shapes and persist for new shape creation:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { StyleProp } from '@tldraw/tlschema'
|
|
56
|
+
import { T } from '@tldraw/validate'
|
|
57
|
+
|
|
58
|
+
// Define a custom style property
|
|
59
|
+
const MyCustomStyle = StyleProp.define('myapp:custom', {
|
|
60
|
+
defaultValue: 'default',
|
|
61
|
+
type: T.string,
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 3. Basic Usage
|
|
66
|
+
|
|
67
|
+
### Creating a Custom Schema
|
|
68
|
+
|
|
69
|
+
You create schemas by combining shape configurations, bindings, and migrations:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { createTLSchema, defaultShapeSchemas, defaultBindingSchemas } from '@tldraw/tlschema'
|
|
73
|
+
|
|
74
|
+
const schema = createTLSchema({
|
|
75
|
+
shapes: {
|
|
76
|
+
...defaultShapeSchemas,
|
|
77
|
+
// Add custom shapes here
|
|
78
|
+
},
|
|
79
|
+
bindings: defaultBindingSchemas,
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Working with Shape Records
|
|
84
|
+
|
|
85
|
+
Shape records are the most common type you'll work with. Every shape extends `TLBaseShape`:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { createShapeValidator } from '@tldraw/tlschema'
|
|
89
|
+
import { T } from '@tldraw/validate'
|
|
90
|
+
|
|
91
|
+
const CUSTOM_TYPE = 'custom'
|
|
92
|
+
|
|
93
|
+
// Define a custom shape type
|
|
94
|
+
declare module '@tldraw/tlschema' {
|
|
95
|
+
export interface TLGlobalShapePropsMap {
|
|
96
|
+
[CUSTOM_TYPE]: MyCustomShapeProps
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface MyCustomShapeProps {
|
|
101
|
+
width: number
|
|
102
|
+
height: number
|
|
103
|
+
color: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type MyCustomShape = TLShape<typeof CUSTOM_TYPE>
|
|
107
|
+
|
|
108
|
+
// Create validation for your shape
|
|
109
|
+
const customShapeValidator = createShapeValidator(CUSTOM_TYPE, {
|
|
110
|
+
width: T.number,
|
|
111
|
+
height: T.number,
|
|
112
|
+
color: T.string,
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Integrating with Store
|
|
117
|
+
|
|
118
|
+
Once you have a schema, you use it to create a store:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { Store } from '@tldraw/store'
|
|
122
|
+
import { TLStoreProps } from '@tldraw/tlschema'
|
|
123
|
+
|
|
124
|
+
const store = new Store({
|
|
125
|
+
schema,
|
|
126
|
+
props: {
|
|
127
|
+
defaultName: 'My Drawing',
|
|
128
|
+
assets: myAssetStore, // Your asset storage implementation
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 4. Advanced Topics
|
|
134
|
+
|
|
135
|
+
### Creating Custom Shapes
|
|
136
|
+
|
|
137
|
+
When creating custom shapes, follow this pattern for complete integration:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import {
|
|
141
|
+
createShapeValidator,
|
|
142
|
+
createShapePropsMigrationSequence,
|
|
143
|
+
RecordProps,
|
|
144
|
+
} from '@tldraw/tlschema'
|
|
145
|
+
import { DefaultColorStyle } from '@tldraw/tlschema'
|
|
146
|
+
|
|
147
|
+
const MY_SHAPE_TYPE = 'myshape'
|
|
148
|
+
|
|
149
|
+
// 1. Define the shape
|
|
150
|
+
declare module '@tldraw/tlschema' {
|
|
151
|
+
export interface TLGlobalShapePropsMap {
|
|
152
|
+
[MY_SHAPE_TYPE]: MyShapeProps
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
interface MyShapeProps {
|
|
157
|
+
color: typeof DefaultColorStyle // Use existing style
|
|
158
|
+
width: number
|
|
159
|
+
height: number
|
|
160
|
+
customData: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type MyShape = TLShape<typeof MY_SHAPE_TYPE>
|
|
164
|
+
|
|
165
|
+
// 2. Create props validation
|
|
166
|
+
const myShapeProps: RecordProps<MyShape> = {
|
|
167
|
+
color: DefaultColorStyle,
|
|
168
|
+
width: T.number,
|
|
169
|
+
height: T.number,
|
|
170
|
+
customData: T.string,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 3. Define migrations for schema evolution
|
|
174
|
+
const myShapeMigrations = createShapePropsMigrationSequence({
|
|
175
|
+
sequenceId: 'com.myapp.shape.myshape',
|
|
176
|
+
sequence: [
|
|
177
|
+
{
|
|
178
|
+
id: 'com.myapp.shape.myshape/1.0.0',
|
|
179
|
+
up: (props) => props, // Initial version
|
|
180
|
+
down: (props) => props,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// 4. Add to schema
|
|
186
|
+
const schema = createTLSchema({
|
|
187
|
+
shapes: {
|
|
188
|
+
...defaultShapeSchemas,
|
|
189
|
+
myshape: {
|
|
190
|
+
props: myShapeProps,
|
|
191
|
+
migrations: myShapeMigrations,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Custom Style Properties
|
|
198
|
+
|
|
199
|
+
Style properties enable consistent styling across shapes and remember the last used value:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { StyleProp, EnumStyleProp } from '@tldraw/tlschema'
|
|
203
|
+
import { T } from '@tldraw/validate'
|
|
204
|
+
|
|
205
|
+
// Free-form style property
|
|
206
|
+
const MyWidthStyle = StyleProp.define('myapp:width', {
|
|
207
|
+
defaultValue: 2,
|
|
208
|
+
type: T.number,
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// Enum-based style property
|
|
212
|
+
const MyPatternStyle = StyleProp.defineEnum('myapp:pattern', {
|
|
213
|
+
defaultValue: 'solid',
|
|
214
|
+
values: ['solid', 'dashed', 'dotted'],
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Use in shape props
|
|
218
|
+
interface MyShapeProps {
|
|
219
|
+
width: typeof MyWidthStyle
|
|
220
|
+
pattern: typeof MyPatternStyle
|
|
221
|
+
// other props...
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Asset Management
|
|
226
|
+
|
|
227
|
+
Assets represent external resources like images, videos, or bookmarks:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { TLImageAsset, TLAssetStore } from '@tldraw/tlschema'
|
|
231
|
+
|
|
232
|
+
// Implement asset storage
|
|
233
|
+
const assetStore: TLAssetStore = {
|
|
234
|
+
async upload(asset, file) {
|
|
235
|
+
// Upload file to your storage service
|
|
236
|
+
const src = await uploadToStorage(file)
|
|
237
|
+
return { src }
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
async resolve(asset, context) {
|
|
241
|
+
// Resolve asset URL for rendering
|
|
242
|
+
return asset.props.src
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
async remove(assetIds) {
|
|
246
|
+
// Clean up removed assets
|
|
247
|
+
await deleteFromStorage(assetIds)
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Migration Strategies
|
|
253
|
+
|
|
254
|
+
Migrations handle schema evolution as your application develops:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { createShapePropsMigrationSequence } from '@tldraw/tlschema'
|
|
258
|
+
|
|
259
|
+
const migrations = createShapePropsMigrationSequence({
|
|
260
|
+
sequenceId: 'com.myapp.shape.custom',
|
|
261
|
+
sequence: [
|
|
262
|
+
{
|
|
263
|
+
id: 'com.myapp.shape.custom/1.1.0',
|
|
264
|
+
up: (props) => {
|
|
265
|
+
// Add new property with default value
|
|
266
|
+
return { ...props, newProperty: 'default' }
|
|
267
|
+
},
|
|
268
|
+
down: ({ newProperty, ...props }) => {
|
|
269
|
+
// Remove property for backward compatibility
|
|
270
|
+
return props
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: 'com.myapp.shape.custom/1.2.0',
|
|
275
|
+
up: (props) => {
|
|
276
|
+
// Rename property
|
|
277
|
+
return {
|
|
278
|
+
...props,
|
|
279
|
+
renamedProperty: props.oldProperty,
|
|
280
|
+
oldProperty: undefined,
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
down: (props) => {
|
|
284
|
+
return {
|
|
285
|
+
...props,
|
|
286
|
+
oldProperty: props.renamedProperty,
|
|
287
|
+
renamedProperty: undefined,
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## 5. Built-in Shape Types
|
|
296
|
+
|
|
297
|
+
### Default Shapes
|
|
298
|
+
|
|
299
|
+
tldraw includes several built-in shape types:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import {
|
|
303
|
+
TLGeoShape, // Rectangles, ellipses, triangles, etc.
|
|
304
|
+
TLTextShape, // Text with rich formatting
|
|
305
|
+
TLDrawShape, // Freehand drawing paths
|
|
306
|
+
TLArrowShape, // Arrows with optional binding to shapes
|
|
307
|
+
TLLineShape, // Multi-point lines and splines
|
|
308
|
+
TLImageShape, // Raster images
|
|
309
|
+
TLVideoShape, // Video files
|
|
310
|
+
TLNoteShape, // Sticky notes
|
|
311
|
+
TLBookmarkShape, // Website bookmarks
|
|
312
|
+
TLEmbedShape, // Embedded content (YouTube, Figma, etc.)
|
|
313
|
+
TLFrameShape, // Frames for grouping content
|
|
314
|
+
TLGroupShape, // Groups for organizing shapes
|
|
315
|
+
TLHighlightShape, // Highlighting tool strokes
|
|
316
|
+
} from '@tldraw/tlschema'
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Shape Properties
|
|
320
|
+
|
|
321
|
+
All shapes share common base properties from `TLBaseShape`:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
interface TLBaseShape<Type, Props> {
|
|
325
|
+
id: TLShapeId
|
|
326
|
+
type: Type
|
|
327
|
+
x: number // Position X
|
|
328
|
+
y: number // Position Y
|
|
329
|
+
rotation: number // Rotation in radians
|
|
330
|
+
index: IndexKey // Fractional index for ordering
|
|
331
|
+
parentId: TLParentId // Parent page or shape
|
|
332
|
+
isLocked: boolean // Whether shape can be selected
|
|
333
|
+
opacity: TLOpacityType // Transparency (0-1)
|
|
334
|
+
props: Props // Shape-specific properties
|
|
335
|
+
meta: JsonObject // User-defined metadata
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Geo Shapes
|
|
340
|
+
|
|
341
|
+
Geometric shapes support various styles and configurations:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { TLGeoShape, GeoShapeGeoStyle } from '@tldraw/tlschema'
|
|
345
|
+
|
|
346
|
+
// Geo shapes can be rectangles, ellipses, triangles, etc.
|
|
347
|
+
const geoShape: TLGeoShape = {
|
|
348
|
+
// ... base properties
|
|
349
|
+
props: {
|
|
350
|
+
geo: GeoShapeGeoStyle, // 'rectangle', 'ellipse', 'triangle', etc.
|
|
351
|
+
w: 100, // Width
|
|
352
|
+
h: 80, // Height
|
|
353
|
+
color: 'blue', // Color style
|
|
354
|
+
fill: 'solid', // Fill style
|
|
355
|
+
dash: 'solid', // Dash style
|
|
356
|
+
size: 'm', // Size style
|
|
357
|
+
richText: null, // Optional text content
|
|
358
|
+
},
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 6. Validation and Type Safety
|
|
363
|
+
|
|
364
|
+
### Runtime Validation
|
|
365
|
+
|
|
366
|
+
All records are validated at runtime to ensure data integrity:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { T } from '@tldraw/validate'
|
|
370
|
+
import { createShapeValidator } from '@tldraw/tlschema'
|
|
371
|
+
|
|
372
|
+
// Validation happens automatically when records enter the store
|
|
373
|
+
const validator = createShapeValidator('myshape', {
|
|
374
|
+
width: T.number.check((n) => n > 0), // Custom validation
|
|
375
|
+
height: T.number.check((n) => n > 0),
|
|
376
|
+
color: T.string,
|
|
377
|
+
})
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Error Handling
|
|
381
|
+
|
|
382
|
+
When validation fails, you can handle errors gracefully:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
try {
|
|
386
|
+
store.put([invalidRecord])
|
|
387
|
+
} catch (error) {
|
|
388
|
+
if (error instanceof ValidationError) {
|
|
389
|
+
console.log('Validation failed:', error.message)
|
|
390
|
+
// Handle validation error appropriately
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Type-Safe IDs
|
|
396
|
+
|
|
397
|
+
Record IDs are strongly typed to prevent mixing different record types:
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import { TLShapeId, TLPageId, createShapeId } from '@tldraw/tlschema'
|
|
401
|
+
|
|
402
|
+
// IDs are branded types - compiler prevents mixing them up
|
|
403
|
+
const shapeId: TLShapeId = createShapeId()
|
|
404
|
+
const pageId: TLPageId = 'page:123' // TypeScript error if you use wrong format
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## 7. Integration with @tldraw/store
|
|
408
|
+
|
|
409
|
+
### Store Configuration
|
|
410
|
+
|
|
411
|
+
The schema integrates with the store system to provide reactive data management:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { Store } from '@tldraw/store'
|
|
415
|
+
import { TLStoreProps, createTLSchema } from '@tldraw/tlschema'
|
|
416
|
+
|
|
417
|
+
const schema = createTLSchema()
|
|
418
|
+
const store = new Store({
|
|
419
|
+
schema,
|
|
420
|
+
props: {
|
|
421
|
+
defaultName: 'Untitled',
|
|
422
|
+
assets: assetStore,
|
|
423
|
+
onMount: (editor) => {
|
|
424
|
+
// Initialize when editor mounts
|
|
425
|
+
console.log('Editor mounted with store')
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
})
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Reactive Queries
|
|
432
|
+
|
|
433
|
+
The store provides reactive access to records:
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
import { track } from '@tldraw/state'
|
|
437
|
+
|
|
438
|
+
// This function will re-run when shapes change
|
|
439
|
+
const ShapeCounter = track(() => {
|
|
440
|
+
const shapes = store.query.records('shape').get()
|
|
441
|
+
return `Total shapes: ${shapes.length}`
|
|
442
|
+
})
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## 8. Performance Considerations
|
|
446
|
+
|
|
447
|
+
### Validation Optimization
|
|
448
|
+
|
|
449
|
+
- Use `validateUsingKnownGoodVersion()` when you know data is already valid
|
|
450
|
+
- Minimize validation in hot paths during user interactions
|
|
451
|
+
- Consider validation levels (development vs production)
|
|
452
|
+
|
|
453
|
+
### Memory Management
|
|
454
|
+
|
|
455
|
+
- Style properties are shared across shapes to reduce memory usage
|
|
456
|
+
- Records use immutable structures to prevent accidental mutations
|
|
457
|
+
- `devFreeze()` helps catch mutation bugs in development
|
|
458
|
+
|
|
459
|
+
### Migration Performance
|
|
460
|
+
|
|
461
|
+
- Group related changes into single migration steps
|
|
462
|
+
- Test migration performance with realistic data sizes
|
|
463
|
+
- Use migration sequence IDs to optimize dependency resolution
|
|
464
|
+
|
|
465
|
+
## 9. Debugging
|
|
466
|
+
|
|
467
|
+
### Understanding Schema Structure
|
|
468
|
+
|
|
469
|
+
You can inspect your schema to understand its configuration:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
const schema = createTLSchema()
|
|
473
|
+
|
|
474
|
+
// Examine record types
|
|
475
|
+
console.log('Record types:', Object.keys(schema.types))
|
|
476
|
+
|
|
477
|
+
// Check validation for specific record type
|
|
478
|
+
const shapeValidator = schema.types.shape
|
|
479
|
+
console.log('Shape validator:', shapeValidator)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Migration Debugging
|
|
483
|
+
|
|
484
|
+
When migrations fail, examine the migration sequence:
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
// Check migration history
|
|
488
|
+
const migrations = schema.sortedMigrations
|
|
489
|
+
console.log(
|
|
490
|
+
'Migration sequence:',
|
|
491
|
+
migrations.map((m) => m.id)
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
// Test individual migrations
|
|
495
|
+
try {
|
|
496
|
+
const migrated = migrator.migrateStoreSnapshot({
|
|
497
|
+
schema: oldSchema,
|
|
498
|
+
store: snapshot,
|
|
499
|
+
})
|
|
500
|
+
console.log('Migration successful')
|
|
501
|
+
} catch (error) {
|
|
502
|
+
console.error('Migration failed:', error)
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Validation Errors
|
|
507
|
+
|
|
508
|
+
When records fail validation, examine the validation path:
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
import { T } from '@tldraw/validate'
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
shapeValidator.validate(invalidShape)
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.log('Validation path:', error.path)
|
|
517
|
+
console.log('Validation message:', error.message)
|
|
518
|
+
console.log('Invalid value:', error.value)
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## 10. Common Patterns
|
|
523
|
+
|
|
524
|
+
### Shape with Asset References
|
|
525
|
+
|
|
526
|
+
Many shapes reference assets for their content:
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
interface MediaShapeProps {
|
|
530
|
+
assetId: TLAssetId | null
|
|
531
|
+
width: number
|
|
532
|
+
height: number
|
|
533
|
+
crop?: TLShapeCrop // Optional cropping info
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Usage pattern
|
|
537
|
+
const imageShape: TLImageShape = {
|
|
538
|
+
// ... base properties
|
|
539
|
+
props: {
|
|
540
|
+
assetId: 'asset:image123',
|
|
541
|
+
w: 200,
|
|
542
|
+
h: 150,
|
|
543
|
+
crop: {
|
|
544
|
+
topLeft: { x: 0, y: 0 },
|
|
545
|
+
bottomRight: { x: 1, y: 1 },
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Binding Relationships
|
|
552
|
+
|
|
553
|
+
Arrows can bind to other shapes:
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
import { TLArrowBinding } from '@tldraw/tlschema'
|
|
557
|
+
|
|
558
|
+
const arrowBinding: TLArrowBinding = {
|
|
559
|
+
id: 'binding:abc123',
|
|
560
|
+
typeName: 'binding',
|
|
561
|
+
type: 'arrow',
|
|
562
|
+
fromId: 'shape:arrow1', // Arrow shape ID
|
|
563
|
+
toId: 'shape:rectangle1', // Target shape ID
|
|
564
|
+
props: {
|
|
565
|
+
terminal: 'end', // 'start' or 'end'
|
|
566
|
+
normalizedAnchor: { x: 0.5, y: 0.5 }, // Position on target
|
|
567
|
+
isExact: false, // Whether position is exact
|
|
568
|
+
isPrecise: true, // Whether binding is precise
|
|
569
|
+
},
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Rich Text Content
|
|
574
|
+
|
|
575
|
+
Shapes can contain formatted text:
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
import { TLRichText, toRichText } from '@tldraw/tlschema'
|
|
579
|
+
|
|
580
|
+
const richText: TLRichText = toRichText('Hello **bold** text')
|
|
581
|
+
|
|
582
|
+
const textShape: TLTextShape = {
|
|
583
|
+
// ... base properties
|
|
584
|
+
props: {
|
|
585
|
+
color: 'black',
|
|
586
|
+
size: 'm',
|
|
587
|
+
font: 'draw',
|
|
588
|
+
textAlign: 'start',
|
|
589
|
+
richText: richText,
|
|
590
|
+
autoSize: true,
|
|
591
|
+
scale: 1,
|
|
592
|
+
},
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## 11. Best Practices
|
|
597
|
+
|
|
598
|
+
### Schema Design
|
|
599
|
+
|
|
600
|
+
- **Start simple** - Begin with minimal shape properties and add complexity gradually
|
|
601
|
+
- **Use existing styles** - Leverage built-in style properties before creating custom ones
|
|
602
|
+
- **Plan for evolution** - Design your schema with migrations in mind
|
|
603
|
+
- **Validate thoroughly** - Include validation for edge cases and invalid states
|
|
604
|
+
|
|
605
|
+
### Migration Strategy
|
|
606
|
+
|
|
607
|
+
- **Version incrementally** - Use semantic versioning in migration IDs
|
|
608
|
+
- **Test both directions** - Ensure up and down migrations work correctly
|
|
609
|
+
- **Handle failures gracefully** - Provide fallbacks when migrations fail
|
|
610
|
+
- **Document breaking changes** - Clearly communicate migration requirements
|
|
611
|
+
|
|
612
|
+
### Performance Optimization
|
|
613
|
+
|
|
614
|
+
- **Minimize validation overhead** - Use efficient validators for frequently accessed properties
|
|
615
|
+
- **Batch related changes** - Group property updates to reduce reactive updates
|
|
616
|
+
- **Use appropriate record scopes** - Choose correct scope (document/session/presence) for data
|
|
617
|
+
- **Optimize asset handling** - Implement efficient asset storage and resolution
|
|
618
|
+
|
|
619
|
+
### Error Handling
|
|
620
|
+
|
|
621
|
+
- **Validate early** - Catch validation errors as close to the source as possible
|
|
622
|
+
- **Provide clear messages** - Use descriptive error messages for debugging
|
|
623
|
+
- **Handle partial failures** - Design systems that can recover from individual record failures
|
|
624
|
+
- **Log strategically** - Include enough context for debugging without overwhelming logs
|
|
625
|
+
|
|
626
|
+
## 12. Extension Points
|
|
627
|
+
|
|
628
|
+
### Custom Shapes
|
|
629
|
+
|
|
630
|
+
Extend the built-in shape system:
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
// Add custom shapes to default schemas
|
|
634
|
+
const customSchema = createTLSchema({
|
|
635
|
+
shapes: {
|
|
636
|
+
...defaultShapeSchemas,
|
|
637
|
+
myCustomShape: customShapeConfig,
|
|
638
|
+
},
|
|
639
|
+
})
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Custom Bindings
|
|
643
|
+
|
|
644
|
+
Create new types of shape relationships:
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
const CUSTOM_TYPE = 'custom'
|
|
648
|
+
|
|
649
|
+
// Define custom binding types
|
|
650
|
+
declare module '@tldraw/tlschema' {
|
|
651
|
+
export interface TLGlobalBindingPropsMap {
|
|
652
|
+
[CUSTOM_TYPE]: MyBindingProps
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
type MyCustomBinding = TLBinding<typeof CUSTOM_TYPE>
|
|
657
|
+
|
|
658
|
+
const customBindingConfig = {
|
|
659
|
+
props: myBindingProps,
|
|
660
|
+
migrations: myBindingMigrations,
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Asset Storage
|
|
665
|
+
|
|
666
|
+
Implement custom asset storage backends:
|
|
667
|
+
|
|
668
|
+
```ts
|
|
669
|
+
const customAssetStore: TLAssetStore = {
|
|
670
|
+
async upload(asset, file) {
|
|
671
|
+
return await myCloudStorage.upload(file)
|
|
672
|
+
},
|
|
673
|
+
async resolve(asset, context) {
|
|
674
|
+
return await myCloudStorage.getUrl(asset.props.src, context)
|
|
675
|
+
},
|
|
676
|
+
async remove(assetIds) {
|
|
677
|
+
await Promise.all(assetIds.map((id) => myCloudStorage.delete(id)))
|
|
678
|
+
},
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
The tlschema package provides a robust foundation for building applications with tldraw. By following these patterns and understanding the core concepts, you can create custom shapes, manage data effectively, and build experiences that scale with your users' needs.
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Type definitions, schema migrations, and other type metadata for the tldraw editor's default persisted data.
|
|
4
4
|
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
A `DOCS.md` file is included alongside this README in the published package, with detailed API documentation and usage examples.
|
|
8
|
+
|
|
5
9
|
## Records
|
|
6
10
|
|
|
7
11
|
There are three main kinds of types:
|
|
@@ -80,7 +84,7 @@ You can find tldraw on npm [here](https://www.npmjs.com/package/@tldraw/tldraw?a
|
|
|
80
84
|
|
|
81
85
|
## Contribution
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
Found a bug? Please [submit an issue](https://github.com/tldraw/tldraw/issues/new).
|
|
84
88
|
|
|
85
89
|
## Community
|
|
86
90
|
|
package/dist-cjs/index.js
CHANGED
|
@@ -205,7 +205,7 @@ var import_translations = require("./translations/translations");
|
|
|
205
205
|
var import_b64Vecs = require("./misc/b64Vecs");
|
|
206
206
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
207
207
|
"@tldraw/tlschema",
|
|
208
|
-
"5.2.0-next.
|
|
208
|
+
"5.2.0-next.cd4a35fc06d5",
|
|
209
209
|
"cjs"
|
|
210
210
|
);
|
|
211
211
|
//# sourceMappingURL=index.js.map
|
package/dist-esm/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/tlschema",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (schema).",
|
|
4
|
-
"version": "5.2.0-next.
|
|
4
|
+
"version": "5.2.0-next.cd4a35fc06d5",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"dist-esm",
|
|
31
31
|
"dist-cjs",
|
|
32
|
-
"src"
|
|
32
|
+
"src",
|
|
33
|
+
"DOCS.md"
|
|
33
34
|
],
|
|
34
35
|
"scripts": {
|
|
35
36
|
"test-ci": "yarn run -T vitest run --passWithNoTests",
|
|
@@ -47,13 +48,13 @@
|
|
|
47
48
|
"kleur": "^4.1.5",
|
|
48
49
|
"lazyrepo": "0.0.0-alpha.27",
|
|
49
50
|
"react": "^19.2.1",
|
|
50
|
-
"vitest": "^
|
|
51
|
+
"vitest": "^4.1.7"
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
|
-
"@tldraw/state": "5.2.0-next.
|
|
54
|
-
"@tldraw/store": "5.2.0-next.
|
|
55
|
-
"@tldraw/utils": "5.2.0-next.
|
|
56
|
-
"@tldraw/validate": "5.2.0-next.
|
|
54
|
+
"@tldraw/state": "5.2.0-next.cd4a35fc06d5",
|
|
55
|
+
"@tldraw/store": "5.2.0-next.cd4a35fc06d5",
|
|
56
|
+
"@tldraw/utils": "5.2.0-next.cd4a35fc06d5",
|
|
57
|
+
"@tldraw/validate": "5.2.0-next.cd4a35fc06d5"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
60
|
"react": "^18.2.0 || ^19.2.1",
|