Code Monkey home page Code Monkey logo

node-exist's Introduction

node-exist

semantic release status js-standard-style

Mostly a shallow wrapper for eXist's XML-RPC API and eXist's REST API. Attempts to translate terminologies into node world. Uses promises.

Install

npm install @existdb/node-exist

NOTE: If you are looking for a command line client have a look at xst

Use

In addition to eXist-db's XML-RPC API you do now also have the option to leverage the potential of its REST-API. This allows to choose the best tool for any particular task. Both APIs are used in combination in the upload example.

REST

Status: unstable

NOTE: eXist-db's REST-API has its on methodology and available options may differ between instances. Especially, the ability to download the source code of XQuery files is prohibited by default (_source=yes is only availabe if enabled in descriptor.xml). For details of available options for each method please see the REST-API documentation

First, we need an instance of the restClient.

import { getRestClient } from '@existdb/node-exist'
const rc = await getRestClient()

For more information see also Connection Options.

The returned HTTP client has 4 methods

  • post(query, path[, options]): execute query in the context of path. The query is expected to be a XQuery main module and will be wrapped in the XML-fragment that exist expects.

    await rc.post('count(//p)', '/db')
  • put(data, path): create resources in the database. If sub-collections in path are missing, they will be created for you. The server will respond with StatusCode 400, Bad Request, for not-well- formed XML resources. In this case, the body contains a detailed description of the validation error.

    await rc.put('<root />', '/db/rest-test/test.xml')
  • get(path [, options][, writableStream]): read data from the database. The response body will contain the contents of the resource or a file listing, if the provided path is a collection. If a writableStream is passed in, the response body will be streamed into it.

    const { body } = await rc.get('/db/rest-test/test.xml')
    console.log(body)
  • del(path): remove resources and collections from an existdb instance

    await rc.del('/db/rest-test/test.xml')

Have a look at the rest-client example. The REST-client uses the Got library and works with streams and generators. Look at the rest tests to see examples.

XML-RPC

Creating, reading and removing a collection:

const {connect} = require('@existdb/node-exist')
const db = connect()

db.collections.create('/db/apps/test')
  .then(result => db.collections.describe('/db/apps/test'))
  .then(result => console.log('collection description:', result))
  .catch(e => console.error('fail', e))

Uploading an XML file into the database

const {connect} = require('@existdb/node-exist')
const db = connect()

db.documents.upload(Buffer.from('<root/>'))
  .then(fileHandle => db.documents.parseLocal(fileHandle, '/db/apps/test/file.xml', {}))
  .then(result => db.documents.read('/db/apps/test/file.xml'))
  .then(result => console.log('test file contents', result))
  .catch(error => console.error('fail', error))

Since all interactions with the database are promises you can also use async functions

const {connect} = require('@existdb/node-exist')
const db = connect()

async function uploadAndParse (filePath, contents) {
  const fileHandle = await db.documents.upload(contents)
  await db.documents.parseLocal(fileHandle, filePath, {})
  return filePath
}

// top-level await is not available everywhere, yet
uploadAndParse('/db/apps/test-file.xml', Buffer.from('<root/>'))
  .then(filePath => console.log("uploaded", filePath))
  .catch(error => console.error(error))

You can now also import node-exist into an ES module

import {connect} from '@existdb/node-exist'
const db = connect()

// do something with the db connection
db.collections.describe('/db/apps')
  .then(result => console.log(result))

You can also have a look at the examples for more use-cases.

Connection Options

In the previous section you learned that there are two APIs you can use to interact with an exist-db instance.

Both client constructor functions do accept an option argument of type NodeExistConnectionOptions. Calling them without arguments, as in the examples above will use default options.

{
  basic_auth: {
    user: 'guest',
    pass: 'guest'
  },
  protocol: 'https:',
  host: 'localhost',
  port: '8443',
  path: '/exist/rest'|'/exist/xmlrpc'
}

NOTE: The path property, the endpoint to reach an API, is different for REST ('/exist/xmlrpc') and XML-RPC ('/exist/xmlrpc'). You most likely do not need to change it. However, if you need to you can override those.

RESTClient with defaults

import {getRestClient} from '@existdb/node-exist'
const rest = await getRestClient({
  basic_auth: {
    user: 'guest',
    pass: 'guest'
  },
  protocol: 'https:',
  host: 'localhost',
  port: '8443',
  path: '/exist/rest'
})

XMLRPCClient with defaults

import {connect} from '@existdb/node-exist'
const db = connect({
  basic_auth: {
    user: 'guest',
    pass: 'guest'
  },
  protocol: 'https:',
  host: 'localhost',
  port: '8443',
  path: '/exist/xmlrpc'
})

