Skip to content

Commit

Permalink
Merge pull request #219 from movesthatmatter/dev
Browse files Browse the repository at this point in the history
Release v1.6
  • Loading branch information
GabrielCTroia authored Sep 26, 2024
2 parents 3912b1c + dcfa331 commit bdd9919
Show file tree
Hide file tree
Showing 115 changed files with 12,160 additions and 1,389 deletions.
12 changes: 12 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { StorybookConfig } from '@storybook/core-common';

export const rootMain: StorybookConfig = {
stories: [],
addons: ['@storybook/addon-essentials'],
// webpackFinal: async (config, { configType }) => {
// // Make whatever fine-grained changes you need that should apply to all storybook configs

// // Return the altered config
// return config;
// },
};
14 changes: 14 additions & 0 deletions .storybook/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.base.json",
"exclude": [
"../**/*.spec.js",
"../**/*.test.js",
"../**/*.spec.ts",
"../**/*.test.ts",
"../**/*.spec.tsx",
"../**/*.test.tsx",
"../**/*.spec.jsx",
"../**/*.test.jsx"
],
"include": ["../**/*"]
}
4 changes: 2 additions & 2 deletions apps/movex-demo/pages/chat/[chatId].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMovexBoundResourceFromRid, useMovexClientId } from 'movex-react';
import { useMovexBoundResourceFromRid, useMovexClient } from 'movex-react';
import { useRouter } from 'next/router';
import { useMemo } from 'react';
import { toResourceIdentifierObj } from 'movex-core-util';
Expand All @@ -20,7 +20,7 @@ const ChatSystem: React.FC<Props> = () => {

// TODO: Validate the rid is correct inside useMovexBoundResouce
const boundResource = useMovexBoundResourceFromRid(movexConfig, rid);
const userId = useMovexClientId(movexConfig);
const userId = useMovexClient(movexConfig)?.id;

if (!(boundResource && userId)) {
return null;
Expand Down
4 changes: 4 additions & 0 deletions apps/movex-docs/pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,9 @@
"about": {
"title": "About",
"type": "page"
},
"changelog": {
"title": "Changelog",
"type": "page"
}
}
21 changes: 21 additions & 0 deletions apps/movex-docs/pages/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Changelog

## v0.1.6 (Sep 26 2024)

### New Features
- [x] Master Actions. [See Pull Request](https://github.com/movesthatmatter/movex/pull/216).
- [x] State Transformers. [See Pull Request](https://github.com/movesthatmatter/movex/pull/218).
- [x] Add ability to disconenect an older client connection (for same client id) in favor of the newer one

### Improvements
- [x] Improve the Connection Events. [See Pull Request](https://github.com/movesthatmatter/movex/pull/220).

### Bug Fixes
- [x] State not re-synching after a forwardAction checksum mismatch.

### Breaking
- none

## v0.1.5 (July 11 2024)
- [x] Add ability to pass in Client Info Object and sync it to all peers
- [x] Improve Logs
1 change: 1 addition & 0 deletions changelog.md
2 changes: 1 addition & 1 deletion libs/movex-core-util/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "movex-core-util",
"version": "0.1.5",
"version": "0.1.6",
"description": "Movex Core Util is the library of utilities for Movex",
"license": "MIT",
"author": {
Expand Down
3 changes: 1 addition & 2 deletions libs/movex-core-util/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ export * from './lib/misc';
export * from './lib/domain';
export * from './lib/Observable/Observable';
export * from './lib/EventEmitter';
export * from './lib/ScketIOEmitter';
export * from './lib/Logsy';
export * from './lib/action';
export * from './lib/reducer';
export * from './lib/core-types';
export * from './lib/public-types';
export * from './lib/checkedState';
export * from './lib/io';
export * from './lib/masterContext';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { EventMap } from 'typed-emitter';
import type { EmptyFn } from './core-types';
import type { EmptyFn } from '../core-types';

export const emptyFn: EmptyFn = () => {};

Expand Down Expand Up @@ -34,4 +34,9 @@ export interface EventEmitter<TEventMap extends EventMap> {
event: E,
request: Parameters<TEventMap[E]>[0]
): Promise<ReturnType<TEventMap[E]>>;

disconnect(): void;

onConnect(fn: () => void): EmptyFn;
onDisconnect(fn: () => void): EmptyFn;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import type {
IOPayloadResult,
MovexClient,
ResourceIdentifier,
ResourceIdentifierStr,
SanitizedMovexClient,
} from '../core-types';
import { MovexMasterContext } from '../masterContext';
// import { MovexMasterContext } from '../reducer';

export type IOEvents<
TState = unknown,
Expand Down Expand Up @@ -72,37 +73,49 @@ export type IOEvents<
rid: ResourceIdentifier<TResourceType>;
action: ActionOrActionTupleFromAction<A>;
}) => IOPayloadResult<
| {
reconciled?: false;
nextChecksum: Checksum;
}
| ({
reconciled: true;
} & CheckedReconciliatoryActions<A>),
(
| {
type: 'ack';
nextChecksum: Checksum;
}
| {
type: 'masterActionAck';
// nextChecksum: Checksum;
nextCheckedAction: ToCheckedAction<A>;
}
| ({
type: 'reconciliation';
} & CheckedReconciliatoryActions<A>)
) & {
masterContext: MovexMasterContext;
},
'MasterResourceInexistent' | string
>; // Type the other errors

/**
* The following events are directed from Master to Client
* */
// @deprecate in favor ofClientReady
setClientId: (clientId: string) => void;
onReady: (p: SanitizedMovexClient) => void;

onClientReady: (client: SanitizedMovexClient) => void;
// onClockSync: (p: undefined) => IOPayloadResult<number, unknown>; // acknowledges the client timestamp

onFwdAction: (
payload: {
rid: ResourceIdentifier<TResourceType>;
masterContext: MovexMasterContext;
} & ToCheckedAction<A>
) => IOPayloadResult<void, unknown>;
onReconciliateActions: (
payload: {
rid: ResourceIdentifier<TResourceType>;
masterContext: MovexMasterContext;
} & CheckedReconciliatoryActions<A>
) => IOPayloadResult<void, unknown>;
onResourceSubscriberAdded: (p: {
rid: ResourceIdentifier<TResourceType>;
client: Pick<MovexClient, 'id' | 'info'>;
client: SanitizedMovexClient;
// TODO: Make required after it works
masterContext: MovexMasterContext;
// clientId: MovexClient['id'];
}) => IOPayloadResult<
void,
Expand All @@ -120,6 +133,7 @@ export type IOEvents<
* The following events are by-directional (from Client to Master and vice-versa)
* */

// They need to be different than ping/pong because those are native to socket.io
ping: () => IOPayloadResult<void, unknown>;
pong: () => IOPayloadResult<void, unknown>;
};
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import { Err, Ok } from 'ts-results';
import { globalLogsy } 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';
import type { EventEmitter } from './EventEmitter';
import type { UnsubscribeFn, WsResponseResultPayload } from './core-types';
import type { UnsubscribeFn, WsResponseResultPayload } from '../core-types';

export type SocketIO = ServerSocket | ClientSocket;

const logsy = globalLogsy.withNamespace('[SocketIOEmitter]');

export class SocketIOEmitter<TEventMap extends EventMap>
implements EventEmitter<TEventMap>
export class SocketIOEmitter<
TEventMap extends EventMap,
TSocketIO extends SocketIO = ServerSocket | ClientSocket
> implements EventEmitter<TEventMap>
{
protected config: {
waitForResponseMs: number;
};

constructor(
private socket: SocketIO,
private config: {
protected socket: TSocketIO,
config?: {
waitForResponseMs?: number;
} = {}
}
) {
this.config.waitForResponseMs = this.config.waitForResponseMs || 15 * 1000;
this.config = {
waitForResponseMs: config?.waitForResponseMs || 15 * 1000,
};
}

on<E extends keyof TEventMap>(
Expand Down Expand Up @@ -149,8 +157,36 @@ export class SocketIOEmitter<TEventMap extends EventMap>
);
}).catch((e) => e) as any;
}

