relay-runtime
Version:
A core runtime for building GraphQL-driven applications.
181 lines (162 loc) • 4.76 kB
Flow
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall relay
*/
;
import type {IEnvironment, RecordSource} from '../store/RelayStoreTypes';
type InspectFn = (environment: IEnvironment, dataID?: ?string) => mixed;
let inspect: InspectFn = () => {};
if (__DEV__) {
let formattersInstalled = false;
/**
* Installs a Chrome Developer Tools custom formatter for Relay proxy objects
* returned by StoreInspector.inspect.
*
* bit.ly/object-formatters
*/
const installDevtoolFormatters = () => {
if (formattersInstalled) {
return;
}
formattersInstalled = true;
// $FlowFixMe[incompatible-use] D61394600
if (window.devtoolsFormatters == null) {
// $FlowFixMe[incompatible-use] D61394600
window.devtoolsFormatters = [];
}
// $FlowFixMe[incompatible-use] D61394600
if (!Array.isArray(window.devtoolsFormatters)) {
return;
}
// eslint-disable-next-line no-console
console.info(
'Make sure to select "Enable custom formatters" in the Chrome ' +
'Developer Tools settings, tab "Preferences" under the "Console" ' +
'section.',
);
// $FlowFixMe[incompatible-use] D61394600
window.devtoolsFormatters.push(...createFormatters());
};
const createFormatters = () => {
const listStyle = {
style:
'list-style-type: none; padding: 0; margin: 0 0 0 12px; font-style: normal',
};
const keyStyle = {style: 'rgb(136, 19, 145)'};
const nullStyle = {style: 'color: #777'};
const reference = (object: $FlowFixMe, config: void) => {
return object == null
? ['span', nullStyle, 'undefined']
: ['object', {object, config}];
};
const renderRecordHeader = (record: $FlowFixMe) => {
return [
'span',
{style: 'font-style: italic'},
record.__typename,
['span', nullStyle, ' {id: "', record.__id, '", …}'],
];
};
const isRecord = (o: $FlowFixMe) => o != null && typeof o.__id === 'string';
class RecordEntry {
+key: string;
+value: mixed;
constructor(key: string, value: mixed) {
this.key = key;
this.value = value;
}
}
const renderRecordEntries = (record: $FlowFixMe) => {
const children = Object.keys(record).map(key => {
return [
'li',
{},
['object', {object: new RecordEntry(key, record[key])}],
];
});
return ['ol', listStyle, ...children];
};
const recordFormatter = {
header(obj: $FlowFixMe) {
if (!isRecord(obj)) {
return null;
}
return renderRecordHeader(obj);
},
hasBody(obj: $FlowFixMe) {
return true;
},
body(obj: $FlowFixMe) {
return renderRecordEntries(obj);
},
};
const recordEntryFormatter = {
header(obj: $FlowFixMe) {
if (obj instanceof RecordEntry) {
const value = isRecord(obj.value)
? renderRecordHeader(obj.value)
: reference(obj.value);
return ['span', keyStyle, obj.key, ': ', value];
}
return null;
},
hasBody(obj: $FlowFixMe) {
return isRecord(obj.value);
},
body(obj: $FlowFixMe) {
return renderRecordEntries(obj.value);
},
};
return [recordFormatter, recordEntryFormatter];
};
const getWrappedRecord = (
source: RecordSource,
dataID: string,
): ?{[string]: mixed} => {
const record = source.get(dataID);
if (record == null) {
return record;
}
return new Proxy(
// $FlowFixMe: Do not assume that record is an object
{...record},
{
get(target, prop) {
// $FlowFixMe[invalid-computed-prop]
const value = target[prop];
if (value == null) {
return value;
}
if (typeof value === 'object') {
if (typeof value.__ref === 'string') {
return getWrappedRecord(source, value.__ref);
}
if (Array.isArray(value.__refs)) {
// $FlowFixMe[incompatible-call]
return value.__refs.map((ref: string) =>
getWrappedRecord(source, ref),
);
}
}
return value;
},
},
);
};
inspect = (environment: IEnvironment, dataID: ?string) => {
installDevtoolFormatters();
return getWrappedRecord(
environment.getStore().getSource(),
dataID ?? 'client:root',
);
};
}
module.exports = {
inspect,
};