'use strict'
var base64 = require('base64-js')
var deprecate = require('util-deprecate')
var errors = require('./errors')
var Expr = require('./Expr')
var util = require('./_util')
var nodeUtil = util.isNodeEnv() ? require('util') : null
var customInspect = nodeUtil && nodeUtil.inspect.custom
var stringify = nodeUtil ? nodeUtil.inspect : JSON.stringify
/**
* FaunaDB value types. Generally, these collections do not need to be instantiated
* directly; they can be constructed through helper methods in {@link module:query}.
*
* Instances of these collections will be returned in responses if the response object
* contains these values. For example, a FaunaDB response containing
*`{ "@ref": { "id": "123", "collection": { "@ref": { "id": "frogs", "collection": { "@ref": { "id": "collectiones" } } } } } }`
* will be returned as `new values.Ref("123", new values.Ref("frogs", values.Native.COLLECTIONS))`.
*
* See the [FaunaDB Query API Documentation](https://app.fauna.com/documentation/reference/queryapi#simple-type)
* for more information.
*
* @module values
*/
/**
* Base type for FaunaDB value objects.
*
* @extends Expr
* @abstract
* @constructor
*/
function Value() {}
util.inherits(Value, Expr)
Value.prototype._isFaunaValue = true
/**
* FaunaDB ref.
* See the [docs](https://app.fauna.com/documentation/reference/queryapi#special-type).
*
* @param {string} id
* The id portion of the ref.
* @param {Ref} [collection]
* The collection portion of the ref.
* @param {Ref} [database]
* The database portion of the ref.
*
* @extends module:values~Value
* @constructor
*/
function Ref(id, collection, database) {
if (!id) throw new errors.InvalidValue('id cannot be null or undefined')
this.value = { id: id }
if (collection) this.value['collection'] = collection
if (database) this.value['database'] = database
}
util.inherits(Ref, Value)
Ref.prototype._isFaunaRef = true
/**
* Gets the collection part out of the Ref.
*
* @member {string}
* @name module:values~Ref#collection
*/
Object.defineProperty(Ref.prototype, 'collection', {
get: function() {
return this.value['collection']
},
})
/**
* DEPRECATED. Gets the class part out of the Ref.
*
* @member {string}
* @name module:values~Ref#class
*/
Object.defineProperty(Ref.prototype, 'class', {
get: deprecate(function() {
return this.value['collection']
}, 'class is deprecated, use collection instead'),
})
/**
* Gets the database part out of the Ref.
*
* @member {Ref}
* @name module:values~Ref#database
*/
Object.defineProperty(Ref.prototype, 'database', {
get: function() {
return this.value['database']
},
})
/**
* Gets the id part out of the Ref.
*
* @member {Ref}
* @name module:values~Ref#id
*/
Object.defineProperty(Ref.prototype, 'id', {
get: function() {
return this.value['id']
},
})
/** @ignore */
Ref.prototype.toJSON = function() {
return { '@ref': this.value }
}
wrapToString(Ref, function() {
var constructors = {
collections: 'Collection',
databases: 'Database',
indexes: 'Index',
functions: 'Function',
roles: 'Role',
access_providers: 'AccessProvider',
}
var isNative = function(ref) {
return ref.collection === undefined
}
var toString = function(ref) {
if (isNative(ref)) {
var db = ref.database !== undefined ? ref.database.toString() : ''
if (ref.id === 'access_providers') return 'AccessProviders(' + db + ')'
return ref.id.charAt(0).toUpperCase() + ref.id.slice(1) + '(' + db + ')'
}
if (isNative(ref.collection)) {
var constructor = constructors[ref.collection.id]
if (constructor !== undefined) {
var db =
ref.database !== undefined ? ', ' + ref.database.toString() : ''
return constructor + '("' + ref.id + '"' + db + ')'
}
}
return 'Ref(' + toString(ref.collection) + ', "' + ref.id + '")'
}
return toString(this)
})
/** @ignore */
Ref.prototype.valueOf = function() {
return this.value
}
/**
* Whether these are both Refs and have the same value.
* @param {any} other
* @returns {boolean}
*/
Ref.prototype.equals = function(other) {
return (
(other instanceof Ref ||
util.checkInstanceHasProperty(other, '_isFaunaRef')) &&
this.id === other.id &&
((this.collection === undefined && other.collection === undefined) ||
this.collection.equals(other.collection)) &&
((this.database === undefined && other.database === undefined) ||
this.database.equals(other.database))
)
}
var Native = {
COLLECTIONS: new Ref('collections'),
INDEXES: new Ref('indexes'),
DATABASES: new Ref('databases'),
FUNCTIONS: new Ref('functions'),
ROLES: new Ref('roles'),
KEYS: new Ref('keys'),
ACCESS_PROVIDERS: new Ref('access_providers'),
}
Native.fromName = function(name) {
switch (name) {
case 'collections':
return Native.COLLECTIONS
case 'indexes':
return Native.INDEXES
case 'databases':
return Native.DATABASES
case 'functions':
return Native.FUNCTIONS
case 'roles':
return Native.ROLES
case 'keys':
return Native.KEYS
case 'access_providers':
return Native.ACCESS_PROVIDERS
}
return new Ref(name)
}
/**
* FaunaDB Set.
* This represents a set returned as part of a response.
* This looks like `{"@set": set_query}`.
* For query sets see {@link match}, {@link union},
* {@link intersection}, {@link difference}, and {@link join}.
*
* @extends module:values~Value
* @constructor
*/
function SetRef(value) {
/** Raw query object. */
this.value = value
}
util.inherits(SetRef, Value)
wrapToString(SetRef, function() {
return Expr.toString(this.value)
})
/** @ignore */
SetRef.prototype.toJSON = function() {
return { '@set': this.value }
}
/** FaunaDB time. See the [docs](https://app.fauna.com/documentation/reference/queryapi#special-type).
*
* @param {string|Date} value If a Date, this is converted to a string.
* @extends module:values~Value
* @constructor
*/
function FaunaTime(value) {
if (value instanceof Date) {
value = value.toISOString()
} else if (!(value.charAt(value.length - 1) === 'Z')) {
throw new errors.InvalidValue("Only allowed timezone is 'Z', got: " + value)
}
this.value = value
}
util.inherits(FaunaTime, Value)
/**
* Returns the date wrapped by this object.
* This is lossy as Dates have millisecond rather than nanosecond precision.
*
* @member {Date}
* @name module:values~FaunaTime#date
*/
Object.defineProperty(FaunaTime.prototype, 'date', {
get: function() {
return new Date(this.value)
},
})
wrapToString(FaunaTime, function() {
return 'Time("' + this.value + '")'
})
/** @ignore */
FaunaTime.prototype.toJSON = function() {
return { '@ts': this.value }
}
/** FaunaDB date. See the [docs](https://app.fauna.com/documentation/reference/queryapi#special-type).
*
* @param {string|Date} value
* If a Date, this is converted to a string, with time-of-day discarded.
* @extends module:values~Value
* @constructor
*/
function FaunaDate(value) {
if (value instanceof Date) {
// The first 10 characters 'YYYY-MM-DD' are the date portion.
value = value.toISOString().slice(0, 10)
}
/**
* ISO8601 date.
* @type {string}
*/
this.value = value
}
util.inherits(FaunaDate, Value)
/**
* @member {Date}
* @name module:values~FaunaDate#date
*/
Object.defineProperty(FaunaDate.prototype, 'date', {
get: function() {
return new Date(this.value)
},
})
wrapToString(FaunaDate, function() {
return 'Date("' + this.value + '")'
})
/** @ignore */
FaunaDate.prototype.toJSON = function() {
return { '@date': this.value }
}
/** FaunaDB bytes. See the [docs](https://app.fauna.com/documentation/reference/queryapi#special-type).
*
* @param {Uint8Array|ArrayBuffer|string} value
* If ArrayBuffer it's converted to Uint8Array
* If string it must be base64 encoded and it's converted to Uint8Array
* @extends module:values~Value
* @constructor
*/
function Bytes(value) {
if (value instanceof ArrayBuffer) {
this.value = new Uint8Array(value)
} else if (typeof value === 'string') {
this.value = base64.toByteArray(value)
} else if (value instanceof Uint8Array) {
this.value = value
} else {
throw new errors.InvalidValue(
'Bytes type expect argument to be either Uint8Array|ArrayBuffer|string, got: ' +
stringify(value)
)
}
}
util.inherits(Bytes, Value)
wrapToString(Bytes, function() {
return 'Bytes("' + base64.fromByteArray(this.value) + '")'
})
/** @ignore */
Bytes.prototype.toJSON = function() {
return { '@bytes': base64.fromByteArray(this.value) }
}
/** FaunaDB query. See the [docs](https://app.fauna.com/documentation/reference/queryapi#special-type).
*
* @param {any} value
* @extends module:values~Value
* @constructor
*/
function Query(value) {
this.value = value
}
util.inherits(Query, Value)
wrapToString(Query, function() {
return 'Query(' + Expr.toString(this.value) + ')'
})
/** @ignore */
Query.prototype.toJSON = function() {
return { '@query': this.value }
}
/** @ignore */
function wrapToString(type, fn) {
type.prototype.toString = fn
type.prototype.inspect = fn
if (customInspect) {
type.prototype[customInspect] = fn
}
}
module.exports = {
Value: Value,
Ref: Ref,
Native: Native,
SetRef: SetRef,
FaunaTime: FaunaTime,
FaunaDate: FaunaDate,
Bytes: Bytes,
Query: Query,
}