Merge branch '2.0.x' into kneth/lazy-enable-sync
This commit is contained in:
commit
37105e7f00
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,4 +1,4 @@
|
|||
x.x.x Release notes (yyyy-MM-dd)
|
||||
x.x.x Release notes (yyyy-MM-dd)
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
* None.
|
||||
|
@ -7,10 +7,22 @@ x.x.x Release notes (yyyy-MM-dd)
|
|||
* None.
|
||||
|
||||
### Bug fixes
|
||||
* Configuration of file system is delay to after module import (#1351).
|
||||
* Configuration of sync file system is not done on module import but later when actually needed by sync (#1351)
|
||||
|
||||
|
||||
2.0.0 Release notes (2017-9-26)
|
||||
2.0.0 Release notes (2017-9-28)
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
* None.
|
||||
|
||||
### Enhancements
|
||||
* None.
|
||||
|
||||
### Bug fixes
|
||||
* An issue where access tokens were not refreshed correctly has been addressed.
|
||||
|
||||
|
||||
2.0.0-rc11 Release notes (2017-9-26)
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
* None
|
||||
|
@ -25,6 +37,9 @@ x.x.x Release notes (yyyy-MM-dd)
|
|||
* Alignment of permission schemas.
|
||||
* Updating sync (2.0.0-rc24).
|
||||
|
||||
|
||||
|
||||
|
||||
2.0.0-rc10 Release notes (2017-9-19)
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
PACKAGE_NAME=realm-js
|
||||
VERSION=2.0.0-rc11
|
||||
VERSION=2.0.0-rc13
|
||||
REALM_CORE_VERSION=3.2.1
|
||||
REALM_SYNC_VERSION=2.0.0-rc24
|
||||
REALM_OBJECT_SERVER_VERSION=2.0.0-alpha.36
|
||||
|
|
|
@ -18,17 +18,39 @@
|
|||
|
||||
/**
|
||||
* Abstract base class containing methods shared by {@link Realm.List} and {@link Realm.Results}.
|
||||
*
|
||||
* A Realm Collection is a homogenous sequence of values of any of the types
|
||||
* that can be stored as properties of Realm objects. A collection can be
|
||||
* accessed in any of the ways that a normal Javascript Array can, including
|
||||
* subscripting, enumerating with `for-of` and so on.
|
||||
*
|
||||
* @memberof Realm
|
||||
* @since 0.11.0
|
||||
*/
|
||||
class Collection {
|
||||
/**
|
||||
* The number of objects in the collection.
|
||||
* The number of values in the collection.
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get length() {}
|
||||
|
||||
/**
|
||||
* The {@linkplain Realm~PropertyType type} of values in the collection.
|
||||
* @type {string}
|
||||
* @readonly
|
||||
* @since 2.0.0
|
||||
*/
|
||||
get type() {}
|
||||
|
||||
/**
|
||||
* Whether `null` is a valid value for the collection.
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @since 2.0.0
|
||||
*/
|
||||
get optional() {}
|
||||
|
||||
/**
|
||||
* Checks if this collection has not been deleted and is part of a valid Realm.
|
||||
* @returns {boolean} indicating if the collection can be safely accessed.
|
||||
|
@ -38,12 +60,15 @@ class Collection {
|
|||
|
||||
/**
|
||||
* Returns new _Results_ that represent this collection being filtered by the provided query.
|
||||
*
|
||||
* @param {string} query - Query used to filter objects from the collection.
|
||||
* @param {...any} [arg] - Each subsequent argument is used by the placeholders
|
||||
* (e.g. `$0`, `$1`, `$2`, …) in the query.
|
||||
* @throws {Error} If the query or any other argument passed into this method is invalid.
|
||||
* @returns {Realm.Results} filtered according to the provided query.
|
||||
*
|
||||
* @returns {Realm.Results<T>} filtered according to the provided query.
|
||||
*
|
||||
* This is currently only supported for collections of Realm Objects.
|
||||
*
|
||||
* See {@tutorial query-language} for details about the query language.
|
||||
* @example
|
||||
* let merlots = wines.filtered('variety == "Merlot" && vintage <= $0', maxYear);
|
||||
|
@ -51,43 +76,76 @@ class Collection {
|
|||
filtered(query, ...arg) {}
|
||||
|
||||
/**
|
||||
* Returns new _Results_ that represent this collection being sorted by the provided property
|
||||
* (or properties) of each object.
|
||||
* @param {string|Realm.Results~SortDescriptor[]} descriptor - The property name(s) to sort
|
||||
* the objects in the collection.
|
||||
* @param {boolean} [reverse=false] - May only be provided if `descriptor` is a string.
|
||||
* Returns new _Results_ that represent a sorted view of this collection.
|
||||
*
|
||||
* A collection of Realm Objects can be sorted on one or more properties of
|
||||
* those objects, or of properties of objects linked to by those objects.
|
||||
* To sort by a single property, simply pass the name of that property to
|
||||
* `sorted()`, optionally followed by a boolean indicating if the sort should be reversed.
|
||||
* For more than one property, you must pass an array of
|
||||
* {@linkplain Realm.Collection~SortDescriptor sort descriptors} which list
|
||||
* which properties to sort on.
|
||||
*
|
||||
* Collections of other types sort on the values themselves rather than
|
||||
* properties of the values, and so no property name or sort descriptors
|
||||
* should be supplied.
|
||||
*
|
||||
* @example
|
||||
* // Sort wines by age
|
||||
* wines.sorted('age')
|
||||
* @example
|
||||
* // Sort wines by price in descending order, then sort ties by age in
|
||||
* // ascending order
|
||||
* wines.sorted([['price', false], ['age'])
|
||||
* @example
|
||||
* // Sort a list of numbers in ascending order
|
||||
* let sortedPrices = wine.pricesSeen.sort()
|
||||
* @example
|
||||
* // Sort people by how expensive their favorite wine is
|
||||
* people.sort("favoriteWine.price")
|
||||
*
|
||||
* @param {string|Realm.Collection~SortDescriptor[]} [descriptor] - The property name(s) to sort the collection on.
|
||||
* @param {boolean} [reverse=false] - Sort in descending order rather than ascended.
|
||||
* May not be supplied if `descriptor` is an array of sort descriptors.
|
||||
* @throws {Error} If a specified property does not exist.
|
||||
* @returns {Realm.Results} sorted according to the arguments passed in
|
||||
* @returns {Realm.Results<T>} sorted according to the arguments passed in.
|
||||
*/
|
||||
sorted(descriptor, reverse) {}
|
||||
|
||||
/**
|
||||
* Create a frozen snapshot of the collection. This means objects added to and removed from the
|
||||
* original collection will not be reflected in the _Results_ returned by this method.
|
||||
* However, objects deleted from the Realm will become `null` at their respective indices.
|
||||
* This is **not** a _deep_ snapshot, meaning the objects contained in this snapshot will
|
||||
* continue to update as changes are made to them.
|
||||
* @returns {Realm.Results} which will **not** live update.
|
||||
* Create a frozen snapshot of the collection.
|
||||
*
|
||||
* Values added to and removed from the original collection will not be
|
||||
* reflected in the _Results_ returned by this method, including if the
|
||||
* values of properties are changed to make them match or not match any
|
||||
* filters applied.
|
||||
*
|
||||
* This is **not** a _deep_ snapshot. Realm objects contained in this
|
||||
* snapshot will continue to update as changes are made to them, and if
|
||||
* they are deleted from the Realm they will be replaced by `null` at the
|
||||
* respective indices.
|
||||
*
|
||||
* @returns {Realm.Results<T>} which will **not** live update.
|
||||
*/
|
||||
snapshot() {}
|
||||
|
||||
/**
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries Array.prototype.entries}
|
||||
* @returns {Realm.Collection~Iterator} of each `[index, object]` pair in the collection
|
||||
* @returns {Realm.Collection~Iterator<T>} of each `[index, object]` pair in the collection
|
||||
* @since 0.11.0
|
||||
*/
|
||||
entries() {}
|
||||
|
||||
/**
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/keys Array.prototype.keys}
|
||||
* @returns {Realm.Collection~Iterator} of each index in the collection
|
||||
* @returns {Realm.Collection~Iterator<T>} of each index in the collection
|
||||
* @since 0.11.0
|
||||
*/
|
||||
keys() {}
|
||||
|
||||
/**
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values Array.prototype.values}
|
||||
* @returns {Realm.Collection~Iterator} of each Realm object in the collection
|
||||
* @returns {Realm.Collection~Iterator<T>} of each Realm object in the collection
|
||||
* @since 0.11.0
|
||||
*/
|
||||
values() {}
|
||||
|
@ -101,7 +159,7 @@ class Collection {
|
|||
* spread operators, and more.
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator Symbol.iterator}
|
||||
* and the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterable iterable protocol}
|
||||
* @returns {Realm.Collection~Iterator} of each Realm object in the collection
|
||||
* @returns {Realm.Collection~Iterator<T>} of each Realm object in the collection
|
||||
* @since 0.11.0
|
||||
* @example
|
||||
* for (let object of collection) {
|
||||
|
@ -128,7 +186,7 @@ class Collection {
|
|||
* index will be include in the return value. If negative, then the end index will be
|
||||
* counted from the end of the collection. If omitted, then all objects from the start
|
||||
* index will be included in the return value.
|
||||
* @returns {Realm.Object[]} containing the objects from the start index up to, but not
|
||||
* @returns {T[]} containing the objects from the start index up to, but not
|
||||
* including, the end index.
|
||||
* @since 0.11.0
|
||||
*/
|
||||
|
@ -143,7 +201,7 @@ class Collection {
|
|||
* - `index` – The index of the object being processed in the collection.
|
||||
* - `collection` – The collection itself.
|
||||
* @param {object} [thisArg] - The value of `this` when `callback` is called.
|
||||
* @returns {Realm.Object|undefined} if the `callback` did not return `true` for any object
|
||||
* @returns {T|undefined} if the `callback` did not return `true` for any object
|
||||
* in the collection.
|
||||
* @since 0.11.0
|
||||
*/
|
||||
|
@ -166,10 +224,11 @@ class Collection {
|
|||
|
||||
/**
|
||||
Finds the index of the given object in the collection.
|
||||
* @param {Realm.Object} [object] - The object to search for in the collection.
|
||||
* @throws {Error} If the argument does not belong to the realm.
|
||||
* @returns {number} representing the index where the object was found, or `-1`
|
||||
* if not in collection.
|
||||
* @param {T} object - The value to search for in the collection.
|
||||
* @throws {Error} If the argument is a {@link Realm.Object} that does not
|
||||
* belong to the same Realm as the collection.
|
||||
* @returns {number} representing the index where the value was found, or
|
||||
* `-1` if not in collection.
|
||||
* @since 1.8.2
|
||||
*/
|
||||
indexOf(object) {}
|
||||
|
@ -294,7 +353,7 @@ class Collection {
|
|||
/**
|
||||
* Remove the listener `callback` from the collection instance.
|
||||
* @param {function(collection, changes)} callback - Callback function that was previously
|
||||
* added as a listener through the {@link Collection#addListener addListener} method.
|
||||
* added as a listener through the {@link Collection#addListener addListener} method.
|
||||
* @throws {Error} If `callback` is not a function.
|
||||
*/
|
||||
removeListener(callback) {}
|
||||
|
@ -324,8 +383,9 @@ class Collection {
|
|||
*/
|
||||
|
||||
/**
|
||||
* The sort descriptors may either just be a string representing the property name, **or** an
|
||||
* array with two items: `[propertyName, reverse]`
|
||||
* A sort descriptor is either a string containing one or more property names
|
||||
* separate by dots, **or** an array with two items: `[propertyName, reverse]`.
|
||||
*
|
||||
* @typedef Realm.Collection~SortDescriptor
|
||||
* @type {string|Array}
|
||||
*/
|
||||
|
|
63
docs/list.js
63
docs/list.js
|
@ -19,59 +19,70 @@
|
|||
/**
|
||||
* Instances of this class will be returned when accessing object properties whose type is `"list"`
|
||||
* (see {@linkplain Realm~ObjectSchemaProperty ObjectSchemaProperty}).
|
||||
* The objects contained in a list are accessible through its index properties and may only be
|
||||
* modified inside a {@linkplain Realm#write write} transaction.
|
||||
*
|
||||
* Lists mostly behave like normal Javascript Arrays, except for that they can
|
||||
* only store values of a single type (indicated by the `type` and `optional`
|
||||
* properties of the List), and can only be modified inside a {@linkplain
|
||||
* Realm#write write} transaction.
|
||||
*
|
||||
* @extends Realm.Collection
|
||||
* @memberof Realm
|
||||
*/
|
||||
class List extends Collection {
|
||||
/**
|
||||
* Remove the **last** object from the list and return it.
|
||||
* Remove the **last** value from the list and return it.
|
||||
* @throws {Error} If not inside a write transaction.
|
||||
* @returns {Realm.Object|undefined} if the list is empty.
|
||||
* @returns {T|undefined} if the list is empty.
|
||||
*/
|
||||
pop() {}
|
||||
|
||||
/**
|
||||
* Add one or more objects to the _end_ of the list.
|
||||
* @param {...Realm.Object} object - Each object’s type must match
|
||||
* {@linkcode Realm~ObjectSchemaProperty objectType} specified in the schema.
|
||||
* @throws {TypeError} If an `object` is of the wrong type.
|
||||
* Add one or more values to the _end_ of the list.
|
||||
*
|
||||
* @param {...T} value - Values to add to the list.
|
||||
* @throws {TypeError} If a `value` is not of a type which can be stored in
|
||||
* the list, or if an object being added to the list does not match the
|
||||
* {@linkcode Realm~ObjectSchema object schema} for the list.
|
||||
*
|
||||
* @throws {Error} If not inside a write transaction.
|
||||
* @returns {number} equal to the new {@link Realm.List#length length} of the list
|
||||
* after adding objects.
|
||||
* @returns {number} equal to the new {@link Realm.List#length length} of
|
||||
* the list after adding the values.
|
||||
*/
|
||||
push(...object) {}
|
||||
push(...value) {}
|
||||
|
||||
/**
|
||||
* Remove the **first** object from the list and return it.
|
||||
* Remove the **first** value from the list and return it.
|
||||
* @throws {Error} If not inside a write transaction.
|
||||
* @returns {Realm.Object|undefined} if the list is empty.
|
||||
* @returns {T|undefined} if the list is empty.
|
||||
*/
|
||||
shift() {}
|
||||
|
||||
/**
|
||||
* Changes the contents of the list by removing objects and/or inserting new objects.
|
||||
* Changes the contents of the list by removing value and/or inserting new value.
|
||||
*
|
||||
* @see {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice Array.prototype.splice}
|
||||
* @param {number} index - The start index. If greater than the length of the list,
|
||||
* the start index will be set to the length instead. If negative, then the start index
|
||||
* will be counted from the end of the list (e.g. `list.length - index`).
|
||||
* @param {number} [count] - The number of objects to remove from the list. If not provided,
|
||||
* then all objects from the start index through the end of the list will be removed.
|
||||
* @param {...Realm.Object} [object] - Objects to insert into the list starting at `index`.
|
||||
* @returns {Realm.Object[]} containing the objects that were removed from the list. The
|
||||
* array is empty if no objects were removed.
|
||||
* @param {number} [count] - The number of values to remove from the list.
|
||||
* If not provided, then all values from the start index through the end of
|
||||
* the list will be removed.
|
||||
* @param {...T} [value] - Values to insert into the list starting at `index`.
|
||||
* @returns {T[]} containing the value that were removed from the list. The
|
||||
* array is empty if no value were removed.
|
||||
*/
|
||||
splice(index, count, ...object) {}
|
||||
|
||||
/**
|
||||
* Add one or more objects to the _beginning_ of the list.
|
||||
* @param {...Realm.Object} object - Each object’s type must match
|
||||
* {@linkcode Realm~ObjectSchemaProperty objectType} specified in the schema.
|
||||
* @throws {TypeError} If an `object` is of the wrong type.
|
||||
* Add one or more values to the _beginning_ of the list.
|
||||
*
|
||||
* @param {...T} value - Values to add to the list.
|
||||
* @throws {TypeError} If a `value` is not of a type which can be stored in
|
||||
* the list, or if an object being added to the list does not match the
|
||||
* {@linkcode Realm~ObjectSchema object schema} for the list.
|
||||
* @throws {Error} If not inside a write transaction.
|
||||
* @returns {number} equal to the new {@link Realm.List#length length} of the list
|
||||
* after adding objects.
|
||||
* @returns {number} equal to the new {@link Realm.List#length length} of
|
||||
* the list after adding the values.
|
||||
*/
|
||||
unshift(...object) {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ class Realm {
|
|||
* Open a Realm asynchronously with a promise. If the Realm is synced, it will be fully
|
||||
* synchronized before it is available.
|
||||
* @param {Realm~Configuration} config
|
||||
* @returns {ProgressPromise} - a promise that will be resolved with the realm instance when it's available.
|
||||
* @returns {ProgressPromise} - a promise that will be resolved with the Realm instance when it's available.
|
||||
*/
|
||||
static open(config) {}
|
||||
|
||||
|
@ -103,7 +103,7 @@ class Realm {
|
|||
* Open a Realm asynchronously with a callback. If the Realm is synced, it will be fully
|
||||
* synchronized before it is available.
|
||||
* @param {Realm~Configuration} config
|
||||
* @param {callback(error, realm)} - will be called when the realm is ready.
|
||||
* @param {callback(error, realm)} - will be called when the Realm is ready.
|
||||
* @param {callback(transferred, transferable)} [progressCallback] - an optional callback for download progress notifications
|
||||
* @throws {Error} If anything in the provided `config` is invalid.
|
||||
*/
|
||||
|
@ -286,6 +286,7 @@ Realm.defaultPath;
|
|||
* @property {boolean} [readOnly=false] - Specifies if this Realm should be opened as read-only.
|
||||
* @property {Array<Realm~ObjectClass|Realm~ObjectSchema>} [schema] - Specifies all the
|
||||
* object types in this Realm. **Required** when first creating a Realm at this `path`.
|
||||
* If omitted, the schema will be read from the existing Realm file.
|
||||
* @property {number} [schemaVersion] - **Required** (and must be incremented) after
|
||||
* changing the `schema`.
|
||||
* @property {Object} [sync] - Sync configuration parameters with the following
|
||||
|
@ -350,19 +351,39 @@ Realm.defaultPath;
|
|||
* that must be unique across all objects of this type within the same Realm.
|
||||
* @property {Object<string, (Realm~PropertyType|Realm~ObjectSchemaProperty)>} properties -
|
||||
* An object where the keys are property names and the values represent the property type.
|
||||
*
|
||||
* @example
|
||||
* let MyClassSchema = {
|
||||
* name: 'MyClass',
|
||||
* primaryKey: 'pk',
|
||||
* properties: {
|
||||
* pk: 'int',
|
||||
* optionalFloatValue: 'float?' // or {type: 'float', optional: true}
|
||||
* listOfStrings: 'string[]',
|
||||
* listOfOptionalDates: 'date?[]',
|
||||
* indexedInt: {type: 'int', indexed: true}
|
||||
*
|
||||
* linkToObject: 'MyClass',
|
||||
* listOfObjects: 'MyClass[]', // or {type: 'list', objectType: 'MyClass'}
|
||||
* objectsLinkingToThisObject: {type: 'linkingObjects', objectType: 'MyClass', property: 'linkToObject'}
|
||||
* }
|
||||
* };
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Realm~ObjectSchemaProperty
|
||||
* @type {Object}
|
||||
* @property {Realm~PropertyType} type - The type of this property.
|
||||
* @property {string} [objectType] - **Required** when `type` is `"list"` or `"linkingObjects"`,
|
||||
* and must match the type of an object in the same schema.
|
||||
* @property {Realm~PropertyType} [objectType] - **Required** when `type` is `"list"` or `"linkingObjects"`,
|
||||
* and must match the type of an object in the same schema, or, for `"list"`
|
||||
* only, any other type which may be stored as a Realm property.
|
||||
* @property {string} [property] - **Required** when `type` is `"linkingObjects"`, and must match
|
||||
* the name of a property on the type specified in `objectType` that links to the type this property belongs to.
|
||||
* @property {any} [default] - The default value for this property on creation when not
|
||||
* otherwise specified.
|
||||
* @property {boolean} [optional] - Signals if this property may be assigned `null` or `undefined`.
|
||||
* For `"list"` properties of non-object types, this instead signals whether the values inside the list may be assigned `null` or `undefined`.
|
||||
* This is not supported for `"list"` properties of object types and `"linkingObjects"` properties.
|
||||
* @property {boolean} [indexed] - Signals if this property should be indexed. Only supported for
|
||||
* `"string"`, `"int"`, and `"bool"` properties.
|
||||
*/
|
||||
|
@ -376,10 +397,21 @@ Realm.defaultPath;
|
|||
*/
|
||||
|
||||
/**
|
||||
* A property type may be specified as one of the standard builtin types, or as an object type
|
||||
* inside the same schema.
|
||||
* A property type may be specified as one of the standard builtin types, or as
|
||||
* an object type inside the same schema.
|
||||
*
|
||||
* When specifying property types in an {@linkplain Realm~ObjectSchema object schema}, you
|
||||
* may append `?` to any of the property types to indicate that it is optional
|
||||
* (i.e. it can be `null` in addition to the normal values) and `[]` to
|
||||
* indicate that it is instead a list of that type. For example,
|
||||
* `optionalIntList: 'int?[]'` would declare a property which is a list of
|
||||
* nullable integers. The property types reported by {@linkplain Realm.Collection
|
||||
* collections} and in a Realm's schema will never
|
||||
* use these forms.
|
||||
*
|
||||
* @typedef Realm~PropertyType
|
||||
* @type {("bool"|"int"|"float"|"double"|"string"|"date"|"data"|"list"|"linkingObjects"|"<ObjectType>")}
|
||||
*
|
||||
* @property {boolean} "bool" - Property value may either be `true` or `false`.
|
||||
* @property {number} "int" - Property may be assigned any number, but will be stored as a
|
||||
* round integer, meaning anything after the decimal will be truncated.
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
* {@link Realm.Results#snapshot snapshot()}, however, will **not** live update
|
||||
* (and listener callbacks added through {@link Realm.Results#addListener addListener()}
|
||||
* will thus never be called).
|
||||
*
|
||||
* @extends Realm.Collection
|
||||
* @memberof Realm
|
||||
*/
|
||||
|
|
|
@ -54,7 +54,7 @@ export function fireMutationListeners(realmId) {
|
|||
}
|
||||
|
||||
function isIndex(propertyName) {
|
||||
return typeof propertyName === 'number' || (typeof propertyName === 'string' && /^\d+$/.test(propertyName));
|
||||
return typeof propertyName === 'number' || (typeof propertyName === 'string' && /^-?\d+$/.test(propertyName));
|
||||
}
|
||||
|
||||
const mutable = Symbol('mutable');
|
||||
|
@ -83,7 +83,10 @@ const traps = {
|
|||
return true;
|
||||
}
|
||||
|
||||
return Reflect.set(collection, property, value, collection);
|
||||
if (!Reflect.set(collection, property, value, collection)) {
|
||||
throw new TypeError(`Cannot assign to read only property '${property}'`)
|
||||
}
|
||||
return true;
|
||||
},
|
||||
ownKeys(collection) {
|
||||
return Reflect.ownKeys(collection).concat(Array.from({ length: collection.length }, (value, key) => String(key)));
|
||||
|
@ -117,8 +120,11 @@ export function createCollection(prototype, realmId, info, _mutable) {
|
|||
'length': {
|
||||
get: getterForProperty('length'),
|
||||
},
|
||||
'-1': {
|
||||
value: undefined,
|
||||
'type': {
|
||||
get: getterForProperty('type'),
|
||||
},
|
||||
'optional': {
|
||||
get: getterForProperty('optional'),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -72,6 +72,12 @@ export function createUser(args) {
|
|||
return deserialize(undefined, result);
|
||||
}
|
||||
|
||||
export function _adminUser(args) {
|
||||
args = args.map((arg) => serialize(null, arg));
|
||||
const result = sendRequest('_adminUser', {arguments: args});
|
||||
return deserialize(undefined, result);
|
||||
}
|
||||
|
||||
export function callMethod(realmId, id, name, args) {
|
||||
if (args) {
|
||||
args = args.map((arg) => serialize(realmId, arg));
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import { createUser as createUserRPC, getAllUsers as getAllUsersRPC } from './rpc';
|
||||
import { createUser as createUserRPC, _adminUser as _adminUserRPC, getAllUsers as getAllUsersRPC } from './rpc';
|
||||
import { keys, objectTypes } from './constants';
|
||||
import { createMethods } from './util';
|
||||
|
||||
|
@ -28,6 +28,10 @@ export default class User {
|
|||
return createUserRPC(Array.from(arguments));
|
||||
}
|
||||
|
||||
static _adminUser(adminToken, server) {
|
||||
return _adminUserRPC(Array.from(arguments));
|
||||
}
|
||||
|
||||
static get all() {
|
||||
return getAllUsersRPC();
|
||||
}
|
||||
|
|
|
@ -76,7 +76,10 @@ function refreshAccessToken(user, localRealmPath, realmUrl) {
|
|||
provider: 'realm',
|
||||
app_id: ''
|
||||
}),
|
||||
headers: postHeaders
|
||||
headers: postHeaders,
|
||||
// FIXME: This timeout appears to be necessary in order for some requests to be sent at all.
|
||||
// See https://github.com/realm/realm-js-private/issues/338 for details.
|
||||
timeout: 1000.0
|
||||
};
|
||||
performFetch(url, options)
|
||||
.then((response) => response.json().then((json) => { return { response, json }; }))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "realm",
|
||||
"description": "Realm is a mobile database: an alternative to SQLite and key-value stores",
|
||||
"version": "2.0.0-rc11",
|
||||
"version": "2.0.0-rc13",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://realm.io",
|
||||
"keywords": [
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/ruby
|
||||
|
||||
require 'json'
|
||||
|
||||
ios_sim_default_device_type = ENV["IOS_SIM_DEVICE_TYPE"] or "iPhone 5s"
|
||||
ios_sim_default_ios_version = ENV["IOS_SIM_OS"] or "iOS 10.1"
|
||||
mode = ARGV[0]
|
||||
|
||||
devices = JSON.parse(%x{xcrun simctl list devices --json})['devices']
|
||||
.each { |os, group| group.each{ |dev| dev['os'] = os } }
|
||||
.flat_map { |x| x[1] }
|
||||
if mode == "booted" then
|
||||
device = devices.select{|x| x['state'] == 'Booted'}
|
||||
else
|
||||
device = devices
|
||||
.select{ |x| x['availability'] == '(available)' }
|
||||
.each { |x| x['score'] = (x['name'] == '$ios_sim_default_device_type' ? 1 : 0) + (x['os'] == '$ios_sim_default_ios_version' ? 1 : 0) }
|
||||
.sort_by! { |x| [x['score'], x['name']] }
|
||||
.reverse!
|
||||
end
|
||||
|
||||
if device and device[0] then
|
||||
puts device[0]['udid']
|
||||
end
|
|
@ -14,8 +14,6 @@ if echo "$CONFIGURATION" | grep -i "^Debug$" > /dev/null ; then
|
|||
fi
|
||||
|
||||
IOS_SIM_DEVICE=${IOS_SIM_DEVICE:-} # use preferentially, otherwise will be set and re-exported
|
||||
ios_sim_default_device_type=${IOS_SIM_DEVICE_TYPE:-iPhone 5s}
|
||||
ios_sim_default_ios_version=${IOS_SIM_OS:-iOS 10.1}
|
||||
|
||||
PATH="/opt/android-sdk-linux/platform-tools:$PATH"
|
||||
SRCROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
|
@ -165,42 +163,43 @@ setup_ios_simulator() {
|
|||
# -- Ensure that the simulator is ready
|
||||
|
||||
if [ $CI_RUN == true ]; then
|
||||
# - Kill the Simulator to ensure we are running the correct one, only when running in CI
|
||||
echo "Resetting simulator using toolchain from: $DEVELOPER_DIR"
|
||||
# - Kill the Simulator to ensure we are running the correct one, only when running in CI
|
||||
echo "Resetting simulator using toolchain from: $DEVELOPER_DIR"
|
||||
|
||||
# Quit Simulator.app to give it a chance to go down gracefully
|
||||
local deadline=$((SECONDS+5))
|
||||
while pgrep -qx Simulator && [ $SECONDS -lt $deadline ]; do
|
||||
osascript -e 'tell app "Simulator" to quit without saving' || true
|
||||
sleep 0.25 # otherwise the pkill following will get it too early
|
||||
done
|
||||
# Quit Simulator.app to give it a chance to go down gracefully
|
||||
local deadline=$((SECONDS+5))
|
||||
while pgrep -qx Simulator && [ $SECONDS -lt $deadline ]; do
|
||||
osascript -e 'tell app "Simulator" to quit without saving' || true
|
||||
sleep 0.25 # otherwise the pkill following will get it too early
|
||||
done
|
||||
|
||||
# stop CoreSimulatorService
|
||||
launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
|
||||
sleep 0.25 # launchtl can take a small moment to kill services
|
||||
# stop CoreSimulatorService
|
||||
launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
|
||||
sleep 0.25 # launchtl can take a small moment to kill services
|
||||
|
||||
# kill them with fire
|
||||
while pgrep -qx Simulator com.apple.CoreSimulator.CoreSimulatorService; do
|
||||
pkill -9 -x Simulator com.apple.CoreSimulator.CoreSimulatorService || true
|
||||
sleep 0.05
|
||||
done
|
||||
# kill them with fire
|
||||
while pgrep -qx Simulator com.apple.CoreSimulator.CoreSimulatorService; do
|
||||
pkill -9 -x Simulator com.apple.CoreSimulator.CoreSimulatorService || true
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
# - Prod `simctl` a few times as sometimes it fails the first couple of times after switching XCode vesions
|
||||
local deadline=$((SECONDS+5))
|
||||
while [ -z "$(xcrun simctl list devices 2>/dev/null)" ] && [ $SECONDS -lt $deadline ]; do
|
||||
: # nothing to see here, will stop cycling on the first successful run
|
||||
done
|
||||
# - Prod `simctl` a few times as sometimes it fails the first couple of times after switching XCode vesions
|
||||
local deadline=$((SECONDS+5))
|
||||
while [ -z "$(xcrun simctl list devices 2>/dev/null)" ] && [ $SECONDS -lt $deadline ]; do
|
||||
: # nothing to see here, will stop cycling on the first successful run
|
||||
done
|
||||
|
||||
# - Choose a device, if it has not already been chosen
|
||||
local deadline=$((SECONDS+5))
|
||||
while [ -z "$IOS_SIM_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
|
||||
IOS_DEVICE=$(ruby -rjson -e "puts JSON.parse(%x{xcrun simctl list devices --json})['devices'].each{|os,group| group.each{|dev| dev['os'] = os}}.flat_map{|x| x[1]}.select{|x| x['availability'] == '(available)'}.each{|x| x['score'] = (x['name'] == '$ios_sim_default_device_type' ? 1 : 0) + (x['os'] == '$ios_sim_default_ios_version' ? 1 : 0)}.sort_by!{|x| [x['score'], x['name']]}.reverse![0]['udid']")
|
||||
export IOS_SIM_DEVICE=$IOS_DEVICE
|
||||
IOS_DEVICE=""
|
||||
while [ -z "$IOS_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
|
||||
IOS_DEVICE="$(ruby $SRCROOT/scripts/find-ios-device.rb best)"
|
||||
done
|
||||
if [ -z "$IOS_SIM_DEVICE" ]; then
|
||||
if [ -z "$IOS_DEVICE" ]; then
|
||||
echo "*** Failed to determine the iOS Simulator device to use ***"
|
||||
exit 1
|
||||
fi
|
||||
export IOS_SIM_DEVICE=$IOS_DEVICE
|
||||
|
||||
# - Reset the device we will be using if running in CI
|
||||
xcrun simctl shutdown "$IOS_SIM_DEVICE" 1>/dev/null 2>/dev/null || true # sometimes simctl gets confused
|
||||
|
@ -214,19 +213,20 @@ setup_ios_simulator() {
|
|||
startedSimulator=true
|
||||
|
||||
else
|
||||
# - ensure that the simulator is running on a developer's workstation
|
||||
# - ensure that the simulator is running on a developer's workstation
|
||||
open "$DEVELOPER_DIR/Applications/Simulator.app"
|
||||
|
||||
# - Select the first device booted in the simulator, since it will boot something for us
|
||||
local deadline=$((SECONDS+10))
|
||||
while [ -z "$IOS_SIM_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
|
||||
IOS_DEVICE=$(ruby -rjson -e "puts JSON.parse(%x{xcrun simctl list devices --json})['devices'].each{|os,group| group.each{|dev| dev['os'] = os}}.flat_map{|x| x[1]}.select{|x| x['state'] == 'Booted'}[0]['udid']")
|
||||
export IOS_SIM_DEVICE=$IOS_DEVICE
|
||||
IOS_DEVICE=""
|
||||
while [ -z "$IOS_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
|
||||
IOS_DEVICE="$(ruby $SRCROOT/scripts/find-ios-device.rb booted)"
|
||||
done
|
||||
if [ -z "$IOS_SIM_DEVICE" ]; then
|
||||
if [ -z "$IOS_DEVICE" ]; then
|
||||
echo "*** Failed to determine the iOS Simulator device in use ***"
|
||||
exit 1
|
||||
fi
|
||||
export IOS_SIM_DEVICE=$IOS_DEVICE
|
||||
fi
|
||||
|
||||
# Wait until the boot completes
|
||||
|
|
|
@ -54,7 +54,7 @@ struct Arguments {
|
|||
};
|
||||
|
||||
template<typename T>
|
||||
using ArgumentsMethodType = void(typename T::Context, typename T::Function, typename T::Object, Arguments<T>, ReturnValue<T> &);
|
||||
using ArgumentsMethodType = void(typename T::Context, typename T::Object, Arguments<T>, ReturnValue<T> &);
|
||||
|
||||
template<typename T>
|
||||
struct PropertyType {
|
||||
|
|
224
src/js_list.hpp
224
src/js_list.hpp
|
@ -54,30 +54,33 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
|
|||
using Object = js::Object<T>;
|
||||
using Value = js::Value<T>;
|
||||
using ReturnValue = js::ReturnValue<T>;
|
||||
using Arguments = js::Arguments<T>;
|
||||
|
||||
static ObjectType create_instance(ContextType, realm::List);
|
||||
|
||||
// properties
|
||||
static void get_length(ContextType, ObjectType, ReturnValue &);
|
||||
static void get_type(ContextType, ObjectType, ReturnValue &);
|
||||
static void get_optional(ContextType, ObjectType, ReturnValue &);
|
||||
static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &);
|
||||
static bool set_index(ContextType, ObjectType, uint32_t, ValueType);
|
||||
|
||||
// methods
|
||||
static void push(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void pop(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void unshift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void shift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void splice(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void push(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void pop(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void unshift(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void shift(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void splice(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void snapshot(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void filtered(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void sorted(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void index_of(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
// observable
|
||||
static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
std::string const name = "List";
|
||||
|
||||
|
@ -99,9 +102,14 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
|
|||
|
||||
PropertyMap<T> const properties = {
|
||||
{"length", {wrap<get_length>, nullptr}},
|
||||
{"type", {wrap<get_type>, nullptr}},
|
||||
{"optional", {wrap<get_optional>, nullptr}},
|
||||
};
|
||||
|
||||
IndexPropertyType<T> const index_accessor = {wrap<get_index>, wrap<set_index>};
|
||||
|
||||
private:
|
||||
static void validate_value(ContextType, realm::List&, ValueType);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -115,70 +123,83 @@ void ListClass<T>::get_length(ContextType, ObjectType object, ReturnValue &retur
|
|||
return_value.set((uint32_t)list->size());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(object);
|
||||
return_value.set(string_for_property_type(list->get_type() & ~realm::PropertyType::Flags));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::get_optional(ContextType, ObjectType object, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(object);
|
||||
return_value.set(is_nullable(list->get_type()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(object);
|
||||
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
|
||||
|
||||
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
|
||||
NativeAccessor<T> accessor(ctx, *list);
|
||||
return_value.set(list->get(accessor, index));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool ListClass<T>::set_index(ContextType ctx, ObjectType object, uint32_t index, ValueType value) {
|
||||
auto list = get_internal<T, ListClass<T>>(object);
|
||||
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
|
||||
validate_value(ctx, *list, value);
|
||||
NativeAccessor<T> accessor(ctx, *list);
|
||||
list->set(accessor, index, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::push(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count_at_least(argc, 1);
|
||||
|
||||
void ListClass<T>::push(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
list->add(accessor, arguments[i]);
|
||||
for (size_t i = 0; i < args.count; i++) {
|
||||
validate_value(ctx, *list, args[i]);
|
||||
}
|
||||
|
||||
NativeAccessor<T> accessor(ctx, *list);
|
||||
for (size_t i = 0; i < args.count; i++) {
|
||||
list->add(accessor, args[i]);
|
||||
}
|
||||
|
||||
return_value.set((uint32_t)list->size());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::pop(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 0);
|
||||
void ListClass<T>::pop(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
size_t size = list->size();
|
||||
auto size = static_cast<unsigned int>(list->size());
|
||||
if (size == 0) {
|
||||
list->verify_in_transaction();
|
||||
return_value.set_undefined();
|
||||
}
|
||||
else {
|
||||
size_t index = size - 1;
|
||||
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
|
||||
|
||||
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
|
||||
list->remove(index);
|
||||
get_index(ctx, this_object, size - 1, return_value);
|
||||
list->remove(size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::unshift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count_at_least(argc, 1);
|
||||
|
||||
void ListClass<T>::unshift(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
list->insert(accessor, i, arguments[i]);
|
||||
for (size_t i = 0; i < args.count; i++) {
|
||||
validate_value(ctx, *list, args[i]);
|
||||
}
|
||||
|
||||
NativeAccessor<T> accessor(ctx, *list);
|
||||
for (size_t i = 0; i < args.count; i++) {
|
||||
list->insert(accessor, i, args[i]);
|
||||
}
|
||||
|
||||
return_value.set((uint32_t)list->size());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::shift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 0);
|
||||
void ListClass<T>::shift(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
if (list->size() == 0) {
|
||||
|
@ -186,151 +207,108 @@ void ListClass<T>::shift(ContextType ctx, FunctionType, ObjectType this_object,
|
|||
return_value.set_undefined();
|
||||
}
|
||||
else {
|
||||
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(0));
|
||||
|
||||
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
|
||||
get_index(ctx, this_object, 0, return_value);
|
||||
list->remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::splice(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count_at_least(argc, 1);
|
||||
|
||||
void ListClass<T>::splice(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
size_t size = list->size();
|
||||
long index = std::min<long>(Value::to_number(ctx, arguments[0]), size);
|
||||
long index = std::min<long>(Value::to_number(ctx, args[0]), size);
|
||||
if (index < 0) {
|
||||
index = std::max<long>(size + index, 0);
|
||||
}
|
||||
|
||||
size_t remove;
|
||||
if (argc < 2) {
|
||||
if (args.count < 2) {
|
||||
remove = size - index;
|
||||
}
|
||||
else {
|
||||
remove = std::max<long>(Value::to_number(ctx, arguments[1]), 0);
|
||||
remove = std::max<long>(Value::to_number(ctx, args[1]), 0);
|
||||
remove = std::min<long>(remove, size - index);
|
||||
}
|
||||
|
||||
std::vector<ValueType> removed_objects;
|
||||
removed_objects.reserve(remove);
|
||||
|
||||
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
|
||||
NativeAccessor<T> accessor(ctx, *list);
|
||||
for (size_t i = 0; i < remove; i++) {
|
||||
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
|
||||
|
||||
removed_objects.push_back(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
|
||||
removed_objects.push_back(list->get(accessor, index));
|
||||
list->remove(index);
|
||||
}
|
||||
for (size_t i = 2; i < argc; i++) {
|
||||
list->insert(accessor, index + i - 2, arguments[i]);
|
||||
for (size_t i = 2; i < args.count; i++) {
|
||||
list->insert(accessor, index + i - 2, args[i]);
|
||||
}
|
||||
|
||||
return_value.set(Object::create_array(ctx, removed_objects));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 0);
|
||||
|
||||
void ListClass<T>::snapshot(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, list->snapshot()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count_at_least(argc, 1);
|
||||
|
||||
void ListClass<T>::filtered(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
return_value.set(ResultsClass<T>::create_filtered(ctx, *list, argc, arguments));
|
||||
return_value.set(ResultsClass<T>::create_filtered(ctx, *list, args));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
void ListClass<T>::sorted(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, list->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, list->sort(ResultsClass<T>::get_keypaths(ctx, args))));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
void ListClass<T>::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 1);
|
||||
|
||||
ObjectType arg = Value::validated_to_object(ctx, arguments[0]);
|
||||
if (Object::template is_instance<RealmObjectClass<T>>(ctx, arg)) {
|
||||
auto object = get_internal<T, RealmObjectClass<T>>(arg);
|
||||
if (!object->is_valid()) {
|
||||
throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.");
|
||||
}
|
||||
|
||||
void ListClass<T>::index_of(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto fn = [&](auto&& row) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
size_t ndx = list->find(object->row());
|
||||
|
||||
if (ndx == realm::not_found) {
|
||||
return_value.set(-1);
|
||||
}
|
||||
else {
|
||||
return_value.set((uint32_t)ndx);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return_value.set(-1);
|
||||
}
|
||||
NativeAccessor<T> accessor(ctx, *list);
|
||||
return list->find(accessor, row);
|
||||
};
|
||||
ResultsClass<T>::index_of(ctx, fn, args, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 1);
|
||||
|
||||
void ListClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
Protected<FunctionType> protected_callback(ctx, callback);
|
||||
Protected<ObjectType> protected_this(ctx, this_object);
|
||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||
|
||||
auto token = list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
|
||||
HANDLESCOPE
|
||||
|
||||
ValueType arguments[2];
|
||||
arguments[0] = static_cast<ObjectType>(protected_this);
|
||||
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
|
||||
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
|
||||
});
|
||||
list->m_notification_tokens.emplace_back(protected_callback, std::move(token));
|
||||
ResultsClass<T>::add_listener(ctx, *list, this_object, args);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 1);
|
||||
|
||||
void ListClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
auto protected_function = Protected<FunctionType>(ctx, callback);
|
||||
|
||||
auto iter = list->m_notification_tokens.begin();
|
||||
typename Protected<FunctionType>::Comparator compare;
|
||||
while (iter != list->m_notification_tokens.end()) {
|
||||
if(compare(iter->first, protected_function)) {
|
||||
iter = list->m_notification_tokens.erase(iter);
|
||||
}
|
||||
else {
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
ResultsClass<T>::remove_listener(ctx, *list, this_object, args);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 0);
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
list->m_notification_tokens.clear();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::validate_value(ContextType ctx, realm::List& list, ValueType value) {
|
||||
auto type = list.get_type();
|
||||
StringData object_type;
|
||||
if (type == realm::PropertyType::Object) {
|
||||
object_type = list.get_object_schema().name;
|
||||
}
|
||||
if (!Value::is_valid_for_property_type(ctx, value, type, object_type)) {
|
||||
throw TypeErrorException("Property", object_type ? object_type : string_for_property_type(type), Value::to_string(ctx, value));
|
||||
}
|
||||
}
|
||||
|
||||
} // js
|
||||
} // realm
|
||||
|
|
|
@ -47,12 +47,19 @@ public:
|
|||
using OptionalValue = util::Optional<ValueType>;
|
||||
|
||||
NativeAccessor(ContextType ctx, std::shared_ptr<Realm> realm, const ObjectSchema& object_schema)
|
||||
: m_ctx(ctx), m_realm(std::move(realm)), m_object_schema(object_schema) { }
|
||||
: m_ctx(ctx), m_realm(std::move(realm)), m_object_schema(&object_schema) { }
|
||||
|
||||
template<typename Collection>
|
||||
NativeAccessor(ContextType ctx, Collection const& collection)
|
||||
: m_ctx(ctx)
|
||||
, m_realm(collection.get_realm())
|
||||
, m_object_schema(collection.get_type() == realm::PropertyType::Object ? &collection.get_object_schema() : nullptr)
|
||||
{ }
|
||||
|
||||
NativeAccessor(NativeAccessor& parent, const Property& prop)
|
||||
: m_ctx(parent.m_ctx)
|
||||
, m_realm(parent.m_realm)
|
||||
, m_object_schema(*m_realm->schema().find(prop.object_type))
|
||||
, m_object_schema(&*m_realm->schema().find(prop.object_type))
|
||||
{ }
|
||||
|
||||
OptionalValue value_for_property(ValueType dict, std::string const& prop_name, size_t prop_index) {
|
||||
|
@ -61,11 +68,9 @@ public:
|
|||
return util::none;
|
||||
}
|
||||
ValueType value = Object::get_property(m_ctx, object, prop_name);
|
||||
const auto& prop = m_object_schema.persisted_properties[prop_index];
|
||||
const auto& prop = m_object_schema->persisted_properties[prop_index];
|
||||
if (!Value::is_valid_for_property(m_ctx, value, prop)) {
|
||||
throw TypeErrorException(m_object_schema.name, prop.name,
|
||||
js_type_name_for_property_type(prop.type),
|
||||
print(value));
|
||||
throw TypeErrorException(*this, m_object_schema->name, prop, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -79,6 +84,14 @@ public:
|
|||
template<typename T>
|
||||
T unbox(ValueType value, bool create = false, bool update = false);
|
||||
|
||||
template<typename T>
|
||||
util::Optional<T> unbox_optional(ValueType value) {
|
||||
return is_null(value) ? util::none : util::make_optional(unbox<T>(value));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ValueType box(util::Optional<T> v) { return v ? box(*v) : null_value(); }
|
||||
|
||||
ValueType box(bool boolean) { return Value::from_boolean(m_ctx, boolean); }
|
||||
ValueType box(int64_t number) { return Value::from_number(m_ctx, number); }
|
||||
ValueType box(float number) { return Value::from_number(m_ctx, number); }
|
||||
|
@ -88,11 +101,20 @@ public:
|
|||
ValueType box(Mixed) { throw std::runtime_error("'Any' type is unsupported"); }
|
||||
|
||||
ValueType box(Timestamp ts) {
|
||||
if (ts.is_null()) {
|
||||
return null_value();
|
||||
}
|
||||
return Object::create_date(m_ctx, ts.get_seconds() * 1000 + ts.get_nanoseconds() / 1000000);
|
||||
}
|
||||
ValueType box(realm::Object realm_object) {
|
||||
return RealmObjectClass<JSEngine>::create_instance(m_ctx, std::move(realm_object));
|
||||
}
|
||||
ValueType box(RowExpr row) {
|
||||
if (!row.is_attached()) {
|
||||
return Value::from_null(m_ctx);
|
||||
}
|
||||
return RealmObjectClass<JSEngine>::create_instance(m_ctx, realm::Object(m_realm, *m_object_schema, row));
|
||||
}
|
||||
ValueType box(realm::List list) {
|
||||
return ListClass<JSEngine>::create_instance(m_ctx, std::move(list));
|
||||
}
|
||||
|
@ -112,7 +134,7 @@ public:
|
|||
auto obj = Value::validated_to_object(m_ctx, value);
|
||||
uint32_t size = Object::validated_get_length(m_ctx, obj);
|
||||
for (uint32_t i = 0; i < size; ++i) {
|
||||
func(Object::validated_get_object(m_ctx, obj, i));
|
||||
func(Object::get_property(m_ctx, obj, i));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,12 +150,14 @@ public:
|
|||
void will_change(realm::Object&, realm::Property const&) { }
|
||||
void did_change() { }
|
||||
|
||||
std::string print(ValueType const& v) { return Value::to_string(m_ctx, v); }
|
||||
std::string print(ValueType const&);
|
||||
void print(std::string&, ValueType const&);
|
||||
const char *typeof(ValueType const& v) { return Value::typeof(m_ctx, v); }
|
||||
|
||||
private:
|
||||
ContextType m_ctx;
|
||||
std::shared_ptr<Realm> m_realm;
|
||||
const ObjectSchema& m_object_schema;
|
||||
const ObjectSchema* m_object_schema;
|
||||
std::string m_string_buffer;
|
||||
OwnedBinaryData m_owned_binary_data;
|
||||
|
||||
|
@ -173,34 +197,37 @@ struct Unbox<JSEngine, double> {
|
|||
template<typename JSEngine>
|
||||
struct Unbox<JSEngine, util::Optional<bool>> {
|
||||
static util::Optional<bool> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
|
||||
return js::Value<JSEngine>::validated_to_boolean(ctx->m_ctx, value, "Property");
|
||||
return ctx->template unbox_optional<bool>(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename JSEngine>
|
||||
struct Unbox<JSEngine, util::Optional<int64_t>> {
|
||||
static util::Optional<int64_t> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
|
||||
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
|
||||
return ctx->template unbox_optional<int64_t>(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename JSEngine>
|
||||
struct Unbox<JSEngine, util::Optional<float>> {
|
||||
static util::Optional<float> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
|
||||
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
|
||||
return ctx->template unbox_optional<float>(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename JSEngine>
|
||||
struct Unbox<JSEngine, util::Optional<double>> {
|
||||
static util::Optional<double> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
|
||||
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
|
||||
return ctx->template unbox_optional<double>(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename JSEngine>
|
||||
struct Unbox<JSEngine, StringData> {
|
||||
static StringData call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
|
||||
if (ctx->is_null(value)) {
|
||||
return StringData();
|
||||
}
|
||||
ctx->m_string_buffer = js::Value<JSEngine>::validated_to_string(ctx->m_ctx, value, "Property");
|
||||
return ctx->m_string_buffer;
|
||||
}
|
||||
|
@ -209,6 +236,9 @@ struct Unbox<JSEngine, StringData> {
|
|||
template<typename JSEngine>
|
||||
struct Unbox<JSEngine, BinaryData> {
|
||||
static BinaryData call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value value, bool, bool) {
|
||||
if (ctx->is_null(value)) {
|
||||
return BinaryData();
|
||||
}
|
||||
ctx->m_owned_binary_data = js::Value<JSEngine>::validated_to_binary(ctx->m_ctx, value, "Property");
|
||||
return ctx->m_owned_binary_data.get();
|
||||
}
|
||||
|
@ -224,6 +254,9 @@ struct Unbox<JSEngine, Mixed> {
|
|||
template<typename JSEngine>
|
||||
struct Unbox<JSEngine, Timestamp> {
|
||||
static Timestamp call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
|
||||
if (ctx->is_null(value)) {
|
||||
return Timestamp();
|
||||
}
|
||||
auto date = js::Value<JSEngine>::validated_to_date(ctx->m_ctx, value, "Property");
|
||||
double milliseconds = js::Value<JSEngine>::to_number(ctx->m_ctx, date);
|
||||
int64_t seconds = milliseconds / 1000;
|
||||
|
@ -248,15 +281,15 @@ struct Unbox<JSEngine, RowExpr> {
|
|||
throw std::runtime_error("Realm object is from another Realm");
|
||||
}
|
||||
}
|
||||
else if (!create) {
|
||||
throw std::runtime_error("object is not a Realm Object");
|
||||
if (!create) {
|
||||
throw NonRealmObjectException();
|
||||
}
|
||||
|
||||
if (Value::is_array(ctx->m_ctx, object)) {
|
||||
object = Schema<JSEngine>::dict_for_property_array(ctx->m_ctx, ctx->m_object_schema, object);
|
||||
object = Schema<JSEngine>::dict_for_property_array(ctx->m_ctx, *ctx->m_object_schema, object);
|
||||
}
|
||||
|
||||
auto child = realm::Object::create<ValueType>(*ctx, ctx->m_realm, ctx->m_object_schema,
|
||||
auto child = realm::Object::create<ValueType>(*ctx, ctx->m_realm, *ctx->m_object_schema,
|
||||
static_cast<ValueType>(object), try_update);
|
||||
return child.row();
|
||||
}
|
||||
|
@ -269,6 +302,62 @@ U NativeAccessor<T>::unbox(ValueType value, bool create, bool update) {
|
|||
return _impl::Unbox<T, U>::call(this, std::move(value), create, update);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string NativeAccessor<T>::print(ValueType const& value) {
|
||||
std::string ret;
|
||||
print(ret, value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void NativeAccessor<T>::print(std::string& str, ValueType const& value) {
|
||||
if (Value::is_null(m_ctx, value)) {
|
||||
str += "null";
|
||||
}
|
||||
else if (Value::is_undefined(m_ctx, value)) {
|
||||
str += "undefined";
|
||||
}
|
||||
else if (Value::is_array(m_ctx, value)) {
|
||||
auto array = Value::to_array(m_ctx, value);
|
||||
auto length = Object::validated_get_length(m_ctx, array);
|
||||
|
||||
str += "[";
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
print(str, Object::get_property(m_ctx, array, i));
|
||||
if (i + 1 < length) {
|
||||
str += ", ";
|
||||
}
|
||||
}
|
||||
str += "]";
|
||||
}
|
||||
else if (Value::is_object(m_ctx, value)) {
|
||||
auto object = Value::to_object(m_ctx, value);
|
||||
if (Object::template is_instance<RealmObjectClass<T>>(m_ctx, object)) {
|
||||
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
|
||||
auto& object_schema = realm_object->get_object_schema();
|
||||
str += object_schema.name;
|
||||
str += "{";
|
||||
for (size_t i = 0, count = object_schema.persisted_properties.size(); i < count; ++i) {
|
||||
print(str, realm_object->template get_property_value<ValueType>(*this, object_schema.persisted_properties[i].name));
|
||||
if (i + 1 < count) {
|
||||
str += ", ";
|
||||
}
|
||||
}
|
||||
str += "}";
|
||||
}
|
||||
else {
|
||||
str += Value::to_string(m_ctx, value);
|
||||
}
|
||||
}
|
||||
else if (Value::is_string(m_ctx, value)) {
|
||||
str += "'";
|
||||
str += Value::to_string(m_ctx, value);
|
||||
str += "'";
|
||||
}
|
||||
else {
|
||||
str += Value::to_string(m_ctx, value);
|
||||
}
|
||||
}
|
||||
|
||||
} // js
|
||||
} // realm
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "platform.hpp"
|
||||
#include "realm_coordinator.hpp"
|
||||
#include "js_types.hpp"
|
||||
|
||||
#if REALM_ENABLE_SYNC
|
||||
#include "sync/sync_manager.hpp"
|
||||
|
@ -61,6 +62,49 @@ void clear_test_state() {
|
|||
SyncManager::shared().configure_file_system(default_realm_file_directory(), SyncManager::MetadataMode::NoEncryption);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string TypeErrorException::type_string(Property const& prop)
|
||||
{
|
||||
using realm::PropertyType;
|
||||
std::string ret;
|
||||
|
||||
switch (prop.type & ~PropertyType::Flags) {
|
||||
case PropertyType::Int:
|
||||
case PropertyType::Float:
|
||||
case PropertyType::Double:
|
||||
ret = "number";
|
||||
break;
|
||||
case PropertyType::Bool:
|
||||
ret = "boolean";
|
||||
break;
|
||||
case PropertyType::String:
|
||||
ret = "string";
|
||||
break;
|
||||
case PropertyType::Date:
|
||||
ret = "date";
|
||||
break;
|
||||
case PropertyType::Data:
|
||||
ret = "binary";
|
||||
break;
|
||||
case PropertyType::LinkingObjects:
|
||||
case PropertyType::Object:
|
||||
ret = prop.object_type;
|
||||
break;
|
||||
case PropertyType::Any:
|
||||
throw std::runtime_error("'Any' type is not supported");
|
||||
default:
|
||||
REALM_UNREACHABLE();
|
||||
}
|
||||
|
||||
if (realm::is_nullable(prop.type)) {
|
||||
ret += "?";
|
||||
}
|
||||
if (realm::is_array(prop.type)) {
|
||||
ret += "[]";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
} // js
|
||||
} // realm
|
||||
|
|
|
@ -164,22 +164,22 @@ public:
|
|||
static FunctionType create_constructor(ContextType);
|
||||
|
||||
// methods
|
||||
static void objects(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void object_for_primary_key(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void create(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_one(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_all(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void write(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void begin_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
|
||||
static void commit_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
|
||||
static void cancel_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
|
||||
static void add_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void wait_for_download_completion(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_all_listeners(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void close(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void compact(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_model(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void objects(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void object_for_primary_key(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void create(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_one(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_all(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void write(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void begin_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
||||
static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
||||
static void cancel_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
||||
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void wait_for_download_completion(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void compact(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
// properties
|
||||
static void get_empty(ContextType, ObjectType, ReturnValue &);
|
||||
|
@ -197,10 +197,10 @@ public:
|
|||
static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
|
||||
static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&);
|
||||
|
||||
static void schema_version(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void clear_test_state(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_file(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
|
||||
static void schema_version(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void copy_bundled_realm_files(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void delete_file(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
// static properties
|
||||
static void get_default_path(ContextType, ObjectType, ReturnValue &);
|
||||
|
@ -503,7 +503,7 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::schema_version(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(2);
|
||||
|
||||
realm::Realm::Config config;
|
||||
|
@ -524,19 +524,19 @@ void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType thi
|
|||
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::clear_test_state(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
js::clear_test_state();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
realm::copy_bundled_realm_files();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::delete_file(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
|
||||
ValueType value = args[0];
|
||||
|
@ -567,7 +567,7 @@ void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_o
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::delete_model(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::delete_model(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
ValueType value = args[0];
|
||||
|
||||
|
@ -641,7 +641,7 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
|
|||
#endif
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(3);
|
||||
auto config_object = Value::validated_to_object(ctx, args[0]);
|
||||
auto callback_function = Value::validated_to_function(ctx, args[1 + (args.count == 3)]);
|
||||
|
@ -743,7 +743,8 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
|
|||
}
|
||||
|
||||
ObjectType object = Object::create_empty(protected_ctx);
|
||||
Object::set_property(protected_ctx, object, "message", Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error"));
|
||||
Object::set_property(protected_ctx, object, "message",
|
||||
Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error"));
|
||||
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1));
|
||||
|
||||
ValueType callback_arguments[1];
|
||||
|
@ -760,7 +761,7 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -771,7 +772,7 @@ void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_objec
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::object_for_primary_key(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(2);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -789,7 +790,7 @@ void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, Object
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::create(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(3);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -813,7 +814,7 @@ void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::delete_one(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -861,7 +862,7 @@ void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_ob
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::delete_all(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -877,7 +878,7 @@ void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_ob
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::write(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -897,7 +898,7 @@ void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object,
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::begin_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -905,7 +906,7 @@ void RealmClass<T>::begin_transaction(ContextType ctx, FunctionType, ObjectType
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::commit_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -913,7 +914,7 @@ void RealmClass<T>::commit_transaction(ContextType ctx, FunctionType, ObjectType
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::cancel_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::cancel_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -921,7 +922,7 @@ void RealmClass<T>::cancel_transaction(ContextType ctx, FunctionType, ObjectType
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(2);
|
||||
|
||||
validated_notification_name(ctx, args[0]);
|
||||
|
@ -933,7 +934,7 @@ void RealmClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(2);
|
||||
|
||||
validated_notification_name(ctx, args[0]);
|
||||
|
@ -945,7 +946,7 @@ void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType th
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
if (args.count) {
|
||||
validated_notification_name(ctx, args[0]);
|
||||
|
@ -957,7 +958,7 @@ void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectTy
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::close(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
@ -965,7 +966,7 @@ void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object,
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::compact(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
void RealmClass<T>::compact(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
|
|
|
@ -106,14 +106,12 @@ typename T::Object RealmObjectClass<T>::create_instance(ContextType ctx, realm::
|
|||
|
||||
template<typename T>
|
||||
void RealmObjectClass<T>::get_property(ContextType ctx, ObjectType object, const String &property, ReturnValue &return_value) {
|
||||
try {
|
||||
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
|
||||
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
|
||||
std::string name = property;
|
||||
if (realm_object->get_object_schema().property_for_name(name)) {
|
||||
NativeAccessor<T> accessor(ctx, realm_object->realm(), realm_object->get_object_schema());
|
||||
std::string name = property;
|
||||
auto result = realm_object->template get_property_value<ValueType>(accessor, name);
|
||||
return_value.set(result);
|
||||
} catch (InvalidPropertyException &ex) {
|
||||
// getters for nonexistent properties in JS should always return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,9 +127,7 @@ bool RealmObjectClass<T>::set_property(ContextType ctx, ObjectType object, const
|
|||
|
||||
NativeAccessor<T> accessor(ctx, realm_object->realm(), realm_object->get_object_schema());
|
||||
if (!Value::is_valid_for_property(ctx, value, *prop)) {
|
||||
throw TypeErrorException(realm_object->get_object_schema().name, property_name,
|
||||
js_type_name_for_property_type(prop->type),
|
||||
accessor.print(value));
|
||||
throw TypeErrorException(accessor, realm_object->get_object_schema().name, *prop, value);
|
||||
}
|
||||
|
||||
realm_object->set_property_value(accessor, property_name, value, true);
|
||||
|
@ -141,12 +137,15 @@ bool RealmObjectClass<T>::set_property(ContextType ctx, ObjectType object, const
|
|||
template<typename T>
|
||||
std::vector<String<T>> RealmObjectClass<T>::get_property_names(ContextType ctx, ObjectType object) {
|
||||
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
|
||||
auto &properties = realm_object->get_object_schema().persisted_properties;
|
||||
auto &object_schema = realm_object->get_object_schema();
|
||||
|
||||
std::vector<String> names;
|
||||
names.reserve(properties.size());
|
||||
names.reserve(object_schema.persisted_properties.size() + object_schema.computed_properties.size());
|
||||
|
||||
for (auto &prop : properties) {
|
||||
for (auto &prop : object_schema.persisted_properties) {
|
||||
names.push_back(prop.name);
|
||||
}
|
||||
for (auto &prop : object_schema.computed_properties) {
|
||||
names.push_back(prop.name);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@ namespace js {
|
|||
template<typename>
|
||||
class NativeAccessor;
|
||||
|
||||
struct NonRealmObjectException : public std::logic_error {
|
||||
NonRealmObjectException() : std::logic_error("Object is not a Realm object") { }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class Results : public realm::Results {
|
||||
public:
|
||||
|
@ -56,29 +60,40 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
|
|||
using Object = js::Object<T>;
|
||||
using Value = js::Value<T>;
|
||||
using ReturnValue = js::ReturnValue<T>;
|
||||
using Arguments = js::Arguments<T>;
|
||||
|
||||
static ObjectType create_instance(ContextType, realm::Results);
|
||||
static ObjectType create_instance(ContextType, SharedRealm, const std::string &object_type);
|
||||
|
||||
template<typename U>
|
||||
static ObjectType create_filtered(ContextType, const U &, size_t, const ValueType[]);
|
||||
static ObjectType create_filtered(ContextType, const U &, Arguments);
|
||||
|
||||
static std::vector<std::pair<std::string, bool>> get_keypaths(ContextType, size_t, const ValueType[]);
|
||||
static std::vector<std::pair<std::string, bool>> get_keypaths(ContextType, Arguments);
|
||||
|
||||
static void get_length(ContextType, ObjectType, ReturnValue &);
|
||||
static void get_type(ContextType, ObjectType, ReturnValue &);
|
||||
static void get_optional(ContextType, ObjectType, ReturnValue &);
|
||||
static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &);
|
||||
|
||||
static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void snapshot(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void filtered(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void sorted(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void index_of(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
template<typename Fn>
|
||||
static void index_of(ContextType, Fn&, Arguments, ReturnValue &);
|
||||
|
||||
// observable
|
||||
static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
|
||||
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
||||
template<typename U>
|
||||
static void add_listener(ContextType, U&, ObjectType, Arguments);
|
||||
template<typename U>
|
||||
static void remove_listener(ContextType, U&, ObjectType, Arguments);
|
||||
|
||||
std::string const name = "Results";
|
||||
|
||||
|
@ -95,6 +110,8 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
|
|||
|
||||
PropertyMap<T> const properties = {
|
||||
{"length", {wrap<get_length>, nullptr}},
|
||||
{"type", {wrap<get_type>, nullptr}},
|
||||
{"optional", {wrap<get_optional>, nullptr}},
|
||||
};
|
||||
|
||||
IndexPropertyType<T> const index_accessor = {wrap<get_index>, nullptr};
|
||||
|
@ -116,15 +133,19 @@ typename T::Object ResultsClass<T>::create_instance(ContextType ctx, SharedRealm
|
|||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &collection, size_t argc, const ValueType arguments[]) {
|
||||
auto query_string = Value::validated_to_string(ctx, arguments[0], "predicate");
|
||||
typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &collection, Arguments args) {
|
||||
if (collection.get_type() != realm::PropertyType::Object) {
|
||||
throw std::runtime_error("Filtering non-object Lists and Results is not yet implemented.");
|
||||
}
|
||||
|
||||
auto query_string = Value::validated_to_string(ctx, args[0], "predicate");
|
||||
auto query = collection.get_query();
|
||||
auto const &realm = collection.get_realm();
|
||||
auto const &object_schema = collection.get_object_schema();
|
||||
|
||||
parser::Predicate predicate = parser::parse(query_string);
|
||||
NativeAccessor<T> accessor(ctx, realm, object_schema);
|
||||
query_builder::ArgumentConverter<ValueType, NativeAccessor<T>> converter(accessor, &arguments[1], argc - 1);
|
||||
query_builder::ArgumentConverter<ValueType, NativeAccessor<T>> converter(accessor, &args.value[1], args.count - 1);
|
||||
query_builder::apply_predicate(query, predicate, converter, realm->schema(), object_schema.name);
|
||||
|
||||
return create_instance(ctx, collection.filter(std::move(query)));
|
||||
|
@ -132,15 +153,18 @@ typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &co
|
|||
|
||||
template<typename T>
|
||||
std::vector<std::pair<std::string, bool>>
|
||||
ResultsClass<T>::get_keypaths(ContextType ctx, size_t argc, const ValueType arguments[]) {
|
||||
validate_argument_count(argc, 1, 2);
|
||||
ResultsClass<T>::get_keypaths(ContextType ctx, Arguments args) {
|
||||
args.validate_maximum(2);
|
||||
|
||||
std::vector<std::pair<std::string, bool>> sort_order;
|
||||
if (args.count == 0) {
|
||||
sort_order.emplace_back("self", true);
|
||||
return sort_order;
|
||||
}
|
||||
else if (Value::is_array(ctx, args[0])) {
|
||||
validate_argument_count(args.count, 1, "Second argument is not allowed if passed an array of sort descriptors");
|
||||
|
||||
if (argc > 0 && Value::is_array(ctx, arguments[0])) {
|
||||
validate_argument_count(argc, 1, "Second argument is not allowed if passed an array of sort descriptors");
|
||||
|
||||
ObjectType js_prop_names = Value::validated_to_object(ctx, arguments[0]);
|
||||
ObjectType js_prop_names = Value::validated_to_object(ctx, args[0]);
|
||||
size_t prop_count = Object::validated_get_length(ctx, js_prop_names);
|
||||
sort_order.reserve(prop_count);
|
||||
|
||||
|
@ -158,8 +182,13 @@ ResultsClass<T>::get_keypaths(ContextType ctx, size_t argc, const ValueType argu
|
|||
}
|
||||
}
|
||||
else {
|
||||
sort_order.emplace_back(Value::validated_to_string(ctx, arguments[0]),
|
||||
argc == 1 || !Value::to_boolean(ctx, arguments[1]));
|
||||
if (Value::is_boolean(ctx, args[0])) {
|
||||
sort_order.emplace_back("self", !Value::to_boolean(ctx, args[0]));
|
||||
}
|
||||
else {
|
||||
sort_order.emplace_back(Value::validated_to_string(ctx, args[0]),
|
||||
args.count == 1 || !Value::to_boolean(ctx, args[1]));
|
||||
}
|
||||
}
|
||||
return sort_order;
|
||||
}
|
||||
|
@ -171,124 +200,136 @@ void ResultsClass<T>::get_length(ContextType ctx, ObjectType object, ReturnValue
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
|
||||
void ResultsClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(object);
|
||||
auto row = results->get(index);
|
||||
|
||||
// Return null for deleted objects in a snapshot.
|
||||
if (!row.is_attached()) {
|
||||
return_value.set_null();
|
||||
return;
|
||||
}
|
||||
|
||||
auto realm_object = realm::Object(results->get_realm(), results->get_object_schema(), results->get(index));
|
||||
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
|
||||
return_value.set(string_for_property_type(results->get_type() & ~realm::PropertyType::Flags));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 0);
|
||||
void ResultsClass<T>::get_optional(ContextType, ObjectType object, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(object);
|
||||
return_value.set(is_nullable(results->get_type()));
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(object);
|
||||
NativeAccessor<T> accessor(ctx, *results);
|
||||
return_value.set(results->get(accessor, index));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::snapshot(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, results->snapshot()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count_at_least(argc, 1);
|
||||
|
||||
void ResultsClass<T>::filtered(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
return_value.set(create_filtered(ctx, *results, argc, arguments));
|
||||
return_value.set(create_filtered(ctx, *results, args));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
void ResultsClass<T>::sorted(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, results->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
|
||||
return_value.set(ResultsClass<T>::create_instance(ctx, results->sort(ResultsClass<T>::get_keypaths(ctx, args))));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 1);
|
||||
|
||||
ObjectType arg = Value::validated_to_object(ctx, arguments[0]);
|
||||
if (Object::template is_instance<RealmObjectClass<T>>(ctx, arg)) {
|
||||
auto object = get_internal<T, RealmObjectClass<T>>(arg);
|
||||
if (!object->is_valid()) {
|
||||
throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.");
|
||||
}
|
||||
|
||||
size_t ndx;
|
||||
try {
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
ndx = results->index_of(object->row());
|
||||
}
|
||||
catch (realm::Results::IncorrectTableException &) {
|
||||
throw std::runtime_error("Object type does not match the type contained in result");
|
||||
}
|
||||
|
||||
if (ndx == realm::not_found) {
|
||||
return_value.set(-1);
|
||||
}
|
||||
else {
|
||||
return_value.set((uint32_t)ndx);
|
||||
}
|
||||
template<typename Fn>
|
||||
void ResultsClass<T>::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
|
||||
size_t ndx;
|
||||
try {
|
||||
ndx = fn(args[0]);
|
||||
}
|
||||
else {
|
||||
catch (realm::Results::IncorrectTableException &) {
|
||||
throw std::runtime_error("Object type does not match the type contained in result");
|
||||
}
|
||||
catch (NonRealmObjectException&) {
|
||||
ndx = realm::not_found;
|
||||
}
|
||||
|
||||
if (ndx == realm::not_found) {
|
||||
return_value.set(-1);
|
||||
}
|
||||
else {
|
||||
return_value.set((uint32_t)ndx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 1);
|
||||
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
void ResultsClass<T>::index_of(ContextType ctx, ObjectType this_object,
|
||||
Arguments args, ReturnValue &return_value) {
|
||||
auto fn = [&](auto&& row) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
NativeAccessor<T> accessor(ctx, *results);
|
||||
return results->index_of(accessor, row);
|
||||
};
|
||||
index_of(ctx, fn, args, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
void ResultsClass<T>::add_listener(ContextType ctx, U& collection, ObjectType this_object, Arguments args) {
|
||||
args.validate_maximum(1);
|
||||
|
||||
auto callback = Value::validated_to_function(ctx, args[0]);
|
||||
Protected<FunctionType> protected_callback(ctx, callback);
|
||||
Protected<ObjectType> protected_this(ctx, this_object);
|
||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||
|
||||
auto token = results->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
|
||||
auto token = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) {
|
||||
HANDLESCOPE
|
||||
|
||||
ValueType arguments[2];
|
||||
arguments[0] = static_cast<ObjectType>(protected_this);
|
||||
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
|
||||
ValueType arguments[] {
|
||||
static_cast<ObjectType>(protected_this),
|
||||
CollectionClass<T>::create_collection_change_set(protected_ctx, change_set)
|
||||
};
|
||||
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
|
||||
});
|
||||
results->m_notification_tokens.emplace_back(protected_callback, std::move(token));
|
||||
collection.m_notification_tokens.emplace_back(protected_callback, std::move(token));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 1);
|
||||
|
||||
void ResultsClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
auto protected_function = Protected<FunctionType>(ctx, callback);
|
||||
|
||||
auto iter = results->m_notification_tokens.begin();
|
||||
typename Protected<FunctionType>::Comparator compare;
|
||||
while (iter != results->m_notification_tokens.end()) {
|
||||
if(compare(iter->first, protected_function)) {
|
||||
iter = results->m_notification_tokens.erase(iter);
|
||||
}
|
||||
else {
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
add_listener(ctx, *results, this_object, args);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
validate_argument_count(argc, 0);
|
||||
|
||||
template<typename U>
|
||||
void ResultsClass<T>::remove_listener(ContextType ctx, U& collection, ObjectType this_object, Arguments args) {
|
||||
args.validate_maximum(1);
|
||||
|
||||
auto callback = Value::validated_to_function(ctx, args[0]);
|
||||
auto protected_function = Protected<FunctionType>(ctx, callback);
|
||||
|
||||
auto& tokens = collection.m_notification_tokens;
|
||||
auto compare = [&](auto&& token) {
|
||||
return typename Protected<FunctionType>::Comparator()(token.first, protected_function);
|
||||
};
|
||||
tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
remove_listener(ctx, *results, this_object, args);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(0);
|
||||
|
||||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
results->m_notification_tokens.clear();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "js_types.hpp"
|
||||
#include "schema.hpp"
|
||||
|
||||
#include "util/format.hpp"
|
||||
|
||||
namespace realm {
|
||||
namespace js {
|
||||
|
||||
|
@ -41,7 +43,7 @@ struct Schema {
|
|||
using ConstructorMap = std::map<std::string, Protected<FunctionType>>;
|
||||
|
||||
static ObjectType dict_for_property_array(ContextType, const ObjectSchema &, ObjectType);
|
||||
static Property parse_property(ContextType, ValueType, std::string, ObjectDefaults &);
|
||||
static Property parse_property(ContextType, ValueType, StringData, std::string, ObjectDefaults &);
|
||||
static ObjectSchema parse_object_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
|
||||
static realm::Schema parse_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
|
||||
|
||||
|
@ -68,8 +70,73 @@ typename T::Object Schema<T>::dict_for_property_array(ContextType ctx, const Obj
|
|||
return dict;
|
||||
}
|
||||
|
||||
static inline void parse_property_type(StringData object_name, Property& prop, StringData type)
|
||||
{
|
||||
using realm::PropertyType;
|
||||
if (!type || !type.size()) {
|
||||
throw std::logic_error(util::format("Property '%1.%2' must have a non-empty type", object_name, prop.name));
|
||||
}
|
||||
if (type.ends_with("[]")) {
|
||||
prop.type |= PropertyType::Array;
|
||||
type = type.substr(0, type.size() - 2);
|
||||
}
|
||||
if (type.ends_with("?")) {
|
||||
prop.type |= PropertyType::Nullable;
|
||||
type = type.substr(0, type.size() - 1);
|
||||
}
|
||||
|
||||
if (type == "bool") {
|
||||
prop.type |= PropertyType::Bool;
|
||||
}
|
||||
else if (type == "int") {
|
||||
prop.type |= PropertyType::Int;
|
||||
}
|
||||
else if (type == "float") {
|
||||
prop.type |= PropertyType::Float;
|
||||
}
|
||||
else if (type == "double") {
|
||||
prop.type |= PropertyType::Double;
|
||||
}
|
||||
else if (type == "string") {
|
||||
prop.type |= PropertyType::String;
|
||||
}
|
||||
else if (type == "date") {
|
||||
prop.type |= PropertyType::Date;
|
||||
}
|
||||
else if (type == "data") {
|
||||
prop.type |= PropertyType::Data;
|
||||
}
|
||||
else if (type == "list") {
|
||||
if (is_nullable(prop.type)) {
|
||||
throw std::logic_error(util::format("List property '%1.%2' cannot be optional", object_name, prop.name));
|
||||
}
|
||||
if (is_array(prop.type)) {
|
||||
throw std::logic_error(util::format("List property '%1.%2' must have a non-list value type", object_name, prop.name));
|
||||
}
|
||||
prop.type |= PropertyType::Object | PropertyType::Array;
|
||||
}
|
||||
else if (type == "linkingObjects") {
|
||||
prop.type |= PropertyType::LinkingObjects | PropertyType::Array;
|
||||
}
|
||||
else if (type == "object") {
|
||||
prop.type |= PropertyType::Object;
|
||||
}
|
||||
else {
|
||||
// The type could be the name of another object type in the same schema.
|
||||
prop.type |= PropertyType::Object;
|
||||
prop.object_type = type;
|
||||
}
|
||||
|
||||
// Object properties are implicitly optional
|
||||
if (prop.type == PropertyType::Object && !is_array(prop.type)) {
|
||||
prop.type |= PropertyType::Nullable;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::string property_name, ObjectDefaults &object_defaults) {
|
||||
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, StringData object_name,
|
||||
std::string property_name, ObjectDefaults &object_defaults) {
|
||||
static const String default_string = "default";
|
||||
static const String indexed_string = "indexed";
|
||||
static const String type_string = "type";
|
||||
|
@ -78,79 +145,23 @@ Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::s
|
|||
static const String property_string = "property";
|
||||
|
||||
Property prop;
|
||||
prop.name = property_name;
|
||||
prop.name = std::move(property_name);
|
||||
|
||||
ObjectType property_object = {};
|
||||
std::string type;
|
||||
|
||||
using realm::PropertyType;
|
||||
PropertyType is_optional = PropertyType::Required;
|
||||
|
||||
if (Value::is_object(ctx, attributes)) {
|
||||
property_object = Value::validated_to_object(ctx, attributes);
|
||||
type = Object::validated_get_string(ctx, property_object, type_string);
|
||||
std::string property_type = Object::validated_get_string(ctx, property_object, type_string);
|
||||
parse_property_type(object_name, prop, property_type);
|
||||
|
||||
ValueType optional_value = Object::get_property(ctx, property_object, optional_string);
|
||||
if (!Value::is_undefined(ctx, optional_value) && Value::validated_to_boolean(ctx, optional_value, "optional")) {
|
||||
is_optional = PropertyType::Nullable;
|
||||
prop.type |= PropertyType::Nullable;
|
||||
}
|
||||
}
|
||||
else {
|
||||
type = Value::validated_to_string(ctx, attributes);
|
||||
}
|
||||
|
||||
if (type == "bool") {
|
||||
prop.type = PropertyType::Bool | is_optional;
|
||||
}
|
||||
else if (type == "int") {
|
||||
prop.type = PropertyType::Int | is_optional;
|
||||
}
|
||||
else if (type == "float") {
|
||||
prop.type = PropertyType::Float | is_optional;
|
||||
}
|
||||
else if (type == "double") {
|
||||
prop.type = PropertyType::Double | is_optional;
|
||||
}
|
||||
else if (type == "string") {
|
||||
prop.type = PropertyType::String | is_optional;
|
||||
}
|
||||
else if (type == "date") {
|
||||
prop.type = PropertyType::Date | is_optional;
|
||||
}
|
||||
else if (type == "data") {
|
||||
prop.type = PropertyType::Data | is_optional;
|
||||
}
|
||||
else if (type == "list") {
|
||||
if (!Value::is_valid(property_object)) {
|
||||
throw std::runtime_error("List property must specify 'objectType'");
|
||||
}
|
||||
prop.type = PropertyType::Object | PropertyType::Array;
|
||||
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
|
||||
}
|
||||
else if (type == "linkingObjects") {
|
||||
prop.type = PropertyType::LinkingObjects | PropertyType::Array;
|
||||
|
||||
if (!Value::is_valid(property_object)) {
|
||||
throw std::runtime_error("Object property must specify 'objectType'");
|
||||
}
|
||||
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
|
||||
prop.link_origin_property_name = Object::validated_get_string(ctx, property_object, property_string);
|
||||
}
|
||||
else if (type == "object") {
|
||||
prop.type = PropertyType::Object | PropertyType::Nullable;
|
||||
|
||||
if (!Value::is_valid(property_object)) {
|
||||
throw std::runtime_error("Object property must specify 'objectType'");
|
||||
}
|
||||
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
|
||||
}
|
||||
else {
|
||||
// The type could be the name of another object type in the same schema.
|
||||
prop.type = PropertyType::Object | PropertyType::Nullable;
|
||||
prop.object_type = type;
|
||||
}
|
||||
|
||||
if (Value::is_valid(property_object)) {
|
||||
ValueType default_value = Object::get_property(ctx, property_object, default_string);
|
||||
if (!Value::is_undefined(ctx, default_value)) {
|
||||
object_defaults.emplace(prop.name, Protected<ValueType>(ctx, default_value));
|
||||
|
@ -161,6 +172,27 @@ Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::s
|
|||
prop.is_indexed = Value::validated_to_boolean(ctx, indexed_value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::string property_type = Value::validated_to_string(ctx, attributes);
|
||||
parse_property_type(object_name, prop, property_type);
|
||||
}
|
||||
|
||||
if (prop.type == PropertyType::Object && prop.object_type.empty()) {
|
||||
if (!Value::is_valid(property_object)) {
|
||||
throw std::logic_error(util::format("%1 property %2.%3 must specify 'objectType'",
|
||||
is_array(prop.type) ? "List" : "Object", object_name, prop.name));
|
||||
}
|
||||
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
|
||||
}
|
||||
|
||||
if (prop.type == PropertyType::LinkingObjects) {
|
||||
if (!Value::is_valid(property_object)) {
|
||||
throw std::logic_error(util::format("Linking objects property %1.%2 must specify 'objectType'",
|
||||
object_name, prop.name));
|
||||
}
|
||||
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
|
||||
prop.link_origin_property_name = Object::validated_get_string(ctx, property_object, property_string);
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
@ -188,7 +220,7 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
|
|||
for (uint32_t i = 0; i < length; i++) {
|
||||
ObjectType property_object = Object::validated_get_object(ctx, properties_object, i);
|
||||
std::string property_name = Object::validated_get_string(ctx, property_object, name_string);
|
||||
Property property = parse_property(ctx, property_object, property_name, object_defaults);
|
||||
Property property = parse_property(ctx, property_object, object_schema.name, std::move(property_name), object_defaults);
|
||||
if (property.type == realm::PropertyType::LinkingObjects) {
|
||||
object_schema.computed_properties.emplace_back(std::move(property));
|
||||
}
|
||||
|
@ -200,9 +232,9 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
|
|||
}
|
||||
else {
|
||||
auto property_names = Object::get_property_names(ctx, properties_object);
|
||||
for (auto &property_name : property_names) {
|
||||
for (auto& property_name : property_names) {
|
||||
ValueType property_value = Object::get_property(ctx, properties_object, property_name);
|
||||
Property property = parse_property(ctx, property_value, property_name, object_defaults);
|
||||
Property property = parse_property(ctx, property_value, object_schema.name, property_name, object_defaults);
|
||||
if (property.type == realm::PropertyType::LinkingObjects) {
|
||||
object_schema.computed_properties.emplace_back(std::move(property));
|
||||
}
|
||||
|
@ -291,13 +323,25 @@ typename T::Object Schema<T>::object_for_property(ContextType ctx, const Propert
|
|||
Object::set_property(ctx, object, name_string, Value::from_string(ctx, property.name));
|
||||
|
||||
static const String type_string = "type";
|
||||
const std::string type = is_array(property.type) ? "list" : string_for_property_type(property.type);
|
||||
Object::set_property(ctx, object, type_string, Value::from_string(ctx, type));
|
||||
if (is_array(property.type)) {
|
||||
if (property.type == realm::PropertyType::LinkingObjects) {
|
||||
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "linkingObjects"));
|
||||
}
|
||||
else {
|
||||
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "list"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
Object::set_property(ctx, object, type_string, Value::from_string(ctx, string_for_property_type(property.type)));
|
||||
}
|
||||
|
||||
static const String object_type_string = "objectType";
|
||||
if (property.object_type.size()) {
|
||||
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, property.object_type));
|
||||
}
|
||||
else if (is_array(property.type)) {
|
||||
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, string_for_property_type(property.type & ~realm::PropertyType::Flags)));
|
||||
}
|
||||
|
||||
static const String property_string = "property";
|
||||
if (property.type == realm::PropertyType::LinkingObjects) {
|
||||
|
@ -305,14 +349,10 @@ typename T::Object Schema<T>::object_for_property(ContextType ctx, const Propert
|
|||
}
|
||||
|
||||
static const String indexed_string = "indexed";
|
||||
if (property.is_indexed) {
|
||||
Object::set_property(ctx, object, indexed_string, Value::from_boolean(ctx, true));
|
||||
}
|
||||
Object::set_property(ctx, object, indexed_string, Value::from_boolean(ctx, property.is_indexed));
|
||||
|
||||
static const String optional_string = "optional";
|
||||
if (is_nullable(property.type)) {
|
||||
Object::set_property(ctx, object, optional_string, Value::from_boolean(ctx, true));
|
||||
}
|
||||
Object::set_property(ctx, object, optional_string, Value::from_boolean(ctx, is_nullable(property.type)));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
|
147
src/js_types.hpp
147
src/js_types.hpp
|
@ -65,7 +65,7 @@ struct String {
|
|||
String(const char *);
|
||||
String(const StringType &);
|
||||
String(StringType &&);
|
||||
String(const std::string &);
|
||||
String(StringData);
|
||||
|
||||
operator StringType() const;
|
||||
operator std::string() const;
|
||||
|
@ -82,16 +82,21 @@ struct Context {
|
|||
|
||||
class TypeErrorException : public std::invalid_argument {
|
||||
public:
|
||||
TypeErrorException(StringData object_type, StringData property,
|
||||
std::string const& type, std::string const& value)
|
||||
: std::invalid_argument(util::format("%1.%2 must be of type '%3', got (%4)",
|
||||
object_type, property, type, value))
|
||||
template<typename NativeAccessor, typename ValueType>
|
||||
TypeErrorException(NativeAccessor& accessor, StringData object_type,
|
||||
Property const& prop, ValueType value)
|
||||
: std::invalid_argument(util::format("%1.%2 must be of type '%3', got '%4' (%5)",
|
||||
object_type, prop.name, type_string(prop),
|
||||
accessor.typeof(value),
|
||||
accessor.print(value)))
|
||||
{}
|
||||
|
||||
TypeErrorException(const char *name, std::string const& type, std::string const& value)
|
||||
: std::invalid_argument(util::format("%1 must be of type '%2', got (%3)",
|
||||
name ? name : "JS value", type, value))
|
||||
{}
|
||||
|
||||
static std::string type_string(Property const& prop);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -101,6 +106,8 @@ struct Value {
|
|||
using ObjectType = typename T::Object;
|
||||
using ValueType = typename T::Value;
|
||||
|
||||
static const char *typeof(ContextType, const ValueType &);
|
||||
|
||||
static bool is_array(ContextType, const ValueType &);
|
||||
static bool is_array_buffer(ContextType, const ValueType &);
|
||||
static bool is_array_buffer_view(ContextType, const ValueType &);
|
||||
|
@ -117,12 +124,17 @@ struct Value {
|
|||
static bool is_valid(const ValueType &);
|
||||
|
||||
static bool is_valid_for_property(ContextType, const ValueType&, const Property&);
|
||||
static bool is_valid_for_property_type(ContextType, const ValueType&, realm::PropertyType type, StringData object_type);
|
||||
|
||||
static ValueType from_boolean(ContextType, bool);
|
||||
static ValueType from_null(ContextType);
|
||||
static ValueType from_number(ContextType, double);
|
||||
static ValueType from_string(ContextType, const String<T> &);
|
||||
static ValueType from_binary(ContextType, BinaryData);
|
||||
static ValueType from_string(ContextType ctx, const char *s) { return s ? from_nonnull_string(ctx, s) : from_null(ctx); }
|
||||
static ValueType from_string(ContextType ctx, StringData s) { return s ? from_nonnull_string(ctx, s) : from_null(ctx); }
|
||||
static ValueType from_string(ContextType ctx, const std::string& s) { return from_nonnull_string(ctx, s.c_str()); }
|
||||
static ValueType from_binary(ContextType ctx, BinaryData b) { return b ? from_nonnull_binary(ctx, b) : from_null(ctx); }
|
||||
static ValueType from_nonnull_string(ContextType, const String<T>&);
|
||||
static ValueType from_nonnull_binary(ContextType, BinaryData);
|
||||
static ValueType from_undefined(ContextType);
|
||||
|
||||
static ObjectType to_array(ContextType, const ValueType &);
|
||||
|
@ -135,6 +147,7 @@ struct Value {
|
|||
static String<T> to_string(ContextType, const ValueType &);
|
||||
static OwnedBinaryData to_binary(ContextType, ValueType);
|
||||
|
||||
|
||||
#define VALIDATED(return_t, type) \
|
||||
static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \
|
||||
if (!is_##type(ctx, value)) { \
|
||||
|
@ -351,82 +364,76 @@ REALM_JS_INLINE void set_internal(const typename T::Object &object, typename Cla
|
|||
template<typename T>
|
||||
inline bool Value<T>::is_valid_for_property(ContextType context, const ValueType &value, const Property& prop)
|
||||
{
|
||||
if (is_nullable(prop.type) && (is_null(context, value) || is_undefined(context, value))) {
|
||||
return true;
|
||||
}
|
||||
return is_valid_for_property_type(context, value, prop.type, prop.object_type);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool Value<T>::is_valid_for_property_type(ContextType context, const ValueType &value, realm::PropertyType type, StringData object_type) {
|
||||
using realm::PropertyType;
|
||||
if (realm::is_array(prop.type)) {
|
||||
if (prop.type != PropertyType::Object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: Do we need to validate the types of the contained objects?
|
||||
if (is_array(context, value)) {
|
||||
auto check_value = [&](auto&& value) {
|
||||
if (is_nullable(type) && (is_null(context, value) || is_undefined(context, value))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_object(context, value)) {
|
||||
auto object = to_object(context, value);
|
||||
return Object<T>::template is_instance<ResultsClass<T>>(context, object)
|
||||
|| Object<T>::template is_instance<ListClass<T>>(context, object);
|
||||
switch (type & ~PropertyType::Flags) {
|
||||
case PropertyType::Int:
|
||||
case PropertyType::Float:
|
||||
case PropertyType::Double:
|
||||
return is_number(context, value);
|
||||
case PropertyType::Bool:
|
||||
return is_boolean(context, value);
|
||||
case PropertyType::String:
|
||||
return is_string(context, value);
|
||||
case PropertyType::Data:
|
||||
return is_binary(context, value);
|
||||
case PropertyType::Date:
|
||||
return is_date(context, value);
|
||||
case PropertyType::Object:
|
||||
return true;
|
||||
case PropertyType::Any:
|
||||
return false;
|
||||
default:
|
||||
REALM_UNREACHABLE();
|
||||
}
|
||||
};
|
||||
auto check_collection_type = [&](auto&& list) {
|
||||
auto list_type = list->get_type();
|
||||
return list_type == type
|
||||
&& is_nullable(list_type) == is_nullable(type)
|
||||
&& (type != PropertyType::Object || list->get_object_schema().name == object_type);
|
||||
};
|
||||
|
||||
if (!realm::is_array(type)) {
|
||||
return check_value(value);
|
||||
}
|
||||
|
||||
if (is_object(context, value)) {
|
||||
auto object = to_object(context, value);
|
||||
if (Object<T>::template is_instance<ResultsClass<T>>(context, object)) {
|
||||
return check_collection_type(get_internal<T, ResultsClass<T>>(object));
|
||||
}
|
||||
if (Object<T>::template is_instance<ListClass<T>>(context, object)) {
|
||||
return check_collection_type(get_internal<T, ListClass<T>>(object));
|
||||
}
|
||||
}
|
||||
|
||||
if (type == PropertyType::Object) {
|
||||
// FIXME: Do we need to validate the types of the contained objects?
|
||||
return is_array(context, value);
|
||||
}
|
||||
|
||||
if (!is_array(context, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (prop.type & ~PropertyType::Flags) {
|
||||
case PropertyType::Int:
|
||||
case PropertyType::Float:
|
||||
case PropertyType::Double:
|
||||
return is_number(context, value);
|
||||
case PropertyType::Bool:
|
||||
return is_boolean(context, value);
|
||||
case PropertyType::String:
|
||||
return is_string(context, value);
|
||||
case PropertyType::Data:
|
||||
return is_binary(context, value);
|
||||
case PropertyType::Date:
|
||||
return is_date(context, value);
|
||||
case PropertyType::Object:
|
||||
return true;
|
||||
case PropertyType::Any:
|
||||
auto array = to_array(context, value);
|
||||
uint32_t size = Object<T>::validated_get_length(context, array);
|
||||
for (uint32_t i = 0; i < size; ++i) {
|
||||
if (!check_value(Object<T>::get_property(context, array, i))) {
|
||||
return false;
|
||||
default:
|
||||
REALM_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string js_type_name_for_property_type(realm::PropertyType type)
|
||||
{
|
||||
using realm::PropertyType;
|
||||
if (realm::is_array(type)) {
|
||||
if (type == PropertyType::LinkingObjects) {
|
||||
throw std::runtime_error("LinkingObjects' type is not supported");
|
||||
}
|
||||
return "array";
|
||||
}
|
||||
|
||||
switch (type & ~PropertyType::Flags) {
|
||||
case PropertyType::Int:
|
||||
case PropertyType::Float:
|
||||
case PropertyType::Double:
|
||||
return "number";
|
||||
case PropertyType::Bool:
|
||||
return "boolean";
|
||||
case PropertyType::String:
|
||||
return "string";
|
||||
case PropertyType::Date:
|
||||
return "date";
|
||||
case PropertyType::Data:
|
||||
return "binary";
|
||||
case PropertyType::Object:
|
||||
return "object";
|
||||
case PropertyType::Any:
|
||||
throw std::runtime_error("'Any' type is not supported");
|
||||
default:
|
||||
REALM_UNREACHABLE();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // js
|
||||
|
|
|
@ -300,20 +300,43 @@ inline void ObjectWrap<ClassType>::get_property_names(JSContextRef ctx, JSObject
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool try_get_int(JSStringRef property, int64_t& value) {
|
||||
value = 0;
|
||||
auto str = JSStringGetCharactersPtr(property);
|
||||
auto end = str + JSStringGetLength(property);
|
||||
while (str != end && iswspace(*str)) {
|
||||
++str;
|
||||
}
|
||||
bool negative = false;
|
||||
if (str != end && *str == '-') {
|
||||
negative = true;
|
||||
++str;
|
||||
}
|
||||
while (str != end && *str >= '0' && *str <= '9') {
|
||||
if (int_multiply_with_overflow_detect(value, 10)) {
|
||||
return false;
|
||||
}
|
||||
value += *str - '0';
|
||||
++str;
|
||||
}
|
||||
if (negative) {
|
||||
value *= -1;
|
||||
}
|
||||
return str == end;
|
||||
}
|
||||
|
||||
template<typename ClassType>
|
||||
inline JSValueRef ObjectWrap<ClassType>::get_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) {
|
||||
if (auto index_getter = s_class.index_accessor.getter) {
|
||||
try {
|
||||
uint32_t index = validated_positive_index(jsc::String(property));
|
||||
int64_t num;
|
||||
if (try_get_int(property, num)) {
|
||||
uint32_t index;
|
||||
if (num < 0 || util::int_cast_with_overflow_detect(num, index)) {
|
||||
// Out-of-bounds index getters should just return undefined in JS.
|
||||
return Value::from_undefined(ctx);
|
||||
}
|
||||
return index_getter(ctx, object, index, exception);
|
||||
}
|
||||
catch (std::out_of_range &) {
|
||||
// Out-of-bounds index getters should just return undefined in JS.
|
||||
return Value::from_undefined(ctx);
|
||||
}
|
||||
catch (std::invalid_argument &) {
|
||||
// Property is not a number.
|
||||
}
|
||||
}
|
||||
if (auto string_getter = s_class.string_accessor.getter) {
|
||||
return string_getter(ctx, object, property, exception);
|
||||
|
@ -326,24 +349,24 @@ inline bool ObjectWrap<ClassType>::set_property(JSContextRef ctx, JSObjectRef ob
|
|||
auto index_setter = s_class.index_accessor.setter;
|
||||
|
||||
if (index_setter || s_class.index_accessor.getter) {
|
||||
try {
|
||||
uint32_t index = validated_positive_index(jsc::String(property));
|
||||
|
||||
int64_t num;
|
||||
if (try_get_int(property, num)) {
|
||||
if (num < 0) {
|
||||
*exception = Exception::value(ctx, util::format("Index %1 cannot be less than zero.", num));
|
||||
return false;
|
||||
}
|
||||
int32_t index;
|
||||
if (util::int_cast_with_overflow_detect(num, index)) {
|
||||
*exception = Exception::value(ctx, util::format("Index %1 cannot be greater than %2.",
|
||||
num, std::numeric_limits<uint32_t>::max()));
|
||||
return false;
|
||||
}
|
||||
if (index_setter) {
|
||||
return index_setter(ctx, object, index, value, exception);
|
||||
}
|
||||
else {
|
||||
*exception = Exception::value(ctx, std::string("Cannot assign to read only index ") + util::to_string(index));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (std::out_of_range &e) {
|
||||
*exception = Exception::value(ctx, e);
|
||||
*exception = Exception::value(ctx, util::format("Cannot assign to read only index %1", index));
|
||||
return false;
|
||||
}
|
||||
catch (std::invalid_argument &) {
|
||||
// Property is not a number.
|
||||
}
|
||||
}
|
||||
if (auto string_setter = s_class.string_accessor.setter) {
|
||||
return string_setter(ctx, object, property, value, exception);
|
||||
|
@ -372,10 +395,10 @@ JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object,
|
|||
}
|
||||
|
||||
template<jsc::ArgumentsMethodType F>
|
||||
JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
|
||||
JSValueRef wrap(JSContextRef ctx, JSObjectRef, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
|
||||
jsc::ReturnValue return_value(ctx);
|
||||
try {
|
||||
F(ctx, function, this_object, jsc::Arguments{ctx, argc, arguments}, return_value);
|
||||
F(ctx, this_object, jsc::Arguments{ctx, argc, arguments}, return_value);
|
||||
return return_value;
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
|
|
|
@ -38,6 +38,9 @@ class ReturnValue<jsc::Types> {
|
|||
void set(const std::string &string) {
|
||||
m_value = JSValueMakeString(m_context, jsc::String(string));
|
||||
}
|
||||
void set(const char *string) {
|
||||
m_value = JSValueMakeString(m_context, jsc::String(string));
|
||||
}
|
||||
void set(bool boolean) {
|
||||
m_value = JSValueMakeBoolean(m_context, boolean);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,8 @@ class String<jsc::Types> {
|
|||
public:
|
||||
String(const char *s) : m_str(JSStringCreateWithUTF8CString(s)) {}
|
||||
String(const JSStringRef &s) : m_str(JSStringRetain(s)) {}
|
||||
String(const std::string &str) : String(str.c_str()) {}
|
||||
String(StringData str) : String(str.data()) {}
|
||||
String(const std::string& str) : String(str.c_str()) {}
|
||||
String(const StringType &o) : String(o.m_str) {}
|
||||
String(StringType &&o) : m_str(o.m_str) {
|
||||
o.m_str = nullptr;
|
||||
|
|
|
@ -47,7 +47,7 @@ bool jsc::Value::is_binary(JSContextRef ctx, const JSValueRef &value)
|
|||
}
|
||||
|
||||
template<>
|
||||
JSValueRef jsc::Value::from_binary(JSContextRef ctx, BinaryData data)
|
||||
JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data)
|
||||
{
|
||||
static jsc::String s_buffer = "buffer";
|
||||
static jsc::String s_uint8_array = "Uint8Array";
|
||||
|
|
|
@ -41,6 +41,18 @@ static inline bool is_object_of_type(JSContextRef ctx, JSValueRef value, jsc::St
|
|||
return result;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline const char *jsc::Value::typeof(JSContextRef ctx, const JSValueRef &value) {
|
||||
switch (JSValueGetType(ctx, value)) {
|
||||
case kJSTypeNull: return "null";
|
||||
case kJSTypeNumber: return "number";
|
||||
case kJSTypeObject: return "object";
|
||||
case kJSTypeString: return "string";
|
||||
case kJSTypeBoolean: return "boolean";
|
||||
case kJSTypeUndefined: return "undefined";
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool jsc::Value::is_array(JSContextRef ctx, const JSValueRef &value) {
|
||||
// JSValueIsArray() is not available until iOS 9.
|
||||
|
@ -124,7 +136,7 @@ inline JSValueRef jsc::Value::from_number(JSContextRef ctx, double number) {
|
|||
}
|
||||
|
||||
template<>
|
||||
inline JSValueRef jsc::Value::from_string(JSContextRef ctx, const jsc::String &string) {
|
||||
inline JSValueRef jsc::Value::from_nonnull_string(JSContextRef ctx, const jsc::String &string) {
|
||||
return JSValueMakeString(ctx, string);
|
||||
}
|
||||
|
||||
|
@ -134,26 +146,12 @@ inline JSValueRef jsc::Value::from_undefined(JSContextRef ctx) {
|
|||
}
|
||||
|
||||
template<>
|
||||
JSValueRef jsc::Value::from_binary(JSContextRef ctx, BinaryData data);
|
||||
JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data);
|
||||
|
||||
template<>
|
||||
inline bool jsc::Value::to_boolean(JSContextRef ctx, const JSValueRef &value) {
|
||||
return JSValueToBoolean(ctx, value);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef &value) {
|
||||
JSValueRef exception = nullptr;
|
||||
double number = JSValueToNumber(ctx, value, &exception);
|
||||
if (exception) {
|
||||
throw jsc::Exception(ctx, exception);
|
||||
}
|
||||
if (isnan(number)) {
|
||||
throw std::invalid_argument("Value not convertible to a number.");
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef &value) {
|
||||
JSValueRef exception = nullptr;
|
||||
|
@ -168,6 +166,21 @@ inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef &val
|
|||
return string;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef &value) {
|
||||
JSValueRef exception = nullptr;
|
||||
double number = JSValueToNumber(ctx, value, &exception);
|
||||
if (exception) {
|
||||
throw jsc::Exception(ctx, exception);
|
||||
}
|
||||
if (isnan(number)) {
|
||||
throw std::invalid_argument(util::format("Value '%1' not convertible to a number.",
|
||||
(std::string)to_string(ctx, value)));
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
inline JSObjectRef jsc::Value::to_object(JSContextRef ctx, const JSValueRef &value) {
|
||||
JSValueRef exception = nullptr;
|
||||
|
|
|
@ -317,7 +317,7 @@ void wrap(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|||
auto arguments = node::get_arguments(info);
|
||||
|
||||
try {
|
||||
F(isolate, info.Callee(), info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value);
|
||||
F(isolate, info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
Nan::ThrowError(node::Exception::value(isolate, e));
|
||||
|
|
|
@ -41,6 +41,17 @@ class ReturnValue<node::Types> {
|
|||
m_value.Set(Nan::New(string).ToLocalChecked());
|
||||
}
|
||||
}
|
||||
void set(const char *str) {
|
||||
if (!str) {
|
||||
m_value.SetNull();
|
||||
}
|
||||
else if (!*str) {
|
||||
m_value.SetEmptyString();
|
||||
}
|
||||
else {
|
||||
m_value.Set(Nan::New(str).ToLocalChecked());
|
||||
}
|
||||
}
|
||||
void set(bool boolean) {
|
||||
m_value.Set(boolean);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,17 @@
|
|||
namespace realm {
|
||||
namespace js {
|
||||
|
||||
template<>
|
||||
inline const char *node::Value::typeof(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
|
||||
if (value->IsNull()) { return "null"; }
|
||||
if (value->IsNumber()) { return "number"; }
|
||||
if (value->IsString()) { return "string"; }
|
||||
if (value->IsBoolean()) { return "boolean"; }
|
||||
if (value->IsUndefined()) { return "undefined"; }
|
||||
if (value->IsObject()) { return "object"; }
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool node::Value::is_array(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
|
||||
return value->IsArray();
|
||||
|
@ -110,12 +121,12 @@ inline v8::Local<v8::Value> node::Value::from_number(v8::Isolate* isolate, doubl
|
|||
}
|
||||
|
||||
template<>
|
||||
inline v8::Local<v8::Value> node::Value::from_string(v8::Isolate* isolate, const node::String &string) {
|
||||
inline v8::Local<v8::Value> node::Value::from_nonnull_string(v8::Isolate* isolate, const node::String &string) {
|
||||
return v8::Local<v8::String>(string);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline v8::Local<v8::Value> node::Value::from_binary(v8::Isolate* isolate, BinaryData data) {
|
||||
inline v8::Local<v8::Value> node::Value::from_nonnull_binary(v8::Isolate* isolate, BinaryData data) {
|
||||
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, data.size());
|
||||
v8::ArrayBuffer::Contents contents = buffer->GetContents();
|
||||
|
||||
|
@ -137,17 +148,18 @@ inline bool node::Value::to_boolean(v8::Isolate* isolate, const v8::Local<v8::Va
|
|||
}
|
||||
|
||||
template<>
|
||||
inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
|
||||
double number = Nan::To<double>(value).FromMaybe(NAN);
|
||||
if (std::isnan(number)) {
|
||||
throw std::invalid_argument("Value not convertible to a number.");
|
||||
}
|
||||
return number;
|
||||
inline node::String node::Value::to_string(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
|
||||
return value->ToString();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline node::String node::Value::to_string(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
|
||||
return value->ToString();
|
||||
inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
|
||||
double number = Nan::To<double>(value).FromMaybe(NAN);
|
||||
if (std::isnan(number)) {
|
||||
throw std::invalid_argument(util::format("Value '%1' not convertible to a number.",
|
||||
(std::string)to_string(isolate, value)));
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
template<>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 30b8a7853b17762719aa7671aac6ebea04473e33
|
||||
Subproject commit 49705c0fbdbff8536cfa134fc17b837db4385d31
|
81
src/rpc.cpp
81
src/rpc.cpp
|
@ -34,6 +34,7 @@ using namespace realm::rpc;
|
|||
|
||||
using Accessor = realm::js::NativeAccessor<jsc::Types>;
|
||||
|
||||
namespace {
|
||||
static const char * const RealmObjectTypesData = "data";
|
||||
static const char * const RealmObjectTypesDate = "date";
|
||||
static const char * const RealmObjectTypesDictionary = "dict";
|
||||
|
@ -46,10 +47,40 @@ static const char * const RealmObjectTypesUser = "user";
|
|||
static const char * const RealmObjectTypesSession = "session";
|
||||
static const char * const RealmObjectTypesUndefined = "undefined";
|
||||
|
||||
static RPCServer*& get_rpc_server(JSGlobalContextRef ctx) {
|
||||
json serialize_object_schema(const realm::ObjectSchema &object_schema) {
|
||||
std::vector<std::string> properties;
|
||||
|
||||
for (auto &prop : object_schema.persisted_properties) {
|
||||
properties.push_back(prop.name);
|
||||
}
|
||||
|
||||
for (auto &prop : object_schema.computed_properties) {
|
||||
properties.push_back(prop.name);
|
||||
}
|
||||
|
||||
return {
|
||||
{"name", object_schema.name},
|
||||
{"properties", properties},
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
json get_type(Container const& c) {
|
||||
auto type = c.get_type();
|
||||
if (type == realm::PropertyType::Object) {
|
||||
return serialize_object_schema(c.get_object_schema());
|
||||
}
|
||||
return {
|
||||
{"type", string_for_property_type(type)},
|
||||
{"optional", is_nullable(type)}
|
||||
};
|
||||
}
|
||||
|
||||
RPCServer*& get_rpc_server(JSGlobalContextRef ctx) {
|
||||
static std::map<JSGlobalContextRef, RPCServer*> s_map;
|
||||
return s_map[ctx];
|
||||
}
|
||||
}
|
||||
|
||||
RPCWorker::RPCWorker() {
|
||||
m_thread = std::thread([this]() {
|
||||
|
@ -144,10 +175,31 @@ RPCServer::RPCServer() {
|
|||
if (!realm_constructor) {
|
||||
throw std::runtime_error("Realm constructor not found!");
|
||||
}
|
||||
|
||||
|
||||
JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync");
|
||||
JSObjectRef user_constructor = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, "User");
|
||||
JSObjectRef create_user_method = (JSObjectRef)jsc::Object::get_property(m_context, user_constructor, "createUser");
|
||||
|
||||
json::array_t args = dict["arguments"];
|
||||
size_t arg_count = args.size();
|
||||
JSValueRef arg_values[arg_count];
|
||||
|
||||
for (size_t i = 0; i < arg_count; i++) {
|
||||
arg_values[i] = deserialize_json_value(args[i]);
|
||||
}
|
||||
|
||||
JSObjectRef user_object = (JSObjectRef)jsc::Function::call(m_context, create_user_method, arg_count, arg_values);
|
||||
return (json){{"result", serialize_json_value(user_object)}};
|
||||
};
|
||||
m_requests["/_adminUser"] = [this](const json dict) {
|
||||
JSObjectRef realm_constructor = m_session_id ? JSObjectRef(m_objects[m_session_id]) : NULL;
|
||||
if (!realm_constructor) {
|
||||
throw std::runtime_error("Realm constructor not found!");
|
||||
}
|
||||
|
||||
JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync");
|
||||
JSObjectRef user_constructor = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, "User");
|
||||
JSObjectRef create_user_method = (JSObjectRef)jsc::Object::get_property(m_context, user_constructor, "_adminUser");
|
||||
|
||||
json::array_t args = dict["arguments"];
|
||||
size_t arg_count = args.size();
|
||||
|
@ -213,11 +265,11 @@ RPCServer::RPCServer() {
|
|||
if (!realm_constructor) {
|
||||
throw std::runtime_error("Realm constructor not found!");
|
||||
}
|
||||
|
||||
|
||||
JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync");
|
||||
JSObjectRef user_constructor = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, "User");
|
||||
JSValueRef value = jsc::Object::get_property(m_context, user_constructor, "all");
|
||||
|
||||
|
||||
return (json){{"result", serialize_json_value(value)}};
|
||||
};
|
||||
m_requests["/clear_test_state"] = [this](const json dict) {
|
||||
|
@ -360,7 +412,7 @@ json RPCServer::serialize_json_value(JSValueRef js_value) {
|
|||
{"type", RealmObjectTypesList},
|
||||
{"id", store_object(js_object)},
|
||||
{"size", list->size()},
|
||||
{"schema", serialize_object_schema(list->get_object_schema())}
|
||||
{"schema", get_type(*list)},
|
||||
};
|
||||
}
|
||||
else if (jsc::Object::is_instance<js::ResultsClass<jsc::Types>>(m_context, js_object)) {
|
||||
|
@ -369,7 +421,7 @@ json RPCServer::serialize_json_value(JSValueRef js_value) {
|
|||
{"type", RealmObjectTypesResults},
|
||||
{"id", store_object(js_object)},
|
||||
{"size", results->size()},
|
||||
{"schema", serialize_object_schema(results->get_object_schema())}
|
||||
{"schema", get_type(*results)},
|
||||
};
|
||||
}
|
||||
else if (jsc::Object::is_instance<js::RealmClass<jsc::Types>>(m_context, js_object)) {
|
||||
|
@ -455,23 +507,6 @@ json RPCServer::serialize_json_value(JSValueRef js_value) {
|
|||
assert(0);
|
||||
}
|
||||
|
||||
json RPCServer::serialize_object_schema(const realm::ObjectSchema &object_schema) {
|
||||
std::vector<std::string> properties;
|
||||
|
||||
for (auto &prop : object_schema.persisted_properties) {
|
||||
properties.push_back(prop.name);
|
||||
}
|
||||
|
||||
for (auto &prop : object_schema.computed_properties) {
|
||||
properties.push_back(prop.name);
|
||||
}
|
||||
|
||||
return {
|
||||
{"name", object_schema.name},
|
||||
{"properties", properties},
|
||||
};
|
||||
}
|
||||
|
||||
JSValueRef RPCServer::deserialize_json_value(const json dict) {
|
||||
json oid = dict["id"];
|
||||
if (oid.is_number()) {
|
||||
|
|
|
@ -81,8 +81,6 @@ class RPCServer {
|
|||
|
||||
json serialize_json_value(JSValueRef value);
|
||||
JSValueRef deserialize_json_value(const json dict);
|
||||
|
||||
json serialize_object_schema(const ObjectSchema &objectSchema);
|
||||
};
|
||||
|
||||
} // rpc
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
context[@"Promise"] = promiseModule[@"Promise"];
|
||||
}
|
||||
|
||||
context[@"global"] = [JSValue valueWithNewObjectInContext:context];
|
||||
|
||||
// Create Realm constructor in the JS context.
|
||||
RJSInitializeInContext(context.JSGlobalContextRef);
|
||||
|
||||
|
|
|
@ -19,75 +19,122 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
assertEqual: function(val1, val2, errorMessage) {
|
||||
assertSimilar: function(type, val1, val2, errorMessage, depth) {
|
||||
depth = depth || 0;
|
||||
this.assertDefined(type, depth + 1);
|
||||
type = type.replace('?', '');
|
||||
if (val2 === null) {
|
||||
this.assertNull(val1, errorMessage, depth + 1);
|
||||
}
|
||||
else if (type === 'float' || type === 'double') {
|
||||
this.assertEqualWithTolerance(val1, val2, 0.000001, errorMessage, depth + 1);
|
||||
}
|
||||
else if (type === 'data') {
|
||||
this.assertArraysEqual(new Uint8Array(val1), val2, errorMessage, depth + 1);
|
||||
}
|
||||
else if (type === 'date') {
|
||||
this.assertEqual(val1 && val1.getTime(), val2.getTime(), errorMessage, depth + 1);
|
||||
}
|
||||
else if (type === 'object') {
|
||||
for (const key of Object.keys(val1)) {
|
||||
this.assertEqual(val1[key], val2[key], errorMessage, depth + 1);
|
||||
}
|
||||
}
|
||||
else if (type === 'list') {
|
||||
this.assertArraysEqual(val1, val2, errorMessage, depth + 1);
|
||||
}
|
||||
else {
|
||||
this.assertEqual(val1, val2, errorMessage, depth + 1);
|
||||
}
|
||||
},
|
||||
|
||||
assertEqual: function(val1, val2, errorMessage, depth) {
|
||||
if (val1 !== val2) {
|
||||
var message = "'" + val1 + "' does not equal expected value '" + val2 + "'";
|
||||
let message = `'${val1}' does not equal expected value '${val2}'`;
|
||||
if (errorMessage) {
|
||||
message = errorMessage + ' - ' + message;
|
||||
message = `${errorMessage} - ${message}`;
|
||||
}
|
||||
throw new TestFailureError(message);
|
||||
throw new TestFailureError(message, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertNotEqual: function(val1, val2, errorMessage) {
|
||||
assertNotEqual: function(val1, val2, errorMessage, depth) {
|
||||
if (val1 === val2) {
|
||||
var message = "'" + val1 + "' equals '" + val2 + "'";
|
||||
let message = `'${val1}' equals '${val2}'`;
|
||||
if (errorMessage) {
|
||||
message = errorMessage + ' - ' + message;
|
||||
message = `${errorMessage} - ${message}`;
|
||||
}
|
||||
throw new TestFailureError(message);
|
||||
throw new TestFailureError(message, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertEqualWithTolerance: function(val1, val2, tolerance, errorMessage) {
|
||||
assertEqualWithTolerance: function(val1, val2, tolerance, errorMessage, depth) {
|
||||
if (val1 < val2 - tolerance || val1 > val2 + tolerance) {
|
||||
var message = "'" + val1 + "' does not equal '" + val2 + "' with tolerance '" + tolerance + "'";
|
||||
let message = `'${val1}' does not equal '${val2}' with tolerance '${tolerance}'`;
|
||||
if (errorMessage) {
|
||||
message = errorMessage + ' - ' + message;
|
||||
message = `${errorMessage} - ${message}`;
|
||||
}
|
||||
throw new TestFailureError(message);
|
||||
throw new TestFailureError(message, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertArray: function(value, length, errorMessage) {
|
||||
assertArray: function(value, length, errorMessage, depth) {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new TestFailureError(errorMessage || `Value ${value} is not an array`);
|
||||
throw new TestFailureError(errorMessage || `Value ${value} is not an array`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertArrayLength: function(value, length, errorMessage) {
|
||||
this.assertArray(value);
|
||||
assertArrayLength: function(value, length, errorMessage, depth) {
|
||||
this.assertArray(value, 1 + depth || 0);
|
||||
if (value.length !== length) {
|
||||
throw new TestFailureError(errorMessage || `Value ${value} is not an array of length ${length}`);
|
||||
throw new TestFailureError(errorMessage || `Value ${value} is not an array of length ${length}`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertArraysEqual: function(val1, val2, errorMessage) {
|
||||
var len1 = val1.length;
|
||||
var len2 = val2.length;
|
||||
var message;
|
||||
assertArraysEqual: function(val1, val2, errorMessage, depth) {
|
||||
this.assertDefined(val1, `val1 should be non-null but is ${val1}`, 1 + (depth || 0));
|
||||
this.assertDefined(val2, `val2 should be non-null but is ${val2}`, 1 + (depth || 0));
|
||||
const len1 = val1.length;
|
||||
const len2 = val2.length;
|
||||
|
||||
if (len1 !== len2) {
|
||||
message = 'Arrays have different lengths (' + len1 + ' != ' + len2 + ')';
|
||||
let message = `Arrays (${val1}) and (${val2}) have different lengths (${len1} != ${len2})`;
|
||||
if (errorMessage) {
|
||||
message = errorMessage + ' - ' + message;
|
||||
message = `${errorMessage} - ${message}`;
|
||||
}
|
||||
throw new TestFailureError(message);
|
||||
throw new TestFailureError(message, depth);
|
||||
}
|
||||
|
||||
for (var i = 0; i < len1; i++) {
|
||||
if (val1[i] !== val2[i]) {
|
||||
message = 'Array contents not equal at index ' + i + ' (' + val1[i] + ' != ' + val2[i] + ')';
|
||||
let compare;
|
||||
if (val1.type === "data") {
|
||||
compare = (i, a, b) => a === b || this.assertArraysEqual(new Uint8Array(a), b, `Data elements at index ${i}`, 1) || true;
|
||||
}
|
||||
else if (val1.type === "date") {
|
||||
compare = (i, a, b) => (a && a.getTime()) === (b && b.getTime());
|
||||
}
|
||||
else if (val1.type === "float" || val1.type === "double") {
|
||||
compare = (i, a, b) => a >= b - 0.000001 && a <= b + 0.000001;
|
||||
}
|
||||
else if (val1.type === 'object') {
|
||||
compare = (i, a, b) => Object.keys(a).every(key => a[key] === b[key]);
|
||||
}
|
||||
else {
|
||||
compare = (i, a, b) => a === b;
|
||||
}
|
||||
|
||||
for (let i = 0; i < len1; i++) {
|
||||
if (!compare(i, val1[i], val2[i])) {
|
||||
let message = `Array contents not equal at index ${i} (${val1[i]} != ${val2[i]})`;
|
||||
if (errorMessage) {
|
||||
message = errorMessage + ' - ' + message;
|
||||
message = `${errorMessage} - ${message}`;
|
||||
}
|
||||
throw new TestFailureError(message);
|
||||
throw new TestFailureError(message, depth);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
assertThrows: function(func, errorMessage) {
|
||||
var caught = false;
|
||||
assertThrows: function(func, errorMessage, depth) {
|
||||
let caught = false;
|
||||
try {
|
||||
func();
|
||||
}
|
||||
|
@ -96,22 +143,22 @@ module.exports = {
|
|||
}
|
||||
|
||||
if (!caught) {
|
||||
throw new TestFailureError(errorMessage || 'Expected exception not thrown');
|
||||
throw new TestFailureError(errorMessage || 'Expected exception not thrown', depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertThrowsException: function(func, expectedException) {
|
||||
var caught = false;
|
||||
let caught = false;
|
||||
try {
|
||||
func();
|
||||
}
|
||||
catch (e) {
|
||||
caught = true;
|
||||
if (e.name !== expectedException.name) {
|
||||
throw new TestFailureError('Expected a ' + expectedException.name + ' exception but caught a ' + e.name + ' instead. Message was: ' + e.message);
|
||||
throw new TestFailureError(`Expected a ${expectedException.name} exception but caught a ${e.name} instead. Message was: ${e.message}`);
|
||||
}
|
||||
if (e.message != expectedException.message) {
|
||||
throw new TestFailureError('Expected exception "' + expectedException + '" not thrown - instead caught: "' + e + '"');
|
||||
throw new TestFailureError(`Expected exception "${expectedException}" not thrown - instead caught: "${e}"`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,48 +167,60 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
|
||||
assertThrowsContaining: function(func, expectedMessage) {
|
||||
var caught = false;
|
||||
assertThrowsContaining: function(func, expectedMessage, depth) {
|
||||
let caught = false;
|
||||
try {
|
||||
func();
|
||||
}
|
||||
catch (e) {
|
||||
caught = true;
|
||||
if (!e.message.includes(expectedMessage)) {
|
||||
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`);
|
||||
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`, depth);
|
||||
}
|
||||
}
|
||||
|
||||
if (!caught) {
|
||||
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`);
|
||||
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertTrue: function(condition, errorMessage) {
|
||||
assertTrue: function(condition, errorMessage, depth) {
|
||||
if (!condition) {
|
||||
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be true`);
|
||||
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be true`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertInstanceOf: function(object, type, errorMessage) {
|
||||
assertFalse: function(condition, errorMessage, depth) {
|
||||
if (condition) {
|
||||
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be false`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertInstanceOf: function(object, type, errorMessage, depth) {
|
||||
if (!(object instanceof type)) {
|
||||
throw new TestFailureError(errorMessage || `Object ${object} expected to be of type ${type}`);
|
||||
throw new TestFailureError(errorMessage || `Object ${object} expected to be of type ${type}`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertType: function(value, type) {
|
||||
this.assertEqual(typeof value, type, `Value ${value} expected to be of type ${type}`);
|
||||
assertType: function(value, type, depth) {
|
||||
this.assertEqual(typeof value, type, `Value ${value} expected to be of type ${type}`, 1 + depth || 0);
|
||||
},
|
||||
|
||||
assertUndefined: function(value, errorMessage) {
|
||||
assertDefined: function(value, errorMessage, depth) {
|
||||
if (value === undefined || value === null) {
|
||||
throw new TestFailureError(errorMessage || `Value ${value} expected to be non-null`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertUndefined: function(value, errorMessage, depth) {
|
||||
if (value !== undefined) {
|
||||
throw new TestFailureError(errorMessage || `Value ${value} expected to be undefined`);
|
||||
throw new TestFailureError(errorMessage || `Value ${value} expected to be undefined`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
assertNull: function(value, errorMessage) {
|
||||
assertNull: function(value, errorMessage, depth) {
|
||||
if (value !== null) {
|
||||
throw new TestFailureError(errorMessage || `Value ${value} expected to be null`);
|
||||
throw new TestFailureError(errorMessage || `Value ${value} expected to be null`, depth);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -176,26 +235,28 @@ module.exports = {
|
|||
},
|
||||
};
|
||||
|
||||
function TestFailureError(message) {
|
||||
var error;
|
||||
function TestFailureError(message, depth) {
|
||||
let error;
|
||||
try {
|
||||
throw new Error(message);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
depth = 2 + (depth || 0);
|
||||
|
||||
// This regular expression will match stack trace lines provided by JavaScriptCore.
|
||||
// Example: someMethod@file:///path/to/file.js:10:24
|
||||
var regex = /^(?:.*?@)?([^\[\(].+?):(\d+)(?::(\d+))?\s*$/;
|
||||
const regex = /^(?:.*?@)?([^\[\(].+?):(\d+)(?::(\d+))?\s*$/;
|
||||
|
||||
// Remove the top two stack frames and use information from the third, if possible.
|
||||
var stack = error.stack && error.stack.split('\n');
|
||||
var match = stack[2] && stack[2].match(regex);
|
||||
const stack = error.stack && error.stack.split('\n');
|
||||
const match = stack[depth] && stack[depth].match(regex);
|
||||
if (match) {
|
||||
this.sourceURL = match[1];
|
||||
this.line = +match[2];
|
||||
this.column = +match[3];
|
||||
this.stack = stack.slice(2).join('\n');
|
||||
this.stack = stack.slice(depth).join('\n');
|
||||
}
|
||||
|
||||
this.__proto__ = error;
|
||||
|
|
|
@ -18,34 +18,78 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
var Realm = require('realm');
|
||||
var TestCase = require('./asserts');
|
||||
var schemas = require('./schemas');
|
||||
const Realm = require('realm');
|
||||
let TestCase = require('./asserts');
|
||||
let schemas = require('./schemas');
|
||||
|
||||
const DATA1 = new Uint8Array([0x01]);
|
||||
const DATA2 = new Uint8Array([0x02]);
|
||||
const DATA3 = new Uint8Array([0x03]);
|
||||
const DATE1 = new Date(1);
|
||||
const DATE2 = new Date(2);
|
||||
const DATE3 = new Date(3);
|
||||
|
||||
module.exports = {
|
||||
testListConstructor: function() {
|
||||
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
|
||||
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('PersonList', {list: []});
|
||||
TestCase.assertTrue(obj.list instanceof Realm.List);
|
||||
TestCase.assertTrue(obj.list instanceof Realm.Collection);
|
||||
realm.write(() => {
|
||||
let obj = realm.create('PersonList', {list: []});
|
||||
TestCase.assertInstanceOf(obj.list, Realm.List);
|
||||
TestCase.assertInstanceOf(obj.list, Realm.Collection);
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
new Realm.List();
|
||||
TestCase.assertThrowsContaining(() => new Realm.List(), 'constructor');
|
||||
|
||||
TestCase.assertType(Realm.List, 'function');
|
||||
TestCase.assertInstanceOf(Realm.List, Function);
|
||||
},
|
||||
|
||||
testListType: function() {
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject, schemas.PrimitiveArrays]});
|
||||
|
||||
let obj, prim;
|
||||
realm.write(() => {
|
||||
obj = realm.create('LinkTypesObject', {});
|
||||
prim = realm.create('PrimitiveArrays', {});
|
||||
});
|
||||
|
||||
TestCase.assertEqual(typeof Realm.List, 'function');
|
||||
TestCase.assertTrue(Realm.List instanceof Function);
|
||||
TestCase.assertEqual(obj.arrayCol.type, 'object');
|
||||
TestCase.assertEqual(obj.arrayCol1.type, 'object');
|
||||
|
||||
TestCase.assertEqual(prim.bool.type, 'bool');
|
||||
TestCase.assertEqual(prim.int.type, 'int');
|
||||
TestCase.assertEqual(prim.float.type, 'float');
|
||||
TestCase.assertEqual(prim.double.type, 'double');
|
||||
TestCase.assertEqual(prim.string.type, 'string');
|
||||
TestCase.assertEqual(prim.date.type, 'date');
|
||||
TestCase.assertEqual(prim.optBool.type, 'bool');
|
||||
TestCase.assertEqual(prim.optInt.type, 'int');
|
||||
TestCase.assertEqual(prim.optFloat.type, 'float');
|
||||
TestCase.assertEqual(prim.optDouble.type, 'double');
|
||||
TestCase.assertEqual(prim.optString.type, 'string');
|
||||
TestCase.assertEqual(prim.optDate.type, 'date');
|
||||
|
||||
TestCase.assertFalse(prim.bool.optional);
|
||||
TestCase.assertFalse(prim.int.optional);
|
||||
TestCase.assertFalse(prim.float.optional);
|
||||
TestCase.assertFalse(prim.double.optional);
|
||||
TestCase.assertFalse(prim.string.optional);
|
||||
TestCase.assertFalse(prim.date.optional);
|
||||
TestCase.assertTrue(prim.optBool.optional);
|
||||
TestCase.assertTrue(prim.optInt.optional);
|
||||
TestCase.assertTrue(prim.optFloat.optional);
|
||||
TestCase.assertTrue(prim.optDouble.optional);
|
||||
TestCase.assertTrue(prim.optString.optional);
|
||||
TestCase.assertTrue(prim.optDate.optional);
|
||||
},
|
||||
|
||||
testListLength: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}],
|
||||
|
@ -60,94 +104,286 @@ module.exports = {
|
|||
obj.arrayCol = [{doubleCol: 1}, {doubleCol: 2}];
|
||||
TestCase.assertEqual(array.length, 2);
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array.length = 0;
|
||||
}, 'cannot set length property on lists');
|
||||
TestCase.assertThrowsContaining(() => array.length = 0,
|
||||
"Cannot assign to read only property 'length'");
|
||||
});
|
||||
|
||||
TestCase.assertEqual(array.length, 2);
|
||||
},
|
||||
|
||||
testListSubscriptGetters: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject, schemas.PrimitiveArrays]});
|
||||
let obj, prim;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
|
||||
arrayCol1: [{doubleCol: 5}, {doubleCol: 6}],
|
||||
});
|
||||
prim = realm.create('PrimitiveArrays', {
|
||||
bool: [true, false],
|
||||
int: [1, 2],
|
||||
float: [1.1, 2.2],
|
||||
double: [1.11, 2.22],
|
||||
string: ['a', 'b'],
|
||||
date: [new Date(1), new Date(2)],
|
||||
data: [DATA1, DATA2],
|
||||
|
||||
array = obj.arrayCol;
|
||||
optBool: [true, null],
|
||||
optInt: [1, null],
|
||||
optFloat: [1.1, null],
|
||||
optDouble: [1.11, null],
|
||||
optString: ['a', null],
|
||||
optDate: [new Date(1), null],
|
||||
optData: [DATA1, null],
|
||||
});
|
||||
});
|
||||
|
||||
TestCase.assertEqual(array[0].doubleCol, 3);
|
||||
TestCase.assertEqual(array[1].doubleCol, 4);
|
||||
TestCase.assertEqual(array[2], undefined);
|
||||
TestCase.assertEqual(array[-1], undefined);
|
||||
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 3);
|
||||
TestCase.assertEqual(obj.arrayCol[1].doubleCol, 4);
|
||||
TestCase.assertEqual(obj.arrayCol[2], undefined);
|
||||
TestCase.assertEqual(obj.arrayCol[-1], undefined);
|
||||
TestCase.assertEqual(obj.arrayCol['foo'], undefined);
|
||||
|
||||
TestCase.assertEqual(obj.arrayCol1[0].doubleCol, 5);
|
||||
TestCase.assertEqual(obj.arrayCol1[1].doubleCol, 6);
|
||||
TestCase.assertEqual(obj.arrayCol1[2], undefined);
|
||||
TestCase.assertEqual(obj.arrayCol1[-1], undefined);
|
||||
TestCase.assertEqual(obj.arrayCol1['foo'], undefined);
|
||||
|
||||
for (let field of Object.keys(prim)) {
|
||||
TestCase.assertEqual(prim[field][2], undefined);
|
||||
TestCase.assertEqual(prim[field][-1], undefined);
|
||||
TestCase.assertEqual(prim[field]['foo'], undefined);
|
||||
if (field.includes('opt')) {
|
||||
TestCase.assertEqual(prim[field][1], null);
|
||||
}
|
||||
}
|
||||
|
||||
TestCase.assertSimilar('bool', prim.bool[0], true);
|
||||
TestCase.assertSimilar('bool', prim.bool[1], false);
|
||||
TestCase.assertSimilar('int', prim.int[0], 1);
|
||||
TestCase.assertSimilar('int', prim.int[1], 2);
|
||||
TestCase.assertSimilar('float', prim.float[0], 1.1);
|
||||
TestCase.assertSimilar('float', prim.float[1], 2.2);
|
||||
TestCase.assertSimilar('double', prim.double[0], 1.11);
|
||||
TestCase.assertSimilar('double', prim.double[1], 2.22);
|
||||
TestCase.assertSimilar('string', prim.string[0], 'a');
|
||||
TestCase.assertSimilar('string', prim.string[1], 'b');
|
||||
TestCase.assertSimilar('data', prim.data[0], DATA1);
|
||||
TestCase.assertSimilar('data', prim.data[1], DATA2);
|
||||
TestCase.assertSimilar('date', prim.date[0], new Date(1));
|
||||
TestCase.assertSimilar('date', prim.date[1], new Date(2));
|
||||
|
||||
TestCase.assertSimilar('bool', prim.optBool[0], true);
|
||||
TestCase.assertSimilar('int', prim.optInt[0], 1);
|
||||
TestCase.assertSimilar('float', prim.optFloat[0], 1.1);
|
||||
TestCase.assertSimilar('double', prim.optDouble[0], 1.11);
|
||||
TestCase.assertSimilar('string', prim.optString[0], 'a');
|
||||
TestCase.assertSimilar('data', prim.optData[0], DATA1);
|
||||
TestCase.assertSimilar('date', prim.optDate[0], new Date(1));
|
||||
},
|
||||
|
||||
testListSubscriptSetters: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject,
|
||||
schemas.PrimitiveArrays]});
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
|
||||
});
|
||||
|
||||
let prim = realm.create('PrimitiveArrays', {});
|
||||
array = obj.arrayCol;
|
||||
|
||||
array[0] = {doubleCol: 5};
|
||||
array[1] = {doubleCol: 6};
|
||||
|
||||
TestCase.assertEqual(array[0].doubleCol, 5);
|
||||
TestCase.assertEqual(array[1].doubleCol, 6);
|
||||
|
||||
array[0] = obj.objectCol;
|
||||
array[1] = obj.objectCol1;
|
||||
|
||||
TestCase.assertEqual(array[0].doubleCol, 1);
|
||||
TestCase.assertEqual(array[1].doubleCol, 2);
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array[2] = {doubleCol: 1};
|
||||
}, 'cannot set list item beyond its bounds');
|
||||
TestCase.assertThrowsContaining(() => array[0] = null,
|
||||
"JS value must be of type 'object', got (null)");
|
||||
TestCase.assertThrowsContaining(() => array[0] = {},
|
||||
"Missing value for property 'TestObject.doubleCol'");
|
||||
TestCase.assertThrowsContaining(() => array[0] = {foo: 'bar'},
|
||||
"Missing value for property 'TestObject.doubleCol'");
|
||||
TestCase.assertThrowsContaining(() => array[0] = prim,
|
||||
"Object of type (PrimitiveArrays) does not match List type (TestObject)");
|
||||
TestCase.assertThrowsContaining(() => array[0] = array,
|
||||
"Missing value for property 'TestObject.doubleCol'");
|
||||
TestCase.assertThrowsContaining(() => array[2] = {doubleCol: 1},
|
||||
"Requested index 2 greater than max 1");
|
||||
TestCase.assertThrowsContaining(() => array[-1] = {doubleCol: 1},
|
||||
"Index -1 cannot be less than zero.");
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array[-1] = {doubleCol: 1};
|
||||
}, 'cannot set list item with negative index');
|
||||
array['foo'] = 'bar';
|
||||
TestCase.assertEqual(array.foo, 'bar');
|
||||
|
||||
function testAssign(name, v1, v2) {
|
||||
prim[name].push(v1);
|
||||
TestCase.assertSimilar(prim[name].type, prim[name][0], v1, undefined, 1);
|
||||
prim[name][0] = v2;
|
||||
TestCase.assertSimilar(prim[name].type, prim[name][0], v2, undefined, 1);
|
||||
}
|
||||
|
||||
testAssign('bool', true, false);
|
||||
testAssign('int', 1, 2);
|
||||
testAssign('float', 1.1, 2.2);
|
||||
testAssign('double', 1.1, 2.2);
|
||||
testAssign('string', 'a', 'b');
|
||||
testAssign('data', DATA1, DATA2);
|
||||
testAssign('date', DATE1, DATE2);
|
||||
|
||||
function testAssignNull(name, expected) {
|
||||
TestCase.assertThrowsContaining(() => prim[name][0] = null,
|
||||
`Property must be of type '${expected}', got (null)`,
|
||||
undefined, 1);
|
||||
}
|
||||
|
||||
testAssignNull('bool', 'bool');
|
||||
testAssignNull('int', 'int');
|
||||
testAssignNull('float', 'float');
|
||||
testAssignNull('double', 'double');
|
||||
testAssignNull('string', 'string');
|
||||
testAssignNull('data', 'data');
|
||||
testAssignNull('date', 'date');
|
||||
|
||||
testAssign('optBool', true, null);
|
||||
testAssign('optInt', 1, null);
|
||||
testAssign('optFloat', 1.1, null);
|
||||
testAssign('optDouble', 1.1, null);
|
||||
testAssign('optString', 'a', null);
|
||||
testAssign('optData', DATA1, null);
|
||||
testAssign('optDate', DATE1, null);
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array[0] = {doubleCol: 1};
|
||||
}, 'cannot set list item outside write transaction');
|
||||
TestCase.assertThrowsContaining(() => array[0] = {doubleCol: 1},
|
||||
"Cannot modify managed objects outside of a write transaction.");
|
||||
|
||||
array['foo'] = 'baz';
|
||||
TestCase.assertEqual(array.foo, 'baz');
|
||||
},
|
||||
|
||||
testListInvalidProperty: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
testListAssignment: function() {
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject,
|
||||
schemas.PersonList, schemas.PersonObject,
|
||||
schemas.PrimitiveArrays]});
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
|
||||
});
|
||||
let obj, prim;
|
||||
realm.write(() => {
|
||||
obj = realm.create('LinkTypesObject', {});
|
||||
prim = realm.create('PrimitiveArrays', {});
|
||||
let person = realm.create('PersonObject', {name: 'a', age: 2.0});
|
||||
let personList = realm.create('PersonList', {list: [person]}).list;
|
||||
|
||||
array = obj.arrayCol;
|
||||
TestCase.assertThrowsContaining(() => obj.arrayCol = [0],
|
||||
"JS value must be of type 'object', got (0)");
|
||||
TestCase.assertThrowsContaining(() => obj.arrayCol = [null],
|
||||
"JS value must be of type 'object', got (null)");
|
||||
TestCase.assertThrowsContaining(() => obj.arrayCol = [person],
|
||||
"Object of type (PersonObject) does not match List type (TestObject)");
|
||||
TestCase.assertThrowsContaining(() => obj.arrayCol = personList,
|
||||
"LinkTypesObject.arrayCol must be of type 'TestObject[]', got 'object' (");
|
||||
obj.arrayCol = [realm.create('TestObject', {doubleCol: 1.0})]
|
||||
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 1.0);
|
||||
obj.arrayCol = obj.arrayCol;
|
||||
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 1.0);
|
||||
|
||||
TestCase.assertThrowsContaining(() => prim.bool = [person],
|
||||
"PrimitiveArrays.bool must be of type 'boolean[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.int = [person],
|
||||
"PrimitiveArrays.int must be of type 'number[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.float = [person],
|
||||
"PrimitiveArrays.float must be of type 'number[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.double = [person],
|
||||
"PrimitiveArrays.double must be of type 'number[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.string = [person],
|
||||
"PrimitiveArrays.string must be of type 'string[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.data = [person],
|
||||
"PrimitiveArrays.data must be of type 'binary[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.date = [person],
|
||||
"PrimitiveArrays.date must be of type 'date[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.optBool = [person],
|
||||
"PrimitiveArrays.optBool must be of type 'boolean?[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.optInt = [person],
|
||||
"PrimitiveArrays.optInt must be of type 'number?[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.optFloat = [person],
|
||||
"PrimitiveArrays.optFloat must be of type 'number?[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.optDouble = [person],
|
||||
"PrimitiveArrays.optDouble must be of type 'number?[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.optString = [person],
|
||||
"PrimitiveArrays.optString must be of type 'string?[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.optData = [person],
|
||||
"PrimitiveArrays.optData must be of type 'binary?[]', got 'object' ([PersonObject{");
|
||||
TestCase.assertThrowsContaining(() => prim.optDate = [person],
|
||||
"PrimitiveArrays.optDate must be of type 'date?[]', got 'object' ([PersonObject{");
|
||||
|
||||
function testAssign(name, value) {
|
||||
prim[name] = [value];
|
||||
TestCase.assertSimilar(prim[name].type, prim[name][0], value, undefined, 1);
|
||||
}
|
||||
|
||||
testAssign('bool', true);
|
||||
testAssign('int', 1);
|
||||
testAssign('float', 1.1);
|
||||
testAssign('double', 1.1);
|
||||
testAssign('string', 'a');
|
||||
testAssign('data', DATA1);
|
||||
testAssign('date', DATE1);
|
||||
|
||||
function testAssignNull(name, expected) {
|
||||
TestCase.assertThrowsContaining(() => prim[name] = [null],
|
||||
`PrimitiveArrays.${name} must be of type '${expected}[]', got 'object' ([null])`,
|
||||
undefined, 1);
|
||||
TestCase.assertEqual(prim[name].length, 1,
|
||||
"List should not have been cleared by invalid assignment", 1);
|
||||
}
|
||||
|
||||
testAssignNull('bool', 'boolean');
|
||||
testAssignNull('int', 'number');
|
||||
testAssignNull('float', 'number');
|
||||
testAssignNull('double', 'number');
|
||||
testAssignNull('string', 'string');
|
||||
testAssignNull('data', 'binary');
|
||||
testAssignNull('date', 'date');
|
||||
|
||||
testAssign('optBool', true);
|
||||
testAssign('optInt', 1);
|
||||
testAssign('optFloat', 1.1);
|
||||
testAssign('optDouble', 1.1);
|
||||
testAssign('optString', 'a');
|
||||
testAssign('optData', DATA1);
|
||||
testAssign('optDate', DATE1);
|
||||
|
||||
testAssign('optBool', null);
|
||||
testAssign('optInt', null);
|
||||
testAssign('optFloat', null);
|
||||
testAssign('optDouble', null);
|
||||
testAssign('optString', null);
|
||||
testAssign('optData', null);
|
||||
testAssign('optDate', null);
|
||||
});
|
||||
|
||||
TestCase.assertEqual(undefined, array.ablasdf);
|
||||
TestCase.assertThrowsContaining(() => obj.arrayCol = [],
|
||||
"Cannot modify managed objects outside of a write transaction.");
|
||||
TestCase.assertThrowsContaining(() => prim.bool = [],
|
||||
"Cannot modify managed objects outside of a write transaction.");
|
||||
},
|
||||
|
||||
testListEnumerate: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var obj;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let obj;
|
||||
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
|
@ -155,19 +391,18 @@ module.exports = {
|
|||
});
|
||||
});
|
||||
|
||||
var index;
|
||||
for (index in obj.arrayCol) {
|
||||
for (const index in obj.arrayCol) {
|
||||
TestCase.assertTrue(false, "No objects should have been enumerated: " + index);
|
||||
}
|
||||
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
obj.arrayCol = [{doubleCol: 0}, {doubleCol: 1}];
|
||||
TestCase.assertEqual(obj.arrayCol.length, 2);
|
||||
});
|
||||
TestCase.assertEqual(obj.arrayCol.length, 2);
|
||||
|
||||
var count = 0;
|
||||
var keys = Object.keys(obj.arrayCol);
|
||||
for (index in obj.arrayCol) {
|
||||
let count = 0;
|
||||
let keys = Object.keys(obj.arrayCol);
|
||||
for (const index in obj.arrayCol) {
|
||||
TestCase.assertEqual(count++, +index);
|
||||
TestCase.assertEqual(keys[index], index);
|
||||
}
|
||||
|
@ -177,11 +412,11 @@ module.exports = {
|
|||
},
|
||||
|
||||
testListPush: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}],
|
||||
|
@ -199,23 +434,22 @@ module.exports = {
|
|||
TestCase.assertEqual(array[2].doubleCol, 1);
|
||||
TestCase.assertEqual(array[3].doubleCol, 2);
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array.push();
|
||||
});
|
||||
TestCase.assertEqual(array.push(), 4);
|
||||
TestCase.assertEqual(array.length, 4);
|
||||
});
|
||||
|
||||
TestCase.assertEqual(array.length, 4);
|
||||
TestCase.assertThrows(function() {
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
array.push([1]);
|
||||
}, 'can only push in a write transaction');
|
||||
}, "Cannot modify managed objects outside of a write transaction.");
|
||||
},
|
||||
|
||||
testListPop: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
|
||||
|
@ -228,22 +462,19 @@ module.exports = {
|
|||
|
||||
TestCase.assertEqual(array.pop(), undefined);
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array.pop(1);
|
||||
});
|
||||
TestCase.assertThrowsContaining(() => array.pop(1), 'Invalid argument');
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array.pop();
|
||||
}, 'can only pop in a write transaction');
|
||||
TestCase.assertThrowsContaining(() => array.pop(),
|
||||
"Cannot modify managed objects outside of a write transaction.");
|
||||
},
|
||||
|
||||
testListUnshift: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}],
|
||||
|
@ -260,20 +491,22 @@ module.exports = {
|
|||
TestCase.assertEqual(array.length, 4);
|
||||
TestCase.assertEqual(array[0].doubleCol, 1);
|
||||
TestCase.assertEqual(array[1].doubleCol, 2);
|
||||
|
||||
TestCase.assertEqual(array.unshift(), 4);
|
||||
TestCase.assertEqual(array.length, 4);
|
||||
});
|
||||
|
||||
TestCase.assertEqual(array.length, 4);
|
||||
TestCase.assertThrows(function() {
|
||||
array.unshift({doubleCol: 1});
|
||||
}, 'can only unshift in a write transaction');
|
||||
TestCase.assertThrowsContaining(() => array.unshift({doubleCol: 1}),
|
||||
'Cannot modify managed objects outside of a write transaction.');
|
||||
},
|
||||
|
||||
testListShift: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
|
||||
|
@ -286,29 +519,27 @@ module.exports = {
|
|||
|
||||
TestCase.assertEqual(array.shift(), undefined);
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array.shift(1);
|
||||
});
|
||||
TestCase.assertThrowsContaining(() => array.shift(1), 'Invalid argument');
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
array.shift();
|
||||
}, 'can only shift in a write transaction');
|
||||
}, "Cannot modify managed objects outside of a write transaction.");
|
||||
},
|
||||
|
||||
testListSplice: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
|
||||
});
|
||||
|
||||
array = obj.arrayCol;
|
||||
var removed;
|
||||
let removed;
|
||||
|
||||
removed = array.splice(0, 0, obj.objectCol, obj.objectCol1);
|
||||
TestCase.assertEqual(removed.length, 0);
|
||||
|
@ -355,26 +586,26 @@ module.exports = {
|
|||
TestCase.assertEqual(removed.length, 1);
|
||||
TestCase.assertEqual(array.length, 0);
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
array.splice('cat', 1);
|
||||
});
|
||||
}, "Value 'cat' not convertible to a number");
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
array.splice(0, 0, 0);
|
||||
});
|
||||
}, "JS value must be of type 'object', got (0)");
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
array.splice(0, 0, {doubleCol: 1});
|
||||
}, 'can only splice in a write transaction');
|
||||
}, "Cannot modify managed objects outside of a write transaction");
|
||||
},
|
||||
|
||||
testListDeletions: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var object;
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let object;
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
object = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
|
@ -385,7 +616,7 @@ module.exports = {
|
|||
});
|
||||
|
||||
try {
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
realm.delete(array[0]);
|
||||
TestCase.assertEqual(array.length, 1);
|
||||
TestCase.assertEqual(array[0].doubleCol, 4);
|
||||
|
@ -398,22 +629,20 @@ module.exports = {
|
|||
TestCase.assertEqual(array.length, 2);
|
||||
TestCase.assertEqual(array[0].doubleCol, 3);
|
||||
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
realm.delete(object);
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
array[0];
|
||||
});
|
||||
TestCase.assertThrowsContaining(() => array[0], 'invalidated');
|
||||
},
|
||||
|
||||
testLiveUpdatingResults: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var objects = realm.objects('TestObject');
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let objects = realm.objects('TestObject');
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', {
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', {
|
||||
objectCol: {doubleCol: 1},
|
||||
objectCol1: {doubleCol: 2},
|
||||
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
|
||||
|
@ -426,7 +655,7 @@ module.exports = {
|
|||
TestCase.assertEqual(objects.length, 4);
|
||||
|
||||
try {
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
array.push({doubleCol: 5});
|
||||
TestCase.assertEqual(objects.length, 5);
|
||||
|
||||
|
@ -449,50 +678,50 @@ module.exports = {
|
|||
},
|
||||
|
||||
testListSnapshot: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var objects = realm.objects('TestObject');
|
||||
var array;
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
let objects = realm.objects('TestObject');
|
||||
let array;
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]);
|
||||
realm.write(() => {
|
||||
let obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]], [[5], [6]]]);
|
||||
array = obj.arrayCol;
|
||||
});
|
||||
|
||||
var objectsCopy = objects.snapshot();
|
||||
var arrayCopy = array.snapshot();
|
||||
let objectsCopy = objects.snapshot();
|
||||
let arrayCopy = array.snapshot();
|
||||
|
||||
TestCase.assertEqual(objectsCopy.length, 4);
|
||||
TestCase.assertEqual(objectsCopy.length, 6);
|
||||
TestCase.assertEqual(arrayCopy.length, 2);
|
||||
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
array.push([5]);
|
||||
TestCase.assertEqual(objectsCopy.length, 4);
|
||||
TestCase.assertEqual(objectsCopy.length, 6);
|
||||
TestCase.assertEqual(arrayCopy.length, 2);
|
||||
|
||||
TestCase.assertEqual(objectsCopy.snapshot().length, 4);
|
||||
TestCase.assertEqual(objectsCopy.snapshot().length, 6);
|
||||
TestCase.assertEqual(arrayCopy.snapshot().length, 2);
|
||||
|
||||
TestCase.assertEqual(objects.snapshot().length, 5);
|
||||
TestCase.assertEqual(objects.snapshot().length, 7);
|
||||
TestCase.assertEqual(array.snapshot().length, 3);
|
||||
|
||||
realm.delete(array[0]);
|
||||
TestCase.assertEqual(objectsCopy.length, 4);
|
||||
TestCase.assertEqual(objectsCopy.length, 6);
|
||||
TestCase.assertEqual(arrayCopy.length, 2);
|
||||
TestCase.assertEqual(arrayCopy[0], null);
|
||||
|
||||
realm.deleteAll();
|
||||
TestCase.assertEqual(objectsCopy.length, 4);
|
||||
TestCase.assertEqual(objectsCopy.length, 6);
|
||||
TestCase.assertEqual(arrayCopy.length, 2);
|
||||
TestCase.assertEqual(arrayCopy[1], null);
|
||||
});
|
||||
},
|
||||
|
||||
testListFiltered: function() {
|
||||
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
|
||||
var list;
|
||||
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
|
||||
let list;
|
||||
|
||||
realm.write(function() {
|
||||
var object = realm.create('PersonList', {list: [
|
||||
realm.write(() => {
|
||||
let object = realm.create('PersonList', {list: [
|
||||
{name: 'Ari', age: 10},
|
||||
{name: 'Tim', age: 11},
|
||||
{name: 'Bjarne', age: 12},
|
||||
|
@ -515,10 +744,11 @@ module.exports = {
|
|||
{name: 'Target', properties: {value: 'int'}},
|
||||
{name: 'Mid', properties: {value: 'int', link: 'Target'}},
|
||||
{name: 'List', properties: {list: {type: 'list', objectType: 'Mid'}}},
|
||||
schemas.PrimitiveArrays
|
||||
];
|
||||
const realm = new Realm({schema: schema});
|
||||
|
||||
let list;
|
||||
let list, prim;
|
||||
realm.write(() => {
|
||||
list = realm.create('List', {list: [
|
||||
{value: 3, link: {value: 1}},
|
||||
|
@ -528,13 +758,31 @@ module.exports = {
|
|||
realm.create('List', {list: [
|
||||
{value: 4, link: {value: 4}},
|
||||
]});
|
||||
prim = realm.create('PrimitiveArrays', {
|
||||
bool: [true, false],
|
||||
int: [3, 1, 2],
|
||||
float: [3, 1, 2],
|
||||
double: [3, 1, 2],
|
||||
string: ['c', 'a', 'b'],
|
||||
data: [DATA3, DATA1, DATA2],
|
||||
date: [DATE3, DATE1, DATE2],
|
||||
optBool: [true, false, null],
|
||||
optInt: [3, 1, 2, null],
|
||||
optFloat: [3, 1, 2, null],
|
||||
optDouble: [3, 1, 2, null],
|
||||
optString: ['c', 'a', 'b', null],
|
||||
optData: [DATA3, DATA1, DATA2, null],
|
||||
optDate: [DATE3, DATE1, DATE2, null],
|
||||
});
|
||||
});
|
||||
|
||||
const values = (results) => results.map((o) => o.value);
|
||||
|
||||
TestCase.assertThrows(() => list.sorted());
|
||||
TestCase.assertThrows(() => list.sorted('nonexistent property'));
|
||||
TestCase.assertThrows(() => list.sorted('link'));
|
||||
// TestCase.assertThrowsContaining(() => list.sorted());
|
||||
TestCase.assertThrowsContaining(() => list.sorted('nonexistent property'),
|
||||
"Cannot sort on key path 'nonexistent property': property 'Mid.nonexistent property' does not exist.");
|
||||
TestCase.assertThrowsContaining(() => list.sorted('link'),
|
||||
"Cannot sort on key path 'link': property 'Mid.link' of type 'object' cannot be the final property in the key path.");
|
||||
|
||||
TestCase.assertArraysEqual(values(list.sorted([])), [3, 1, 2]);
|
||||
|
||||
|
@ -551,24 +799,50 @@ module.exports = {
|
|||
TestCase.assertArraysEqual(values(list.sorted(['link.value'])), [3, 2, 1]);
|
||||
TestCase.assertArraysEqual(values(list.sorted([['link.value', false]])), [3, 2, 1]);
|
||||
TestCase.assertArraysEqual(values(list.sorted([['link.value', true]])), [1, 2, 3]);
|
||||
|
||||
TestCase.assertThrowsContaining(() => prim.int.sorted('value', true),
|
||||
"Cannot sort on key path 'value': arrays of 'int' can only be sorted on 'self'");
|
||||
TestCase.assertThrowsContaining(() => prim.int.sorted('!ARRAY_VALUE', true),
|
||||
"Cannot sort on key path '!ARRAY_VALUE': arrays of 'int' can only be sorted on 'self'");
|
||||
|
||||
TestCase.assertArraysEqual(prim.int.sorted([]), [3, 1, 2]);
|
||||
TestCase.assertArraysEqual(prim.int.sorted(), [1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.int.sorted(false), [1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.int.sorted(true), [3, 2, 1]);
|
||||
|
||||
TestCase.assertArraysEqual(prim.optInt.sorted([]), [3, 1, 2, null]);
|
||||
TestCase.assertArraysEqual(prim.optInt.sorted(), [null, 1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.optInt.sorted(false), [null, 1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.optInt.sorted(true), [3, 2, 1, null]);
|
||||
|
||||
TestCase.assertArraysEqual(prim.bool.sorted(), [false, true]);
|
||||
TestCase.assertArraysEqual(prim.float.sorted(), [1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.double.sorted(), [1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.string.sorted(), ['a', 'b', 'c']);
|
||||
TestCase.assertArraysEqual(prim.data.sorted(), [DATA1, DATA2, DATA3]);
|
||||
TestCase.assertArraysEqual(prim.date.sorted(), [DATE1, DATE2, DATE3]);
|
||||
TestCase.assertArraysEqual(prim.optBool.sorted(), [null, false, true]);
|
||||
TestCase.assertArraysEqual(prim.optFloat.sorted(), [null, 1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.optDouble.sorted(), [null, 1, 2, 3]);
|
||||
TestCase.assertArraysEqual(prim.optString.sorted(), [null, 'a', 'b', 'c']);
|
||||
TestCase.assertArraysEqual(prim.optData.sorted(), [null, DATA1, DATA2, DATA3]);
|
||||
TestCase.assertArraysEqual(prim.optDate.sorted(), [null, DATE1, DATE2, DATE3]);
|
||||
},
|
||||
|
||||
testArrayMethods: function() {
|
||||
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
|
||||
var object;
|
||||
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList, schemas.PrimitiveArrays]});
|
||||
let object, prim;
|
||||
|
||||
realm.write(function() {
|
||||
realm.write(() => {
|
||||
object = realm.create('PersonList', {list: [
|
||||
{name: 'Ari', age: 10},
|
||||
{name: 'Tim', age: 11},
|
||||
{name: 'Bjarne', age: 12},
|
||||
]});
|
||||
prim = realm.create('PrimitiveArrays', {int: [10, 11, 12]});
|
||||
});
|
||||
|
||||
[
|
||||
object.list,
|
||||
realm.objects('PersonObject'),
|
||||
].forEach(function(list) {
|
||||
for (const list of [object.list, realm.objects('PersonObject')]) {
|
||||
TestCase.assertEqual(list.slice().length, 3);
|
||||
TestCase.assertEqual(list.slice(-1).length, 1);
|
||||
TestCase.assertEqual(list.slice(-1)[0].age, 12);
|
||||
|
@ -581,43 +855,43 @@ module.exports = {
|
|||
TestCase.assertEqual(list.join(' '), 'Ari Tim Bjarne');
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
list.forEach(function(p, i) {
|
||||
let count = 0;
|
||||
list.forEach((p, i) => {
|
||||
TestCase.assertEqual(p.name, list[i].name);
|
||||
count++;
|
||||
});
|
||||
TestCase.assertEqual(count, list.length);
|
||||
|
||||
TestCase.assertArraysEqual(list.map(function(p) {return p.age}), [10, 11, 12]);
|
||||
TestCase.assertTrue(list.some(function(p) {return p.age > 10}));
|
||||
TestCase.assertTrue(list.every(function(p) {return p.age > 0}));
|
||||
TestCase.assertArraysEqual(list.map(p => p.age), [10, 11, 12]);
|
||||
TestCase.assertTrue(list.some(p => p.age > 10));
|
||||
TestCase.assertTrue(list.every(p => p.age > 0));
|
||||
|
||||
var person = list.find(function(p) {return p.name == 'Tim'});
|
||||
let person = list.find(p => p.name == 'Tim');
|
||||
TestCase.assertEqual(person.name, 'Tim');
|
||||
|
||||
var index = list.findIndex(function(p) {return p.name == 'Tim'});
|
||||
let index = list.findIndex(p => p.name == 'Tim');
|
||||
TestCase.assertEqual(index, 1);
|
||||
TestCase.assertEqual(list.indexOf(list[index]), index);
|
||||
|
||||
TestCase.assertEqual(list.reduce(function(n, p) {return n + p.age}, 0), 33);
|
||||
TestCase.assertEqual(list.reduceRight(function(n, p) {return n + p.age}, 0), 33);
|
||||
TestCase.assertEqual(list.reduce((n, p) => n + p.age, 0), 33);
|
||||
TestCase.assertEqual(list.reduceRight((n, p) => n + p.age, 0), 33);
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
var iteratorMethodNames = ['entries', 'keys', 'values'];
|
||||
let iteratorMethodNames = ['entries', 'keys', 'values'];
|
||||
|
||||
iteratorMethodNames.push(Symbol.iterator);
|
||||
|
||||
iteratorMethodNames.forEach(function(methodName) {
|
||||
var iterator = list[methodName]();
|
||||
var count = 0;
|
||||
var result;
|
||||
iteratorMethodNames.forEach(methodName => {
|
||||
let iterator = list[methodName]();
|
||||
let count = 0;
|
||||
let result;
|
||||
|
||||
// This iterator should itself be iterable.
|
||||
// TestCase.assertEqual(iterator[iteratorSymbol](), iterator);
|
||||
TestCase.assertEqual(iterator[Symbol.iterator](), iterator);
|
||||
|
||||
while ((result = iterator.next()) && !result.done) {
|
||||
var value = result.value;
|
||||
let value = result.value;
|
||||
|
||||
switch (methodName) {
|
||||
case 'entries':
|
||||
|
@ -640,14 +914,83 @@ module.exports = {
|
|||
TestCase.assertEqual(result.value, undefined);
|
||||
TestCase.assertEqual(count, list.length);
|
||||
});
|
||||
}
|
||||
|
||||
const list = prim.int;
|
||||
TestCase.assertEqual(list.slice().length, 3);
|
||||
TestCase.assertEqual(list.slice(-1).length, 1);
|
||||
TestCase.assertEqual(list.slice(-1)[0], 12);
|
||||
TestCase.assertEqual(list.slice(1, 3).length, 2);
|
||||
TestCase.assertEqual(list.slice(1, 3)[1], 12);
|
||||
|
||||
TestCase.assertEqual(list.join(' '), '10 11 12');
|
||||
|
||||
let count = 0;
|
||||
list.forEach((v, i) => {
|
||||
TestCase.assertEqual(v, i + 10);
|
||||
count++;
|
||||
});
|
||||
TestCase.assertEqual(count, list.length);
|
||||
|
||||
TestCase.assertArraysEqual(list.map(p => p + 1), [11, 12, 13]);
|
||||
TestCase.assertTrue(list.some(p => p > 10));
|
||||
TestCase.assertTrue(list.every(p => p > 0));
|
||||
|
||||
let value = list.find(p => p == 11);
|
||||
TestCase.assertEqual(value, 11)
|
||||
|
||||
let index = list.findIndex(p => p == 11);
|
||||
TestCase.assertEqual(index, 1);
|
||||
TestCase.assertEqual(list.indexOf(list[index]), index);
|
||||
|
||||
TestCase.assertEqual(list.reduce((n, p) => n + p, 0), 33);
|
||||
TestCase.assertEqual(list.reduceRight((n, p) => n + p, 0), 33);
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
let iteratorMethodNames = ['entries', 'keys', 'values'];
|
||||
|
||||
iteratorMethodNames.push(Symbol.iterator);
|
||||
|
||||
iteratorMethodNames.forEach(methodName => {
|
||||
let iterator = list[methodName]();
|
||||
let count = 0;
|
||||
let result;
|
||||
|
||||
// This iterator should itself be iterable.
|
||||
// TestCase.assertEqual(iterator[iteratorSymbol](), iterator);
|
||||
TestCase.assertEqual(iterator[Symbol.iterator](), iterator);
|
||||
|
||||
while ((result = iterator.next()) && !result.done) {
|
||||
let value = result.value;
|
||||
|
||||
switch (methodName) {
|
||||
case 'entries':
|
||||
TestCase.assertEqual(value.length, 2);
|
||||
TestCase.assertEqual(value[0], count);
|
||||
TestCase.assertEqual(value[1], list[count]);
|
||||
break;
|
||||
case 'keys':
|
||||
TestCase.assertEqual(value, count);
|
||||
break;
|
||||
default:
|
||||
TestCase.assertEqual(value.name, list[count].name);
|
||||
break;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
TestCase.assertEqual(result.done, true);
|
||||
TestCase.assertEqual(result.value, undefined);
|
||||
TestCase.assertEqual(count, list.length);
|
||||
});
|
||||
},
|
||||
|
||||
testIsValid: function() {
|
||||
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
|
||||
var object;
|
||||
var list;
|
||||
realm.write(function() {
|
||||
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
|
||||
let object;
|
||||
let list;
|
||||
realm.write(() => {
|
||||
object = realm.create('PersonList', {list: [
|
||||
{name: 'Ari', age: 10},
|
||||
{name: 'Tim', age: 11},
|
||||
|
@ -659,8 +1002,6 @@ module.exports = {
|
|||
});
|
||||
|
||||
TestCase.assertEqual(list.isValid(), false);
|
||||
TestCase.assertThrows(function() {
|
||||
list.length;
|
||||
});
|
||||
TestCase.assertThrowsContaining(() => list.length, 'invalidated');
|
||||
},
|
||||
};
|
||||
|
|
|
@ -88,8 +88,8 @@ module.exports = {
|
|||
renamed: 'string',
|
||||
prop1: 'int',
|
||||
}
|
||||
}],
|
||||
schemaVersion: 1,
|
||||
}],
|
||||
schemaVersion: 1,
|
||||
migration: function(oldRealm, newRealm) {
|
||||
var oldObjects = oldRealm.objects('TestObject');
|
||||
var newObjects = newRealm.objects('TestObject');
|
||||
|
@ -136,8 +136,8 @@ module.exports = {
|
|||
renamed: 'string',
|
||||
prop1: 'int',
|
||||
}
|
||||
}],
|
||||
schemaVersion: 1,
|
||||
}],
|
||||
schemaVersion: 1,
|
||||
migration: function(oldRealm, newRealm) {
|
||||
var oldSchema = oldRealm.schema;
|
||||
var newSchema = newRealm.schema;
|
||||
|
@ -324,4 +324,44 @@ module.exports = {
|
|||
|
||||
realm.close();
|
||||
},
|
||||
|
||||
testMigrateToListOfInts: function() {
|
||||
let realm = new Realm({schema: [{name: 'TestObject', properties: {values: 'IntObject[]'}},
|
||||
{name: 'IntObject', properties: {value: 'int'}}]});
|
||||
realm.write(function() {
|
||||
realm.create('TestObject', {values: [{value: 1}, {value: 2}, {value: 3}]});
|
||||
realm.create('TestObject', {values: [{value: 1}, {value: 4}, {value: 5}]});
|
||||
});
|
||||
realm.close();
|
||||
|
||||
realm = new Realm({
|
||||
schema: [{name: 'TestObject', properties: {values: 'int[]'}}],
|
||||
schemaVersion: 1,
|
||||
migration: function(oldRealm, newRealm) {
|
||||
const oldObjects = oldRealm.objects('TestObject');
|
||||
const newObjects = newRealm.objects('TestObject');
|
||||
TestCase.assertEqual(oldObjects.length, 2);
|
||||
TestCase.assertEqual(newObjects.length, 2);
|
||||
|
||||
for (let i = 0; i < oldObjects.length; ++i) {
|
||||
TestCase.assertEqual(oldObjects[i].values.length, 3);
|
||||
TestCase.assertEqual(newObjects[i].values.length, 0);
|
||||
newObjects[i].values = oldObjects[i].values.map(o => o.value);
|
||||
TestCase.assertEqual(newObjects[i].values.length, 3);
|
||||
}
|
||||
newRealm.deleteModel('IntObject');
|
||||
}
|
||||
});
|
||||
|
||||
const objects = realm.objects('TestObject');
|
||||
TestCase.assertEqual(objects.length, 2);
|
||||
TestCase.assertEqual(objects[0].values.length, 3);
|
||||
TestCase.assertEqual(objects[1].values.length, 3);
|
||||
TestCase.assertEqual(objects[0].values[0], 1);
|
||||
TestCase.assertEqual(objects[0].values[1], 2);
|
||||
TestCase.assertEqual(objects[0].values[2], 3);
|
||||
TestCase.assertEqual(objects[1].values[0], 1);
|
||||
TestCase.assertEqual(objects[1].values[1], 4);
|
||||
TestCase.assertEqual(objects[1].values[2], 5);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -18,238 +18,158 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
var Realm = require('realm');
|
||||
var TestCase = require('./asserts');
|
||||
var schemas = require('./schemas');
|
||||
const Realm = require('realm');
|
||||
const TestCase = require('./asserts');
|
||||
const schemas = require('./schemas');
|
||||
|
||||
var RANDOM_DATA = new Uint8Array([
|
||||
const RANDOM_DATA = new Uint8Array([
|
||||
0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9,
|
||||
0x67, 0x1e, 0x40, 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff,
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
testBasicTypesPropertyGetters: function() {
|
||||
var realm = new Realm({schema: [schemas.BasicTypes]});
|
||||
var object;
|
||||
const allTypesValues = {
|
||||
boolCol: true,
|
||||
intCol: 1,
|
||||
floatCol: 1.1,
|
||||
doubleCol: 1.11,
|
||||
stringCol: 'string',
|
||||
dateCol: new Date(1),
|
||||
dataCol: RANDOM_DATA,
|
||||
objectCol: {doubleCol: 2.2},
|
||||
|
||||
var basicTypesValues = {
|
||||
boolCol: true,
|
||||
intCol: 1,
|
||||
floatCol: 1.1,
|
||||
doubleCol: 1.11,
|
||||
stringCol: 'string',
|
||||
dateCol: new Date(1),
|
||||
dataCol: RANDOM_DATA,
|
||||
};
|
||||
optBoolCol: true,
|
||||
optIntCol: 1,
|
||||
optFloatCol: 1.1,
|
||||
optDoubleCol: 1.11,
|
||||
optStringCol: 'string',
|
||||
optDateCol: new Date(1),
|
||||
optDataCol: RANDOM_DATA,
|
||||
|
||||
boolArrayCol: [true],
|
||||
intArrayCol: [1],
|
||||
floatArrayCol: [1.1],
|
||||
doubleArrayCol: [1.11],
|
||||
stringArrayCol: ['string'],
|
||||
dateArrayCol: [new Date(1)],
|
||||
dataArrayCol: [RANDOM_DATA],
|
||||
objectArrayCol: [{doubleCol: 2.2}],
|
||||
|
||||
optBoolArrayCol: [true],
|
||||
optIntArrayCol: [1],
|
||||
optFloatArrayCol: [1.1],
|
||||
optDoubleArrayCol: [1.11],
|
||||
optStringArrayCol: ['string'],
|
||||
optDateArrayCol: [new Date(1)],
|
||||
optDataArrayCol: [RANDOM_DATA],
|
||||
};
|
||||
const nullPropertyValues = (() => {
|
||||
let values = {}
|
||||
for (let name in allTypesValues) {
|
||||
if (name.includes('opt')) {
|
||||
values[name] = name.includes('Array') ? [null] : null;
|
||||
}
|
||||
else {
|
||||
values[name] = allTypesValues[name];
|
||||
}
|
||||
}
|
||||
return values;
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
testAllPropertyGetters: function() {
|
||||
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
|
||||
let object, nullObject;
|
||||
|
||||
realm.write(function() {
|
||||
object = realm.create('BasicTypesObject', basicTypesValues);
|
||||
object = realm.create('AllTypesObject', allTypesValues);
|
||||
nullObject = realm.create('AllTypesObject', nullPropertyValues);
|
||||
});
|
||||
|
||||
for (var name in schemas.BasicTypes.properties) {
|
||||
var prop = schemas.BasicTypes.properties[name];
|
||||
var type = typeof prop == 'object' ? prop.type : prop;
|
||||
const objectSchema = realm.schema[0];
|
||||
for (const name of Object.keys(objectSchema.properties)) {
|
||||
const type = objectSchema.properties[name].type;
|
||||
if (type === 'linkingObjects') {
|
||||
TestCase.assertEqual(object[name].length, 0);
|
||||
TestCase.assertEqual(nullObject[name].length, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == 'float' || type == 'double') {
|
||||
TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001);
|
||||
}
|
||||
else if (type == 'data') {
|
||||
TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA);
|
||||
}
|
||||
else if (type == 'date') {
|
||||
TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime());
|
||||
}
|
||||
else {
|
||||
TestCase.assertEqual(object[name], basicTypesValues[name]);
|
||||
}
|
||||
TestCase.assertSimilar(type, object[name], allTypesValues[name]);
|
||||
TestCase.assertSimilar(type, nullObject[name], nullPropertyValues[name]);
|
||||
}
|
||||
|
||||
TestCase.assertEqual(object.nonexistent, undefined);
|
||||
},
|
||||
testNullableBasicTypesPropertyGetters: function() {
|
||||
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
|
||||
var object, nullObject;
|
||||
|
||||
var basicTypesValues = {
|
||||
boolCol: true,
|
||||
intCol: 1,
|
||||
floatCol: 1.1,
|
||||
doubleCol: 1.11,
|
||||
stringCol: 'string',
|
||||
dateCol: new Date(1),
|
||||
dataCol: RANDOM_DATA,
|
||||
};
|
||||
testAllTypesPropertySetters: function() {
|
||||
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
|
||||
let obj;
|
||||
|
||||
realm.write(function() {
|
||||
object = realm.create('NullableBasicTypesObject', basicTypesValues);
|
||||
|
||||
nullObject = realm.create('NullableBasicTypesObject', {
|
||||
boolCol: null,
|
||||
intCol: null,
|
||||
floatCol: null,
|
||||
doubleCol: null,
|
||||
stringCol: null,
|
||||
dateCol: null,
|
||||
dataCol: null,
|
||||
});
|
||||
obj = realm.create('AllTypesObject', allTypesValues);
|
||||
});
|
||||
|
||||
for (var name in schemas.BasicTypes.properties) {
|
||||
var prop = schemas.BasicTypes.properties[name];
|
||||
var type = typeof prop == 'object' ? prop.type : prop;
|
||||
|
||||
TestCase.assertEqual(nullObject[name], null);
|
||||
|
||||
if (type == 'float' || type == 'double') {
|
||||
TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001);
|
||||
}
|
||||
else if (type == 'data') {
|
||||
TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA);
|
||||
}
|
||||
else if (type == 'date') {
|
||||
TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime());
|
||||
}
|
||||
else {
|
||||
TestCase.assertEqual(object[name], basicTypesValues[name]);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
testBasicTypesPropertySetters: function() {
|
||||
var realm = new Realm({schema: [schemas.BasicTypes]});
|
||||
var obj;
|
||||
|
||||
var basicTypesValues = {
|
||||
boolCol: true,
|
||||
intCol: 1,
|
||||
floatCol: 1.1,
|
||||
doubleCol: 1.11,
|
||||
stringCol: 'string',
|
||||
dateCol: new Date(1),
|
||||
dataCol: new ArrayBuffer(),
|
||||
};
|
||||
|
||||
realm.write(function() {
|
||||
obj = realm.create('BasicTypesObject', basicTypesValues);
|
||||
TestCase.assertThrows(function() {
|
||||
obj.boolCol = false;
|
||||
obj.intCol = 2;
|
||||
obj.floatCol = 2.2;
|
||||
obj.doubleCol = 2.22;
|
||||
obj.stringCol = 'STRING';
|
||||
obj.dateCol = new Date(2);
|
||||
obj.dataCol = RANDOM_DATA;
|
||||
});
|
||||
|
||||
TestCase.assertEqual(obj.boolCol, false, 'wrong bool value');
|
||||
TestCase.assertEqual(obj.intCol, 2, 'wrong int value');
|
||||
TestCase.assertEqualWithTolerance(obj.floatCol, 2.2, 0.000001, 'wrong float value');
|
||||
TestCase.assertEqualWithTolerance(obj.doubleCol, 2.22, 0.000001, 'wrong double value');
|
||||
TestCase.assertEqual(obj.stringCol, 'STRING', 'wrong string value');
|
||||
TestCase.assertEqual(obj.dateCol.getTime(), 2, 'wrong date value');
|
||||
TestCase.assertArraysEqual(new Uint8Array(obj.dataCol), RANDOM_DATA, 'wrong data value');
|
||||
|
||||
realm.write(function() {
|
||||
TestCase.assertThrows(function() {
|
||||
obj.boolCol = 'cat';
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.intCol = 'dog';
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
obj.boolCol = null;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.boolCol = undefined;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.intCol = null;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.intCol = undefined;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.floatCol = null;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.floatCol = undefined;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.doubleCol = null;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.doubleCol = undefined;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.stringCol = null;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.stringCol = undefined;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.dateCol = null;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.dateCol = undefined;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.dataCol = null;
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.dataCol = undefined;
|
||||
});
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
obj.boolCol = true;
|
||||
}, 'can only set property values in a write transaction');
|
||||
|
||||
TestCase.assertEqual(obj.boolCol, false, 'bool value changed outside transaction');
|
||||
},
|
||||
testNullableBasicTypesPropertySetters: function() {
|
||||
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
|
||||
var obj, obj1;
|
||||
|
||||
var basicTypesValues = {
|
||||
boolCol: true,
|
||||
intCol: 1,
|
||||
floatCol: 1.1,
|
||||
doubleCol: 1.11,
|
||||
stringCol: 'string',
|
||||
dateCol: new Date(1),
|
||||
dataCol: RANDOM_DATA,
|
||||
};
|
||||
TestCase.assertEqual(obj.boolCol, true, 'bool value changed outside transaction');
|
||||
|
||||
realm.write(function() {
|
||||
obj = realm.create('NullableBasicTypesObject', basicTypesValues);
|
||||
obj1 = realm.create('NullableBasicTypesObject', basicTypesValues);
|
||||
TestCase.assertThrows(() => obj.boolCol = 'cat');
|
||||
TestCase.assertThrows(() => obj.intCol = 'dog');
|
||||
|
||||
for (var name in schemas.NullableBasicTypes.properties) {
|
||||
obj[name] = null;
|
||||
obj1[name] = undefined;
|
||||
// Non-optional properties should complain about null
|
||||
for (const name of ['boolCol', 'intCol', 'floatCol', 'doubleCol', 'stringCol', 'dataCol', 'dateCol']) {
|
||||
TestCase.assertThrows(() => obj[name] = null, `Setting ${name} to null should throw`);
|
||||
TestCase.assertThrows(() => obj[name] = undefined, `Setting ${name} to undefined should throw`);
|
||||
}
|
||||
|
||||
// Optional properties should allow it
|
||||
for (const name of ['optBoolCol', 'optIntCol', 'optFloatCol', 'optDoubleCol',
|
||||
'optStringCol', 'optDataCol', 'optDateCol', 'objectCol']) {
|
||||
obj[name] = null;
|
||||
TestCase.assertEqual(obj[name], null);
|
||||
obj[name] = undefined;
|
||||
TestCase.assertEqual(obj[name], null);
|
||||
}
|
||||
|
||||
function tryAssign(name, value) {
|
||||
var prop = schemas.AllTypes.properties[name];
|
||||
var type = typeof prop == 'object' ? prop.type : prop;
|
||||
obj[name] = value;
|
||||
TestCase.assertSimilar(type, obj[name], value, undefined, 1);
|
||||
}
|
||||
|
||||
tryAssign('boolCol', false);
|
||||
tryAssign('intCol', 10);
|
||||
tryAssign('floatCol', 2.2);
|
||||
tryAssign('doubleCol', 3.3);
|
||||
tryAssign('stringCol', 'new str');
|
||||
tryAssign('dateCol', new Date(2));
|
||||
tryAssign('dataCol', RANDOM_DATA);
|
||||
|
||||
tryAssign('optBoolCol', null);
|
||||
tryAssign('optIntCol', null);
|
||||
tryAssign('optFloatCol', null);
|
||||
tryAssign('optDoubleCol', null);
|
||||
tryAssign('optStringCol', null);
|
||||
tryAssign('optDateCol', null);
|
||||
tryAssign('optDataCol', null);
|
||||
|
||||
tryAssign('optBoolCol', false);
|
||||
tryAssign('optIntCol', 10);
|
||||
tryAssign('optFloatCol', 2.2);
|
||||
tryAssign('optDoubleCol', 3.3);
|
||||
tryAssign('optStringCol', 'new str');
|
||||
tryAssign('optDateCol', new Date(2));
|
||||
tryAssign('optDataCol', RANDOM_DATA);
|
||||
|
||||
});
|
||||
|
||||
for (var name in schemas.NullableBasicTypes.properties) {
|
||||
TestCase.assertEqual(obj[name], null);
|
||||
TestCase.assertEqual(obj1[name], null);
|
||||
}
|
||||
|
||||
realm.write(function() {
|
||||
TestCase.assertThrows(function() {
|
||||
obj.boolCol = 'cat';
|
||||
});
|
||||
TestCase.assertThrows(function() {
|
||||
obj.intCol = 'dog';
|
||||
});
|
||||
});
|
||||
|
||||
TestCase.assertThrows(function() {
|
||||
obj.boolCol = null;
|
||||
}, 'can only set property values in a write transaction');
|
||||
},
|
||||
|
||||
testLinkTypesPropertyGetters: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var obj = null;
|
||||
|
||||
realm.write(function() {
|
||||
|
@ -273,8 +193,9 @@ module.exports = {
|
|||
TestCase.assertEqual(arrayVal.length, 1);
|
||||
TestCase.assertEqual(arrayVal[0].doubleCol, 3);
|
||||
},
|
||||
|
||||
testLinkTypesPropertySetters: function() {
|
||||
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
|
||||
var objects = realm.objects('TestObject');
|
||||
var obj;
|
||||
|
||||
|
@ -340,33 +261,25 @@ module.exports = {
|
|||
});
|
||||
TestCase.assertEqual(obj.objectCol.doubleCol, 3);
|
||||
},
|
||||
|
||||
testEnumerablePropertyNames: function() {
|
||||
var realm = new Realm({schema: [schemas.BasicTypes]});
|
||||
var object;
|
||||
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
|
||||
let object;
|
||||
|
||||
realm.write(function() {
|
||||
object = realm.create('BasicTypesObject', {
|
||||
boolCol: true,
|
||||
intCol: 1,
|
||||
floatCol: 1.1,
|
||||
doubleCol: 1.11,
|
||||
stringCol: 'string',
|
||||
dateCol: new Date(1),
|
||||
dataCol: RANDOM_DATA,
|
||||
});
|
||||
});
|
||||
realm.write(() => object = realm.create('AllTypesObject', allTypesValues));
|
||||
|
||||
var propNames = Object.keys(schemas.BasicTypes.properties);
|
||||
const propNames = Object.keys(schemas.AllTypes.properties);
|
||||
TestCase.assertArraysEqual(Object.keys(object), propNames, 'Object.keys');
|
||||
|
||||
for (var key in object) {
|
||||
for (let key in object) {
|
||||
TestCase.assertEqual(key, propNames.shift());
|
||||
}
|
||||
|
||||
TestCase.assertEqual(propNames.length, 0);
|
||||
},
|
||||
|
||||
testDataProperties: function() {
|
||||
var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]});
|
||||
const realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]});
|
||||
var object;
|
||||
|
||||
// Should be be able to set a data property with a typed array.
|
||||
|
@ -444,7 +357,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
testObjectConstructor: function() {
|
||||
var realm = new Realm({schema: [schemas.TestObject]});
|
||||
const realm = new Realm({schema: [schemas.TestObject]});
|
||||
|
||||
realm.write(function() {
|
||||
var obj = realm.create('TestObject', {doubleCol: 1});
|
||||
|
@ -456,7 +369,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
testIsValid: function() {
|
||||
var realm = new Realm({schema: [schemas.TestObject]});
|
||||
const realm = new Realm({schema: [schemas.TestObject]});
|
||||
var obj;
|
||||
realm.write(function() {
|
||||
obj = realm.create('TestObject', {doubleCol: 1});
|
||||
|
@ -470,9 +383,9 @@ module.exports = {
|
|||
obj.doubleCol;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
testObjectSchema: function() {
|
||||
var realm = new Realm({schema: [schemas.TestObject]});
|
||||
const realm = new Realm({schema: [schemas.TestObject]});
|
||||
var obj;
|
||||
realm.write(function() {
|
||||
obj = realm.create('TestObject', {doubleCol: 1});
|
||||
|
@ -515,7 +428,7 @@ module.exports = {
|
|||
TestCase.assertEqual(realm_v3.objects('Date')[0].nullDate.getTime(), 1462500087955);
|
||||
TestCase.assertEqual(realm_v3.objects('Date')[1].currentDate.getTime(), -10000);
|
||||
TestCase.assertEqual(realm_v3.objects('Date')[1].nullDate, null);
|
||||
|
||||
|
||||
// test different dates
|
||||
var realm = new Realm({schema: [schemas.DateObject]});
|
||||
realm.write(function() {
|
||||
|
|
|
@ -95,7 +95,8 @@ module.exports = {
|
|||
},
|
||||
|
||||
testRealmConstructorSchemaValidation: function() {
|
||||
TestCase.assertThrowsContaining(() => new Realm({schema: schemas.AllTypes}), "schema must be of type 'array', got");
|
||||
TestCase.assertThrowsContaining(() => new Realm({schema: schemas.AllTypes}),
|
||||
"schema must be of type 'array', got");
|
||||
TestCase.assertThrowsContaining(() => new Realm({schema: ['SomeType']}),
|
||||
"Failed to read ObjectSchema: JS value must be of type 'object', got (SomeType)");
|
||||
TestCase.assertThrowsContaining(() => new Realm({schema: [{}]}),
|
||||
|
@ -105,26 +106,21 @@ module.exports = {
|
|||
TestCase.assertThrowsContaining(() => new Realm({schema: [{properties: {intCol: 'int'}}]}),
|
||||
"Failed to read ObjectSchema: name must be of type 'string', got (undefined)");
|
||||
|
||||
// linkingObjects property where the source property is missing
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
new Realm({schema: [{
|
||||
name: 'InvalidObject',
|
||||
properties: {
|
||||
linkingObjects: {type:'linkingObjects', objectType: 'InvalidObject', property: 'nosuchproperty'}
|
||||
}
|
||||
}]});
|
||||
}, "Property 'InvalidObject.nosuchproperty' declared as origin of linking objects property 'InvalidObject.linkingObjects' does not exist");
|
||||
function assertPropertyInvalid(prop, message) {
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
new Realm({schema: [{name: 'InvalidObject', properties: { int: 'int', bad: prop }}]});
|
||||
}, message, 1);
|
||||
}
|
||||
|
||||
// linkingObjects property where the source property is not a link
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
new Realm({schema: [{
|
||||
name: 'InvalidObject',
|
||||
properties: {
|
||||
integer: 'int',
|
||||
linkingObjects: {type:'linkingObjects', objectType: 'InvalidObject', property: 'integer'}
|
||||
}
|
||||
}]});
|
||||
}, "Property 'InvalidObject.integer' declared as origin of linking objects property 'InvalidObject.linkingObjects' is not a link")
|
||||
assertPropertyInvalid({type:'list[]', objectType: 'InvalidObject'},
|
||||
"List property 'InvalidObject.bad' must have a non-list value type");
|
||||
assertPropertyInvalid({type:'list?', objectType: 'InvalidObject'},
|
||||
"List property 'InvalidObject.bad' cannot be optional");
|
||||
assertPropertyInvalid('', "Property 'InvalidObject.bad' must have a non-empty type");
|
||||
assertPropertyInvalid({type:'linkingObjects', objectType: 'InvalidObject', property: 'nosuchproperty'},
|
||||
"Property 'InvalidObject.nosuchproperty' declared as origin of linking objects property 'InvalidObject.bad' does not exist");
|
||||
assertPropertyInvalid({type:'linkingObjects', objectType: 'InvalidObject', property: 'int'},
|
||||
"Property 'InvalidObject.int' declared as origin of linking objects property 'InvalidObject.bad' is not a link");
|
||||
|
||||
// linkingObjects property where the source property links elsewhere
|
||||
TestCase.assertThrowsContaining(() => {
|
||||
|
@ -141,8 +137,18 @@ module.exports = {
|
|||
}
|
||||
}]});
|
||||
}, "Property 'InvalidObject.link' declared as origin of linking objects property 'InvalidObject.linkingObjects' links to type 'IntObject'")
|
||||
|
||||
{
|
||||
new Realm({schema: [{
|
||||
name: 'Object',
|
||||
properties: {
|
||||
// weird but valid
|
||||
objectList: {type:'object[]', objectType: 'Object'}
|
||||
}
|
||||
}]});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
testRealmConstructorInMemory: function() {
|
||||
// open in-memory realm instance
|
||||
const realm1 = new Realm({inMemory: true, schema: [schemas.TestObject]});
|
||||
|
@ -166,7 +172,7 @@ module.exports = {
|
|||
// Open the same in-memory realm again and verify that it is now empty
|
||||
const realm3 = new Realm({inMemory: true});
|
||||
TestCase.assertEqual(realm3.schema.length, 0);
|
||||
|
||||
|
||||
// try to open the same realm in persistent mode (should fail as you cannot mix modes)
|
||||
TestCase.assertThrowsContaining(() => new Realm({}), 'already opened with different inMemory settings.');
|
||||
},
|
||||
|
@ -296,23 +302,10 @@ module.exports = {
|
|||
});
|
||||
},
|
||||
|
||||
testRealmCreateOptionals: function() {
|
||||
const realm = new Realm({schema: [schemas.NullableBasicTypes, schemas.LinkTypes, schemas.TestObject]});
|
||||
let basic, links;
|
||||
realm.write(() => {
|
||||
basic = realm.create('NullableBasicTypesObject', {});
|
||||
links = realm.create('LinkTypesObject', {});
|
||||
});
|
||||
for (const name in schemas.NullableBasicTypes.properties) {
|
||||
TestCase.assertEqual(basic[name], null);
|
||||
}
|
||||
TestCase.assertEqual(links.objectCol, null);
|
||||
TestCase.assertEqual(links.arrayCol.length, 0);
|
||||
},
|
||||
|
||||
testRealmCreateUpsert: function() {
|
||||
const realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
|
||||
realm.write(() => {
|
||||
const realm = new Realm({schema: [schemas.AllPrimaryTypes, schemas.TestObject,
|
||||
schemas.StringPrimary]});
|
||||
realm.write(function() {
|
||||
const values = {
|
||||
primaryCol: '0',
|
||||
boolCol: true,
|
||||
|
@ -326,12 +319,12 @@ module.exports = {
|
|||
arrayCol: [],
|
||||
};
|
||||
|
||||
const obj0 = realm.create('AllTypesObject', values);
|
||||
const obj0 = realm.create('AllPrimaryTypesObject', values);
|
||||
|
||||
TestCase.assertThrowsContaining(() => realm.create('AllTypesObject', values),
|
||||
"Attempting to create an object of type 'AllTypesObject' with an existing primary key value '0'.");
|
||||
TestCase.assertThrowsContaining(() => realm.create('AllPrimaryTypesObject', values),
|
||||
"Attempting to create an object of type 'AllPrimaryTypesObject' with an existing primary key value ''0''.");
|
||||
|
||||
const obj1 = realm.create('AllTypesObject', {
|
||||
const obj1 = realm.create('AllPrimaryTypesObject', {
|
||||
primaryCol: '1',
|
||||
boolCol: false,
|
||||
intCol: 2,
|
||||
|
@ -344,10 +337,10 @@ module.exports = {
|
|||
arrayCol: [{doubleCol: 2}],
|
||||
}, true);
|
||||
|
||||
const objects = realm.objects('AllTypesObject');
|
||||
const objects = realm.objects('AllPrimaryTypesObject');
|
||||
TestCase.assertEqual(objects.length, 2);
|
||||
|
||||
realm.create('AllTypesObject', {
|
||||
realm.create('AllPrimaryTypesObject', {
|
||||
primaryCol: '0',
|
||||
boolCol: false,
|
||||
intCol: 2,
|
||||
|
@ -371,13 +364,13 @@ module.exports = {
|
|||
TestCase.assertEqual(obj0.objectCol, null);
|
||||
TestCase.assertEqual(obj0.arrayCol.length, 1);
|
||||
|
||||
realm.create('AllTypesObject', {primaryCol: '0'}, true);
|
||||
realm.create('AllTypesObject', {primaryCol: '1'}, true);
|
||||
realm.create('AllPrimaryTypesObject', {primaryCol: '0'}, true);
|
||||
realm.create('AllPrimaryTypesObject', {primaryCol: '1'}, true);
|
||||
TestCase.assertEqual(obj0.stringCol, '2');
|
||||
TestCase.assertEqual(obj0.objectCol, null);
|
||||
TestCase.assertEqual(obj1.objectCol.doubleCol, 0);
|
||||
|
||||
realm.create('AllTypesObject', {
|
||||
realm.create('AllPrimaryTypesObject', {
|
||||
primaryCol: '0',
|
||||
stringCol: '3',
|
||||
objectCol: {doubleCol: 0},
|
||||
|
@ -393,13 +386,13 @@ module.exports = {
|
|||
TestCase.assertEqual(obj0.objectCol.doubleCol, 0);
|
||||
TestCase.assertEqual(obj0.arrayCol.length, 1);
|
||||
|
||||
realm.create('AllTypesObject', {primaryCol: '0', objectCol: undefined}, true);
|
||||
realm.create('AllTypesObject', {primaryCol: '1', objectCol: null}, true);
|
||||
realm.create('AllPrimaryTypesObject', {primaryCol: '0', objectCol: undefined}, true);
|
||||
realm.create('AllPrimaryTypesObject', {primaryCol: '1', objectCol: null}, true);
|
||||
TestCase.assertEqual(obj0.objectCol, null);
|
||||
TestCase.assertEqual(obj1.objectCol, null);
|
||||
|
||||
// test with string primaries
|
||||
const obj =realm.create('StringPrimaryObject', {
|
||||
const obj = realm.create('StringPrimaryObject', {
|
||||
primaryCol: '0',
|
||||
valueCol: 0
|
||||
});
|
||||
|
@ -784,8 +777,9 @@ module.exports = {
|
|||
},
|
||||
|
||||
testSchema: function() {
|
||||
const originalSchema = [schemas.TestObject, schemas.BasicTypes, schemas.NullableBasicTypes, schemas.IndexedTypes, schemas.IntPrimary,
|
||||
schemas.PersonObject, schemas.LinkTypes, schemas.LinkingObjectsObject];
|
||||
const originalSchema = [schemas.TestObject, schemas.AllTypes, schemas.LinkToAllTypes,
|
||||
schemas.IndexedTypes, schemas.IntPrimary, schemas.PersonObject,
|
||||
schemas.LinkTypes, schemas.LinkingObjectsObject];
|
||||
|
||||
const schemaMap = {};
|
||||
originalSchema.forEach(objectSchema => {
|
||||
|
@ -801,45 +795,67 @@ module.exports = {
|
|||
const schema = realm.schema;
|
||||
TestCase.assertEqual(schema.length, originalSchema.length);
|
||||
|
||||
function isString(val) {
|
||||
return typeof val === 'string' || val instanceof String;
|
||||
}
|
||||
const normalizeProperty = (val) => {
|
||||
let prop;
|
||||
if (typeof val !== 'string' && !(val instanceof String)) {
|
||||
prop = val;
|
||||
prop.optional = val.optional || false;
|
||||
prop.indexed = val.indexed || false;
|
||||
}
|
||||
else {
|
||||
prop = {type: val, indexed: false, optional: false};
|
||||
}
|
||||
if (prop.type.includes('?')) {
|
||||
prop.optional = true;
|
||||
prop.type = prop.type.replace('?', '');
|
||||
}
|
||||
if (prop.type.includes('[]')) {
|
||||
prop.objectType = prop.type.replace('[]', '');
|
||||
prop.type = 'list';
|
||||
}
|
||||
return prop;
|
||||
};
|
||||
|
||||
function verifyObjectSchema(returned) {
|
||||
let original = schemaMap[returned.name];
|
||||
for (const objectSchema of schema) {
|
||||
let original = schemaMap[objectSchema.name];
|
||||
if (original.schema) {
|
||||
original = original.schema;
|
||||
}
|
||||
|
||||
TestCase.assertEqual(returned.primaryKey, original.primaryKey);
|
||||
for (const propName in returned.properties) {
|
||||
const prop1 = returned.properties[propName];
|
||||
const prop2 = original.properties[propName];
|
||||
if (prop1.type == 'object') {
|
||||
TestCase.assertEqual(prop1.objectType, isString(prop2) ? prop2 : prop2.objectType);
|
||||
TestCase.assertEqual(prop1.optional, true);
|
||||
TestCase.assertEqual(objectSchema.primaryKey, original.primaryKey);
|
||||
for (const propName in objectSchema.properties) {
|
||||
TestCase.assertDefined(original.properties[propName], `schema has unexpected property ${propName}`);
|
||||
|
||||
const actual = objectSchema.properties[propName];
|
||||
const expected = normalizeProperty(original.properties[propName]);
|
||||
TestCase.assertEqual(actual.name, propName);
|
||||
TestCase.assertEqual(actual.indexed, expected.indexed);
|
||||
|
||||
if (actual.type == 'object') {
|
||||
TestCase.assertEqual(actual.objectType, expected.type === 'object' ? expected.objectType : expected.type);
|
||||
TestCase.assertEqual(actual.optional, true);
|
||||
TestCase.assertUndefined(actual.property);
|
||||
}
|
||||
else if (prop1.type == 'list') {
|
||||
TestCase.assertEqual(prop1.objectType, prop2.objectType);
|
||||
TestCase.assertEqual(prop1.optional, undefined);
|
||||
else if (actual.type == 'list') {
|
||||
TestCase.assertEqual(actual.type, expected.type);
|
||||
TestCase.assertEqual(actual.objectType, expected.objectType);
|
||||
TestCase.assertEqual(actual.optional, expected.optional);
|
||||
TestCase.assertUndefined(actual.property);
|
||||
}
|
||||
else if (prop1.type == 'linking objects') {
|
||||
TestCase.assertEqual(prop1.objectType, prop2.objectType);
|
||||
TestCase.assertEqual(prop1.property, prop2.property);
|
||||
TestCase.assertEqual(prop1.optional, undefined);
|
||||
else if (actual.type == 'linkingObjects') {
|
||||
TestCase.assertEqual(actual.type, expected.type);
|
||||
TestCase.assertEqual(actual.objectType, expected.objectType);
|
||||
TestCase.assertEqual(actual.property, expected.property);
|
||||
TestCase.assertEqual(actual.optional, false);
|
||||
}
|
||||
else {
|
||||
TestCase.assertEqual(prop1.type, isString(prop2) ? prop2 : prop2.type);
|
||||
TestCase.assertEqual(prop1.optional, prop2.optional || undefined);
|
||||
TestCase.assertEqual(actual.type, expected.type);
|
||||
TestCase.assertEqual(actual.optional, expected.optional);
|
||||
TestCase.assertUndefined(actual.property);
|
||||
TestCase.assertUndefined(actual.objectType);
|
||||
}
|
||||
|
||||
TestCase.assertEqual(prop1.indexed, prop2.indexed || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < originalSchema.length; i++) {
|
||||
verifyObjectSchema(schema[i]);
|
||||
}
|
||||
},
|
||||
|
||||
testCopyBundledRealmFiles: function() {
|
||||
|
@ -869,7 +885,7 @@ module.exports = {
|
|||
const p1 = realm.create('PersonObject', { name: 'Ari', age: 10 });
|
||||
p1.age = "Ten";
|
||||
});
|
||||
}, new Error("PersonObject.age must be of type 'number', got (Ten)"));
|
||||
}, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')"));
|
||||
},
|
||||
|
||||
testErrorMessageFromInvalidCreate: function() {
|
||||
|
@ -879,7 +895,7 @@ module.exports = {
|
|||
realm.write(() => {
|
||||
const p1 = realm.create('PersonObject', { name: 'Ari', age: 'Ten' });
|
||||
});
|
||||
}, new Error("PersonObject.age must be of type 'number', got (Ten)"));
|
||||
}, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')"));
|
||||
},
|
||||
|
||||
testValidTypesForListProperties: function() {
|
||||
|
@ -934,7 +950,7 @@ module.exports = {
|
|||
realm.cancelTransaction();
|
||||
TestCase.assertTrue(!realm.isInTransaction);
|
||||
},
|
||||
|
||||
|
||||
testCompact: function() {
|
||||
let wasCalled = false;
|
||||
const count = 1000;
|
||||
|
|
|
@ -299,7 +299,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
testResultsInvalidation: function() {
|
||||
var realm = new Realm({schema: [schemas.TestObject]});
|
||||
let realm = new Realm({schema: [schemas.TestObject]});
|
||||
realm.write(function() {
|
||||
for (var i = 10; i > 0; i--) {
|
||||
realm.create('TestObject', [i]);
|
||||
|
@ -322,7 +322,7 @@ module.exports = {
|
|||
realm.close();
|
||||
realm = new Realm({
|
||||
schemaVersion: 1,
|
||||
schema: [schemas.TestObject, schemas.BasicTypes]
|
||||
schema: [schemas.TestObject, schemas.DateObject]
|
||||
});
|
||||
|
||||
resultsVariants.forEach(function(objects) {
|
||||
|
|
|
@ -33,7 +33,7 @@ PersonObject.schema = {
|
|||
properties: {
|
||||
name: 'string',
|
||||
age: 'double',
|
||||
married: {type: 'bool', default: false},
|
||||
married: {type: 'bool', default: false},
|
||||
children: {type: 'list', objectType: 'PersonObject'},
|
||||
parents: {type: 'linkingObjects', objectType: 'PersonObject', property: 'children'},
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ exports.PersonObject = PersonObject;
|
|||
exports.PersonList = {
|
||||
name: 'PersonList',
|
||||
properties: {
|
||||
list: {type: 'list', objectType: 'PersonObject'},
|
||||
list: 'PersonObject[]',
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -68,26 +68,82 @@ exports.BasicTypes = {
|
|||
}
|
||||
};
|
||||
|
||||
exports.NullableBasicTypes = {
|
||||
name: 'NullableBasicTypesObject',
|
||||
exports.AllTypes = {
|
||||
name: 'AllTypesObject',
|
||||
properties: {
|
||||
boolCol: {type: 'bool', optional: true},
|
||||
intCol: {type: 'int', optional: true},
|
||||
floatCol: {type: 'float', optional: true},
|
||||
doubleCol: {type: 'double', optional: true},
|
||||
stringCol: {type: 'string', optional: true},
|
||||
dateCol: {type: 'date', optional: true},
|
||||
dataCol: {type: 'data', optional: true},
|
||||
boolCol: 'bool',
|
||||
intCol: 'int',
|
||||
floatCol: 'float',
|
||||
doubleCol: 'double',
|
||||
stringCol: 'string',
|
||||
dateCol: 'date',
|
||||
dataCol: 'data',
|
||||
objectCol: 'TestObject',
|
||||
|
||||
optBoolCol: 'bool?',
|
||||
optIntCol: 'int?',
|
||||
optFloatCol: 'float?',
|
||||
optDoubleCol: 'double?',
|
||||
optStringCol: 'string?',
|
||||
optDateCol: 'date?',
|
||||
optDataCol: 'data?',
|
||||
|
||||
boolArrayCol: 'bool[]',
|
||||
intArrayCol: 'int[]',
|
||||
floatArrayCol: 'float[]',
|
||||
doubleArrayCol: 'double[]',
|
||||
stringArrayCol: 'string[]',
|
||||
dateArrayCol: 'date[]',
|
||||
dataArrayCol: 'data[]',
|
||||
objectArrayCol: 'TestObject[]',
|
||||
|
||||
optBoolArrayCol: 'bool?[]',
|
||||
optIntArrayCol: 'int?[]',
|
||||
optFloatArrayCol: 'float?[]',
|
||||
optDoubleArrayCol: 'double?[]',
|
||||
optStringArrayCol: 'string?[]',
|
||||
optDateArrayCol: 'date?[]',
|
||||
optDataArrayCol: 'data?[]',
|
||||
|
||||
linkingObjectsCol: {type: 'linkingObjects', objectType: 'LinkToAllTypesObject', property: 'allTypesCol'},
|
||||
}
|
||||
};
|
||||
|
||||
exports.AllPrimaryTypes = {
|
||||
name: 'AllPrimaryTypesObject',
|
||||
primaryKey: 'primaryCol',
|
||||
properties: {
|
||||
primaryCol: 'string',
|
||||
boolCol: 'bool',
|
||||
intCol: 'int',
|
||||
floatCol: 'float',
|
||||
doubleCol: 'double',
|
||||
stringCol: 'string',
|
||||
dateCol: 'date',
|
||||
dataCol: 'data',
|
||||
objectCol: 'TestObject',
|
||||
arrayCol: {type: 'list', objectType: 'TestObject'},
|
||||
}
|
||||
};
|
||||
|
||||
exports.LinkToAllTypes = {
|
||||
name: 'LinkToAllTypesObject',
|
||||
properties: {
|
||||
allTypesCol: 'AllTypesObject',
|
||||
}
|
||||
}
|
||||
|
||||
exports.IndexedTypes = {
|
||||
name: 'IndexedTypesObject',
|
||||
properties: {
|
||||
boolCol: {type: 'bool', indexed: true},
|
||||
intCol: {type: 'int', indexed: true},
|
||||
stringCol: {type: 'string', indexed: true},
|
||||
dateCol: {type: 'date', indexed: true},
|
||||
boolCol: {type: 'bool', indexed: true},
|
||||
intCol: {type: 'int', indexed: true},
|
||||
stringCol: {type: 'string', indexed: true},
|
||||
dateCol: {type: 'date', indexed: true},
|
||||
optBoolCol: {type: 'bool?', indexed: true},
|
||||
optIntCol: {type: 'int?', indexed: true},
|
||||
optStringCol: {type: 'string?', indexed: true},
|
||||
optDateCol: {type: 'date?', indexed: true},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -95,9 +151,31 @@ exports.IndexedTypes = {
|
|||
exports.LinkTypes = {
|
||||
name: 'LinkTypesObject',
|
||||
properties: {
|
||||
objectCol: 'TestObject',
|
||||
objectCol: 'TestObject',
|
||||
objectCol1: {type: 'object', objectType: 'TestObject'},
|
||||
arrayCol: {type: 'list', objectType: 'TestObject'},
|
||||
arrayCol: 'TestObject[]',
|
||||
arrayCol1: {type: 'list', objectType: 'TestObject'},
|
||||
}
|
||||
};
|
||||
|
||||
exports.PrimitiveArrays = {
|
||||
name: 'PrimitiveArrays',
|
||||
properties: {
|
||||
bool: 'bool[]',
|
||||
int: 'int[]',
|
||||
float: 'float[]',
|
||||
double: 'double[]',
|
||||
string: 'string[]',
|
||||
date: 'date[]',
|
||||
data: 'data[]',
|
||||
|
||||
optBool: 'bool?[]',
|
||||
optInt: 'int?[]',
|
||||
optFloat: 'float?[]',
|
||||
optDouble: 'double?[]',
|
||||
optString: 'string?[]',
|
||||
optDate: 'date?[]',
|
||||
optData: 'data?[]',
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -126,44 +204,19 @@ exports.StringOnly = {
|
|||
}
|
||||
};
|
||||
|
||||
exports.AllTypes = {
|
||||
name: 'AllTypesObject',
|
||||
primaryKey: 'primaryCol',
|
||||
properties: {
|
||||
primaryCol: 'string',
|
||||
boolCol: 'bool',
|
||||
intCol: 'int',
|
||||
floatCol: 'float',
|
||||
doubleCol: 'double',
|
||||
stringCol: 'string',
|
||||
dateCol: 'date',
|
||||
dataCol: 'data',
|
||||
objectCol: 'TestObject',
|
||||
arrayCol: {type: 'list', objectType: 'TestObject'},
|
||||
linkingObjectsCol: {type: 'linkingObjects', objectType: 'LinkToAllTypesObject', property: 'allTypesCol'},
|
||||
}
|
||||
};
|
||||
|
||||
exports.LinkToAllTypes = {
|
||||
name: 'LinkToAllTypesObject',
|
||||
properties: {
|
||||
allTypesCol: 'AllTypesObject',
|
||||
}
|
||||
}
|
||||
|
||||
exports.DefaultValues = {
|
||||
name: 'DefaultValuesObject',
|
||||
properties: {
|
||||
boolCol: {type: 'bool', default: true},
|
||||
intCol: {type: 'int', default: -1},
|
||||
floatCol: {type: 'float', default: -1.1},
|
||||
doubleCol: {type: 'double', default: -1.11},
|
||||
stringCol: {type: 'string', default: 'defaultString'},
|
||||
dateCol: {type: 'date', default: new Date(1.111)},
|
||||
dataCol: {type: 'data', default: new ArrayBuffer(1)},
|
||||
objectCol: {type: 'TestObject', default: {doubleCol: 1}},
|
||||
nullObjectCol: {type: 'TestObject', default: null},
|
||||
arrayCol: {type: 'list', objectType: 'TestObject', default: [{doubleCol: 2}]},
|
||||
boolCol: {type: 'bool', default: true},
|
||||
intCol: {type: 'int', default: -1},
|
||||
floatCol: {type: 'float', default: -1.1},
|
||||
doubleCol: {type: 'double', default: -1.11},
|
||||
stringCol: {type: 'string', default: 'defaultString'},
|
||||
dateCol: {type: 'date', default: new Date(1.111)},
|
||||
dataCol: {type: 'data', default: new ArrayBuffer(1)},
|
||||
objectCol: {type: 'TestObject', default: {doubleCol: 1}},
|
||||
nullObjectCol: {type: 'TestObject', default: null},
|
||||
arrayCol: {type: 'TestObject[]', default: [{doubleCol: 2}]},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -203,7 +256,7 @@ exports.DateObject = {
|
|||
name: 'Date',
|
||||
properties: {
|
||||
currentDate: 'date',
|
||||
nullDate: { type: 'date', optional: true }
|
||||
nullDate: 'date?'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -211,7 +264,7 @@ exports.LinkingObjectsObject = {
|
|||
name: 'LinkingObjectsObject',
|
||||
properties: {
|
||||
value: 'int',
|
||||
links: {type: 'list', objectType: 'LinkingObjectsObject'},
|
||||
links: 'LinkingObjectsObject[]',
|
||||
linkingObjects: {type: 'linkingObjects', objectType: 'LinkingObjectsObject', property: 'links'}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue