# `@livy/util`

This package contains common utilities for building components for the [Livy](https://github.com/loilo/livy#readme) logger.

---

**Table of Contents:**

- [General Purpose Utilities](#general-purpose-utilities)
  - [Helpers](#helpers)
  - [Types](#types)
  - [`Environment`](#environment)
  - [`ValidatableSet`](#validatableset)
  - [`GatedSet`](#gatedset)
  - [`HTML`](#html)
  - [`isResettableInterface`](#isresettableinterface)
  - [`Mixin`](#mixin)
  - [`Timer`](#timer)
- [Formatter-Related Utilities](#formatter-related-utilities)
  - [`IncludedRecordProperties`](#includedrecordproperties)
  - [`isFormattableHandlerInterface`](#isformattablehandlerinterface)
  - [`AbstractBatchFormatter`](#abstractbatchformatter)
  - [`LineFomatter`](#linefomatter)
- [Handler-Related Utilities](#handler-related-utilities)
  - [`isClosableHandlerInterface`](#isclosablehandlerinterface)
  - [`isProcessableHandlerInterface`](#isprocessablehandlerinterface)
  - [`isSyncHandlerInterface`](#issynchandlerinterface)
  - [`MirrorSyncHandlerMixin`](#mirrorsynchandlermixin)
  - [`ProcessableHandlerMixin`](#processablehandlermixin)
  - [`RespectLevelMixin`](#respectlevelmixin)
  - [`FormattableHandlerMixin`](#formattablehandlermixin)
  - [`AbstractBatchHandler`](#abstractbatchhandler)
  - [`AbstractLevelBubbleHandler`](#abstractlevelbubblehandler)
  - [`AbstractFormattingProcessingHandler`](#abstractformattingprocessinghandler)

---

## General Purpose Utilities

### [Helpers](https://github.com/loilo/livy/tree/master/packages/util/src/helpers.ts)

There's also a whole bunch general-purpose helper functions in this file. They are thoroughly documented, including examples, so you're probably best off to just dive into the source code.

### [Types](https://github.com/loilo/livy/tree/master/packages/util/src/types.ts)

For TypeScript users, there are also some handy types to be found in this file.

### [`Environment`](https://github.com/loilo/livy/tree/master/packages/util/src/environment.ts)

Determines whether the runtime is Node.js or a browser and derives related environment-specific data.

```js
const Environment = require('@livy/util/lib/environment')

Environment.isNodeJs // `true` or `false`
Environment.isBrowser // `true` or `false`
Environment.EOL // os.EOL in Node.js, '\n' otherwise
```

### [`ValidatableSet`](https://github.com/loilo/livy/tree/master/packages/util/src/validatable-set.ts)

An extended [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) with the array methods [`some`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) and [`every`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) on top:

<!-- prettier-ignore -->

```js
const { ValidatableSet } = require('@livy/util/lib/validatable-set')

const set = new ValidatableSet([1, 2, 3])

set.every(item => typeof item === 'number') // true
set.some(item => item < 0)                  // false

// Behaves like the corresponding array methods on empty sets:
set.clear()
set.every(anyCondition) // false
set.some(anyCondition)  // true
```

### [`GatedSet`](https://github.com/loilo/livy/tree/master/packages/util/src/gated-set.ts)

An extended [`ValidatableSet`](#validatableset) that allows to validate and reject values that go inside the set.

<!-- prettier-ignore -->

```js
const { GatedSet } = require('@livy/util/lib/gated-set')

const integers = new GatedSet(value => {
  if (typeof value !== 'number' || !Number.isInteger(value)) {
    throw new TypeError('Set only accepts integers')
  }
})

integers.add(5)     // OK
integers.add(-50)   // OK
integers.add(5.5)   // Throws TypeError
integers.add(NaN)   // Throws TypeError
integers.add('foo') // Throws TypeError
```

### [`HTML`](https://github.com/loilo/livy/tree/master/packages/util/src/html.ts)

A function/template tag that allows you to easily construct HTML strings without having to worry about escaping:

```js
const { HTML } = require('@livy/util/lib/html')

const snippet = '<em>awesome</em>'

HTML`<p>Markup is ${snippet}!</p>`.toString()
// '<p>Markup is &lt;em&gt;awesome&lt;/em&gt;!</p>'

// Use HTML as template tag in interpolations to avoid escaping:
HTML`<p>Markup is ${HTML`<em>awesome</em>`}!</p>`.toString()
// '<p>Markup is <em>awesome</em>!</p>'

// Use HTML as a function to avoid escaping of variables:
HTML`<p>Markup is ${HTML(snippet)}!</p>`.toString()
// '<p>Markup is <em>awesome</em>!</p>'
```

### [`isResettableInterface`](https://github.com/loilo/livy/tree/master/packages/util/src/is-resettable-interface.ts)

Check whether a handler or processors is resettable.

> See also: [`ResettableInterface`](https://npmjs.com/package/@livy/contracts#resettableinterface)

### [`Mixin`](https://github.com/loilo/livy/tree/master/packages/util/src/mixin.ts)

An approach to use TypeScript mixins slightly altered from [Mixin classes](https://mariusschulz.com/blog/mixin-classes-in-typescript).

Supports extending abstract classes and correctly type-checks `super()` calls for the tradeoff of having to wrap the mixin in an additional function call:

```ts
// Mixin definition:
const WriteAccess = Mixin(
  _ =>
    class extends _ {
      write(file: string, content: string) {
        // Do some write action
      }
    }
)

// Mixin usage:
class User {
  constructor(protected name: string) {}
}

class PrivilegedUser extends WriteAccess(User) {
  constructor(name: string, protected role: 'editor' | 'admin') {
    super(name) // <- type-hinted!
  }

  otherMethod() {
    this.write('/some/file/path', 'some content') // <- type-hinted!
  }
}
```

### [`Timer`](https://github.com/loilo/livy/tree/master/packages/util/src/timer.ts)

A cross-runtime performance timer implementation:

```js
const { Timer } = require('@livy/util/lib/timer')

const timer = new Timer()

// Start the timer
timer.start()

// ... do some work ...

// Get number of milliseconds elapsed since timer.start()
timer.get()

// ... do some work ...

// Still get number of milliseconds elapsed since timer.start()
timer.get()

// Stop and reset the timer
timer.reset()
```

## Formatter-Related Utilities

### [`IncludedRecordProperties`](https://github.com/loilo/livy/tree/master/packages/util/src/formatters/included-record-properties.ts)

A simple TypeScript type which represents an object with all [`LogRecord`](https://github.com/loilo/livy#log-records) properties mapped to boolean values.

This can be useful as type of a formatter's option that determines which parts of a log record should be represented in the formatted output — see for example the [`LineFormatter`](https://github.com/loilo/livy/tree/master/packages/util/src/formatters/line-formatter.ts) implementation.

### [`AbstractBatchFormatter`](https://github.com/loilo/livy/tree/master/packages/util/src/formatters/abstract-batch-formatter.ts)

A base class for formatters to extend which implements `formatBatch` as a series of `format` calls, joined by a delimiter (which by default is the `EOL` determined by the [environment](#Environment), can be changed by overriding the `batchDelimiter` property):

```js
const { AbstractBatchFormatter } = require('@livy/util/lib/abstract-batch-formatter')

class QuestionableFormatter extends AbstractBatchFormatter {
  format(record) {
    return '?'
  }
}

const q = new QuestionableFormatter()
q.format({ ... }) // '?'
q.formatBatch([{ ... }, { ... }, { ... }]) // '?\n?\n?'
```

> See also: [`FormatterInterface`](https://npmjs.com/package/@livy/contracts#formatterinterface)

### [`LineFomatter`](https://github.com/loilo/livy/tree/master/packages/util/src/formatters/line-formatter.ts)

This formatter — because it's very ubiquitous throughout the project — is implemented here to remove complexity from the dependency graph (i.e. to avoid a mutual dependency with `@livy/line-formatter`).

See [`@livy/line-formatter`](https://github.com/loilo/livy/tree/master/packages/line-formatter#readme) for documentation.

## Handler-Related Utilities

This includes various base classes, mixins and check functions for implementing handlers.

### [`isClosableHandlerInterface`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/is-closable-handler-interface.ts)

Check whether a handler is [closable](https://npmjs.com/package/@livy/contracts#closablehandlerinterface).

### [`isFormattableHandlerInterface`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/is-formattable-handler-interface.ts)

Checks whether a handler implements the [`FormattableHandlerInterface`](https://npmjs.com/package/@livy/contracts#formattablehandlerinterface)

### [`isProcessableHandlerInterface`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/is-processable-handler-interface.ts)

Check whether a handler can [use processors](https://npmjs.com/package/@livy/contracts#processablehandlerinterface).

### [`isSyncHandlerInterface`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/is-sync-handler-interface.ts)

Check whether a handler can be [invoked synchronously](https://npmjs.com/package/@livy/contracts#handlerinterface).

### [`MirrorSyncHandlerMixin`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/mirror-sync-handler-mixin.ts)

Implements the mandatory asynchronous handler methods `handle` and `handleBatch` by replicating the behavior of their corresponding synchronous methods:

<!-- prettier-ignore -->

```js
const SomeBaseClass = require('SomeBaseClass')
const {
  MirrorSyncHandlerMixin
} = require('@livy/util/lib/handlers/mirror-sync-handler-mixin')

class Handler extends MirrorSyncHandlerMixin(SomeBaseClass) {
  handleSync(record) {
    // ...
  }

  handleBatchSync(record) {
    // ...
  }
}

const handler = new Handler()
handler.handle()      // calls handler.handleSync()
handler.handleBatch() // calls handler.handleBatchSync()
```

> See also: [`SyncHandlerInterface`](https://npmjs.com/package/@livy/contracts#handlerinterface)

### [`ProcessableHandlerMixin`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/processable-handler-mixin.ts)

Makes a handler able to work with processors, implementing the [`ProcessableHandlerInterface`](https://npmjs.com/package/@livy/contracts#processablehandlerinterface).

It adds:

- a public `processors` [set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
- an internal `processRecord` method which your handler may call with a [log record](https://npmjs.com/package/@livy/contracts#logrecord) to run all registered processors on it.
- an internal `resetProcessors` method which resets all resettable processors.
- a basic `reset` method which calls `resetProcessors`, thus making the handler [resettable](#isresettableinterface)

### [`RespectLevelMixin`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/respect-level-mixin.ts)

Adds a public `level` property which defaults to `'debug'`, as well as a `isHandling`
implementation based on that level.

> See also: [`HandlerInterface`](https://npmjs.com/package/@livy/contracts#handlerinterface)

### [`FormattableHandlerMixin`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/formattable-handler-mixin.ts)

Adds a `formatter` property to the applied-to class with support for an implicit default formatter:

```js
const SomeBaseClass = require('SomeBaseClass')
const SomeFormatter = require('SomeFormatter')
const {
  FormattableHandlerMixin
} = require('@livy/util/lib/handlers/formattable-handler-mixin')

class Handler extends FormattableHandlerMixin(SomeBaseClass) {
  /**
   * Allow setting a formatter through a handler option
   */
  constructor({ formatter, ...options }) {
    // Pass other options up
    super(options)

    // Set the `this.explicitFormatter` property.
    // If the user provides no `formatter` option, it will be undefined
    // and `this.formatter` will return the default formatter
    this.explicitFormatter = formatter
  }

  /**
   * Define the default formatter (required)
   */
  get defaultFormatter() {
    return new SomeFormatter()
  }

  handle(record) {
    // ...
  }
}
```

> See also: [`FormattableHandlerInterface`](https://npmjs.com/package/@livy/contracts#formattablehandlerinterface)

### [`AbstractBatchHandler`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/abstract-batch-handler.ts)

This is a base handler class that implements the `handleBatch` and `handleBatchSync` methods by sequentially executing `handle`/`handleSync` for each passed record.

> See also: [`HandlerInterface`](https://npmjs.com/package/@livy/contracts#handlerinterface)

### [`AbstractLevelBubbleHandler`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/abstract-level-bubble-handler.ts)

Base handler class (extending the [`AbstractBatchHandler`](#abstractbatchhandler)) which adds a `level` and a `bubble` option and implements the `isHandling` method based on the configured level:

```js
const {
  AbstractLevelBubbleHandler
} = require('@livy/util/lib/handlers/abstract-level-bubble-handler')

class Handler extends AbstractLevelBubbleHandler {
  handle(record) {
    // ...do something with the record...

    // Indicate bubbling behavior based on the `bubble` option:
    return !this.bubble
  }
}
```

See also:

- [Bubbling](https://github.com/loilo/livy#bubbling)
- [`HandlerInterface`](https://npmjs.com/package/@livy/contracts#handlerinterface)
- [`SyncHandlerInterface`](https://npmjs.com/package/@livy/contracts#handlerinterface)

### [`AbstractFormattingProcessingHandler`](https://github.com/loilo/livy/tree/master/packages/util/src/handlers/abstract-formatting-processing-handler.ts)

Base handler class extending the [`AbstractLevelBubbleHandler`](#abstractlevelbubblehandler) with the [`FormattableHandlerMixin`](#formattablehandlermixin) and [`ProcessableHandlerMixin`](#processablehandlermixin). It completely abstracts the nitty gritty details of writing a handler with formatter and processor support away from you so you only have to implement a `write` method:

```js
const {
  AbstractFormattingProcessingHandler
} = require('@livy/util/lib/handlers/abstract-formatting-processing-handler')

class FileHandler extends AbstractFormattingProcessingHandler {
  async write(record, formattedRecord) {
    await someFileHandler.write(formattedRecord)
  }
}
```

There's also `AbstractSyncFormattingProcessingHandler` to implement a synchronous handler by implementing `writeSync`:

```js
const {
  AbstractSyncFormattingProcessingHandler
} = require('@livy/util/lib/handlers/abstract-formatting-processing-handler')

class SyncFileHandler extends AbstractSyncFormattingProcessingHandler {
  writeSync(record, formattedRecord) {
    someFileHandler.writeSync(formattedRecord)
  }

  // You can still implement your own asynchronous `write` method,
  // but if you omit it, it will just fall back to `writeSync` instead
  async write(record, formattedRecord) {
    await someFileHandler.write(formattedRecord)
  }
}
```
