From a3bec040887cd41be486f708b4c29911385987da Mon Sep 17 00:00:00 2001 From: Aria Minaei Date: Fri, 3 Sep 2021 21:20:05 +0200 Subject: [PATCH] Added some railguards to the API --- theatre/core/src/coreExports.ts | 36 +++++++++++++++++-- .../core/src/sheetObjects/SheetObject.test.ts | 2 +- theatre/core/src/sheetObjects/SheetObject.ts | 12 ------- theatre/core/src/sheets/TheatreSheet.ts | 18 +++++++--- theatre/package.json | 3 ++ yarn.lock | 1 + 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/theatre/core/src/coreExports.ts b/theatre/core/src/coreExports.ts index 08ccd4d..a21f8e4 100644 --- a/theatre/core/src/coreExports.ts +++ b/theatre/core/src/coreExports.ts @@ -10,12 +10,44 @@ import * as types from '@theatre/core/propTypes' import {InvalidArgumentError} from '@theatre/shared/utils/errors' import {validateName} from '@theatre/shared/utils/sanitizers' import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' +import deepEqual from 'fast-deep-equal' export {types} +/** + * Returns a project of the given id, or creates one if it doesn't already exist. + * + * If @theatre/studio is also loaded, then the state of the project will be managed by the studio. + * + * Usage: + * ```ts + * import {getProject} from '@theatre/core' + * const config = {} // the config can be empty when starting a new project + * const project = getProject("a-unique-id", config) + * ``` + * + * Usage with an explicit state: + * ```ts + * import {getProject} from '@theatre/core' + * import state from './saved-state.json' + * const config = {state} // here the config contains our saved state + * const project = getProject("a-unique-id", config) + * ``` + */ export function getProject(id: string, config: IProjectConfig = {}): IProject { const {...restOfConfig} = config - if (projectsSingleton.has(id)) { - return projectsSingleton.get(id)!.publicApi + const existingProject = projectsSingleton.get(id) + if (existingProject) { + if (process.env.NODE_ENV !== 'production') { + if (!deepEqual(config, existingProject.config)) { + throw new Error( + `You seem to have called Theatre.getProject("${id}", config) twice, with different config objects. ` + + `This is disallowed because changing the config of a project on the fly can lead to hard-to-debug issues.\n\n` + + `You can fix this by either calling Theatre.getProject() once per project-id,` + + ` or calling it multiple times but with the exact same config.`, + ) + } + } + return existingProject.publicApi } if (process.env.NODE_ENV !== 'production') { diff --git a/theatre/core/src/sheetObjects/SheetObject.test.ts b/theatre/core/src/sheetObjects/SheetObject.test.ts index 162a4d2..a2b3f20 100644 --- a/theatre/core/src/sheetObjects/SheetObject.test.ts +++ b/theatre/core/src/sheetObjects/SheetObject.test.ts @@ -91,7 +91,7 @@ describe(`SheetObject`, () => { }, }) - const seq = sheet.publicApi.sequence() + const seq = sheet.publicApi.sequence const objValues = iterateOver( prism(() => { diff --git a/theatre/core/src/sheetObjects/SheetObject.ts b/theatre/core/src/sheetObjects/SheetObject.ts index b2ef69b..045d754 100644 --- a/theatre/core/src/sheetObjects/SheetObject.ts +++ b/theatre/core/src/sheetObjects/SheetObject.ts @@ -1,6 +1,5 @@ import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime' import type Sheet from '@theatre/core/sheets/Sheet' -import type {SheetObjectConfig} from '@theatre/core/sheets/TheatreSheet' import type {SheetObjectAddress} from '@theatre/shared/utils/addresses' import deepMergeWithCache from '@theatre/shared/utils/deepMergeWithCache' import type {SequenceTrackId} from '@theatre/shared/utils/ids' @@ -53,17 +52,6 @@ export default class SheetObject implements IdentityDerivationProvider { this.publicApi = new TheatreSheetObject(this) } - overrideConfig( - nativeObject: unknown, - config: SheetObjectConfig<$IntentionalAny>, - ) { - if (nativeObject !== this.nativeObject) { - // @todo - } - - this.template.overrideConfig(nativeObject, config) - } - getValues(): IDerivation> { return this._cache.get('getValues()', () => prism(() => { diff --git a/theatre/core/src/sheets/TheatreSheet.ts b/theatre/core/src/sheets/TheatreSheet.ts index 29ce91f..554a77d 100644 --- a/theatre/core/src/sheets/TheatreSheet.ts +++ b/theatre/core/src/sheets/TheatreSheet.ts @@ -10,6 +10,7 @@ import {InvalidArgumentError} from '@theatre/shared/utils/errors' import {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths' import type {$IntentionalAny} from '@theatre/shared/utils/types' import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' +import deepEqual from 'fast-deep-equal' export type SheetObjectConfig< Props extends PropTypeConfig_Compound<$IntentionalAny>, @@ -25,7 +26,7 @@ export interface ISheet { config: SheetObjectConfig, ): ISheetObject - sequence(): ISequence + readonly sequence: ISequence } export default class TheatreSheet implements ISheet { @@ -49,14 +50,21 @@ export default class TheatreSheet implements ISheet { `sheet.object("${key}", ...)`, ) - // @todo sanitize config - const existingObject = internal.getObject(sanitizedPath) const nativeObject = null if (existingObject) { - existingObject.overrideConfig(nativeObject, config) + if (process.env.NODE_ENV !== 'production') { + if (!deepEqual(config, existingObject.template.config)) { + throw new Error( + `You seem to have called sheet.object("${key}", config) twice, with different values for \`config\`. ` + + `This is disallowed because changing the config of an object on the fly would make it difficult to reason about.\n\n` + + `You can fix this by either re-using the existing object, or calling sheet.object("${key}", config) with the same config.`, + ) + } + } + return existingObject.publicApi as $IntentionalAny } else { const object = internal.createObject(sanitizedPath, nativeObject, config) @@ -64,7 +72,7 @@ export default class TheatreSheet implements ISheet { } } - sequence(): TheatreSequence { + get sequence(): TheatreSequence { return privateAPI(this).getSequence().publicApi } diff --git a/theatre/package.json b/theatre/package.json index 095eb35..508b6ed 100644 --- a/theatre/package.json +++ b/theatre/package.json @@ -87,5 +87,8 @@ "typescript": "^4.4.2", "url-loader": "^4.1.1", "uuid": "^8.3.2" + }, + "dependencies": { + "fast-deep-equal": "^3.1.3" } } diff --git a/yarn.lock b/yarn.lock index 6fa9d20..f433b4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22697,6 +22697,7 @@ fsevents@^1.2.7: esbuild-loader: ^2.13.1 esbuild-register: ^2.5.0 exec-loader: ^4.0.0 + fast-deep-equal: ^3.1.3 file-loader: ^6.2.0 fs-extra: ^10.0.0 html-loader: ^2.1.2