[chore] move schema construction to tlschema package (#1334)

Our private tlsync package currently depends on the editor package,
which balloons the size of the cloudflare worker. It also makes it so
that any change to any package triggers a worker refresh, which makes
working on multiplayer stuff kinda miserable.

This is the first PR to fix that problem.

The second PR will need to resolve TLSyncClient's dependency on the
debugFlags somehow. Easiest would be to just remove the offending flag,
but we might want cross-bublic debug flags at some point in the future
so I'll try to find a low-cost way to make that happen while making
`tlsync` not depend on `editor`.

cc @TodePond since you added the flag in question
(`tldrawResetConnectionEveryPing`)

### Release Note

- internal moving stuff around
This commit is contained in:
David Sheldrick 2023-05-09 15:40:58 +01:00 committed by GitHub
parent 1f90c3f2b4
commit 67f5c25c73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 95 deletions

View file

@ -1,36 +1,18 @@
import {
CLIENT_FIXUP_SCRIPT,
TLAsset,
TLCamera,
TLDOCUMENT_ID,
TLDocument,
TLInstance,
TLInstanceId,
TLInstancePageState,
TLInstancePresence,
TLPage,
TLRecord,
TLShape,
TLStore,
TLStoreProps,
TLUser,
TLUserDocument,
TLUserId,
TLUserPresence,
ensureStoreIsUsable,
onValidationFailure,
rootShapeTypeMigrations,
storeMigrations,
createTLSchema,
} from '@tldraw/tlschema'
import {
RecordType,
Store,
StoreSchema,
StoreSnapshot,
createRecordType,
defineMigrations,
} from '@tldraw/tlstore'
import { T } from '@tldraw/tlvalidate'
import { RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
import { Signal } from 'signia'
import { TLArrowShapeDef } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
import { TLBookmarkShapeDef } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
@ -46,23 +28,6 @@ import { TLTextShapeDef } from '../app/shapeutils/TLTextUtil/TLTextUtil'
import { TLVideoShapeDef } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
import { StateNodeConstructor } from '../app/statechart/StateNode'
import { TLShapeDef, TLUnknownShapeDef } from './TLShapeDefinition'
import { defaultDerivePresenceState } from './defaultDerivePresenceState'
const CORE_SHAPE_DEFS = () =>
[
TLDrawShapeDef,
TLTextShapeDef,
TLLineShapeDef,
TLArrowShapeDef,
TLImageShapeDef,
TLVideoShapeDef,
TLGeoShapeDef,
TLNoteShapeDef,
TLGroupShapeDef,
TLBookmarkShapeDef,
TLEmbedShapeDef,
TLFrameShapeDef,
] as const
/** @public */
export class TldrawEditorConfig {
@ -80,68 +45,35 @@ export class TldrawEditorConfig {
/** @internal */
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
}) {
const {
shapes = [],
tools = [],
allowUnknownShapes = false,
derivePresenceState = defaultDerivePresenceState,
} = args
const { shapes = [], tools = [], allowUnknownShapes = false, derivePresenceState } = args
this.tools = tools
const allShapeDefs = [...CORE_SHAPE_DEFS(), ...shapes]
this.shapes = allShapeDefs
this.shapes = [
TLArrowShapeDef,
TLBookmarkShapeDef,
TLDrawShapeDef,
TLEmbedShapeDef,
TLFrameShapeDef,
TLGeoShapeDef,
TLGroupShapeDef,
TLImageShapeDef,
TLLineShapeDef,
TLNoteShapeDef,
TLTextShapeDef,
TLVideoShapeDef,
...shapes,
]
const typeSet = new Set<string>()
for (const shapeDef of allShapeDefs) {
if (typeSet.has(shapeDef.type)) {
throw new Error(`Shape type ${shapeDef.type} is already defined`)
}
typeSet.add(shapeDef.type)
}
const shapeTypeMigrations = defineMigrations({
currentVersion: rootShapeTypeMigrations.currentVersion,
firstVersion: rootShapeTypeMigrations.firstVersion,
migrators: rootShapeTypeMigrations.migrators,
subTypeKey: 'type',
subTypeMigrations: Object.fromEntries(allShapeDefs.map((def) => [def.type, def.migrations])),
this.storeSchema = createTLSchema({
allowUnknownShapes,
customShapeDefs: shapes,
derivePresenceState,
})
let shapeValidator = T.union('type', {
...Object.fromEntries(allShapeDefs.map((def) => [def.type, def.validator])),
}) as T.UnionValidator<'type', any, any>
if (allowUnknownShapes) {
shapeValidator = shapeValidator.validateUnknownVariants((shape) => shape as any)
}
const shapeRecord = createRecordType<TLShape>('shape', {
migrations: shapeTypeMigrations,
validator: T.model('shape', shapeValidator),
scope: 'document',
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
this.TLShape = shapeRecord
this.storeSchema = StoreSchema.create<TLRecord, TLStoreProps>(
{
asset: TLAsset,
camera: TLCamera,
document: TLDocument,
instance: TLInstance,
instance_page_state: TLInstancePageState,
page: TLPage,
shape: shapeRecord,
user: TLUser,
user_document: TLUserDocument,
user_presence: TLUserPresence,
instance_presence: TLInstancePresence,
},
{
snapshotMigrations: storeMigrations,
onValidationFailure,
ensureStoreIsUsable,
derivePresenceState,
}
)
this.TLShape = this.storeSchema.types.shape as RecordType<
TLShape,
'type' | 'props' | 'index' | 'parentId'
>
}
createStore(config: {

View file

@ -5,13 +5,16 @@
```ts
import { BaseRecord } from '@tldraw/tlstore';
import { defineMigrations } from '@tldraw/tlstore';
import { ID } from '@tldraw/tlstore';
import { Migrations } from '@tldraw/tlstore';
import { RecordType } from '@tldraw/tlstore';
import { Signal } from 'signia';
import { Store } from '@tldraw/tlstore';
import { StoreSchema } from '@tldraw/tlstore';
import { StoreSchemaOptions } from '@tldraw/tlstore';
import { StoreSnapshot } from '@tldraw/tlstore';
import { StoreValidator } from '@tldraw/tlstore';
import { T } from '@tldraw/tlvalidate';
// @internal (undocumented)
@ -102,15 +105,32 @@ export function createShapeValidator<Type extends string, Props extends object>(
props: Props;
}>;
// @public (undocumented)
export function createTLSchema({ customShapeDefs, allowUnknownShapes, derivePresenceState, }: {
customShapeDefs?: readonly CustomShapeTypeInfo[];
allowUnknownShapes?: boolean;
derivePresenceState?: (store: TLStore) => Signal<null | TLInstancePresence>;
}): StoreSchema<TLRecord, TLStoreProps>;
// @public (undocumented)
export const cursorTypeValidator: T.Validator<string>;
// @public (undocumented)
export const cursorValidator: T.Validator<TLCursor>;
// @public (undocumented)
export type CustomShapeTypeInfo = {
type: string;
migrations: ReturnType<typeof defineMigrations>;
validator?: StoreValidator<TLShape>;
};
// @internal (undocumented)
export const dashValidator: T.Validator<"dashed" | "dotted" | "draw" | "solid">;
// @internal (undocumented)
export const defaultDerivePresenceState: (store: TLStore) => Signal<null | TLInstancePresence>;
// @public (undocumented)
export const documentTypeMigrations: Migrations;

View file

@ -63,5 +63,8 @@
"@tldraw/tlvalidate": "workspace:*",
"@tldraw/utils": "workspace:*",
"nanoid": "^3.0.0"
},
"peerDependencies": {
"signia": "*"
}
}

View file

@ -0,0 +1,119 @@
import { StoreSchema, StoreValidator, createRecordType, defineMigrations } from '@tldraw/tlstore'
import { T } from '@tldraw/tlvalidate'
import { Signal } from 'signia'
import { TLRecord } from './TLRecord'
import { TLStore, TLStoreProps, ensureStoreIsUsable, onValidationFailure } from './TLStore'
import { defaultDerivePresenceState } from './defaultDerivePresenceState'
import { TLAsset } from './records/TLAsset'
import { TLCamera } from './records/TLCamera'
import { TLDocument } from './records/TLDocument'
import { TLInstance } from './records/TLInstance'
import { TLInstancePageState } from './records/TLInstancePageState'
import { TLInstancePresence } from './records/TLInstancePresence'
import { TLPage } from './records/TLPage'
import { TLShape, rootShapeTypeMigrations } from './records/TLShape'
import { TLUser } from './records/TLUser'
import { TLUserDocument } from './records/TLUserDocument'
import { TLUserPresence } from './records/TLUserPresence'
import { storeMigrations } from './schema'
import { arrowShapeMigrations, arrowShapeTypeValidator } from './shapes/TLArrowShape'
import { bookmarkShapeMigrations, bookmarkShapeTypeValidator } from './shapes/TLBookmarkShape'
import { drawShapeMigrations, drawShapeTypeValidator } from './shapes/TLDrawShape'
import { embedShapeMigrations, embedShapeTypeValidator } from './shapes/TLEmbedShape'
import { frameShapeMigrations, frameShapeTypeValidator } from './shapes/TLFrameShape'
import { geoShapeMigrations, geoShapeTypeValidator } from './shapes/TLGeoShape'
import { groupShapeMigrations, groupShapeTypeValidator } from './shapes/TLGroupShape'
import { imageShapeMigrations, imageShapeTypeValidator } from './shapes/TLImageShape'
import { lineShapeMigrations, lineShapeTypeValidator } from './shapes/TLLineShape'
import { noteShapeMigrations, noteShapeTypeValidator } from './shapes/TLNoteShape'
import { textShapeMigrations, textShapeTypeValidator } from './shapes/TLTextShape'
import { videoShapeMigrations, videoShapeTypeValidator } from './shapes/TLVideoShape'
const CORE_SHAPE_DEFS: readonly CustomShapeTypeInfo[] = [
{ type: 'draw', migrations: drawShapeMigrations, validator: drawShapeTypeValidator },
{ type: 'text', migrations: textShapeMigrations, validator: textShapeTypeValidator },
{ type: 'line', migrations: lineShapeMigrations, validator: lineShapeTypeValidator },
{ type: 'arrow', migrations: arrowShapeMigrations, validator: arrowShapeTypeValidator },
{ type: 'image', migrations: imageShapeMigrations, validator: imageShapeTypeValidator },
{ type: 'video', migrations: videoShapeMigrations, validator: videoShapeTypeValidator },
{ type: 'geo', migrations: geoShapeMigrations, validator: geoShapeTypeValidator },
{ type: 'note', migrations: noteShapeMigrations, validator: noteShapeTypeValidator },
{ type: 'group', migrations: groupShapeMigrations, validator: groupShapeTypeValidator },
{
type: 'bookmark',
migrations: bookmarkShapeMigrations,
validator: bookmarkShapeTypeValidator,
},
{ type: 'frame', migrations: frameShapeMigrations, validator: frameShapeTypeValidator },
{ type: 'embed', migrations: embedShapeMigrations, validator: embedShapeTypeValidator },
]
/** @public */
export type CustomShapeTypeInfo = {
type: string
migrations: ReturnType<typeof defineMigrations>
validator?: StoreValidator<TLShape>
}
/** @public */
export function createTLSchema({
customShapeDefs,
allowUnknownShapes,
derivePresenceState,
}: {
customShapeDefs?: readonly CustomShapeTypeInfo[]
allowUnknownShapes?: boolean
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
}) {
const allShapeDefs = [...CORE_SHAPE_DEFS, ...(customShapeDefs ?? [])]
const typeSet = new Set<string>()
for (const shapeDef of allShapeDefs) {
if (typeSet.has(shapeDef.type)) {
throw new Error(`Shape type ${shapeDef.type} is already defined`)
}
typeSet.add(shapeDef.type)
}
const shapeTypeMigrations = defineMigrations({
currentVersion: rootShapeTypeMigrations.currentVersion,
firstVersion: rootShapeTypeMigrations.firstVersion,
migrators: rootShapeTypeMigrations.migrators,
subTypeKey: 'type',
subTypeMigrations: Object.fromEntries(allShapeDefs.map((def) => [def.type, def.migrations])),
})
let shapeValidator = T.union('type', {
...Object.fromEntries(allShapeDefs.map((def) => [def.type, def.validator ?? (T.any as any)])),
}) as T.UnionValidator<'type', any, any>
if (allowUnknownShapes) {
shapeValidator = shapeValidator.validateUnknownVariants((shape) => shape as any)
}
const shapeRecord = createRecordType<TLShape>('shape', {
migrations: shapeTypeMigrations,
validator: T.model('shape', shapeValidator),
scope: 'document',
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
return StoreSchema.create<TLRecord, TLStoreProps>(
{
asset: TLAsset,
camera: TLCamera,
document: TLDocument,
instance: TLInstance,
instance_page_state: TLInstancePageState,
page: TLPage,
shape: shapeRecord,
user: TLUser,
user_document: TLUserDocument,
user_presence: TLUserPresence,
instance_presence: TLInstancePresence,
},
{
snapshotMigrations: storeMigrations,
onValidationFailure,
ensureStoreIsUsable,
derivePresenceState: derivePresenceState ?? defaultDerivePresenceState,
}
)
}

View file

@ -1,5 +1,6 @@
import { TLInstancePresence, TLStore } from '@tldraw/tlschema'
import { Signal, computed } from 'signia'
import { TLStore } from './TLStore'
import { TLInstancePresence } from './records/TLInstancePresence'
/** @internal */
export const defaultDerivePresenceState = (store: TLStore): Signal<TLInstancePresence | null> => {

View file

@ -24,6 +24,9 @@ export {
type TLVideoAsset,
} from './assets/TLVideoAsset'
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
export { createTLSchema } from './createTLSchema'
export type { CustomShapeTypeInfo } from './createTLSchema'
export { defaultDerivePresenceState } from './defaultDerivePresenceState'
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
export { type Box2dModel, type Vec2dModel } from './geometry-types'
export {

View file

@ -4542,6 +4542,8 @@ __metadata:
kleur: ^4.1.5
lazyrepo: 0.0.0-alpha.26
nanoid: ^3.0.0
peerDependencies:
signia: "*"
languageName: unknown
linkType: soft