Migrate snapshot (#1843)
Add `Store.migrateSnapshot`, another surface API alongside getSnapshot and loadSnapshot. ### Change Type - [x] `minor` — New feature ### Release Notes - [editor] add `Store.migrateSnapshot`
This commit is contained in:
parent
0b3e83be52
commit
48a1bb4d88
7 changed files with 126 additions and 13 deletions
|
@ -2495,9 +2495,9 @@ export type TLStoreOptions = {
|
|||
initialData?: SerializedStore<TLRecord>;
|
||||
defaultName?: string;
|
||||
} & ({
|
||||
schema: StoreSchema<TLRecord, TLStoreProps>;
|
||||
schema?: StoreSchema<TLRecord, TLStoreProps>;
|
||||
} | {
|
||||
shapeUtils: readonly TLAnyShapeUtilConstructor[];
|
||||
shapeUtils?: readonly TLAnyShapeUtilConstructor[];
|
||||
});
|
||||
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -15,8 +15,8 @@ export type TLStoreOptions = {
|
|||
initialData?: SerializedStore<TLRecord>
|
||||
defaultName?: string
|
||||
} & (
|
||||
| { shapeUtils: readonly TLAnyShapeUtilConstructor[] }
|
||||
| { schema: StoreSchema<TLRecord, TLStoreProps> }
|
||||
| { shapeUtils?: readonly TLAnyShapeUtilConstructor[] }
|
||||
| { schema?: StoreSchema<TLRecord, TLStoreProps> }
|
||||
)
|
||||
|
||||
/** @public */
|
||||
|
@ -30,11 +30,16 @@ export type TLStoreEventInfo = HistoryEntry<TLRecord>
|
|||
* @public */
|
||||
export function createTLStore({ initialData, defaultName = '', ...rest }: TLStoreOptions): TLStore {
|
||||
const schema =
|
||||
'schema' in rest
|
||||
? rest.schema
|
||||
: createTLSchema({
|
||||
shapes: currentPageShapesToShapeMap(checkShapesAndAddCore(rest.shapeUtils)),
|
||||
'schema' in rest && rest.schema
|
||||
? // we have a schema
|
||||
rest.schema
|
||||
: // we need a schema
|
||||
createTLSchema({
|
||||
shapes: currentPageShapesToShapeMap(
|
||||
checkShapesAndAddCore('shapeUtils' in rest && rest.shapeUtils ? rest.shapeUtils : [])
|
||||
),
|
||||
})
|
||||
|
||||
return new Store({
|
||||
schema,
|
||||
initialData,
|
||||
|
|
|
@ -257,6 +257,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
// @internal (undocumented)
|
||||
markAsPossiblyCorrupted(): void;
|
||||
mergeRemoteChanges: (fn: () => void) => void;
|
||||
migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>;
|
||||
onAfterChange?: (prev: R, next: R, source: 'remote' | 'user') => void;
|
||||
onAfterCreate?: (record: R, source: 'remote' | 'user') => void;
|
||||
onAfterDelete?: (prev: R, source: 'remote' | 'user') => void;
|
||||
|
|
|
@ -565,6 +565,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
* ```
|
||||
*
|
||||
* @param scope - The scope of records to serialize. Defaults to 'document'.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getSnapshot(scope: RecordScope | 'all' = 'document'): StoreSnapshot<R> {
|
||||
|
@ -574,6 +575,30 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate a serialized snapshot of the store and its schema.
|
||||
*
|
||||
* ```ts
|
||||
* const snapshot = store.getSnapshot()
|
||||
* store.migrateSnapshot(snapshot)
|
||||
* ```
|
||||
*
|
||||
* @param snapshot - The snapshot to load.
|
||||
* @public
|
||||
*/
|
||||
migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R> {
|
||||
const migrationResult = this.schema.migrateStoreSnapshot(snapshot)
|
||||
|
||||
if (migrationResult.type === 'error') {
|
||||
throw new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)
|
||||
}
|
||||
|
||||
return {
|
||||
store: migrationResult.value,
|
||||
schema: this.schema.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a serialized snapshot.
|
||||
*
|
||||
|
@ -583,7 +608,6 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
* ```
|
||||
*
|
||||
* @param snapshot - The snapshot to load.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
loadSnapshot(snapshot: StoreSnapshot<R>): void {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { BaseBoxShapeUtil, PageRecordType, TLShape, createShapeId } from '@tldraw/editor'
|
||||
import {
|
||||
AssetRecordType,
|
||||
BaseBoxShapeUtil,
|
||||
PageRecordType,
|
||||
TLShape,
|
||||
createShapeId,
|
||||
} from '@tldraw/editor'
|
||||
import { TestEditor } from './TestEditor'
|
||||
import { TL } from './test-jsx'
|
||||
|
||||
|
@ -504,3 +510,73 @@ describe('getShapeUtil', () => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('snapshots', () => {
|
||||
it('creates and loads a snapshot', () => {
|
||||
const ids = {
|
||||
imageA: createShapeId('imageA'),
|
||||
boxA: createShapeId('boxA'),
|
||||
imageAssetA: AssetRecordType.createId('imageAssetA'),
|
||||
}
|
||||
|
||||
editor.createAssets([
|
||||
{
|
||||
type: 'image',
|
||||
id: ids.imageAssetA,
|
||||
typeName: 'asset',
|
||||
props: {
|
||||
w: 1200,
|
||||
h: 800,
|
||||
name: '',
|
||||
isAnimated: false,
|
||||
mimeType: 'png',
|
||||
src: '',
|
||||
},
|
||||
meta: {},
|
||||
},
|
||||
])
|
||||
|
||||
editor.createShapes([
|
||||
{ type: 'geo', x: 0, y: 0 },
|
||||
{ type: 'geo', x: 100, y: 0 },
|
||||
{
|
||||
id: ids.imageA,
|
||||
type: 'image',
|
||||
props: {
|
||||
playing: false,
|
||||
url: '',
|
||||
w: 1200,
|
||||
h: 800,
|
||||
assetId: ids.imageAssetA,
|
||||
},
|
||||
x: 0,
|
||||
y: 1200,
|
||||
},
|
||||
])
|
||||
|
||||
const page2Id = PageRecordType.createId('page2')
|
||||
|
||||
editor.createPage({
|
||||
id: page2Id,
|
||||
})
|
||||
|
||||
editor.setCurrentPage(page2Id)
|
||||
|
||||
editor.createShapes([
|
||||
{ type: 'geo', x: 0, y: 0 },
|
||||
{ type: 'geo', x: 100, y: 0 },
|
||||
])
|
||||
|
||||
editor.selectAll()
|
||||
|
||||
// now serialize
|
||||
|
||||
const snapshot = editor.store.getSnapshot()
|
||||
|
||||
const newEditor = new TestEditor()
|
||||
|
||||
newEditor.store.loadSnapshot(snapshot)
|
||||
|
||||
expect(editor.store.serialize()).toEqual(newEditor.store.serialize())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -14,6 +14,7 @@ import { SerializedStore } from '@tldraw/store';
|
|||
import { Signal } from '@tldraw/state';
|
||||
import { Store } from '@tldraw/store';
|
||||
import { StoreSchema } from '@tldraw/store';
|
||||
import { StoreSnapshot } from '@tldraw/store';
|
||||
import { T } from '@tldraw/validate';
|
||||
import { UnknownRecord } from '@tldraw/store';
|
||||
|
||||
|
@ -1203,7 +1204,7 @@ export type TLStoreProps = {
|
|||
export type TLStoreSchema = StoreSchema<TLRecord, TLStoreProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStoreSnapshot = SerializedStore<TLRecord>;
|
||||
export type TLStoreSnapshot = StoreSnapshot<TLRecord>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLTextShape = TLBaseShape<'text', TLTextShapeProps>;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { SerializedStore, Store, StoreSchema, StoreSchemaOptions } from '@tldraw/store'
|
||||
import {
|
||||
SerializedStore,
|
||||
Store,
|
||||
StoreSchema,
|
||||
StoreSchemaOptions,
|
||||
StoreSnapshot,
|
||||
} from '@tldraw/store'
|
||||
import { annotateError, structuredClone } from '@tldraw/utils'
|
||||
import { CameraRecordType, TLCameraId } from './records/TLCamera'
|
||||
import { DocumentRecordType, TLDOCUMENT_ID } from './records/TLDocument'
|
||||
|
@ -36,7 +42,7 @@ export type TLStoreSchema = StoreSchema<TLRecord, TLStoreProps>
|
|||
export type TLSerializedStore = SerializedStore<TLRecord>
|
||||
|
||||
/** @public */
|
||||
export type TLStoreSnapshot = SerializedStore<TLRecord>
|
||||
export type TLStoreSnapshot = StoreSnapshot<TLRecord>
|
||||
|
||||
/** @public */
|
||||
export type TLStoreProps = {
|
||||
|
|
Loading…
Reference in a new issue