import { Migrations, StoreSchema, createRecordType, defineMigrations } from '@tldraw/tlstore' import { T } from '@tldraw/tlvalidate' import { TLRecord } from './TLRecord' import { TLStoreProps, createIntegrityChecker, onValidationFailure } from './TLStore' import { AssetRecordType } from './records/TLAsset' import { CameraRecordType } from './records/TLCamera' import { DocumentRecordType } from './records/TLDocument' import { InstanceRecordType } from './records/TLInstance' import { InstancePageStateRecordType } from './records/TLInstancePageState' import { InstancePresenceRecordType } from './records/TLInstancePresence' import { PageRecordType } from './records/TLPage' import { PointerRecordType } from './records/TLPointer' import { TLShape, TLUnknownShape, rootShapeTypeMigrations } from './records/TLShape' import { UserDocumentRecordType } from './records/TLUserDocument' import { storeMigrations } from './schema' import { arrowShapeTypeMigrations, arrowShapeTypeValidator } from './shapes/TLArrowShape' import { bookmarkShapeTypeMigrations, bookmarkShapeTypeValidator } from './shapes/TLBookmarkShape' import { drawShapeTypeMigrations, drawShapeTypeValidator } from './shapes/TLDrawShape' import { embedShapeTypeMigrations, embedShapeTypeValidator } from './shapes/TLEmbedShape' import { frameShapeTypeMigrations, frameShapeTypeValidator } from './shapes/TLFrameShape' import { geoShapeTypeMigrations, geoShapeTypeValidator } from './shapes/TLGeoShape' import { groupShapeTypeMigrations, groupShapeTypeValidator } from './shapes/TLGroupShape' import { highlightShapeMigrations, highlightShapeTypeValidator } from './shapes/TLHighlightShape' import { imageShapeTypeMigrations, imageShapeTypeValidator } from './shapes/TLImageShape' import { lineShapeTypeMigrations, lineShapeTypeValidator } from './shapes/TLLineShape' import { noteShapeTypeMigrations, noteShapeTypeValidator } from './shapes/TLNoteShape' import { textShapeTypeMigrations, textShapeTypeValidator } from './shapes/TLTextShape' import { videoShapeTypeMigrations, videoShapeTypeValidator } from './shapes/TLVideoShape' type DefaultShapeInfo = { validator: T.Validator migrations: Migrations } const DEFAULT_SHAPES: { [K in TLShape['type']]: DefaultShapeInfo> } = { arrow: { migrations: arrowShapeTypeMigrations, validator: arrowShapeTypeValidator }, bookmark: { migrations: bookmarkShapeTypeMigrations, validator: bookmarkShapeTypeValidator }, draw: { migrations: drawShapeTypeMigrations, validator: drawShapeTypeValidator }, embed: { migrations: embedShapeTypeMigrations, validator: embedShapeTypeValidator }, frame: { migrations: frameShapeTypeMigrations, validator: frameShapeTypeValidator }, geo: { migrations: geoShapeTypeMigrations, validator: geoShapeTypeValidator }, group: { migrations: groupShapeTypeMigrations, validator: groupShapeTypeValidator }, image: { migrations: imageShapeTypeMigrations, validator: imageShapeTypeValidator }, line: { migrations: lineShapeTypeMigrations, validator: lineShapeTypeValidator }, note: { migrations: noteShapeTypeMigrations, validator: noteShapeTypeValidator }, text: { migrations: textShapeTypeMigrations, validator: textShapeTypeValidator }, video: { migrations: videoShapeTypeMigrations, validator: videoShapeTypeValidator }, highlight: { migrations: highlightShapeMigrations, validator: highlightShapeTypeValidator }, } type CustomShapeInfo = { validator?: { validate: (record: T) => T } migrations?: Migrations } /** * Create a store schema for a tldraw store that includes all the default shapes together with any custom shapes. * @public */ export function createTLSchema( opts = {} as { customShapes?: { [K in T['type']]: CustomShapeInfo } } ) { const { customShapes = {} } = opts const defaultShapeSubTypeEntries = Object.entries(DEFAULT_SHAPES) as [ TLShape['type'], DefaultShapeInfo ][] const customShapeSubTypeEntries = Object.entries(customShapes) as [ T['type'], CustomShapeInfo ][] // Create a shape record that incorporates the default shapes and any custom shapes // into its subtype migrations and validators, so that we can migrate any new custom // subtypes. Note that migrations AND validators for custom shapes are optional. If // not provided, we use an empty migrations set and/or an "any" validator. const shapeSubTypeMigrationsWithCustomSubTypeMigrations = { ...Object.fromEntries(defaultShapeSubTypeEntries.map(([k, v]) => [k, v.migrations])), ...Object.fromEntries( customShapeSubTypeEntries.map(([k, v]) => [k, v.migrations ?? defineMigrations({})]) ), } const validatorWithCustomShapeValidators = T.model( 'shape', T.union('type', { ...Object.fromEntries(defaultShapeSubTypeEntries.map(([k, v]) => [k, v.validator])), ...Object.fromEntries( customShapeSubTypeEntries.map(([k, v]) => [k, (v.validator as T.Validator) ?? T.any]) ), }) ) const shapeRecord = createRecordType('shape', { migrations: defineMigrations({ currentVersion: rootShapeTypeMigrations.currentVersion, firstVersion: rootShapeTypeMigrations.firstVersion, migrators: rootShapeTypeMigrations.migrators, subTypeKey: 'type', subTypeMigrations: shapeSubTypeMigrationsWithCustomSubTypeMigrations, }), validator: validatorWithCustomShapeValidators, scope: 'document', }).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false })) return StoreSchema.create( { asset: AssetRecordType, camera: CameraRecordType, document: DocumentRecordType, instance: InstanceRecordType, instance_page_state: InstancePageStateRecordType, page: PageRecordType, shape: shapeRecord, user_document: UserDocumentRecordType, instance_presence: InstancePresenceRecordType, pointer: PointerRecordType, }, { snapshotMigrations: storeMigrations, onValidationFailure, createIntegrityChecker: createIntegrityChecker, } ) }