Added some railguards to the API

This commit is contained in:
Aria Minaei 2021-09-03 21:20:05 +02:00
parent 9955730876
commit a3bec04088
6 changed files with 52 additions and 20 deletions

View file

@ -10,12 +10,44 @@ import * as types from '@theatre/core/propTypes'
import {InvalidArgumentError} from '@theatre/shared/utils/errors' import {InvalidArgumentError} from '@theatre/shared/utils/errors'
import {validateName} from '@theatre/shared/utils/sanitizers' import {validateName} from '@theatre/shared/utils/sanitizers'
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue'
import deepEqual from 'fast-deep-equal'
export {types} 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 { export function getProject(id: string, config: IProjectConfig = {}): IProject {
const {...restOfConfig} = config const {...restOfConfig} = config
if (projectsSingleton.has(id)) { const existingProject = projectsSingleton.get(id)
return projectsSingleton.get(id)!.publicApi 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') { if (process.env.NODE_ENV !== 'production') {

View file

@ -91,7 +91,7 @@ describe(`SheetObject`, () => {
}, },
}) })
const seq = sheet.publicApi.sequence() const seq = sheet.publicApi.sequence
const objValues = iterateOver( const objValues = iterateOver(
prism(() => { prism(() => {

View file

@ -1,6 +1,5 @@
import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime' import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime'
import type Sheet from '@theatre/core/sheets/Sheet' 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 type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
import deepMergeWithCache from '@theatre/shared/utils/deepMergeWithCache' import deepMergeWithCache from '@theatre/shared/utils/deepMergeWithCache'
import type {SequenceTrackId} from '@theatre/shared/utils/ids' import type {SequenceTrackId} from '@theatre/shared/utils/ids'
@ -53,17 +52,6 @@ export default class SheetObject implements IdentityDerivationProvider {
this.publicApi = new TheatreSheetObject(this) this.publicApi = new TheatreSheetObject(this)
} }
overrideConfig(
nativeObject: unknown,
config: SheetObjectConfig<$IntentionalAny>,
) {
if (nativeObject !== this.nativeObject) {
// @todo
}
this.template.overrideConfig(nativeObject, config)
}
getValues(): IDerivation<Pointer<SerializableMap>> { getValues(): IDerivation<Pointer<SerializableMap>> {
return this._cache.get('getValues()', () => return this._cache.get('getValues()', () =>
prism(() => { prism(() => {

View file

@ -10,6 +10,7 @@ import {InvalidArgumentError} from '@theatre/shared/utils/errors'
import {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths' import {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths'
import type {$IntentionalAny} from '@theatre/shared/utils/types' import type {$IntentionalAny} from '@theatre/shared/utils/types'
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue'
import deepEqual from 'fast-deep-equal'
export type SheetObjectConfig< export type SheetObjectConfig<
Props extends PropTypeConfig_Compound<$IntentionalAny>, Props extends PropTypeConfig_Compound<$IntentionalAny>,
@ -25,7 +26,7 @@ export interface ISheet {
config: SheetObjectConfig<Props>, config: SheetObjectConfig<Props>,
): ISheetObject<Props> ): ISheetObject<Props>
sequence(): ISequence readonly sequence: ISequence
} }
export default class TheatreSheet implements ISheet { export default class TheatreSheet implements ISheet {
@ -49,14 +50,21 @@ export default class TheatreSheet implements ISheet {
`sheet.object("${key}", ...)`, `sheet.object("${key}", ...)`,
) )
// @todo sanitize config
const existingObject = internal.getObject(sanitizedPath) const existingObject = internal.getObject(sanitizedPath)
const nativeObject = null const nativeObject = null
if (existingObject) { 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 return existingObject.publicApi as $IntentionalAny
} else { } else {
const object = internal.createObject(sanitizedPath, nativeObject, config) 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 return privateAPI(this).getSequence().publicApi
} }

View file

@ -87,5 +87,8 @@
"typescript": "^4.4.2", "typescript": "^4.4.2",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"uuid": "^8.3.2" "uuid": "^8.3.2"
},
"dependencies": {
"fast-deep-equal": "^3.1.3"
} }
} }

View file

@ -22697,6 +22697,7 @@ fsevents@^1.2.7:
esbuild-loader: ^2.13.1 esbuild-loader: ^2.13.1
esbuild-register: ^2.5.0 esbuild-register: ^2.5.0
exec-loader: ^4.0.0 exec-loader: ^4.0.0
fast-deep-equal: ^3.1.3
file-loader: ^6.2.0 file-loader: ^6.2.0
fs-extra: ^10.0.0 fs-extra: ^10.0.0
html-loader: ^2.1.2 html-loader: ^2.1.2