tldraw/packages/store
Steve Ruiz 72ae8ddefd
Don't double squash (#3182)
This PR changes the way `Store.squashHistoryEntries` works. Previously,
the function would iterate through every entry and squash it against the
current entry (using `squashRecordDiffs`) to get the new current entry.
However, `squashRecordDiffs` does basically the same pattern, iterating
through the properties of every diff. As a result, each diff would be
iterated through twice: once as itself, and once again in the next
current.

This PR tweaks the function to operate on as many diffs as possible at
once.

### Change Type

<!--  Please select a 'Scope' label ️ -->

- [x] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [ ] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [ ] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [x] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know


### Test Plan

- [x] Unit Tests
- [ ] End to end tests

### Release Notes

- Minor improvement when modifying multiple shapes at once.
2024-03-20 12:44:09 +00:00
..
api bump typescript / api-extractor (#2949) 2024-02-25 11:43:17 +00:00
src Don't double squash (#3182) 2024-03-20 12:44:09 +00:00
api-extractor.json Rename tlstore to store (#1507) 2023-06-03 08:59:04 +00:00
api-report.md Faster validations + record reference stability at the same time (#2848) 2024-02-20 12:35:25 +00:00
CHANGELOG.md Update CHANGELOG.md [skip ci] 2024-02-29 16:41:45 +00:00
LICENSE.md unbrivate, dot com in (#2475) 2024-01-16 14:38:05 +00:00
package.json Update CHANGELOG.md [skip ci] 2024-02-29 18:28:45 +00:00
README.md Rename tlstore to store (#1507) 2023-06-03 08:59:04 +00:00
tsconfig.json Check tsconfig "references" arrays (#2891) 2024-02-21 13:07:53 +00:00

@tldraw/tlstore

tlstore is a library for creating and managing data.

In this library, a "record" is an object that is stored under a typed id.

tlstore is used by tldraw to store its data.

It is designed to be used with tlstate (@tldraw/tlstate).

Usage

First create types for your records.

interface Book extends BaseRecord<'book'> {
	title: string
	author: ID<Author>
	numPages: number
}

interface Author extends BaseRecord<'author'> {
	name: string
	isPseudonym: boolean
}

Then create your RecordType instances.

const Book = createRecordType<Book>('book')

const Author = createRecordType<Author>('author').withDefaultProperties(() => ({
	isPseudonym: false,
}))

Then create your RecordStore instance.

const store = new RecordStore<Book | Author>()

Then you can create records, add them to the store, update, and remove them.

const tolkeinId = Author.createCustomId('tolkein')

store.put([
	Author.create({
		id: jrrTolkeinId,
		name: 'J.R.R Tolkein',
	}),
])

store.update(tolkeinId, (author) => ({
	...author,
	name: 'DJJ Tolkz',
	isPseudonym: true,
}))

store.remove(tolkeinId)

API

RecordStore

The RecordStore class is the main class of the library.

const store = new RecordStore()

put(records: R[]): void

Add some records to the store. It's an error if they already exist.

const record = Author.create({
	name: 'J.R.R Tolkein',
	id: Author.createCustomId('tolkein'),
})

store.put([record])

update(id: ID<R>, updater: (record: R) => R): void

Update a record. To update multiple records at once, use the update method of the TypedRecordStore class.

const id = Author.createCustomId('tolkein')

store.update(id, (r) => ({ ...r, name: 'Jimmy Tolks' }))

remove(ids: ID<R>[]): void

Remove some records from the store via their ids.

const id = Author.createCustomId('tolkein')

store.remove([id])

get(id: ID<R>): R

Get the value of a store record by its id.

const id = Author.createCustomId('tolkein')

const result = store.get(id)

allRecords(): R[]

Get an array of all values in the store.

const results = store.allRecords()

clear(): void

Remove all records from the store.

store.clear()

has(id: ID<R>): boolean

Get whether the record store has an record stored under the given id.

const id = Author.createCustomId('tolkein')

const result = store.has(id)

serialize(filter?: (record: R) => boolean): RecordStoreSnapshot<R>

Opposite of deserialize. Creates a JSON payload from the record store.

const serialized = store.serialize()
const serialized = store.serialize((record) => record.name === 'J.R.R Tolkein')

deserialize(snapshot: RecordStoreSnapshot<R>): void

Opposite of serialize. Replace the store's current records with records as defined by a simple JSON structure into the stores.

const serialized = { ... }

store.deserialize(serialized)

listen(listener: ((entry: HistoryEntry) => void): () => void

Add a new listener to the store The store will call the function each time the history changes. Returns a function to remove the listener.

store.listen((entry) => doSomethingWith(entry))

mergeRemoteChanges(fn: () => void): void

Merge changes from a remote source without triggering listeners.

store.mergeRemoteChanges(() => {
	store.put(recordsFromRemoteSource)
})

createDerivationCache(name: string, derive: ((record: R) => R | undefined)): DerivationCache<R>

Create a new derivation cache.

const derivationCache = createDerivationCache('popular_authors', (record) => {
	return record.popularity > 62 ? record : undefined
})

RecordType

The RecordType class is used to define the structure of a record.

const recordType = new RecordType('author', () => ({ living: true }))

RecordType instances are most often created with createRecordType.

create(properties: Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>): R

Create a new record of this type.

const record = recordType.create({ name: 'J.R.R Tolkein' })

clone(record: R): R

Clone a record of this type.

const record = recordType.create({ name: 'J.R.R Tolkein' })

const clone = recordType.clone(record)

createId(): ID<R>

Create an Id for a record of this type.

const id = recordType.createId()

createCustomId(id: string): ID<R>

Create a custom Id for a record of this type.

const id = recordType.createCustomId('tolkein')

isInstance

Check if a value is an instance of this record type.

const record = recordType.create({ name: 'J.R.R Tolkein' })

const result1 = recordType.isInstance(record) // true
const result2 = recordType.isInstance(someOtherRecord) // false

isId

Check if a value is an id for a record of this type.

const id = recordType.createCustomId('tolkein')

const result1 = recordType.isId(id) // true
const result2 = recordType.isId(someOtherId) // false

withDefaultProperties

Create a new record type with default properties.

const youngLivingAuthor = new RecordType('author', () => ({ age: 28, living: true }))

const oldDeadAuthor = recordType.withDefaultProperties({ age: 93, living: false })

RecordStoreQueries

TODO

Helpers

executeQuery

TODO

DerivationCache

The DerivationCache class is used to create a cache of derived records.

const derivationCache = new DerivationCache('popular_authors', (record) => {
	return record.popularity > 62 ? record : undefined
})

createRecordType

A helper used to create a new RecordType instance with no default properties.

const recordType = createRecordType('author'))

assertIdType

A helper used to assert that a value is an id for a record of a given type.

const id = recordType.createCustomId('tolkein')

assertIdType(id, recordType)

Types

ID

A type used to represent a record's id.

const id: ID<Author> = Author.createCustomId('tolkein')

BaseRecord

A BaseRecord is a record that has an id and a type. It is the base type for all records.

type AuthorRecord extends BaseRecord<"author"> {
  name: string
  age: number
  living: boolean
}

AllRecords

A helper to get the type of all records in a record store.

type AllAuthorRecords = AllRecords<RecordStore<Author>>

RecordsDiff

A diff describing the changes to a record.

CollectionDiff

A diff describing the changes to a collection.

License

The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at hello@tldraw.com.