From c373b3abc522c253409e821ed3c6377c8058bdef Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 26 May 2020 21:14:46 -0500 Subject: [PATCH] begin rework of notifications The notification reducer should decide when to display notifications rather than having notifications scattered throughout the code. Also start using translations so that user-visible strings are not scattered in code. --- .vscode/extensions.json | 6 +- package.json | 1 + src/actions/bootloader.ts | 35 +++- src/actions/index.ts | 2 + src/actions/notification.ts | 2 +- src/actions/service-worker.ts | 27 +++ src/components/Notification.tsx | 28 ++- src/components/NotificationStack.tsx | 16 +- src/components/notification.en.json | 18 ++ src/index.tsx | 28 ++- src/react-app-env.d.ts | 5 + src/reducers/bootloader.ts | 9 +- src/reducers/notification.ts | 99 +++++++-- src/sagas/bootloader.ts | 22 +- src/services/bootloader.ts | 58 ++---- src/services/editor.ts | 12 +- src/services/error-log.ts | 18 +- src/services/index.ts | 21 +- yarn.lock | 296 ++++++++++++++++++++++++++- 19 files changed, 575 insertions(+), 128 deletions(-) create mode 100644 src/actions/service-worker.ts create mode 100644 src/components/notification.en.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7086feb2..6c8424cd 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,7 +3,11 @@ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp // List of extensions which should be recommended for users of this workspace. - "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"], + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "mrmlnc.vscode-json5" + ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] } diff --git a/package.json b/package.json index a2449a1b..e3bf0a89 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "dependencies": { "@pybricks/firmware": "^1.0.0", "@pybricks/mpy-cross-v4": "^1.0.0", + "@shopify/react-i18n": "^3.0.2", "@testing-library/jest-dom": "^5.8.0", "@testing-library/react": "^10.0.4", "@testing-library/user-event": "^10.3.4", diff --git a/src/actions/bootloader.ts b/src/actions/bootloader.ts index bc9d7427..96415ea6 100644 --- a/src/actions/bootloader.ts +++ b/src/actions/bootloader.ts @@ -18,9 +18,9 @@ export enum BootloaderConnectionActionType { */ DidConnect = 'bootloader.action.connection.did.connect', /** - * The connection was cancelled. + * The connection was not successful. */ - DidCancel = 'bootloader.action.connection.did.cancel', + DidFailToConnect = 'bootloader.action.connection.did.connect.fail', /** * There was a connection error. */ @@ -62,12 +62,31 @@ export function didConnect( return { type: BootloaderConnectionActionType.DidConnect, canWriteWithoutResponse }; } -export type BootloaderConnectionDidCancelAction = Action< - BootloaderConnectionActionType.DidCancel ->; +/** + * Possible reasons a device could fail to connect. + */ +export enum BootloaderConnectionFailureReason { + /** The reason is not known */ + Unknown = 'unknown', + /** The connection was canceled */ + Canceled = 'canceled', + /** Web Bluetooth is not available */ + NoWebBluetooth = 'no-web-bluetooth', + /** Connected but failed to find the bootloader GATT service */ + GattServiceNotFound = 'gatt-service-not-found', +} -export function didCancel(): BootloaderConnectionDidCancelAction { - return { type: BootloaderConnectionActionType.DidCancel }; +export interface BootloaderConnectionDidFailToConnectAction + extends Action { + reason: BootloaderConnectionFailureReason; + err?: Error; +} + +export function didFailToConnect( + reason: BootloaderConnectionFailureReason, + err?: Error, +): BootloaderConnectionDidFailToConnectAction { + return { type: BootloaderConnectionActionType.DidFailToConnect, reason, err }; } export interface BootloaderConnectionDidErrorAction @@ -124,7 +143,7 @@ export function didDisconnect(): BootloaderConnectionDidDisconnectAction { export type BootloaderConnectionAction = | BootloaderConnectionConnectAction | BootloaderConnectionDidConnectAction - | BootloaderConnectionDidCancelAction + | BootloaderConnectionDidFailToConnectAction | BootloaderConnectionDidErrorAction | BootloaderConnectionSendAction | BootloaderConnectionDidSendAction diff --git a/src/actions/index.ts b/src/actions/index.ts index 4a1969a4..82e55208 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -8,6 +8,7 @@ import { } from './bootloader'; import { EditorAction } from './editor'; import { NotificationAction } from './notification'; +import { ServiceWorkerAction } from './service-worker'; import { TerminalDataAction } from './terminal'; /** @@ -21,6 +22,7 @@ export type Action = | BootloaderAction | EditorAction | NotificationAction + | ServiceWorkerAction | TerminalDataAction; /** diff --git a/src/actions/notification.ts b/src/actions/notification.ts index b41b019d..7207c8c2 100644 --- a/src/actions/notification.ts +++ b/src/actions/notification.ts @@ -59,7 +59,7 @@ export function add( message: string, helpUrl?: string, ): NotificationAddAction { - return { type: NotificationActionType.Add, id: nextId(), level, message, helpUrl }; + return { type: NotificationActionType.Add, id: -nextId(), level, message, helpUrl }; } /** diff --git a/src/actions/service-worker.ts b/src/actions/service-worker.ts new file mode 100644 index 00000000..408a17fe --- /dev/null +++ b/src/actions/service-worker.ts @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2020 The Pybricks Authors + +import { Action } from 'redux'; + +export enum ServiceWorkerActionType { + Update = 'serviceWorker.update', + Success = 'serviceWorker.success', +} + +export interface ServiceWorkerAction< + T extends ServiceWorkerActionType = ServiceWorkerActionType +> extends Action { + registration: ServiceWorkerRegistration; +} + +export function update( + registration: ServiceWorkerRegistration, +): ServiceWorkerAction { + return { type: ServiceWorkerActionType.Update, registration }; +} + +export function success( + registration: ServiceWorkerRegistration, +): ServiceWorkerAction { + return { type: ServiceWorkerActionType.Success, registration }; +} diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx index a4352b13..8f32cc9a 100644 --- a/src/components/Notification.tsx +++ b/src/components/Notification.tsx @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020 The Pybricks Authors +import { WithI18nProps, withI18n } from '@shopify/react-i18n'; import React from 'react'; import Toast from 'react-bootstrap/Toast'; import { connect } from 'react-redux'; import { Dispatch } from '../actions'; -import * as notification from '../actions/notification'; +import { remove } from '../actions/notification'; +import en from './notification.en.json'; interface DispatchProps { onClose: () => void; @@ -14,11 +16,12 @@ interface DispatchProps { interface OwnProps { id: number; style: string; - message: string; + message?: string; + messageId?: string; helpUrl?: string; } -type NotificationProps = DispatchProps & OwnProps; +type NotificationProps = DispatchProps & OwnProps & WithI18nProps; function mapTitle(style: string): string { switch (style) { @@ -47,7 +50,11 @@ class Notification extends React.Component { -

{this.props.message}

+

+ {this.props.messageId + ? this.props.i18n.translate(this.props.messageId) + : this.props.message || 'missing message!'} +

{this.props.helpUrl && ( { const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps): DispatchProps => ({ onClose: (): void => { - dispatch(notification.remove(ownProps.id)); + dispatch(remove(ownProps.id)); }, }); -export default connect(null, mapDispatchToProps)(Notification); +export default connect( + null, + mapDispatchToProps, +)( + withI18n({ + id: 'notification', + fallback: en, + translations: () => ({ en }), + })(Notification), +); diff --git a/src/components/NotificationStack.tsx b/src/components/NotificationStack.tsx index 852b4947..330e9fef 100644 --- a/src/components/NotificationStack.tsx +++ b/src/components/NotificationStack.tsx @@ -6,7 +6,7 @@ import { Collapse } from 'react-bootstrap'; import { connect } from 'react-redux'; import { TransitionGroup } from 'react-transition-group'; import { RootState } from '../reducers'; -import { NotificationList } from '../reducers/notification'; +import { Level, NotificationList } from '../reducers/notification'; import Notification from './Notification'; interface StateProps { @@ -15,6 +15,17 @@ interface StateProps { type NotificationStackProps = StateProps; +function mapLevelToStyle(level: Level): string { + switch (level) { + case Level.Error: + return 'danger'; + case Level.Warning: + return 'warning'; + case Level.Info: + return 'info'; + } +} + class NotificationStack extends React.Component { render(): JSX.Element { return ( @@ -33,8 +44,9 @@ class NotificationStack extends React.Component { diff --git a/src/components/notification.en.json b/src/components/notification.en.json new file mode 100644 index 00000000..6762ec8b --- /dev/null +++ b/src/components/notification.en.json @@ -0,0 +1,18 @@ +{ + "bootloader": { + "connection": { + "didConnect": { + "cannotWriteWithoutResponse": "This web browser does not support Web Bluetooth Write Characteristic Without Response. Flashing firmware will take a long time." + }, + "didFailToConnect": { + "gattServiceNotFound": "Connected to hub but failed to get LEGO bootloader service. Try removing the \"LEGO Bootloader\" device in your OS Bluetooth settings, then try again.", + "noWebBluetooth": "This web browser does not support Web Bluetooth or it is not enabled.", + "unknown": "Unexpected error while trying to connect. Check console log and report the error." + } + } + }, + "serviceWorker": { + "success": "Content is cached for offline use.", + "update": "New content is available and will be used when all tabs for this page are closed.'" + } +} diff --git a/src/index.tsx b/src/index.tsx index 735019c1..4f3bbe9c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020 The Pybricks Authors +import { I18nContext, I18nManager } from '@shopify/react-i18n'; import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; @@ -10,7 +11,7 @@ import { createEpicMiddleware } from 'redux-observable'; import createSagaMiddleware from 'redux-saga'; import thunkMiddleware from 'redux-thunk'; import './index.scss'; -import * as notification from './actions/notification'; +import { success, update } from './actions/service-worker'; import App from './components/App'; import NotificationStack from './components/NotificationStack'; import rootEpic from './epics'; @@ -24,6 +25,11 @@ const epicMiddleware = createEpicMiddleware(); // TODO: add runtime option or filter - logger affects firmware flash performance const loggerMiddleware = createLogger({ predicate: () => false }); +const i18n = new I18nManager({ + locale: 'en', + onError: (err): void => console.error(err), +}); + const store = createStore( rootReducer, applyMiddleware( @@ -41,8 +47,10 @@ epicMiddleware.run(rootEpic); ReactDOM.render( - - + + + + , document.getElementById('root'), @@ -52,16 +60,6 @@ ReactDOM.render( // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.register({ - onUpdate: () => { - store.dispatch( - notification.add( - 'info', - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', - ), - ); - }, - onSuccess: () => { - store.dispatch(notification.add('info', 'Content is cached for offline use.')); - }, + onUpdate: (r) => store.dispatch(update(r)), + onSuccess: (r) => store.dispatch(success(r)), }); diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index e5b43ded..4942ceef 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1,5 +1,10 @@ /// +declare module '*.json' { + const src: object; + export default src; +} + declare module '*.zip' { const src: string; export default src; diff --git a/src/reducers/bootloader.ts b/src/reducers/bootloader.ts index 4cd7f41a..b0ad06d9 100644 --- a/src/reducers/bootloader.ts +++ b/src/reducers/bootloader.ts @@ -44,15 +44,8 @@ const connection: Reducer = ( case BootloaderRequestActionType.Disconnect: return BootloaderConnectionState.Disconnecting; case BootloaderConnectionActionType.DidDisconnect: - case BootloaderConnectionActionType.DidCancel: + case BootloaderConnectionActionType.DidFailToConnect: return BootloaderConnectionState.Disconnected; - case BootloaderConnectionActionType.DidError: - // Error while connecting means we didn't connect. - if (state === BootloaderConnectionState.Connecting) { - return BootloaderConnectionState.Disconnected; - } else { - return state; - } default: return state; } diff --git a/src/reducers/notification.ts b/src/reducers/notification.ts index 93dbaf06..565907ba 100644 --- a/src/reducers/notification.ts +++ b/src/reducers/notification.ts @@ -3,35 +3,110 @@ import { Reducer } from 'react'; import { combineReducers } from 'redux'; -import { NotificationAction, NotificationActionType } from '../actions/notification'; +import { Action } from '../actions'; +import { + BootloaderConnectionActionType, + BootloaderConnectionFailureReason, +} from '../actions/bootloader'; +import { NotificationActionType } from '../actions/notification'; +import { ServiceWorkerActionType } from '../actions/service-worker'; +import { createCountFunc } from '../utils/iter'; -export type NotificationList = Array<{ +/** + * Severity level of notification. + */ +export enum Level { + /** + * This is an error (requires user action to resolve) + */ + Error = 'error', + /** + * This is a warning (user could take action or ignore) + */ + Warning = 'warning', + /** + * This is just FYI (no user action required) + */ + Info = 'info', +} + +export interface Notification { readonly id: number; - readonly style: string; - readonly message: string; + readonly level: Level; + readonly message?: string; + readonly messageId?: string; readonly helpUrl?: string; -}>; +} -const levelMap = { - error: 'danger', - warning: 'warning', - info: 'info', -}; +export type NotificationList = Array; -const list: Reducer = (state = [], action) => { +const nextId = createCountFunc(); + +function append( + state: NotificationList, + level: Level, + messageId: string, + helpUrl?: string, +): NotificationList { + return [...state, { id: nextId(), level, messageId, helpUrl }]; +} + +const list: Reducer = (state = [], action) => { switch (action.type) { + case BootloaderConnectionActionType.DidConnect: + if (!action.canWriteWithoutResponse) { + return append( + state, + Level.Warning, + 'bootloader.connection.didConnect.cannotWriteWithoutResponse', + 'https://github.com/WebBluetoothCG/web-bluetooth/blob/master/implementation-status.md', + ); + } + return state; + case BootloaderConnectionActionType.DidFailToConnect: + switch (action.reason) { + case BootloaderConnectionFailureReason.GattServiceNotFound: + return append( + state, + Level.Error, + 'bootloader.connection.didFailToConnect.gattServiceNotFound', + ); + case BootloaderConnectionFailureReason.NoWebBluetooth: + return append( + state, + Level.Error, + 'bootloader.connection.didFailToConnect.noWebBluetooth', + 'https://github.com/WebBluetoothCG/web-bluetooth/blob/master/implementation-status.md', + ); + case BootloaderConnectionFailureReason.Unknown: + return append( + state, + Level.Error, + 'bootloader.connection.didFailToConnect.unknown', + ); + } + return state; case NotificationActionType.Add: return [ ...state, { id: action.id, - style: levelMap[action.level], + level: action.level as Level, message: action.message, helpUrl: action.helpUrl, }, ]; case NotificationActionType.Remove: return state.filter((e) => e.id !== action.id); + case ServiceWorkerActionType.Update: + return append( + state, + Level.Info, + 'serviceWorker.update', + 'https://bit.ly/CRA-PWA', + ); + case ServiceWorkerActionType.Success: + return append(state, Level.Info, 'serviceWorker.success'); default: return state; } diff --git a/src/sagas/bootloader.ts b/src/sagas/bootloader.ts index fc8ad8ba..b290720d 100644 --- a/src/sagas/bootloader.ts +++ b/src/sagas/bootloader.ts @@ -22,9 +22,8 @@ import { BootloaderActionType, BootloaderChecksumResponseAction, BootloaderConnectionActionType, - BootloaderConnectionDidCancelAction, BootloaderConnectionDidConnectAction, - BootloaderConnectionDidErrorAction, + BootloaderConnectionDidFailToConnectAction, BootloaderConnectionDidReceiveAction, BootloaderConnectionDidSendAction, BootloaderDidRequestAction, @@ -312,24 +311,17 @@ function* flashFirmware(action: BootloaderFlashFirmwareAction): Generator { } yield put(connect()); - const didConnect = (yield take([ + const connectResult = (yield take([ BootloaderConnectionActionType.DidConnect, - BootloaderConnectionActionType.DidCancel, - BootloaderConnectionActionType.DidError, + BootloaderConnectionActionType.DidFailToConnect, ])) as | BootloaderConnectionDidConnectAction - | BootloaderConnectionDidCancelAction - | BootloaderConnectionDidErrorAction; + | BootloaderConnectionDidFailToConnectAction; - if (didConnect.type === BootloaderConnectionActionType.DidCancel) { + if (connectResult.type === BootloaderConnectionActionType.DidFailToConnect) { return; } - if (didConnect.type === BootloaderConnectionActionType.DidError) { - // TODO: proper error handling - throw didConnect.err; - } - yield put(infoRequest()); const info = (yield wait(BootloaderResponseActionType.Info)) as WaitResponse< BootloaderInfoResponseAction @@ -373,7 +365,7 @@ function* flashFirmware(action: BootloaderFlashFirmwareAction): Generator { } // City hub bootloader is buggy. See note in encodeRequest(). - if (info[0].hubType === HubType.CityHub && !didConnect.canWriteWithoutResponse) { + if (info[0].hubType === HubType.CityHub && !connectResult.canWriteWithoutResponse) { yield put( notification.add( 'error', @@ -420,7 +412,7 @@ function* flashFirmware(action: BootloaderFlashFirmwareAction): Generator { yield put(progress(offset, firmware.length)); - if (didConnect.canWriteWithoutResponse) { + if (connectResult.canWriteWithoutResponse) { // request checksum every 8K to prevent buffer overrun on the hub // because of sending too much data at once if (++count % 585 === 0) { diff --git a/src/services/bootloader.ts b/src/services/bootloader.ts index b8302fa6..fe73dba2 100644 --- a/src/services/bootloader.ts +++ b/src/services/bootloader.ts @@ -5,14 +5,13 @@ import { Action, Dispatch } from '../actions'; import { BootloaderConnectionActionType, - didCancel, + BootloaderConnectionFailureReason as Reason, didConnect, didDisconnect, - didError, + didFailToConnect, didReceive, didSend, } from '../actions/bootloader'; -import * as notification from '../actions/notification'; import { CharacteristicUUID, ServiceUUID } from '../protocols/bootloader'; import { PolyfillBluetoothRemoteGATTCharacteristic, @@ -33,13 +32,7 @@ async function connect(action: Action, dispatch: Dispatch): Promise { throw Error('already connected'); } if (navigator.bluetooth === undefined) { - dispatch( - notification.add( - 'error', - 'This web browser does not support Web Bluetooth or it is not enabled.', - 'https://github.com/WebBluetoothCG/web-bluetooth/blob/master/implementation-status.md', - ), - ); + dispatch(didFailToConnect(Reason.NoWebBluetooth)); return; } // TODO: check navigator.bluetooth.getAvailability() @@ -55,7 +48,7 @@ async function connect(action: Action, dispatch: Dispatch): Promise { ) { // this error is received if the user clicks the cancel button in // the bluetooth scan dialog - dispatch(didCancel()); + dispatch(didFailToConnect(Reason.Canceled)); return; } throw err; @@ -81,44 +74,29 @@ async function connect(action: Action, dispatch: Dispatch): Promise { dispatch(didReceive(char.value)); }); await char.startNotifications(); - - // char.writeValueWithoutResponse() was introduced in Chrome 85 - // Older versions of Chrome for Android will write without response - // by default, so don't warn on Android. - if ( - !char.writeValueWithoutResponse && - !/Android/i.test(navigator.userAgent) - ) { - // TODO: this needs to be an error if connected to city hub - // however it is not currently possible to get mfg-specific - // advertising data, so we don't know what type of hub it is - // until after we connect - dispatch( - notification.add( - 'warning', - 'This web browser does not support Web Bluetooth Write Characteristic Without Response. Flashing firmware will take a long time.', - 'https://github.com/WebBluetoothCG/web-bluetooth/blob/master/implementation-status.md', - ), - ); - } } catch (err) { + device.gatt.disconnect(); if ( err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR ) { - dispatch( - notification.add( - 'error', - 'Connected to hub but failed to get LEGO bootloader service. Try removing the "LEGO Bootloader" device in your OS Bluetooth settings, then try again.', - ), - ); + // Possibly/probably caused by Chrome BlueZ back-end bug + // https://chromium-review.googlesource.com/c/chromium/src/+/2214098 + dispatch(didFailToConnect(Reason.GattServiceNotFound)); + return; } - device.gatt.disconnect(); throw err; } - dispatch(didConnect(char.writeValueWithoutResponse !== undefined)); + + // char.writeValueWithoutResponse() was introduced in Chrome 85. + // Older versions of Chrome for Android will write without response + // by default when using the deprecated writeValue(). + const canWriteWithoutResponse = + char.writeValueWithoutResponse !== undefined || + /Android/i.test(navigator.userAgent); + dispatch(didConnect(canWriteWithoutResponse)); } catch (err) { - dispatch(didError(err)); + dispatch(didFailToConnect(Reason.Unknown, err)); } } diff --git a/src/services/editor.ts b/src/services/editor.ts index 722a49b4..44f3d50e 100644 --- a/src/services/editor.ts +++ b/src/services/editor.ts @@ -9,11 +9,7 @@ import { combineServices } from '.'; const decoder = new TextDecoder(); -async function open( - action: Action, - _dispatch: Dispatch, - state: RootState, -): Promise { +function open(action: Action, _dispatch: Dispatch, state: RootState): void { if (action.type !== EditorActionType.Open) { return; } @@ -26,11 +22,7 @@ async function open( state.editor.current.getDocument().setValue(text); } -async function save( - action: Action, - _dispatch: Dispatch, - state: RootState, -): Promise { +function save(action: Action, _dispatch: Dispatch, state: RootState): void { if (action.type !== EditorActionType.Save) { return; } diff --git a/src/services/error-log.ts b/src/services/error-log.ts index f0d3b934..1c312f2a 100644 --- a/src/services/error-log.ts +++ b/src/services/error-log.ts @@ -2,11 +2,25 @@ // Copyright (c) 2020 The Pybricks Authors import { Action } from '../actions'; -import { BootloaderConnectionActionType } from '../actions/bootloader'; +import { + BootloaderConnectionActionType, + BootloaderConnectionFailureReason, +} from '../actions/bootloader'; import { combineServices } from '.'; -async function consoleLog(action: Action): Promise { +/** + * Logs unexpected errors to console.error and expected errors to console.debug. + * @param action An action + */ +function consoleLog(action: Action): void { switch (action.type) { + case BootloaderConnectionActionType.DidFailToConnect: + if (action.reason === BootloaderConnectionFailureReason.Unknown) { + console.error(action.err); + } else { + console.debug(action.err); + } + break; case BootloaderConnectionActionType.DidError: console.error(action.err); break; diff --git a/src/services/index.ts b/src/services/index.ts index e17d3477..6272d44e 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -8,7 +8,11 @@ import bootloader from './bootloader'; import editor from './editor'; import errorLog from './error-log'; -type Service = (action: Action, dispatch: Dispatch, state: RootState) => Promise; +type Service = ( + action: Action, + dispatch: Dispatch, + state: RootState, +) => void | Promise; function runService( service: Service, @@ -16,15 +20,20 @@ function runService( dispatch: Dispatch, state: RootState, ): void { - service(action, dispatch, state).catch((err) => - console.log(`Unhandled exception in service: ${err}`), - ); + // Services are deferred so that the current action completes before + // dispatching another action by calling dispatch() in the service. + setTimeout(async () => { + try { + await service(action, dispatch, state); + } catch (err) { + console.log(`Unhandled exception in service: ${err}`); + } + }, 0); } export function combineServices(...services: Service[]): Service { - return (a, d, s): Promise => { + return (a, d, s): void => { services.forEach((x) => runService(x, a, d, s)); - return Promise.resolve(); }; } diff --git a/yarn.lock b/yarn.lock index 55fd822e..dcd22cf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,6 +62,16 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/generator@^7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.0.tgz#a238837896edf35ee5fbfb074548d3256b4bc55d" + integrity sha512-ThoWCJHlgukbtCP79nAK4oLqZt5fVo70AHUni/y8Jotyg5rtJiG2FVl+iJjRNKIyl4hppqztLyAoEWcCvqyOFQ== + dependencies: + "@babel/types" "^7.10.0" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + "@babel/generator@^7.4.0", "@babel/generator@^7.9.0", "@babel/generator@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" @@ -296,6 +306,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== +"@babel/parser@^7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.0.tgz#8eca3e71a73dd562c5222376b08253436bb4995b" + integrity sha512-fnDUl1Uy2gThM4IFVW4ISNHqr3cJrCsRkSCasFgx0XDO9JcttDS5ytyBc4Cu4X1+fjoo3IVvFbRD6TeFlHJlEQ== + "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" @@ -995,6 +1010,15 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.0.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.0.tgz#f15d852ce16cd5fb3e219097a75f662710b249b1" + integrity sha512-aMLEQn5tcG49LEWrsEwxiRTdaJmvLem3+JMCMSeCy2TILau0IDVyWdm/18ACx7XOCady64FLt6KkHy28tkDQHQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.10.0" + "@babel/types" "^7.10.0" + "@babel/template@^7.4.0", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -1004,6 +1028,21 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" +"@babel/traverse@^7.0.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.0.tgz#290935529881baf619398d94fd453838bef36740" + integrity sha512-NZsFleMaLF1zX3NxbtXI/JCs2RPOdpGru6UBdGsfhdsDsP+kFF+h2QQJnMJglxk0kc69YmMFs4A44OJY0tKo5g== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.10.0" + "@babel/helper-function-name" "^7.9.5" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.10.0" + "@babel/types" "^7.10.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.0", "@babel/traverse@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" @@ -1028,6 +1067,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.0.tgz#d47d92249e42393a5723aad5319035ae411e3e38" + integrity sha512-t41W8yWFyQFPOAAvPvjyRhejcLGnJTA3iRpFcDbEKwVJ3UnHQePFzLk8GagTsucJlImyNwrGikGsYURrWbQG8w== + dependencies: + "@babel/helper-validator-identifier" "^7.9.5" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1321,6 +1369,77 @@ lodash "^4.17.15" lodash-es "^4.17.15" +"@shopify/dates@^0.3.6": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@shopify/dates/-/dates-0.3.6.tgz#5118ae0b16cbe384dcb7ae19c21f17c6396e8302" + integrity sha512-dr71oU2x7ThQ7r7jzWWkk61+0nwHFSiIpZXIvlUDCJ/31rWZGWv9S1XDV7/rLJQgNfVIwA2x9eOz0CbYCAuL6Q== + dependencies: + "@shopify/decorators" "^1.1.11" + tslib "^1.9.3" + +"@shopify/decorators@^1.1.11": + version "1.1.11" + resolved "https://registry.yarnpkg.com/@shopify/decorators/-/decorators-1.1.11.tgz#ae111160185d9f33aebbab4df401b4bea858f302" + integrity sha512-qE5y08Oh6ukSQuF7CIPaCs9oLy99nRv1Rmq9rhb7Q02DB9+8z6uNGVmhcftbrIs/7VEMlqk0LrfL6e0ijrxy3g== + dependencies: + "@shopify/function-enhancers" "^1.0.9" + +"@shopify/function-enhancers@^1.0.9": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@shopify/function-enhancers/-/function-enhancers-1.0.9.tgz#aa95e49ca2a3be1c76259ebce078b607faf26007" + integrity sha512-iGoGIuJIFgWyEnEttjvtSH+5nQS6Lv4LAAooSNaviL10Ah+gLBfFcuSIj19fVLaRl9WxYSIu4sWqwnwn/W+n1g== + +"@shopify/i18n@^0.1.10": + version "0.1.10" + resolved "https://registry.yarnpkg.com/@shopify/i18n/-/i18n-0.1.10.tgz#8f07f538cba43581ea79dc1104d351b30da6d03d" + integrity sha512-RCSMyiDIMrjdiG8BDCtlu2L+0CCM7XDiWx5VbAvSb0GteBRcH8BZHh+22LJt3rcbCNnbiyKlLg/7ikbSyEgzHg== + dependencies: + tslib "^1.9.3" + +"@shopify/react-effect@^3.2.12": + version "3.2.12" + resolved "https://registry.yarnpkg.com/@shopify/react-effect/-/react-effect-3.2.12.tgz#81b641cee3f5f451cabc5dd11318f57756786e76" + integrity sha512-q4R2x+K3D/M1F5e32qTFLVrN+9BonQACQxu+GnKjQGdZaRqndKgaLUKQQZJbIhCa1/rcj8H/d1JEADeU1XvSrQ== + dependencies: + tslib "^1.9.3" + +"@shopify/react-hooks@^1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@shopify/react-hooks/-/react-hooks-1.10.0.tgz#39671f889872a345f7e60a635a3d9fcb2a487825" + integrity sha512-kgw/lUOhvd5hqOWVGT3Z41Yl9k9knr0s8n+m8YdQTIklww7QzDNV341XKslaRy0qHAf2GUw7JH5g8BQFm1ZQSA== + +"@shopify/react-i18n@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@shopify/react-i18n/-/react-i18n-3.0.2.tgz#dd4a07185952707bae71b01bbec6ef3ce2087279" + integrity sha512-xfOXZgE/DDQQRFxAYGBelQjygcddRPHzUo/ejHP5aU1W2Bs7RqiIQFv/CpLghwO8rKZvKoaHPickEk7ULwbQ3w== + dependencies: + "@shopify/dates" "^0.3.6" + "@shopify/decorators" "^1.1.11" + "@shopify/function-enhancers" "^1.0.9" + "@shopify/i18n" "^0.1.10" + "@shopify/react-effect" "^3.2.12" + "@shopify/react-hooks" "^1.10.0" + "@shopify/useful-types" "^2.1.5" + "@types/hoist-non-react-statics" "^3.0.1" + change-case "^3.1.0" + glob "^7.1.4" + hoist-non-react-statics "^3.0.1" + lodash.clonedeep "^4.0.0" + lodash.merge "^4.0.0" + string-hash "^1.1.3" + tslib "^1.9.3" + optionalDependencies: + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.0.0" + fs-extra "^8.1.0" + +"@shopify/useful-types@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@shopify/useful-types/-/useful-types-2.1.5.tgz#9fd09dcdfe272d8eb2cdd8e94058456eefdf57fa" + integrity sha512-V+6gW+fzr+XY//4RyI6XVvwu9cvLVWt9rae1t8Lx2rPjGt/kIM6TdCzk/Lfhd5E3RUR7vVgvWTfTw1FtrxmYUw== + dependencies: + tslib "^1.9.3" + "@svgr/babel-plugin-add-jsx-attribute@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" @@ -1527,7 +1646,7 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/hoist-non-react-statics@^3.3.0": +"@types/hoist-non-react-statics@^3.0.1", "@types/hoist-non-react-statics@^3.3.0": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -2841,6 +2960,14 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camel-case@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + camel-case@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" @@ -2932,6 +3059,30 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +change-case@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.1.0.tgz#0e611b7edc9952df2e8513b27b42de72647dd17e" + integrity sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw== + dependencies: + camel-case "^3.0.0" + constant-case "^2.0.0" + dot-case "^2.1.0" + header-case "^1.0.0" + is-lower-case "^1.1.0" + is-upper-case "^1.1.0" + lower-case "^1.1.1" + lower-case-first "^1.0.0" + no-case "^2.3.2" + param-case "^2.1.0" + pascal-case "^2.0.0" + path-case "^2.1.0" + sentence-case "^2.1.0" + snake-case "^2.1.0" + swap-case "^1.1.0" + title-case "^2.1.0" + upper-case "^1.1.1" + upper-case-first "^1.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -3234,6 +3385,14 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= +constant-case@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46" + integrity sha1-QXV2TTidP6nI7NKRhu1gBSQ7akY= + dependencies: + snake-case "^2.1.0" + upper-case "^1.1.1" + constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -3983,6 +4142,13 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" +dot-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-2.1.1.tgz#34dcf37f50a8e93c2b3bca8bb7fb9155c7da3bee" + integrity sha1-NNzzf1Co6TwrO8qLt/uRVcfaO+4= + dependencies: + no-case "^2.2.0" + dot-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" @@ -5310,6 +5476,14 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +header-case@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/header-case/-/header-case-1.0.1.tgz#9535973197c144b09613cd65d317ef19963bd02d" + integrity sha1-lTWXMZfBRLCWE81l0xfvGZY70C0= + dependencies: + no-case "^2.2.0" + upper-case "^1.1.3" + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -5324,7 +5498,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.1, hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -5908,6 +6082,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-lower-case@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393" + integrity sha1-fhR75HaNxGbbO/shzGCzHmrWk5M= + dependencies: + lower-case "^1.1.0" + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -6012,6 +6193,13 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-upper-case@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" + integrity sha1-jQsfp+eTOh5YSDYA7H2WYcuvdW8= + dependencies: + upper-case "^1.1.0" + is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" @@ -6907,6 +7095,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.clonedeep@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -6922,6 +7115,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.0.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -6972,6 +7170,18 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lower-case-first@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" + integrity sha1-5dp8JvKacHO+AtUrrJmA5ZIq36E= + dependencies: + lower-case "^1.1.2" + +lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + lower-case@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" @@ -7394,6 +7604,13 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +no-case@^2.2.0, no-case@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + no-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" @@ -7910,6 +8127,13 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" +param-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= + dependencies: + no-case "^2.2.0" + param-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" @@ -7977,6 +8201,14 @@ parseurl@~1.3.2, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +pascal-case@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-2.0.1.tgz#2d578d3455f660da65eca18ef95b4e0de912761e" + integrity sha1-LVeNNFX2YNpl7KGO+VtODekSdh4= + dependencies: + camel-case "^3.0.0" + upper-case-first "^1.1.0" + pascal-case@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" @@ -7995,6 +8227,13 @@ path-browserify@0.0.1: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== +path-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5" + integrity sha1-lLgDfDctP+KQbkZbtF4l0ibo7qU= + dependencies: + no-case "^2.2.0" + path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" @@ -9990,6 +10229,14 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" +sentence-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-2.1.1.tgz#1f6e2dda39c168bf92d13f86d4a918933f667ed4" + integrity sha1-H24t2jnBaL+S0T+G1KkYkz9mftQ= + dependencies: + no-case "^2.2.0" + upper-case-first "^1.1.2" + serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" @@ -10161,6 +10408,13 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +snake-case@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" + integrity sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8= + dependencies: + no-case "^2.2.0" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -10427,6 +10681,11 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +string-hash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -10686,6 +10945,14 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" +swap-case@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-1.1.2.tgz#c39203a4587385fad3c850a0bd1bcafa081974e3" + integrity sha1-w5IDpFhzhfrTyFCgvRvK+ggZdOM= + dependencies: + lower-case "^1.1.1" + upper-case "^1.1.1" + symbol-observable@^1.0.3, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -10809,6 +11076,14 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +title-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa" + integrity sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o= + dependencies: + no-case "^2.2.0" + upper-case "^1.0.3" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10910,6 +11185,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^1.9.3: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -11093,6 +11373,18 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +upper-case-first@^1.1.0, upper-case-first@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" + integrity sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU= + dependencies: + upper-case "^1.1.1" + +upper-case@^1.0.3, upper-case@^1.1.0, upper-case@^1.1.1, upper-case@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"