npm package: @healthstreet/fms-api-client
Description: A FileMaker Data API client designed to allow easier interaction with a FileMaker database from a web environment. Particularly in a distributed compute environment such as AWS Lambda.
Author: Lui de la Parra, Will Slaughter
Latest version: 2.2.6

# fms-api-client

[![Build Status](https://travis-ci.com/Luidog/fms-api-client.png?branch=master)](https://travis-ci.com/Luidog/fms-api-client) [![Known Vulnerabilities](https://snyk.io/test/github/Luidog/fms-api-client/badge.svg?targetFile=package.json)](https://snyk.io/test/github/Luidog/fms-api-client?targetFile=package.json) [![Coverage Status](https://coveralls.io/repos/github/Luidog/fms-api-client/badge.svg?branch=master)](https://coveralls.io/github/Luidog/fms-api-client?branch=master) [![GitHub issues](https://img.shields.io/github/issues/Luidog/fms-api-client.svg?style=plastic)](https://github.com/Luidog/fms-api-client/issues) [![Github commits (since latest release)](https://img.shields.io/github/commits-since/luidog/fms-api-client/latest.svg)](https://img.shields.io/github/issues/Luidog/fms-api-client.svg) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![GitHub license](https://img.shields.io/github/license/Luidog/fms-api-client.svg)](https://github.com/Luidog/fms-api-client/blob/master/LICENSE.md)

A FileMaker Data API client designed to allow easier interaction with a FileMaker database from a web environment. This client abstracts the FileMaker 17 & 18 Data API into class based methods.

[fms-api-client documentation](https://luidog.github.io/fms-api-client/)

## Table of Contents

- [License](#license)
- [Installation](#installation)
- [Usage](#usage)
  - [Introduction](#introduction)
  - [Parameter Syntax](#parameter-syntax)
    - [Script Array Syntax](#script-array-syntax)
    - [Portals Array Syntax](#portals-array-syntax)
    - [Data Syntax](#data-syntax)
    - [Sort Syntax](#sort-syntax)
    - [Query Syntax](#query-syntax)
  - [Datastore Connection](#datastore-connection)
  - [Client Creation](#client-creation)
  - [Client Use](#client-use)
  - [Request Queue](#request-queue)
  - [Data API Sessions](#data-api-sessions)
    - [Login Method](#login-method)
    - [Logout Method](#logout-method)
  - [Client Methods](#client-methods)
    - [Product Info](#product-info)
    - [Get Databases](#get-databases)
    - [Get Layouts](#get-layouts)
    - [Get Scripts](#get-scripts)
    - [Get Layout](#get-layout)
    - [Create Records](#create-records)
    - [Duplicate Record](#duplicate-record)
    - [Get Record Details](#get-record-details)
    - [List Records](#list-records)
    - [Find Records](#find-records)
    - [Edit Records](#edit-records)
    - [Delete Records](#delete-records)
    - [Trigger Script](#trigger-script)
    - [Run Multiple Scripts](#run-multiple-scripts)
    - [Upload Files](#upload-files)
    - [Set Session Globals](#set-session-globals)
    - [Client Status](#client-status)
    - [Client Reset](#client-reset)
  - [Utilities](#utilities)
    - [Record Id Utility](#record-id-utility)
      - [Record Id Utility Results](#record-id-utility-results)
    - [Field Data Utility](#field-data-utility)
      - [Field Data Utility Results](#field-data-utility-results)
    - [Transform Utility](#transform-utility)
      - [Transform Utility Results](#transform-utility-results)
    - [Container Data Utility](#container-data-utility)
    - [Databases Utility](#databases-utility)
    - [Product Info Utility](#product-info-utility)
  - [Additional Client Capabilities](#additional-client-capabilities)
    - [Data Merge](#data-merge)
    - [Custom Request Agents](#custom-request-agents)
    - [Custom Request Parameters](#custom-request-parameters)
    - [Proxies](#proxies)
- [Tests](#tests)
- [Dependencies](#dependencies)
- [Dev Dependencies](#dev-dependencies)

## License

MIT © Lui de la Parra

## Installation

```sh
npm install --save fms-api-client
```

## Usage

### Introduction

The fms-api-client is a wrapper around the [FileMaker Data API](https://fm.mutesymphony.com/fmi/data/apidoc/). Much :heart: to FileMaker for their work on the Data API. This client attempts to follow the terminology and conventions used by FileMaker wherever possible. The client uses a lightweight datastore to hold Data API connections. For more information about datastore configuration see the [datastore connection section](#datastore-connection) and the [marpat project](https://github.com/Luidog/marpat).

Each client committed to the datastore will automatically handle Data API Sessions. If required, clients can also manually open or close their FileMaker sessions by calling either the `client.login()` method or the `client.logout()` method. To remove a client from a datastore and log out a session call `client.destroy()`. For more information on Data API Session handling see the [Data API sessions section](#data-api-sessions).

All client methods accept parameters as defined by FileMaker. The client also allows for an expanded parameter syntax designed to make interacting with the client easier. Scripts and portals can be defined as arrays. Find queries can be defined as either an object or an array. For more information on the syntax supported by the client see [the parameter syntax section](#parameter-syntax).

In addition to the expanded syntax the client will also automatically parse arrays, objects, and numbers to adhere to the requirements of the Data API. The `limit` and `offset` parameters can be either strings or a numbers. The client will also automatically convert `limit`, `find`, and `offset` parameters into their underscored conterparts as needed. Additionally, if a script result is valid JSON it will be automatically parsed for you by the client.

All methods on the client return promises and each method will reject with a message and code upon encountering an error. All messages and codes follow the FileMaker Data API codes where applicable.

The client will queue concurrent requests to the Data API. The client can be configured with a [concurrency setting](#client-creation). The client will attempt ensure that each request to the Data API is made with an valid token which is not in use. This behavior prevents request session collisions. For more information on Data API Session handling see the [request queue section](#request-queue).

The client also provides utility modules to aid in working with FileMaker Data API Results. The provided utility modules are `fieldData`, `recordId`, `containerData`, and `transform`. These utilities will accept and return either an object or an array objects. For more information on the utility modules see the [utilities section](#utilities).

### Parameter Syntax

The client supports the same parameter syntax as is found in the [Data API Documentation](https://fm.mutesymphony.com/fmi/data/apidoc/). Where appropriate and useful the client also allows additional parameters.

#### Script Array Syntax

The custom script parameter follows the following syntax:

```json
{
  "scripts": [
    {
      "name": "At Mos Eisley",
      "phase": "presort",
      "param": "awesome bar"
    },
    {
      "name": "First Shot",
      "phase": "prerequest",
      "param": "Han"
    },
    {
      "name": "Moof Milker",
      "param": "Greedo"
    }
  ]
}
```

> File [./examples/schema/scripts-array-schema.json](./examples/schema/scripts-array-schema.json)

Following the Data API, the `prerequest` phase occurs before executing request and sorting of records, the `presort` phase after executing request and before sorting records. Not specifying a phase will run the script after the request and sorting are executed.

**Note:** The FileMaker script and portal syntax will override the alternative scripts and portals parameter syntax.

#### Portals Array Syntax

The custom portals parameter follows the following syntax:

```json
{
  "portals": [
    { "name": "planets", "limit": 1, "offset": 1 },
    { "name": "vehicles", "limit": 2 }
  ]
}
```

> File [./examples/schema/portals-array-schema.json](./examples/schema/portals-array-schema.json)

If a portals array is not used all portals on a queried layout will be returned.

**Note:** The FileMaker script and portal syntax will override the alternative scripts and portals parameter syntax.

#### Data Syntax

Arrays and objects are stringified before being inserted into field or portal data.

```json
{
  "data": {
    "name": "Yoda",
    "Vehicles::name": "The Force"
  }
}
```

> File [./examples/schema/data-schema.json](./examples/schema/data-schema.json)

Any property not nested in a `portalData` property will be moved into the `fieldData` property.

#### Sort Syntax

The client accepts the same sort parameters as the Data API.

```json
{
  "sort": [
    { "fieldName": "name", "sortOrder": "ascend" },
    { "fieldName": "modificationTimestamp", "sortOrder": "descend" }
  ]
}
```

> File [./examples/schema/sort-schema.json](./examples/schema/sort-schema.json)

#### Query Syntax

When using the `find` method a query is required. The query can either be a single json object or an array of json objects.

```json
{
  "query": [
    {
      "name": "Han solo",
      "Vehicles::name": "Millenium Falcon"
    },
    {
      "name": "Luke Skywalker",
      "Vehicles::name": "X-Wing Starfighter"
    },
    {
      "age": ">10000"
    }
  ]
}
```

> File [./examples/schema/query-schema.json](./examples/schema/query-schema.json)

### Datastore Connection

The connect method must be called before the FileMaker class is used. The connect method is not currently exposed by fms-api-client, but from the marpat dependency. marpat is a fork of camo. Thanks and love to [Scott Robinson](https://github.com/scottwrobinson) for his creation and maintenance of camo.

marpat is designed to allow the use of multiple datastores with the focus on encrypted file storage and project flexibility.

For more information on marpat and the different types of supported storage visit [marpat](https://github.com/Luidog/marpat)

```js
const { connect } = require('marpat');
connect('nedb://memory');
```

> Excerpt from [./examples/index.js](./examples/index.js#L27-L28)

### Client Creation

After connecting to a datastore you can import and create clients. A client is created using the create method on the Filemaker class. The FileMaker class accepts an object with the following properties:

| Property      |         Type         |                                     Description                                     |
| ------------- | :------------------: | :---------------------------------------------------------------------------------: |
| database      | <code>String</code>  |                        The FileMaker database to connect to                         |
| server        | <code>String</code>  | The FileMaker server to use as the host. **Note:** Must be an http or https Domain. |
| user          | <code>String</code>  |     The FileMaker user account to be used when authenticating into the Data API     |
| password      | <code>String</code>  |                       The FileMaker user account's password.                        |
| [name]        | <code>String</code>  |                               A name for the client.                                |
| [usage]       | <code>Boolean</code> |          Track Data API usage for this client. **Note:** Default is `true`          |
| [timeout]     | <code>Number</code>  |     The default timeout time for requests **Note:** Default is 0, (no timeout)      |
| [concurrency] | <code>Number</code>  |          The number of concurrent requests that will be made to FileMaker           |
| [proxy]       | <code>Object</code>  |                             settings for a proxy server                             |
| [agent]       | <code>Object</code>  |                         settings for a custom request agent                         |

:warning: You should only use the agent parameter when absolutely necessary. The Data API was designed to be used on https. Deviating from the intended use should be done with caution.

```js
const client = Filemaker.create({
  name: process.env.CLIENT_NAME,
  database: process.env.DATABASE,
  concurrency: 3,
  server: process.env.SERVER,
  user: process.env.USERNAME,
  password: process.env.PASSWORD,
  usage: process.env.CLIENT_USAGE_TRACKING
});
```

> Excerpt from [./examples/index.js](./examples/index.js#L32-L40)

**Note:** The server must be an http or https domain.

A client can be used directly after saving it. The `client.save()` method takes no arguments and will either reject with an error or resolve with a useable client. The client will automatically handle Data API session creation and expiration. Once a client is saved it will be stored on the datastore for reuse later.

```js
    return client.save();
  })
  .then(client => authentication(client))
  .then(client => metadata(client))
  .then(client => creates(client))
  .then(client => duplicate(client))
  .then(client => gets(client))
  .then(client => lists(client))
  .then(client => finds(client))
  .then(client => edits(client))
  .then(client => scripts(client))
  .then(client => script(client))
  .then(client => globals(client))
  .then(client => deletes(client))
  .then(client => uploads(client))
  .then(client => utilities(client))
```

> Excerpt from [./examples/index.js](./examples/index.js#L43-L58)
> A client can be removed using either the `client.destroy()` method, the `Filemaker.deleteOne(query)` method or the `Filemaker.deleteMany(query)` method.
> **Note** Only the `client.destroy()` method will close a FileMaker session. Any client removed using the the `Filemaker.deleteOne(query)` method or the `Filemaker.deleteMany(query)` method will not log out before being destroyed.

### Client Use

A client can be used after it is created and saved or recalled from the datastore. The `Filemaker.find(query)` or `Filemaker.findOne(query)` methods can be used to recall clients. The `Filemaker.findOne(query)` method will return either one client or null. The `Filemaker.find(query)` will return either an empty array or an array of clients. All public methods on the client return promises.

```js
const createManyRecords = client =>
  Promise.all([
    client.create('Heroes', { name: 'Anakin Skywalker' }, { merge: true }),
    client.create('Heroes', { name: 'Obi-Wan' }, { merge: true }),
    client.create('Heroes', { name: 'Yoda' }, { merge: true })
  ]).then(result => log('create-many-records-example', result));
```

> Excerpt from [./examples/create.examples.js](./examples/create.examples.js#L28-L33)

Results:

```json
[
  {
    "name": "Anakin Skywalker",
    "recordId": "1138",
    "modId": "327"
  },
  {
    "name": "Obi-Wan",
    "recordId": "1138",
    "modId": "327"
  },
  {
    "name": "Yoda",
    "recordId": "1138",
    "modId": "327"
  }
]
```

> File [./examples/results/create-many-records-example.json](./examples/results/create-many-records-example.json)

### Request Queue

The client will automatically queue a requests to the FileMaker Data API if there are no available sessions to make the request. The client will ensure that a session is not in use or expired before it uses a session token in a request. This functionality is designed to prevent session collisions. The client will open and use a new session up to the configured concurrency limit. The concurrency limit is set when a client is created. By default the concurrency limit is one.

### Data API Sessions

The client will automatically handle creating and closing Data API sessions. If a new session is required the client will authenticate and generate a new session or queue requests if the session limit has been reached.

The Data API session is also monitored, updated, and saved as the client interacts with the Data API. The client will always attempt to reuse a valid token whenever possible.

The client contains two methods related to Data API sessions. These methods are `client.login()` and `client.logout()`. The login method is used to start a Data API session and the logout method will end a Data API session.

#### Login Method

The client will automatically call the login method if it does not have a valid token. This method returns an object with a token property. This method will also save the token to the client's connection for future use.

`client.login()`

```js
const login = client =>
  client.login().then(result => log('client-login-example', result));
```

> Excerpt from [./examples/authentication.examples.js](./examples/authentication.examples.js#L6-L7)

#### Logout Method

The logout method is used to end a Data API session. This method will also remove the current client's authentication token. The logout method accepts an optional id parameter. The logout method will use the id parameter as the session to target for ending.

`client.logout(id)`

```js
const logout = client =>
  client
    .login()
    .then(() => client.logout())
    .then(result => log('client-logout-example', result));
```

> Excerpt from [./examples/authentication.examples.js](./examples/authentication.examples.js#L11-L15)

### Client Methods

#### Product Info

A client can get server product info. The productInfo method will return metadata about the FileMaker server the client is configured to use.

`client.productInfo()`

```js
const productInfo = client =>
  client.productInfo().then(result => log('product-info-example', result));
```

> Excerpt from [./examples/metadata.examples.js](./examples/metadata.examples.js#L6-L7)
> Result:

```json
{
  "name": "FileMaker Data API Engine",
  "buildDate": "07/05/2019",
  "version": "18.0.2.217",
  "dateFormat": "MM/dd/yyyy",
  "timeFormat": "HH:mm:ss",
  "timeStampFormat": "MM/dd/yyyy HH:mm:ss"
}
```

> File [./examples/results/product-info-example.json](./examples/results/product-info-example.json)

#### Get Databases

A client can get the databases accessible using the configured credentials. This method will return all databases hosted by the currently configured server that the client can connect to. An alternative set of credentials can be passed to this method to check what databases alternate credentials are able to access.

`client.databases(credentials)`

```js
const databases = client =>
  client.databases().then(result => log('databases-info-example', result));
```

> Excerpt from [./examples/metadata.examples.js](./examples/metadata.examples.js#L11-L12)

Result:

```json
{
  "databases": [
    {
      "name": "fms-api-app"
    },
    {
      "name": "node-red-app"
    }
  ]
}
```

> File [./examples/results/databases-info-example.json](./examples/results/databases-info-example.json)

#### Get Layouts

The layouts method will return a list of layouts accessible by the configured client.

`client.layouts()`

```js
const layouts = client =>
  client.layouts().then(result => log('layouts-example', result));
```

> Excerpt from [./examples/metadata.examples.js](./examples/metadata.examples.js#L16-L17)

Result:

```json
{
  "layouts": [
    {
      "name": "Parameter Storage Demo"
    },
    {
      "name": "Chalmuns"
    },
    {
      "name": "Card Windows",
      "isFolder": true,
      "folderLayoutNames": [
        {
          "name": "Authorization Window"
        }
      ]
    },
    {
      "name": "API Integration Layouts",
      "isFolder": true,
      "folderLayoutNames": [
        {
          "name": "authentication"
        }
      ]
    },
    {
      "name": "Base Tables",
      "isFolder": true,
      "folderLayoutNames": [
        {
          "name": "RequestLog"
        },
        {
          "name": "SyncLog"
        },
        {
          "name": "AuthenticationStore"
        }
      ]
    },
    {
      "name": "Heroes"
    },
    {
      "name": "Transform"
    },
    {
      "name": "Hero Search"
    },
    {
      "name": "Hero"
    },
    {
      "name": "Globals"
    },
    {
      "name": "Scripts"
    },
    {
      "name": "Logs"
    },
    {
      "name": "Planets"
    },
    {
      "name": "Driods"
    },
    {
      "name": "Vehicles"
    },
    {
      "name": "Species"
    },
    {
      "name": "Images"
    }
  ]
}
```

> File [./examples/results/layouts-example.json](./examples/results/layouts-example.json)

#### Get Scripts

The scripts method will return metadata for the scripts accessible by the configured client.

`client.scripts()`

```js
const scripts = client =>
  client
    .scripts(process.env.LAYOUT)
    .then(result => log('scripts-example', result));
```

> Excerpt from [./examples/metadata.examples.js](./examples/metadata.examples.js#L28-L31)

Result:

```json
{
  "scripts": [
    {
      "name": "Upon Open",
      "isFolder": false
    },
    {
      "name": "FMS Triggered Script",
      "isFolder": false
    },
    {
      "name": "node-red-script",
      "isFolder": false
    },
    {
      "name": "example script",
      "isFolder": false
    },
    {
      "name": "Error Script",
      "isFolder": false
    },
    {
      "name": "Non JSON Script",
      "isFolder": false
    },
    {
      "name": "Create Droids",
      "isFolder": false
    },
    {
      "name": "Delete All Records",
      "isFolder": false
    },
    {
      "name": "Buttons",
      "folderScriptNames": [
        {
          "name": "New Clip Record",
          "isFolder": false
        },
        {
          "name": "Update Clip Button",
          "isFolder": false
        },
        {
          "name": "Replace Clip Record Data",
          "isFolder": false
        },
        {
          "name": "Delete All Clip Records",
          "isFolder": false
        },
        {
          "name": "Push all flagged clips",
          "isFolder": false
        },
        {
          "name": "Set Clipboard",
          "isFolder": false
        }
      ],
      "isFolder": true
    }
  ]
}
```

> File [./examples/results/scripts-example.json](./examples/results/scripts-example.json)

#### Get Layout

The layout method will get metadata for the specified layout.

`client.layout(layout)`

| Param        | Type                | Description                                  |
| ------------ | ------------------- | -------------------------------------------- |
| layout       | <code>String</code> | The layout to use in the request.            |
| [parameters] | <code>Object</code> | optional request parameters for the request. |

```js
const layout = client =>
  client
    .layout(process.env.LAYOUT)
    .then(result => log('layout-details-example', result));
```

> Excerpt from [./examples/metadata.examples.js](./examples/metadata.examples.js#L21-L24)

Result:

```json
{
  "fieldMetaData": [
    {
      "name": "name",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": false,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "image",
      "type": "normal",
      "displayType": "editText",
      "result": "container",
      "global": false,
      "autoEnter": false,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "object",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": false,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "array",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": false,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "height",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": false,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "id",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": true,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "imageName",
      "type": "calculation",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": false,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "creationAccountName",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": true,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "creationTimestamp",
      "type": "normal",
      "displayType": "editText",
      "result": "timeStamp",
      "global": false,
      "autoEnter": true,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "modificationAccountName",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": true,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "modificationTimestamp",
      "type": "normal",
      "displayType": "editText",
      "result": "timeStamp",
      "global": false,
      "autoEnter": true,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    },
    {
      "name": "Vehicles::name",
      "type": "normal",
      "displayType": "editText",
      "result": "text",
      "global": false,
      "autoEnter": false,
      "fourDigitYear": false,
      "maxRepeat": 1,
      "maxCharacters": 0,
      "notEmpty": false,
      "numeric": false,
      "timeOfDay": false,
      "repetitionStart": 1,
      "repetitionEnd": 1
    }
  ],
  "portalMetaData": {
    "Planets": [
      {
        "name": "Planets::name",
        "type": "normal",
        "displayType": "editText",
        "result": "text",
        "global": false,
        "autoEnter": false,
        "fourDigitYear": false,
        "maxRepeat": 1,
        "maxCharacters": 0,
        "notEmpty": false,
        "numeric": false,
        "timeOfDay": false,
        "repetitionStart": 1,
        "repetitionEnd": 1
      },
      {
        "name": "Species::name",
        "type": "normal",
        "displayType": "editText",
        "result": "text",
        "global": false,
        "autoEnter": false,
        "fourDigitYear": false,
        "maxRepeat": 1,
        "maxCharacters": 0,
        "notEmpty": false,
        "numeric": false,
        "timeOfDay": false,
        "repetitionStart": 1,
        "repetitionEnd": 1
      }
    ],
    "Vehicles": [
      {
        "name": "Vehicles::name",
        "type": "normal",
        "displayType": "editText",
        "result": "text",
        "global": false,
        "autoEnter": false,
        "fourDigitYear": false,
        "maxRepeat": 1,
        "maxCharacters": 0,
        "notEmpty": false,
        "numeric": false,
        "timeOfDay": false,
        "repetitionStart": 1,
        "repetitionEnd": 1
      },
      {
        "name": "Vehicles::type",
        "type": "normal",
        "displayType": "editText",
        "result": "text",
        "global": false,
        "autoEnter": false,
        "fourDigitYear": false,
        "maxRepeat": 1,
        "maxCharacters": 0,
        "notEmpty": false,
        "numeric": false,
        "timeOfDay": false,
        "repetitionStart": 1,
        "repetitionEnd": 1
      }
    ]
  }
}
```

> File [./examples/results/layout-details-example.json](./examples/results/layout-details-example.json)

#### Create Records

Using the client you can create filemaker records. To create a record specify the layout to use and the data to insert on creation. The client will automatically convert numbers, arrays, and objects into strings so they can be inserted into a filemaker field. The create method will automatically create a `fieldData` property and add all data to that property if there is no fieldData property present. The client will preserve the contents of the `portalData` property.

`client.create(layout, data, parameters)`

| Param      | Type                | Description                                             |
| ---------- | ------------------- | ------------------------------------------------------- |
| layout     | <code>String</code> | The layout to use when creating a record.               |
| data       | <code>Object</code> | The data to use when creating a record.                 |
| parameters | <code>Object</code> | The request parameters to use when creating the record. |

```js
const createRecord = client =>
  client
    .create('Heroes', {
      name: 'George Lucas'
    })
    .then(result => log('create-record-example', result));
```

> Excerpt from [./examples/create.examples.js](./examples/create.examples.js#L6-L11)

Result:

```json
{
  "recordId": "1138",
  "modId": "327"
}
```

> File [./examples/results/create-record-example.json](./examples/results/create-record-example.json)

The create method also allows you to trigger scripts when creating a record. Notice the scripts property in the following example. You can specify scripts to run using either FileMaker's script.key syntax or specify an array of in a `scripts` property. The script objects should have with `name`, optional `phase`, and optional `params` parameters. For more information see the scripts syntax example in the introduction.

```js
const triggerScriptsOnCreate = client =>
  client
    .create(
      'Heroes',
      { name: 'Anakin Skywalker' },
      {
        merge: true,
        scripts: [
          { name: 'Create Droids', param: { droids: ['C3-PO', 'R2-D2'] } }
        ]
      }
    )
    .then(result => log('trigger-scripts-on-create-example', result));
```

> Excerpt from [./examples/create.examples.js](./examples/create.examples.js#L37-L49)

Result:

```json
{
  "name": "Anakin Skywalker",
  "scriptError": "0",
  "recordId": "1138",
  "modId": "327"
}
```

> File [./examples/results/trigger-scripts-on-create-example.json](./examples/results/trigger-scripts-on-create-example.json)

#### Duplicate Record

The duplicate method duplicates the FileMaker record using the layout and recordId parameters passed to it.

`client.duplicate(layout, recordId, parameters)`

| Param        | Type                | Description                                  |
| ------------ | ------------------- | -------------------------------------------- |
| layout       | <code>String</code> | The layout to use in the request.            |
| recordId     | <code>String</code> | The record id to target for duplication.     |
| [parameters] | <code>Object</code> | optional request parameters for the request. |

```js
client
  .duplicate('Heroes', response.data[0].recordId)
  .then(result => log('duplicate-record-example', result));
```

> Excerpt from [./examples/duplicate.examples.js](./examples/duplicate.examples.js#L9-L11)

Result:

```json
{
  "recordId": "1138",
  "modId": "327"
}
```

> File [./examples/results/duplicate-record-example.json](./examples/results/duplicate-record-example.json)

The duplicate method also allows you to trigger scripts when duplicating a record. Notice the scripts property in the following example. You can specify scripts to run using either FileMaker's script.key syntax or specify an array of in a `scripts` property. The script objects should have with `name`, optional `phase`, and optional `params` parameters. For more information see the scripts syntax example in the introduction.

```js
const triggerScriptsOnCreate = client =>
  client
    .create(
      'Heroes',
      { name: 'Anakin Skywalker' },
      {
        merge: true,
        scripts: [
          { name: 'Create Droids', param: { droids: ['C3-PO', 'R2-D2'] } }
        ]
      }
    )
    .then(result => log('trigger-scripts-on-create-example', result));
```

> Excerpt from [./examples/create.examples.js](./examples/create.examples.js#L37-L49)

Result:

```json
{
  "name": "Anakin Skywalker",
  "scriptError": "0",
  "recordId": "1138",
  "modId": "327"
}
```

> File [./examples/results/trigger-scripts-on-create-example.json](./examples/results/trigger-scripts-on-create-example.json)

#### Get Record Details

The Get method will return a specific FileMaker record based on the recordId passed to it. The recordId can be a string or a number.

`client.get(layout, recordId, parameters)`

| Param        | Type                | Description                                                         |
| ------------ | ------------------- | ------------------------------------------------------------------- |
| layout       | <code>String</code> | The layout to use when retrieving the record.                       |
| recordId     | <code>String</code> | The FileMaker internal record ID to use when retrieving the record. |
| [parameters] | <code>Object</code> | Parameters to add for the get query.                                |

```js
client
  .get('Heroes', response.data[0].recordId)
  .then(result => log('get-record-example', result));
```

> Excerpt from [./examples/get.examples.js](./examples/get.examples.js#L9-L11)
> Result:

```json
{
  "dataInfo": {
    "database": "fms-api-app",
    "layout": "Heroes",
    "table": "Heroes",
    "totalRecordCount": "1977",
    "foundCount": 1,
    "returnedCount": 1
  },
  "data": [
    {
      "fieldData": {
        "name": "yoda",
        "image": "https://some-server.com/Streaming_SSL/MainDB/E8BDBF29B9388B2F212002F7F6A7D6B8EF87A16E7E2D231EA27B8FC32147F64D?RCType=EmbeddedRCFileProcessor",
        "object": "",
        "array": "",
        "height": "",
        "id": "r2d2-c3po-l3-37-bb-8",
        "imageName": "placeholder.md",
        "creationAccountName": "obi-wan",
        "creationTimestamp": "05/25/1977 6:00:00",
        "modificationAccountName": "obi-wan",
        "modificationTimestamp": "05/25/1977 6:00:00",
        "Vehicles::name": ""
      },
      "portalData": {
        "Planets": [],
        "Vehicles": []
      },
      "recordId": "1138",
      "modId": "327",
      "portalDataInfo": [
        {
          "database": "fms-api-app",
          "table": "Planets",
          "foundCount": 0,
          "returnedCount": 0
        },
        {
          "database": "fms-api-app",
          "table": "Vehicles",
          "foundCount": 0,
          "returnedCount": 0
        }
      ]
    }
  ]
}
```

> File [./examples/results/get-record-example.json](./examples/results/get-record-example.json)

#### List Records

You can use the client to list filemaker records. The list method accepts a layout and parameter variable. The client will automatically santize the limit, offset, and sort keys to correspond with the DAPI's requirements.

`client.list(layout, parameters)`

| Param        | Type                | Description                                   |
| ------------ | ------------------- | --------------------------------------------- |
| layout       | <code>String</code> | The layout to use when retrieving the record. |
| [parameters] | <code>Object</code> | the parameters to use to modify the query.    |

```js
const listHeroes = client =>
  client
    .list('Heroes', { limit: 2 })
    .then(result => log('list-records-example', result));
```

> Excerpt from [./examples/list.examples.js](./examples/list.examples.js#L6-L9)

Result:

```json
{
  "dataInfo": {
    "database": "fms-api-app",
    "layout": "Heroes",
    "table": "Heroes",
    "totalRecordCount": "1977",
    "foundCount": 29493,
    "returnedCount": 2
  },
  "data": [
    {
      "fieldData": {
        "name": "George Lucas",
        "image": "https://some-server.com/Streaming_SSL/MainDB/6BA0C4191156DE2DF3036BB1ABF079B0CF8BF6F286316A5603EF789FB45C66D7.png?RCType=EmbeddedRCFileProcessor",
        "object": "",
        "array": "",
        "height": "",
        "id": "r2d2-c3po-l3-37-bb-8",
        "imageName": "IMG_0001.PNG",
        "creationAccountName": "obi-wan",
        "creationTimestamp": "05/25/1977 6:00:00",
        "modificationAccountName": "obi-wan",
        "modificationTimestamp": "05/25/1977 6:00:00",
        "Vehicles::name": "test"
      },
      "portalData": {
        "Planets": [],
        "Vehicles": [
          {
            "recordId": "1138",
            "Vehicles::name": "test",
            "Vehicles::type": "",
            "modId": "327"
          }
        ]
      },
      "recordId": "1138",
      "modId": "327",
      "portalDataInfo": [
        {
          "database": "fms-api-app",
          "table": "Planets",
          "foundCount": 0,
          "returnedCount": 0
        },
        {
          "database": "fms-api-app",
          "table": "Vehicles",
          "foundCount": 1,
          "returnedCount": 1
        }
      ]
    },
    {
      "fieldData": {
        "name": "George Lucas",
        "image": "",
        "object": "",
        "array": "",
        "height": "",
        "id": "r2d2-c3po-l3-37-bb-8",
        "imageName": "",
        "creationAccountName": "obi-wan",
        "creationTimestamp": "05/25/1977 6:00:00",
        "modificationAccountName": "obi-wan",
        "modificationTimestamp": "05/25/1977 6:00:00",
        "Vehicles::name": ""
      },
      "portalData": {
        "Planets": [],
        "Vehicles": []
      },
      "recordId": "1138",
      "modId": "327",
      "portalDataInfo": [
        {
          "database": "fms-api-app",
          "table": "Planets",
          "foundCount": 0,
          "returnedCount": 0
        },
        {
          "database": "fms-api-app",
          "table": "Vehicles",
          "foundCount": 0,
          "returnedCount": 0
        }
      ]
    }
  ]
}
```

> File [./examples/results/list-records-example.json](./examples/results/list-records-example.json)

#### Find Records

The client's find method will accept either a single object as find parameters or an array. The find method will also santize the limit, sort, and offset parameters to conform with the Data API's requirements.

`client.find(layout, query, parameters)`

| Param      | Type                | Description                                 |
| ---------- | ------------------- | ------------------------------------------- |
| layout     | <code>String</code> | The layout to use when performing the find. |
| query      | <code>Object</code> | to use in the find request.                 |
| parameters | <code>Object</code> | the parameters to use to modify the query.  |

```js
const findRecords = client =>
  client
    .find('Heroes', [{ name: 'Anakin Skywalker' }], { limit: 1 })
    .then(result => log('find-records-example', result));
```

> Excerpt from [./examples/find.examples.js](./examples/find.examples.js#L6-L9)

Result:

```json
{
  "dataInfo": {
    "database": "fms-api-app",
    "layout": "Heroes",
    "table": "Heroes",
    "totalRecordCount": "1977",
    "foundCount": 153,
    "returnedCount": 1
  },
  "data": [
    {
      "fieldData": {
        "name": "Anakin Skywalker",
        "image": "",
        "object": "",
        "array": "",
        "height": "",
        "id": "r2d2-c3po-l3-37-bb-8",
        "imageName": "",
        "creationAccountName": "obi-wan",
        "creationTimestamp": "05/25/1977 6:00:00",
        "modificationAccountName": "obi-wan",
        "modificationTimestamp": "05/25/1977 6:00:00",
        "Vehicles::name": ""
      },
      "portalData": {
        "Planets": [],
        "Vehicles": []
      },
      "recordId": "1138",
      "modId": "327",
      "portalDataInfo": [
        {
          "database": "fms-api-app",
          "table": "Planets",
          "foundCount": 0,
          "returnedCount": 0
        },
        {
          "database": "fms-api-app",
          "table": "Vehicles",
          "foundCount": 0,
          "returnedCount": 0
        }
      ]
    }
  ]
}
```

> File [./examples/results/find-records-example.json](./examples/results/find-records-example.json)

#### Edit Records

The client's edit method requires a layout, recordId, and object to use for updating the record.

`client.edit(layout, recordId, data, parameters)`

| Param      | Type                | Description                                                      |
| ---------- | ------------------- | ---------------------------------------------------------------- |
| layout     | <code>String</code> | The layout to use when editing the record.                       |
| recordId   | <code>String</code> | The FileMaker internal record ID to use when editing the record. |
| data       | <code>Object</code> | The data to use when editing a record.                           |
| parameters | <code>Object</code> | parameters to use when performing the query.                     |

```js
const editRecord = client =>
  client
    .find('Heroes', [{ name: 'Anakin Skywalker' }], { limit: 1 })
    .then(response => response.data[0].recordId)
    .then(recordId => client.edit('Heroes', recordId, { name: 'Darth Vader' }))
    .then(result => log('edit-record-example', result));
```

> Excerpt from [./examples/edit.examples.js](./examples/edit.examples.js#L6-L11)

Result:

```json
{
  "modId": "327"
}
```

> File [./examples/results/edit-record-example.json](./examples/results/edit-record-example.json)

#### Delete Records

The client's delete method requires a layout and a record id. The recordId can be a number or a string.

`client.delete(layout, recordId, parameters)`

| Param      | Type                | Description                                                      |
| ---------- | ------------------- | ---------------------------------------------------------------- |
| layout     | <code>String</code> | The layout to use when deleting the record.                      |
| recordId   | <code>String</code> | The FileMaker internal record ID to use when editing the record. |
| parameters | <code>Object</code> | parameters to use when performing the query.                     |

```js
const deleteRecords = client =>
  client
    .find('Heroes', [{ name: 'yoda' }], { limit: 1 })
    .then(response => response.data[0].recordId)
    .then(recordId => client.delete('Heroes', recordId))
    .then(result => log('delete-record-example', result));
```

> Excerpt from [./examples/delete.examples.js](./examples/delete.examples.js#L6-L11)

Result:

```json
{}
```

> File [./examples/results/delete-record-example.json](./examples/results/delete-record-example.json)

#### Trigger Script

The client's script method will trigger a script. You can also trigger scripts with the create, edit, list, find, and delete methods. This method performs a list with a limit of one on the specified layout before triggering the script. this is the most lightweight request possible while still being able to trigger a script.

`client.script(layout, script, param, parameters)`

| Param        | Type                                       | Description                            |
| ------------ | ------------------------------------------ | -------------------------------------- |
| layout       | <code>String</code>                        | The layout to use for the list request |
| script       | <code>String</code>                        | The name of the script                 |
| param        | <code>Object</code> \| <code>String</code> | Parameter to pass to the script        |
| [parameters] | <code>Object</code>                        | Optional request parameters.           |

```js
const triggerScript = client =>
  client
    .script('Heroes', 'FMS Triggered Script', { name: 'Han' })
    .then(result => log('script-trigger-example', result));
```

> Excerpt from [./examples/script.examples.js](./examples/script.examples.js#L6-L9)

Result:

```json
{
  "scriptResult": {
    "answer": "Han shot first"
  },
  "scriptError": "0"
}
```

> File [./examples/results/script-trigger-example.json](./examples/results/script-trigger-example.json)

#### Run Multiple Scripts

The client's script method will trigger a script. You can also trigger scripts with the create, edit, list, find, and delete methods. This method performs a list with a limit of one on the specified layout before triggering the script. this is the most lightweight request possible while still being able to trigger a script.

`client.run(layout, scripts,parameters)`

| Param      | Type                                      | Description                                 |
| ---------- | ----------------------------------------- | ------------------------------------------- |
| layout     | <code>String</code>                       | The layout to use for the list request      |
| scripts    | <code>Object</code> \| <code>Array</code> | The name of the script                      |
| parameters | <code>Object</code>                       | Parameters to pass to the script            |
| request    | <code>Object</code>                       | A request to run alongside the list method. |

```js
const runMultipleScripts = client =>
  client
    .run('Heroes', [
      { name: 'FMS Triggered Script', param: { name: 'Han' } },
      { name: 'FMS Triggered Script', phase: 'presort', param: { name: 'Han' } }
    ])
    .then(result => log('run-scripts-example', result));
```

> Excerpt from [./examples/scripts.examples.js](./examples/scripts.examples.js#L27-L33)

Result:

```json
{
  "scriptResult.presort": {
    "answer": "Han shot first"
  },
  "scriptResult": {
    "answer": "Han shot first"
  }
}
```

> File [./examples/results/run-scripts-example.json](./examples/results/run-scripts-example.json)

#### Upload Files

The upload method will upload binary data to a container. The file parameter should be either a path to a file or a buffer. If you need to set a field repetition, you can set that in parameters. If recordId is 0 or undefined a new record will be created.

`client.upload(file, layout, container, recordId, parameters)`

| Param              | Type                                       | Description                                                            |
| ------------------ | ------------------------------------------ | ---------------------------------------------------------------------- |
| file               | <code>String</code>                        | The path to the file to upload.                                        |
| layout             | <code>String</code>                        | The layout to use when performing the find.                            |
| containerFieldName | <code>String</code>                        | The field name to insert the data into. It must be a container field.  |
| recordId           | <code>Number</code> \| <code>String</code> | the recordId to use when uploading the file.                           |
| parameters         | <code>Object</code>                        | parameters to use when performing the query.                           |
| [fieldRepetition]  | <code>String</code> \| <code>Number</code> | The field repetition to use when inserting the file. The default is 1. |

```js
const uploadImage = client =>
  client
    .upload('./assets/placeholder.md', 'Heroes', 'image')
    .then(result => log('upload-image-example', result));
```

> Excerpt from [./examples/upload.examples.js](./examples/upload.examples.js#L6-L9)

Result:

```json
{
  "modId": "327",
  "recordId": "1138"
}
```

> File [./examples/results/upload-image-example.json](./examples/results/upload-image-example.json)

You can also provide a record Id to the upload method and the file will be uploaded to that
record.

```js
          client
            .upload('./assets/placeholder.md', 'Heroes', 'image', recordId)
            .then(result => log('upload-specific-record-example', result))
            .catch(error => error),
```

> Excerpt from [./examples/upload.examples.js](./examples/upload.examples.js#L20-L23)

Result:

```json
{
  "modId": "327",
  "recordId": "1138"
}
```

> File [./examples/results/upload-specific-record-example.json](./examples/results/upload-specific-record-example.json)

#### Set Session Globals

The globals method will set global fields for the current session.

`client.globals(data, parameters)`

| Param      | Type                                      | Description                                           |
| ---------- | ----------------------------------------- | ----------------------------------------------------- |
| data       | <code>Object</code> \| <code>Array</code> | a json object containing the name value pairs to set. |
| parameters | <code>Object</code>                       | parameters to use when performing the query.          |

```js
const setGlobals = client =>
  client
    .globals({ 'Globals::ship': 'Millenium Falcon' })
    .then(result => log('set-globals-example', result));
```

> Excerpt from [./examples/globals.examples.js](./examples/globals.examples.js#L6-L9)

Result:

```json
{}
```

> File [./examples/results/set-globals-example.json](./examples/results/set-globals-example.json)

#### Client Status

The client status method will return an object description the current client's connection status. This method will return overall client data usage, urls for pending and queued requests and current session information.

`client.status()`

```js
const getStatus = client =>
  client.status().then(result => log('client-status-example', result));
```

> Excerpt from [./examples/utility.examples.js](./examples/utility.examples.js#L100-L101)

Result:

```json
{
  "data": {
    "since": "2019-09-15T15:30:43-07:00",
    "in": "438 Bytes",
    "out": "29.8 kB"
  },
  "queue": [],
  "pending": [],
  "sessions": [
    {
      "issued": "2019-09-15T15:30:44-07:00",
      "expires": "2019-09-15T15:45:47-07:00",
      "id": "r2d2-c3po-l3-37-bb-8",
      "active": false
    },
    {
      "issued": "2019-09-15T15:30:44-07:00",
      "expires": "2019-09-15T15:45:44-07:00",
      "id": "r2d2-c3po-l3-37-bb-8",
      "active": true
    },
    {
      "issued": "2019-09-15T15:30:44-07:00",
      "expires": "2019-09-15T15:45:44-07:00",
      "id": "r2d2-c3po-l3-37-bb-8",
      "active": true
    }
  ]
}
```

> File [./examples/results/client-status-example.json](./examples/results/client-status-example.json)

#### Client Reset

The client reset method will drop all pending and queued requests while also clearing all open sessions.

:warning: This method is experimental. It does not reject pending or queued requests before clearing them. Use with caution.

`client.reset()`

```js
const resetClient = client =>
  client.reset().then(result => log('client-reset-example', result));
```

> Excerpt from [./examples/utility.examples.js](./examples/utility.examples.js#L105-L106)

Result:

```json
{
  "message": "Client Reset"
}
```

> File [./examples/results/client-reset-example.json](./examples/results/client-reset-example.json)

### Utilities

The client also provides utilities to aid in parsing and manipulating FileMaker Data. The client exports the `recordId(data)`, `fieldData(data)`, and `transform(data, options)` to aid in transforming Data API response data into other formats. Each utility is capable of recieving either an object or a array.

#### Record Id Utility

The record id utility retrieves the `recordId` properties for a response. This utility will return either a single string or an array of strings.

`recordId(data)`

| Param | Type                                      | Description                                                                |
| ----- | ----------------------------------------- | -------------------------------------------------------------------------- |
| data  | <code>Object</code> \| <code>Array</code> | the raw data returned from a filemaker. This can be an array or an object. |

```js
const extractRecordIdOriginal = client =>
  client
    .find('Heroes', { name: 'yoda' }, { limit: 2 })
    .then(response => recordId(response.data))
    .then(result => log('record-id-utility-example', result));
```

> Excerpt from [./examples/utility.examples.js](./examples/utility.examples.js#L22-L26)

##### Record Id Utility Results

original:

```json
[
  {
    "fieldData": {
      "name": "Yoda",
      "image": "https://some-server.com/Streaming_SSL/MainDB/1D86123EDDA1866BDDC2962F71108F8D78EFCAE349E66934D2737E3615918167?RCType=EmbeddedRCFileProcessor",
      "object": "",
      "array": "",
      "height": "",
      "id": "r2d2-c3po-l3-37-bb-8",
      "imageName": "placeholder.md",
      "creationAccountName": "obi-wan",
      "creationTimestamp": "05/25/1977 6:00:00",
      "modificationAccountName": "obi-wan",
      "modificationTimestamp": "05/25/1977 6:00:00",
      "Vehicles::name": ""
    },
    "portalData": {
      "Planets": [],
      "Vehicles": []
    },
    "recordId": "1138",
    "modId": "327",
    "portalDataInfo": [
      {
        "database": "fms-api-app",
        "table": "Planets",
        "foundCount": 0,
        "returnedCount": 0
      },
      {
        "database": "fms-api-app",
        "table": "Vehicles",
        "foundCount": 0,
        "returnedCount": 0
      }
    ]
  },
  {
    "fieldData": {
      "name": "yoda",
      "image": "",
      "object": "",
      "array": "",
      "height": "",
      "id": "r2d2-c3po-l3-37-bb-8",
      "imageName": "",
      "creationAccountName": "obi-wan",
      "creationTimestamp": "05/25/1977 6:00:00",
      "modificationAccountName": "obi-wan",
      "modificationTimestamp": "05/25/1977 6:00:00",
      "Vehicles::name": ""
    },
    "portalData": {
      "Planets": [],
      "Vehicles": []
    },
    "recordId": "1138",
    "modId": "327",
    "portalDataInfo": [
      {
        "database": "fms-api-app",
        "table": "Planets",
        "foundCount": 0,
        "returnedCount": 0
      },
      {
        "database": "fms-api-app",
        "table": "Vehicles",
        "foundCount": 0,
        "returnedCount": 0
      }
    ]
  }
]
```

> File [./examples/results/record-id-utility-original-example.json](./examples/results/record-id-utility-original-example.json)

Transformed:

```json
["751326", "751329"]
```

> File [./examples/results/record-id-utility-example.json](./examples/results/record-id-utility-example.json)

#### Field Data Utility

The field data utility retrieves the `fieldData`, `recordId`, and `modId` properties from a Data API response. The fieldData utility will merge the `recordId` and `modId` properties into fielData properties. This utility will not convert `table::field` properties.

`fieldData(data)`

| Param | Type                                      | Description                                                                |
| ----- | ----------------------------------------- | -------------------------------------------------------------------------- |
| data  | <code>Object</code> \| <code>Array</code> | The raw data returned from a filemaker. This can be an array or an object. |

```js
const extractFieldData = client =>
  client
    .find('Heroes', { name: 'yoda' }, { limit: 2 })
    .then(response => fieldData(response.data))
    .then(result => log('field-data-utility-example', result));
```

> Excerpt from [./examples/utility.examples.js](./examples/utility.examples.js#L38-L42)

##### Field Data Utility Results

Original:

```json
[
  {
    "fieldData": {
      "name": "Yoda",
      "image": "https://some-server.com/Streaming_SSL/MainDB/8F7489E114CFFE0FC13E8A45A2F52D77A50ABFB36B05C0858114F37EA3141634?RCType=EmbeddedRCFileProcessor",
      "object": "",
      "array": "",
      "height": "",
      "id": "r2d2-c3po-l3-37-bb-8",
      "imageName": "placeholder.md",
      "creationAccountName": "obi-wan",
      "creationTimestamp": "05/25/1977 6:00:00",
      "modificationAccountName": "obi-wan",
      "modificationTimestamp": "05/25/1977 6:00:00",
      "Vehicles::name": ""
    },
    "portalData": {
      "Planets": [],
      "Vehicles": []
    },
    "recordId": "1138",
    "modId": "327",
    "portalDataInfo": [
      {
        "database": "fms-api-app",
        "table": "Planets",
        "foundCount": 0,
        "returnedCount": 0
      },
      {
        "database": "fms-api-app",
        "table": "Vehicles",
        "foundCount": 0,
        "returnedCount": 0
      }
    ]
  },
  {
    "fieldData": {
      "name": "yoda",
      "image": "",
      "object": "",
      "array": "",
      "height": "",
      "id": "r2d2-c3po-l3-37-bb-8",
      "imageName": "",
      "creationAccountName": "obi-wan",
      "creationTimestamp": "05/25/1977 6:00:00",
      "modificationAccountName": "obi-wan",
      "modificationTimestamp": "05/25/1977 6:00:00",
      "Vehicles::name": ""
    },
    "portalData": {
      "Planets": [],
      "Vehicles": []
    },
    "recordId": "1138",
    "modId": "327",
    "portalDataInfo": [
      {
        "database": "fms-api-app",
        "table": "Planets",
        "foundCount": 0,
        "returnedCount": 0
      },
      {
        "database": "fms-api-app",
        "table": "Vehicles",
        "foundCount": 0,
        "returnedCount": 0
      }
    ]
  }
]
```

> File [./examples/results/field-data-utility-original-example.json](./examples/results/field-data-utility-original-example.json)

Transformed:

```json
[
  {
    "name": "Yoda",
    "image": "https://some-server.com/Streaming_SSL/MainDB/27EA97DBE4B14E5BE2E09713E6C5CF75604F9458EC0885B00E436B618C798D87?RCType=EmbeddedRCFileProcessor",
    "object": "",
    "array": "",
    "height": "",
    "id": "r2d2-c3po-l3-37-bb-8",
    "imageName": "placeholder.md",
    "creationAccountName": "obi-wan",
    "creationTimestamp": "05/25/1977 6:00:00",
    "modificationAccountName": "obi-wan",
    "modificationTimestamp": "05/25/1977 6:00:00",
    "Vehicles::name": "",
    "recordId": "1138",
    "modId": "327"
  },
  {
    "name": "yoda",
    "image": "",
    "object": "",
    "array": "",
    "height": "",
    "id": "r2d2-c3po-l3-37-bb-8",
    "imageName": "",
    "creationAccountName": "obi-wan",
    "creationTimestamp": "05/25/1977 6:00:00",
    "modificationAccountName": "obi-wan",
    "modificationTimestamp": "05/25/1977 6:00:00",
    "Vehicles::name": "",
    "recordId": "1138",
    "modId": "327"
  }
]
```

> File [./examples/results/field-data-utility-example.json](./examples/results/field-data-utility-example.json)

#### Transform Utility

The transform utility converts Data API response data by converting `table::field` properties into objects. This utility will traverse the response data converting `{ table::field : value}` properties to `{ table:{ field : value } }`. This utility will also convert `portalData` into arrays of objects.

The transform utility accepts three option properties. The three option properties are all booleans and true by default. The properties are `convert`,`fieldData`,`portalData`. The `convert` property toggles the transformation of `table::field` properties. The `fieldData` property toggles merging of fieldData to the result. The `portalData` property toggles merging portalData to the result. Setting any property to false will turn that transformation off.

`transform(data, parameters)`

| Param   | Type                | Description                                        |
| ------- | ------------------- | -------------------------------------------------- |
| data    | <code>Object</code> | The data to transform.                             |
| options | <code>Object</code> | transformation options to pass to transformObject. |

```js
const transformData = client =>
  client
    .find('Transform', { name: 'Han Solo' }, { limit: 1 })
    .then(result => transform(result.data))
    .then(result => log('transform-utility-example', result));
```

> Excerpt from [./examples/utility.examples.js](./examples/utility.examples.js#L46-L50)

##### Transform Utility Results

Original:

```json
[
  {
    "fieldData": {
      "starships": "",
      "vehicles": "",
      "species": "",
      "biography": "",
      "birthYear": "",
      "id": "r2d2-c3po-l3-37-bb-8",
      "name": "Han Solo"
    },
    "portalData": {
      "Planets": [
        {
          "recordId": "1138",
          "Planets::name": "Coriella",
          "modId": "327"
        }
      ],
      "Vehicles": [
        {
          "recordId": "1138",
          "Vehicles::name": "Millenium Falcon",
          "Vehicles::type": "Starship",
          "modId": "327"
        }
      ]
    },
    "recordId": "1138",
    "modId": "327",
    "portalDataInfo": [
      {
        "database": "fms-api-app",
        "table": "Planets",
        "foundCount": 1,
        "returnedCount": 1
      },
      {
        "database": "fms-api-app",
        "table": "Vehicles",
        "foundCount": 1,
        "returnedCount": 1
      }
    ]
  }
]
```

> File [./examples/results/transform-utility-original-example.json](./examples/results/transform-utility-original-example.json)

Transformed:

```json
[
  {
    "starships": "",
    "vehicles": "",
    "species": "",
    "biography": "",
    "birthYear": "",
    "id": "r2d2-c3po-l3-37-bb-8",
    "name": "Han Solo",
    "Planets": [
      {
        "recordId": "1138",
        "name": "Coriella",
        "modId": "327"
      }
    ],
    "Vehicles": [
      {
        "recordId": "1138",
        "name": "Millenium Falcon",
        "type": "Starship",
        "modId": "327"
      }
    ],
    "recordId": "1138",
    "modId": "327"
  }
]
```

> File [./examples/results/transform-utility-example.json](./examples/results/transform-utility-example.json)

#### Container Data Utility

The container data utility will retrieve FileMaker container data by following the links returned by the Data API. This utility will accept either a single data object or an array of objects. The utility will use the `field` parameter to target container data urls in the data parameter. This utility also requires a `name` parameter which will be used to target a data property that should be used as the file's name. If a name parameter is provided that is not a property or nested property in the `data` parameter, the name parameter itself will be used. The `destination` parameter should be either 'buffer' to indicate that an object with a file's name and buffer should be returned or the path, relative to the current working directory, where the utility should write container data to a file. This utility will also accept optional request `parameters` to modify the http request.

`containerData(data, field, destination, name, parameters)`

| Param                | Type                                      | Description                                                                   |
| -------------------- | ----------------------------------------- | ----------------------------------------------------------------------------- |
| data                 | <code>Object</code> \| <code>Array</code> | The response recieved from the FileMaker DAPI.                                |
| field                | <code>String</code>                       | The container field name to target. This can be a nested property.            |
| destination          | <code>String</code>                       | "buffer" if a buffer object should be returned or the path to write the file. |
| name                 | <code>String</code>                       | The field to use for the file name or a static string.                        |
| [parameters]         | <code>Object</code>                       | Request configuration parameters.                                             |
| [parameters.timeout] | <code>Number</code>                       | a timeout for the request.                                                    |

```js
const getContainerData = client =>
  client
    .find('Heroes', { imageName: '*' }, { limit: 1 })
    .then(result =>
      containerData(
        result.data,
        'fieldData.image',
        './assets',
        'fieldData.imageName'
      )
    )
    .then(result => log('container-data-example', result));
```

> Excerpt from [./examples/utility.examples.js](./examples/utility.examples.js#L71-L82)

Result:

```json
[
  {
    "name": "IMG_0001.PNG",
    "path": "assets/IMG_0001.PNG"
  }
]
```

> File [.