Collection Operation Configuration

Collections support configuration at multiple levels with two types of configuration: Collection-level configuration, which may have implications for certain Collection operations, and CollectionOperation-specific configs.

CollectionOperation-specific configs should be set on the Collection. If they are omitted, and an operation is enabled, they will be instantiated as if they were initialized as {} using the appropriate OperationConfig subclass (e.g. if insertConfig is left as undefined, it will be instantiated as o({}, this.InsertConfigClass), where this.InsertConfigClass is a class member that allows subclasses of Collection to override the default config class). Operations should be configured with the following properties:

When instantiating an instance of Collection, you can configure each operation using the config property for that operation (e.g. insertConfig for the insert operation). When defining the config, you can either explicitly instantiate a config instance using the appropriate config class (e.g., InsertConfig for the insert operation):

insertConfig: o({
  _type: carbond.collections.MyCustomInsertConfig,
  description: "My collection's insert operation",
  parameters: {
    foo: {
      name: "foo",
      location: "query",
      schema: {
        type: "string"
      }
    }
  }
})

Or you can simply define an object that will be instantiated using a default config class for that operation (in this case, InsertConfig by way of InsertConfigClass):

insertConfig: {
  description: "My collection's insert operation",
  parameters: {
    foo: {
      name: "foo",
      location: "query",
      schema: {
        type: "string"
      }
    }
  }
}

Subclasses that require additional parameters for certain operations or that can not support certain features (e.g., returning removed objects), should subclass the individual config classes and override these member properties in the subclass. When the subclass is instantiated, it will use these overridden config classes instead of the default ones as defined on Collection.

The OperationConfig class

CollectionOperationConfig is the base class for all Collection configs. It defines basic properties that are common to all operation configs like:

InsertConfig

The InsertConfig class is the base insert operation config class and the default for Collection. It can be used to configure whether or not inserted objects are returned (returnsInsertedObjects) in the response body and to define a schema separate from the collection level schema that will be used to verify incoming objects (schema).

...
insertConfig: {
  description: 'My collection insert operation',
  returnsInsertedObjects: false,
  schema: {
    type: 'array',
    items: {
      type: 'object',
      properties: {
        foo: {type: 'boolean'},
        bar: {type: 'number'}
      },
      required: ['foo'],
      additionalProperties: {type: 'string'}
    }
  }
},
...

In the previous example, the insert operation on this collection will not return the objects that were inserted and each incoming object must contain at least the foo property.

FindConfig

The FindConfig class is the base find operation config class and the default for Collection. By default, the find operation supports ID queries and pagination. ID queries on the find operation are used by Carbon to communicate the location (i.e., the Location header) of objects in response to a bulk insert. If this is disabled, then the insert operation should likely be disabled as well (note, insertObject makes use of the ID path parameter instead). Enabling pagination adds the page parameter to the list of parameters for the collection operation. Note, the find operation parameter list includes two more parameters: skip and limit. When pagination is enabled, it will be transparent to the operation handlers themselves. Instead, Collection will update options.skip and options.limit to reflect the page start and size. This allows the handler to be implemented without concern for whether pagination is enabled or not.

...
findConfig: {
  description: 'My collection find operation',
  supportsIdQuery: false,
  supportsPagination: false
},
...

In the previous example, ID queries and pagination are disabled. This will result in the omission of both parameters from the collection operation parameters list that are used to support these features (note, skip and limit will still be present, but page will not be honored).

SaveConfig

The SaveConfig class is the base save operation config class and the default for Collection. Similar to InsertConfig, this config class allows you to specify a separate schema for the objects being saved and whether or not the objects saved are returned in the response.

...
saveConfig: {
  description: 'My collection save operation',
  returnsSavedObjects: false,
  schema: {
    type: 'object',
    properties: {
      _id: {type: 'string'},
      foo: {type: 'boolean'},
      bar: {type: 'number'}
    },
    required: ['_id', 'foo'],
    additionalProperties: {type: 'string'}
  }
},
...

Note, unlike schema, it is necessary to specify the ID parameter (_id in this case) on schema. Note, it should have the same name as idParameterName or an error will be thrown on initialization.

UpdateConfig

The UpdateConfig class is the base update operation config class and the default for Collection. It allows you to configure an update schema, whether or not upserts are supported, and whether upserted objects are returned in the response body. The update schema is “loose” by default and only specifies that it should be an object. This should be tailored depending on the update scheme that your collection/datastore understands (e.g., json patch). If upserts are enabled, an upsert parameter will be added to the list of parameters for the collection operation.

...
updateConfig: {
  description: 'My collection update operation',
  supportsUpsert: false,
  schema: {
    oneOf: [
      {
        type: 'object',
        properties: {
          inc: {
            type: object,
            minProperties: 1,
            additionalProperties: {
              type: 'integer',
              minimum: 1
            }
          },
          requiredProperties: ['inc'],
          additionalProperties: false
        },
        {
          type: 'object',
          dec: {
            type: object,
            minProperties: 1,
            additionalProperties: {
              type: 'integer',
              minimum: 1
            }
          },
          requiredProperties: ['dec'],
          additionalProperties: false
        }
      }
    ]
  }
}
...

The example config above disallows upserts and specifies a simple schema that allows updates to increment or decrement properties in the collection by an arbitrary amount (e.g. {inc: {foo: 5}} or {dec: {foo: 1}}).

