Skip to content

Commit

Permalink
feat: add augment function (#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
ignatiusmb authored Jul 3, 2024
1 parent dd35bb4 commit 6ae2f07
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 204 deletions.
2 changes: 1 addition & 1 deletion workspace/mauss/src/core/standard/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ suites['identical/']('identical object checks', () => {
assert.ok(std.identical({ x: [{}], y: { a: 0 } }, { x: [{}], y: { a: 0 } }));
});
suites['identical/']('identical clone', async () => {
const { clone } = await import('../../std/object.js');
const { clone } = await import('../../std/index.js');
const data = { a: [1, '', {}], o: { now: new Date() } };
assert.ok(std.identical(data, clone(data)));
});
Expand Down
37 changes: 12 additions & 25 deletions workspace/mauss/src/std/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ Standard modules, augmented and refined.
import { :util } from 'mauss/std';
```

## `augment`

Augments the source object with various utility methods, such as

- `build(keys: string[])` - creates a new object with the keys passed
- `readonly entries` - returns an array of the object entries
- `filter(keys: string[])` - returns the object with only the keys passed
- `freeze()` - deep freezes the object
- `readonly keys` - returns an array of the object keys
- `readonly size` - returns the size of the object

## `clone`

Original function, creates a deep copy of any data type, use sparingly.
Expand All @@ -16,13 +27,9 @@ export function clone<T>(i: T): T;

Creating a copy of a data type, especially an object, is useful for removing the reference to the original object, keeping it clean from unexpected changes and side effects. This is possible because we are creating a new instance, making sure that any mutation or changes that are applied won't affect one or the other.

## `freeze`

Augmented `Object.freeze()`, deep freezes and strongly-typed.

## `iterate`

Original function, iterate over the key-value pair of an object, returns a new object using the pairs returned from the callback function. If callback is omitted, the default behaviour will create a deep copy of the original object.
Original function, iterate over the key-value pair of an object, returns a new object using the pairs returned from the callback function. If callback is omitted, the default behavior will create a deep copy of the original object.

```typescript
export function iterate<T extends object>(
Expand All @@ -36,26 +43,6 @@ export function iterate<T extends object>(

The returned object will be filtered to only contain a key-value pair of the 2-tuple from `fn()`, any other values returned from the callback will be ignored, i.e. `void | Falsy`.

## `pick`

Original function, returns a curried function that constructs a new object consisting of the properties passed to `keys` as an array of strings.

```typescript
export function pick<K extends string[]>(keys: K): <T>(o: T) => Pick<T, K[number]>;
```

In the case of picking the same properties from multiple different objects, we can store it as `unwrap`

```typescript
const unwrap = pick(['a', 'b', 'c']);

unwrap({ ... });
```

## `size`

Convenience method to get the size of an object by checking the `length` of its keys.

## `zip`

Original function, aggregates elements from each of the arrays and returns a single array of objects with the length of the largest array.
Expand Down
57 changes: 0 additions & 57 deletions workspace/mauss/src/std/array.spec.ts

This file was deleted.

15 changes: 0 additions & 15 deletions workspace/mauss/src/std/array.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { suite } from 'uvu';
import * as assert from 'uvu/assert';

import * as ntv from './object.js';
import * as std from './index.js';

const suites = {
'arr/zip': suite('arr/zip'),

'obj/clone': suite('obj/clone'),
'obj/entries': suite('obj/entries'),
'obj/freeze': suite('obj/freeze'),
Expand All @@ -13,9 +15,56 @@ const suites = {
'obj/size': suite('obj/size'),
};

suites['arr/zip']('zip multiple arrays of objects', () => {
const zipped = std.zip(
[{ a: 0 }, { x: 0 }],
[{ b: 0 }, { y: 0 }],
[{ c: 0 }, { z: 0 }],
[{ d: 0 }, { x: 1 }],
);

assert.equal(zipped, [
{ a: 0, b: 0, c: 0, d: 0 },
{ x: 1, y: 0, z: 0 },
]);
});
suites['arr/zip']('zip multiple uneven arrays', () => {
const zipped = std.zip(
[{ a: 0 }],
[{ a: 1 }, { x: 0 }],
[{ b: 0 }, { y: 0 }],
[{ c: 0 }, { z: 0 }, { v: 0 }],
[{ d: 0 }, { x: 1 }],
[null, null, { w: 0 }, { w: 0 }],
[null, null, { x: 0 }, { x: 0 }],
[null, null, { v: 1 }, { y: 0 }],
);

assert.equal(zipped, [
{ a: 1, b: 0, c: 0, d: 0 },
{ x: 1, y: 0, z: 0 },
{ v: 1, w: 0, x: 0 },
{ w: 0, x: 0, y: 0 },
]);
});
suites['arr/zip']('zip remove all nullish index', () => {
const zipped = std.zip(
[{ a: 0 }, null, { x: 0 }, null, { a: 0 }, undefined],
[{ b: 0 }, null, { y: 0 }, undefined, { b: 0 }, null],
[{ c: 0 }, null, { z: 0 }, undefined, { c: 0 }, null],
[{ d: 0 }, null, { x: 1 }, null, { d: 0 }, undefined],
);

assert.equal(zipped, [
{ a: 0, b: 0, c: 0, d: 0 },
{ x: 1, y: 0, z: 0 },
{ a: 0, b: 0, c: 0, d: 0 },
]);
});

suites['obj/clone']('clone any possible data type', () => {
const base = { arr: [0, 'hi', /wut/], obj: { now: new Date() } };
const cloned = ntv.clone(base);
const cloned = std.clone(base);

assert.ok(base !== cloned);
assert.ok(base.arr !== cloned.arr);
Expand All @@ -28,28 +77,32 @@ suites['obj/clone']('clone any possible data type', () => {
});

suites['obj/entries']('return object entries', () => {
assert.equal(ntv.entries({ hello: 'world', foo: 0, bar: { baz: 1 } }), [
assert.equal(std.augment({ hello: 'world', foo: 0, bar: { baz: 1 } }).entries, [
['hello', 'world'],
['foo', 0],
['bar', { baz: 1 }],
]);
});

suites['obj/freeze']('deep freezes nested objects', () => {
const nested = ntv.freeze({
foo: { a: 0 },
bar: { b: 1 },
});
const nested = std
.augment({
foo: { a: 0 },
bar: { b: 1 },
})
.freeze();

assert.ok(Object.isFrozen(nested));
assert.ok(Object.isFrozen(nested.foo));
assert.ok(Object.isFrozen(nested.bar));
});
suites['obj/freeze']('deep freeze ignore function', () => {
const nested = ntv.freeze({
identity: (v: any) => v,
namespace: { a() {} },
});
const nested = std
.augment({
identity: (v: any) => v,
namespace: { a() {} },
})
.freeze();

assert.ok(!Object.isFrozen(nested.identity));
assert.equal(nested.identity(0), 0);
Expand All @@ -70,8 +123,8 @@ suites['obj/iterate']('iterate over nested objects', () => {
);

assert.equal(
ntv.iterate(nested, ([month, v]) => {
const updated = ntv.iterate(v, ([currency, { income, expense }]) => {
std.iterate(nested, ([month, v]) => {
const updated = std.iterate(v, ([currency, { income, expense }]) => {
return [currency, { balance: income - expense }];
});
return [month, updated];
Expand All @@ -92,43 +145,41 @@ suites['obj/iterate']('iterate over nested objects', () => {
});
suites['obj/iterate']('iterate with empty/falsy return', () => {
assert.equal(
ntv.iterate({}, ([]) => {}),
std.iterate({}, ([]) => {}),
{},
);

assert.equal(
ntv.iterate(
std.iterate(
{ a: '0', b: 1, c: null, d: '3', e: undefined, f: false },
([k, v]) => v != null && v !== false && [k, v],
),
{ a: '0', b: 1, d: '3' },
);

type Nested = { [P in 'a' | 'b']?: { [K in 'x' | 'y']: { foo: string } } };
ntv.iterate({ a: { x: { foo: 'ax' } } } as Nested, ([parent, v]) => {
std.iterate({ a: { x: { foo: 'ax' } } } as Nested, ([parent, v]) => {
assert.equal(parent, 'a');
v &&
ntv.iterate(v, ([key, { foo }]) => {
std.iterate(v, ([key, { foo }]) => {
assert.equal(key, 'x');
assert.equal(foo, 'ax');
});
});
});
suites['obj/iterate']('iterate creates deep copy', () => {
const original = { x: 1, y: { z: 'foo' } };
const copy = ntv.iterate(original);
const copy = std.iterate(original);
assert.ok(original !== copy);
assert.ok(original.y !== copy.y);
});

suites['obj/keys']('return object keys', () => {
assert.equal(ntv.keys({ a: 0, b: 1, c: 2 }), ['a', 'b', 'c']);
assert.equal(std.augment({ a: 0, b: 1, c: 2 }).keys, ['a', 'b', 'c']);
});

suites['obj/pick']('pick properties from an object', () => {
const { build, filter } = ntv.pick(['a', 'b', 'c', 'd', 'e', 'z']);

assert.equal(build({ a: 0, c: 'b', z: null }), {
assert.equal(std.augment({ a: 0, c: 'b', z: null }).build(['a', 'b', 'c', 'd', 'e', 'z']), {
a: 0,
b: null,
c: 'b',
Expand All @@ -137,15 +188,14 @@ suites['obj/pick']('pick properties from an object', () => {
z: null,
});

assert.equal(filter({ a: 0, c: 'b', y: undefined, z: null }), {
a: 0,
c: 'b',
z: null,
});
assert.equal(
std.augment({ a: 0, c: 'b', y: undefined, z: null }).filter(['a', 'b', 'c', 'd', 'e', 'z']),
{ a: 0, c: 'b', z: null },
);
});

suites['obj/size']('return size of an object', () => {
assert.equal(ntv.size({ a: 0, b: 1, c: 2 }), 3);
assert.equal(std.augment({ a: 0, b: 1, c: 2 }).size, 3);
});

Object.values(suites).forEach((v) => v.run());
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { entries } from './object.js';
import { augment } from './index.js';

entries<{}>({});
augment<{}>({});

// ---- errors ----

// @ts-expect-error - error on empty argument
entries();
augment();
// @ts-expect-error - error on non-object type
entries(null);
augment(null);
Loading

0 comments on commit 6ae2f07

Please sign in to comment.