Examples

  • Connect as someone else than guest

    {
      basic_auth: {
        user: 'me',
        pass: '1 troubadour artisanal #compost'
      }
    }
  • Connect to a local development server using HTTP

    {
      protocol: 'http:',
      port: 8080
    }
  • Connect to a server with an invalid or expired certificate.

    {
      host: 'here.be.dragons',
      rejectUnauthorized: false
    }

    NOTE: For remote hosts this is considered bad practice as it does only offer a false sense of security. For hosts considered local - localhost, 127.0.0.1 and [::1] - this is set automatically, because it is impossible to have trusted certificates for local hosts.

Read options from environment

readOptionsFromEnv offers a comfortable way to read the connection options from a set of environment variables

variable name default description
EXISTDB_USER none the user used to connect to the database and to execute queries with
EXISTDB_PASS none the password to authenticate the user against the database
EXISTDB_SERVER https://localhost:8443 the URL of the database instance to connect to (only http and https protocol allowed)

NOTE: In order to connect to an instance as a user other than guest both EXISTDB_USER and EXISTDB_PASS have to be set!

const {connect, restClient, readOptionsFromEnv} = require('@existdb/node-exist')
const db = connect(readOptionsFromEnv())
const rest = await getRestClient(readOptionsFromEnv())

For more details you can have a look how it is used in the connection script that is used for testing and in all example scripts.

Components

The XML-RPC commands are grouped into components by what they operate on. Every method returns a promise.

Queries

Status: working

execute

db.queries.execute(query, options)

read

db.queries.read(query, options)

readAll

This convenience function calls queries.count then retrieves all result pages and returns them in an array.

db.queries.readAll(query, options)

Example:

const query = `xquery version "3.1";
xmldb:get-child-collections($collection)
  => string-join(",\n")
`
const options = { variables: collection: "/db/apps" }

db.queries.readAll(query, options)
  .then(result => {
    const response = Buffer.concat(result.pages).toString() 
    console.log(response)
  })
  .catch(error => console.error(error))

count

db.queries.count(resultHandle)

retrieve

db.queries.retrieveResult(resultHandle, page)

retrieveAll

db.queries.retrieveAll(resultHandle)

releaseResult

free result on server

db.queries.releaseResult(resultHandle)

Documents

A document can be seen as a file. It might be indexed if it's type is not binary.

upload

Resolves into a file handle which can then be used by db.documents.parseLocal.

db.documents.upload(Buffer.from('test'))

parseLocal

db.documents.parseLocal(fileHandle, 'foo/test.txt', {})

read

Reads resources stored as XML (XMLResource). You can control how they are serialized by setting serialization options in the options parameter.

Use default serialization options.

db.documents.read('foo.xml', {})

Force XML declaration to be returned.

db.documents.read('foo.xml', { "omit-xml-declaration": "no" })

Force the file to end in a blank line (available since eXist-db v6.0.1).

db.documents.read('foo.xml', { "insert-final-newline": "yes" })

readBinary

Reads resources stored as binary (BinaryResource) inside existdb such as XQuery, textfiles, PDFs, CSS, images and the like.

db.documents.readBinary('foo.xml')

remove

db.documents.remove('foo.xml')

Resources

Status: working

A resource is identified by its path in the database. Documents and collections are resources.

describe

db.resources.describe(resourcePath)

setPermissions

db.resources.setPermissions(resourcePath, 400)

getPermissions

db.resources.getPermissions(resourcePath)

Collections

Status: working

create

db.collections.create(collectionPath)

remove

db.collections.remove(collectionPath)

describe

db.collections.describe(collectionPath)

read

db.collections.read(collectionPath)

exists

This function checks if the collection exists.

  • returns true if the collection exists and the current user can open it
  • returns false if the collection does not exist
  • throws an exception if the collection exists but the current user cannot access it
db.collections.exists(collectionPath)

existsAndCanOpen

This function checks if the collection exists and if it does, if the current user can access it.

  • returns true if the collection exists and the current user can open it
  • returns false if the collection does not exist
  • throws an exception if the collection exists but the current user cannot access it
db.collections.existsAndCanOpen(collectionPath)

App

Status: working

upload

After uploading a XAR you can install it

db.app.upload(xarBuffer, xarName)

Example:

const xarContents = fs.readFileSync('spec/files/test-app.xar')

db.app.upload(xarContents, 'test-app.xar')
  .then(result => console.log(result))
  .catch(error => console.error(error))

install

Install an uploaded XAR (this will call repo:install-and-deploy-from-db). For extra safety a previously installed version will be removed before installing the new version.

Dependencies will be resolved from http://exist-db.org/exist/apps/public-repo by default. If you want to use a different repository provide the optional customPackageRepoUrl.

db.app.install(xarName[, customPackageRepoUrl])

Example:

db.app.install('test-app.xar')
  .then(result => console.log(result))
  .catch(error => console.error(error))

