Skip to content

Commit

Permalink
feat(sherlock-utils): add new parallelStruct util (#37)
Browse files Browse the repository at this point in the history
The new `parallelStruct` util does the same as the existing `struct` util, the only difference being that it activates the nested Derivables in parallel (eagerly).
  • Loading branch information
pavadeli authored Aug 31, 2022
1 parent f3263ee commit ecc1719
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 15 deletions.
27 changes: 19 additions & 8 deletions libs/sherlock-utils/src/lib/struct.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import { atom } from '@skunkteam/sherlock';
import { struct } from './struct';
import { parallelStruct, struct } from './struct';

describe.each([
[struct, 'serial'],
[parallelStruct, 'parallel'],
])('sherlock-utils/%p', (fn, mode) => {
test(`should activate derivables in ${mode}`, () => {
const parts = [1, 2, 3].map(() => atom.unresolved<number>());
const result = fn(parts).autoCache();

expect(result.resolved).toBeFalse();
expect(parts.map(d$ => d$.connected)).toEqual([true, mode === 'parallel', mode === 'parallel']);
});

describe('sherlock-utils/struct', () => {
it('should copy any value-type as-is', () => {
const obj = {
date: new Date(),
number: 123,
string: 'asdf',
strings: ['asdf', 'sdfg'],
};
const result = struct(obj).get();
const result = fn(obj).get();
expect(result).toEqual(obj);
expect(result.date).toBe(obj.date);
});

it('should return a Derivables as is', () => {
const a = atom(123);
expect(struct(a)).toBe(a);
expect(fn(a)).toBe(a);
});

it('should turn an array of derivables into an unwrapped derivable', () => {
Expand All @@ -25,7 +36,7 @@ describe('sherlock-utils/struct', () => {
const number3$ = number1$.derive(n => n + number2$.get());

const number$s = [number1$, number2$, number3$];
const numbers$ = struct(number$s);
const numbers$ = fn(number$s);

expect(numbers$.get()).toEqual([1, 2, 3]);

Expand All @@ -37,7 +48,7 @@ describe('sherlock-utils/struct', () => {
const name$ = atom('Edwin');
const tel$ = atom('0612345678');
const person = { name: name$, tel: tel$ };
const person$ = struct(person);
const person$ = fn(person);

expect(person$.get()).toEqual({ name: 'Edwin', tel: '0612345678' });

Expand All @@ -62,7 +73,7 @@ describe('sherlock-utils/struct', () => {
},
],
};
const nested$ = struct(obj).autoCache();
const nested$ = fn(obj).autoCache();

expect(nested$.get()).toEqual({
name: 'Edwin',
Expand Down Expand Up @@ -96,7 +107,7 @@ describe('sherlock-utils/struct', () => {
const readonlyObject = { a$, b$ } as const;
const array = [a$, b$];
const readonlyArray = [a$, b$] as const;
const d$ = struct({ object, readonlyObject, array, readonlyArray });
const d$ = fn({ object, readonlyObject, array, readonlyArray });

const result = d$.get();

Expand Down
50 changes: 43 additions & 7 deletions libs/sherlock-utils/src/lib/struct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Derivable, derive, isDerivable, utils } from '@skunkteam/sherlock';
import { Derivable, derive, isDerivable, unresolved, utils } from '@skunkteam/sherlock';

/**
* Converts a map or array of Derivables or any nested structure containing maps, arrays and Derivables into a single
Expand All @@ -12,23 +12,59 @@ import { Derivable, derive, isDerivable, utils } from '@skunkteam/sherlock';
*
* It only touches Arrays, plain Objects and Derivables, the rest is simply returned inside the Derivable as-is.
*
* @param obj the object to deepunwrap into a derivable
* This function unwraps Derivables one by one, waiting for a Derivable to become resolved before looking for the next
* Derivable.
*
* @param input the object to deepunwrap into a derivable
*/
export function struct<T>(input: T): Derivable<StructUnwrap<T>> {
return (isDerivable(input) ? input : derive(deepUnwrap, input)) as Derivable<StructUnwrap<T>>;
return (isDerivable(input) ? input : derive(deepUnwrapSerial, input)) as Derivable<StructUnwrap<T>>;
}

/**
* Converts a map or array of Derivables or any nested structure containing maps, arrays and Derivables into a single
* Derivable with all nested Derivables unwrapped into it.
*
* ```typescript
* const obj = { key1: atom(123), key2: atom(456) };
* const obj$ = struct(obj);
* expect(obj$.get()).to.deep.equal({ key1: 123, key2: 456 });
* ```
*
* It only touches Arrays, plain Objects and Derivables, the rest is simply returned inside the Derivable as-is.
*
* This function eagerly activates Derivables in the input, so they get activated ("connected") in parallel.
*
* @param input the object to deepunwrap into a derivable
*/
export function parallelStruct<T>(input: T): Derivable<StructUnwrap<T>> {
return (isDerivable(input) ? input : derive(deepUnwrapParallel, input)) as Derivable<StructUnwrap<T>>;
}

function deepUnwrapSerial(obj: any) {
return deepUnwrap(obj, d$ => d$.get());
}

function deepUnwrapParallel(obj: any) {
let resolved = true;
const value = deepUnwrap(obj, d$ => {
resolved &&= d$.resolved;
return d$.value;
});
return resolved ? value : unresolved;
}

function deepUnwrap(obj: any): any {
function deepUnwrap(obj: any, handleDerivable: (d$: Derivable<unknown>) => unknown): any {
if (isDerivable(obj)) {
return obj.get();
return handleDerivable(obj);
}
if (Array.isArray(obj)) {
return obj.map(deepUnwrap);
return obj.map(element => deepUnwrap(element, handleDerivable));
}
if (utils.isPlainObject(obj)) {
const result: Record<string, unknown> = {};
for (const key of Object.keys(obj)) {
result[key] = deepUnwrap(obj[key]);
result[key] = deepUnwrap(obj[key], handleDerivable);
}
return result;
}
Expand Down

0 comments on commit ecc1719

Please sign in to comment.