onConnect(fn: () => void) {
this.socket.on('connect', fn);

return () => {
this.socket.off('connect', fn);
};
}

onDisconnect(fn: () => void) {
this.socket.on('disconnect', fn);

return () => {
this.socket.off('disconnect', fn);
};
}

disconnect(): void {
this.socket.disconnect();
}
}

/**
* TODO: Deprecate this in favor of using the native timeout See https://socket.io/docs/v4/emitting-events/#with-timeout
*
* @param onSuccess
* @param onTimeout
* @param timeout
* @returns
*/
const withTimeout = (
onSuccess: (...args: any[]) => void,
onTimeout: () => void,
Expand Down
3 changes: 3 additions & 0 deletions libs/movex-core-util/src/lib/EventEmitter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './EventEmitter';
export * from './IOEvents';
export * from './ScketIOEmitter';
72 changes: 67 additions & 5 deletions libs/movex-core-util/src/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { isObject, keyInObject } from './misc';

// TODO: ith the new Reducer Refactoring, all of the action with collection map and generic actions can be removed I believe

/**
* TODO: 18.08.2024 - This file needs a restructuring:
* - there are too many types, many of them aren't used or are intersecting with others
* - others are just confusing and create more issues in understanding (AnyAction vs GenericAction - which one is what???)
* - others are remnants of long past times (e..g all of the action with collection map and generic actions can be removed I believe)
*/

export type BaseAction<
TType extends string,
TPayload = undefined
Expand All @@ -27,9 +34,20 @@ export type PublicAction<
isPrivate?: false;
};

export type MasterAction<TType extends string, TPayload = undefined> =
| (PrivateAction<TType, TPayload> & {
_isMaster: true; // Note: This is only meant for internal useage
})
| (PublicAction<TType, TPayload> & {
_isMaster: true; // Note: This is only meant for internal useage
});

export type Action<TType extends string, TPayload = undefined> =
| PublicAction<TType, TPayload>
| PrivateAction<TType, TPayload>;
| PrivateAction<TType, TPayload>
| MasterAction<TType, TPayload>;

export type AnyMasterAction = MasterAction<string>;

export type ActionWithAnyPayload<TType extends string> = Action<TType, unknown>;

Expand All @@ -56,10 +74,14 @@ export type AnyPublicActionOf<

export type GenericPrivateAction = PrivateAction<string, unknown>;
export type GenericPublicAction = PublicAction<string, unknown>;
export type GenericMasterAction = MasterAction<string, unknown>;

export type GenericAction = GenericPrivateAction | GenericPublicAction;
export type GenericAction =
| GenericPrivateAction
| GenericPublicAction
| GenericMasterAction;

export type AnyAction = Action<string>;
export type AnyAction = Action<string> | GenericAction;

export type UnknownAction = Action<string, unknown>;

Expand Down Expand Up @@ -99,6 +121,10 @@ export type ToPublicAction<A extends AnyAction> = A & {
isPrivate?: false;
};

export type ToMasterAction<A extends AnyAction> = A & {
_isMaster: true;
};

export type CheckedReconciliatoryActions<A extends AnyAction> = {
actions: ToPublicAction<A>[];
finalChecksum: Checksum;
Expand Down Expand Up @@ -144,7 +170,7 @@ export type ActionCreatorsMapBase = {
[k in string]: ReturnType<typeof createActionCreator>;
};

export type ActionTupleFrom<TAction extends AnyAction> = [
export type ActionTupleFrom<TAction extends AnyAction | AnyMasterAction> = [
ToPrivateAction<TAction>,
ToPublicAction<TAction>
];
Expand Down Expand Up @@ -267,6 +293,42 @@ export function createActionCreator<
});
}

export const isAction = (a: unknown): a is AnyAction => {
export const isAction = (
a: ActionOrActionTupleFromAction<AnyAction> | AnyAction
): a is AnyAction => {
// TODO: This isn't a super thorough check, but if the input is limited
// to only action or ActionTuple should be enough
return isObject(a) && keyInObject(a, 'type');
};

export const isMasterAction = (
a: ActionOrActionTupleFromAction<AnyAction> | AnyAction
): a is AnyMasterAction => {
// TODO: This isn't a super thorough check, but if the input is limited
// to only action or ActionTuple should be enough
return isAction(a) && keyInObject(a, '_isMaster') && a._isMaster === true;
};

export const toMasterAction = <TAction extends AnyAction>(
action: TAction
): ToMasterAction<TAction> => ({
...action,
_isMaster: true,
});

export const toMasterActionFromActionOrTuple = <TAction extends AnyAction>(
actionOrActionTuple: ActionOrActionTupleFromAction<TAction>
) => {
return (
isAction(actionOrActionTuple)
? toMasterAction(actionOrActionTuple)
: // TODO: In the case of a tuple, there is not enough information
// so I'm just making them both a masterAction, but I should only make the one that is needed actually
// - this isn't the worst possible thing as if there's nothing to parse nothing will parse
// and the client will simply attempt to run again with the same
[
toMasterAction(actionOrActionTuple[0]),
toMasterAction(actionOrActionTuple[1]),
]
) as ActionOrActionTupleFromAction<ToMasterAction<TAction>>;
};
Loading

0 comments on commit bdd9919

Please sign in to comment.