
Typescript's type aliases (`type X = thing`) can refer to basically anything, which makes it hard to write an automatic document formatter for them. Interfaces on the other hand are only object, so they play much nicer with docs. Currently, object-flavoured type aliases don't really get expanded at all on our docs site, which means we have a bunch of docs content that's not shown on the site. This diff introduces a lint rule that forces `interface X {foo: bar}`s instead of `type X = {foo: bar}` where possible, as it results in a much better documentation experience: Before: <img width="437" alt="Screenshot 2024-05-22 at 15 24 13" src="https://github.com/tldraw/tldraw/assets/1489520/32606fd1-6832-4a1e-aa5f-f0534d160c92"> After: <img width="431" alt="Screenshot 2024-05-22 at 15 33 01" src="https://github.com/tldraw/tldraw/assets/1489520/4e0d59ee-c38e-4056-b9fd-6a7f15d28f0f"> ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `docs` — Changes to the documentation, examples, or templates. - [x] `improvement` — Improving existing features
107 lines
2.7 KiB
TypeScript
107 lines
2.7 KiB
TypeScript
import { objectMapEntries } from '@tldraw/utils'
|
|
import { IdOf, UnknownRecord } from './BaseRecord'
|
|
|
|
/**
|
|
* A diff describing the changes to a record.
|
|
*
|
|
* @public
|
|
*/
|
|
export interface RecordsDiff<R extends UnknownRecord> {
|
|
added: Record<IdOf<R>, R>
|
|
updated: Record<IdOf<R>, [from: R, to: R]>
|
|
removed: Record<IdOf<R>, R>
|
|
}
|
|
|
|
/** @internal */
|
|
export function createEmptyRecordsDiff<R extends UnknownRecord>(): RecordsDiff<R> {
|
|
return { added: {}, updated: {}, removed: {} } as RecordsDiff<R>
|
|
}
|
|
|
|
/** @public */
|
|
export function reverseRecordsDiff(diff: RecordsDiff<any>) {
|
|
const result: RecordsDiff<any> = { added: diff.removed, removed: diff.added, updated: {} }
|
|
for (const [from, to] of Object.values(diff.updated)) {
|
|
result.updated[from.id] = [to, from]
|
|
}
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Is a records diff empty?
|
|
* @internal
|
|
*/
|
|
export function isRecordsDiffEmpty<T extends UnknownRecord>(diff: RecordsDiff<T>) {
|
|
return (
|
|
Object.keys(diff.added).length === 0 &&
|
|
Object.keys(diff.updated).length === 0 &&
|
|
Object.keys(diff.removed).length === 0
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Squash a collection of diffs into a single diff.
|
|
*
|
|
* @param diffs - An array of diffs to squash.
|
|
* @returns A single diff that represents the squashed diffs.
|
|
* @public
|
|
*/
|
|
export function squashRecordDiffs<T extends UnknownRecord>(
|
|
diffs: RecordsDiff<T>[]
|
|
): RecordsDiff<T> {
|
|
const result = { added: {}, removed: {}, updated: {} } as RecordsDiff<T>
|
|
|
|
squashRecordDiffsMutable(result, diffs)
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Apply the array `diffs` to the `target` diff, mutating it in-place.
|
|
* @internal
|
|
*/
|
|
export function squashRecordDiffsMutable<T extends UnknownRecord>(
|
|
target: RecordsDiff<T>,
|
|
diffs: RecordsDiff<T>[]
|
|
): void {
|
|
for (const diff of diffs) {
|
|
for (const [id, value] of objectMapEntries(diff.added)) {
|
|
if (target.removed[id]) {
|
|
const original = target.removed[id]
|
|
delete target.removed[id]
|
|
if (original !== value) {
|
|
target.updated[id] = [original, value]
|
|
}
|
|
} else {
|
|
target.added[id] = value
|
|
}
|
|
}
|
|
|
|
for (const [id, [_from, to]] of objectMapEntries(diff.updated)) {
|
|
if (target.added[id]) {
|
|
target.added[id] = to
|
|
delete target.updated[id]
|
|
delete target.removed[id]
|
|
continue
|
|
}
|
|
if (target.updated[id]) {
|
|
target.updated[id] = [target.updated[id][0], to]
|
|
delete target.removed[id]
|
|
continue
|
|
}
|
|
|
|
target.updated[id] = diff.updated[id]
|
|
delete target.removed[id]
|
|
}
|
|
|
|
for (const [id, value] of objectMapEntries(diff.removed)) {
|
|
// the same record was added in this diff sequence, just drop it
|
|
if (target.added[id]) {
|
|
delete target.added[id]
|
|
} else if (target.updated[id]) {
|
|
target.removed[id] = target.updated[id][0]
|
|
delete target.updated[id]
|
|
} else {
|
|
target.removed[id] = value
|
|
}
|
|
}
|
|
}
|
|
}
|