diff --git a/apps/bemo-worker/src/BemoDO.ts b/apps/bemo-worker/src/BemoDO.ts index 0ba4ab690..7aa3bfc15 100644 --- a/apps/bemo-worker/src/BemoDO.ts +++ b/apps/bemo-worker/src/BemoDO.ts @@ -5,6 +5,7 @@ import { T } from '@tldraw/validate' import { createPersistQueue, createSentry, parseRequestQuery } from '@tldraw/worker-shared' import { DurableObject } from 'cloudflare:workers' import { IRequest, Router } from 'itty-router' +import { makePermissiveSchema } from './makePermissiveSchema' import { Environment } from './types' const connectRequestQuery = T.object({ @@ -106,6 +107,7 @@ export class BemoDO extends DurableObject { if (!this._room) { this._room = this.loadFromDatabase(slug).then((result) => { return new TLSocketRoom({ + schema: makePermissiveSchema(), initialSnapshot: result.type === 'room_found' ? result.snapshot : undefined, onSessionRemoved: async (room, args) => { if (args.numSessionsRemaining > 0) return diff --git a/apps/bemo-worker/src/makePermissiveSchema.ts b/apps/bemo-worker/src/makePermissiveSchema.ts new file mode 100644 index 000000000..7a26203b9 --- /dev/null +++ b/apps/bemo-worker/src/makePermissiveSchema.ts @@ -0,0 +1,49 @@ +import { + TLBaseBinding, + TLBaseShape, + TLSchema, + bindingIdValidator, + createTLSchema, + opacityValidator, + parentIdValidator, + shapeIdValidator, +} from '@tldraw/tlschema' +import { T } from '@tldraw/validate' + +export function makePermissiveSchema(): TLSchema { + const schema = createTLSchema() + + const shapeValidator = T.object>({ + id: shapeIdValidator, + typeName: T.literal('shape'), + x: T.number, + y: T.number, + rotation: T.number, + index: T.indexKey, + parentId: parentIdValidator, + type: T.string, + isLocked: T.boolean, + opacity: opacityValidator, + props: T.jsonValue as any, + meta: T.jsonValue as any, + }) as (typeof schema)['types']['shape']['validator'] + + const shapeType = schema.getType('shape') + // @ts-expect-error + shapeType.validator = shapeValidator + + const bindingValidator = T.object>({ + id: bindingIdValidator, + typeName: T.literal('binding'), + type: T.string, + fromId: shapeIdValidator, + toId: shapeIdValidator, + props: T.jsonValue as any, + meta: T.jsonValue as any, + }) as (typeof schema)['types']['binding']['validator'] + + const bindingType = schema.getType('binding') + // @ts-expect-error + bindingType.validator = bindingValidator + return schema +} diff --git a/apps/dotcom-worker/src/routes/createRoom.ts b/apps/dotcom-worker/src/routes/createRoom.ts index 11f7f0c52..07544bc57 100644 --- a/apps/dotcom-worker/src/routes/createRoom.ts +++ b/apps/dotcom-worker/src/routes/createRoom.ts @@ -1,5 +1,6 @@ import { CreateRoomRequestBody } from '@tldraw/dotcom-shared' -import { RoomSnapshot, schema } from '@tldraw/sync-core' +import { RoomSnapshot } from '@tldraw/sync-core' +import { createTLSchema } from '@tldraw/tlschema' import { IRequest } from 'itty-router' import { nanoid } from 'nanoid' import { getR2KeyForRoom } from '../r2' @@ -22,7 +23,7 @@ export async function createRoom(request: IRequest, env: Environment): Promise ({ state: r, diff --git a/apps/dotcom-worker/src/utils/validateSnapshot.ts b/apps/dotcom-worker/src/utils/validateSnapshot.ts index 9f78bea8b..1ae58a60d 100644 --- a/apps/dotcom-worker/src/utils/validateSnapshot.ts +++ b/apps/dotcom-worker/src/utils/validateSnapshot.ts @@ -1,6 +1,5 @@ import { SerializedSchema, SerializedStore } from '@tldraw/store' -import { schema } from '@tldraw/sync-core' -import { TLRecord } from '@tldraw/tlschema' +import { TLRecord, createTLSchema } from '@tldraw/tlschema' import { Result, objectMapEntries } from '@tldraw/utils' interface SnapshotRequestBody { @@ -8,6 +7,8 @@ interface SnapshotRequestBody { snapshot: SerializedStore } +const schema = createTLSchema() + export function validateSnapshot( body: SnapshotRequestBody ): Result, string> { diff --git a/apps/dotcom/src/pages/new.tsx b/apps/dotcom/src/pages/new.tsx index 53aad3279..8b3e10c20 100644 --- a/apps/dotcom/src/pages/new.tsx +++ b/apps/dotcom/src/pages/new.tsx @@ -1,12 +1,14 @@ import { ROOM_PREFIX, Snapshot } from '@tldraw/dotcom-shared' -import { schema } from '@tldraw/sync-core' import { Navigate } from 'react-router-dom' +import { createTLSchema } from 'tldraw' import '../../styles/globals.css' import { ErrorPage } from '../components/ErrorPage/ErrorPage' import { defineLoader } from '../utils/defineLoader' import { isInIframe } from '../utils/iFrame' import { getNewRoomResponse } from '../utils/sharing' +const schema = createTLSchema() + const { loader, useData } = defineLoader(async (_args) => { if (isInIframe()) return null diff --git a/apps/dotcom/src/pages/public-touchscreen-side-panel.tsx b/apps/dotcom/src/pages/public-touchscreen-side-panel.tsx index cef269374..3b0866ad0 100644 --- a/apps/dotcom/src/pages/public-touchscreen-side-panel.tsx +++ b/apps/dotcom/src/pages/public-touchscreen-side-panel.tsx @@ -1,11 +1,12 @@ import { CreateRoomRequestBody, ROOM_PREFIX, Snapshot } from '@tldraw/dotcom-shared' -import { schema } from '@tldraw/sync-core' import { useState } from 'react' import { Helmet } from 'react-helmet-async' -import { TldrawUiButton, fetch } from 'tldraw' +import { TldrawUiButton, createTLSchema, fetch } from 'tldraw' import '../../styles/globals.css' import { getParentOrigin } from '../utils/iFrame' +const schema = createTLSchema() + export function Component() { const [isCreating, setIsCreating] = useState(false) const [isSessionStarted, setIsSessionStarted] = useState(false) diff --git a/apps/examples/src/examples/custom-shape/CustomShapeExample.tsx b/apps/examples/src/examples/custom-shape/CustomShapeExample.tsx index a6efa2f10..27a5543fa 100644 --- a/apps/examples/src/examples/custom-shape/CustomShapeExample.tsx +++ b/apps/examples/src/examples/custom-shape/CustomShapeExample.tsx @@ -75,6 +75,7 @@ export class MyShapeUtil extends ShapeUtil { // [3] const customShape = [MyShapeUtil] + export default function CustomShapeExample() { return (
diff --git a/packages/sync-core/src/index.ts b/packages/sync-core/src/index.ts index 685187a48..68b33c682 100644 --- a/packages/sync-core/src/index.ts +++ b/packages/sync-core/src/index.ts @@ -33,5 +33,4 @@ export { type TLSocketClientSentEvent, type TLSocketServerSentEvent, } from './lib/protocol' -export { schema } from './lib/schema' export type { PersistedRoomSnapshotForSupabase } from './lib/server-types' diff --git a/packages/sync-core/src/lib/TLSocketRoom.ts b/packages/sync-core/src/lib/TLSocketRoom.ts index 628ca1eee..86c64f074 100644 --- a/packages/sync-core/src/lib/TLSocketRoom.ts +++ b/packages/sync-core/src/lib/TLSocketRoom.ts @@ -22,7 +22,7 @@ export class TLSocketRoom { constructor( public readonly opts: { initialSnapshot?: RoomSnapshot - schema?: StoreSchema + schema?: StoreSchema // how long to wait for a client to communicate before disconnecting them clientTimeout?: number log?: TLSyncLog diff --git a/packages/sync-core/src/lib/schema.ts b/packages/sync-core/src/lib/schema.ts deleted file mode 100644 index e7f16e8a0..000000000 --- a/packages/sync-core/src/lib/schema.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createTLSchema, defaultShapeSchemas } from '@tldraw/tlschema' - -export const schema = createTLSchema({ - shapes: defaultShapeSchemas, -}) diff --git a/packages/sync-core/src/test/TLSyncRoom.test.ts b/packages/sync-core/src/test/TLSyncRoom.test.ts index 2d7acb877..cfe08c1ae 100644 --- a/packages/sync-core/src/test/TLSyncRoom.test.ts +++ b/packages/sync-core/src/test/TLSyncRoom.test.ts @@ -18,8 +18,8 @@ import { TLSyncRoom, TOMBSTONE_PRUNE_BUFFER_SIZE, } from '../lib/TLSyncRoom' -import { schema } from '../lib/schema' +const schema = createTLSchema() const compareById = (a: { id: string }, b: { id: string }) => a.id.localeCompare(b.id) const records = [ diff --git a/packages/sync-core/src/test/syncFuzz.test.ts b/packages/sync-core/src/test/syncFuzz.test.ts index 5b3ddcc85..2e94311b5 100644 --- a/packages/sync-core/src/test/syncFuzz.test.ts +++ b/packages/sync-core/src/test/syncFuzz.test.ts @@ -7,17 +7,19 @@ import { TLStore, computed, createPresenceStateDerivation, + createTLSchema, createTLStore, isRecordsDiffEmpty, } from 'tldraw' import { prettyPrintDiff } from '../../../tldraw/src/test/testutils/pretty' import { TLSyncClient } from '../lib/TLSyncClient' -import { schema } from '../lib/schema' import { FuzzEditor, Op } from './FuzzEditor' import { RandomSource } from './RandomSource' import { TestServer } from './TestServer' import { TestSocketPair } from './TestSocketPair' +const schema = createTLSchema() + jest.mock('@tldraw/editor/src/lib/editor/managers/TickManager.ts', () => { return { TickManager: class { diff --git a/packages/sync/src/useMultiplayerSync.ts b/packages/sync/src/useMultiplayerSync.ts index bcfcc509a..2ad8b01b3 100644 --- a/packages/sync/src/useMultiplayerSync.ts +++ b/packages/sync/src/useMultiplayerSync.ts @@ -5,7 +5,6 @@ import { TLPersistentClientSocketStatus, TLRemoteSyncError, TLSyncClient, - schema, } from '@tldraw/sync-core' import { useEffect } from 'react' import { @@ -14,11 +13,13 @@ import { TAB_ID, TLAssetStore, TLRecord, + TLSchema, TLStore, TLStoreWithStatus, TLUserPreferences, computed, createPresenceStateDerivation, + createTLSchema, createTLStore, defaultUserPreferences, getUserPreferences, @@ -48,6 +49,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto assets, onEditorMount, trackAnalyticsEvent: track, + schema, } = opts const error: NonNullable['error'] = state?.error ?? undefined @@ -91,7 +93,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto const store = createTLStore({ id: storeId, - schema, + schema: schema ?? createTLSchema(), assets, onEditorMount, multiplayerStatus: computed('multiplayer status', () => @@ -133,7 +135,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto client.close() socket.close() } - }, [assets, error, onEditorMount, prefs, roomId, setState, track, uri]) + }, [assets, error, onEditorMount, prefs, roomId, setState, track, uri, schema]) return useValue( 'remote synced store', @@ -161,4 +163,5 @@ export interface UseMultiplayerSyncOptions { trackAnalyticsEvent?(name: string, data: { [key: string]: any }): void assets?: Partial onEditorMount?: (editor: Editor) => void + schema?: TLSchema } diff --git a/packages/sync/src/useMutliplayerDemo.ts b/packages/sync/src/useMutliplayerDemo.ts index d16bf3f7e..a0a058a31 100644 --- a/packages/sync/src/useMutliplayerDemo.ts +++ b/packages/sync/src/useMutliplayerDemo.ts @@ -6,6 +6,7 @@ import { Signal, TLAsset, TLAssetStore, + TLSchema, TLUserPreferences, getHashForString, uniqueId, @@ -18,6 +19,7 @@ export interface UseMultiplayerDemoOptions { userPreferences?: Signal /** @internal */ host?: string + schema?: TLSchema } /** @@ -43,6 +45,7 @@ export function useMultiplayerDemo({ roomId, userPreferences, host = DEMO_WORKER, + schema, }: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus { const assets = useMemo(() => createDemoAssetStore(host), [host]) @@ -51,6 +54,7 @@ export function useMultiplayerDemo({ roomId, userPreferences, assets, + schema, onEditorMount: useCallback( (editor: Editor) => { editor.registerExternalAssetHandler('url', async ({ url }) => {