kopia lustrzana https://github.com/Tldraw/Tldraw
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
rodzic
eb26964130
commit
0375b5d86d
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
})
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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)
|
||||
|
||||
```
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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`)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }>>>
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -20,6 +20,7 @@ export {
|
|||
getOwnProperty,
|
||||
hasOwnProperty,
|
||||
objectMapEntries,
|
||||
objectMapFromEntries,
|
||||
objectMapKeys,
|
||||
objectMapValues,
|
||||
} from './lib/object'
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue