       callback(error, results)
     )
 
+class BlobHandler
+  constructor: (@name, @encodeFunction, @decodeFunction, @logging) ->
+
+  encode: (data, callback) =>
+    start = new Date().getTime()
+    @encodeFunction data, (error, result) =>
+      elapsed = new Date().getTime() - start
+      console.log(@name+'Blob:Compressed['+data.length+','+result.length+','+elapsed+'ms]') if @logging
+      callback?(error, result)
+
+  decode: (data, callback) =>
+    start = new Date().getTime()
+    @decodeFunction data, (error, result) =>
+      elapsed = new Date().getTime() - start
+      console.log(@name+'Blob:Decompressed['+data.length+','+result.length+','+elapsed+'ms]') if @logging
+      callback?(error, result)
+
+class Compressor
+  constructor: (@compression, logging) ->
+    @gzip = new BlobHandler 'Gzip',
+      (data, callback) ->
+        zlib.gzip(new Buffer(data), callback)
+      (data, callback) ->
+        zlib.gunzip(data, callback)
+      logging
+
+    @base64 = new BlobHandler 'Base64',
+      (data, callback) ->
+        callback?(null, new Buffer(data, 'binary').toString('base64'))
+      (data, callback) ->
+        callback?(null, new Buffer(data, 'base64'))
+      logging
+
+  encode: (data, callback) =>
+    if @compression
+      @gzip.encode data, (error, result) =>
+        @base64.encode(result, callback)
+    else
+      callback?(null, data)
+
+  decode: (data, callback) =>
+    if @compression
+      @base64.decode data, (error, result) =>
+        @gzip.decode(result, callback)
+    else
+      callback?(null, data)
+
 module.exports = AmazonDb = (options) ->
   return new Db if !(this instanceof AmazonDb)
 

        callback?('Failed to fetch operations')
      else
        data = []
        for operation in results.get_metadata.Items
          try
            op = JSON.parse(operation.op.S)
          catch error
            op = {}
            console.error('Failure: data was corrupt for Operation('+docName+'-'+operation.v.N+')')

          try
            meta = JSON.parse(operation.meta.S)
          catch error
            meta = {}
            console.error('Failure: metadata was corrupt for Operation('+docName+'-'+operation.v.N+')')

          item = {
            op: op
            meta: meta
          }
          data.push(item)

        callback? null, data
    )

  # Public: Write an operation to a document.

  # Calls callback(error) on failure.
  # Calls callback() on success.
  @writeSnapshot = (docName, docData, dbMeta, callback) ->
    compressor = new Compressor(options['compress'], options['timing'])
    async.auto(
      compress_meta: (cb) ->
        compressor.encode(JSON.stringify(docData.meta), cb)

      write_metadata: ['compress_meta', (cb, results) ->
        request =
          TableName: snapshots_table,
          Item:
            doc: { S: docName },
            v: { N: docData.v.toString() },
            meta: { S: results.compress_meta },
            type: { S: docData.type },
            created_at: { N: new Date().getTime().toString() }
          Expected:
            doc:
              Exists: false

        # Mark a snapshot as being compressed
        request.Item['c'] = { S: 't' } if options['compress']

        snapshots_rw_queue.push((c) ->
          db.putItem(request, c)
        , 'write Snapshot('+docName+'-'+docData.v+')', cb)
      ]

      compress_data: (cb) ->
        compressor.encode(JSON.stringify(docData.snapshot), cb)

      write_data: ['compress_data', (cb, results) ->
        path = snapshots_bucket+'/'+docName+'-'+docData.v+'.snapshot'
        headers = {}

        s3_rw_queue.push((c) ->
          s3.put(path, headers, results.compress_data, c)
        , 'write Snapshot('+docName+'-'+docData.v+')', cb)
      ]
    (error, results) ->
      if error?
        if error.message? and error.message.match 'The conditional request failed'