Returns

{
  "success": true,
  "result": {
    "update": false, // true if a previous version was found
    "target": "/db/apps/test-app"
  }
}

Error

{
  success: false,
  error: Error
}

remove

Uninstall and remove the application identified by its namespace URL. If no app with packageUri could be found then this counts as success.

db.app.remove(packageUri)

Example:

db.app.remove('http://exist-db.org/apps/test-app')
  .then(result => console.log(result))
  .catch(error => console.error(error))

Returns

{ success: true }

Error

{
  success: false,
  error: Object | Error
}

packageCollection

The path to the collection where node-exist will upload packages to (/db/pkgtmp). Useful for cleanup after succesful installation.

Example:

db.documents.remove(`${db.app.packageCollection}/test-app.xar`)

Indices

Status: TODO

Users

Status: working

getUserInfo

Will return the information about the given user.

db.users.getUserInfo(username)

Example:

db.users.getUserInfo('admin')

Returns:

{
    uid: 1048574,
    'default-group-id': 1048575,
    umask: 18,
    metadata: {
      'http://exist-db.org/security/description': 'System Administrator',
      'http://axschema.org/namePerson': 'admin'
    },
    'default-group-name': 'dba',
    'default-group-realmId': 'exist',
    name: 'admin',
    groups: [ 'dba' ],
    enabled: 'true'
}

list

db.users.list()

Returns an array of user info objects (see getUserInfo()).

server

Status: working

version

Query the eXist-db version running on the server. Returns the SemVer version as a string (e.g. 5.4.1 or 6.1.0-SNAPSHOT).

db.server.version()

syncToDisk

db.server.syncToDisk()

shutdown

db.server.shutdown()

Note: There is no way to bring it up again.

Command Line Scripts

You can use this library to build a command line interface that interacts with existdb instances. A few basic examples how to do this are included in this repository.

Example:

spec/examples/exist-ls /db/apps

NOTE: Have a look at xst for a CLI client built with node-exist.

Test

All tests are in spec/tests and written for tape

npm test

NOTE: You can override connection settings with environment variables. See examples for more information.

To execute a single run using a different server you can also just define the variable directly:

EXISTDB_SERVER=http://localhost:8888 npm test

Roadmap

  • switch to use eXist-db's REST-API (available through rest-client)
  • refactor to ES6 modules
  • better type hints

Compatibility

node-exist is tested to be compatible with eXist-db 4, 5 and 6. It should be compatible with version 3, except for the XAR installation.

Disclaimer

Use at your own risk.

This software is safe for development. It may be used to work with a production instance, but think twice before your data is lost.

node-exist's People

Contributors

dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar duncdrum avatar line-o avatar reinhapa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-exist's Issues

improve resource size formatting in `exist-ls --extended`

Currently the raw bytes are listed, preferably this will behave as the unix ls command.

So that each value have a integer or floating point value of one to four characters in total followed by one character that determines if the numerical value is either

  • bytes (B)
  • kilobytes (K)
  • megabytes (M)
  • gigabytes (G)

Larger powers of 1024 (tera, peta, ...) are not considered at this point as resources that are this large are not feasible to be handled by exist at the moment.

limit `exist-ls` to collections, resources

Similar to ls -d that only lists directories we could introduce flags that will limit output of exist-ls to either resources or collections.

A flag to see only XML or binary resources might also be useful.
Even the possibility to list only files of a specific mime-type could come in handy.

Feature originally requested by @chakl

support https

Currently, only http protocol is supported.
To enable db instances that are only available through https, a few changes have to be made.

Switch to xmlrpc.createSecureClient if https is needed.

var client = xmlrpc.createClient(assign({}, defaultRPCoptions, options))

It is yet not decided on how to trigger switching to https.

Idea:

const ex = require('@existdb/node-exist')

ex.connect({
    'secure': true
})

cleanup app.packageCollection after use

db/pkgtmp used for xar installs hangs around after use, which adds another location where outdated xars might unneccesarily accumulate.

since this is a temporary collection, node-exist should clean up after itself, without user intervention

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this πŸ’ͺ.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Invalid npm token.

The npm token configured in the NPM_TOKEN environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/.

If you are using Two-Factor Authentication, make configure the auth-only level is supported. semantic-release cannot publish with the default auth-and-writes level.

Please make sure to set the NPM_TOKEN environment variable in your CI with the exact value of the npm token.


Good luck with your project ✨

Your semantic-release bot πŸ“¦πŸš€

add cli-tool `exist-get`

Download resources or collection trees from an existdb into the filesystem.

  • exist-get source-collection target-directory
  • exist-get resource target

add cli tool to list packages currently installed in an instance

Possible names:

  • exist-package
  • exist-pkg
  • expath-pkg
exist-pkg [options]

