/kaba-dat-api

kaba-dat-api2

Primary LanguageJavaScriptMIT LicenseMIT

kaba-dat-api2

The internal implementation for Kaba's DatArchive APIs. Works with Dat 2.0.

All async methods work with callbacks and promises. If no callback is provided, a promise will be returned.

Any time a hyperdrive archive is expected, a scoped-fs instance can be provided, unless otherwise stated.

var hyperdrive = require('hyperdrive')
var ScopedFS = require('scoped-fs')

var archive = hyperdrive('./my-hyperdrive')
var scopedfs = new ScopedFS('./my-scoped-fs')

await pda.readFile(archive, '/hello.txt') // read the published hello.txt
await pda.readFile(scopedfs, '/hello.txt') // read the local hello.txt

** NOTE: this library is written natively for node 12 and above. **

const pda = require('kaba-dat-api')

Lookup

stat(archive, name[, opts, cb])

  • archive Hyperdrive archive (object).
  • name Entry name (string).
  • opts.lstat Get symlink information if target is a symlink (boolean).
  • Returns a Hyperdrive Stat entry (object).
  • Throws NotFoundError
// by name:
var st = await pda.stat(archive, '/index.json')
st.isDirectory()
st.isFile()
console.log(st) /* =>
Stat {
  dev: 0,
  nlink: 1,
  rdev: 0,
  blksize: 0,
  ino: 0,
  mode: 16877,
  uid: 0,
  gid: 0,kaba
  size: 0,
  offset: 0,
  blocks: 0,
  atime: 2017-04-10T18:59:00.147Z,
  mtime: 2017-04-10T18:59:00.147Z,
  ctime: 2017-04-10T18:59:00.147Z,
  linkname: undefined } */

Read

