Skip to content

Commit

Permalink
feat: enhance typing for doamin entities
Browse files Browse the repository at this point in the history
  • Loading branch information
tada5hi committed Nov 4, 2024
1 parent ede8fb8 commit 9d7c516
Show file tree
Hide file tree
Showing 26 changed files with 199 additions and 338 deletions.
48 changes: 23 additions & 25 deletions packages/client-vue/src/core/entity-manager/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { hasOwnProperty } from '@privateaim/kit';
import type { DomainAPI } from '@authup/core-http-kit';
import type {
DomainEntity, DomainEntityID, DomainType,
DomainEntityID, DomainTypeMap,
} from '@privateaim/core-kit';
import type { BuildInput } from 'rapiq';
import { isObject } from 'smob';
Expand All @@ -26,22 +26,20 @@ import type {
} from './type';
import { buildEntityManagerSlotProps } from './utils';

type DomainTypeInfer<T> = T extends DomainEntity<infer U> ? U extends `${DomainType}` ? U : never : never;

export function createEntityManager<
A extends DomainTypeInfer<DomainEntity<any>>,
T = DomainEntity<A>,
TYPE extends keyof DomainTypeMap,
RECORD extends DomainTypeMap[TYPE],
>(
ctx: EntityManagerContext<A, T>,
) : EntityManager<T> {
ctx: EntityManagerContext<TYPE, RECORD>,
) : EntityManager<RECORD> {
const client = injectCoreHTTPClient();
let domainAPI : DomainAPI<T> | undefined;
let domainAPI : DomainAPI<RECORD> | undefined;
if (hasOwnProperty(client, ctx.type)) {
domainAPI = client[ctx.type] as any;
}

const entity : Ref<T | undefined> = ref(undefined);
const entityId = computed<DomainEntityID<T> | undefined>(
const entity : Ref<RECORD | undefined> = ref(undefined);
const entityId = computed<DomainEntityID<RECORD> | undefined>(
() => (
entity.value ? (entity.value as any).id : undefined),
);
Expand Down Expand Up @@ -71,13 +69,13 @@ export function createEntityManager<
},
);

const lockId = ref(undefined) as Ref<DomainEntityID<T> | undefined>;
const lockId = ref(undefined) as Ref<DomainEntityID<RECORD> | undefined>;

if (ctx.props && ctx.props.entity) {
entity.value = ctx.props.entity;
}

const created = (value: T) => {
const created = (value: RECORD) => {
if (ctx.setup && ctx.setup.emit) {
ctx.setup.emit('created', value);
}
Expand All @@ -87,31 +85,31 @@ export function createEntityManager<
}
};

