Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 171 additions & 70 deletions src/Bones.UI/core/composableFactory.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@
import { Ref, onUnmounted, ref } from "vue";
import { Ref, onUnmounted, ref, computed } from "vue";

import { FilterFactory } from "./filterFactory";
import { INotifyService } from "../abstractions";
import { onCollectionChanged, onEntityChanged } from "../tools";
import { INotifyService } from "../abstractions";
import { FilterFactory } from "./filterFactory";

export class ComposableFactory {
public static get<TDetails>(service: { get(id: string): Promise<TDetails> } & INotifyService<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
return ComposableFactory.customGet(service, service.get, applyFactory);
public static get<TDetails>(
service: { get(id: string, cancellationToken?: AbortController): Promise<TDetails> } & INotifyService<TDetails>,
applyFactory?: () => (entity: Ref<TDetails>) => void,
allowCancellation: boolean = true
) {
return ComposableFactory.customGet(service, service.get, applyFactory, allowCancellation);
}

public static getMany<TDetails extends TInfos, TInfos, TFilter>(service: { getMany(filter?: TFilter): Promise<TInfos[]> } & INotifyService<TDetails>, applyFactory?: () => (entities: Ref<TInfos[]>) => void) {
return ComposableFactory.customGetMany(service, service.getMany, applyFactory);
public static getMany<TDetails extends TInfos, TInfos, TFilter>(
service: { getMany(filter?: TFilter, cancellationToken?: AbortController): Promise<TInfos[]> } & INotifyService<TDetails>,
applyFactory?: () => (entities: Ref<TInfos[]>) => void,
allowCancellation: boolean = true
) {
return ComposableFactory.customGetMany(service, service.getMany, applyFactory, allowCancellation);
}

public static create<TCreateDTO, TDetails>(service: { create(payload: TCreateDTO): Promise<TDetails> } & INotifyService<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
return ComposableFactory.customCreate(service, service.create, applyFactory);
public static create<TCreateDTO, TDetails>(
service: { create(payload: TCreateDTO, cancellationToken?: AbortController): Promise<TDetails> } & INotifyService<TDetails>,
applyFactory?: () => (entity: Ref<TDetails>) => void,
allowCancellation: boolean = true
) {
return ComposableFactory.customCreate(service, service.create, applyFactory, allowCancellation);
}

public static update<TUpdateDTO, TDetails>(service: { update(id: string, payload: TUpdateDTO): Promise<TDetails> } & INotifyService<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
return ComposableFactory.customUpdate(service, service.update, applyFactory);
public static update<TUpdateDTO, TDetails>(
service: { update(id: string, payload: TUpdateDTO, cancellationToken?: AbortController): Promise<TDetails> } & INotifyService<TDetails>,
applyFactory?: () => (entity: Ref<TDetails>) => void,
allowCancellation: boolean = true
) {
return ComposableFactory.customUpdate(service, service.update, applyFactory, allowCancellation);
}

public static remove(service: { remove(id: string): Promise<void> }) {
return ComposableFactory.customRemove(service.remove);
public static remove(
service: { remove(id: string, cancellationToken?: AbortController): Promise<void> },
allowCancellation: boolean = true
) {
return ComposableFactory.customRemove(service.remove, allowCancellation);
}

public static subscribe<TDetails>(service: INotifyService<TDetails>) {
Expand All @@ -46,7 +65,10 @@ export class ComposableFactory {
}
}

public static custom<TDetails, TArgs extends any[]>(method: (...args: TArgs) => Promise<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
public static custom<TDetails, TArgs extends any[]>(
method: (...args: TArgs) => Promise<TDetails>,
applyFactory?: () => (entity: Ref<TDetails>) => void
) {
return () => {
const apply = applyFactory ? applyFactory() : () => { };

Expand Down Expand Up @@ -74,48 +96,16 @@ export class ComposableFactory {
}
}


public static customGet<TDetails, TArgs extends any[]>(service: INotifyService<TDetails>, method: (...args: TArgs) => Promise<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
return () => {
const apply = applyFactory ? applyFactory() : () => { };
let subscribersIds: number[] = [];

onUnmounted(() => {
subscribersIds.forEach(id => service.unsubscribe(id));
subscribersIds = [];
});

const getting = ref(false);
const entity = ref<TDetails | null>(null) as Ref<TDetails | null>;

const get = async (...args: TArgs) => {
getting.value = true;
try {
entity.value = await method(...args);
if (apply) apply(entity as Ref<TDetails>);
}
finally {
getting.value = false;
}

subscribersIds.push(service.subscribe("all", onEntityChanged(entity, apply)));

return entity;
}

return {
getting: getting,
get,
entity: entity
}
}
}

/**
* Warning : read the code before using this method, the first argument in the method is used to create a filter
* The last argument passed to the getMany composable can be a custom filter function that will override the default filter
* */
public static customGetMany<TDetails extends TInfos, TInfos, TArgs extends any[]>(service: INotifyService<TDetails>, method: (...args: TArgs) => Promise<TInfos[]>, applyFactory?: () => (entities: Ref<TInfos[]>) => void) {
public static customGetMany<TDetails extends TInfos, TInfos, TArgs extends any[]>(
service: INotifyService<TDetails>,
method: (...args: [...TArgs, AbortController]) => Promise<TInfos[]>,
applyFactory?: () => (entities: Ref<TInfos[]>) => void,
allowCancellation: boolean = true
) {
return () => {
const apply = applyFactory ? applyFactory() : () => { };
let subscribersIds: number[] = [];
Expand All @@ -126,12 +116,24 @@ export class ComposableFactory {
});

const fetching = ref(false);
const entities = ref<TInfos[]>([]) as Ref<TInfos[]>;
const _entities = ref<TInfos[]>([]) as Ref<TInfos[]>;
let _filter: Ref<(el: TInfos) => boolean> = ref(() => true);

const entities = computed(() => {
return _entities.value.filter(e => _filter.value(e))
});

const getMany = async (...args: [...TArgs, ((el: TDetails) => boolean)?]) => {
let cancellationToken: AbortController | null = null;

const getMany = async (...args: [...TArgs, ((el: TInfos) => boolean)?]) => {
if (cancellationToken && allowCancellation) {
cancellationToken.abort();
cancellationToken = null;
}
fetching.value = true;
cancellationToken = new AbortController();

let customFilter: ((el: TDetails) => boolean) | undefined = undefined
let customFilter: ((el: TInfos) => boolean) | undefined = undefined

if (args.length > 1 && typeof args[args.length - 1] === 'function') {
customFilter = args.pop();
Expand All @@ -140,23 +142,31 @@ export class ComposableFactory {
let actualArgs = args as unknown as TArgs;

try {
entities.value = await method(...actualArgs);
if (apply) apply(entities)
_entities.value = await method(...actualArgs, cancellationToken);
if (apply) apply(_entities)
}
finally {
cancellationToken = null;
fetching.value = false;
}

const filterMethod = customFilter || (actualArgs.length > 0 ? FilterFactory.create(actualArgs[0]) : () => true);
_filter.value = filterMethod

subscribersIds.push(service.subscribe("all", onCollectionChanged(entities, filterMethod)));
subscribersIds.push(service.subscribe("all", onCollectionChanged(_entities)));
subscribersIds.push(service.subscribe("reset", async () => {
if (cancellationToken && allowCancellation) {
cancellationToken.abort();
cancellationToken = null;
}
fetching.value = true;
cancellationToken = new AbortController();
try {
entities.value = await method(...actualArgs);
if (apply) apply(entities)
_entities.value = await method(...actualArgs, cancellationToken);
if (apply) apply(_entities)
}
finally {
cancellationToken = null;
fetching.value = false;
}
}));
Expand All @@ -167,12 +177,68 @@ export class ComposableFactory {
return {
fetching: fetching,
getMany,
entities: entities
entities: entities,
cancellationToken
}
}
}

public static customCreate<TDetails, TArgs extends any[]>(service: INotifyService<TDetails>, method: (...args: TArgs) => Promise<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
public static customGet<TDetails, TArgs extends any[]>(
service: INotifyService<TDetails>,
method: (...args: [...TArgs, AbortController]) => Promise<TDetails>,
applyFactory?: () => (entity: Ref<TDetails>) => void,
allowCancellation: boolean = true
) {
return () => {
const apply = applyFactory ? applyFactory() : () => { };
let subscribersIds: number[] = [];

onUnmounted(() => {
subscribersIds.forEach(id => service.unsubscribe(id));
subscribersIds = [];
});

const getting = ref(false);
const entity = ref<TDetails | null>(null) as Ref<TDetails | null>;

let cancellationToken: AbortController | null = null;

const get = async (...args: TArgs) => {
if (cancellationToken && allowCancellation) {
cancellationToken.abort();
cancellationToken = null;
}
getting.value = true;
cancellationToken = new AbortController();
try {
entity.value = await method(...args, cancellationToken);
if (apply) apply(entity as Ref<TDetails>);
}
finally {
cancellationToken = null;
getting.value = false;
}

subscribersIds.push(service.subscribe("all", onEntityChanged(entity, apply)));

return entity;
}

return {
getting: getting,
get,
entity: entity,
cancellationToken
}
}
}

public static customCreate<TDetails, TArgs extends any[]>(
service: INotifyService<TDetails>,
method: (...args: [...TArgs, AbortController]) => Promise<TDetails>,
applyFactory?: () => (entity: Ref<TDetails>) => void,
allowCancellation: boolean = true
) {
return () => {
const apply = applyFactory ? applyFactory() : () => { };
let subscribersIds: number[] = [];
Expand All @@ -185,13 +251,21 @@ export class ComposableFactory {
const creating = ref(false);
const created = ref<TDetails | null>(null) as Ref<TDetails | null>;

let cancellationToken: AbortController | null = null;

const create = async (...args: TArgs) => {
if (cancellationToken && allowCancellation) {
cancellationToken.abort();
cancellationToken = null;
}
creating.value = true;
cancellationToken = new AbortController();
try {
created.value = await method(...args);
created.value = await method(...args, cancellationToken);
if (apply) apply(created as Ref<TDetails>);
}
finally {
cancellationToken = null;
creating.value = false;
}

Expand All @@ -203,12 +277,18 @@ export class ComposableFactory {
return {
creating: creating,
create,
created: created
created: created,
cancellationToken
}
}
}

public static customUpdate<TDetails, TArgs extends any[]>(service: INotifyService<TDetails>, method: (...args: TArgs) => Promise<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
public static customUpdate<TDetails, TArgs extends any[]>(
service: INotifyService<TDetails>,
method: (...args: [...TArgs, AbortController]) => Promise<TDetails>,
applyFactory?: () => (entity: Ref<TDetails>) => void,
allowCancellation: boolean = true
) {
return () => {
const apply = applyFactory ? applyFactory() : () => { };
let subscribersIds: number[] = [];
Expand All @@ -221,13 +301,21 @@ export class ComposableFactory {
const updating = ref(false);
const updated = ref<TDetails | null>(null) as Ref<TDetails | null>;

let cancellationToken: AbortController | null = null;

const update = async (...args: TArgs) => {
if (cancellationToken && allowCancellation) {
cancellationToken.abort();
cancellationToken = null;
}
updating.value = true;
cancellationToken = new AbortController();
try {
updated.value = await method(...args);
updated.value = await method(...args, cancellationToken);
if (apply) apply(updated as Ref<TDetails>);
}
finally {
cancellationToken = null;
updating.value = false;
}

Expand All @@ -239,28 +327,41 @@ export class ComposableFactory {
return {
updating: updating,
update,
updated: updated
updated: updated,
cancellationToken
}
}
}

public static customRemove<TArgs extends any[]>(method: (...args: TArgs) => Promise<void>) {
public static customRemove<TArgs extends any[]>(
method: (...args: [...TArgs, AbortController]) => Promise<void>,
allowCancellation: boolean = true
) {
return () => {
const removing = ref(false);

let cancellationToken: AbortController | null = null;

const remove = async (...args: TArgs) => {
if (cancellationToken && allowCancellation) {
cancellationToken.abort();
cancellationToken = null;
}
removing.value = true;
cancellationToken = new AbortController();
try {
await method(...args);
await method(...args, cancellationToken);
}
finally {
cancellationToken = null;
removing.value = false;
}
}

return {
removing: removing,
remove
remove,
cancellationToken
}
}
}
Expand Down
Loading