Should return tabulated list with package-abbreviation and version.

Example Output:

package-service  1.2.39
dashboard        10.3.2
monex             3.0.3
html-templating   1.0.4
eXide             3.0.1

Possible options:

  • --libraries, -l or -L: list only installed libraries
  • --applicationts, -a or -A: list only installed applications
  • --filter, -f: list only libraries and/or applications that match a given pattern
  • --extended, -e: add more information to the result (date of installation, namespace URI, declared dependencies, ...)
  • --json, -j: return result as json (in order to pipe it to jq for example or store it in a file)

[question]: add mocha xqsuite script from generator

I now have a pretty robust and dynamic test generator for xqsuite tests executed on the server and then "parsed" by Mocha. see generators/app/templates/tests/xqs/xqSuite.js. Should we include that here?

The only thing that we would need to adjust is adding a method to provide the location of the xqsuite runner (and or suite) file.

The downside it would add Mocha as a dependency.

The upside you could run xqsuite tests from inside node-exist.

@line-o what do you think?

[BUG] misleading authentication error message

Describe the bug

Looks like username admin is hardcoded in the authentication error message.

exsol:~$ npx -p @existdb/node-exist exist-ls /db/apps
Wrong password for user [admin] 

Forgot to pass credentials... Specify admin password via EXISTDB_PASS env var:

exsol:~$ env | grep EXIST
EXISTDB_PASS=MY_ADMIN_SECRET
exsol:~$ npx -p @existdb/node-exist exist-ls /db/apps
Wrong password for user [admin] 

Probably the default guest user is used for login, but why does it report "wrong pass for user admin"? Explicitly setting EXISTDB_USER works as expected.

exsol:~$ env | grep EXIST
EXISTDB_USER=admin
EXISTDB_PASS=MY_ADMIN_SECRET
exsol:~$ npx -p @existdb/node-exist exist-ls /db/apps
[... works ...]

Expected behavior
Report wrong password for the username that is actually used for authentication:

Wrong password for user [guest] 

Context (please always complete the following information):

test use of docker for CI

user docker images for creating exist-db instanced and running test.
Let's see if these are faster or not

`exist-ls --extended` cannot read permissions of restricted collection

When exist-ls tries to read permissions on a collection the current user is not allowed to access it will throw an error and stop.

Steps to reproduce

run

EXISTDB_USER=guest \
EXISTDB_PASS=guest \
exist-ls --extended /db/system

The above will return

exerr:ERROR Permission to retrieve permissions is denied for user 'guest' on '/db/system/security': Permission denied to open collection: /db/system/security by guest

refs eXist-db/exist#4240

add sorting to `exist-ls`

Sort by

  • name
  • size
  • age

allow sorting to be inverted (ascending instead of descending)

Sorting by name was originally requested by @joewiz and he suggests to sort the listing in XQuery using the collation http://www.w3.org/2013/collation/UCA?numeric=yes.

move examples to `bin` directory

This will allow the npm package to ignore the spec folder and will result in a smaller package size.

  • move ./spec/examples/* to ./bin
  • change package.json to pick up CLI tools from new location
  • add ./spec to npm ignore
  • copy ./spec/connection.js to ./bin
  • adapt ./bin/connection.js to meet CLI tool requirements (maybe default to guest/guest instead of admin)

add cli-tool `exist-exec`

exist-exec should execute queries in an existdb instance and the result should go to standard out

The query can be provided as a positional argument:

$ exist-exec '1 to 9'
1 2 3 4 5 6 7 8 9

The query can be in a file:

Contents of query.xq

1 to 9
$ exist-exec -f query.xq
1 2 3 4 5 6 7 8 9

packages cannot be installed with default repository URL

The default repository URL is set to https://exist-db.org/exist/apps/public-repo.

db.app.install fails to install packages using default repository URL because of eXist-db/public-repo#74

As a workaround pass http://exist-db.org/exist/apps/public-repo as your custom repository URL.

Example

db.app.install(xarName, "http://exist-db.org/exist/apps/public-repo")

Upload to DB from inside Docker container

Hi. With this code I can upload a XML file to the instance of ExistDB I'm connected to without any problem, if executed locally (not on the host the DB is in).

`

const path = collection.concat(data.recordId).concat('.xml'); 

const content = Buffer.from(modifiedXML); 

const fh = await db.documents.upload(content, content.length);

const r = await db.documents.parseLocal(fh, path);

`

However, I'm unable to upload anything when the script is executed from inside a Docker container, as it timeouts during the upload function. From the container I can even correctly ping the IP address of my ExistDB instance. The DB is on a container on the same host

Is it possible to upload from a container? Do I need some more configurations?

Thanks in advance

support .existdb.json

This Is more of a question:
Should node-exist read the settings file used by atom-existdb and existdb-langserver?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.