From ded030c50481339d28e438a9fa853cc868551f94 Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Sat, 6 Apr 2024 17:10:09 -0600 Subject: [PATCH 1/9] fix(movex-react-local-master): :bug: fix the bndResource --- .../src/lib/MovexLocalMasterProvider.tsx | 2 +- .../src/lib/MovexLocalProvider.tsx | 31 ++++++++++++++++--- libs/movex-react/src/index.ts | 1 + 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx b/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx index db648fa7..3eb7a533 100644 --- a/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx +++ b/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx @@ -8,7 +8,7 @@ import { type GetReducerState, type BaseMovexDefinitionResourcesMap, type MovexDefinition, -} from 'movex-core-util'; +} from 'movex-core-util'; import { MemoryMovexStore, type MovexStoreItem } from 'movex-store'; import { MovexMasterServer, initMovexMaster } from 'movex-master'; import { MovexLocalContext, MovexLocalContextProps } from './MovexLocalContext'; diff --git a/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx b/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx index 720bc0f4..7cefed12 100644 --- a/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx +++ b/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx @@ -2,9 +2,11 @@ import React from 'react'; import { invoke, ConnectionToClient, - type MovexClient, + type MovexClient as MovexClientUser, type MovexDefinition, type BaseMovexDefinitionResourcesMap, + StringKeys, + ResourceIdentifier, } from 'movex-core-util'; import { MovexMasterServer, @@ -12,13 +14,18 @@ import { orchestrateDefinedMovex, getUuid, // This can actually be mocked here as it's just client only! } from 'movex-master'; -import { MovexReactContext, MovexReactContextProps } from 'movex-react'; +import { MovexClient } from 'movex'; +import { + MovexReactContext, + MovexReactContextProps, + MovexResourceObservablesRegistry, +} from 'movex-react'; import { MovexLocalContextConsumerProvider } from './MovexLocalContextConsumer'; type Props = React.PropsWithChildren<{ movexDefinition: MovexDefinition; - clientId?: MovexClient['id']; + clientId?: MovexClientUser['id']; onConnected?: ( state: Extract, { connected: true }> ) => void; @@ -77,12 +84,28 @@ export class MovexLocalProvider< emitterOnMaster ); + // This resets each time movex re-initiates + const resourceRegistry = new MovexResourceObservablesRegistry( + mockedMovex.movex + ); + const nextState = { connected: true, movex: mockedMovex.movex, clientId: mockedMovex.movex.getClientId(), movexDefinition: this.props.movexDefinition, - bindResource: () => () => {}, // Added this on Apr 01 2024, w/o testing - not sure it doesn't create more issues but needed it for tsc + bindResource: >( + rid: ResourceIdentifier, + onStateUpdate: (p: MovexClient.MovexBoundResource) => void + ) => { + const $resource = resourceRegistry.register(rid); + + onStateUpdate(new MovexClient.MovexBoundResource($resource)); + + return $resource.onUpdate(() => { + onStateUpdate(new MovexClient.MovexBoundResource($resource)); + }); + }, } as const; this.setState({ diff --git a/libs/movex-react/src/index.ts b/libs/movex-react/src/index.ts index d4507102..dca97359 100644 --- a/libs/movex-react/src/index.ts +++ b/libs/movex-react/src/index.ts @@ -8,5 +8,6 @@ export { } from './lib/MovexContext'; export { MovexBoundResourceComponent as MovexBoundResource } from './lib/MovexBoundResourceComponent'; +export { ResourceObservablesRegistry as MovexResourceObservablesRegistry } from './lib/ResourceObservableRegistry'; export * from './lib/MovexConnection'; export * from './lib/hooks'; From c1a980944e0ddecfbc64d70199f42cb002f17b54 Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Sat, 6 Apr 2024 20:03:21 -0600 Subject: [PATCH 2/9] feat(movex-demo): :sparkles: add an RPS game using the movex-react-local-master --- apps/movex-demo/pages/local/rps-v2/Game.tsx | 109 ++++++++ apps/movex-demo/pages/local/rps-v2/GameUI.tsx | 263 ++++++++++++++++++ apps/movex-demo/pages/local/rps-v2/index.tsx | 22 ++ .../pages/local/rps-v2/movex.config.ts | 10 + apps/movex-demo/pages/local/rps-v2/movex.ts | 241 ++++++++++++++++ 5 files changed, 645 insertions(+) create mode 100644 apps/movex-demo/pages/local/rps-v2/Game.tsx create mode 100644 apps/movex-demo/pages/local/rps-v2/GameUI.tsx create mode 100644 apps/movex-demo/pages/local/rps-v2/index.tsx create mode 100644 apps/movex-demo/pages/local/rps-v2/movex.config.ts create mode 100644 apps/movex-demo/pages/local/rps-v2/movex.ts diff --git a/apps/movex-demo/pages/local/rps-v2/Game.tsx b/apps/movex-demo/pages/local/rps-v2/Game.tsx new file mode 100644 index 00000000..2f059ec6 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/Game.tsx @@ -0,0 +1,109 @@ +import * as React from "react"; +import { MovexBoundResource } from "movex-react"; +import movexConfig from "./movex.config"; +import { initialState } from "./movex"; +import { useState } from "react"; +import { ResourceIdentifier, toResourceIdentifierStr } from "movex-core-util"; +import { GameUI } from "./GameUI"; +// import { MovexStoreItem } from "movex"; +import { MovexLocalInstance } from 'movex-react-local-master'; +import { MovexStoreItem } from 'movex-store'; + +type Props = { + masterStore?: MovexStoreItem; +}; + +export function Game(props: Props) { + const [rpsRid, setRpsRid] = useState>(); + const [masterStateUpdated, setMasterStateUpdated] = useState(false); + + return ( +
+ { + const reg = movex.register("rps"); + + reg.create(initialState).map(({ rid }) => { + setRpsRid(rid); + + setTimeout(() => { + // HACK. Fake the Maser State Update in order to fix a current issue with working with + // master state values, instead of local ones. + // See https://github.com/movesthatmatter/movex/issues/9 for more info. + // This feature witll be added in the close future + setMasterStateUpdated(true); + }, 10); + }); + }} + > + {rpsRid && ( + <> + yesss + ( +
+ +
+ )} + /> + + )} +
+
+
+ + 🆚 + +
+ {props.masterStore && ( +
+
+

+ Master Movex State +

+
+                
+                  {JSON.stringify(
+                    {
+                      submissions: props.masterStore.state[0].submissions,
+                      winner: props.masterStore.state[0].winner
+                    },
+                    null,
+                    1
+                  )}
+                
+              
+
+
+ )} +
+ {rpsRid && masterStateUpdated && ( + + here 2 + ( +
+ yess works + +
+ )} + /> +
+ )} +
+ ); +} diff --git a/apps/movex-demo/pages/local/rps-v2/GameUI.tsx b/apps/movex-demo/pages/local/rps-v2/GameUI.tsx new file mode 100644 index 00000000..98f92ab7 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/GameUI.tsx @@ -0,0 +1,263 @@ +import React from "react"; +import { MovexBoundResourceFromConfig } from "movex-react"; +import { useCallback, useEffect, useMemo } from "react"; +import { logsy } from "movex-core-util"; +import movexConfig from "./movex.config"; +import { + PlayerLabel, + toOppositeLabel, + RPS, + selectAvailableLabels +} from "./movex"; + +type Props = { + boundResource: MovexBoundResourceFromConfig< + typeof movexConfig["resources"], + "rps" + >; + userId: string; + buttonClassName?: string; + containerClassName?: string; + backgroundColor?: string; +}; + +export const GameUI: React.FC = ({ + boundResource, + userId, + ...props +}) => { + const { state, dispatch, dispatchPrivate } = boundResource; + const { players } = state; + + const myPlayerLabel = useMemo((): PlayerLabel | undefined => { + if (!(players.playerA && players.playerB)) { + return undefined; + } + + if (players.playerA.id === userId) { + return "playerA"; + } + + if (players.playerB.id === userId) { + return "playerB"; + } + + return undefined; + }, [players, userId]); + + const oppnentPlayerLabel = useMemo(() => { + return myPlayerLabel ? toOppositeLabel(myPlayerLabel) : undefined; + }, [myPlayerLabel]); + + // Add Player + useEffect(() => { + // TODO: here there is a major issue, as selectAvailableLables works with local state + // but it needs to check on the actual (master) state. How to solve this? + // add an api to be able to read master state seperately? + + // Or in this case change the strategy altogether, and work with master generated values, in which case + // the local optimistic state udate gets turned off by default, so that means it will wait for the real state to update. + // Kinda like a dispatchAndWait + const availableLabels = selectAvailableLabels(state); + + if ( + state.players.playerA?.id === userId || + state.players.playerB?.id === userId + ) { + return; + } + + if (availableLabels.length === 0) { + logsy.warn("Player Slots taken"); + + return; + } + + dispatch({ + type: "addPlayer", + payload: { + id: userId, + playerLabel: availableLabels[0], + atTimestamp: new Date().getTime() + } + }); + }, [userId, state, dispatch]); + + const submit = useCallback( + (play: RPS) => { + if (!myPlayerLabel) { + console.warn("Not A Player"); + return; + } + + dispatchPrivate( + { + type: "submit", + payload: { + playerLabel: myPlayerLabel, + rps: play + }, + isPrivate: true + }, + { + type: "setReadySubmission", + payload: { + playerLabel: myPlayerLabel + } + } + ); + }, + [dispatchPrivate, myPlayerLabel] + ); + + const winner = useMemo(() => { + if (!state.winner) { + return undefined; + } + + if (state.winner === "1/2") { + return "1/2"; + } + + const { + submissions: { playerA } + } = state; + + if (playerA.play === state.winner) { + return state.players.playerA.label; + } + + return state.players.playerB.label; + }, [state.winner]); + + return ( +
+
+
{userId}
+ {state.winner ? ( + <> +

+ {winner === "1/2" + ? "Draw 😫" + : winner === myPlayerLabel + ? "You Won 🎉" + : "You Lost 😢"} +

+ + + ) : ( + <> +
+
+ +
+
+ +
+
+ +
+
+
+ {oppnentPlayerLabel && + state.submissions[oppnentPlayerLabel]?.play && ( +
+ Opponent Submitted{" "} + + ⌛️⏰ + +
+ )} +
+ + )} +
+
+
+

{userId} Movex State

+
+            
+              {JSON.stringify(
+                {
+                  submissions: state.submissions,
+                  winner: state.winner
+                },
+                null,
+                1
+              )}
+            
+          
+
+
+
+ ); +}; diff --git a/apps/movex-demo/pages/local/rps-v2/index.tsx b/apps/movex-demo/pages/local/rps-v2/index.tsx new file mode 100644 index 00000000..0e7cfe30 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/index.tsx @@ -0,0 +1,22 @@ +import { useState } from 'react'; +import movexConfig from './movex.config'; +import { Game } from './Game'; +import { MovexLocalMasterProvider } from 'movex-react-local-master'; +import { MovexStoreItem } from 'movex-store'; + +export default function App() { + const [masterStore, setMasterStore] = useState>(); + + return ( + /** + * This is using the Local Provider in order to simulate + * multiple players in the same browser instance + */ + + + + ); +} diff --git a/apps/movex-demo/pages/local/rps-v2/movex.config.ts b/apps/movex-demo/pages/local/rps-v2/movex.config.ts new file mode 100644 index 00000000..d558b418 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/movex.config.ts @@ -0,0 +1,10 @@ +import { reducer as rpsReducer } from "./movex"; + +// The idea of this is so both the server and the client can read it +// Obviosuly being a node/js env helps a lot :) + +export default { + resources: { + rps: rpsReducer + } +}; diff --git a/apps/movex-demo/pages/local/rps-v2/movex.ts b/apps/movex-demo/pages/local/rps-v2/movex.ts new file mode 100644 index 00000000..2dc0c8c3 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/movex.ts @@ -0,0 +1,241 @@ +// import { Action } from "movex"; +// import { MovexClient } from "movex-core-util"; + +import { Action, MovexClient } from 'movex-core-util'; + +export type PlayerId = MovexClient["id"]; +export type Color = string; + +export type RPS = "rock" | "paper" | "scissors"; + +export const playerLabels = ["playerA", "playerB"] as const; +export type PlayerLabel = "playerA" | "playerB"; + +export type State = Game; + +export const initialState: State = { + players: { + playerA: null, + playerB: null + }, + winner: null, + submissions: { + playerA: null, + playerB: null + } +}; + +export const selectAvailableLabels = (state: State): PlayerLabel[] => { + return playerLabels.filter((l) => state.players[l] === null); +}; + +export function toOppositeLabel( + c: L +): L extends "playerA" ? "playerB" : "playerA"; +export function toOppositeLabel(l: L) { + return l === "playerA" ? "playerB" : "playerA"; +} + +export const getRPSWinner = ([a, b]: [ + RPS | "$SECRET" | null | undefined, + RPS | "$SECRET" | null | undefined +]): RPS | "1/2" | null => { + if (!a || a === "$SECRET" || !b || b === "$SECRET") { + return null; + } + + if (a === b) { + return "1/2"; + } + + if (a === "paper") { + if (b === "rock") { + return a; + } + + return b; + } else if (a === "rock") { + if (b === "scissors") { + return a; + } + + return b; + } else if (b === "paper") { + return a; + } + + return b; +}; + +type Player = { + id: PlayerId; + label: PlayerLabel; +}; + +type RevealedSubmission = { + play: RPS; +}; + +type SecretSubmission = { + play: "$SECRET"; +}; + +export type Submission = RevealedSubmission | SecretSubmission; + +export type GameInProgress = { + players: { + playerA: Player | null; + playerB: Player | null; + }; + submissions: { + playerA: Submission | null; + playerB: Submission | null; + }; + winner: null; +}; + +export type GameCompleted = { + players: { + playerA: Player; + playerB: Player; + }; + submissions: { + playerA: Submission; + playerB: Submission; + }; + winner: RPS | "1/2"; +}; + +export type Game = GameInProgress | GameCompleted; + +export type Actions = + | Action< + "addPlayer", + { + id: PlayerId; + playerLabel: PlayerLabel; + atTimestamp: number; + } + > + | Action<"playAgain"> + | Action< + "submit", + { + playerLabel: PlayerLabel; + rps: RPS; + } + > + | Action< + "setReadySubmission", + { + playerLabel: PlayerLabel; + } + >; + +export const reducer = (state = initialState, action: Actions): State => { + if (action.type === "playAgain") { + return { + ...state, + players: state.players, + winner: null, + submissions: { + playerA: null, + playerB: null + } + }; + } + + if (action.type === "addPlayer") { + // If already taken return + if (state.players[action.payload.playerLabel] !== null) { + return state; + } + + return { + ...state, + + players: { + // TODO: This is just stupid needing a recast b/c it cannot determine if the game is completed or inProgress, but at this point I care not + ...(state.players as any), + [action.payload.playerLabel]: { + label: action.payload.playerLabel, + id: action.payload.id + } + } + }; + } + + if (action.type === "submit") { + // If game is completed + if (state.winner !== null) { + return state; + } + + const oppositeLabel = toOppositeLabel(action.payload.playerLabel); + + // 1st submission + if (state.submissions[oppositeLabel] === null) { + return { + ...state, + submissions: { + ...(action.payload.playerLabel === "playerA" + ? { + playerA: { + play: action.payload.rps + }, + playerB: null + } + : { + playerB: { + play: action.payload.rps + }, + playerA: null + }) + } + }; + } else { + // final submission: game gets completed + const nextSubmission = { + ...state.submissions, + [action.payload.playerLabel]: { + play: action.payload.rps + } + }; + + const nextWinner = getRPSWinner([ + nextSubmission.playerA?.play, + nextSubmission.playerB?.play + ]); + + return { + ...state, + submissions: nextSubmission, + winner: nextWinner as any + }; + } + } else if (action.type === "setReadySubmission") { + // If game is completed + if (state.winner !== null) { + return state; + } + + const nextSubmission = { + ...state.submissions, + [action.payload.playerLabel]: { + play: "$SECRET" + } + }; + + return { + ...state, + submissions: nextSubmission + }; + } + + return state; +}; + +reducer.$canReconcileState = (state: State) => + state.submissions.playerA !== null && state.submissions.playerB !== null; + +export default reducer; From f8a80dcf2dee534d4a8fecd912e2054d3b96b72f Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Sat, 6 Apr 2024 22:24:03 -0600 Subject: [PATCH 3/9] refactor(all): :recycle: make Logsy an Event Emitter and expose in move-react MovexProvider --- libs/movex-core-util/src/lib/Logsy.ts | 135 +++++------ .../movex-core-util/src/lib/ScketIOEmitter.ts | 45 ++-- .../src/lib/MockConnectionEmitter.ts | 17 +- .../movex-master/src/lib/MovexMasterServer.ts | 83 +++---- libs/movex-master/src/lib/orchestrator.ts | 12 +- .../src/lib/MovexLocalMasterProvider.tsx | 36 +-- .../src/lib/MovexBoundResourceComponent.tsx | 3 +- libs/movex-server/src/lib/movex-server.ts | 16 +- .../lib/MemoryMovexStore/MemoryMovexStore.ts | 6 +- .../src/lib/ConnectionToMasterResource.ts | 13 - libs/movex/src/lib/MovexResource.ts | 229 ++++-------------- .../src/lib/MovexResourceObservable.spec.ts | 8 - libs/movex/src/lib/MovexResourceObservable.ts | 6 +- 13 files changed, 226 insertions(+), 383 deletions(-) diff --git a/libs/movex-core-util/src/lib/Logsy.ts b/libs/movex-core-util/src/lib/Logsy.ts index 8f3a8745..f68925c1 100644 --- a/libs/movex-core-util/src/lib/Logsy.ts +++ b/libs/movex-core-util/src/lib/Logsy.ts @@ -1,5 +1,7 @@ // const movexLogsy = Logger.get('Movex'); +import { Pubsy } from 'ts-pubsy'; + type LogsyMethods = | 'log' | 'info' @@ -9,98 +11,70 @@ type LogsyMethods = | 'groupEnd' | 'debug'; -const globalDisabled = false; - -const globalLogsyConfigWrapper = { - config: { - disabled: false, - verbose: false, - }, +export type LoggingEvent = { + method: LogsyMethods; + prefix?: string; + message?: unknown; + payload?: LogsyPayload; }; -// Depreacte from here! maybe make own library +export type LogsyPayload = Record; class Logsy { - constructor( - public prefix: string = '', - disabled = false, - private globalConfig?: typeof globalLogsyConfigWrapper - ) { - // This is needed - if (disabled) { - this.disable(); - } - } - - private handler = ( - method: LogsyMethods, - message?: unknown, - ...optionalParams: unknown[] - ) => { - if (!this.ON || globalLogsyConfigWrapper.config.disabled) { - return; - } + private pubsy = new Pubsy<{ + onLog: LoggingEvent; + }>(); - // If verbose is false, we don't want to log, info or debug - if ( - (method === 'log' || method === 'info' || method === 'debug') && - !globalLogsyConfigWrapper.config.verbose - ) { - return; - } + constructor(public prefix: string = '') {} - const prefix = this.hasGroupOpen() ? '' : this.prefix; + onLog = (fn: (event: LoggingEvent) => void) => + this.pubsy.subscribe('onLog', fn); - if (typeof message === 'string') { - console[method](prefix + ' ' + message, ...optionalParams); - } else { - console[method](prefix, message, ...optionalParams); - } + // To be overriden + // public onLog: (event: LoggingEvent) => void = (event: LoggingEvent) => { + // this.pubsy.publish('onLog', { + // ...event, + // }); + // }; + + private handler = (event: LoggingEvent) => { + const prefix = this.hasGroupOpen() ? '' : this.prefix; + this.pubsy.publish('onLog', { + ...event, + prefix, + }); + // this.onLog({ ...event, prefix }); }; - public ON: boolean = globalDisabled || true; + // public ON: boolean = globalDisabled || true; private activeGroups = 0; - enable() { - this.ON = true; - - if (this.globalConfig) { - this.globalConfig.config.disabled = false; - } - } - - disable() { - this.ON = false; - - if (this.globalConfig) { - this.globalConfig.config.disabled = true; - } - } - - log = (message?: any, ...optionalParams: any[]) => { - this.handler('log', message, ...optionalParams); + log = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'log', message, payload }); }; - info = (message?: any, ...optionalParams: any[]) => { - this.handler('info', message, ...optionalParams); + info = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'info', message, payload }); }; - warn = (message?: any, ...optionalParams: any[]) => { - this.handler('warn', message, ...optionalParams); + warn = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'warn', message, payload }); }; - error = (message?: any, ...optionalParams: any[]) => { - this.handler('error', message, ...optionalParams); + error = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'error', message, payload }); }; - group = (message?: any, ...optionalParams: any[]) => { - this.handler('group', message, ...optionalParams); + group = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'group', message, payload }); this.openGroup(); }; - groupEnd = () => { - // console.groupEnd() - this.handler('groupEnd'); + groupEnd = (message?: string, payload?: LogsyPayload) => { + if (message) { + this.handler({ method: 'log', message, payload }); + } + this.handler({ method: 'groupEnd' }); this.closeGroup(); }; @@ -118,14 +92,25 @@ class Logsy { private hasGroupOpen = () => this.activeGroups > 0; - debug = (message?: any, ...optionalParams: any[]) => { - this.handler('debug', message, ...optionalParams); + debug = (message?: any, payload?: LogsyPayload) => { + this.handler({ method: 'debug', message, payload }); }; withNamespace = (s: string) => { - return new Logsy(this.prefix + s); + const next = new Logsy(this.prefix + s); + + // next.onLog((...args) => this.onLog(...args)); + // // this.onLog(); + // this.onLog((event) => { + // this.pubsy.publish('onLog', event); + // }); + // next.onLog = (event) => this.pubsy.publish('onLog', event); + next.onLog((event) => { + this.pubsy.publish('onLog', event); + }); + + return next; }; } -export const globalLogsy = new Logsy('', false, globalLogsyConfigWrapper); -export const logsy = globalLogsy; \ No newline at end of file +export const globalLogsy = new Logsy(); diff --git a/libs/movex-core-util/src/lib/ScketIOEmitter.ts b/libs/movex-core-util/src/lib/ScketIOEmitter.ts index 7028621c..4f93cfc0 100644 --- a/libs/movex-core-util/src/lib/ScketIOEmitter.ts +++ b/libs/movex-core-util/src/lib/ScketIOEmitter.ts @@ -1,5 +1,5 @@ import { Err, Ok } from 'ts-results'; -import { logsy } from './Logsy'; +import { globalLogsy } from './Logsy'; import type { Socket as ServerSocket } from 'socket.io'; import type { Socket as ClientSocket } from 'socket.io-client'; import type { EventMap } from 'typed-emitter'; @@ -8,18 +8,17 @@ import type { UnsubscribeFn, WsResponseResultPayload } from './core-types'; export type SocketIO = ServerSocket | ClientSocket; +const logsy = globalLogsy.withNamespace('[SocketIOEmitter]'); + export class SocketIOEmitter implements EventEmitter { - private logger: typeof console; constructor( private socket: SocketIO, private config: { - logger?: typeof console; waitForResponseMs?: number; } = {} ) { - this.logger = config.logger || console; this.config.waitForResponseMs = this.config.waitForResponseMs || 15 * 1000; } @@ -67,7 +66,7 @@ export class SocketIOEmitter acknowledgeCb?: (response: ReturnType) => void ): boolean { const reqId = `${event as string}(${String(Math.random()).slice(-3)})`; - logsy.debug('[ServerSocketEmitter]', reqId, 'Emit:', event, request); + logsy.debug('Emit', { reqId, event, request }); this.socket.emit( event as string, @@ -76,10 +75,10 @@ export class SocketIOEmitter withTimeout( (res: WsResponseResultPayload) => { if (res.ok) { - logsy.debug('[ServerSocketEmitter]', reqId, 'Response Ok:', res); + logsy.debug('Emit Response Ok', { reqId, event, res }); acknowledgeCb(new Ok(res.val) as ReturnType); } else { - logsy.warn('[ServerSocketEmitter]', reqId, 'Response Err:', res); + logsy.debug('Emit Response Err', { reqId, event, res }); acknowledgeCb(new Err(res.val) as ReturnType); } }, @@ -106,13 +105,11 @@ export class SocketIOEmitter ): Promise> { return new Promise((resolve, reject) => { const reqId = `${event as string}(${String(Math.random()).slice(-3)})`; - logsy.debug( - '[ServerSocketEmitter]', + logsy.debug('EmitAndAcknowledge', { reqId, - 'EmitAndAcknowledge:', event, - request - ); + request, + }); this.socket.emit( event as string, @@ -120,20 +117,30 @@ export class SocketIOEmitter withTimeout( (res: WsResponseResultPayload) => { if (res.ok) { - logsy.debug('[ServerSocketEmitter]', reqId, 'Response Ok:', res); + logsy.debug('EmitAndAcknowledge Response Ok', { + reqId, + res, + request, + event, + }); resolve(new Ok(res.val)); } else { - logsy.warn('[ServerSocketEmitter]', reqId, 'Response Err:', res); + logsy.debug('EmitAndAcknowledge Response Err', { + reqId, + res, + request, + event, + }); reject(new Err(res.val)); } }, () => { - logsy.warn( - '[ServerSocketEmitter]', + logsy.error('EmitAndAcknowledge Request Timeout', { + reqId, + request, event, - 'Request Timeout:', - request - ); + }); + // TODO This error could be typed better using a result error reject(new Err('RequestTimeout')); }, diff --git a/libs/movex-master/src/lib/MockConnectionEmitter.ts b/libs/movex-master/src/lib/MockConnectionEmitter.ts index ee8bde91..c2bb1fa9 100644 --- a/libs/movex-master/src/lib/MockConnectionEmitter.ts +++ b/libs/movex-master/src/lib/MockConnectionEmitter.ts @@ -1,13 +1,16 @@ -import type { +import { AnyAction, EventEmitter, UnsubscribeFn, IOEvents, -} from 'movex-core-util'; + globalLogsy, +} from 'movex-core-util'; import { Pubsy } from 'ts-pubsy'; import { getRandomInt, getUuid } from './util'; +const logsy = globalLogsy.withNamespace('MockConnectionEmitter'); + export class MockConnectionEmitter< TState extends any = any, TAction extends AnyAction = AnyAction, @@ -163,14 +166,12 @@ export class MockConnectionEmitter< // TODO: Need a way for this to call the unsubscriber this.ackPubsy.subscribe(ackId, (ackMsg) => { - console.log( - '[MockConnectionEmitter] emit', + logsy.log('Emit', { event, request, - 'response:', - ackMsg - ); - console.trace('[MockConnectionEmitter] Trace', event, request); + response: ackMsg, + }); + acknowledgeCb( ackMsg as ReturnType[E]> ); diff --git a/libs/movex-master/src/lib/MovexMasterServer.ts b/libs/movex-master/src/lib/MovexMasterServer.ts index fd2aa564..9d100412 100644 --- a/libs/movex-master/src/lib/MovexMasterServer.ts +++ b/libs/movex-master/src/lib/MovexMasterServer.ts @@ -99,7 +99,10 @@ export class MovexMasterServer { // Forwardable objectKeys(peerActions.byClientId).forEach((peerId) => { if (!peerActions.byClientId[peerId]) { - logsy.error('Inexistant Peer Connection for peerId:', peerId); + logsy.error('Inexistant Peer Connection for peerId:', { + peerId, + peerActionsByClientId: peerActions.byClientId, + }); return; } @@ -261,11 +264,10 @@ export class MovexMasterServer { const peerConnection = this.clientConnectionsByClientId[peerId]; if (!peerConnection) { - logsy.error( - 'onAddResourceSubscriber: Peer Connection not found for', + logsy.error('OnAddResourceSubscriber PeerConnectionNotFound', { peerId, - this.clientConnectionsByClientId - ); + clientId: this.clientConnectionsByClientId, + }); return; } @@ -312,12 +314,10 @@ export class MovexMasterServer { >, }; - logsy.log( - '[MovexMasterServer] Added Connection Succesfully:', - clientConnection.clientId, - '| Connections', - Object.keys(this.clientConnectionsByClientId).length - ); + logsy.info('Connection Added Succesfully', { + clientId: clientConnection.clientId, + connectionsCount: Object.keys(this.clientConnectionsByClientId).length, + }); // Unsubscribe return () => { @@ -343,39 +343,34 @@ export class MovexMasterServer { this.clientConnectionsByClientId = restOfConnections; - logsy.log( - '[MovexMasterServer] Removed Connection Succesfully for Client:', + logsy.info('Connection Removed', { clientId, - '| Connections Left:', - Object.keys(this.clientConnectionsByClientId).length - ); + connectionsLeft: Object.keys(this.clientConnectionsByClientId).length, + }); } private unsubscribeClientFromResources(clientId: MovexClient['id']) { - const clientSubscricptions = this.subscribersToRidsMap[clientId]; + const clientSubscriptions = this.subscribersToRidsMap[clientId]; - if (!clientSubscricptions) { - logsy.log('No Resource Subscription'); - return; - } - - const subscribedRidsList = objectKeys(clientSubscricptions); + if (clientSubscriptions) { + const subscribedRidsList = objectKeys(clientSubscriptions); - subscribedRidsList.forEach(async (rid) => { - await this.removeResourceSubscriberAndNotifyPeersOfClientUnsubscription( - rid, - clientId - ); + subscribedRidsList.forEach(async (rid) => { + await this.removeResourceSubscriberAndNotifyPeersOfClientUnsubscription( + rid, + clientId + ); - // TODO: should this wait for the client to ack or smtg? probably not needed + // TODO: should this wait for the client to ack or smtg? probably not needed - // Remove the rid from the client's record - const { [rid]: removed, ...rest } = this.subscribersToRidsMap[clientId]; + // Remove the rid from the client's record + const { [rid]: removed, ...rest } = this.subscribersToRidsMap[clientId]; - this.subscribersToRidsMap[clientId] = rest; - }); + this.subscribersToRidsMap[clientId] = rest; + }); + } - logsy.log('Removed client subscriptions ok'); + logsy.info('Client Subscriptions Removed', { clientId }); } private async removeResourceSubscriberAndNotifyPeersOfClientUnsubscription( @@ -387,7 +382,10 @@ export class MovexMasterServer { const masterResource = this.masterResourcesByType[resourceType]; if (!masterResource) { - logsy.error('MasterResourceInexistent', resourceType); + logsy.error( + 'RemoveResourceSubscriberAndNotifyPeersOfClientUnsubscription MasterResourceInexistent', + { resourceType } + ); return; } @@ -399,13 +397,6 @@ export class MovexMasterServer { await masterResource .getSubscribers(rid) .map((clientIds) => { - console.log( - 'notifyPeersOfClientUnsubscription resource', - rid, - 'subscribers', - clientIds - ); - objectKeys(clientIds) .filter((clientId) => clientId !== subscriberClientId) .forEach((peerId) => { @@ -413,9 +404,11 @@ export class MovexMasterServer { if (!peerConnection) { logsy.error( - 'notifyPeersOfClientUnsubscription Peer Connection not found for', - peerId, - this.clientConnectionsByClientId + 'RemoveResourceSubscriberAndNotifyPeersOfClientUnsubscription Peer Connection not found for', + { + peerId, + resourceType, + } ); return; } diff --git a/libs/movex-master/src/lib/orchestrator.ts b/libs/movex-master/src/lib/orchestrator.ts index 7d033aa0..3ef989d5 100644 --- a/libs/movex-master/src/lib/orchestrator.ts +++ b/libs/movex-master/src/lib/orchestrator.ts @@ -2,19 +2,21 @@ import { MovexClient, ResourceIdentifier, invoke, - logsy, + globalLogsy, AnyAction, MovexReducer, ConnectionToMaster, BaseMovexDefinitionResourcesMap, MovexDefinition, ConnectionToClient, -} from 'movex-core-util'; +} from 'movex-core-util'; import { Movex, MovexFromDefintion } from 'movex'; import { MovexMasterResource, MovexMasterServer } from 'movex-master'; import { MemoryMovexStore } from 'movex-store'; import { MockConnectionEmitter } from './MockConnectionEmitter'; +const logsy = globalLogsy.withNamespace('[MovexClientMasterOrchestrator]'); + export const movexClientMasterOrchestrator = () => { let unsubscribe = async () => {}; @@ -136,13 +138,13 @@ export const orchestrateDefinedMovex = < const unsubscribers = [ emitterOnClient._onEmitted((r, ackCb) => { - logsy.log('[Orchestrator] emitterOnClient _onEmitted', r); + logsy.debug('EmitterOnClient _onEmitted', r); // Calling the master with the given event from the client in order to process it emitterOnMaster._publish(r.event, r.payload, ackCb); }), emitterOnMaster._onEmitted((r, ackCb) => { - logsy.log('[Orchestrator] emitterOnMaster _onEmitted', r); - // Calling the client with the given event from the client in order to process it + logsy.debug('EmitterOnMaster _onEmitted', r); + // Calling the client with the given event from the master in order to process it emitterOnClient._publish(r.event, r.payload, ackCb); }), ]; diff --git a/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx b/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx index 3eb7a533..11dcdb1a 100644 --- a/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx +++ b/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx @@ -1,18 +1,21 @@ import React, { useEffect, useState } from 'react'; import { invoke, - logsy, + globalLogsy, noop, emptyFn, type StringKeys, type GetReducerState, type BaseMovexDefinitionResourcesMap, type MovexDefinition, + LoggingEvent, } from 'movex-core-util'; import { MemoryMovexStore, type MovexStoreItem } from 'movex-store'; import { MovexMasterServer, initMovexMaster } from 'movex-master'; import { MovexLocalContext, MovexLocalContextProps } from './MovexLocalContext'; +const logsy = globalLogsy.withNamespace('[MovexLocalMasterProvider]'); + type Props = React.PropsWithChildren<{ movexDefinition: MovexDefinition; @@ -26,6 +29,9 @@ type Props = >, updateKind: 'create' | 'update' ) => void; + logger?: { + onLog: (event: LoggingEvent) => void; + }; }>; /** @@ -36,7 +42,7 @@ type Props = */ export const MovexLocalMasterProvider: React.FC< Props -> = ({ onInit = noop, onMasterResourceUpdated = noop, ...props }) => { +> = ({ onInit = noop, onMasterResourceUpdated = noop, logger, ...props }) => { const [contextState, setContextState] = useState(); useEffect(() => { @@ -59,21 +65,15 @@ export const MovexLocalMasterProvider: React.FC< >(); const unsubscribers = [ - localStore.onCreated((s) => { - logsy.group('[Master.LocalStore] onCreated'); - logsy.log('Item', s); - logsy.log('All Store', localStore.all()); - logsy.groupEnd(); + localStore.onCreated((item) => { + logsy.info('onCreated', { item }); - onMasterResourceUpdated(s as any, 'create'); + onMasterResourceUpdated(item as any, 'create'); }), - localStore.onUpdated((s) => { - logsy.group('[Master.LocalStore] onUpdated'); - logsy.log('Item', s); - logsy.log('All Store', localStore.all()); - logsy.groupEnd(); + localStore.onUpdated((item) => { + logsy.info('onUpdated', { item }); - onMasterResourceUpdated(s as any, 'update'); + onMasterResourceUpdated(item as any, 'update'); }), ]; @@ -95,6 +95,14 @@ export const MovexLocalMasterProvider: React.FC< } }, [contextState]); + useEffect(() => { + if (logger) { + return globalLogsy.onLog(logger.onLog); + } + + return () => {}; + }, [logger]); + if (!contextState) { return null; } diff --git a/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx b/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx index 381a483f..a4c639ee 100644 --- a/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx +++ b/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { MovexClient } from 'movex'; -import type { +import { GetReducerState, GetReducerAction, ResourceIdentifier, @@ -9,7 +9,6 @@ import type { BaseMovexDefinitionResourcesMap, MovexDefinition, UnsubscribeFn, - DistributiveOmit, } from 'movex-core-util'; import { invoke, isSameResourceIdentifier } from 'movex-core-util'; import { MovexContextStateChange } from './MovexContextStateChange'; diff --git a/libs/movex-server/src/lib/movex-server.ts b/libs/movex-server/src/lib/movex-server.ts index 8d8e97ed..b14bc6df 100644 --- a/libs/movex-server/src/lib/movex-server.ts +++ b/libs/movex-server/src/lib/movex-server.ts @@ -3,7 +3,7 @@ import { Server as SocketServer } from 'socket.io'; import express from 'express'; import cors from 'cors'; import { - logsy, + globalLogsy, SocketIOEmitter, ConnectionToClient, type MovexDefinition, @@ -16,6 +16,8 @@ import { MemoryMovexStore, MovexStore } from 'movex-store'; import { initMovexMaster } from 'movex-master'; import { isOneOf } from './util'; +const logsy = globalLogsy.withNamespace('[MovexServer]'); + export const movexServer = ( { httpServer = http.createServer(), @@ -53,7 +55,7 @@ export const movexServer = ( socket.on('connection', (io) => { const clientId = getClientId(io.handshake.query['clientId'] as string); - logsy.log('[MovexServer] Client Connected', clientId); + logsy.info('Client Connected', { clientId }); const connectionToClient = new ConnectionToClient( clientId, @@ -65,7 +67,7 @@ export const movexServer = ( movexMaster.addClientConnection(connectionToClient); io.on('disconnect', () => { - logsy.log('[MovexServer] Client Disconnected', clientId); + logsy.info('Client Disconnected', { clientId }); movexMaster.removeConnection(clientId); }); @@ -133,10 +135,10 @@ export const movexServer = ( const address = httpServer.address(); if (typeof address !== 'string') { - logsy.info( - `Movex Server started on port ${address?.port} for definition`, - Object.keys(definition.resources) - ); + logsy.info('Server started', { + port, + definitionResources: Object.keys(definition.resources), + }); } }); }; diff --git a/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts b/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts index c2f0774e..d027f2fe 100644 --- a/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts +++ b/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts @@ -31,7 +31,7 @@ import type { } from '../MovexStore'; import { toResultError } from '../ResultError'; -const logsy = globalLogsy.withNamespace('[LocalMovexStore]'); +const logsy = globalLogsy.withNamespace('[MemoryMovexStore]'); export class MemoryMovexStore< TResourcesMap extends BaseMovexDefinitionResourcesMap = BaseMovexDefinitionResourcesMap @@ -304,8 +304,8 @@ export class MemoryMovexStore< ) ); }).mapErr( - AsyncResult.passThrough((e) => { - logsy.error('Update Error', e, rid); + AsyncResult.passThrough((error) => { + logsy.error('Update Error', { error, rid }); }) ); } diff --git a/libs/movex/src/lib/ConnectionToMasterResource.ts b/libs/movex/src/lib/ConnectionToMasterResource.ts index e0fd303e..dbae0148 100644 --- a/libs/movex/src/lib/ConnectionToMasterResource.ts +++ b/libs/movex/src/lib/ConnectionToMasterResource.ts @@ -89,13 +89,7 @@ export class ConnectionToMasterResource< rid: ResourceIdentifier; clientId: MovexClient['id']; }) => { - console.log('ConnectionToMaster onRemoveResourceSubscriberHandler', p); if (toResourceIdentifierObj(p.rid).resourceType !== resourceType) { - console.log( - 'ConnectionToMaster onRemoveResourceSubscriberHandler', - 'nooooope', - p - ); return; } @@ -108,14 +102,7 @@ export class ConnectionToMasterResource< rid: ResourceIdentifier; clientId: MovexClient['id']; }) => { - console.log('ConnectionToMaster onAddResourceSubscriberHandler', p); - if (toResourceIdentifierObj(p.rid).resourceType !== resourceType) { - console.log( - 'ConnectionToMaster onAddResourceSubscriberHandler', - p, - 'noooope' - ); return; } diff --git a/libs/movex/src/lib/MovexResource.ts b/libs/movex/src/lib/MovexResource.ts index ab3bbad1..d78f391b 100644 --- a/libs/movex/src/lib/MovexResource.ts +++ b/libs/movex/src/lib/MovexResource.ts @@ -2,34 +2,22 @@ import type { ResourceIdentifier, ResourceIdentifierStr, UnsubscribeFn, - ActionWithAnyPayload, AnyAction, CheckedReconciliatoryActions, MovexReducer, IOConnection, } from 'movex-core-util'; import { - logsy as rawLogsy, + globalLogsy, toResourceIdentifierObj, toResourceIdentifierStr, - isAction, invoke, } from 'movex-core-util'; import { ConnectionToMasterResource } from './ConnectionToMasterResource'; import { MovexResourceObservable } from './MovexResourceObservable'; import * as deepObject from 'deep-object-diff'; -// TODO: Take away from here as it's adding to the size -const logUnimportantStyle = 'color: grey;'; -const logImportantStyle = 'font-weight: bold;'; -const logIncomingStyle = 'color: #4CAF50; font-weight: bold;'; -const logOutgoingStyle = 'color: #1EA7FD; font-weight: bold;'; -const logOpenConnectionStyle = 'color: #EF5FA0; font-weight: bold'; -const logClosedConnectionStyle = 'color: #DF9D04; font-weight: bold'; -const logErrorStyle = 'color: red; font-weight: bold;'; - -const logsy = rawLogsy.withNamespace('[Movex][MovexResource]'); -// const logsy = rawLogsy; +const logsy = globalLogsy.withNamespace('[Movex][MovexResource]'); export class MovexResource< S, @@ -120,21 +108,14 @@ export class MovexResource< const prevCheckedState = resourceObservable.state; return syncLocalState().map((masterCheckState) => { - logsy.group('State Resynch-ed Warning'); - logsy.log( - '%cPrev (Local) State', - logUnimportantStyle, - prevCheckedState - ); - logsy.log('%cNext (Master) State', logIncomingStyle, masterCheckState); - logsy.debug( - 'Diff', - deepObject.detailedDiff(prevCheckedState, masterCheckState) - ); - logsy.warn( + logsy.warn('State Resynch-ed', { + prevCheckedState, + masterCheckState, + diff: deepObject.detailedDiff(prevCheckedState, masterCheckState), + }); + console.warn( "This shouldn't happen too often! If it does, make sure there's no way around it! See this for more https://github.com/movesthatmatter/movex/issues/8" ); - logsy.groupEnd(); return masterCheckState; }); @@ -151,66 +132,37 @@ export class MovexResource< resourceObservable.syncState(res.state); resourceObservable.updateSubscribers(res.subscribers); }) - .mapErr((e) => { - logsy.error('Add Resource Subscriber Error', e); + .mapErr((error) => { + logsy.error('Add Resource Subscriber Error', { error }); }); const onReconciliateActionsHandler = ( p: CheckedReconciliatoryActions ) => { const prevState = resourceObservable.getCheckedState(); - - logsy.group( - `%c\u{25BC} %cReconciliatory Actions Received (${p.actions.length}). FinalCheckum ${p.finalChecksum}`, - logIncomingStyle, - logUnimportantStyle, - 'Client:', - this.connectionToMaster.clientId - ); - logsy.log('%cPrev state', logUnimportantStyle, prevState[0]); - const nextState = resourceObservable.applyMultipleActions( p.actions ).checkedState; - p.actions.forEach((action, i) => { - logsy.log( - `%cAction(${i + 1}/${p.actions.length}): %c${action.type}`, - logOutgoingStyle, - logImportantStyle, - (action as ActionWithAnyPayload).payload, - action - ); + logsy.log('Reconciliatory Actions Received', { + ...p, + actionsCount: p.actions.length, + clientId: this.connectionToMaster.clientId, + nextState, + prevState, }); - logsy.log('%cNextState', logIncomingStyle, nextState[0]); - logsy.log( - '%cChecksums', - logUnimportantStyle, - prevState[1], - '>', - nextState[1] - ); - - if (nextState[1] === p.finalChecksum) { - logsy.log( - '%cFinal Checksum Matches', - logUnimportantStyle, - p.finalChecksum - ); - } else { + if (nextState[1] !== p.finalChecksum) { // If the checksums are different then it this case it's needed to resync. // See this https://github.com/movesthatmatter/movex/issues/8 resyncLocalState(); // Here is where this happens!!! - logsy.warn( - '%cLocal and Final Master Checksum Mismatch', - logErrorStyle, - nextState[1], - p.finalChecksum - ); + logsy.warn('Local and Final Master Checksum Mismatch', { + ...p, + nextState: nextState[1], + }); } logsy.groupEnd(); @@ -245,33 +197,23 @@ export class MovexResource< // When the checksums are not the same, need to resync the state! // this is expensive and ideally doesn't happen too much. - logsy.group( - `[Movex] Dispatch Ack Error: "Checksums MISMATCH"\n`, - `client: '${this.connectionToMaster.clientId}',\n`, - 'action:', - action - ); + logsy.error(`Dispatch Ack Error: "Checksums MISMATCH"`, { + action, + clientId: this.connectionToMaster.clientId, + }); await resyncLocalState() .map((masterState) => { - logsy.log( - 'Master State:', - JSON.stringify(masterState[0], null, 2), - masterState[1] - ); - logsy.log( - 'Local State:', - JSON.stringify(nextLocalCheckedState[0], null, 2), - nextLocalCheckedState[1] - ); - logsy.log( - 'Diff', - deepObject.detailedDiff(masterState, nextLocalCheckedState) - ); + logsy.info('Resynched Result', { + masterState, + nextLocalCheckedState, + diff: deepObject.detailedDiff( + masterState, + nextLocalCheckedState + ), + }); }) .resolve(); - - logsy.groupEnd(); }); } ), @@ -282,35 +224,12 @@ export class MovexResource< const nextState = resourceObservable.getCheckedState(); - logsy.group( - `%c\u{25BC} %cForwarded Action Received: %c${p.action.type}`, - logIncomingStyle, - logUnimportantStyle, - logImportantStyle, - 'Client:', - this.connectionToMaster.clientId - ); - logsy.log('%cPrev State', logUnimportantStyle, prevState[0]); - logsy.log( - `%cAction: %c${p.action.type}`, - logOutgoingStyle, - logImportantStyle, - (p.action as ActionWithAnyPayload).payload - ); - logsy.log('%cNext State', logIncomingStyle, nextState[0]); - if (prevState[1] !== nextState[1]) { - logsy.log( - '%cchecksums', - logUnimportantStyle, - prevState[1], - '>', - nextState[1] - ); - } else { - logsy.log('%cNo Diff', logErrorStyle, prevState[1]); - } - - logsy.groupEnd(); + logsy.info('Forwarded Action Received', { + ...p, + clientId: this.connectionToMaster.clientId, + prevState, + nextState, + }); }), this.connectionToMasterResource.onReconciliatoryActions( rid, @@ -319,7 +238,7 @@ export class MovexResource< // Subscribers this.connectionToMasterResource.onSubscriberAdded(rid, (clientId) => { - logsy.log('Subscriber Added', clientId); + logsy.info('Subscriber Added', { clientId }); resourceObservable.updateSubscribers((prev) => ({ ...prev, @@ -327,7 +246,7 @@ export class MovexResource< })); }), this.connectionToMasterResource.onSubscriberRemoved(rid, (clientId) => { - logsy.log('Subscriber Removed', clientId); + logsy.info('Subscriber Removed', { clientId }); resourceObservable.updateSubscribers((prev) => { const { [clientId]: removed, ...rest } = prev; @@ -351,66 +270,12 @@ export class MovexResource< next: nextLocalCheckedState, prev: prevLocalCheckedState, }) => { - const [nextLocalState, nextLocalChecksum] = nextLocalCheckedState; - - logsy.group( - `%c\u{25B2} %cAction Dispatched: %c${ - isAction(action) - ? action.type - : action[0].type + ' + ' + action[1].type - }`, - logOutgoingStyle, - logUnimportantStyle, - logImportantStyle, - 'Client:', - this.connectionToMaster.clientId - ); - logsy.log( - '%cPrev state', - logUnimportantStyle, - prevLocalCheckedState[0] - ); - if (isAction(action)) { - logsy.log( - `%cPublic Action: %c${action.type}`, - logOutgoingStyle, - logImportantStyle, - (action as ActionWithAnyPayload).payload - ); - } else { - const [privateAction, publicAction] = action; - logsy.log( - `%cPrivate Action: %c${privateAction.type}`, - logOpenConnectionStyle, - logImportantStyle, - (privateAction as ActionWithAnyPayload).payload - ); - logsy.log( - `%cPublic Action payload: %c${publicAction.type}`, - logOutgoingStyle, - logImportantStyle, - (publicAction as ActionWithAnyPayload).payload - ); - } - logsy.log('%cNext State', logIncomingStyle, nextLocalState); - - if (prevLocalCheckedState[1] !== nextLocalChecksum) { - logsy.log( - '%cChecksums', - logUnimportantStyle, - prevLocalCheckedState[1], - '>', - nextLocalChecksum - ); - } else { - logsy.log( - '%cNo Diff', - logUnimportantStyle, - prevLocalCheckedState[1] - ); - } - - logsy.groupEnd(); + logsy.info('Action Dispatched', { + action, + clientId: this.connectionToMaster.clientId, + prevState: prevLocalCheckedState, + nextLocalState: nextLocalCheckedState, + }); } ), ]; diff --git a/libs/movex/src/lib/MovexResourceObservable.spec.ts b/libs/movex/src/lib/MovexResourceObservable.spec.ts index 98de4d36..16bc56d0 100644 --- a/libs/movex/src/lib/MovexResourceObservable.spec.ts +++ b/libs/movex/src/lib/MovexResourceObservable.spec.ts @@ -13,14 +13,6 @@ import { const rid: ResourceIdentifier = 'counter:test-id'; -beforeAll(() => { - globalLogsy.disable(); -}); - -afterAll(() => { - globalLogsy.enable(); -}); - test('Dispatch Local Actions', async () => { const $resource = new MovexResourceObservable( 'test-client', diff --git a/libs/movex/src/lib/MovexResourceObservable.ts b/libs/movex/src/lib/MovexResourceObservable.ts index 8256d845..96a7aa28 100644 --- a/libs/movex/src/lib/MovexResourceObservable.ts +++ b/libs/movex/src/lib/MovexResourceObservable.ts @@ -2,7 +2,7 @@ import { Pubsy } from 'ts-pubsy'; import { Err, Ok, Result } from 'ts-results'; import { invoke, - logsy, + globalLogsy, computeCheckedState, isAction, Observable, @@ -24,6 +24,8 @@ import type { import { createDispatcher, DispatchedEvent } from './dispatch'; import { PromiseDelegate } from 'promise-delegate'; +const logsy = globalLogsy.withNamespace('[MovexResourceObservable]'); + type ObservedItem = { subscribers: Record; checkedState: CheckedState; @@ -106,7 +108,7 @@ export class MovexResourceObservable< this.dispatcher = (...args: Parameters) => { if (!this.isSynchedPromiseDelegate.settled) { - logsy.info('[Movex] Attempt to dispatch before sync!', ...args); + logsy.warn('Attempt to dispatch before sync!', { args }); } this.isSynchedPromiseDelegate.promise.then(() => { From 96b19aef1178ae74f6fcc72fa2c593035df41b07 Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Sat, 6 Apr 2024 22:25:28 -0600 Subject: [PATCH 4/9] feat(rps-v2): :sparkles: hookup a logger to the MovexLocalProvider --- apps/movex-demo/pages/local/rps-v2/Game.tsx | 27 ++++++++----------- apps/movex-demo/pages/local/rps-v2/GameUI.tsx | 4 +-- apps/movex-demo/pages/local/rps-v2/index.tsx | 6 +++++ libs/movex-react/src/lib/MovexProvider.tsx | 15 ++++++++++- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/apps/movex-demo/pages/local/rps-v2/Game.tsx b/apps/movex-demo/pages/local/rps-v2/Game.tsx index 2f059ec6..5e7612a0 100644 --- a/apps/movex-demo/pages/local/rps-v2/Game.tsx +++ b/apps/movex-demo/pages/local/rps-v2/Game.tsx @@ -1,10 +1,10 @@ -import * as React from "react"; -import { MovexBoundResource } from "movex-react"; -import movexConfig from "./movex.config"; -import { initialState } from "./movex"; -import { useState } from "react"; -import { ResourceIdentifier, toResourceIdentifierStr } from "movex-core-util"; -import { GameUI } from "./GameUI"; +import * as React from 'react'; +import { MovexBoundResource } from 'movex-react'; +import movexConfig from './movex.config'; +import { initialState } from './movex'; +import { useState } from 'react'; +import { ResourceIdentifier, toResourceIdentifierStr } from 'movex-core-util'; +import { GameUI } from './GameUI'; // import { MovexStoreItem } from "movex"; import { MovexLocalInstance } from 'movex-react-local-master'; import { MovexStoreItem } from 'movex-store'; @@ -14,7 +14,7 @@ type Props = { }; export function Game(props: Props) { - const [rpsRid, setRpsRid] = useState>(); + const [rpsRid, setRpsRid] = useState>(); const [masterStateUpdated, setMasterStateUpdated] = useState(false); return ( @@ -23,7 +23,7 @@ export function Game(props: Props) { clientId="playerA" movexDefinition={movexConfig} onConnected={(movex) => { - const reg = movex.register("rps"); + const reg = movex.register('rps'); reg.create(initialState).map(({ rid }) => { setRpsRid(rid); @@ -39,8 +39,6 @@ export function Game(props: Props) { }} > {rpsRid && ( - <> - yesss )} /> - )}
@@ -63,7 +60,7 @@ export function Game(props: Props) {
@@ -78,7 +75,7 @@ export function Game(props: Props) { {JSON.stringify( { submissions: props.masterStore.state[0].submissions, - winner: props.masterStore.state[0].winner + winner: props.masterStore.state[0].winner, }, null, 1 @@ -91,13 +88,11 @@ export function Game(props: Props) {
{rpsRid && masterStateUpdated && ( - here 2 (
- yess works
)} diff --git a/apps/movex-demo/pages/local/rps-v2/GameUI.tsx b/apps/movex-demo/pages/local/rps-v2/GameUI.tsx index 98f92ab7..60947fff 100644 --- a/apps/movex-demo/pages/local/rps-v2/GameUI.tsx +++ b/apps/movex-demo/pages/local/rps-v2/GameUI.tsx @@ -1,7 +1,7 @@ import React from "react"; import { MovexBoundResourceFromConfig } from "movex-react"; import { useCallback, useEffect, useMemo } from "react"; -import { logsy } from "movex-core-util"; +import { globalLogsy } from "movex-core-util"; import movexConfig from "./movex.config"; import { PlayerLabel, @@ -68,7 +68,7 @@ export const GameUI: React.FC = ({ } if (availableLabels.length === 0) { - logsy.warn("Player Slots taken"); + globalLogsy.warn("Player Slots taken"); return; } diff --git a/apps/movex-demo/pages/local/rps-v2/index.tsx b/apps/movex-demo/pages/local/rps-v2/index.tsx index 0e7cfe30..0efdd323 100644 --- a/apps/movex-demo/pages/local/rps-v2/index.tsx +++ b/apps/movex-demo/pages/local/rps-v2/index.tsx @@ -15,6 +15,12 @@ export default function App() { { + // console.log('event', method, prefix, message, payload) + console[method](prefix + ' ' + message, payload); + }, + }} > diff --git a/libs/movex-react/src/lib/MovexProvider.tsx b/libs/movex-react/src/lib/MovexProvider.tsx index 8991ad08..87c9422c 100644 --- a/libs/movex-react/src/lib/MovexProvider.tsx +++ b/libs/movex-react/src/lib/MovexProvider.tsx @@ -8,6 +8,8 @@ import { type MovexDefinition, ResourceIdentifier, StringKeys, + LoggingEvent, + globalLogsy, } from 'movex-core-util'; import { MovexClient } from 'movex'; import { ResourceObservablesRegistry } from './ResourceObservableRegistry'; @@ -29,11 +31,14 @@ type Props = { connected: false } > ) => void; + logger?: { + onLog: (event: LoggingEvent) => void; + }; }>; export const MovexProvider: React.FC< Props -> = ({ onConnected = noop, onDisconnected = noop, ...props }) => { +> = ({ onConnected = noop, onDisconnected = noop, logger, ...props }) => { type TResourcesMap = typeof props['movexDefinition']['resources']; const [contextState, setContextState] = useState< @@ -108,6 +113,14 @@ export const MovexProvider: React.FC< } }, [contextState.connected, onDisconnected]); + useEffect(() => { + if (logger) { + return globalLogsy.onLog(logger.onLog); + } + + return () => {}; + }, [logger]); + return ( {props.children} From a29a6ed6d594102ca82d5d1fc80dd621533b5ed2 Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Thu, 11 Apr 2024 17:10:51 -0600 Subject: [PATCH 5/9] ci(libs version): :ferris_wheel: bump to 0.1.5-1 --- libs/movex-core-util/package.json | 2 +- libs/movex-master/package.json | 2 +- libs/movex-react-local-master/package.json | 2 +- libs/movex-react/package.json | 2 +- libs/movex-server/package.json | 2 +- libs/movex-service/package.json | 2 +- libs/movex-store/package.json | 2 +- libs/movex/package.json | 2 +- package.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/movex-core-util/package.json b/libs/movex-core-util/package.json index 505952b7..738f7d27 100644 --- a/libs/movex-core-util/package.json +++ b/libs/movex-core-util/package.json @@ -1,6 +1,6 @@ { "name": "movex-core-util", - "version": "0.1.5-0", + "version": "0.1.5-1", "description": "Movex Core Util is the library of utilities for Movex", "license": "MIT", "author": { diff --git a/libs/movex-master/package.json b/libs/movex-master/package.json index 6096e5e5..2f50b8ca 100644 --- a/libs/movex-master/package.json +++ b/libs/movex-master/package.json @@ -1,6 +1,6 @@ { "name": "movex-master", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Movex-master defines the master that wil be used on movex-server and movex-react-local-master", "author": { diff --git a/libs/movex-react-local-master/package.json b/libs/movex-react-local-master/package.json index fb864314..a90c6404 100644 --- a/libs/movex-react-local-master/package.json +++ b/libs/movex-react-local-master/package.json @@ -1,6 +1,6 @@ { "name": "movex-react-local-master", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Run the movex-master locally with react", "author": { diff --git a/libs/movex-react/package.json b/libs/movex-react/package.json index b3ea220e..7b164cc0 100644 --- a/libs/movex-react/package.json +++ b/libs/movex-react/package.json @@ -1,6 +1,6 @@ { "name": "movex-react", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Movex React is the library of React components for Movex", "author": { diff --git a/libs/movex-server/package.json b/libs/movex-server/package.json index e4aab595..3b6a1736 100644 --- a/libs/movex-server/package.json +++ b/libs/movex-server/package.json @@ -1,6 +1,6 @@ { "name": "movex-server", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "type": "commonjs", "description": "Movex Server is the backend runtime for Movex", diff --git a/libs/movex-service/package.json b/libs/movex-service/package.json index cd0dd2f6..73a09518 100644 --- a/libs/movex-service/package.json +++ b/libs/movex-service/package.json @@ -1,6 +1,6 @@ { "name": "movex-service", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "type": "commonjs", "description": "Movex Service is the CLI for Movex", diff --git a/libs/movex-store/package.json b/libs/movex-store/package.json index f926d2ec..4377810f 100644 --- a/libs/movex-store/package.json +++ b/libs/movex-store/package.json @@ -1,6 +1,6 @@ { "name": "movex-store", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Movex-store defines the store interface and comes with a MemoryStore", "author": { diff --git a/libs/movex/package.json b/libs/movex/package.json index 3bb2beb1..88347046 100644 --- a/libs/movex/package.json +++ b/libs/movex/package.json @@ -1,6 +1,6 @@ { "name": "movex", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Movex is a Multiplayer (Game) State Synchronization Library using Deterministic Action Propagation without the need to write Server Specific Code.", "author": { diff --git a/package.json b/package.json index a583d1c8..e39a9628 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "movex", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "author": { "name": "Gabriel C. Troia", From cb30fc18d378294f0446de7faedf34fe87f103a2 Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Thu, 11 Apr 2024 17:29:13 -0600 Subject: [PATCH 6/9] fix(movex-react): :bug: add the logger to MovexProviderClass as well --- libs/movex-react/src/lib/MovexProvider.tsx | 46 +++++++-------- .../src/lib/MovexProviderClass.tsx | 56 ++++++++----------- 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/libs/movex-react/src/lib/MovexProvider.tsx b/libs/movex-react/src/lib/MovexProvider.tsx index 87c9422c..63a36e12 100644 --- a/libs/movex-react/src/lib/MovexProvider.tsx +++ b/libs/movex-react/src/lib/MovexProvider.tsx @@ -14,30 +14,32 @@ import { import { MovexClient } from 'movex'; import { ResourceObservablesRegistry } from './ResourceObservableRegistry'; -type Props = - React.PropsWithChildren<{ - movexDefinition: MovexDefinition; - endpointUrl: string; - clientId?: MovexClientUser['id']; - onConnected?: ( - state: Extract< - MovexContextProps, - { connected: true } - > - ) => void; - onDisconnected?: ( - state: Extract< - MovexContextProps, - { connected: false } - > - ) => void; - logger?: { - onLog: (event: LoggingEvent) => void; - }; - }>; +export type MovexProviderProps< + TMovexConfigResourcesMap extends BaseMovexDefinitionResourcesMap +> = React.PropsWithChildren<{ + movexDefinition: MovexDefinition; + endpointUrl: string; + clientId?: MovexClientUser['id']; + onConnected?: ( + state: Extract< + MovexContextProps, + { connected: true } + > + ) => void; + onDisconnected?: ( + state: Extract< + MovexContextProps, + { connected: false } + > + ) => void; + logger?: { + onLog: (event: LoggingEvent) => void; + }; + test:number; +}>; export const MovexProvider: React.FC< - Props + MovexProviderProps > = ({ onConnected = noop, onDisconnected = noop, logger, ...props }) => { type TResourcesMap = typeof props['movexDefinition']['resources']; diff --git a/libs/movex-react/src/lib/MovexProviderClass.tsx b/libs/movex-react/src/lib/MovexProviderClass.tsx index 5c1846a8..80c95159 100644 --- a/libs/movex-react/src/lib/MovexProviderClass.tsx +++ b/libs/movex-react/src/lib/MovexProviderClass.tsx @@ -4,25 +4,28 @@ import type { StringKeys, MovexDefinition, BaseMovexDefinitionResourcesMap, -} from 'movex-core-util'; +} from 'movex-core-util'; import type { MovexContextProps } from './MovexContext'; -import { MovexProvider } from './MovexProvider'; +import { MovexProvider, MovexProviderProps } from './MovexProvider'; type Props< TResourcesMap extends BaseMovexDefinitionResourcesMap, TResourceType extends Extract -> = React.PropsWithChildren<{ - movexDefinition: MovexDefinition; - endpointUrl: string; - clientId?: MovexClient['id']; - onConnected?: ( - state: Extract< - MovexContextProps, - { connected: true } - >['movex'] - ) => void; - onDisconnected?: () => void; -}>; +> = React.PropsWithChildren< + Omit< + MovexProviderProps, + 'movexDefinition' | 'onConnected' | 'onDisconnected' + > & { + movexDefinition: MovexDefinition; + onConnected?: ( + state: Extract< + MovexContextProps, + { connected: true } + >['movex'] + ) => void; + onDisconnected?: () => void; + } +>; type State = { clientId?: MovexClient['id']; @@ -36,36 +39,21 @@ export class MovexProviderClass< TResourcesMap extends BaseMovexDefinitionResourcesMap, TResourceType extends StringKeys > extends React.Component, State> { - constructor(props: Props) { - super(props); - - this.state = {}; - } - override render() { + const { onConnected, ...props } = this.props; + return ( { - this.setState({ clientId: r.clientId }); - - this.props.onConnected?.( + onConnected?.( r.movex as Extract< MovexContextProps, { connected: true } >['movex'] ); }} - // onDisconnected={() => { - // this.setState({ clientId: undefined }); - - // this.props.onDisconnected?.(); - // }} - > - {this.props.children} - + {...props} + /> ); } } From 0af75b00a0b9340b959328fa099457dae420e077 Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Thu, 11 Apr 2024 17:35:40 -0600 Subject: [PATCH 7/9] ci(movex-react): :ferris_wheel: bump version to 0.1.5-1 --- libs/movex-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/movex-react/package.json b/libs/movex-react/package.json index 7b164cc0..5999b94d 100644 --- a/libs/movex-react/package.json +++ b/libs/movex-react/package.json @@ -1,6 +1,6 @@ { "name": "movex-react", - "version": "0.1.5-1", + "version": "0.1.5-2", "license": "MIT", "description": "Movex React is the library of React components for Movex", "author": { From 358dd782c5e3ea361702782ce0cd177e3727d4df Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Thu, 11 Apr 2024 17:37:52 -0600 Subject: [PATCH 8/9] fix(movex-react): :bug: remove unneded "test" prop from movex-provider --- libs/movex-react/src/lib/MovexProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/movex-react/src/lib/MovexProvider.tsx b/libs/movex-react/src/lib/MovexProvider.tsx index 63a36e12..ca9eaaec 100644 --- a/libs/movex-react/src/lib/MovexProvider.tsx +++ b/libs/movex-react/src/lib/MovexProvider.tsx @@ -35,7 +35,6 @@ export type MovexProviderProps< logger?: { onLog: (event: LoggingEvent) => void; }; - test:number; }>; export const MovexProvider: React.FC< From 9660c7608e461823062185bd68f73c2c277f944d Mon Sep 17 00:00:00 2001 From: "Gabriel C. Troia" Date: Thu, 11 Apr 2024 17:38:38 -0600 Subject: [PATCH 9/9] ci(movex-react): :ferris_wheel: bump to 0.1.5-3 --- libs/movex-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/movex-react/package.json b/libs/movex-react/package.json index 5999b94d..23a360d2 100644 --- a/libs/movex-react/package.json +++ b/libs/movex-react/package.json @@ -1,6 +1,6 @@ { "name": "movex-react", - "version": "0.1.5-2", + "version": "0.1.5-3", "license": "MIT", "description": "Movex React is the library of React components for Movex", "author": {