const deleted = (value: T) => {
const deleted = (value: RECORD) => {
if (ctx.setup && ctx.setup.emit) {
ctx.setup.emit('deleted', (value || entity.value) as T);
ctx.setup.emit('deleted', (value || entity.value) as RECORD);
}

if (ctx.onDeleted) {
ctx.onDeleted((value || entity.value) as T);
ctx.onDeleted((value || entity.value) as RECORD);
}
};

const updated = (value: Partial<T>) => {
const updated = (value: Partial<RECORD>) => {
if (entity.value) {
extendObjectProperties(entity.value, value);
}

if (ctx.setup && ctx.setup.emit) {
ctx.setup.emit('updated', (entity.value || value) as T);
ctx.setup.emit('updated', (entity.value || value) as RECORD);
}

if (ctx.onUpdated) {
ctx.onUpdated(entity.value || value);
}
};

const resolved = (value?: T) => {
const resolved = (value?: RECORD) => {
if (ctx.setup && ctx.setup.emit) {
ctx.setup.emit('resolved', value);
}
Expand All @@ -133,7 +131,7 @@ export function createEntityManager<

const busy = ref(false);

const update = async (data: Partial<T>) => {
const update = async (data: Partial<RECORD>) => {
if (!domainAPI || busy.value || !entityId.value) {
return;
}
Expand Down Expand Up @@ -186,7 +184,7 @@ export function createEntityManager<
}
};

const create = async (data: Partial<T>) : Promise<void> => {
const create = async (data: Partial<RECORD>) : Promise<void> => {
if (!domainAPI || busy.value) {
return;
}
Expand All @@ -211,7 +209,7 @@ export function createEntityManager<
}
};

const createOrUpdate = async (data: Partial<T>) : Promise<void> => {
const createOrUpdate = async (data: Partial<RECORD>) : Promise<void> => {
if (entity.value) {
await update(data);
} else {
Expand All @@ -227,7 +225,7 @@ export function createEntityManager<
typeof ctx.socket === 'function' ||
ctx.socket
) {
let socketContext : EntitySocketContext<A, T> = {
let socketContext : EntitySocketContext<TYPE, RECORD> = {
type: ctx.type,
};

Expand Down Expand Up @@ -288,12 +286,12 @@ export function createEntityManager<

resolveByProps();

const resolve = async (resolveCtx: EntityManagerResolveContext<T> = {}) => {
const resolve = async (resolveCtx: EntityManagerResolveContext<RECORD> = {}) => {
if (entity.value && !resolveCtx.reset) {
return;
}

let query : (T extends Record<string, any> ? BuildInput<T> : never) | undefined;
let query : (RECORD extends Record<string, any> ? BuildInput<RECORD> : never) | undefined;
if (resolveCtx.query) {
query = resolveCtx.query;
}
Expand Down Expand Up @@ -384,7 +382,7 @@ export function createEntityManager<
}
};

const manager : EntityManager<T> = {
const manager : EntityManager<RECORD> = {
resolve,
lockId,
busy,
Expand Down
11 changes: 3 additions & 8 deletions packages/client-vue/src/core/entity-manager/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
* view the LICENSE file that was distributed with this source code.
*/

import type {
DomainEntity,
DomainType,
} from '@privateaim/core-kit';
import type { BuildInput, FieldsBuildInput, FiltersBuildInput } from 'rapiq';
import type {
MaybeRef, Ref, SetupContext, SlotsType,
Expand Down Expand Up @@ -77,10 +73,9 @@ export type EntityManagerEventsType<T> = {
resolved: (_item: T | undefined) => true
};

type SocketContext<A extends `${DomainType}`, T> = Omit<EntitySocketContext<A, T>, 'type'>;
export type EntityManagerContext<
A extends `${DomainType}`,
T = DomainEntity<A>,
A extends string,
T extends Record<string, any>,
> = {
type: A,
setup?: Partial<SetupContext<EntityManagerEventsType<T>, SlotsType<EntityManagerSlotsType<T>>>>,
Expand All @@ -91,5 +86,5 @@ export type EntityManagerContext<
onUpdated?(entity: Partial<T>): any,
onDeleted?(entity: T): any,
onFailed?(e: Error): any,
socket?: SocketContext<A, T> | boolean;
socket?: Omit<EntitySocketContext<A, T>, 'type'> | boolean;
};
87 changes: 45 additions & 42 deletions packages/client-vue/src/core/entity-socket/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
*/

import { DomainEventName, REALM_MASTER_NAME } from '@authup/core-kit';
import type {
DomainEventSubscriptionFullName,
DomainTypeMap,
} from '@privateaim/core-kit';
import {
DomainEventSubscriptionName,
buildDomainChannelName,
Expand All @@ -14,14 +18,8 @@ import {
buildDomainNamespaceName,
} from '@privateaim/core-kit';
import type {
DomainEntity,
DomainEventContext,
DomainEventSubscriptionFullName,
DomainInput,
DomainType,
} from '@privateaim/core-kit';
import type {
STCEventContext,
STCEventHandler,
STCEventRecord,
} from '@privateaim/core-realtime-kit';
import {
computed, isRef, onMounted, onUnmounted, watch,
Expand All @@ -30,13 +28,11 @@ import { storeToRefs, useStore } from '@authup/client-web-kit';
import type { EntitySocket, EntitySocketContext } from './type';
import { injectSocketManager, isSocketManagerUsable } from '../socket';

type DT<T> = T extends DomainEntity<infer U> ? U extends `${DomainType}` ? U : never : never;

export function createEntitySocket<
A extends DT<DomainEntity<any>>,
T = DomainEntity<A>,
TYPE extends keyof DomainTypeMap,
RECORD extends DomainTypeMap[TYPE],
>(
ctx: EntitySocketContext<A, T>,
ctx: EntitySocketContext<TYPE, RECORD>,
) : EntitySocket {
if (!isSocketManagerUsable()) {
return {
Expand Down Expand Up @@ -73,7 +69,7 @@ export function createEntitySocket<

const lockId = computed(() => (isRef(ctx.lockId) ? ctx.lockId.value : ctx.lockId));

const processEvent = (event: STCEventContext<DomainEventContext<A>>) : boolean => {
const processEvent = (event: STCEventRecord<TYPE, RECORD>) : boolean => {
if (
ctx.processEvent &&
!ctx.processEvent(event, realmId.value)
Expand All @@ -96,32 +92,38 @@ export function createEntitySocket<
return event.data.id !== lockId.value;
};

const handleCreated = (event: STCEventContext<DomainEventContext<A>>) => {
const handleCreated : STCEventHandler<TYPE, RECORD> = (
event,
) => {
if (!processEvent(event)) {
return;
}

if (ctx.onCreated) {
ctx.onCreated(event.data as T);
ctx.onCreated(event.data as RECORD);
}
};

const handleUpdated = (event: STCEventContext<DomainEventContext<A>>) => {
const handleUpdated : STCEventHandler<TYPE, RECORD> = (
event,
) => {
if (!processEvent(event)) {
return;
}

if (ctx.onUpdated) {
ctx.onUpdated(event.data as T);
ctx.onUpdated(event.data as RECORD);
}
};
const handleDeleted = (event: STCEventContext<DomainEventContext<A>>) => {
const handleDeleted : STCEventHandler<TYPE, RECORD> = (
event,
) => {
if (!processEvent(event)) {
return;
}

if (ctx.onDeleted) {
ctx.onDeleted(event.data as T);
ctx.onDeleted(event.data as RECORD);
}
};

Expand All @@ -135,41 +137,41 @@ export function createEntitySocket<

const socket = await socketManager.connect(buildDomainNamespaceName(realmId.value));

let event : DomainEventSubscriptionFullName | undefined;
let event : DomainEventSubscriptionFullName<TYPE> | undefined;
if (ctx.buildSubscribeEventName) {
event = ctx.buildSubscribeEventName();
} else {
event = buildDomainEventSubscriptionFullName(
ctx.type as DomainInput,
ctx.type,
DomainEventSubscriptionName.SUBSCRIBE,
);
}

socket.emit(
socket.emit<any>(
event,
targetId.value,
targetId.value as EventTarget,
() => {
// todo: handle error!
},
);

if (ctx.onCreated) {
socket.on(buildDomainEventFullName(
ctx.type as DomainInput,
socket.on<any>(buildDomainEventFullName(
ctx.type,
DomainEventName.CREATED,
), handleCreated);
}

if (ctx.onUpdated) {
socket.on(buildDomainEventFullName(
ctx.type as DomainInput,
socket.on<any>(buildDomainEventFullName(
ctx.type,
DomainEventName.UPDATED,
), handleUpdated);
}

if (ctx.onDeleted) {
socket.on(buildDomainEventFullName(
ctx.type as DomainInput,
socket.on<any>(buildDomainEventFullName(
ctx.type,
DomainEventName.DELETED,
), handleDeleted);
}
Expand All @@ -184,38 +186,38 @@ export function createEntitySocket<

const socket = await socketManager.connect(buildDomainNamespaceName(realmId.value));

let event : DomainEventSubscriptionFullName | undefined;
let event : DomainEventSubscriptionFullName<TYPE>;
if (ctx.buildUnsubscribeEventName) {
event = ctx.buildUnsubscribeEventName();
} else {
event = buildDomainEventSubscriptionFullName(
ctx.type as DomainInput,
ctx.type,
DomainEventSubscriptionName.SUBSCRIBE,
);
}

socket.emit(
socket.emit<any>(
event,
targetId.value,
targetId.value as EventTarget,
);

if (ctx.onCreated) {
socket.off(buildDomainEventFullName(
ctx.type as DomainInput,
socket.off<any>(buildDomainEventFullName(
ctx.type,
DomainEventName.UPDATED,
), handleCreated);
}

if (ctx.onUpdated) {
socket.off(buildDomainEventFullName(
ctx.type as DomainInput,
socket.off<any>(buildDomainEventFullName(
ctx.type,
DomainEventName.UPDATED,
), handleUpdated);
}

if (ctx.onDeleted) {
socket.off(buildDomainEventFullName(
ctx.type as DomainInput,
socket.off<any>(buildDomainEventFullName(
ctx.type,
DomainEventName.DELETED,
), handleDeleted);
}
Expand All @@ -226,8 +228,9 @@ export function createEntitySocket<

watch(targetId, (val, oldValue) => {
if (val !== oldValue) {
unmount();
mount();
Promise.resolve()
.then(() => unmount())
.then(() => mount());
}
});

Expand Down
Loading

0 comments on commit 9d7c516

Please sign in to comment.