RemoveConfig

The RemoveConfig class is the base remove operation config class and the default for Collection. It allows you to configure whether or not removed objects are returned.

...
removeConfig: {
  description: 'My collection remove operation',
  returnsRemovedObjects: true
}
...

InsertObjectConfig

The InsertObjectConfig class is the base insertObject operation config class and the default for Collection. This config follows the same pattern as InsertConfig, allowing you to configure a schema specific to this operation and to specify whether the inserted object should be returned or not.

...
insertObjectConfig: {
  description: 'My collection insertObject operation',
  returnsInsertedObject: false,
  schema: {
    type: 'object',
    properties: {
      foo: {type: 'boolean'},
      bar: {type: 'number'}
    },
    required: ['foo'],
    additionalProperties: {type: 'string'}
  }
},
...

FindObjectConfig

The FindObjectConfig class is the base findObject operation config class and the default for Collection.

...
findObjectConfig: {
  description: 'My collection findObject operation'
},
...

SaveObjectConfig

The SaveObjectConfig class is the base saveObject operation config class and the default for Collection. Like previous config classes, it allows you to set a specific schema for the incoming object (again, an ID property is required). Additionally, like the update config class, the saveObject operation can be configured to create objects in the collection (this is the default) and to return the object in the response to the client. Note, unlike saveObject, the save operation never “creates” objects since it is an operation at the collection level. Instead, it “replaces” the collection.

...
saveObjectConfig: {
  description: 'My collection saveObject operation',
  supportsUpsert: false,
  returnsSavedObject: false
}
...

UpdateObjectConfig

The UpdateObjectConfig class is the base updateObject operation config class and the default for Collection. Like UpdateConfig, this config allows you to specify an update spec schema, whether or not upserts are allowed, and, if they are allowed, whether an object is returned if an upsert takes place.

...
updateObjectConfig: {
  description: 'My collection updateObject operation',
  supportsUpsert: true,
  returnsUpsertedObject: true,
  schema: {
    oneOf: [
      {
        type: 'object',
        properties: {
          inc: {
            type: object,
            minProperties: 1,
            additionalProperties: {
              type: 'integer',
              minimum: 1
            }
          },
          requiredProperties: ['inc'],
          additionalProperties: false
        },
        {
          type: 'object',
          dec: {
            type: object,
            minProperties: 1,
            additionalProperties: {
              type: 'integer',
              minimum: 1
            }
          },
          requiredProperties: ['dec'],
          additionalProperties: false
        }
      }
    ]
  }
},
...

RemoveObjectConfig

The RemoveObjectConfig class is the base removeObject operation config class and the default for Collection. This config offers essentially the same configuration parameters as RemoveObjectConfig.

...
removeObjectConfig: {
  description: 'My collection remove operation',
  returnsRemovedObject: true
},
...

Collection Operation Parameter and Response configuration (Advanced)

For the most part, you should not need to alter the parameter or response specs themselves when instantiating a Collection. The Collection class (or a subclass) and the CollectionOperationConfig classes themselves should provide the levers necessary to specify the appropriate parameter/response schemas. The InsertConfig class, for example, has the property insertSchema, which allows you to specify a custom schema for the body of a POST request that ends up getting routed to the collection. If insertSchema is not configured, it will default to schema.

If you do find that you need to further configure parameters/responses beyond what is available via either the config or collection classes themselves, you can do this using the various operators available via the o operator (e.g., $merge, $delete, etc.). See Atom for an in-depth description of the available operators.

For instance, if you want to configure a custom description for the 201 response returned for a successful insert, you can set that using a property path:

...
insertConfig: {
  responses: {
    '$201.description': 'Foo bar baz'
  }
},
...

If you would prefer to override the 201 response wholesale, you can use the $merge operator:

...
insertConfig: {
  responses: {
    $merge: {
      201: {
        statusCode: 201,
        description: 'Foo bar baz',
        schema: {
          type: 'object',
          properties: {
            foo: {type: 'string'},
            bar: {type: 'string'}
          },
          additionalProperties: false,
          required: ['foo']
        },
        headers: ['Location', 'baz']
      }
    }
  }
},
...

Similarly, you can add custom parameters using the $merge operator:

...
insertConfig: {
  parameters: {
    $merge: {
      baz: {
        description: 'Foo bar baz',
        location: 'header',
        schema: {type: 'string'},
        default: 'yaz'
      }
    }
  }
},
...

If you’re paying close attention, these examples should strike you as odd given that the operators provided by o only work if they are applied to the top level properties of the object being instantiated. For example, $merge would not get applied in the following case:

var proto = {a: {b: {c: 1}}}
var instance = o({a: {b: {$merge: {d: 2}}}}, proto)
console.dir(instance, {depth: 4}) // => { a: { b: { '$merge': { d: 1 } } } }

Whereas, if the $merge operator is chained, you would get the expected result:

var proto = {a: {b: {c: 1}}}
var instance = o({a: {$merge: {b: {$merge: {d: 2}}}}}, proto)
console.dir(instance, {depth: 4}) // => { a: { b: { c: 0, d: 1 } } }

The reason, this works in the context of Collections is because the instantiation of the various CollectionOperationConfigs is handled by the Collection class during initialization after construction.