derived presence state (#1204)
This PR adds - A new `TLInstancePresence` record type, to collect info about the presence state in a particular instance of the editor. This will eventually be used to sync presence data instead of sending instance-only state across the wire. - **Record Scopes** `RecordType` now has a `scope` property which can be one of three things: - `document`: the record belongs to the document and should be synced and persisted freely. Currently: `TLDocument`, `TLPage`, `TLShape`, and `TLAsset` - `instance`: the record belongs to a single instance of the store and should not be synced at all. It should not be persisted directly in most cases, but rather compiled into a kind of 'instance configuration' to store alongside the local document data so that when reopening the associated document it can remember some of the previous instance state. Currently: `TLInstance`, `TLInstancePageState`, `TLCamera`, `TLUser`, `TLUserDocument`, `TLUserPresence` - `presence`: the record belongs to a single instance of the store and should not be persisted, but may be synced using the special presence sync protocol. Currently just `TLInstancePresence` This sets us up for the following changes, which are gonna be pretty high-impact in terms of integrating tldraw into existing systems: - Removing `instanceId` as a config option. Each instance gets a randomly generated ID. - We'd replace it with an `instanceConfig` option that has stuff like selectedIds, camera positions, and so on. Then it's up to library users to get and reinstate the instance config at persistence boundaries. - Removing `userId` as config option, and removing the `TLUser` type altogether. - We might need to revisit when doing auth-enabled features like locking shapes, but I suspect that will be separate.
This commit is contained in:
parent
da613ea6ef
commit
731da1bc77
40 changed files with 396 additions and 93 deletions
|
@ -49,11 +49,10 @@ export function generateSharedTasks(bublic: '<rootDir>' | '<rootDir>/bublic') {
|
||||||
cache: {
|
cache: {
|
||||||
inputs: {
|
inputs: {
|
||||||
include: [
|
include: [
|
||||||
'{.,./bublic}/packages/*/src/**/*.{ts,tsx}',
|
'{,bublic/}packages/*/src/**/*.{ts,tsx}',
|
||||||
'{.,./bublic}/{apps,scripts,e2e}/**/*.{ts,tsx}',
|
'{,bublic/}{apps,scripts,e2e}/**/*.{ts,tsx}',
|
||||||
'{.,./bublic}/{apps,packages}/*/tsconfig.json',
|
'{,bublic/}{apps,packages}/*/tsconfig.json',
|
||||||
'{.,./bublic}/{scripts,e2e}/tsconfig.json',
|
'{,bublic/}{scripts,e2e}/tsconfig.json',
|
||||||
`${bublic}/config/tsconfig.base.json`,
|
|
||||||
],
|
],
|
||||||
exclude: ['**/dist*/**/*.d.ts'],
|
exclude: ['**/dist*/**/*.d.ts'],
|
||||||
},
|
},
|
||||||
|
@ -95,7 +94,7 @@ export function generateSharedTasks(bublic: '<rootDir>' | '<rootDir>/bublic') {
|
||||||
baseCommand: `tsx ${bublic}/scripts/api-check.ts`,
|
baseCommand: `tsx ${bublic}/scripts/api-check.ts`,
|
||||||
runsAfter: { 'build:api': {} },
|
runsAfter: { 'build:api': {} },
|
||||||
cache: {
|
cache: {
|
||||||
inputs: ['**/api/bublic.d.ts'],
|
inputs: [`${bublic}/packages/*/api/public.d.ts`],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} satisfies LazyConfig['tasks']
|
} satisfies LazyConfig['tasks']
|
||||||
|
|
|
@ -87,8 +87,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "^7.34.1",
|
"@microsoft/api-extractor": "^7.34.1",
|
||||||
"@swc/core": "^1.3.41",
|
"@swc/core": "^1.3.55",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/jest": "^0.2.26",
|
||||||
"@types/glob": "^8.1.0",
|
"@types/glob": "^8.1.0",
|
||||||
"auto": "^10.44.0",
|
"auto": "^10.44.0",
|
||||||
"fs-extra": "^11.1.0",
|
"fs-extra": "^11.1.0",
|
||||||
|
|
|
@ -48,8 +48,6 @@
|
||||||
"@tldraw/utils": "workspace:*"
|
"@tldraw/utils": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "^1.2.204",
|
|
||||||
"@swc/jest": "^0.2.21",
|
|
||||||
"lazyrepo": "0.0.0-alpha.22",
|
"lazyrepo": "0.0.0-alpha.22",
|
||||||
"ts-node-dev": "^1.1.8"
|
"ts-node-dev": "^1.1.8"
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { SelectionCorner } from '@tldraw/primitives';
|
||||||
import { SelectionEdge } from '@tldraw/primitives';
|
import { SelectionEdge } from '@tldraw/primitives';
|
||||||
import { SelectionHandle } from '@tldraw/primitives';
|
import { SelectionHandle } from '@tldraw/primitives';
|
||||||
import { SerializedSchema } from '@tldraw/tlstore';
|
import { SerializedSchema } from '@tldraw/tlstore';
|
||||||
|
import { Signal } from 'signia';
|
||||||
import { StoreSchema } from '@tldraw/tlstore';
|
import { StoreSchema } from '@tldraw/tlstore';
|
||||||
import { StoreSnapshot } from '@tldraw/tlstore';
|
import { StoreSnapshot } from '@tldraw/tlstore';
|
||||||
import { StoreValidator } from '@tldraw/tlstore';
|
import { StoreValidator } from '@tldraw/tlstore';
|
||||||
|
@ -62,6 +63,7 @@ import { TLImageShape } from '@tldraw/tlschema';
|
||||||
import { TLInstance } from '@tldraw/tlschema';
|
import { TLInstance } from '@tldraw/tlschema';
|
||||||
import { TLInstanceId } from '@tldraw/tlschema';
|
import { TLInstanceId } from '@tldraw/tlschema';
|
||||||
import { TLInstancePageState } from '@tldraw/tlschema';
|
import { TLInstancePageState } from '@tldraw/tlschema';
|
||||||
|
import { TLInstancePresence } from '@tldraw/tlschema';
|
||||||
import { TLInstancePropsForNextShape } from '@tldraw/tlschema';
|
import { TLInstancePropsForNextShape } from '@tldraw/tlschema';
|
||||||
import { TLLineShape } from '@tldraw/tlschema';
|
import { TLLineShape } from '@tldraw/tlschema';
|
||||||
import { TLNoteShape } from '@tldraw/tlschema';
|
import { TLNoteShape } from '@tldraw/tlschema';
|
||||||
|
@ -1797,10 +1799,11 @@ export function TldrawEditor(props: TldrawEditorProps): JSX.Element;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export class TldrawEditorConfig {
|
export class TldrawEditorConfig {
|
||||||
constructor({ shapes, tools, allowUnknownShapes, }: {
|
constructor(args: {
|
||||||
shapes?: readonly TLShapeDef<any, any>[];
|
shapes?: readonly TLShapeDef<any, any>[];
|
||||||
tools?: readonly StateNodeConstructor[];
|
tools?: readonly StateNodeConstructor[];
|
||||||
allowUnknownShapes?: boolean;
|
allowUnknownShapes?: boolean;
|
||||||
|
derivePresenceState?: (store: TLStore) => Signal<null | TLInstancePresence>;
|
||||||
});
|
});
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
createStore(config: {
|
createStore(config: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require('fake-indexeddb/auto')
|
require('fake-indexeddb/auto')
|
||||||
global.ResizeObserver = require('resize-observer-polyfill')
|
global.ResizeObserver = require('resize-observer-polyfill')
|
||||||
global.crypto = new (require('@peculiar/webcrypto').Crypto)()
|
global.crypto ??= new (require('@peculiar/webcrypto').Crypto)()
|
||||||
global.FontFace = class FontFace {
|
global.FontFace = class FontFace {
|
||||||
load() {
|
load() {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
import {
|
import {
|
||||||
CLIENT_FIXUP_SCRIPT,
|
CLIENT_FIXUP_SCRIPT,
|
||||||
ensureStoreIsUsable,
|
|
||||||
onValidationFailure,
|
|
||||||
rootShapeTypeMigrations,
|
|
||||||
storeMigrations,
|
|
||||||
TLAsset,
|
TLAsset,
|
||||||
TLCamera,
|
TLCamera,
|
||||||
TLDocument,
|
|
||||||
TLDOCUMENT_ID,
|
TLDOCUMENT_ID,
|
||||||
|
TLDocument,
|
||||||
TLInstance,
|
TLInstance,
|
||||||
TLInstanceId,
|
TLInstanceId,
|
||||||
TLInstancePageState,
|
TLInstancePageState,
|
||||||
|
TLInstancePresence,
|
||||||
TLPage,
|
TLPage,
|
||||||
TLRecord,
|
TLRecord,
|
||||||
TLShape,
|
TLShape,
|
||||||
|
@ -20,16 +17,21 @@ import {
|
||||||
TLUserDocument,
|
TLUserDocument,
|
||||||
TLUserId,
|
TLUserId,
|
||||||
TLUserPresence,
|
TLUserPresence,
|
||||||
|
ensureStoreIsUsable,
|
||||||
|
onValidationFailure,
|
||||||
|
rootShapeTypeMigrations,
|
||||||
|
storeMigrations,
|
||||||
} from '@tldraw/tlschema'
|
} from '@tldraw/tlschema'
|
||||||
import {
|
import {
|
||||||
createRecordType,
|
|
||||||
defineMigrations,
|
|
||||||
RecordType,
|
RecordType,
|
||||||
Store,
|
Store,
|
||||||
StoreSchema,
|
StoreSchema,
|
||||||
StoreSnapshot,
|
StoreSnapshot,
|
||||||
|
createRecordType,
|
||||||
|
defineMigrations,
|
||||||
} from '@tldraw/tlstore'
|
} from '@tldraw/tlstore'
|
||||||
import { T } from '@tldraw/tlvalidate'
|
import { T } from '@tldraw/tlvalidate'
|
||||||
|
import { Signal } from 'signia'
|
||||||
import { TLArrowShapeDef } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
import { TLArrowShapeDef } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||||
import { TLBookmarkShapeDef } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
import { TLBookmarkShapeDef } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||||
import { TLDrawShapeDef } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
|
import { TLDrawShapeDef } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
|
||||||
|
@ -44,6 +46,7 @@ import { TLTextShapeDef } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
||||||
import { TLVideoShapeDef } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
import { TLVideoShapeDef } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||||
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
||||||
import { TLShapeDef, TLUnknownShapeDef } from './TLShapeDefinition'
|
import { TLShapeDef, TLUnknownShapeDef } from './TLShapeDefinition'
|
||||||
|
import { defaultDerivePresenceState } from './defaultDerivePresenceState'
|
||||||
|
|
||||||
const CORE_SHAPE_DEFS = () =>
|
const CORE_SHAPE_DEFS = () =>
|
||||||
[
|
[
|
||||||
|
@ -70,15 +73,19 @@ export class TldrawEditorConfig {
|
||||||
readonly TLShape: RecordType<TLShape, 'type' | 'props' | 'index' | 'parentId'>
|
readonly TLShape: RecordType<TLShape, 'type' | 'props' | 'index' | 'parentId'>
|
||||||
readonly tools: readonly StateNodeConstructor[]
|
readonly tools: readonly StateNodeConstructor[]
|
||||||
|
|
||||||
constructor({
|
constructor(args: {
|
||||||
shapes = [],
|
|
||||||
tools = [],
|
|
||||||
allowUnknownShapes = false,
|
|
||||||
}: {
|
|
||||||
shapes?: readonly TLShapeDef<any, any>[]
|
shapes?: readonly TLShapeDef<any, any>[]
|
||||||
tools?: readonly StateNodeConstructor[]
|
tools?: readonly StateNodeConstructor[]
|
||||||
allowUnknownShapes?: boolean
|
allowUnknownShapes?: boolean
|
||||||
|
/** @internal */
|
||||||
|
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||||
}) {
|
}) {
|
||||||
|
const {
|
||||||
|
shapes = [],
|
||||||
|
tools = [],
|
||||||
|
allowUnknownShapes = false,
|
||||||
|
derivePresenceState = defaultDerivePresenceState,
|
||||||
|
} = args
|
||||||
this.tools = tools
|
this.tools = tools
|
||||||
|
|
||||||
const allShapeDefs = [...CORE_SHAPE_DEFS(), ...shapes]
|
const allShapeDefs = [...CORE_SHAPE_DEFS(), ...shapes]
|
||||||
|
@ -110,6 +117,7 @@ export class TldrawEditorConfig {
|
||||||
const shapeRecord = createRecordType<TLShape>('shape', {
|
const shapeRecord = createRecordType<TLShape>('shape', {
|
||||||
migrations: shapeTypeMigrations,
|
migrations: shapeTypeMigrations,
|
||||||
validator: T.model('shape', shapeValidator),
|
validator: T.model('shape', shapeValidator),
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
|
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
|
||||||
this.TLShape = shapeRecord
|
this.TLShape = shapeRecord
|
||||||
|
|
||||||
|
@ -125,11 +133,13 @@ export class TldrawEditorConfig {
|
||||||
user: TLUser,
|
user: TLUser,
|
||||||
user_document: TLUserDocument,
|
user_document: TLUserDocument,
|
||||||
user_presence: TLUserPresence,
|
user_presence: TLUserPresence,
|
||||||
|
instance_presence: TLInstancePresence,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
snapshotMigrations: storeMigrations,
|
snapshotMigrations: storeMigrations,
|
||||||
onValidationFailure,
|
onValidationFailure,
|
||||||
ensureStoreIsUsable,
|
ensureStoreIsUsable,
|
||||||
|
derivePresenceState,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
56
packages/editor/src/lib/config/defaultDerivePresenceState.ts
Normal file
56
packages/editor/src/lib/config/defaultDerivePresenceState.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { TLInstancePresence, TLStore } from '@tldraw/tlschema'
|
||||||
|
import { Signal, computed } from 'signia'
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const defaultDerivePresenceState = (store: TLStore): Signal<TLInstancePresence | null> => {
|
||||||
|
const $instance = store.query.record('instance', () => ({
|
||||||
|
id: { eq: store.props.instanceId },
|
||||||
|
}))
|
||||||
|
const $user = store.query.record('user', () => ({ id: { eq: store.props.userId } }))
|
||||||
|
const $userPresence = store.query.record('user_presence', () => ({
|
||||||
|
userId: { eq: store.props.userId },
|
||||||
|
}))
|
||||||
|
const $pageState = store.query.record('instance_page_state', () => ({
|
||||||
|
instanceId: { eq: store.props.instanceId },
|
||||||
|
pageId: { eq: $instance.value?.currentPageId ?? ('' as any) },
|
||||||
|
}))
|
||||||
|
const $camera = store.query.record('camera', () => ({
|
||||||
|
id: { eq: $pageState.value?.cameraId ?? ('' as any) },
|
||||||
|
}))
|
||||||
|
return computed('instancePresence', () => {
|
||||||
|
const pageState = $pageState.value
|
||||||
|
const instance = $instance.value
|
||||||
|
const user = $user.value
|
||||||
|
const userPresence = $userPresence.value
|
||||||
|
const camera = $camera.value
|
||||||
|
if (!pageState || !instance || !user || !userPresence || !camera) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return TLInstancePresence.create({
|
||||||
|
id: TLInstancePresence.createCustomId(store.props.instanceId),
|
||||||
|
instanceId: store.props.instanceId,
|
||||||
|
selectedIds: pageState.selectedIds,
|
||||||
|
brush: instance.brush,
|
||||||
|
scribble: instance.scribble,
|
||||||
|
userId: store.props.userId,
|
||||||
|
userName: user.name,
|
||||||
|
followingUserId: instance.followingUserId,
|
||||||
|
camera: {
|
||||||
|
x: camera.x,
|
||||||
|
y: camera.y,
|
||||||
|
z: camera.z,
|
||||||
|
},
|
||||||
|
color: userPresence.color,
|
||||||
|
currentPageId: instance.currentPageId,
|
||||||
|
cursor: {
|
||||||
|
x: userPresence.cursor.x,
|
||||||
|
y: userPresence.cursor.y,
|
||||||
|
rotation: instance.cursor.rotation,
|
||||||
|
type: instance.cursor.type,
|
||||||
|
},
|
||||||
|
lastActivityTimestamp: userPresence.lastActivityTimestamp,
|
||||||
|
screenBounds: instance.screenBounds,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
require('fake-indexeddb/auto')
|
require('fake-indexeddb/auto')
|
||||||
require('jest-canvas-mock')
|
require('jest-canvas-mock')
|
||||||
global.ResizeObserver = require('resize-observer-polyfill')
|
global.ResizeObserver = require('resize-observer-polyfill')
|
||||||
global.crypto = new (require('@peculiar/webcrypto').Crypto)()
|
global.crypto ??= new (require('@peculiar/webcrypto').Crypto)()
|
||||||
global.FontFace = class FontFace {
|
global.FontFace = class FontFace {
|
||||||
load() {
|
load() {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
|
|
@ -1011,6 +1011,48 @@ export const TLInstancePageState: RecordType<TLInstancePageState, "cameraId" | "
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLInstancePageStateId = ID<TLInstancePageState>;
|
export type TLInstancePageStateId = ID<TLInstancePageState>;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export interface TLInstancePresence extends BaseRecord<'instance_presence'> {
|
||||||
|
// (undocumented)
|
||||||
|
brush: Box2dModel | null;
|
||||||
|
// (undocumented)
|
||||||
|
camera: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
|
// (undocumented)
|
||||||
|
color: string;
|
||||||
|
// (undocumented)
|
||||||
|
currentPageId: TLPageId;
|
||||||
|
// (undocumented)
|
||||||
|
cursor: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
type: TLCursor['type'];
|
||||||
|
rotation: number;
|
||||||
|
};
|
||||||
|
// (undocumented)
|
||||||
|
followingUserId: null | TLUserId;
|
||||||
|
// (undocumented)
|
||||||
|
instanceId: TLInstanceId;
|
||||||
|
// (undocumented)
|
||||||
|
lastActivityTimestamp: number;
|
||||||
|
// (undocumented)
|
||||||
|
screenBounds: Box2dModel;
|
||||||
|
// (undocumented)
|
||||||
|
scribble: null | TLScribble;
|
||||||
|
// (undocumented)
|
||||||
|
selectedIds: TLShapeId[];
|
||||||
|
// (undocumented)
|
||||||
|
userId: TLUserId;
|
||||||
|
// (undocumented)
|
||||||
|
userName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export const TLInstancePresence: RecordType<TLInstancePresence, "brush" | "camera" | "color" | "currentPageId" | "cursor" | "followingUserId" | "instanceId" | "lastActivityTimestamp" | "screenBounds" | "scribble" | "selectedIds" | "userId" | "userName">;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLInstancePropsForNextShape = Pick<TLShapeProps, TLStyleType>;
|
export type TLInstancePropsForNextShape = Pick<TLShapeProps, TLStyleType>;
|
||||||
|
|
||||||
|
@ -1078,7 +1120,7 @@ export type TLPageId = ID<TLPage>;
|
||||||
export type TLParentId = TLPageId | TLShapeId;
|
export type TLParentId = TLPageId | TLShapeId;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLRecord = TLAsset | TLCamera | TLDocument | TLInstance | TLInstancePageState | TLPage | TLShape | TLUser | TLUserDocument | TLUserPresence;
|
export type TLRecord = TLAsset | TLCamera | TLDocument | TLInstance | TLInstancePageState | TLInstancePresence | TLPage | TLShape | TLUser | TLUserDocument | TLUserPresence;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLScribble = {
|
export type TLScribble = {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { TLCamera } from './records/TLCamera'
|
||||||
import { TLDocument } from './records/TLDocument'
|
import { TLDocument } from './records/TLDocument'
|
||||||
import { TLInstance } from './records/TLInstance'
|
import { TLInstance } from './records/TLInstance'
|
||||||
import { TLInstancePageState } from './records/TLInstancePageState'
|
import { TLInstancePageState } from './records/TLInstancePageState'
|
||||||
|
import { TLInstancePresence } from './records/TLInstancePresence'
|
||||||
import { TLPage } from './records/TLPage'
|
import { TLPage } from './records/TLPage'
|
||||||
import { TLShape } from './records/TLShape'
|
import { TLShape } from './records/TLShape'
|
||||||
import { TLUser } from './records/TLUser'
|
import { TLUser } from './records/TLUser'
|
||||||
|
@ -21,3 +22,4 @@ export type TLRecord =
|
||||||
| TLUser
|
| TLUser
|
||||||
| TLUserDocument
|
| TLUserDocument
|
||||||
| TLUserPresence
|
| TLUserPresence
|
||||||
|
| TLInstancePresence
|
||||||
|
|
|
@ -59,6 +59,7 @@ export {
|
||||||
instancePageStateTypeValidator,
|
instancePageStateTypeValidator,
|
||||||
type TLInstancePageStateId,
|
type TLInstancePageStateId,
|
||||||
} from './records/TLInstancePageState'
|
} from './records/TLInstancePageState'
|
||||||
|
export { TLInstancePresence } from './records/TLInstancePresence'
|
||||||
export { TLPage, pageTypeMigrations, pageTypeValidator, type TLPageId } from './records/TLPage'
|
export { TLPage, pageTypeMigrations, pageTypeValidator, type TLPageId } from './records/TLPage'
|
||||||
export {
|
export {
|
||||||
createCustomShapeId,
|
createCustomShapeId,
|
||||||
|
|
|
@ -172,6 +172,7 @@ describe('TLImageAsset AddIsAnimated', () => {
|
||||||
|
|
||||||
const ShapeRecord = createRecordType('shape', {
|
const ShapeRecord = createRecordType('shape', {
|
||||||
validator: { validate: (record) => record as TLShape },
|
validator: { validate: (record) => record as TLShape },
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Store removing Icon and Code shapes', () => {
|
describe('Store removing Icon and Code shapes', () => {
|
||||||
|
@ -634,6 +635,24 @@ describe('Add crop=null to image shapes', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Adding instance_presence to the schema', () => {
|
||||||
|
const { up, down } = storeMigrations.migrators[2]
|
||||||
|
|
||||||
|
test('up works as expected', () => {
|
||||||
|
expect(up({})).toEqual({})
|
||||||
|
})
|
||||||
|
test('down works as expected', () => {
|
||||||
|
expect(
|
||||||
|
down({
|
||||||
|
'instance_presence:123': { id: 'instance_presence:123', typeName: 'instance_presence' },
|
||||||
|
'instance:123': { id: 'instance:123', typeName: 'instance' },
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
'instance:123': { id: 'instance:123', typeName: 'instance' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
||||||
|
|
||||||
for (const migrator of allMigrators) {
|
for (const migrator of allMigrators) {
|
||||||
|
|
|
@ -60,6 +60,7 @@ export type TLAssetPartial<T extends TLAsset = TLAsset> = T extends T
|
||||||
export const TLAsset = createRecordType<TLAsset>('asset', {
|
export const TLAsset = createRecordType<TLAsset>('asset', {
|
||||||
migrations: assetTypeMigrations,
|
migrations: assetTypeMigrations,
|
||||||
validator: assetTypeValidator,
|
validator: assetTypeValidator,
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
|
@ -49,6 +49,7 @@ export const cameraTypeMigrations = defineMigrations({
|
||||||
export const TLCamera = createRecordType<TLCamera>('camera', {
|
export const TLCamera = createRecordType<TLCamera>('camera', {
|
||||||
migrations: cameraTypeMigrations,
|
migrations: cameraTypeMigrations,
|
||||||
validator: cameraTypeValidator,
|
validator: cameraTypeValidator,
|
||||||
|
scope: 'instance',
|
||||||
}).withDefaultProperties(
|
}).withDefaultProperties(
|
||||||
(): Omit<TLCamera, 'id' | 'typeName'> => ({
|
(): Omit<TLCamera, 'id' | 'typeName'> => ({
|
||||||
x: 0,
|
x: 0,
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const documentTypeMigrations = defineMigrations({
|
||||||
export const TLDocument = createRecordType<TLDocument>('document', {
|
export const TLDocument = createRecordType<TLDocument>('document', {
|
||||||
migrations: documentTypeMigrations,
|
migrations: documentTypeMigrations,
|
||||||
validator: documentTypeValidator,
|
validator: documentTypeValidator,
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(
|
}).withDefaultProperties(
|
||||||
(): Omit<TLDocument, 'id' | 'typeName'> => ({
|
(): Omit<TLDocument, 'id' | 'typeName'> => ({
|
||||||
gridSize: 10,
|
gridSize: 10,
|
||||||
|
|
|
@ -213,6 +213,7 @@ export const instanceTypeMigrations = defineMigrations({
|
||||||
export const TLInstance = createRecordType<TLInstance>('instance', {
|
export const TLInstance = createRecordType<TLInstance>('instance', {
|
||||||
migrations: instanceTypeMigrations,
|
migrations: instanceTypeMigrations,
|
||||||
validator: instanceTypeValidator,
|
validator: instanceTypeValidator,
|
||||||
|
scope: 'instance',
|
||||||
}).withDefaultProperties(
|
}).withDefaultProperties(
|
||||||
(): Omit<TLInstance, 'typeName' | 'id' | 'userId' | 'currentPageId'> => ({
|
(): Omit<TLInstance, 'typeName' | 'id' | 'userId' | 'currentPageId'> => ({
|
||||||
followingUserId: null,
|
followingUserId: null,
|
||||||
|
|
|
@ -74,6 +74,7 @@ export const instancePageStateMigrations = defineMigrations({
|
||||||
export const TLInstancePageState = createRecordType<TLInstancePageState>('instance_page_state', {
|
export const TLInstancePageState = createRecordType<TLInstancePageState>('instance_page_state', {
|
||||||
migrations: instancePageStateMigrations,
|
migrations: instancePageStateMigrations,
|
||||||
validator: instancePageStateTypeValidator,
|
validator: instancePageStateTypeValidator,
|
||||||
|
scope: 'instance',
|
||||||
}).withDefaultProperties(
|
}).withDefaultProperties(
|
||||||
(): Omit<
|
(): Omit<
|
||||||
TLInstancePageState,
|
TLInstancePageState,
|
||||||
|
|
89
packages/tlschema/src/records/TLInstancePresence.ts
Normal file
89
packages/tlschema/src/records/TLInstancePresence.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { BaseRecord, createRecordType, defineMigrations, ID } from '@tldraw/tlstore'
|
||||||
|
import { T } from '@tldraw/tlvalidate'
|
||||||
|
import { Box2dModel } from '../geometry-types'
|
||||||
|
import { cursorTypeValidator, scribbleTypeValidator, TLCursor, TLScribble } from '../ui-types'
|
||||||
|
import { idValidator, userIdValidator } from '../validation'
|
||||||
|
import { TLInstanceId } from './TLInstance'
|
||||||
|
import { TLPageId } from './TLPage'
|
||||||
|
import { TLShapeId } from './TLShape'
|
||||||
|
import { TLUserId } from './TLUser'
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export interface TLInstancePresence extends BaseRecord<'instance_presence'> {
|
||||||
|
instanceId: TLInstanceId
|
||||||
|
userId: TLUserId
|
||||||
|
userName: string
|
||||||
|
lastActivityTimestamp: number
|
||||||
|
color: string // can be any hex color
|
||||||
|
camera: { x: number; y: number; z: number }
|
||||||
|
selectedIds: TLShapeId[]
|
||||||
|
currentPageId: TLPageId
|
||||||
|
brush: Box2dModel | null
|
||||||
|
scribble: TLScribble | null
|
||||||
|
screenBounds: Box2dModel
|
||||||
|
followingUserId: TLUserId | null
|
||||||
|
cursor: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
type: TLCursor['type']
|
||||||
|
rotation: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type TLInstancePresenceID = ID<TLInstancePresence>
|
||||||
|
|
||||||
|
// --- VALIDATION ---
|
||||||
|
/** @public */
|
||||||
|
export const instancePresenceTypeValidator: T.Validator<TLInstancePresence> = T.model(
|
||||||
|
'instance_presence',
|
||||||
|
T.object({
|
||||||
|
instanceId: idValidator<TLInstanceId>('instance'),
|
||||||
|
typeName: T.literal('instance_presence'),
|
||||||
|
id: idValidator<TLInstancePresenceID>('instance_presence'),
|
||||||
|
userId: userIdValidator,
|
||||||
|
userName: T.string,
|
||||||
|
lastActivityTimestamp: T.number,
|
||||||
|
followingUserId: userIdValidator.nullable(),
|
||||||
|
cursor: T.object({
|
||||||
|
x: T.number,
|
||||||
|
y: T.number,
|
||||||
|
type: cursorTypeValidator,
|
||||||
|
rotation: T.number,
|
||||||
|
}),
|
||||||
|
color: T.string,
|
||||||
|
camera: T.object({
|
||||||
|
x: T.number,
|
||||||
|
y: T.number,
|
||||||
|
z: T.number,
|
||||||
|
}),
|
||||||
|
screenBounds: T.boxModel,
|
||||||
|
selectedIds: T.arrayOf(idValidator<TLShapeId>('shape')),
|
||||||
|
currentPageId: idValidator<TLPageId>('page'),
|
||||||
|
brush: T.boxModel.nullable(),
|
||||||
|
scribble: scribbleTypeValidator.nullable(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- MIGRATIONS ---
|
||||||
|
// STEP 1: Add a new version number here, give it a meaningful name.
|
||||||
|
// It should be 1 higher than the current version
|
||||||
|
const Versions = {
|
||||||
|
Initial: 0,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const userPresenceTypeMigrations = defineMigrations({
|
||||||
|
// STEP 2: Update the current version to point to your latest version
|
||||||
|
currentVersion: Versions.Initial,
|
||||||
|
firstVersion: Versions.Initial,
|
||||||
|
migrators: {
|
||||||
|
// STEP 3: Add an up+down migration for the new version here
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export const TLInstancePresence = createRecordType<TLInstancePresence>('instance_presence', {
|
||||||
|
migrations: userPresenceTypeMigrations,
|
||||||
|
validator: instancePresenceTypeValidator,
|
||||||
|
scope: 'presence',
|
||||||
|
})
|
|
@ -47,4 +47,5 @@ export const pageTypeMigrations = defineMigrations({
|
||||||
export const TLPage = createRecordType<TLPage>('page', {
|
export const TLPage = createRecordType<TLPage>('page', {
|
||||||
migrations: pageTypeMigrations,
|
migrations: pageTypeMigrations,
|
||||||
validator: pageTypeValidator,
|
validator: pageTypeValidator,
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
|
@ -48,6 +48,7 @@ export const userTypeMigrations = defineMigrations({
|
||||||
export const TLUser = createRecordType<TLUser>('user', {
|
export const TLUser = createRecordType<TLUser>('user', {
|
||||||
migrations: userTypeMigrations,
|
migrations: userTypeMigrations,
|
||||||
validator: userTypeValidator,
|
validator: userTypeValidator,
|
||||||
|
scope: 'instance',
|
||||||
}).withDefaultProperties((): Omit<TLUser, 'id' | 'typeName'> => {
|
}).withDefaultProperties((): Omit<TLUser, 'id' | 'typeName'> => {
|
||||||
let lang
|
let lang
|
||||||
if (typeof window !== 'undefined' && window.navigator) {
|
if (typeof window !== 'undefined' && window.navigator) {
|
||||||
|
|
|
@ -87,6 +87,7 @@ export const userDocumentTypeMigrations = defineMigrations({
|
||||||
export const TLUserDocument = createRecordType<TLUserDocument>('user_document', {
|
export const TLUserDocument = createRecordType<TLUserDocument>('user_document', {
|
||||||
migrations: userDocumentTypeMigrations,
|
migrations: userDocumentTypeMigrations,
|
||||||
validator: userDocumentTypeValidator,
|
validator: userDocumentTypeValidator,
|
||||||
|
scope: 'instance',
|
||||||
}).withDefaultProperties(
|
}).withDefaultProperties(
|
||||||
(): Omit<TLUserDocument, 'id' | 'typeName' | 'userId'> => ({
|
(): Omit<TLUserDocument, 'id' | 'typeName' | 'userId'> => ({
|
||||||
/* STEP 6: Add any new default values for properties here */
|
/* STEP 6: Add any new default values for properties here */
|
||||||
|
|
|
@ -65,6 +65,7 @@ export const userPresenceTypeMigrations = defineMigrations({
|
||||||
export const TLUserPresence = createRecordType<TLUserPresence>('user_presence', {
|
export const TLUserPresence = createRecordType<TLUserPresence>('user_presence', {
|
||||||
migrations: userPresenceTypeMigrations,
|
migrations: userPresenceTypeMigrations,
|
||||||
validator: userPresenceTypeValidator,
|
validator: userPresenceTypeValidator,
|
||||||
|
scope: 'instance',
|
||||||
}).withDefaultProperties(
|
}).withDefaultProperties(
|
||||||
(): Omit<TLUserPresence, 'id' | 'typeName' | 'userId'> => ({
|
(): Omit<TLUserPresence, 'id' | 'typeName' | 'userId'> => ({
|
||||||
lastUsedInstanceId: null,
|
lastUsedInstanceId: null,
|
||||||
|
|
|
@ -7,13 +7,14 @@ import { TLRecord } from './TLRecord'
|
||||||
const Versions = {
|
const Versions = {
|
||||||
Initial: 0,
|
Initial: 0,
|
||||||
RemoveCodeAndIconShapeTypes: 1,
|
RemoveCodeAndIconShapeTypes: 1,
|
||||||
|
AddInstancePresenceType: 2,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const storeMigrations = defineMigrations({
|
export const storeMigrations = defineMigrations({
|
||||||
// STEP 2: Update the current version to point to your latest version
|
// STEP 2: Update the current version to point to your latest version
|
||||||
firstVersion: Versions.Initial,
|
firstVersion: Versions.Initial,
|
||||||
currentVersion: Versions.RemoveCodeAndIconShapeTypes,
|
currentVersion: Versions.AddInstancePresenceType,
|
||||||
migrators: {
|
migrators: {
|
||||||
// STEP 3: Add an up+down migration for the new version here
|
// STEP 3: Add an up+down migration for the new version here
|
||||||
[Versions.RemoveCodeAndIconShapeTypes]: {
|
[Versions.RemoveCodeAndIconShapeTypes]: {
|
||||||
|
@ -29,5 +30,15 @@ export const storeMigrations = defineMigrations({
|
||||||
return store
|
return store
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[Versions.AddInstancePresenceType]: {
|
||||||
|
up: (store: StoreSnapshot<TLRecord>) => {
|
||||||
|
return store
|
||||||
|
},
|
||||||
|
down: (store: StoreSnapshot<TLRecord>) => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(store).filter(([_, v]) => v.typeName !== 'instance_presence')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { Atom } from 'signia';
|
import { Atom } from 'signia';
|
||||||
import { Computed } from 'signia';
|
import { Computed } from 'signia';
|
||||||
|
import { Signal } from 'signia';
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export type AllRecords<T extends Store<any>> = ExtractR<ExtractRecordType<T>>;
|
export type AllRecords<T extends Store<any>> = ExtractR<ExtractRecordType<T>>;
|
||||||
|
@ -31,7 +32,7 @@ export type CollectionDiff<T> = {
|
||||||
export function compareRecordVersions(a: RecordVersion, b: RecordVersion): -1 | 0 | 1;
|
export function compareRecordVersions(a: RecordVersion, b: RecordVersion): -1 | 0 | 1;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => number;
|
export const compareSchemas: (a: SerializedSchema, b: SerializedSchema) => -1 | 0 | 1;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export type ComputedCache<Data, R extends BaseRecord> = {
|
export type ComputedCache<Data, R extends BaseRecord> = {
|
||||||
|
@ -42,6 +43,7 @@ export type ComputedCache<Data, R extends BaseRecord> = {
|
||||||
export function createRecordType<R extends BaseRecord>(typeName: R['typeName'], config: {
|
export function createRecordType<R extends BaseRecord>(typeName: R['typeName'], config: {
|
||||||
migrations?: Migrations;
|
migrations?: Migrations;
|
||||||
validator: StoreValidator<R>;
|
validator: StoreValidator<R>;
|
||||||
|
scope: Scope;
|
||||||
}): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>;
|
}): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -158,6 +160,7 @@ export class RecordType<R extends BaseRecord, RequiredProperties extends keyof O
|
||||||
readonly validator?: {
|
readonly validator?: {
|
||||||
validate: (r: unknown) => R;
|
validate: (r: unknown) => R;
|
||||||
} | StoreValidator<R>;
|
} | StoreValidator<R>;
|
||||||
|
readonly scope?: Scope;
|
||||||
});
|
});
|
||||||
clone(record: R): R;
|
clone(record: R): R;
|
||||||
create(properties: Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>): R;
|
create(properties: Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>): R;
|
||||||
|
@ -170,6 +173,8 @@ export class RecordType<R extends BaseRecord, RequiredProperties extends keyof O
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
readonly migrations: Migrations;
|
readonly migrations: Migrations;
|
||||||
parseId(id: string): ID<R>;
|
parseId(id: string): ID<R>;
|
||||||
|
// (undocumented)
|
||||||
|
readonly scope: Scope;
|
||||||
readonly typeName: R['typeName'];
|
readonly typeName: R['typeName'];
|
||||||
validate(record: unknown): R;
|
validate(record: unknown): R;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -274,6 +279,8 @@ export class StoreSchema<R extends BaseRecord, P = unknown> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
get currentStoreVersion(): number;
|
get currentStoreVersion(): number;
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
|
derivePresenceState(store: Store<R, P>): Signal<null | R> | undefined;
|
||||||
|
// @internal (undocumented)
|
||||||
ensureStoreIsUsable(store: Store<R, P>): void;
|
ensureStoreIsUsable(store: Store<R, P>): void;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>;
|
migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>;
|
||||||
|
@ -302,6 +309,7 @@ export type StoreSchemaOptions<R extends BaseRecord, P> = {
|
||||||
recordBefore: null | R;
|
recordBefore: null | R;
|
||||||
}) => R;
|
}) => R;
|
||||||
ensureStoreIsUsable?: (store: Store<R, P>) => void;
|
ensureStoreIsUsable?: (store: Store<R, P>) => void;
|
||||||
|
derivePresenceState?: (store: Store<R, P>) => Signal<null | R>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
|
|
|
@ -6,6 +6,17 @@ import { Migrations } from './migrate'
|
||||||
|
|
||||||
export type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>
|
export type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the scope of the record
|
||||||
|
*
|
||||||
|
* instance: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.
|
||||||
|
* document: The record is persisted and synced. It is available to all store instances.
|
||||||
|
* presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* */
|
||||||
|
export type Scope = 'instance' | 'document' | 'presence'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A record type is a type that can be stored in a record store. It is created with
|
* A record type is a type that can be stored in a record store. It is created with
|
||||||
* `createRecordType`.
|
* `createRecordType`.
|
||||||
|
@ -20,6 +31,8 @@ export class RecordType<
|
||||||
readonly migrations: Migrations
|
readonly migrations: Migrations
|
||||||
readonly validator: StoreValidator<R> | { validate: (r: unknown) => R }
|
readonly validator: StoreValidator<R> | { validate: (r: unknown) => R }
|
||||||
|
|
||||||
|
readonly scope: Scope
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
/**
|
||||||
* The unique type associated with this record.
|
* The unique type associated with this record.
|
||||||
|
@ -32,11 +45,13 @@ export class RecordType<
|
||||||
readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>
|
readonly createDefaultProperties: () => Exclude<OmitMeta<R>, RequiredProperties>
|
||||||
readonly migrations: Migrations
|
readonly migrations: Migrations
|
||||||
readonly validator?: StoreValidator<R> | { validate: (r: unknown) => R }
|
readonly validator?: StoreValidator<R> | { validate: (r: unknown) => R }
|
||||||
|
readonly scope?: Scope
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
this.createDefaultProperties = config.createDefaultProperties
|
this.createDefaultProperties = config.createDefaultProperties
|
||||||
this.migrations = config.migrations
|
this.migrations = config.migrations
|
||||||
this.validator = config.validator ?? { validate: (r: unknown) => r as R }
|
this.validator = config.validator ?? { validate: (r: unknown) => r as R }
|
||||||
|
this.scope = config.scope ?? 'document'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,6 +189,7 @@ export class RecordType<
|
||||||
createDefaultProperties: createDefaultProperties as any,
|
createDefaultProperties: createDefaultProperties as any,
|
||||||
migrations: this.migrations,
|
migrations: this.migrations,
|
||||||
validator: this.validator,
|
validator: this.validator,
|
||||||
|
scope: this.scope,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,12 +220,14 @@ export function createRecordType<R extends BaseRecord>(
|
||||||
migrations?: Migrations
|
migrations?: Migrations
|
||||||
// todo: optional validations
|
// todo: optional validations
|
||||||
validator: StoreValidator<R>
|
validator: StoreValidator<R>
|
||||||
|
scope: Scope
|
||||||
}
|
}
|
||||||
): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {
|
): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {
|
||||||
return new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {
|
return new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {
|
||||||
createDefaultProperties: () => ({} as any),
|
createDefaultProperties: () => ({} as any),
|
||||||
migrations: config.migrations ?? { currentVersion: 0, firstVersion: 0, migrators: {} },
|
migrations: config.migrations ?? { currentVersion: 0, firstVersion: 0, migrators: {} },
|
||||||
validator: config.validator,
|
validator: config.validator,
|
||||||
|
scope: config.scope,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -766,15 +766,4 @@ class HistoryAccumulator<T extends BaseRecord> {
|
||||||
hasChanges() {
|
hasChanges() {
|
||||||
return this._history.length > 0
|
return this._history.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that the store is usable. A class that extends this store should override this method.
|
|
||||||
*
|
|
||||||
* @param config - The configuration object. This can be any object that allows the store to
|
|
||||||
* validate that it is usable; the extending class should specify the type.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
ensureStoreIsUsable(_config = {} as any): void {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { getOwnProperty, objectMapValues } from '@tldraw/utils'
|
import { getOwnProperty, objectMapValues } from '@tldraw/utils'
|
||||||
|
import { Signal } from 'signia'
|
||||||
import { BaseRecord } from './BaseRecord'
|
import { BaseRecord } from './BaseRecord'
|
||||||
import { RecordType } from './RecordType'
|
import { RecordType } from './RecordType'
|
||||||
import { Store, StoreSnapshot } from './Store'
|
import { Store, StoreSnapshot } from './Store'
|
||||||
|
@ -48,6 +49,8 @@ export type StoreSchemaOptions<R extends BaseRecord, P> = {
|
||||||
}) => R
|
}) => R
|
||||||
/** @internal */
|
/** @internal */
|
||||||
ensureStoreIsUsable?: (store: Store<R, P>) => void
|
ensureStoreIsUsable?: (store: Store<R, P>) => void
|
||||||
|
/** @internal */
|
||||||
|
derivePresenceState?: (store: Store<R, P>) => Signal<R | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -241,6 +244,11 @@ export class StoreSchema<R extends BaseRecord, P = unknown> {
|
||||||
this.options.ensureStoreIsUsable?.(store)
|
this.options.ensureStoreIsUsable?.(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
derivePresenceState(store: Store<R, P>): Signal<R | null> | undefined {
|
||||||
|
return this.options.derivePresenceState?.(store)
|
||||||
|
}
|
||||||
|
|
||||||
serialize(): SerializedSchema {
|
serialize(): SerializedSchema {
|
||||||
return {
|
return {
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { SerializedSchema } from './StoreSchema'
|
import { SerializedSchema } from './StoreSchema'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const compareSchemas = (a: SerializedSchema, b: SerializedSchema): number => {
|
export const compareSchemas = (a: SerializedSchema, b: SerializedSchema): 0 | 1 | -1 => {
|
||||||
if (a.schemaVersion > b.schemaVersion) {
|
if (a.schemaVersion > b.schemaVersion) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,10 @@ interface Book extends BaseRecord<'book'> {
|
||||||
numPages: number
|
numPages: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const Book = createRecordType<Book>('book', { validator: { validate: (book) => book as Book } })
|
const Book = createRecordType<Book>('book', {
|
||||||
|
validator: { validate: (book) => book as Book },
|
||||||
|
scope: 'document',
|
||||||
|
})
|
||||||
|
|
||||||
interface Author extends BaseRecord<'author'> {
|
interface Author extends BaseRecord<'author'> {
|
||||||
name: string
|
name: string
|
||||||
|
@ -19,6 +22,7 @@ interface Author extends BaseRecord<'author'> {
|
||||||
|
|
||||||
const Author = createRecordType<Author>('author', {
|
const Author = createRecordType<Author>('author', {
|
||||||
validator: { validate: (author) => author as Author },
|
validator: { validate: (author) => author as Author },
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(() => ({
|
}).withDefaultProperties(() => ({
|
||||||
isPseudonym: false,
|
isPseudonym: false,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -21,6 +21,7 @@ const Author = createRecordType<Author>('author', {
|
||||||
return author
|
return author
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(() => ({ age: 23 }))
|
}).withDefaultProperties(() => ({ age: 23 }))
|
||||||
|
|
||||||
interface Book extends BaseRecord<'book'> {
|
interface Book extends BaseRecord<'book'> {
|
||||||
|
@ -38,6 +39,7 @@ const Book = createRecordType<Book>('book', {
|
||||||
return book
|
return book
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
||||||
const bookComparator = (a: Book, b: Book) => a.id.localeCompare(b.id)
|
const bookComparator = (a: Book, b: Book) => a.id.localeCompare(b.id)
|
||||||
|
|
|
@ -19,6 +19,7 @@ const Author = createRecordType<Author>('author', {
|
||||||
return author
|
return author
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(() => ({ age: 23 }))
|
}).withDefaultProperties(() => ({ age: 23 }))
|
||||||
|
|
||||||
interface Book extends BaseRecord<'book'> {
|
interface Book extends BaseRecord<'book'> {
|
||||||
|
@ -36,6 +37,7 @@ const Book = createRecordType<Book>('book', {
|
||||||
return book
|
return book
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
const authors = {
|
const authors = {
|
||||||
tolkein: Author.create({ name: 'J.R.R. Tolkein' }),
|
tolkein: Author.create({ name: 'J.R.R. Tolkein' }),
|
||||||
|
|
|
@ -29,6 +29,7 @@ const User = createRecordType<User>('user', {
|
||||||
return record as User
|
return record as User
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
||||||
const ShapeVersion = {
|
const ShapeVersion = {
|
||||||
|
@ -90,6 +91,7 @@ const Shape = createRecordType<Shape<RectangleProps | OvalProps>>('shape', {
|
||||||
return record as Shape<RectangleProps | OvalProps>
|
return record as Shape<RectangleProps | OvalProps>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
||||||
// this interface only exists to be removed
|
// this interface only exists to be removed
|
||||||
|
@ -107,6 +109,7 @@ const Org = createRecordType<Org>('org', {
|
||||||
return record as Org
|
return record as Org
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const testSchemaV0 = StoreSchema.create(
|
export const testSchemaV0 = StoreSchema.create(
|
||||||
|
|
|
@ -62,6 +62,7 @@ const User = createRecordType<User>('user', {
|
||||||
return record as User
|
return record as User
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(() => ({
|
}).withDefaultProperties(() => ({
|
||||||
/* STEP 6: Add any new default values for properties here */
|
/* STEP 6: Add any new default values for properties here */
|
||||||
name: 'New User',
|
name: 'New User',
|
||||||
|
@ -192,6 +193,7 @@ const Shape = createRecordType<Shape<RectangleProps | OvalProps>>('shape', {
|
||||||
return record as Shape<RectangleProps | OvalProps>
|
return record as Shape<RectangleProps | OvalProps>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(() => ({
|
}).withDefaultProperties(() => ({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
|
|
@ -21,6 +21,7 @@ const Book = createRecordType<Book>('book', {
|
||||||
return book
|
return book
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
})
|
})
|
||||||
|
|
||||||
interface Author extends BaseRecord<'author'> {
|
interface Author extends BaseRecord<'author'> {
|
||||||
|
@ -39,6 +40,7 @@ const Author = createRecordType<Author>('author', {
|
||||||
return author
|
return author
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scope: 'document',
|
||||||
}).withDefaultProperties(() => ({
|
}).withDefaultProperties(() => ({
|
||||||
isPseudonym: false,
|
isPseudonym: false,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
require('fake-indexeddb/auto')
|
require('fake-indexeddb/auto')
|
||||||
global.ResizeObserver = require('resize-observer-polyfill')
|
global.ResizeObserver = require('resize-observer-polyfill')
|
||||||
global.crypto = new (require('@peculiar/webcrypto').Crypto)()
|
global.crypto ??= new (require('@peculiar/webcrypto').Crypto)()
|
||||||
|
|
|
@ -37,6 +37,13 @@ export type ErrorResult<E> = {
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export function exhaustiveSwitchError(value: never, property?: string): never;
|
export function exhaustiveSwitchError(value: never, property?: string): never;
|
||||||
|
|
||||||
|
// @internal
|
||||||
|
export function filterEntries<Key extends string, Value>(object: {
|
||||||
|
[K in Key]: Value;
|
||||||
|
}, predicate: (key: Key, value: Value) => boolean): {
|
||||||
|
[K in Key]: Value;
|
||||||
|
};
|
||||||
|
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export function getErrorAnnotations(error: Error): ErrorAnnotations;
|
export function getErrorAnnotations(error: Error): ErrorAnnotations;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ export { getFirstFromIterable } from './lib/iterable'
|
||||||
export { lerp, modulate, rng } from './lib/number'
|
export { lerp, modulate, rng } from './lib/number'
|
||||||
export {
|
export {
|
||||||
deepCopy,
|
deepCopy,
|
||||||
|
filterEntries,
|
||||||
getOwnProperty,
|
getOwnProperty,
|
||||||
hasOwnProperty,
|
hasOwnProperty,
|
||||||
objectMapEntries,
|
objectMapEntries,
|
||||||
|
|
|
@ -87,3 +87,24 @@ export function objectMapEntries<Key extends string, Value>(object: {
|
||||||
}): Array<[Key, Value]> {
|
}): Array<[Key, Value]> {
|
||||||
return Object.entries(object) as [Key, Value][]
|
return Object.entries(object) as [Key, Value][]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters an object using a predicate function.
|
||||||
|
* @returns a new object with only the entries that pass the predicate
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function filterEntries<Key extends string, Value>(
|
||||||
|
object: { [K in Key]: Value },
|
||||||
|
predicate: (key: Key, value: Value) => boolean
|
||||||
|
): { [K in Key]: Value } {
|
||||||
|
const result: { [K in Key]?: Value } = {}
|
||||||
|
let didChange = false
|
||||||
|
for (const [key, value] of objectMapEntries(object)) {
|
||||||
|
if (predicate(key, value)) {
|
||||||
|
result[key] = value
|
||||||
|
} else {
|
||||||
|
didChange = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return didChange ? (result as { [K in Key]: Value }) : object
|
||||||
|
}
|
||||||
|
|
|
@ -4049,90 +4049,90 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-darwin-arm64@npm:1.3.52":
|
"@swc/core-darwin-arm64@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-darwin-arm64@npm:1.3.52"
|
resolution: "@swc/core-darwin-arm64@npm:1.3.55"
|
||||||
conditions: os=darwin & cpu=arm64
|
conditions: os=darwin & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-darwin-x64@npm:1.3.52":
|
"@swc/core-darwin-x64@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-darwin-x64@npm:1.3.52"
|
resolution: "@swc/core-darwin-x64@npm:1.3.55"
|
||||||
conditions: os=darwin & cpu=x64
|
conditions: os=darwin & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-linux-arm-gnueabihf@npm:1.3.52":
|
"@swc/core-linux-arm-gnueabihf@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.52"
|
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.55"
|
||||||
conditions: os=linux & cpu=arm
|
conditions: os=linux & cpu=arm
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-linux-arm64-gnu@npm:1.3.52":
|
"@swc/core-linux-arm64-gnu@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.52"
|
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.55"
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-linux-arm64-musl@npm:1.3.52":
|
"@swc/core-linux-arm64-musl@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-linux-arm64-musl@npm:1.3.52"
|
resolution: "@swc/core-linux-arm64-musl@npm:1.3.55"
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-linux-x64-gnu@npm:1.3.52":
|
"@swc/core-linux-x64-gnu@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-linux-x64-gnu@npm:1.3.52"
|
resolution: "@swc/core-linux-x64-gnu@npm:1.3.55"
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-linux-x64-musl@npm:1.3.52":
|
"@swc/core-linux-x64-musl@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-linux-x64-musl@npm:1.3.52"
|
resolution: "@swc/core-linux-x64-musl@npm:1.3.55"
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-win32-arm64-msvc@npm:1.3.52":
|
"@swc/core-win32-arm64-msvc@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.52"
|
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.55"
|
||||||
conditions: os=win32 & cpu=arm64
|
conditions: os=win32 & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-win32-ia32-msvc@npm:1.3.52":
|
"@swc/core-win32-ia32-msvc@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.52"
|
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.55"
|
||||||
conditions: os=win32 & cpu=ia32
|
conditions: os=win32 & cpu=ia32
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core-win32-x64-msvc@npm:1.3.52":
|
"@swc/core-win32-x64-msvc@npm:1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core-win32-x64-msvc@npm:1.3.52"
|
resolution: "@swc/core-win32-x64-msvc@npm:1.3.55"
|
||||||
conditions: os=win32 & cpu=x64
|
conditions: os=win32 & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/core@npm:^1.2.204, @swc/core@npm:^1.3.41":
|
"@swc/core@npm:^1.3.55":
|
||||||
version: 1.3.52
|
version: 1.3.55
|
||||||
resolution: "@swc/core@npm:1.3.52"
|
resolution: "@swc/core@npm:1.3.55"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@swc/core-darwin-arm64": 1.3.52
|
"@swc/core-darwin-arm64": 1.3.55
|
||||||
"@swc/core-darwin-x64": 1.3.52
|
"@swc/core-darwin-x64": 1.3.55
|
||||||
"@swc/core-linux-arm-gnueabihf": 1.3.52
|
"@swc/core-linux-arm-gnueabihf": 1.3.55
|
||||||
"@swc/core-linux-arm64-gnu": 1.3.52
|
"@swc/core-linux-arm64-gnu": 1.3.55
|
||||||
"@swc/core-linux-arm64-musl": 1.3.52
|
"@swc/core-linux-arm64-musl": 1.3.55
|
||||||
"@swc/core-linux-x64-gnu": 1.3.52
|
"@swc/core-linux-x64-gnu": 1.3.55
|
||||||
"@swc/core-linux-x64-musl": 1.3.52
|
"@swc/core-linux-x64-musl": 1.3.55
|
||||||
"@swc/core-win32-arm64-msvc": 1.3.52
|
"@swc/core-win32-arm64-msvc": 1.3.55
|
||||||
"@swc/core-win32-ia32-msvc": 1.3.52
|
"@swc/core-win32-ia32-msvc": 1.3.55
|
||||||
"@swc/core-win32-x64-msvc": 1.3.52
|
"@swc/core-win32-x64-msvc": 1.3.55
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@swc/helpers": ^0.5.0
|
"@swc/helpers": ^0.5.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
|
@ -4159,7 +4159,7 @@ __metadata:
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@swc/helpers":
|
"@swc/helpers":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: ae92657347b223ddbcc47d995966517356bbd600f775fcd74805c95eb8b10e80d0db1def315c710675fa40cae3c89cf26c413bccf1ea884066ba435f65425864
|
checksum: e8ae32e21e78761597b802bd76bb5f0d819441454c4cc5624c077dfa8cf84760eb589e4a1eb6fdc1b1ec65a4b03f7ab42413952b38234f5c13b8b2afb4d453f4
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -4172,7 +4172,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@swc/jest@npm:^0.2.21, @swc/jest@npm:^0.2.24":
|
"@swc/jest@npm:^0.2.26":
|
||||||
version: 0.2.26
|
version: 0.2.26
|
||||||
resolution: "@swc/jest@npm:0.2.26"
|
resolution: "@swc/jest@npm:0.2.26"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4274,8 +4274,6 @@ __metadata:
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@tldraw/assets@workspace:packages/assets"
|
resolution: "@tldraw/assets@workspace:packages/assets"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@swc/core": ^1.2.204
|
|
||||||
"@swc/jest": ^0.2.21
|
|
||||||
"@tldraw/utils": "workspace:*"
|
"@tldraw/utils": "workspace:*"
|
||||||
lazyrepo: 0.0.0-alpha.22
|
lazyrepo: 0.0.0-alpha.22
|
||||||
ts-node-dev: ^1.1.8
|
ts-node-dev: ^1.1.8
|
||||||
|
@ -4382,8 +4380,8 @@ __metadata:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@microsoft/api-extractor": ^7.34.1
|
"@microsoft/api-extractor": ^7.34.1
|
||||||
"@next/eslint-plugin-next": ^13.3.0
|
"@next/eslint-plugin-next": ^13.3.0
|
||||||
"@swc/core": ^1.3.41
|
"@swc/core": ^1.3.55
|
||||||
"@swc/jest": ^0.2.24
|
"@swc/jest": ^0.2.26
|
||||||
"@types/glob": ^8.1.0
|
"@types/glob": ^8.1.0
|
||||||
"@types/jest": ^28.1.2
|
"@types/jest": ^28.1.2
|
||||||
"@types/node": 18.7.3
|
"@types/node": 18.7.3
|
||||||
|
|
|
@ -55,9 +55,8 @@ async function main() {
|
||||||
console.log('Checking with tsconfig:', tsconfig)
|
console.log('Checking with tsconfig:', tsconfig)
|
||||||
writeFileSync(`${tempDir}/tsconfig.json`, JSON.stringify(tsconfig, null, '\t'), 'utf8')
|
writeFileSync(`${tempDir}/tsconfig.json`, JSON.stringify(tsconfig, null, '\t'), 'utf8')
|
||||||
writeFileSync(`${tempDir}/package.json`, JSON.stringify({ dependencies: {} }, null, '\t'), 'utf8')
|
writeFileSync(`${tempDir}/package.json`, JSON.stringify({ dependencies: {} }, null, '\t'), 'utf8')
|
||||||
writeFileSync(`${tempDir}/.yarnrc.yml`, 'nodeLinker: node-modules\n', 'utf8')
|
|
||||||
|
|
||||||
await exec('yarn', ['add', ...packagesOurTypesCanDependOn], { pwd: tempDir })
|
await exec('npm', ['install', ...packagesOurTypesCanDependOn], { pwd: tempDir })
|
||||||
await exec(resolve('./node_modules/.bin/tsc'), [], { pwd: tempDir })
|
await exec(resolve('./node_modules/.bin/tsc'), [], { pwd: tempDir })
|
||||||
|
|
||||||
await exec('rm', ['-rf', tempDir])
|
await exec('rm', ['-rf', tempDir])
|
||||||
|
|
Loading…
Reference in a new issue