Module: models/filesystemBackedImpostersRepository

An abstraction for loading imposters from the filesystem
The root of the database is provided on the command line as --datadir
The file layout looks like this:

/{datadir}
/3000
/imposter.json
{
"protocol": "http",
"port": 3000,
"stubs: [{
"predicates": [{ "equals": { "path": "/" } }],
"meta": {
"dir": "stubs/{epoch-pid-counter}"
}
}]
}

/stubs
  /{epoch-pid-counter}
    /meta.json
      {
        "responseFiles": ["responses/{epoch-pid-counter}.json"],
          // An array of indexes into responseFiles which handle repeat behavior
        "orderWithRepeats": [0],
          // The next index into orderWithRepeats; incremented with each call to nextResponse()
        "nextIndex": 0
      }

    /responses
      /{epoch-pid-counter}.json
        {
          "is": { "body": "Hello, world!" }
        }

    /matches
        /{epoch-pid-counter}.json
            { ... }

/requests
  /{epoch-pid-counter}.json
    { ... }

This structure is designed to minimize the amount of file locking and to maximize parallelism and throughput.

The imposters.json file needs to be locked during imposter-level activities (e.g. adding a stub).
Readers do not lock; they just get the data at the time they read it. Since this file doesn't
contain transient state, there's no harm from a stale read. Writes (which happen for
stub changing operations, either for proxy response recording or stub API calls) grab a file lock
for both the read and the write. Writes to this file should be infrequent, except perhaps during
proxy recording. Newly added stubs may change the index of existing stubs in the stubs array, but
will never change the stub meta.dir, so it is always safe to hang on to it for subsequent operations.

The stub meta.json needs to be locked to add responses or trigger the next response, but is
separated from the imposter.json so we can have responses from multiple stubs in parallel with no
lock conflict. Again, readers (e.g. to generate imposter JSON) do not need a lock because the responseFiles
array is mostly read-only, and even when it's not (adding responses during proxyAlways recording), there's
no harm from a stale read. Writers (usually generating the next response for a stub) grab a lock for the
read and the write. This should be the most common write across files, which is why the meta.json file
is small.

In both cases where a file needs to be locked, an exponential backoff retry strategy is used. Inconsistent
reads of partially written files (which can happen by default with the system calls - fs.writeFile is not
atomic) are avoided by writing first to a temp file (during which time reads can happen to the original file)
and then renaming to the original file.

New directories and filenames use a timestamp-based filename to allow creating them without synchronizing
with a read. Since multiple files (esp. requests) can be added during the same millisecond, a pid and counter
is tacked on to the filename to improve uniqueness. It doesn't provide the * ironclad guarantee a GUID
does -- two different processes on two different machines could in theory have the same pid and create files
during the same timestamp with the same counter, but the odds of that happening * are so small that it's not
worth giving up the easy time-based sortability based on filenames alone.

Keeping all imposter information under a directory (instead of having metadata outside the directory)
allows us to remove the imposter by simply removing the directory.

Source:

Methods

add(stub) → {Object}

Adds a new stub to imposter

Parameters:
Name Type Description
stub Object

the stub to add

Source:
Returns:
  • the promise
Type
Object

add(imposter) → {Object}

Adds a new imposter

Parameters:
Name Type Description
imposter Object

the imposter to add

Source:
Returns:
  • the promise
Type
Object

addReference(imposter)

Saves a reference to the imposter so that the functions
(which can't be persisted) can be rehydrated to a loaded imposter.
This means that any data in the function closures will be held in
memory.

Parameters:
Name Type Description
imposter Object

the imposter

Source:

addRequest(request) → {Object}

Adds a request for the imposter

Parameters:
Name Type Description
request Object

the request

Source:
Returns:
  • the promise
Type
Object

all() → {Object}

Gets all imposters

Source:
Returns:
  • all imposters keyed by port
Type
Object

count() → {Object}

Returns the number of stubs for the imposter

Source:
Returns:
  • the promise
Type
Object

del(id) → {Object}

Deletes the imposter at the given id

Parameters:
Name Type Description
id Number

the id (e.g. the port)

Source:
Returns:
  • the deletion promise
Type
Object

deleteAll() → {Object}

Deletes all imposters

Source:
Returns:
  • the deletion promise
Type
Object

deleteAtIndex(index) → {Object}

Deletes the stub at the given index

Parameters:
Name Type Description
index Number

the index of the stub to delete

Source:
Returns:
  • the promise
Type
Object

deleteSavedProxyResponses() → {Object}

Removes the saved proxy responses

Source:
Returns:
  • Promise
Type
Object

deleteSavedRequests() → {Object}

Deletes the requests directory for an imposter

Source:
Returns:
  • Promise
Type
Object

exists(id) → {boolean}

Returns whether an imposter at the given id exists or not

Parameters:
Name Type Description
id Number

the id (e.g. the port)

Source:
Returns:
Type
boolean

first(filter, startIndex) → {Object}

Returns the first stub whose predicates matches the filter

Parameters:
Name Type Default Description
filter function

the filter function

startIndex Number 0

the index to to start searching

Source:
Returns:
  • the promise
Type
Object

get(id) → {Object}

Gets the imposter by id

Parameters:
Name Type Description
id Number

the id of the imposter (e.g. the port)

Source:
Returns:
  • the promise resolving to the imposter
Type
Object

insertAtIndex(stub, index) → {Object}

Inserts a new stub at the given index

Parameters:
Name Type Description
stub Object

the stub to add

index Number

the index to insert the new stub at

Source:
Returns:
  • the promise
Type
Object

loadRequests() → {Object}

Returns the saved requests for the imposter

Source:
Returns:
  • the promise resolving to the array of requests
Type
Object

overwriteAll(newStubs) → {Object}

Overwrites all stubs with a new list

Parameters:
Name Type Description
newStubs Object

the new list of stubs

Source:
Returns:
  • the promise
Type
Object

overwriteAtIndex(stub, index) → {Object}

Overwrites the stub at the given index

Parameters:
Name Type Description
stub Object

the new stub

index Number

the index of the stub to overwrite

Source:
Returns:
  • the promise
Type
Object

stopAllSync()

Deletes all imposters synchronously; used during shutdown

Source:

stubsFor(id) → {Object}

Returns the stubs repository for the imposter

Parameters:
Name Type Description
id Number

the id of the imposter

Source:
Returns:
  • the stubs repository
Type
Object

toJSON(options) → {Object}

Returns a JSON-convertible representation

Parameters:
Name Type Description
options Object

The formatting options

Properties
Name Type Description
debug Boolean

If true, includes debug information

Source:
Returns:
  • the promise resolving to the JSON object
Type
Object

(inner) create(config, logger) → {Object}

Creates the repository

Parameters:
Name Type Description
config Object

The database configuration

Properties
Name Type Description
datadir String

The database directory

logger Object

The logger

Source:
Returns:
Type
Object