Stricter ID types (#1439)

We noticed that when inferring the type of a shape from its ID, it was
getting inferred as `any` which was hiding some issues. This diff
switches `BaseRecord`'s automatic ID to an explicit one, which lets us
pass in our correct `TLShapeId` definition and still have it play nicely
with other places.

### Change Type

- [x] `patch` — Bug Fix

### Release Notes

[internal only, covered by #1432 changelog]

---------

Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
pull/1453/head
alex 2023-05-24 12:25:41 +01:00 zatwierdzone przez GitHub
rodzic eb26964130
commit 0375b5d86d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
40 zmienionych plików z 331 dodań i 286 usunięć

Wyświetl plik

@ -1,4 +1,4 @@
import { BaseRecord } from '@tldraw/tlstore'
import { UnknownRecord } from '@tldraw/tlstore'
import { isEqual } from 'lodash'
import fetch from 'node-fetch'
import * as vscode from 'vscode'
@ -173,7 +173,7 @@ export class WebViewMessageHandler {
}
}
private omit = (records: BaseRecord<any>[], keys: RegExp) => {
private omit = (records: UnknownRecord[], keys: RegExp) => {
return records.filter((record) => {
return !record.id.match(keys)
})

Wyświetl plik

@ -7,7 +7,6 @@
/// <reference types="react" />
import { Atom } from 'signia';
import { BaseRecord } from '@tldraw/tlstore';
import { Box2d } from '@tldraw/primitives';
import { Box2dModel } from '@tldraw/tlschema';
import { Computed } from 'signia';
@ -25,7 +24,6 @@ import { getIndicesAbove } from '@tldraw/indices';
import { getIndicesBelow } from '@tldraw/indices';
import { getIndicesBetween } from '@tldraw/indices';
import { HistoryEntry } from '@tldraw/tlstore';
import { ID } from '@tldraw/tlstore';
import { MatLike } from '@tldraw/primitives';
import { Matrix2d } from '@tldraw/primitives';
import { Matrix2dModel } from '@tldraw/primitives';
@ -101,6 +99,7 @@ import { TLUserId } from '@tldraw/tlschema';
import { TLUserPresence } from '@tldraw/tlschema';
import { TLVideoAsset } from '@tldraw/tlschema';
import { TLVideoShape } from '@tldraw/tlschema';
import { UnknownRecord } from '@tldraw/tlstore';
import { Vec2d } from '@tldraw/primitives';
import { Vec2dModel } from '@tldraw/tlschema';
import { VecLike } from '@tldraw/primitives';
@ -1727,7 +1726,7 @@ export type TLCancelEventInfo = {
};
// @public (undocumented)
export type TLChange<T extends BaseRecord<any> = any> = HistoryEntry<T>;
export type TLChange<T extends UnknownRecord = any> = HistoryEntry<T>;
// @public (undocumented)
export type TLClickEvent = (info: TLClickEventInfo) => void;
@ -2101,7 +2100,7 @@ export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
index: string;
parentId: TLParentId;
isLocked: boolean;
id: ID<TLGeoShape>;
id: TLShapeId;
typeName: "shape";
} | undefined;
// (undocumented)
@ -2130,7 +2129,7 @@ export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
index: string;
parentId: TLParentId;
isLocked: boolean;
id: ID<TLGeoShape>;
id: TLShapeId;
typeName: "shape";
} | undefined;
// (undocumented)
@ -2145,7 +2144,7 @@ export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
index: string;
parentId: TLParentId;
isLocked: boolean;
id: ID<TLGeoShape>;
id: TLShapeId;
typeName: "shape";
} | {
props: {
@ -2158,7 +2157,7 @@ export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
index: string;
parentId: TLParentId;
isLocked: boolean;
id: ID<TLGeoShape>;
id: TLShapeId;
typeName: "shape";
} | undefined;
// (undocumented)
@ -2336,7 +2335,7 @@ export class TLNoteUtil extends TLShapeUtil<TLNoteShape> {
index: string;
parentId: TLParentId;
isLocked: boolean;
id: ID<TLNoteShape>;
id: TLShapeId;
typeName: "shape";
} | undefined;
// (undocumented)
@ -2358,7 +2357,7 @@ export class TLNoteUtil extends TLShapeUtil<TLNoteShape> {
index: string;
parentId: TLParentId;
isLocked: boolean;
id: ID<TLNoteShape>;
id: TLShapeId;
typeName: "shape";
} | undefined;
// (undocumented)
@ -2549,7 +2548,7 @@ export class TLTextUtil extends TLShapeUtil<TLTextShape> {
parentId: TLParentId;
isLocked: boolean;
props: TLTextShapeProps;
id: ID<TLTextShape>;
id: TLShapeId;
typeName: "shape";
} | undefined;
// (undocumented)
@ -2572,19 +2571,19 @@ export class TLTextUtil extends TLShapeUtil<TLTextShape> {
index: string;
parentId: TLParentId;
isLocked: boolean;
id: ID<TLTextShape>;
id: TLShapeId;
typeName: "shape";
} | undefined;
// (undocumented)
onDoubleClickEdge: (shape: TLTextShape) => {
id: ID<TLTextShape>;
id: TLShapeId;
type: "text";
props: {
autoSize: boolean;
scale?: undefined;
};
} | {
id: ID<TLTextShape>;
id: TLShapeId;
type: "text";
props: {
scale: number;

Wyświetl plik

@ -62,7 +62,7 @@ import {
isShape,
isShapeId,
} from '@tldraw/tlschema'
import { BaseRecord, ComputedCache, HistoryEntry } from '@tldraw/tlstore'
import { ComputedCache, HistoryEntry, UnknownRecord } from '@tldraw/tlstore'
import {
annotateError,
compact,
@ -137,7 +137,7 @@ import { RequiredKeys } from './types/misc-types'
import { TLResizeHandle } from './types/selection-types'
/** @public */
export type TLChange<T extends BaseRecord<any> = any> = HistoryEntry<T>
export type TLChange<T extends UnknownRecord = any> = HistoryEntry<T>
/** @public */
export type AnimationOptions = Partial<{

Wyświetl plik

@ -5,7 +5,6 @@
```ts
import { App } from '@tldraw/editor';
import { BaseRecord } from '@tldraw/tlstore';
import { MigrationFailureReason } from '@tldraw/tlstore';
import { Result } from '@tldraw/utils';
import { SerializedSchema } from '@tldraw/tlstore';
@ -15,6 +14,7 @@ import { TLStore } from '@tldraw/editor';
import { TLTranslationKey } from '@tldraw/ui';
import { TLUserId } from '@tldraw/editor';
import { ToastsContextType } from '@tldraw/ui';
import { UnknownRecord } from '@tldraw/tlstore';
// @public (undocumented)
export function isV1File(data: any): boolean;
@ -45,7 +45,7 @@ export const TLDRAW_FILE_MIMETYPE: "application/vnd.tldraw+json";
// @public (undocumented)
export interface TldrawFile {
// (undocumented)
records: BaseRecord[];
records: UnknownRecord[];
// (undocumented)
schema: SerializedSchema;
// (undocumented)

Wyświetl plik

@ -10,12 +10,12 @@ import {
TLUserId,
} from '@tldraw/editor'
import {
BaseRecord,
ID,
MigrationFailureReason,
MigrationResult,
SerializedSchema,
StoreSnapshot,
UnknownRecord,
} from '@tldraw/tlstore'
import { T } from '@tldraw/tlvalidate'
import { TLTranslationKey, ToastsContextType } from '@tldraw/ui'
@ -35,7 +35,7 @@ const LATEST_TLDRAW_FILE_FORMAT_VERSION = 1
export interface TldrawFile {
tldrawFileFormatVersion: number
schema: SerializedSchema
records: BaseRecord[]
records: UnknownRecord[]
}
const tldrawFileValidator: T.Validator<TldrawFile> = T.object({

Wyświetl plik

@ -1,5 +1,5 @@
import { createCustomShapeId, TldrawEditorConfig, TLInstance, TLUser } from '@tldraw/editor'
import { BaseRecord, MigrationFailureReason } from '@tldraw/tlstore'
import { MigrationFailureReason, UnknownRecord } from '@tldraw/tlstore'
import { assert } from '@tldraw/utils'
import { parseTldrawJsonFile as _parseTldrawJsonFile, TldrawFile } from '../lib/file'
@ -100,7 +100,7 @@ describe('parseTldrawJsonFile', () => {
id: createCustomShapeId('shape'),
type: 'geo',
props: {},
} as BaseRecord,
} as UnknownRecord,
],
})
)

Wyświetl plik

@ -14,6 +14,7 @@ import { StoreSchema } from '@tldraw/tlstore';
import { StoreSchemaOptions } from '@tldraw/tlstore';
import { StoreSnapshot } from '@tldraw/tlstore';
import { T } from '@tldraw/tlvalidate';
import { UnknownRecord } from '@tldraw/tlstore';
// @internal (undocumented)
export const alignValidator: T.Validator<"end" | "middle" | "start">;
@ -369,7 +370,7 @@ export const iconShapeTypeValidator: T.Validator<TLIconShape>;
export const iconValidator: T.Validator<"activity" | "airplay" | "alert-circle" | "alert-octagon" | "alert-triangle" | "align-center" | "align-justify" | "align-left" | "align-right" | "anchor" | "aperture" | "archive" | "arrow-down-circle" | "arrow-down-left" | "arrow-down-right" | "arrow-down" | "arrow-left-circle" | "arrow-left" | "arrow-right-circle" | "arrow-right" | "arrow-up-circle" | "arrow-up-left" | "arrow-up-right" | "arrow-up" | "at-sign" | "award" | "bar-chart-2" | "bar-chart" | "battery-charging" | "battery" | "bell-off" | "bell" | "bluetooth" | "bold" | "book-open" | "book" | "bookmark" | "briefcase" | "calendar" | "camera-off" | "camera" | "cast" | "check-circle" | "check-square" | "check" | "chevron-down" | "chevron-left" | "chevron-right" | "chevron-up" | "chevrons-down" | "chevrons-left" | "chevrons-right" | "chevrons-up" | "chrome" | "circle" | "clipboard" | "clock" | "cloud-drizzle" | "cloud-lightning" | "cloud-off" | "cloud-rain" | "cloud-snow" | "cloud" | "codepen" | "codesandbox" | "coffee" | "columns" | "command" | "compass" | "copy" | "corner-down-left" | "corner-down-right" | "corner-left-down" | "corner-left-up" | "corner-right-down" | "corner-right-up" | "corner-up-left" | "corner-up-right" | "cpu" | "credit-card" | "crop" | "crosshair" | "database" | "delete" | "disc" | "divide-circle" | "divide-square" | "divide" | "dollar-sign" | "download-cloud" | "download" | "dribbble" | "droplet" | "edit-2" | "edit-3" | "edit" | "external-link" | "eye-off" | "eye" | "facebook" | "fast-forward" | "feather" | "figma" | "file-minus" | "file-plus" | "file-text" | "file" | "film" | "filter" | "flag" | "folder-minus" | "folder-plus" | "folder" | "framer" | "frown" | "geo" | "gift" | "git-branch" | "git-commit" | "git-merge" | "git-pull-request" | "github" | "gitlab" | "globe" | "grid" | "hard-drive" | "hash" | "headphones" | "heart" | "help-circle" | "hexagon" | "home" | "image" | "inbox" | "info" | "instagram" | "italic" | "key" | "layers" | "layout" | "life-buoy" | "link-2" | "link" | "linkedin" | "list" | "loader" | "lock" | "log-in" | "log-out" | "mail" | "map-pin" | "map" | "maximize-2" | "maximize" | "meh" | "menu" | "message-circle" | "message-square" | "mic-off" | "mic" | "minimize-2" | "minimize" | "minus-circle" | "minus-square" | "minus" | "monitor" | "moon" | "more-horizontal" | "more-vertical" | "mouse-pointer" | "move" | "music" | "navigation-2" | "navigation" | "octagon" | "package" | "paperclip" | "pause-circle" | "pause" | "pen-tool" | "percent" | "phone-call" | "phone-forwarded" | "phone-incoming" | "phone-missed" | "phone-off" | "phone-outgoing" | "phone" | "pie-chart" | "play-circle" | "play" | "plus-circle" | "plus-square" | "plus" | "pocket" | "power" | "printer" | "radio" | "refresh-ccw" | "refresh-cw" | "repeat" | "rewind" | "rotate-ccw" | "rotate-cw" | "rss" | "save" | "scissors" | "search" | "send" | "server" | "settings" | "share-2" | "share" | "shield-off" | "shield" | "shopping-bag" | "shopping-cart" | "shuffle" | "sidebar" | "skip-back" | "skip-forward" | "slack" | "slash" | "sliders" | "smartphone" | "smile" | "speaker" | "square" | "star" | "stop-circle" | "sun" | "sunrise" | "sunset" | "table" | "tablet" | "tag" | "target" | "terminal" | "thermometer" | "thumbs-down" | "thumbs-up" | "toggle-left" | "toggle-right" | "tool" | "trash-2" | "trash" | "trello" | "trending-down" | "trending-up" | "triangle" | "truck" | "tv" | "twitch" | "twitter" | "type" | "umbrella" | "underline" | "unlock" | "upload-cloud" | "upload" | "user-check" | "user-minus" | "user-plus" | "user-x" | "user" | "users" | "video-off" | "video" | "voicemail" | "volume-1" | "volume-2" | "volume-x" | "volume" | "watch" | "wifi-off" | "wifi" | "wind" | "x-circle" | "x-octagon" | "x-square" | "x" | "youtube" | "zap-off" | "zap" | "zoom-in" | "zoom-out">;
// @internal (undocumented)
export function idValidator<Id extends ID<BaseRecord<any>>>(prefix: Id['__type__']['typeName']): T.Validator<Id>;
export function idValidator<Id extends ID<UnknownRecord>>(prefix: Id['__type__']['typeName']): T.Validator<Id>;
// @public (undocumented)
export const imageAssetMigrations: Migrations;
@ -399,7 +400,7 @@ export const instanceTypeMigrations: Migrations;
export const instanceTypeValidator: T.Validator<TLInstance>;
// @public (undocumented)
export function isShape(record?: BaseRecord<string>): record is TLShape;
export function isShape(record?: UnknownRecord): record is TLShape;
// @public (undocumented)
export function isShapeId(id?: string): id is TLShapeId;
@ -622,7 +623,7 @@ export type TLAssetShape = Extract<TLShape, {
}>;
// @public (undocumented)
export interface TLBaseAsset<Type extends string, Props> extends BaseRecord<'asset'> {
export interface TLBaseAsset<Type extends string, Props> extends BaseRecord<'asset', TLAssetId> {
// (undocumented)
props: Props;
// (undocumented)
@ -630,7 +631,7 @@ export interface TLBaseAsset<Type extends string, Props> extends BaseRecord<'ass
}
// @public (undocumented)
export interface TLBaseShape<Type extends string, Props extends object> extends BaseRecord<'shape'> {
export interface TLBaseShape<Type extends string, Props extends object> extends BaseRecord<'shape', TLShapeId> {
// (undocumented)
index: string;
// (undocumented)
@ -678,7 +679,7 @@ export type TLBookmarkShapeProps = {
};
// @public
export interface TLCamera extends BaseRecord<'camera'> {
export interface TLCamera extends BaseRecord<'camera', TLCameraId> {
// (undocumented)
x: number;
// (undocumented)
@ -732,7 +733,7 @@ export type TLDashType = SetValue<typeof TL_DASH_TYPES>;
export type TLDefaultShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEmbedShape | TLFrameShape | TLGeoShape | TLGroupShape | TLIconShape | TLImageShape | TLLineShape | TLNoteShape | TLTextShape | TLVideoShape;
// @public
export interface TLDocument extends BaseRecord<'document'> {
export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> {
// (undocumented)
gridSize: number;
}
@ -959,7 +960,7 @@ export type TLImageShapeProps = {
};
// @public
export interface TLInstance extends BaseRecord<'instance'> {
export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
// (undocumented)
brush: Box2dModel | null;
// (undocumented)
@ -995,7 +996,7 @@ export const TLInstance: RecordType<TLInstance, "currentPageId" | "userId">;
export type TLInstanceId = ID<TLInstance>;
// @public
export interface TLInstancePageState extends BaseRecord<'instance_page_state'> {
export interface TLInstancePageState extends BaseRecord<'instance_page_state', TLInstancePageStateId> {
// (undocumented)
cameraId: ID<TLCamera>;
// (undocumented)
@ -1025,7 +1026,7 @@ export const TLInstancePageState: RecordType<TLInstancePageState, "cameraId" | "
export type TLInstancePageStateId = ID<TLInstancePageState>;
// @public (undocumented)
export interface TLInstancePresence extends BaseRecord<'instance_presence'> {
export interface TLInstancePresence extends BaseRecord<'instance_presence', TLInstancePresenceID> {
// (undocumented)
brush: Box2dModel | null;
// (undocumented)
@ -1116,7 +1117,7 @@ export interface TLOpacityStyle extends TLBaseStyle {
export type TLOpacityType = SetValue<typeof TL_OPACITY_TYPES>;
// @public
export interface TLPage extends BaseRecord<'page'> {
export interface TLPage extends BaseRecord<'page', TLPageId> {
// (undocumented)
index: string;
// (undocumented)
@ -1149,7 +1150,7 @@ export type TLScribble = {
export type TLShape = TLDefaultShape | TLUnknownShape;
// @public (undocumented)
export type TLShapeId = ID<TLBaseShape<any, any>>;
export type TLShapeId = ID<TLUnknownShape>;
// @public (undocumented)
export type TLShapePartial<T extends TLShape = TLShape> = T extends T ? {
@ -1262,7 +1263,7 @@ export type TLUiColorType = SetValue<typeof TL_UI_COLOR_TYPES>;
export type TLUnknownShape = TLBaseShape<string, object>;
// @public
export interface TLUser extends BaseRecord<'user'> {
export interface TLUser extends BaseRecord<'user', TLUserId> {
// (undocumented)
locale: string;
// (undocumented)
@ -1273,7 +1274,7 @@ export interface TLUser extends BaseRecord<'user'> {
export const TLUser: RecordType<TLUser, never>;
// @public
export interface TLUserDocument extends BaseRecord<'user_document'> {
export interface TLUserDocument extends BaseRecord<'user_document', TLUserDocumentId> {
// (undocumented)
isDarkMode: boolean;
// (undocumented)
@ -1302,7 +1303,7 @@ export type TLUserDocumentId = ID<TLUserDocument>;
export type TLUserId = ID<TLUser>;
// @public (undocumented)
export interface TLUserPresence extends BaseRecord<'user_presence'> {
export interface TLUserPresence extends BaseRecord<'user_presence', TLUserPresenceId> {
// (undocumented)
color: string;
// (undocumented)

Wyświetl plik

@ -4,7 +4,7 @@ import { TLAssetId } from '../records/TLAsset'
import { assetIdValidator } from '../validation'
/** @public */
export interface TLBaseAsset<Type extends string, Props> extends BaseRecord<'asset'> {
export interface TLBaseAsset<Type extends string, Props> extends BaseRecord<'asset', TLAssetId> {
type: Type
props: Props
}

Wyświetl plik

@ -7,7 +7,7 @@ import { idValidator } from '../validation'
*
* @public
*/
export interface TLCamera extends BaseRecord<'camera'> {
export interface TLCamera extends BaseRecord<'camera', TLCameraId> {
x: number
y: number
z: number

Wyświetl plik

@ -6,7 +6,7 @@ import { T } from '@tldraw/tlvalidate'
*
* @public
*/
export interface TLDocument extends BaseRecord<'document'> {
export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> {
gridSize: number
}

Wyświetl plik

@ -34,7 +34,7 @@ export type TLInstancePropsForNextShape = Pick<TLShapeProps, TLStyleType>
*
* @public
*/
export interface TLInstance extends BaseRecord<'instance'> {
export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
userId: TLUserId
currentPageId: TLPageId
followingUserId: TLUserId | null

Wyświetl plik

@ -13,7 +13,8 @@ import { TLShapeId } from './TLShape'
*
* @public
*/
export interface TLInstancePageState extends BaseRecord<'instance_page_state'> {
export interface TLInstancePageState
extends BaseRecord<'instance_page_state', TLInstancePageStateId> {
instanceId: ID<TLInstance>
pageId: ID<TLPage>
cameraId: ID<TLCamera>

Wyświetl plik

@ -9,7 +9,7 @@ import { TLShapeId } from './TLShape'
import { TLUserId } from './TLUser'
/** @public */
export interface TLInstancePresence extends BaseRecord<'instance_presence'> {
export interface TLInstancePresence extends BaseRecord<'instance_presence', TLInstancePresenceID> {
instanceId: TLInstanceId
userId: TLUserId
userName: string

Wyświetl plik

@ -7,7 +7,7 @@ import { pageIdValidator } from '../validation'
*
* @public
*/
export interface TLPage extends BaseRecord<'page'> {
export interface TLPage extends BaseRecord<'page', TLPageId> {
name: string
index: string
}

Wyświetl plik

@ -1,4 +1,4 @@
import { BaseRecord, defineMigrations, ID } from '@tldraw/tlstore'
import { defineMigrations, ID, UnknownRecord } from '@tldraw/tlstore'
import { nanoid } from 'nanoid'
import { TLBaseShape } from '../shapes/shape-validation'
import { TLArrowShape } from '../shapes/TLArrowShape'
@ -60,7 +60,7 @@ export type TLShapePartial<T extends TLShape = TLShape> = T extends T
: never
/** @public */
export type TLShapeId = ID<TLBaseShape<any, any>>
export type TLShapeId = ID<TLUnknownShape>
/** @public */
export type TLShapeProps = SmooshedUnionObject<TLShape['props']>
@ -100,7 +100,7 @@ export const rootShapeTypeMigrations = defineMigrations({
})
/** @public */
export function isShape(record?: BaseRecord<string>): record is TLShape {
export function isShape(record?: UnknownRecord): record is TLShape {
if (!record) return false
return record.typeName === 'shape'
}

Wyświetl plik

@ -8,7 +8,7 @@ import { userIdValidator } from '../validation'
*
* @public
*/
export interface TLUser extends BaseRecord<'user'> {
export interface TLUser extends BaseRecord<'user', TLUserId> {
name: string
locale: string
}

Wyświetl plik

@ -12,7 +12,7 @@ import { TLUserId } from './TLUser'
*
* @public
*/
export interface TLUserDocument extends BaseRecord<'user_document'> {
export interface TLUserDocument extends BaseRecord<'user_document', TLUserDocumentId> {
userId: TLUserId
isPenMode: boolean
isGridMode: boolean

Wyświetl plik

@ -6,7 +6,7 @@ import { TLInstanceId } from './TLInstance'
import { TLUserId } from './TLUser'
/** @public */
export interface TLUserPresence extends BaseRecord<'user_presence'> {
export interface TLUserPresence extends BaseRecord<'user_presence', TLUserPresenceId> {
userId: TLUserId
lastUsedInstanceId: TLInstanceId | null
lastActivityTimestamp: number

Wyświetl plik

@ -1,11 +1,11 @@
import { BaseRecord } from '@tldraw/tlstore'
import { T } from '@tldraw/tlvalidate'
import { TLParentId } from '../records/TLShape'
import { TLParentId, TLShapeId } from '../records/TLShape'
import { parentIdValidator, shapeIdValidator } from '../validation'
/** @public */
export interface TLBaseShape<Type extends string, Props extends object>
extends BaseRecord<'shape'> {
extends BaseRecord<'shape', TLShapeId> {
type: Type
x: number
y: number

Wyświetl plik

@ -1,5 +1,4 @@
import type { ID } from '@tldraw/tlstore'
import { BaseRecord } from '@tldraw/tlstore'
import type { ID, UnknownRecord } from '@tldraw/tlstore'
import { T } from '@tldraw/tlvalidate'
import type { TLAssetId } from './records/TLAsset'
import type { TLInstanceId } from './records/TLInstance'
@ -22,7 +21,7 @@ import {
} from './style-types'
/** @internal */
export function idValidator<Id extends ID<BaseRecord<any>>>(
export function idValidator<Id extends ID<UnknownRecord>>(
prefix: Id['__type__']['typeName']
): T.Validator<Id> {
return T.string.refine((id) => {

Wyświetl plik

@ -12,12 +12,12 @@ import { Signal } from 'signia';
export type AllRecords<T extends Store<any>> = ExtractR<ExtractRecordType<T>>;
// @public
export function assertIdType<R extends BaseRecord>(id: string | undefined, type: RecordType<R, any>): asserts id is ID<R>;
export function assertIdType<R extends UnknownRecord>(id: string | undefined, type: RecordType<R, any>): asserts id is IdOf<R>;
// @public
export interface BaseRecord<TypeName extends string = string> {
export interface BaseRecord<TypeName extends string, Id extends ID<UnknownRecord>> {
// (undocumented)
readonly id: ID<this>;
readonly id: Id;
// (undocumented)
readonly typeName: TypeName;
}
@ -35,12 +35,12 @@ export function compareRecordVersions(a: RecordVersion, b: RecordVersion): -1 |
export const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => -1 | 0 | 1;
// @public
export type ComputedCache<Data, R extends BaseRecord> = {
get(id: ID<R>): Data | undefined;
export type ComputedCache<Data, R extends UnknownRecord> = {
get(id: IdOf<R>): Data | undefined;
};
// @public
export function createRecordType<R extends BaseRecord>(typeName: R['typeName'], config: {
export function createRecordType<R extends UnknownRecord>(typeName: R['typeName'], config: {
migrations?: Migrations;
validator?: StoreValidator<R>;
scope: Scope;
@ -65,19 +65,22 @@ export function defineMigrations<FirstVersion extends EMPTY_SYMBOL | number = EM
export function devFreeze<T>(object: T): T;
// @public (undocumented)
export function getRecordVersion(record: BaseRecord, serializedSchema: SerializedSchema): RecordVersion;
export function getRecordVersion(record: UnknownRecord, serializedSchema: SerializedSchema): RecordVersion;
// @public
export type HistoryEntry<R extends BaseRecord = BaseRecord> = {
export type HistoryEntry<R extends UnknownRecord = UnknownRecord> = {
changes: RecordsDiff<R>;
source: 'remote' | 'user';
};
// @public (undocumented)
export type ID<R extends BaseRecord = BaseRecord> = string & {
export type ID<R extends UnknownRecord> = string & {
__type__: R;
};
// @public (undocumented)
export type IdOf<R extends UnknownRecord> = R['id'];
// @internal
export class IncrementalSetConstructor<T> {
constructor(
@ -102,7 +105,7 @@ export function migrate<T>({ value, migrations, fromVersion, toVersion, }: {
}): MigrationResult<T>;
// @public (undocumented)
export function migrateRecord<R extends BaseRecord>({ record, migrations, fromVersion, toVersion, }: {
export function migrateRecord<R extends UnknownRecord>({ record, migrations, fromVersion, toVersion, }: {
record: unknown;
migrations: Migrations;
fromVersion: number;
@ -149,14 +152,14 @@ export interface Migrations extends BaseMigrationsInfo {
}
// @public
export type RecordsDiff<R extends BaseRecord> = {
added: Record<string, R>;
updated: Record<string, [from: R, to: R]>;
removed: Record<string, R>;
export type RecordsDiff<R extends UnknownRecord> = {
added: Record<IdOf<R>, R>;
updated: Record<IdOf<R>, [from: R, to: R]>;
removed: Record<IdOf<R>, R>;
};
// @public
export class RecordType<R extends BaseRecord, RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>> {
export class RecordType<R extends UnknownRecord, RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>> {
constructor(
typeName: R['typeName'], config: {
readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>;
@ -168,15 +171,15 @@ export class RecordType<R extends BaseRecord, RequiredProperties extends keyof O
});
clone(record: R): R;
create(properties: Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>): R;
createCustomId(id: string): ID<R>;
createCustomId(id: string): IdOf<R>;
// (undocumented)
readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>;
createId(): ID<R>;
isId(id?: string): id is ID<R>;
isInstance: (record?: BaseRecord) => record is R;
createId(): IdOf<R>;
isId(id?: string): id is IdOf<R>;
isInstance: (record?: UnknownRecord) => record is R;
// (undocumented)
readonly migrations: Migrations;
parseId(id: string): ID<R>;
parseId(id: string): IdOf<R>;
// (undocumented)
readonly scope: Scope;
readonly typeName: R['typeName'];
@ -211,10 +214,10 @@ export interface SerializedSchema {
}
// @public
export function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>;
export function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T>;
// @public
export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
constructor(config: {
initialData?: StoreSnapshot<R>;
schema: StoreSchema<R, Props>;
@ -233,8 +236,8 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
extractingChanges(fn: () => void): RecordsDiff<R>;
// (undocumented)
_flushHistory(): void;
get: <K extends ID<R>>(id: K) => RecFromId<K> | undefined;
has: <K extends ID<R>>(id: K) => boolean;
get: <K extends IdOf<R>>(id: K) => RecFromId<K> | undefined;
has: <K extends IdOf<R>>(id: K) => boolean;
readonly history: Atom<number, RecordsDiff<R>>;
// @internal (undocumented)
isPossiblyCorrupted(): boolean;
@ -250,13 +253,13 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
readonly props: Props;
put: (records: R[], phaseOverride?: 'initialize') => void;
readonly query: StoreQueries<R>;
remove: (ids: ID<R>[]) => void;
remove: (ids: IdOf<R>[]) => void;
// (undocumented)
readonly schema: StoreSchema<R, Props>;
serialize: (filter?: ((record: R) => boolean) | undefined) => StoreSnapshot<R>;
serializeDocumentState: () => StoreSnapshot<R>;
unsafeGetWithoutCapture: <K extends ID<R>>(id: K) => RecFromId<K> | undefined;
update: <K extends ID<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => void;
unsafeGetWithoutCapture: <K extends IdOf<R>>(id: K) => RecFromId<K> | undefined;
update: <K extends IdOf<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => void;
// (undocumented)
validate(phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'): void;
}
@ -271,12 +274,12 @@ export type StoreError = {
};
// @public
export type StoreListener<R extends BaseRecord> = (entry: HistoryEntry<R>) => void;
export type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void;
// @public (undocumented)
export class StoreSchema<R extends BaseRecord, P = unknown> {
export class StoreSchema<R extends UnknownRecord, P = unknown> {
// (undocumented)
static create<R extends BaseRecord, P = unknown>(types: {
static create<R extends UnknownRecord, P = unknown>(types: {
[TypeName in R['typeName']]: {
createId: any;
};
@ -304,7 +307,7 @@ export class StoreSchema<R extends BaseRecord, P = unknown> {
}
// @public (undocumented)
export type StoreSchemaOptions<R extends BaseRecord, P> = {
export type StoreSchemaOptions<R extends UnknownRecord, P> = {
snapshotMigrations?: Migrations;
onValidationFailure?: (data: {
error: unknown;
@ -318,20 +321,23 @@ export type StoreSchemaOptions<R extends BaseRecord, P> = {
};
// @public
export type StoreSnapshot<R extends BaseRecord> = Record<string, R>;
export type StoreSnapshot<R extends UnknownRecord> = Record<IdOf<R>, R>;
// @public (undocumented)
export type StoreValidator<R extends BaseRecord> = {
export type StoreValidator<R extends UnknownRecord> = {
validate: (record: unknown) => R;
};
// @public (undocumented)
export type StoreValidators<R extends BaseRecord> = {
export type StoreValidators<R extends UnknownRecord> = {
[K in R['typeName']]: StoreValidator<Extract<R, {
typeName: K;
}>>;
};
// @public (undocumented)
export type UnknownRecord = BaseRecord<string, ID<UnknownRecord>>;
// (No @packageDocumentation comment for this package)
```

Wyświetl plik

@ -1,4 +1,4 @@
export type { BaseRecord, ID } from './lib/BaseRecord'
export type { BaseRecord, ID, IdOf, UnknownRecord } from './lib/BaseRecord'
export { IncrementalSetConstructor } from './lib/IncrementalSetConstructor'
export { RecordType, assertIdType, createRecordType } from './lib/RecordType'
export { Store, reverseRecordsDiff, squashRecordDiffs } from './lib/Store'

Wyświetl plik

@ -1,18 +1,24 @@
/** @public */
export type ID<R extends BaseRecord = BaseRecord> = string & { __type__: R }
export type ID<R extends UnknownRecord> = string & { __type__: R }
/** @public */
export type IdOf<R extends UnknownRecord> = R['id']
/**
* The base record that all records must extend.
*
* @public
*/
export interface BaseRecord<TypeName extends string = string> {
readonly id: ID<this>
export interface BaseRecord<TypeName extends string, Id extends ID<UnknownRecord>> {
readonly id: Id
readonly typeName: TypeName
}
export type OmitMeta<R extends BaseRecord> = R extends R ? Omit<R, 'id' | 'typeName'> : R
/** @public */
export type UnknownRecord = BaseRecord<string, ID<UnknownRecord>>
export function isRecord(record: unknown): record is BaseRecord {
export type OmitMeta<R extends UnknownRecord> = R extends R ? Omit<R, 'id' | 'typeName'> : R
export function isRecord(record: unknown): record is UnknownRecord {
return typeof record === 'object' && record !== null && 'id' in record && 'typeName' in record
}

Wyświetl plik

@ -1,6 +1,6 @@
import { structuredClone } from '@tldraw/utils'
import { nanoid } from 'nanoid'
import { BaseRecord, ID, OmitMeta } from './BaseRecord'
import { IdOf, OmitMeta, UnknownRecord } from './BaseRecord'
import { StoreValidator } from './Store'
import { Migrations } from './migrate'
@ -24,7 +24,7 @@ export type Scope = 'instance' | 'document' | 'presence'
* @public
*/
export class RecordType<
R extends BaseRecord,
R extends UnknownRecord,
RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>
> {
readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>
@ -97,8 +97,8 @@ export class RecordType<
* @returns The new ID.
* @public
*/
createId(): ID<R> {
return (this.typeName + ':' + nanoid()) as ID<R>
createId(): IdOf<R> {
return (this.typeName + ':' + nanoid()) as IdOf<R>
}
/**
@ -113,8 +113,8 @@ export class RecordType<
* @param id - The ID to base the new ID on.
* @returns The new ID.
*/
createCustomId(id: string): ID<R> {
return (this.typeName + ':' + id) as ID<R>
createCustomId(id: string): IdOf<R> {
return (this.typeName + ':' + id) as IdOf<R>
}
/**
@ -123,12 +123,12 @@ export class RecordType<
* @param id - The id
* @returns
*/
parseId(id: string): ID<R> {
parseId(id: string): IdOf<R> {
if (!this.isId(id)) {
throw new Error(`ID "${id}" is not a valid ID for type "${this.typeName}"`)
}
return id.slice(this.typeName.length + 1) as ID<R>
return id.slice(this.typeName.length + 1) as IdOf<R>
}
/**
@ -143,7 +143,7 @@ export class RecordType<
* @param record - The record to check.
* @returns Whether the record is an instance of this record type.
*/
isInstance = (record?: BaseRecord): record is R => {
isInstance = (record?: UnknownRecord): record is R => {
return record?.typeName === this.typeName
}
@ -159,7 +159,7 @@ export class RecordType<
* @param id - The id to check.
* @returns Whether the id is an id of this type.
*/
isId(id?: string): id is ID<R> {
isId(id?: string): id is IdOf<R> {
if (!id) return false
for (let i = 0; i < this.typeName.length; i++) {
if (id[i] !== this.typeName[i]) return false
@ -214,7 +214,7 @@ export class RecordType<
* @param typeName - The name of the type to create.
* @public
*/
export function createRecordType<R extends BaseRecord>(
export function createRecordType<R extends UnknownRecord>(
typeName: R['typeName'],
config: {
migrations?: Migrations
@ -243,10 +243,10 @@ export function createRecordType<R extends BaseRecord>(
* @param type - The type of the record.
* @public
*/
export function assertIdType<R extends BaseRecord>(
export function assertIdType<R extends UnknownRecord>(
id: string | undefined,
type: RecordType<R, any>
): asserts id is ID<R> {
): asserts id is IdOf<R> {
if (!id || !type.isId(id)) {
throw new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)
}

Wyświetl plik

@ -1,23 +1,29 @@
import { throttledRaf } from '@tldraw/utils'
import { atom, Atom, computed, Computed, Reactor, reactor, transact } from 'signia'
import { BaseRecord, ID } from './BaseRecord'
import {
objectMapEntries,
objectMapFromEntries,
objectMapKeys,
objectMapValues,
throttledRaf,
} from '@tldraw/utils'
import { Atom, Computed, Reactor, atom, computed, reactor, transact } from 'signia'
import { ID, IdOf, UnknownRecord } from './BaseRecord'
import { Cache } from './Cache'
import { devFreeze } from './devFreeze'
import { RecordType } from './RecordType'
import { StoreQueries } from './StoreQueries'
import { StoreSchema } from './StoreSchema'
import { devFreeze } from './devFreeze'
type RecFromId<K extends ID> = K extends ID<infer R> ? R : never
type RecFromId<K extends ID<UnknownRecord>> = K extends ID<infer R> ? R : never
/**
* A diff describing the changes to a record.
*
* @public
*/
export type RecordsDiff<R extends BaseRecord> = {
added: Record<string, R>
updated: Record<string, [from: R, to: R]>
removed: Record<string, R>
export type RecordsDiff<R extends UnknownRecord> = {
added: Record<IdOf<R>, R>
updated: Record<IdOf<R>, [from: R, to: R]>
removed: Record<IdOf<R>, R>
}
/**
@ -32,7 +38,7 @@ export type CollectionDiff<T> = { added?: Set<T>; removed?: Set<T> }
*
* @public
*/
export type HistoryEntry<R extends BaseRecord = BaseRecord> = {
export type HistoryEntry<R extends UnknownRecord = UnknownRecord> = {
changes: RecordsDiff<R>
source: 'user' | 'remote'
}
@ -42,15 +48,15 @@ export type HistoryEntry<R extends BaseRecord = BaseRecord> = {
*
* @public
*/
export type StoreListener<R extends BaseRecord> = (entry: HistoryEntry<R>) => void
export type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void
/**
* A record store is a collection of records of different types.
*
* @public
*/
export type ComputedCache<Data, R extends BaseRecord> = {
get(id: ID<R>): Data | undefined
export type ComputedCache<Data, R extends UnknownRecord> = {
get(id: IdOf<R>): Data | undefined
}
/**
@ -58,15 +64,15 @@ export type ComputedCache<Data, R extends BaseRecord> = {
*
* @public
*/
export type StoreSnapshot<R extends BaseRecord> = Record<string, R>
export type StoreSnapshot<R extends UnknownRecord> = Record<IdOf<R>, R>
/** @public */
export type StoreValidator<R extends BaseRecord> = {
export type StoreValidator<R extends UnknownRecord> = {
validate: (record: unknown) => R
}
/** @public */
export type StoreValidators<R extends BaseRecord> = {
export type StoreValidators<R extends UnknownRecord> = {
[K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>>
}
@ -87,14 +93,14 @@ export type StoreRecord<S extends Store<any>> = S extends Store<infer R> ? R : n
*
* @public
*/
export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
/**
* An atom containing the store's atoms.
*
* @internal
* @readonly
*/
private readonly atoms: Atom<Record<ID<R>, Atom<R>>> = atom('store_atoms', {})
private readonly atoms = atom('store_atoms', {} as Record<IdOf<R>, Atom<R>>)
/**
* An atom containing the store's history.
@ -157,8 +163,8 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
if (initialData) {
this.atoms.set(
Object.fromEntries(
Object.entries(initialData).map(([id, record]) => [
objectMapFromEntries(
objectMapEntries(initialData).map(([id, record]) => [
id,
atom('atom:' + id, this.schema.validateRecord(this, record, 'initialize', null)),
])
@ -249,11 +255,11 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
*/
put = (records: R[], phaseOverride?: 'initialize'): void => {
transact(() => {
const updates: Record<ID<R>, [from: R, to: R]> = {}
const additions: Record<ID<R>, R> = {}
const updates: Record<IdOf<UnknownRecord>, [from: R, to: R]> = {}
const additions: Record<IdOf<UnknownRecord>, R> = {}
const currentMap = this.atoms.__unsafe__getWithoutCapture()
let map = null as null | Record<ID<R>, Atom<R>>
let map = null as null | Record<IdOf<UnknownRecord>, Atom<R>>
// Iterate through all records, creating, updating or removing as needed
let record: R
@ -267,7 +273,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
for (let i = 0, n = records.length; i < n; i++) {
record = records[i]
const recordAtom = (map ?? currentMap)[record.id]
const recordAtom = (map ?? currentMap)[record.id as IdOf<R>]
if (recordAtom) {
// If we already have an atom for this record, update its value.
@ -326,7 +332,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
this.updateHistory({
added: additions,
updated: updates,
removed: {},
removed: {} as Record<IdOf<R>, R>,
})
const { onAfterCreate, onAfterChange } = this
@ -353,7 +359,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @param ids - The ids of the records to remove.
* @public
*/
remove = (ids: ID<R>[]): void => {
remove = (ids: IdOf<R>[]): void => {
transact(() => {
if (this.onBeforeDelete && this._runCallbacks) {
for (const id of ids) {
@ -373,7 +379,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
for (const id of ids) {
if (!(id in atoms)) continue
if (!result) result = { ...atoms }
if (!removed) removed = {}
if (!removed) removed = {} as Record<IdOf<R>, R>
delete result[id]
removed[id] = atoms[id].value
}
@ -383,7 +389,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
if (!removed) return
// Update the history with the removed records.
this.updateHistory({ added: {}, updated: {}, removed })
this.updateHistory({ added: {}, updated: {}, removed } as RecordsDiff<R>)
// If we have an onAfterChange, run it for each removed record.
if (this.onAfterDelete && this._runCallbacks) {
@ -400,7 +406,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @param id - The id of the record to get.
* @public
*/
get = <K extends ID<R>>(id: K): RecFromId<K> | undefined => {
get = <K extends IdOf<R>>(id: K): RecFromId<K> | undefined => {
return this.atoms.value[id]?.value as any
}
@ -410,7 +416,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @param id - The id of the record to get.
* @public
*/
unsafeGetWithoutCapture = <K extends ID<R>>(id: K): RecFromId<K> | undefined => {
unsafeGetWithoutCapture = <K extends IdOf<R>>(id: K): RecFromId<K> | undefined => {
return this.atoms.value[id]?.__unsafe__getWithoutCapture() as any
}
@ -421,11 +427,11 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @returns The record store snapshot as a JSON payload.
*/
serialize = (filter?: (record: R) => boolean): StoreSnapshot<R> => {
const result: Record<string, any> = {}
for (const [id, atom] of Object.entries(this.atoms.value)) {
const result = {} as StoreSnapshot<R>
for (const [id, atom] of objectMapEntries(this.atoms.value)) {
const record = atom.value
if (typeof filter === 'function' && !filter(record)) continue
result[id] = record
result[id as IdOf<R>] = record
}
return result
}
@ -462,7 +468,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @public
*/
allRecords = (): R[] => {
return Object.values(this.atoms.value).map((atom) => atom.value)
return objectMapValues(this.atoms.value).map((atom) => atom.value)
}
/**
@ -471,7 +477,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @public
*/
clear = (): void => {
this.remove(Object.keys(this.atoms.value) as any)
this.remove(objectMapKeys(this.atoms.value))
}
/**
@ -481,7 +487,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @param id - The id of the record to update.
* @param updater - A function that updates the record.
*/
update = <K extends ID<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => {
update = <K extends IdOf<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => {
const atom = this.atoms.value[id]
if (!atom) {
console.error(`Record ${id} not found. This is probably an error`)
@ -496,7 +502,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @param id - The id of the record to check.
* @public
*/
has = <K extends ID<R>>(id: K): boolean => {
has = <K extends IdOf<R>>(id: K): boolean => {
return !!this.atoms.value[id]
}
@ -562,10 +568,10 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
try {
this._runCallbacks = runCallbacks
transact(() => {
const toPut = Object.values(diff.added).concat(
Object.values(diff.updated).map(([_from, to]) => to)
const toPut = objectMapValues(diff.added).concat(
objectMapValues(diff.updated).map(([_from, to]) => to)
)
const toRemove = Object.keys(diff.removed) as ID<R>[]
const toRemove = objectMapKeys(diff.removed)
if (toPut.length) {
this.put(toPut)
}
@ -591,7 +597,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
): ComputedCache<T, V> => {
const cache = new Cache<Atom<any>, Computed<T | undefined>>()
return {
get: (id: ID<V>) => {
get: (id: IdOf<V>) => {
const atom = this.atoms.value[id]
if (!atom) {
return undefined
@ -618,7 +624,7 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
): ComputedCache<J, V> => {
const cache = new Cache<Atom<any>, Computed<J | undefined>>()
return {
get: (id: ID<V>) => {
get: (id: IdOf<V>) => {
const atom = this.atoms.value[id]
if (!atom) {
return undefined
@ -660,11 +666,13 @@ export class Store<R extends BaseRecord = BaseRecord, Props = unknown> {
* @returns A single diff that represents the squashed diffs.
* @public
*/
export function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[]): RecordsDiff<T> {
const result: RecordsDiff<T> = { added: {}, removed: {}, updated: {} }
export function squashRecordDiffs<T extends UnknownRecord>(
diffs: RecordsDiff<T>[]
): RecordsDiff<T> {
const result = { added: {}, removed: {}, updated: {} } as RecordsDiff<T>
for (const diff of diffs) {
for (const [id, value] of Object.entries(diff.added)) {
for (const [id, value] of objectMapEntries(diff.added)) {
if (result.removed[id]) {
const original = result.removed[id]
delete result.removed[id]
@ -676,7 +684,7 @@ export function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[])
}
}
for (const [id, [_from, to]] of Object.entries(diff.updated)) {
for (const [id, [_from, to]] of objectMapEntries(diff.updated)) {
if (result.added[id]) {
result.added[id] = to
delete result.updated[id]
@ -693,7 +701,7 @@ export function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[])
delete result.removed[id]
}
for (const [id, value] of Object.entries(diff.removed)) {
for (const [id, value] of objectMapEntries(diff.removed)) {
// the same record was added in this diff sequence, just drop it
if (result.added[id]) {
delete result.added[id]
@ -716,7 +724,9 @@ export function squashRecordDiffs<T extends BaseRecord>(diffs: RecordsDiff<T>[])
* @returns A map of history entries by their sources.
* @public
*/
function squashHistoryEntries<T extends BaseRecord>(entries: HistoryEntry<T>[]): HistoryEntry<T>[] {
function squashHistoryEntries<T extends UnknownRecord>(
entries: HistoryEntry<T>[]
): HistoryEntry<T>[] {
const result: HistoryEntry<T>[] = []
let current = entries[0]
@ -750,7 +760,7 @@ export function reverseRecordsDiff(diff: RecordsDiff<any>) {
return result
}
class HistoryAccumulator<T extends BaseRecord> {
class HistoryAccumulator<T extends UnknownRecord> {
private _history: HistoryEntry<T>[] = []
private _inteceptors: Set<(entry: HistoryEntry<T>) => void> = new Set()

Wyświetl plik

@ -1,3 +1,4 @@
import { objectMapValues } from '@tldraw/utils'
import isEqual from 'lodash.isequal'
import {
Atom,
@ -8,34 +9,34 @@ import {
RESET_VALUE,
withDiff,
} from 'signia'
import { BaseRecord, ID } from './BaseRecord'
import { IdOf, UnknownRecord } from './BaseRecord'
import { executeQuery, objectMatchesQuery, QueryExpression } from './executeQuery'
import { IncrementalSetConstructor } from './IncrementalSetConstructor'
import { diffSets } from './setUtils'
import { CollectionDiff, RecordsDiff } from './Store'
export type RSIndexDiff<
R extends BaseRecord = BaseRecord,
R extends UnknownRecord,
Property extends string & keyof R = string & keyof R
> = Map<R[Property], CollectionDiff<ID<R>>>
> = Map<R[Property], CollectionDiff<IdOf<R>>>
export type RSIndexMap<
R extends BaseRecord = BaseRecord,
R extends UnknownRecord,
Property extends string & keyof R = string & keyof R
> = Map<R[Property], Set<ID<R>>>
> = Map<R[Property], Set<IdOf<R>>>
export type RSIndex<
R extends BaseRecord = BaseRecord,
R extends UnknownRecord,
Property extends string & keyof R = string & keyof R
> = Computed<Map<R[Property], Set<ID<R>>>, RSIndexDiff<R, Property>>
> = Computed<Map<R[Property], Set<IdOf<R>>>, RSIndexDiff<R, Property>>
/**
* A class that provides a 'namespace' for the various kinds of indexes one may wish to derive from
* the record store.
*/
export class StoreQueries<R extends BaseRecord = BaseRecord> {
export class StoreQueries<R extends UnknownRecord> {
constructor(
private readonly atoms: Atom<Record<ID<R>, Atom<R>>>,
private readonly atoms: Atom<Record<IdOf<R>, Atom<R>>>,
private readonly history: Atom<number, RecordsDiff<R>>
) {}
@ -79,56 +80,56 @@ export class StoreQueries<R extends BaseRecord = BaseRecord> {
const diff = this.history.getDiffSince(lastComputedEpoch)
if (diff === RESET_VALUE) return this.history.value
const res: RecordsDiff<S> = { added: {}, removed: {}, updated: {} }
const res = { added: {}, removed: {}, updated: {} } as RecordsDiff<S>
let numAdded = 0
let numRemoved = 0
let numUpdated = 0
for (const changes of diff) {
for (const added of Object.values(changes.added)) {
for (const added of objectMapValues(changes.added)) {
if (added.typeName === typeName) {
if (res.removed[added.id]) {
const original = res.removed[added.id]
delete res.removed[added.id]
if (res.removed[added.id as IdOf<S>]) {
const original = res.removed[added.id as IdOf<S>]
delete res.removed[added.id as IdOf<S>]
numRemoved--
if (original !== added) {
res.updated[added.id] = [original, added as S]
res.updated[added.id as IdOf<S>] = [original, added as S]
numUpdated++
}
} else {
res.added[added.id] = added as S
res.added[added.id as IdOf<S>] = added as S
numAdded++
}
}
}
for (const [from, to] of Object.values(changes.updated)) {
for (const [from, to] of objectMapValues(changes.updated)) {
if (to.typeName === typeName) {
if (res.added[to.id]) {
res.added[to.id] = to as S
} else if (res.updated[to.id]) {
res.updated[to.id] = [res.updated[to.id][0], to as S]
if (res.added[to.id as IdOf<S>]) {
res.added[to.id as IdOf<S>] = to as S
} else if (res.updated[to.id as IdOf<S>]) {
res.updated[to.id as IdOf<S>] = [res.updated[to.id as IdOf<S>][0], to as S]
} else {
res.updated[to.id] = [from as S, to as S]
res.updated[to.id as IdOf<S>] = [from as S, to as S]
numUpdated++
}
}
}
for (const removed of Object.values(changes.removed)) {
for (const removed of objectMapValues(changes.removed)) {
if (removed.typeName === typeName) {
if (res.added[removed.id]) {
if (res.added[removed.id as IdOf<S>]) {
// was added during this diff sequence, so just undo the add
delete res.added[removed.id]
delete res.added[removed.id as IdOf<S>]
numAdded--
} else if (res.updated[removed.id]) {
} else if (res.updated[removed.id as IdOf<S>]) {
// remove oldest version
res.removed[removed.id] = res.updated[removed.id][0]
delete res.updated[removed.id]
res.removed[removed.id as IdOf<S>] = res.updated[removed.id as IdOf<S>][0]
delete res.updated[removed.id as IdOf<S>]
numUpdated--
numRemoved++
} else {
res.removed[removed.id] = removed as S
res.removed[removed.id as IdOf<S>] = removed as S
numRemoved++
}
}
@ -192,15 +193,15 @@ export class StoreQueries<R extends BaseRecord = BaseRecord> {
// deref typeHistory early so that the first time the incremental version runs
// it gets a diff to work with instead of having to bail to this from-scratch version
typeHistory.value
const res = new Map<S[Property], Set<ID<S>>>()
for (const atom of Object.values(this.atoms.value)) {
const res = new Map<S[Property], Set<IdOf<S>>>()
for (const atom of objectMapValues(this.atoms.value)) {
const record = atom.value
if (record.typeName === typeName) {
const value = (record as S)[property]
if (!res.has(value)) {
res.set(value, new Set())
}
res.get(value)!.add((record as S).id)
res.get(value)!.add(record.id)
}
}
@ -217,44 +218,46 @@ export class StoreQueries<R extends BaseRecord = BaseRecord> {
return fromScratch()
}
const setConstructors = new Map<any, IncrementalSetConstructor<ID<S>>>()
const setConstructors = new Map<any, IncrementalSetConstructor<IdOf<S>>>()
const add = (value: S[Property], id: ID<S>) => {
const add = (value: S[Property], id: IdOf<S>) => {
let setConstructor = setConstructors.get(value)
if (!setConstructor)
setConstructor = new IncrementalSetConstructor<ID<S>>(prevValue.get(value) ?? new Set())
setConstructor = new IncrementalSetConstructor<IdOf<S>>(
prevValue.get(value) ?? new Set()
)
setConstructor.add(id)
setConstructors.set(value, setConstructor)
}
const remove = (value: S[Property], id: ID<S>) => {
const remove = (value: S[Property], id: IdOf<S>) => {
let set = setConstructors.get(value)
if (!set) set = new IncrementalSetConstructor<ID<S>>(prevValue.get(value) ?? new Set())
if (!set) set = new IncrementalSetConstructor<IdOf<S>>(prevValue.get(value) ?? new Set())
set.remove(id)
setConstructors.set(value, set)
}
for (const changes of history) {
for (const record of Object.values(changes.added)) {
for (const record of objectMapValues(changes.added)) {
if (record.typeName === typeName) {
const value = (record as S)[property]
add(value, (record as S).id)
add(value, record.id)
}
}
for (const [from, to] of Object.values(changes.updated)) {
for (const [from, to] of objectMapValues(changes.updated)) {
if (to.typeName === typeName) {
const prev = (from as S)[property]
const next = (to as S)[property]
if (prev !== next) {
remove(prev, (to as S).id)
add(next, (to as S).id)
remove(prev, to.id)
add(next, to.id)
}
}
}
for (const record of Object.values(changes.removed)) {
for (const record of objectMapValues(changes.removed)) {
if (record.typeName === typeName) {
const value = (record as S)[property]
remove(value, (record as S).id)
remove(value, record.id)
}
}
}
@ -348,8 +351,8 @@ export class StoreQueries<R extends BaseRecord = BaseRecord> {
queryCreator: () => QueryExpression<Extract<R, { typeName: TypeName }>> = () => ({}),
name = 'ids:' + typeName + (queryCreator ? ':' + queryCreator.toString() : '')
): Computed<
Set<ID<Extract<R, { typeName: TypeName }>>>,
CollectionDiff<ID<Extract<R, { typeName: TypeName }>>>
Set<IdOf<Extract<R, { typeName: TypeName }>>>,
CollectionDiff<IdOf<Extract<R, { typeName: TypeName }>>>
> {
type S = Extract<R, { typeName: TypeName }>
@ -360,11 +363,11 @@ export class StoreQueries<R extends BaseRecord = BaseRecord> {
typeHistory.value
const query: QueryExpression<S> = queryCreator()
if (Object.keys(query).length === 0) {
return new Set<ID<S>>(
Object.values(this.atoms.value).flatMap((v) => {
return new Set<IdOf<S>>(
objectMapValues(this.atoms.value).flatMap((v) => {
const r = v.value
if (r.typeName === typeName) {
return r.id as ID<S>
return r.id
} else {
return []
}
@ -375,7 +378,7 @@ export class StoreQueries<R extends BaseRecord = BaseRecord> {
return executeQuery(this, typeName, query)
}
const fromScratchWithDiff = (prevValue: Set<ID<S>>) => {
const fromScratchWithDiff = (prevValue: Set<IdOf<S>>) => {
const nextValue = fromScratch()
const diff = diffSets(prevValue, nextValue)
if (diff) {
@ -407,28 +410,28 @@ export class StoreQueries<R extends BaseRecord = BaseRecord> {
return fromScratchWithDiff(prevValue)
}
const setConstructor = new IncrementalSetConstructor<ID<S>>(
const setConstructor = new IncrementalSetConstructor<IdOf<S>>(
prevValue
) as IncrementalSetConstructor<ID<S>>
) as IncrementalSetConstructor<IdOf<S>>
for (const changes of history) {
for (const added of Object.values(changes.added)) {
for (const added of objectMapValues(changes.added)) {
if (added.typeName === typeName && objectMatchesQuery(query, added)) {
setConstructor.add(added.id as ID<S>)
setConstructor.add(added.id)
}
}
for (const [_, updated] of Object.values(changes.updated)) {
for (const [_, updated] of objectMapValues(changes.updated)) {
if (updated.typeName === typeName) {
if (objectMatchesQuery(query, updated)) {
setConstructor.add(updated.id as ID<S>)
setConstructor.add(updated.id)
} else {
setConstructor.remove(updated.id as ID<S>)
setConstructor.remove(updated.id)
}
}
}
for (const removed of Object.values(changes.removed)) {
for (const removed of objectMapValues(changes.removed)) {
if (removed.typeName === typeName) {
setConstructor.remove(removed.id as ID<S>)
setConstructor.remove(removed.id)
}
}
}

Wyświetl plik

@ -1,6 +1,6 @@
import { getOwnProperty, objectMapValues } from '@tldraw/utils'
import { Signal } from 'signia'
import { BaseRecord } from './BaseRecord'
import { IdOf, UnknownRecord } from './BaseRecord'
import { RecordType } from './RecordType'
import { Store, StoreSnapshot } from './Store'
import {
@ -36,7 +36,7 @@ export interface SerializedSchema {
}
/** @public */
export type StoreSchemaOptions<R extends BaseRecord, P> = {
export type StoreSchemaOptions<R extends UnknownRecord, P> = {
/** @public */
snapshotMigrations?: Migrations
/** @public */
@ -54,8 +54,8 @@ export type StoreSchemaOptions<R extends BaseRecord, P> = {
}
/** @public */
export class StoreSchema<R extends BaseRecord, P = unknown> {
static create<R extends BaseRecord, P = unknown>(
export class StoreSchema<R extends UnknownRecord, P = unknown> {
static create<R extends UnknownRecord, P = unknown>(
// HACK: making this param work with RecordType is an enormous pain
// let's just settle for making sure each typeName has a corresponding RecordType
// and accept that this function won't be able to infer the record type from it's arguments
@ -222,7 +222,7 @@ export class StoreSchema<R extends BaseRecord, P = unknown> {
}
const updated: R[] = []
for (const r of Object.values(storeSnapshot)) {
for (const r of objectMapValues(storeSnapshot)) {
const result = this.migratePersistedRecord(r, persistedSchema)
if (result.type === 'error') {
return result
@ -233,7 +233,7 @@ export class StoreSchema<R extends BaseRecord, P = unknown> {
if (updated.length) {
storeSnapshot = { ...storeSnapshot }
for (const r of updated) {
storeSnapshot[r.id] = r
storeSnapshot[r.id as IdOf<R>] = r
}
}
return { type: 'success', value: storeSnapshot }

Wyświetl plik

@ -1,4 +1,4 @@
import { BaseRecord, ID } from './BaseRecord'
import { IdOf, UnknownRecord } from './BaseRecord'
import { intersectSets } from './setUtils'
import { StoreQueries } from './StoreQueries'
@ -24,11 +24,11 @@ export function objectMatchesQuery<T extends object>(query: QueryExpression<T>,
return true
}
export function executeQuery<R extends BaseRecord, TypeName extends R['typeName']>(
export function executeQuery<R extends UnknownRecord, TypeName extends R['typeName']>(
store: StoreQueries<R>,
typeName: TypeName,
query: QueryExpression<Extract<R, { typeName: TypeName }>>
): Set<ID<Extract<R, { typeName: TypeName }>>> {
): Set<IdOf<Extract<R, { typeName: TypeName }>>> {
const matchIds = Object.fromEntries(Object.keys(query).map((key) => [key, new Set()]))
for (const [k, matcher] of Object.entries(query)) {
@ -61,5 +61,5 @@ export function executeQuery<R extends BaseRecord, TypeName extends R['typeName'
}
}
return intersectSets(Object.values(matchIds)) as Set<ID<Extract<R, { typeName: TypeName }>>>
return intersectSets(Object.values(matchIds)) as Set<IdOf<Extract<R, { typeName: TypeName }>>>
}

Wyświetl plik

@ -1,4 +1,4 @@
import { BaseRecord, isRecord } from './BaseRecord'
import { UnknownRecord, isRecord } from './BaseRecord'
import { SerializedSchema } from './StoreSchema'
type EMPTY_SYMBOL = symbol
@ -77,7 +77,7 @@ export enum MigrationFailureReason {
export type RecordVersion = { rootVersion: number; subTypeVersion?: number }
/** @public */
export function getRecordVersion(
record: BaseRecord,
record: UnknownRecord,
serializedSchema: SerializedSchema
): RecordVersion {
const persistedType = serializedSchema.recordVersions[record.typeName]
@ -112,7 +112,7 @@ export function compareRecordVersions(a: RecordVersion, b: RecordVersion) {
}
/** @public */
export function migrateRecord<R extends BaseRecord>({
export function migrateRecord<R extends UnknownRecord>({
record,
migrations,
fromVersion,

Wyświetl plik

@ -4,7 +4,7 @@ import { createRecordType } from '../RecordType'
import { CollectionDiff, RecordsDiff, Store } from '../Store'
import { StoreSchema } from '../StoreSchema'
interface Book extends BaseRecord<'book'> {
interface Book extends BaseRecord<'book', ID<Book>> {
title: string
author: ID<Author>
numPages: number
@ -15,7 +15,7 @@ const Book = createRecordType<Book>('book', {
scope: 'document',
})
interface Author extends BaseRecord<'author'> {
interface Author extends BaseRecord<'author', ID<Author>> {
name: string
isPseudonym: boolean
}

Wyświetl plik

@ -1,12 +1,12 @@
import { atom, EffectScheduler, RESET_VALUE } from 'signia'
import { BaseRecord, ID } from '../BaseRecord'
import { BaseRecord, ID, IdOf, UnknownRecord } from '../BaseRecord'
import { executeQuery } from '../executeQuery'
import { createRecordType } from '../RecordType'
import { CollectionDiff, Store } from '../Store'
import { RSIndexDiff } from '../StoreQueries'
import { StoreSchema } from '../StoreSchema'
interface Author extends BaseRecord<'author'> {
interface Author extends BaseRecord<'author', ID<Author>> {
name: AuthorName
age: number
}
@ -24,7 +24,7 @@ const Author = createRecordType<Author>('author', {
scope: 'document',
}).withDefaultProperties(() => ({ age: 23 }))
interface Book extends BaseRecord<'book'> {
interface Book extends BaseRecord<'book', ID<Book>> {
title: BookName
authorId: ID<Author>
}
@ -55,10 +55,10 @@ type Record = Author | Book
type Op =
| { readonly type: 'add'; readonly record: Record }
| { readonly type: 'delete'; readonly id: ID<Record> }
| { readonly type: 'delete'; readonly id: IdOf<Record> }
| { readonly type: 'update'; readonly record: Record }
| { readonly type: 'set_book_name_query_param'; readonly bookName: BookName }
| { readonly type: 'set_author_id_query_param'; readonly authorId: ID<Author> }
| { readonly type: 'set_author_id_query_param'; readonly authorId: IdOf<Author> }
const BOOK_NAMES = [
'Breakfast of Champions',
@ -281,11 +281,11 @@ function getRandomOp(
}
}
function recreateIndexFromDiffs(diffs: RSIndexDiff[]) {
const result = new Map<string, Set<ID>>()
function recreateIndexFromDiffs(diffs: RSIndexDiff<any>[]) {
const result = new Map<string, Set<IdOf<UnknownRecord>>>()
for (const diff of diffs) {
for (const [key, changes] of diff) {
const index = result.get(key) || new Set<ID>()
const index = result.get(key) || new Set<IdOf<UnknownRecord>>()
if (changes.added) {
for (const id of changes.added) {
index.add(id)
@ -352,8 +352,8 @@ function runTest(seed: number) {
const authorNameIndex = store.query.index('author', 'name')
const authorIdIndex = store.query.index('book', 'authorId')
const authorNameIndexDiffs: RSIndexDiff[] = []
const authorIdIndexDiffs: RSIndexDiff[] = []
const authorNameIndexDiffs: RSIndexDiff<Author>[] = []
const authorIdIndexDiffs: RSIndexDiff<Book>[] = []
const authorIdQueryParam = atom('authorId', Author.createCustomId('does-not-exist'))
const bookTitleQueryParam = atom('bookTitle', getRandomBookName(getRandomNumber))

Wyświetl plik

@ -4,7 +4,7 @@ import { createRecordType } from '../RecordType'
import { Store } from '../Store'
import { StoreSchema } from '../StoreSchema'
interface Author extends BaseRecord<'author'> {
interface Author extends BaseRecord<'author', ID<Author>> {
name: string
age: number
}
@ -22,7 +22,7 @@ const Author = createRecordType<Author>('author', {
scope: 'document',
}).withDefaultProperties(() => ({ age: 23 }))
interface Book extends BaseRecord<'book'> {
interface Book extends BaseRecord<'book', ID<Book>> {
title: string
authorId: ID<Author>
}

Wyświetl plik

@ -1,11 +1,11 @@
import { assert } from '@tldraw/utils'
import { BaseRecord } from '../BaseRecord'
import { BaseRecord, ID } from '../BaseRecord'
import { createRecordType } from '../RecordType'
import { StoreSchema } from '../StoreSchema'
import { defineMigrations } from '../migrate'
/** A user of tldraw */
interface User extends BaseRecord<'user'> {
interface User extends BaseRecord<'user', ID<User>> {
name: string
}
@ -24,7 +24,7 @@ const User = createRecordType<User>('user', {
scope: 'document',
})
interface Shape<Props> extends BaseRecord<'shape'> {
interface Shape<Props> extends BaseRecord<'shape', ID<Shape<object>>> {
type: string
x: number
y: number
@ -72,7 +72,7 @@ const Shape = createRecordType<Shape<RectangleProps | OvalProps>>('shape', {
})
// this interface only exists to be removed
interface Org extends BaseRecord<'org'> {
interface Org extends BaseRecord<'org', ID<Org>> {
name: string
}

Wyświetl plik

@ -11,7 +11,7 @@ const UserVersion = {
} as const
/** A user of tldraw */
interface User extends BaseRecord<'user'> {
interface User extends BaseRecord<'user', ID<User>> {
name: string
locale: string
phoneNumber: string | null
@ -79,12 +79,14 @@ const OvalVersion = {
AddBorderStyle: 1,
} as const
interface Shape<Props> extends BaseRecord<'shape'> {
type ShapeId = ID<Shape<object>>
interface Shape<Props> extends BaseRecord<'shape', ShapeId> {
type: string
x: number
y: number
rotation: number
parentId: ID<Shape<Props>> | null
parentId: ShapeId | null
props: Props
}

Wyświetl plik

@ -1,11 +1,11 @@
import { BaseRecord, ID } from '../BaseRecord'
import { BaseRecord, ID, IdOf } from '../BaseRecord'
import { createRecordType } from '../RecordType'
import { Store, StoreSnapshot } from '../Store'
import { StoreSchema } from '../StoreSchema'
interface Book extends BaseRecord<'book'> {
interface Book extends BaseRecord<'book', ID<Book>> {
title: string
author: ID<Author>
author: IdOf<Author>
numPages: number
}
@ -24,7 +24,7 @@ const Book = createRecordType<Book>('book', {
scope: 'document',
})
interface Author extends BaseRecord<'author'> {
interface Author extends BaseRecord<'author', ID<Author>> {
name: string
isPseudonym: boolean
}

Wyświetl plik

@ -1,4 +1,4 @@
import { TLBookmarkUtil, TLShape, useApp } from '@tldraw/editor'
import { TLBaseShape, TLBookmarkUtil, useApp } from '@tldraw/editor'
import { useCallback, useRef, useState } from 'react'
import { track } from 'signia-react'
import { DialogProps } from '../hooks/useDialogsProvider'
@ -13,46 +13,45 @@ const validUrlRegex = new RegExp(
// A url can either be invalid, or valid with a protocol, or valid without a protocol.
// For example, "aol.com" would be valid with a protocol ()
function valiateUrl(url: string) {
function validateUrl(url: string) {
if (validUrlRegex.test(url)) return true
if (validUrlRegex.test('https://' + url)) return 'needs protocol'
return false
}
type ShapeWithUrl = TLBaseShape<string, { url: string }>
export const EditLinkDialog = track(function EditLinkDialog({ onClose }: DialogProps) {
const app = useApp()
const selectedShape = app.onlySelectedShape
if (!(selectedShape && 'url' in selectedShape.props)) {
if (
!(selectedShape && 'url' in selectedShape.props && typeof selectedShape.props.url === 'string')
) {
return null
}
return (
<EditLinkDialogInner
onClose={onClose}
selectedShape={selectedShape as TLShape & { props: { url: string } }}
/>
)
return <EditLinkDialogInner onClose={onClose} selectedShape={selectedShape as ShapeWithUrl} />
})
export const EditLinkDialogInner = track(function EditLinkDialogInner({
onClose,
selectedShape,
}: DialogProps & { selectedShape: TLShape & { props: { url: string } } }) {
}: DialogProps & { selectedShape: ShapeWithUrl }) {
const app = useApp()
const msg = useTranslation()
const [validState, setValid] = useState(valiateUrl(selectedShape?.props.url))
const [validState, setValid] = useState(validateUrl(selectedShape.props.url))
const rInitialValue = useRef(selectedShape?.props.url)
const rInitialValue = useRef(selectedShape.props.url)
const rValue = useRef(selectedShape?.props.url)
const rValue = useRef(selectedShape.props.url)
const [urlValue, setUrlValue] = useState<string>(
validState
? validState === 'needs protocol'
? 'https://' + selectedShape?.props.url
: selectedShape?.props.url
? 'https://' + selectedShape.props.url
: selectedShape.props.url
: 'https://'
)
@ -63,7 +62,7 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({
})
setUrlValue(value)
const validStateUrl = valiateUrl(value.trim())
const validStateUrl = validateUrl(value.trim())
setValid((s) => (s === validStateUrl ? s : validStateUrl))
if (validStateUrl) {
rValue.current = value
@ -78,7 +77,7 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({
const handleComplete = useCallback(
(value: string) => {
value = value.trim()
const validState = valiateUrl(value)
const validState = validateUrl(value)
const shape = app.selectedShapes[0]

Wyświetl plik

@ -211,21 +211,22 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
trackEvent('toggle-auto-size', { source })
app.mark()
app.updateShapes(
(
app.selectedShapes.filter(
(shape) => app.isShapeOfType(shape, TLTextUtil) && shape.props.autoSize === false
) as TLTextShape[]
).map((shape) => {
return {
id: shape.id,
type: shape.type,
props: {
...shape.props,
w: 8,
autoSize: true,
},
}
})
app.selectedShapes
.filter(
(shape): shape is TLTextShape =>
app.isShapeOfType(shape, TLTextUtil) && shape.props.autoSize === false
)
.map((shape) => {
return {
id: shape.id,
type: shape.type,
props: {
...shape.props,
w: 8,
autoSize: true,
},
}
})
)
},
},

Wyświetl plik

@ -94,6 +94,11 @@ export function objectMapEntries<Key extends string, Value>(object: {
[K in Key]: Value;
}): Array<[Key, Value]>;
// @internal
export function objectMapFromEntries<Key extends string, Value>(entries: ReadonlyArray<readonly [Key, Value]>): {
[K in Key]: Value;
};
// @internal
export function objectMapKeys<Key extends string>(object: {
readonly [K in Key]: unknown;

Wyświetl plik

@ -20,6 +20,7 @@ export {
getOwnProperty,
hasOwnProperty,
objectMapEntries,
objectMapFromEntries,
objectMapKeys,
objectMapValues,
} from './lib/object'

Wyświetl plik

@ -88,6 +88,18 @@ export function objectMapEntries<Key extends string, Value>(object: {
return Object.entries(object) as [Key, Value][]
}
/**
* An alias for `Object.fromEntries` that treats the object as a map and so preserves the type of the
* keys.
*
* @internal
*/
export function objectMapFromEntries<Key extends string, Value>(
entries: ReadonlyArray<readonly [Key, Value]>
): { [K in Key]: Value } {
return Object.fromEntries(entries) as { [K in Key]: Value }
}
/**
* Filters an object using a predicate function.
* @returns a new object with only the entries that pass the predicate