readFile(archive, name[, opts, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • opts. Options (object|string). If a string, will act as opts.encoding.
  • opts.encoding Desired output encoding (string). May be 'binary', 'utf8', 'hex', 'base64', or 'json'. Default 'utf8'.
  • Returns the content of the file in the requested encoding.
  • Throws NotFoundError.
var manifestStr = await pda.readFile(archive, '/index.json')
var manifestObj = await pda.readFile(archive, '/index.json', 'json')
var imageBase64 = await pda.readFile(archive, '/favicon.png', 'base64')

readdir(archive, path[, opts, cb])

  • archive Hyperdrive archive (object).
  • path Target directory path (string).
  • opts.recursive Read all subfolders and their files as well if true. Note: does not recurse into mounts.
  • opts.includeStats Output an object which includes the file name, stats object, and parent mount information.
  • Returns an array of file and folder names.
var listing = await pda.readdir(archive, '/assets')
console.log(listing) // => ['profile.png', 'styles.css']

var listing = await pda.readdir(archive, '/', { recursive: true })
console.log(listing) /* => [
  'index.html',
  'assets',
  'assets/profile.png',
  'assets/styles.css'
]*/

var listing = await pda.readdir(archive, '/', { includeStats: true })
console.log(listing) /* => [
  {
    name: 'profile.png',
    stats: { ... },
    mount: { ... }
  },
  ...
]*/

readSize(archive, path[, cb])

  • archive Hyperdrive archive (object).
  • path Target directory path (string).
  • Returns a number (size in bytes).

This method will recurse on folders.

var size = await pda.readSize(archive, '/assets')
console.log(size) // => 123

createReadStream(archive, name[, opts, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • opts. Options (object|string). If a string, will act as opts.encoding.
  • opts.start Starting offset (number). Default 0.
  • opts.end. Ending offset inclusive (number). Default undefined.
  • opts.length. How many bytes to read (number). Default undefined.
  • Returns a readable stream.
  • Throws NotFoundError.
pda.createReadStream(archive, '/favicon.png')
pda.createReadStream(archive, '/favicon.png', {
  start: 1,
  end: 3
})

Write

writeFile(archive, name, data[, opts, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • data Data to write (string|Buffer).
  • opts. Options (object|string). If a string, will act as opts.encoding.
  • opts.encoding Desired file encoding (string). May be 'binary', 'utf8', 'hex', 'base64', or 'json'. Default 'utf8' if data is a string, 'binary' if data is a Buffer.
  • Throws ArchiveNotWritableError, InvalidPathError, EntryAlreadyExistsError, InvalidEncodingError.
await pda.writeFile(archive, '/hello.txt', 'world', 'utf8')
await pda.writeFile(archive, '/thing.json', {hello: 'world'}, 'json')
await pda.writeFile(archive, '/profile.png', fs.readFileSync('/tmp/dog.png'))

mkdir(archive, name[, cb])

  • archive Hyperdrive archive (object).
  • name Directory path (string).
  • Throws ArchiveNotWritableError, InvalidPathError, EntryAlreadyExistsError, InvalidEncodingError.
await pda.mkdir(archive, '/stuff')

symlink(archive, target, linkname[, cb])

  • archive Hyperdrive archive (object).
  • target Path to symlink to (string).
  • linkname Path to create the symlink (string).
  • Throws ArchiveNotWritableError, InvalidPathError, EntryAlreadyExistsError, InvalidEncodingError.
await pda.symlink(archive, '/hello.txt', '/goodbye.txt')

copy(srcArchive, srcName, dstArchive, dstName[, cb])

  • srcArchive Source Hyperdrive archive (object).
  • srcName Path to file or directory to copy (string).
  • dstArchive Destination Hyperdrive archive (object).
  • dstName Where to copy the file or folder to (string).
  • Throws ArchiveNotWritableError, InvalidPathError, EntryAlreadyExistsError, InvalidEncodingError.
// copy file:
await pda.copy(archive, '/foo.txt', archive, '/foo.txt.back')
// copy folder:
await pda.copy(archive, '/stuff', otherArchive, '/stuff')

rename(srcArchive, srcName, dstName[, cb])

  • srcArchive Source Hyperdrive archive (object).
  • srcName Path to file or directory to rename (string).
  • dstArchive Destination Hyperdrive archive (object).
  • dstName What the file or folder should be named (string).
  • Throws ArchiveNotWritableError, InvalidPathError, EntryAlreadyExistsError, InvalidEncodingError.

This is equivalent to moving a file/folder.

// move file:
await pda.rename(archive, '/foo.txt', archive, '/foo.md')
// move folder:
await pda.rename(archive, '/stuff', otherArchive, '/stuff')

updateMetadata(archive, path, metadata[, cb])

  • archive Hyperdrive archive (object).
  • path Entry path (string).
  • metadata Metadata values to set (object).

Updates the file/folder metadata. Does not overwrite all values; any existing metadata keys which are not specified in the metadata param are preserved.

await pda.updateMetadata(archive, '/hello.txt', {foo: 'bar'})

The default encoding for metadata attributes is utf8. Attributes which start with bin: are encoded in binary.

await pda.updateMetadata(archive, '/hello.txt', {'bin:foo': Buffer.from([1,2,3,4]})
(await pda.stat(archive, '/hello.txt')).metadata['bin:foo'] //=> Buffer([1,2,3,4])

deleteMetadata(archive, path, keys[, cb])

  • archive Hyperdrive archive (object).
  • path Entry path (string).
  • keys Metadata keys to delete (string | string[]).
await pda.deleteMetadata(archive, '/hello.txt', ['foo'])

createWriteStream(archive, name[, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • Throws ArchiveNotWritableError, InvalidPathError, EntryAlreadyExistsError.
await pda.createWriteStream(archive, '/hello.txt')

Delete

unlink(archive, name[, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • Throws ArchiveNotWritableError, NotFoundError, NotAFileError
await pda.unlink(archive, '/hello.txt')

rmdir(archive, name[, opts, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • opts.recursive Delete all subfolders and files if the directory is not empty.
  • Throws ArchiveNotWritableError, NotFoundError, NotAFolderError, DestDirectoryNotEmpty
await pda.rmdir(archive, '/stuff', {recursive: true})

Mounts

mount(archive, name, opts[, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • opts. Options (object|string). If a string or buffer, will act as opts.key.
  • opts.key Key of archive to mount. May be a hex string or Buffer.
  • Throws ArchiveNotWritableError, InvalidPathError
await pda.mount(archive, '/foo', archive2.key)

unmount(archive, name[, cb])

  • archive Hyperdrive archive (object).
  • name Entry path (string).
  • Throws ArchiveNotWritableError, InvalidPathError, NotFoundError
await pda.unmount(archive, '/foo')

Activity Streams

watch(archive[, path])

  • archive Hyperdrive archive (object).
  • path Prefix path. If falsy, will watch all files.
  • Returns a Readable stream.

Watches the given path for file events, which it emits as an emit-stream. Supported events:

  • ['changed',{path}] - The contents of the file has changed. path is the path-string of the file.
var es = pda.watch(archive, 'foo.txt')

es.on('data', ([event, args]) => {
  if (event === 'changed') {
    console.log(args.path, 'has changed')
  }
})

// alternatively, via emit-stream:

var emitStream = require('emit-stream')
var events = emitStream(pda.watch(archive))
events.on('changed', args => {
  console.log(args.path, 'has changed')
})

createNetworkActivityStream(archive)

  • archive Hyperdrive archive (object). Can not be a scoped-fs object.
  • Returns a Readable stream.

Watches the archive for network events, which it emits as an emit-stream. Supported events:

  • ['network-changed',{connections}] - The number of connections has changed. connections is a number.
  • ['download',{feed,block,bytes}] - A block has been downloaded. feed will either be "metadata" or "content". block is the index of data downloaded. bytes is the number of bytes in the block.
  • ['upload',{feed,block,bytes}] - A block has been uploaded. feed will either be "metadata" or "content". block is the index of data downloaded. bytes is the number of bytes in the block.
  • ['sync',{feed}] - All known blocks have been downloaded. feed will either be "metadata" or "content".
var es = pda.createNetworkActivityStream(archive)

es.on('data', ([event, args]) => {
  if (event === 'network-changed') {
    console.log('Connected to %d peers', args.connections)
  } else if (event === 'download') {
    console.log('Just downloaded %d bytes (block %d) of the %s feed', args.bytes, args.block, args.feed)
  } else if (event === 'upload') {
    console.log('Just uploaded %d bytes (block %d) of the %s feed', args.bytes, args.block, args.feed)
  } else if (event === 'sync') {
    console.log('Finished downloading', args.feed)
  }
})

// alternatively, via emit-stream:

var emitStream = require('emit-stream')
var events = emitStream(es)
events.on('network-changed', args => {
  console.log('Connected to %d peers', args.connections)
})
events.on('download', args => {
  console.log('Just downloaded %d bytes (block %d) of the %s feed', args.bytes, args.block, args.feed)
})
events.on('upload', args => {
  console.log('Just uploaded %d bytes (block %d) of the %s feed', args.bytes, args.block, args.feed)
})
events.on('sync', args => {
  console.log('Finished downloading', args.feed)
})

Exporters

exportFilesystemToArchive(opts[, cb])

  • opts.srcPath Source path in the filesystem (string). Required.
  • opts.dstArchive Destination archive (object). Required.
  • opts.dstPath Destination path within the archive. Optional, defaults to '/'.
  • opts.ignore Files not to copy (array of strings). Optional. Uses anymatch.
  • opts.inplaceImport Should import source directory in-place? (boolean). If true and importing a directory, this will cause the directory's content to be copied directy into the dstPath. If false, will cause the source-directory to become a child of the dstPath.
  • opts.dryRun Don't actually make changes, just list what changes will occur. Optional, defaults to false.
  • opts.progress Function called with the stats object on each file updated.
  • Returns stats on the export.

Copies a file-tree into an archive.

var stats = await pda.exportFilesystemToArchive({
  srcPath: '/tmp/mystuff',
  dstArchive: archive,
  inplaceImport: true
})
console.log(stats) /* => {
  addedFiles: ['fuzz.txt', 'foo/bar.txt'],
  updatedFiles: ['something.txt'],
  removedFiles: [],
  addedFolders: ['foo'],
  removedFolders: [],
  skipCount: 3, // files skipped due to the target already existing
  fileCount: 3,
  totalSize: 400 // bytes
}*/

exportArchiveToFilesystem(opts[, cb])

  • opts.srcArchive Source archive (object). Required.
  • opts.dstPath Destination path in the filesystem (string). Required.
  • opts.srcPath Source path within the archive. Optional, defaults to '/'.
  • opts.ignore Files not to copy (array of strings). Optional. Uses anymatch.
  • opts.overwriteExisting Proceed if the destination isn't empty (boolean). Default false.
  • opts.skipUndownloadedFiles Ignore files that haven't been downloaded yet (boolean). Default false. If false, will wait for source files to download.
  • Returns stats on the export.

Copies an archive into the filesystem.

NOTE

  • Unlike exportFilesystemToArchive, this will not compare the target for equality before copying. If overwriteExisting is true, it will simply copy all files again.
var stats = await pda.exportArchiveToFilesystem({
  srcArchive: archive,
  dstPath: '/tmp/mystuff',
  skipUndownloadedFiles: true
})
console.log(stats) /* => {
  addedFiles: ['fuzz.txt', 'foo/bar.txt'],
  updatedFiles: ['something.txt'],
  fileCount: 3,
  totalSize: 400 // bytes
}*/

exportArchiveToArchive(opts[, cb])

  • opts.srcArchive Source archive (object). Required.
  • opts.dstArchive Destination archive (object). Required.
  • opts.srcPath Source path within the source archive (string). Optional, defaults to '/'.
  • opts.dstPath Destination path within the destination archive (string). Optional, defaults to '/'.
  • opts.ignore Files not to copy (array of strings). Optional. Uses anymatch.
  • opts.skipUndownloadedFiles Ignore files that haven't been downloaded yet (boolean). Default false. If false, will wait for source files to download.
  • opts.dryRun Don't actually make changes, just list what changes will occur. Optional, defaults to false.
  • opts.intoTargetFolder Export into the dstPath target folder instead of replacing it.

Copies an archive into another archive.

NOTE

  • Unlike exportFilesystemToArchive, this will not compare the target for equality before copying. It copies files indescriminately.
var stats = await pda.exportArchiveToArchive({
  srcArchive: archiveA,
  dstArchive: archiveB,
  skipUndownloadedFiles: true
})
console.log(stats) /* => {
  addedFiles: ['fuzz.txt', 'foo/bar.txt'],
  updatedFiles: ['something.txt'],
  removedFiles: ['hi.png'],
  addedFolders: ['foo']
  removedFolders: [],
  fileCount: 3,
  totalSize: 400 // bytes
}*/

Manifest

readManifest(archive[, cb])

  • archive Hyperdrive archive (object).

A sugar to get the manifest object.

var manifestObj = await pda.readManifest(archive)

writeManifest(archive, manifest[, cb])

  • archive Hyperdrive archive (object).
  • manifest Manifest values (object).

A sugar to write the manifest object.

await pda.writeManifest(archive, { title: 'My dat!' })

updateManifest(archive, manifest[, cb])

  • archive Hyperdrive archive (object).
  • manifest Manifest values (object).

A sugar to modify the manifest object.

await pda.writeManifest(archive, { title: 'My dat!', description: 'the desc' })
await pda.writeManifest(archive, { title: 'My new title!' }) // preserves description

generateManifest(opts)

  • opts Manifest options (object).

Helper to generate a manifest object. Opts in detail:

{
  url: String, the dat's url
  title: String
  description: String
  type: String
  author: String | Object{url: String}
  links: Object
  web_root: String
  fallback_page: String
}

See: https://github.com/datprotocol/index.json

Diff

diff(archive, other[, prefix])

  • archive Archive (object). Required.
  • other Other version to diff against (number|object). Required.
  • prefix Path prefix to filter down to (string). Optional.
  • Returns diff data.

Get a list of differences between an archive at two points in its history

await pda.diff(archive, 2)
await pda.diff(archive, await archive.checkout(2))
await pda.diff(archive, 2, '/subfolder')

Output looks like:

[
  {type: 'put', name: 'hello.txt', value: {stat: {...}}},
  {type: 'mount', name: 'mounted-folder', value: {mount: {...}}},
  {type: 'del', name: 'hello.txt'}
]

Util

setInvalidAuthHandler(fn)

  • fn Function. Required.

Sets a handler for when the daemon fails authentication. This can occur sometimes because the daemon has reset recently, forcing the auth token to change.