diff --git a/.gitignore b/.gitignore
index 8d2f293d2098c8..566068abc766e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,17 @@
.DS_Store
-node_modules
-
+/node_modules
+docsPostProcessed/
lib/core/metadata.js
lib/core/MetadataBlog.js
-website/translated_docs
+docsPostProcessed
+
+website/siteConfig.js
+
website/build/
website/yarn.lock
website/node_modules
!website/node_modules/highlight.js/lib/index.js
!website/node_modules/highlight.js/lib/languages/code4d.js
+
diff --git a/crowdin.yml b/crowdin.yml
index 2f8898eda5573d..ebc1a79010dc79 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,7 +1,6 @@
files:
- source: /docs/**/*.md
translation: /website/translated_docs/%two_letters_code%/**/%original_file_name%
- type: fm_md7
- source: /docs/assets/en/**/*.png
translation: /docs/assets/%two_letters_code%/**/%original_file_name%
- source: /website/i18n/en.json
diff --git a/docs/API/BlobClass.md b/docs/API/BlobClass.md
new file mode 100644
index 00000000000000..f211fde7068ef2
--- /dev/null
+++ b/docs/API/BlobClass.md
@@ -0,0 +1,92 @@
+---
+id: BlobClass
+title: Blob
+---
+
+The Blob class lets you create and manipulate [blob objects](../Concepts/dt_blob.md#blob-types) (`4D.Blob`).
+
+### Summary
+
+||
+|---|
+|[](#4dblobnew)
|
+|[](#size)
|
+|[](#slice)
|
+
+## 4D.Blob.new()
+
+History
+|Version|Changes|
+|---|---|
+|v19 R2|Added|
+
+
+
+**4D.Blob.new()** : 4D.Blob
**4D.Blob.new**( *blobScal* : Blob ) : 4D.Blob
**4D.Blob.new**( *blobObj* : 4D.Blob ) : 4D.Blob
+
+
+
+| Parameter | Type | | Description |
+| --------- | --------------- | :-: | ------------ |
+| blob | Blob or 4D.Blob | -> | Blob to copy |
+| Result | 4D.Blob | <- | New 4D.Blob |
+
+
+#### Description
+
+`4D.Blob.new` creates a new `4D.Blob` object optionally encapsulating a copy of the data from another blob (scalar blob or `4D.Blob`).
+
+If the `blob` parameter is omitted, the method returns an empty 4D.Blob.
+
+## .size
+
+
+**.size** : Real
+
+#### Description
+The `.size` property returns the size of a `4D.Blob`, expressed in bytes.
+
+## .slice()
+
+History
+|Version|Changes|
+|---|---|
+|v19 R2|Added|
+
+
+
+**.slice()** : 4D.Blob
**.slice**( *start* : Real ) : 4D.Blob
**.slice**( *start* : Real; *end* : Real ) : 4D.Blob
+
+
+| Parameter | Type ||Description |
+| --------- | ------- | :-: | --- |
+| start| Real | -> | index of the first byte to include in the new `4D.Blob`. |
+| end| Real | -> | index of the first byte that will not be included in the new `4D.Blob` |
+| Result| 4D.Blob | <- | New `4D.Blob`|
+
+#### Description
+
+`.slice()` creates and returns a `4D.Blob ` that references data from a subset of the blob on which it's called. The original blob is not altered.
+
+The `start` parameter is an index into the blob indicating the first byte to include in the new `4D.Blob`. If you specify a negative value, 4D treats it as an offset from the end of the blob toward the beginning. For example, -10 would be the 10th from last byte in the blob. The default value is 0. If you specify a value for start that is larger than the size of the source blob, the returned `4D.Blob`'s size is 0, and it contains no data.
+
+The `end` parameter is an index into the blob indicating the first byte that will not be included in the new `4D.Blob` (i.e. the byte exactly at this index is not included). If you specify a negative value, 4D treats it as an offset from the end of the blob toward the beginning. For example, -10 would be the 10th from last byte in the blob. The default value is the size of the blob.
+
+#### Example
+
+```4d
+var $myBlob : 4D.Blob
+
+// Store text in a 4D.Blob
+CONVERT FROM TEXT("Hello, World!"; "UTF-8"; $myBlob)
+$is4DBlob:=OB Instance of($myBlob; 4D.Blob); //True
+
+$myString:=Convert to text($myBlob; "UTF-8")
+// $myString contains "Hello, World!"
+
+// Create a new 4D.Blob from $myBlob
+$myNewBlob:=$myBlob.slice(0; 5)
+
+$myString:=Convert to text($myNewBlob; "UTF-8")
+// $myString contains "Hello"
+```
diff --git a/docs/API/ClassClass.md b/docs/API/ClassClass.md
new file mode 100644
index 00000000000000..b4efe13478eb28
--- /dev/null
+++ b/docs/API/ClassClass.md
@@ -0,0 +1,146 @@
+---
+id: ClassClass
+title: Class
+---
+
+
+When a user class is [defined](Concepts/classes.md#class-definition) in the project, it is loaded in the 4D language environment. A class is an object itself, of "Class" class, which has properties and a function.
+
+
+
+### Summary
+
+
+||
+|---|
+|[](#name)
|
+|[](#new)
|
+|[](#superclass)
|
+
+
+
+
+## .name
+
+History
+|Version|Changes|
+|---|---|
+|v18 R3|Added|
+
+
+
+
+**.name** : Text
+
+#### Description
+
+The `.name` property contains the name of the `4D.Class` object. Class names are case sensitive.
+
+This property is **read-only**.
+
+
+
+
+
+
+## .new()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R3|Added|
+
+
+
+**.new**( *param* : any { *;...paramN* } ) : 4D.Class
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|param|any|->|Parameter(s) to pass to the constructor function|
+|Result|4D.Class|<-|New object of the class|
+
+
+
+#### Description
+
+The `.new()` function creates and returns a `cs.className` object which is a new instance of the class on which it is called. This function is automatically available on all classes from the [`cs` class store](Concepts/classes.md#cs).
+
+You can pass one or more optional *param* parameters, which will be passed to the [class constructor](Concepts/classes.md#class-constructor) function (if any) in the className class definition. Within the constructor function, the [`This`](Concepts/classes.md#this) is bound to the new object being constructed.
+
+If `.new()` is called on a non-existing class, an error is returned.
+
+#### Examples
+
+To create a new instance of the Person class:
+
+```4d
+var $person : cs.Person
+$person:=cs.Person.new() //create the new instance
+//$person contains functions of the class
+```
+
+To create a new instance of the Person class with parameters:
+
+```4d
+//Class: Person.4dm
+Class constructor($firstname : Text; $lastname : Text; $age : Integer)
+ This.firstName:=$firstname
+ This.lastName:=$lastname
+ This.age:=$age
+```
+
+```4d
+//In a method
+var $person : cs.Person
+$person:=cs.Person.new("John";"Doe";40)
+//$person.firstName = "John"
+//$person.lastName = "Doe"
+//$person.age = 40
+```
+
+
+
+
+
+
+
+## .superclass
+
+History
+|Version|Changes|
+|---|---|
+|v18 R3|Added|
+
+
+
+
+**.superclass** : 4D.Class
+
+#### Description
+
+The `.superclass` property returns the parent class of the class. A superclass can be a `4D.Class` object, or a `cs.className` object. If the class does not have a parent class, the property returns **null**.
+
+A superclass of a user class is declared in a class by using the [`Class extends `](Concepts/classes.md#class-extends-classname) keyword.
+
+This property is **read-only**.
+
+#### Examples
+
+```4d
+$sup:=4D.File.superclass //Document
+$sup:=4D.Document.superclass //Object
+$sup:=4D.Object.superclass //null
+
+// If you created a MyFile class
+// with `Class extends File`
+$sup:=cs.MyFile.superclass //File
+
+```
+
+
+
+**See also:** [Super](Concepts/classes.md#super)
+
+
+
diff --git a/docs/API/CollectionClass.md b/docs/API/CollectionClass.md
new file mode 100644
index 00000000000000..b8addf8e30d062
--- /dev/null
+++ b/docs/API/CollectionClass.md
@@ -0,0 +1,2872 @@
+---
+id: CollectionClass
+title: Collection
+---
+
+
+The Collection class manages [Collection](Concepts/dt_collection.md) type variables.
+
+A collection is initialized with:
+
+||
+|---|
+|[](#new-collection) |
+|[](#new-shared-collection)
|
+
+
+### Example
+
+```4d
+ var $colVar : Collection //creation of collection type 4D variable
+ $colVar:=New collection //initialization of the collection and assignment to the 4D variable
+```
+
+
+### Summary
+
+
+||
+|---|
+|[](#average)
|
+|[](#clear)
|
+|[](#combine)
|
+|[](#concat)
|
+|[](#copy)
|
+|[](#count)
|
+|[](#countvalues)
|
+|[](#distinct)
|
+|[](#equal)
|
+|[](#every)
|
+|[](#extract)
|
+|[](#fill)
|
+|[](#filter)
|
+|[](#find)
|
+|[](#find)
|
+|[](#indexof)
|
+|[](#indices)
|
+|[](#insert)
|
+|[](#join)
|
+|[](#lastindexof)
|
+|[](#length)
|
+|[](#map)
|
+|[](#max)
|
+|[](#min)
|
+|[](#orderby)
|
+|[](#orderbymethod)
|
+|[](#pop)
|
+|[](#push)
|
+|[](#query)
|
+|[](#reduce)
|
+|[](#remove)
|
+|[](#resize)
|
+|[](#reverse)
|
+|[](#shift)
|
+|[](#slice)
|
+|[](#some)
|
+|[](#sort)
|
+|[](#sum)
|
+|[](#unshift)
|
+
+
+
+## `New collection`
+
+
+
+**New collection** {( *...value* : any )} : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|value|Number, Text, Date, Time, Boolean, Object, Collection, Picture, Pointer|->|Collection's value(s)|
+|Result|Collection|<-|New collection|
+
+
+
+#### Description
+
+The `New collection` command creates a new empty or prefilled collection and returns its reference.
+
+If you do not pass any parameters, `New collection` creates an empty collection and returns its reference.
+
+You must assign the returned reference to a 4D variable of the Collection type.
+
+>Keep in mind that `var : Collection` or `C_COLLECTION` statements declare a variable of the `Collection` type but does not create any collection.
+
+Optionally, you can prefill the new collection by passing one or several *value*(s) as parameter(s).
+
+Otherwise, you can add or modify elements subsequently through assignment. For example:
+
+```4d
+ myCol[10]:="My new element"
+```
+
+If the new element index is beyond the last existing element of the collection, the collection is automatically resized and all new intermediary elements are assigned a **null** value.
+
+You can pass any number of values of any supported type (number, text, date, picture, pointer, object, collection...). Unlike arrays, collections can mix data of different types.
+
+You must pay attention to the following conversion issues:
+
+* If you pass a pointer, it is kept "as is"; it is evaluated using the `JSON Stringify` command
+* Dates are stored as "yyyy-mm-dd" dates or strings with the "YYYY-MM-DDTHH:mm:ss.SSSZ" format, according to the current "dates inside objects" database setting. When converting 4D dates into text prior to storing them in the collection, by default the program takes the local time zone into account. You can modify this behavior using the `Dates inside objects` selector of the `SET DATABASE PARAMETER` command.
+* If you pass a time, it is stored as a number of milliseconds (Real).
+
+#### Example 1
+
+
+
+You want to create a new empty collection and assign it to a 4D collection variable:
+
+```4d
+ var $myCol : Collection
+ $myCol:=New collection
+ //$myCol=[]
+```
+
+#### Example 2
+
+You want to create a prefilled collection:
+
+```4d
+ var $filledColl : Collection
+ $filledColl:=New collection(33;"mike";"november";->myPtr;Current date)
+ //$filledColl=[33,"mike","november","->myPtr","2017-03-28T22:00:00.000Z"]
+```
+
+#### Example 3
+
+You create a new collection and then add a new element:
+
+```4d
+ var $coll : Collection
+ $coll:=New collection("a";"b";"c")
+ //$coll=["a","b","c"]
+ $coll[9]:="z" //add a 10th element with value "z"
+ $vcolSize:=$coll.length //10
+ //$coll=["a","b","c",null,null,null,null,null,null,"z"]
+```
+
+
+
+
+## `New shared collection`
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**New shared collection** {( *...value* : any )} : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|value|Number, Text, Date, Time, Boolean, Shared object, Shared collection|->|Shared collection's value(s)|
+|Result|Collection|<-|New shared collection|
+
+
+
+#### Description
+
+The `New shared collection` command creates a new empty or prefilled shared collection and returns its reference.
+
+Adding an element to this collection must be surrounded by the [`Use...End`](Concepts/shared.md#useend-use) use structure, otherwise an error is generated. Reading an element without a structure is, however, possible.
+
+>For more information on shared collections, please refer to the [Shared objects and collections](Concepts/shared.md) page.
+
+If you do not pass any parameters, `New shared collection` creates an empty shared collection and returns its reference.
+
+You must assign the returned reference to a 4D variable of the Collection type.
+
+>Keep in mind that `var : Collection` or `C_COLLECTION` statements declare a variable of the `Collection` type but does not create any collection.
+
+Optionally, you can prefill the new shared collection by passing one or several *value*(s) as parameter(s). Otherwise, you can add or modify elements subsequently through object notation assignment (see example).
+
+If the new element index is beyond the last existing element of the shared collection, the collection is automatically resized and all new intermediary elements are assigned a **null** value.
+
+You can pass any number of values of the following supported types:
+
+* number (real, longint...). Number values are always stored as reals.
+* text
+* boolean
+* date
+* time (stored as number of milliseconds - real)
+* null
+* shared object(*)
+* shared collection(*)
+
+>Unlike standard (not shared) collections, shared collections do not support pictures, pointers, and objects or collections that are not shared.
+
+(*)When a shared object or collection is added to a shared collection, they share the same *locking identifier*. For more information on this point, refer to the **4D Developer**'s guide.
+
+#### Example
+
+```4d
+ $mySharedCol:=New shared collection("alpha";"omega")
+ Use($mySharedCol)
+ $mySharedCol[1]:="beta"
+ End use
+```
+
+
+
+
+## .average()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.average**( {*propertyPath* : Text } ) : Real
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|propertyPath|Text|->|Object property path to be used for calculation|
+|Result|Real, Undefined|<-|Arithmetic mean (average) of collection values|
+
+
+
+
+#### Description
+
+The `.average()` function returns the arithmetic mean (average) of defined values in the collection instance.
+
+
+
+Only numerical elements are taken into account for the calculation (other element types are ignored).
+
+If the collection contains objects, pass the *propertyPath* parameter to indicate the object property to take into account.
+
+`.average()` returns `undefined` if:
+
+* the collection is empty,
+* the collection does not contain numerical elements,
+* *propertyPath* is not found in the collection.
+
+
+#### Example 1
+
+```4d
+ var $col : Collection
+ $col:=New collection(10;20;"Monday";True;6)
+ $vAvg:=$col.average() //12
+```
+
+#### Example 2
+
+```4d
+ var $col : Collection
+ $col:=New collection
+ $col.push(New object("name";"Smith";"salary";10000))
+ $col.push(New object("name";"Wesson";"salary";50000))
+ $col.push(New object("name";"Gross";"salary";10500))
+ $vAvg:=$col.average("salary") //23500
+```
+
+
+
+
+
+
+## .clear()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.clear()** : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Collection|<-|Original collection with all elements removed|
+
+
+
+#### Description
+
+The `.clear()` function removes all elements from the collection instance and returns an empty collection.
+
+>This function modifies the original collection.
+
+#### Example
+
+```4d
+var $col : Collection
+$col:=New collection(1;2;5)
+$col.clear()
+$vSize:=$col.length //$vSize=0
+```
+
+
+
+
+
+
+
+
+## .combine()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.combine**( *col2* : Collection {; *index* : Integer } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|col2|Collection|->|Collection to combine|
+|index|Integer|->|Position to which insert elements to combine in collection (default=length+1)|
+|Result|Collection|<-|Original collection containing combined element(s)|
+
+
+
+#### Description
+
+The `.combine()` function inserts *col2* elements at the end or at the specified *index* position in the collection instance and returns the edited collection. Unlike the `.insert()` function, `.combine()` adds each value of *col2* in the original collection, and not as a single collection element.
+
+>This function modifies the original collection.
+
+By default, *col2* elements are added at the end of the orginal collection. You can pass in *index* the position where you want the *col2* elements to be inserted in the collection.
+
+>**Warning**: Keep in mind that collection elements are numbered from 0.
+
+* If *index* > the length of the collection, the actual starting *index* will be set to the length of the collection.
+* If *index* < 0, it is recalculated as *index:=index+length* (it is considered as the offset from the end of the collection).
+* If the calculated value is negative, *index* is set to 0.
+
+
+#### Example
+
+```4d
+var $c; $fruits : Collection
+$c:=New collection(1;2;3;4;5;6)
+$fruits:=New collection("Orange";"Banana";"Apple";"Grape")
+$c.combine($fruits;3) //[1,2,3,"Orange","Banana","Apple","Grape",4,5,6]
+```
+
+
+
+
+
+
+
+
+## .concat()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.concat**( *value* : any { *;...valueN* } ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|value|Number, Text, Object, Collection, Date, Time, Boolean, Picture|->|Value(s) to concatenate. If *value* is a collection, all collection elements are added to the original collection|
+|Result|Collection|<-|New collection with value(s) added to the original collection|
+
+
+
+#### Description
+
+The `.concat()` function returns a new collection containing the elements of the original collection with all elements of the *value* parameter added to the end.
+
+>This function does not modify the original collection.
+
+If *value* is a collection, all its elements are added as new elements at the end of the original collection. If *value* is not a collection, it is added itself as a new element.
+
+
+#### Example
+
+```4d
+var $c : Collection
+$c:=New collection(1;2;3;4;5)
+$fruits:=New collection("Orange";"Banana";"Apple";"Grape")
+$fruits.push(New object("Intruder";"Tomato"))
+$c2:=$c.concat($fruits) //[1,2,3,4,5,"Orange","Banana","Apple","Grape",{"Intruder":"Tomato"}]
+$c2:=$c.concat(6;7;8) //[1,2,3,4,5,6,7,8]
+```
+
+
+
+
+
+
+
+## .copy()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R3|New *ck shared* option. New *groupWith* parameters|
+|v16 R6|Added|
+
+
+
+**.copy**() : Collection
**.copy**( *option* : Integer ) : Collection
**.copy**( *option* : Integer ; *groupWithCol* : Collection ) : Collection
**.copy**( *option* : Integer ; *groupWithObj* : Object ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|option|Integer|->|`ck resolve pointers`: resolve pointers before copying,
`ck shared`: return a shared collection|
+|groupWithCol |Collection|->|Shared collection to be grouped with the resulting collection|
+|groupWithObj |Object|->|Shared object to be grouped with the resulting collection|
+|Result|Collection|<-|Deep copy of the original collection|
+
+
+
+#### Description
+
+The `.copy()` function returns a deep copy of the collection instance.***Deep copy*** means that objects or collections within the original collection are duplicated and do not share any reference with the returned collection.
+
+>This function does not modify the original collection.
+
+If passed, the *option* parameter can contain one of the following constants (or both):
+
+|option |Description|
+|---|---|
+|`ck resolve pointers`| If the original collection contains pointer type values, by default the copy also contains the pointers. However, you can resolve pointers when copying by passing the ck resolve pointers. In this case, each pointer present in the collection is evaluated when copying and its dereferenced value is used.|
+|`ck shared`| By default, copy() returns a regular (not shared) collection, even if the command is applied to a shared collection. Pass the ck shared constant to create a shared collection. In this case, you can use the groupWith parameter to associate the shared collection with another collection or object (see below).|
+
+The *groupWithCol* or *groupWithObj* parameters allow you to designate a collection or an object with which the resulting collection should be associated.
+
+
+#### Example 1
+
+We want to copy the *$lastnames* regular (non shared) collection into the *$sharedObject* shared object. To do this, we must create a shared copy of the collection (*$sharedLastnames*).
+
+```4d
+var $sharedObject : Object
+var $lastnames;$sharedLastnames : Collection
+var $text : Text
+
+$sharedObject:=New shared object
+
+$text:=Document to text(Get 4D folder(Current resources folder)+"lastnames.txt")
+$lastnames:=JSON Parse($text) //$lastnames is a regular collection
+
+$sharedLastnames:=$lastnames.copy(ck shared) //$sharedLastnames is a shared collection
+
+//Now we can put $sharedLastnames into $sharedObject
+Use($sharedObject)
+ $sharedObject.lastnames:=$sharedLastnames
+End use
+```
+
+
+#### Example 2
+
+We want to combine *$sharedColl1* and *$sharedColl2*. Since they belong to different shared groups, a direct combination would result in an error. Therefore, we must make a shared copy of *$sharedColl1* and designate *$sharedColl2* as a shared group for the copy.
+
+```4d
+var $sharedColl1;$sharedColl2;$copyColl : Collection
+
+$sharedColl1:=New shared collection(New shared object("lastname";"Smith"))
+$sharedColl2:=New shared collection(New shared object("lastname";"Brown"))
+
+//$copyColl belongs to the same shared group as $sharedColl2
+ $copyColl:=$sharedColl1.copy(ck shared;$sharedColl2)
+ Use($sharedColl2)
+ $sharedColl2.combine($copyColl)
+ End use
+```
+
+#### Example 3
+
+We have a regular collection (*$lastnames*) and we want to put it in the **Storage** of the application. To do this, we must create a shared copy beforehand (*$sharedLastnames*).
+
+```4d
+var $lastnames;$sharedLastnames : Collection
+var $text : Text
+
+$text:=Document to text(Get 4D folder(Current resources folder)+"lastnames.txt")
+$lastnames:=JSON Parse($text) //$lastnames is a regular collection
+
+$sharedLastnames:=$lastnames.copy(ck shared) // shared copy
+
+Use(Storage)
+ Storage.lastnames:=$sharedLastnames
+End use
+```
+
+#### Example 4
+
+This example illustrates the use of the `ck resolve pointers` option:
+
+```4d
+ var $col : Collection
+ var $p : Pointer
+ $p:=->$what
+
+ $col:=New collection
+ $col.push(New object("alpha";"Hello";"num";1))
+ $col.push(New object("beta";"You";"what";$p))
+
+ $col2:=$col.copy()
+ $col2[1].beta:="World!"
+ ALERT($col[0].alpha+" "+$col2[1].beta) //displays "Hello World!"
+
+ $what:="You!"
+ $col3:=$col2.copy(ck resolve pointers)
+ ALERT($col3[0].alpha+" "+$col3[1].what) //displays "Hello You!"
+```
+
+
+
+
+
+
+
+
+
+## .count()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.count**( { *propertyPath* : Text } ) : Real
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|propertyPath|Text|->|Object property path to be used for calculation|
+|Result|Real|<-|Number of elements in the collection|
+
+
+
+#### Description
+
+The `.count()` function returns the number of non-null elements in the collection.
+
+If the collection contains objects, you can pass the *propertyPath* parameter. In this case, only elements that contain the *propertyPath* are taken into account.
+
+#### Example
+
+```4d
+ var $col : Collection
+ var $count1;$count2 : Real
+ $col:=New collection(20;30;Null;40)
+ $col.push(New object("name";"Smith";"salary";10000))
+ $col.push(New object("name";"Wesson";"salary";50000))
+ $col.push(New object("name";"Gross";"salary";10500))
+ $col.push(New object("lastName";"Henry";"salary";12000))
+ $count1:=$col.count() //$count1=7
+ $count2:=$col.count("name") //$count2=3
+
+```
+
+
+
+
+
+
+
+
+## .countValues()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.countValues**( *value* : any {; *propertyPath* : Text } ) : Real
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|value|Text, Number, Boolean, Date, Object, Collection|->|Value to count|
+|propertyPath|Text|->|Object property path to be used for calculation|
+|Result|Real|<-|Number of occurrences of the value |
+
+
+
+#### Description
+
+The `.countValues()` function returns the number of times value is found in the collection.
+
+You can pass in *value*:
+
+* a scalar value (text, number, boolean, date),
+* an object or a collection reference.
+
+
+For an element to be found, the type of *value* must be equivalent to the type of the element; the method uses the equality operator.
+
+The optional *propertyPath* parameter allows you to count values inside a collection of objects: pass in *propertyPath* the path of the property whose values you want to count.
+
+>This function does not modify the original collection.
+
+#### Example 1
+
+```4d
+ var $col : Collection
+ var $vCount : Integer
+ $col:=New collection(1;2;5;5;5;3;6;4)
+ $vCount:=$col.countValues(5) // $vCount=3
+```
+
+
+#### Example 2
+
+```4d
+ var $col : Collection
+ var $vCount : Integer
+ $col:=New collection
+ $col.push(New object("name";"Smith";"age";5))
+ $col.push(New object("name";"Wesson";"age";2))
+ $col.push(New object("name";"Jones";"age";3))
+ $col.push(New object("name";"Henry";"age";4))
+ $col.push(New object("name";"Gross";"age";5))
+ $vCount:=$col.countValues(5;"age") //$vCount=2
+```
+
+
+#### Example 3
+
+```4d
+ var $numbers; $letters : Collection
+ var $vCount : Integer
+
+ $letters:=New collection("a";"b";"c")
+ $numbers:=New collection(1;2;$letters;3;4;5)
+
+ $vCount:=$numbers.countValues($letters) //$vCount=1
+```
+
+
+
+
+
+
+
+
+
+## .distinct()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.distinct**( {*option* : Integer} ) : Collection
**.distinct**( *propertyPath* : Text {; *option* : Integer } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|option|Integer|->|`ck diacritical`: diacritical evaluation ("A" # "a" for example)|
+|propertyPath|Text|->|Path of attribute whose distinct values you want to get|
+|Result|Collection|<-|New collection with only distinct values|
+
+
+
+#### Description
+
+The `.distinct()` function returns a collection containing only distinct (different) values from the original collection.
+
+>This function does not modify the original collection.
+
+The returned collection is automatically sorted. **Null** values are not returned.
+
+By default, a non-diacritical evaluation is performed. If you want the evaluation to be case sensitive or to differentiate accented characters, pass the `ck diacritical` constant in the *option* parameter.
+
+If the collection contains objects, you can pass the *propertyPath* parameter to indicate the object property whose distinct values you want to get.
+
+
+
+#### Example
+
+```4d
+ var $c; $c2 : Collection
+ $c:=New collection
+ $c.push("a";"b";"c";"A";"B";"c";"b";"b")
+ $c.push(New object("size";1))
+ $c.push(New object("size";3))
+ $c.push(New object("size";1))
+ $c2:=$c.distinct() //$c2=["a","b","c",{"size":1},{"size":3},{"size":1}]
+ $c2:=$c.distinct(ck diacritical) //$c2=["a","A","b","B","c",{"size":1},{"size":3},{"size":1}]
+ $c2:=$c.distinct("size") //$c2=[1,3]
+```
+
+
+
+
+
+
+
+
+## .equal()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.equal**( *collection2* : Collection {; *option* : Integer } ) : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|collection2|Collection|->|Collection to compare|
+|option|Integer|->|`ck diacritical`: diacritical evaluation ("A" # "a" for example)
+|Result|Boolean|<-|True if collections are identical, false otherwise|
+
+
+
+#### Description
+
+The `.equal()` function compares the collection with collection2 and returns **true** if they are identical (deep comparison).
+
+By default, a non-diacritical evaluation is performed. If you want the evaluation to be case sensitive or to differentiate accented characters, pass the `ck diacritical` constant in the option parameter.
+
+>Elements with **Null** values are not equal to Undefined elements.
+
+#### Example
+
+```4d
+ var $c; $c2 : Collection
+ var $b : Boolean
+
+ $c:=New collection(New object("a";1;"b";"orange");2;3)
+ $c2:=New collection(New object("a";1;"b";"orange");2;3;4)
+ $b:=$c.equal($c2) // false
+
+ $c:=New collection(New object("1";"a";"b";"orange");2;3)
+ $c2:=New collection(New object("a";1;"b";"orange");2;3)
+ $b:=$c.equal($c2) // false
+
+ $c:=New collection(New object("a";1;"b";"orange");2;3)
+ $c2:=New collection(New object("a";1;"b";"ORange");2;3)
+ $b:=$c.equal($c2) // true
+
+ $c:=New collection(New object("a";1;"b";"orange");2;3)
+ $c2:=New collection(New object("a";1;"b";"ORange");2;3)
+ $b:=$c.equal($c2;ck diacritical) //false
+```
+
+
+
+
+
+
+
+## .every()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.every**( *methodName* : Text { ;*...param* : any } ) : Boolean
**.every**( *startFrom* : Integer ; *methodName* : Text { ;*...param* : any } ) : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|startFrom|Integer|->|Index to start the test at|
+|methodName|Text|->|Name of the method to call for the test|
+|param|Mixed|->|Parameter(s) to pass to methodName|
+|Result|Boolean|<-|True if all elements successfully passed the test|
+
+
+
+#### Description
+
+The `.every()` function returns **true** if all elements in the collection successfully passed a test implemented in the provided *methodName* method.
+
+
+In *methodName*, pass the name of the method to use to evaluate collection elements, along with its parameter(s) in *param* (optional). *methodName* can perform any test, with or without the parameter(s). This method receives an `Object` in first parameter ($1) and must set *$1.result* to true for every element fulfilling the test.
+
+*methodName* receives the following parameters:
+
+* in *$1.value*: element value to be evaluated
+* in *$2*: param
+* in *$N...*: paramN...
+
+*methodName* sets the following parameter(s):
+
+* *$1.result* (Boolean): **true** if the element value evaluation is successful, **false** otherwise.
+* *$1.stop* (Boolean, optional): **true** to stop the method callback. The returned value is the last calculated.
+
+In all cases, at the point when the `.every()` function encounters the first collection element returning **false** in *$1.result*, it stops calling *methodName* and returns **false**.
+
+By default, `.every()` tests the whole collection. Optionally, you can pass in *startFrom* the index of the element from which to start the test.
+
+* If *startFrom* >= the collection's length, **false** is returned, which means the collection is not tested.
+* If *startFrom* < 0, it is considered as the offset from the end of the collection ( *startFrom:=startFrom+length*).
+* If *startFrom* = 0, the whole collection is searched (default).
+
+
+#### Example 1
+
+```4d
+var $c : Collection
+var $b : Boolean
+$c:=New collection
+$c.push(5;3;1;4;6;2)
+$b:=$c.every("NumberGreaterThan0") //returns true
+$c.push(-1)
+$b:=$c.every("NumberGreaterThan0") //returns false
+```
+
+With the following ***NumberGreaterThan0*** method:
+
+```4d
+$1.result:=$1.value>0
+```
+
+#### Example 2
+
+This example tests that all elements of a collection are of the real type:
+
+```4d
+var $c : Collection
+var $b : Boolean
+$c:=New collection
+$c.push(5;3;1;4;6;2)
+$b:=$c.every("TypeLookUp";Is real) //$b=true
+$c:=$c.push(New object("name";"Cleveland";"zc";35049))
+$c:=$c.push(New object("name";"Blountsville";"zc";35031))
+$b:=$c.every("TypeLookUp";Is real) //$b=false
+```
+
+With the following ***TypeLookUp*** method:
+
+```4d
+#DECLARE ($toEval : Object ; $param : Integer) //$1; $2
+If(Value type($toEval.value)=$param)
+ $toEval.result:=True
+End if
+```
+
+
+
+
+
+
+
+## .extract()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.extract**( *propertyPath* : Text { ; *option* : Integer } ) : Collection
**.extract**( *propertyPath* : Text ; *targetPath* : Text { ;...*propertyPathN* : Text ;... *targetPathN* : Text } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|propertyPath|Text|->|Object property path whose values must be extracted to the new collection|
+|targetpath|Text|->|Target property path or property name|
+|option|Integer|->|`ck keep null`: include null properties in the returned collection (ignored by default). Parameter ignored if *targetPath* passed.|
+|Result|Collection|<-|New collection containing extracted values|
+
+
+
+#### Description
+
+The `.extract()` function creates and returns a new collection containing *propertyPath* values extracted from the original collection of objects.
+
+>This function does not modify the original collection.
+
+The contents of the returned collection depends on the *targetPath* parameter:
+
+* If the *targetPath* parameter is omitted, `.extract()` populates the new collection with the *propertyPath* values of the original collection.
+
+ By default, elements for which *propertyPath* is null or undefined are ignored in the resulting collection. You can pass the `ck keep null` constant in the *option* parameter to include these values as null elements in the returned collection.
+
+
+* If one or more *targetPath* parameter(s) are passed, `.extract()` populates the new collection with the *propertyPath* properties and each element of the new collection is an object with *targetPath* properties filled with the corresponding *propertyPath* properties. Null values are kept (*option* parameter is ignored with this syntax).
+
+
+#### Example 1
+
+```4d
+var $c : Collection
+$c:=New collection
+$c.push(New object("name";"Cleveland"))
+$c.push(New object("zip";5321))
+$c.push(New object("name";"Blountsville"))
+$c.push(42)
+$c2:=$c.extract("name") // $c2=[Cleveland,Blountsville]
+$c2:=$c.extract("name";ck keep null) //$c2=[Cleveland,null,Blountsville,null]
+```
+
+
+#### Example 2
+
+
+```4d
+var $c : Collection
+$c:=New collection
+$c.push(New object("zc";35060))
+$c.push(New object("name";Null;"zc";35049))
+$c.push(New object("name";"Cleveland";"zc";35049))
+$c.push(New object("name";"Blountsville";"zc";35031))
+$c.push(New object("name";"Adger";"zc";35006))
+$c.push(New object("name";"Clanton";"zc";35046))
+$c.push(New object("name";"Clanton";"zc";35045))
+$c2:=$c.extract("name";"City") //$c2=[{City:null},{City:Cleveland},{City:Blountsville},{City:Adger},{City:Clanton},{City:Clanton}]
+$c2:=$c.extract("name";"City";"zc";"Zip") //$c2=[{Zip:35060},{City:null,Zip:35049},{City:Cleveland,Zip:35049},{City:Blountsville,Zip:35031},{City:Adger,Zip:35006},{City:Clanton,Zip:35046},{City:Clanton,Zip:35045}]
+```
+
+
+
+
+
+
+
+
+## .fill()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.fill**( *value* : any ) : Collection
**.fill**( *value* : any ; *startFrom* : Integer { ; *end* : Integer } ) : Collection
+
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|value|number, Text, Collection, Object, Date, Boolean|->|Filling value|
+|startFrom|Integer|->|Start index (included)|
+|end|Integer|->|End index (not included)|
+|Result|collection|<-|Original collection with filled values|
+
+
+
+#### Description
+
+The `.fill()` function fills the collection with the specified *value*, optionally from *startFrom* index to *end* index, and returns the resulting collection.
+
+>This function modifies the original collection.
+
+* If the *startFrom* parameter is omitted, *value* is set to all collection elements (*startFrom*=0).
+* If the *startFrom* parameter is passed and *end* omitted, *value* is set to collection elements starting at *startFrom* to the last element of the collection (*end*=length).
+* If both the *startFrom* parameter and *end* are passed, *value* is set to collection elements starting at *startFrom* to the element *end*.
+
+In case of inconsistency, the following rules apply:
+
+* If *startFrom* < 0, it is recalculated as *startFrom:=startFrom+length* (it is considered as the offset from the end of the collection). If the calculated value is negative, *startFrom* is set to 0.
+* If *end* < 0 , it is recalculated as *end:=end+length*.
+* If *end* < *startFrom* (passed or calculated values), the method does nothing.
+
+
+#### Example
+
+```4d
+ var $c : Collection
+ $c:=New collection(1;2;3;"Lemon";Null;"";4;5)
+ $c.fill("2") // $c:=[2,2,2,2,2,2,2,2]
+ $c.fill("Hello";5) // $c=[2,2,2,2,2,Hello,Hello,Hello]
+ $c.fill(0;1;5) // $c=[2,0,0,0,0,Hello,Hello,Hello]
+ $c.fill("world";1;-5) //-5+8=3 -> $c=[2,"world","world",0,0,Hello,Hello,Hello]
+```
+
+
+
+
+
+
+
+
+## .filter()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.filter**( *methodName* : Text { ; *...param* : any } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|methodName|Text|->|Name of the function to call to filter the collection|
+|param|Mixed|->|Parameter(s) to pass to *methodName*|
+|Result|Collection|<-|New collection containing filtered elements (shallow copy)|
+
+
+
+#### Description
+
+The `.filter()` function returns a new collection containing all elements of the original collection for which *methodName* method result is **true**. This function returns a ***shallow copy***, which means that objects or collections in both collections share the same reference. If the original collection is a shared collection, the returned collection is also a shared collection.
+
+>This function does not modify the original collection.
+
+In *methodName*, pass the name of the method to use to evaluate collection elements, along with its parameter(s) in *param* (optional). *methodName* can perform any test, with or without the parameter(s). This method receives an `Object` in first parameter ($1) and must set *$1.result* to **true** for each element fulfilling the condition and thus, to push to the new collection.
+
+*methodName* receives the following parameters:
+
+* in *$1.value*: element value to be filtered
+* in *$2*: *param*
+* in *$N...*: param2...paramN
+
+*methodName* sets the following parameter(s):
+
+* *$1.result* (boolean): **true** if the element value matches the filter condition and must be kept.
+* *$1.stop* (boolean, optional): **true** to stop the method callback. The returned value is the last calculated.
+
+
+#### Example 1
+
+You want to get the collection of text elements whose length is smaller than 6:
+
+```4d
+ var $col;$colNew : Collection
+ $col:=New collection("hello";"world";"red horse";66;"tim";"san jose";"miami")
+ $colNew:=$col.filter("LengthLessThan";6)
+ //$colNew=["hello","world","tim","miami"]
+```
+
+The code for ***LengthLessThan*** method is:
+
+```4d
+ C_OBJECT($1)
+ C_LONGINT($2)
+ If(Value type($1.value)=Is text)
+ $1.result:=(Length($1.value))<$2
+ End if
+```
+
+#### Example 2
+
+You want to filter elements according to their value type:
+
+```4d
+ var $c;$c2;$c3 : Collection
+ $c:=New collection(5;3;1;4;6;2)
+ $c.push(New object("name";"Cleveland";"zc";35049))
+ $c.push(New object("name";"Blountsville";"zc";35031))
+ $c2:=$c.filter("TypeLookUp";Is real) // $c2=[5,3,1,4,6,2]
+ $c3:=$c.filter("TypeLookUp";Is object)
+ // $c3=[{name:Cleveland,zc:35049},{name:Blountsville,zc:35031}]
+```
+
+The code for ***TypeLookUp*** is:
+
+```4d
+ C_OBJECT($1)
+ C_LONGINT($2)
+ If(OB Get type($1;"value")=$2)
+
+
+ $1.result:=True
+ End if
+```
+
+
+
+
+
+
+
+
+## .find()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.find**( *methodName* : Text { ; *...param* : any } ) : any
**.find**( *startFrom* : Integer ; *methodName* : Text { ; *...param* : any } ) : any
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|startFrom|Integer|->|Index to start the search at|
+|methodName|Text|->|Name of the function to call for the find|
+|param|any|->|Parameter(s) to pass to *methodName*|
+|Result|any |<-|First value found, or Undefined if not found|
+
+
+
+#### Description
+
+The `.find()` function returns the first value in the collection for which *methodName*, applied on each element, returns **true**.
+
+>This function does not modify the original collection.
+
+In *methodName*, pass the name of the method to use to evaluate collection elements, along with its parameter(s) in *param* (optional). *methodName* can perform any test, with or without the parameter(s). This method receives an `Object` in the first parameter ($1) and must set *$1.result* to **true** for the first element fulfilling the condition.
+
+*methodName* receives the following parameters:
+
+* in *$1.value:* element value to be evaluated
+* in *$2: param*
+* in *$N...*: param2...paramN
+
+*methodName* sets the following parameter(s):
+
+* *$1.result* (boolean): **true** if the element value matches the search condition.
+* *$1.stop* (boolean, optional): **true** to stop the method callback. The returned value is the last calculated.
+
+By default, `.find()` searches in the whole collection. Optionally, you can pass in *startFrom* the index of element from which to start the search.
+
+* If *startFrom* >= the collection's length, -1 is returned, which means the collection is not searched.
+* If *startFrom* < 0, it is considered as the offset from the end of the collection (*startFrom:=startFrom+length*).
+ **Note**: Even if *startFrom* is negative, the collection is still searched from left to right.
+* If *startFrom* = 0, the whole collection is searched (default).
+
+
+#### Example 1
+
+You want to get the first element with a length smaller than 5:
+
+```4d
+ var $col : Collection
+ $col:=New collection("hello";"world";4;"red horse";"tim";"san jose")
+ $value:=$col.find("LengthLessThan";5) //$value="tim"
+```
+
+The code for ***LengthLessThan*** method is:
+
+```4d
+ var $1 : Object
+ var $2 : Integer
+ If(Value type($1.value)=Is text)
+ $1.result:=(Length($1.value))<$2
+ End if
+```
+
+#### Example 2
+
+You want to find a city name within a collection:
+
+```4d
+ var $c; $c2 : Collection
+ $c:=New collection
+ $c.push(New object("name";"Cleveland";"zc";35049))
+ $c.push(New object("name";"Blountsville";"zc";35031))
+ $c.push(New object("name";"Adger";"zc";35006))
+ $c.push(New object("name";"Clanton";"zc";35046))
+ $c.push(New object("name";"Clanton";"zc";35045))
+ $c2:=$c.find("FindCity";"Clanton") //$c2={name:Clanton,zc:35046}
+```
+
+The code for ***FindCity*** is:
+
+```4d
+ var $1 : Object
+ var $2 : Text
+ $1.result:=$1.value.name=$2 //name is a property name of objects in the collection
+```
+
+
+
+
+
+
+
+
+## .findIndex()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+
+**.findIndex**( *methodName* : Text { ; *...param* : any } ) : Integer
**.findIndex**( *startFrom* : Integer ; *methodName* : Text { ; *...param* : any } ) : Integer
+
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|startFrom|Integer|->|Index to start the search at|
+|methodName|Text|->|Name of the function to call for the find|
+|param|any|->|Parameter(s) to pass to *methodName*|
+|Result|Integer |<-|Index of first value found, or -1 if not found|
+
+
+
+#### Description
+
+The `.findIndex()` function returns the index, in the collection, of the first value for which *methodName*, applied on each element, returns **true**.
+
+>This function does not modify the original collection.
+
+In *methodName*, pass the name of the method to use to evaluate collection elements, along with its parameter(s) in *param* (optional). *methodName* can perform any test, using or not the parameter(s). This method receives an `Object` as first parameter ($1) and must set *$1.result* to **true** for the first element fulfilling the condition.
+
+*methodName* receives the following parameters:
+
+* in *$1.value*: element value to be evaluated
+* in *$2: param*
+* in *$N...*: param2...paramN
+
+*methodName* sets the following parameter(s):
+
+* *$1.result* (boolean): **true** if the element value matches the search condition.
+* *$1.stop* (boolean, optional): **true** to stop the method callback. The returned value is the last calculated.
+
+By default, `.findIndex()` searches in the whole collection. Optionally, you can pass in *startFrom* the index of element from which to start the search.
+
+* If *startFrom* >= the collection's length, -1 is returned, which means the collection is not searched.
+* If *startFrom* < 0, it is considered as the offset from the end of the collection (*startFrom:=startFrom+length*).
+ **Note**: Even if *startFrom* is negative, the collection is still searched from left to right.
+* If *startFrom* = 0, the whole collection is searched (default).
+
+#### Example
+
+You want to find the position of the first city name within a collection:
+
+```4d
+ var $c : Collection
+ var $val2;$val3 : Integer
+ $c:=New collection
+ $c.push(New object("name";"Cleveland";"zc";35049))
+ $c.push(New object("name";"Blountsville";"zc";35031))
+ $c.push(New object("name";"Adger";"zc";35006))
+ $c.push(New object("name";"Clanton";"zc";35046))
+ $c.push(New object("name";"Clanton";"zc";35045))
+ $val2:=$c.findIndex("FindCity";"Clanton") // $val2=3
+ $val3:=$c.findIndex($val2+1;"FindCity";"Clanton") //$val3=4
+```
+
+The code for ***FindCity*** method is:
+
+```4d
+ var $1 : Object
+ var $2 : Text
+ $1.result:=$1.value.name=$2
+```
+
+
+
+
+
+
+
+
+
+## .indexOf()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.indexOf**( *toSearch* : expression { ; *startFrom* : Integer } ) : Integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|toSearch|expression|->|Expression to search in the collection|
+|startFrom|Integer|->|Index to start the search at|
+|Result|Integer |<-|Index of the first occurrence of toSearch in the collection, -1 if not found|
+
+
+
+#### Description
+
+The `.indexOf()` function searches the *toSearch* expression among collection elements and returns the index of the first found occurrence, or -1 if it was not found.
+
+
+>This function does not modify the original collection.
+
+In *toSearch*, pass the expression to find in the collection. You can pass:
+
+* a scalar value (text, number, boolean, date),
+* the null value,
+* an object or a collection reference.
+
+*toSearch* must match exactly the element to find (the same rules as for the equality operator of the data type are applied).
+
+Optionally, you can pass the index of collection from which to start the search in *startFrom*.
+
+* If *startFrom* >= the collection's length, -1 is returned, which means the collection is not searched.
+* If *startFrom* < 0, it is considered as the offset from the end of the collection (*startFrom:=startFrom+length*).
+ **Note**: Even if *startFrom* is negative, the collection is still searched from left to right.
+* If *startFrom* = 0, the whole collection is searched (default).
+
+#### Example
+
+
+
+
+
+```4d
+ var $col : Collection
+ var $i : Integer
+ $col:=New collection(1;2;"Henry";5;3;"Albert";6;4;"Alan";5)
+ $i:=$col.indexOf(3) //$i=4
+ $i:=$col.indexOf(5;5) //$i=9
+ $i:=$col.indexOf("al@") //$i=5
+ $i:=$col.indexOf("Hello") //$i=-1
+```
+
+
+
+
+
+
+
+
+## .indices()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.indices**( *queryString* : Text { ; *...value* : any } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|queryString|Text|->|Search criteria|
+|value|any|->|Value(s) to compare when using placeholder(s)|
+|Result|Collection |<-|Element index(es) matching queryString in the collection|
+
+
+
+#### Description
+
+The `.indices()` function works exactly the same as the [`.query()`](#query) function but returns indexes, in the original collection, of object collection elements that match the *queryString* search conditions, and not elements themselves. Indexes are returned in ascending order.
+
+>This function does not modify the original collection.
+
+The *queryString* parameter uses the following syntax:
+
+```4d
+propertyPath comparator value {logicalOperator propertyPath comparator value}
+```
+
+For a detailed description of the *queryString* and *value* parameters, please refer to the `dataClass.query()` function.
+
+#### Example
+
+
+```4d
+ var $c; $icol : Collection
+ $c:=New collection
+ $c.push(New object("name";"Cleveland";"zc";35049))
+ $c.push(New object("name";"Blountsville";"zc";35031))
+
+ $c.push(New object("name";"Adger";"zc";35006))
+ $c.push(New object("name";"Clanton";"zc";35046))
+ $c.push(New object("name";"Clanton";"zc";35045))
+ $icol:=$c.indices("name = :1";"Cleveland") // $icol=[0]
+ $icol:=$c.indices("zc > 35040") // $icol=[0,3,4]
+```
+
+
+
+
+
+
+
+## .insert()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.insert**( *index* : Integer ; *element* : any ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|index|Integer|->|Where to insert the element|
+|element|any|->|Element to insert in the collection|
+|Result|Collection |<-|Original collection containing inserted element|
+
+
+
+#### Description
+
+The `.insert()` function inserts *element* at the specified *index* position in the collection instance and returns the edited collection.
+
+>This function modifies the original collection.
+
+In *index*, pass the position where you want the element to be inserted in the collection.
+
+>**Warning**: Keep in mind that collection elements are numbered from 0.
+
+* If *index* > the length of the collection, actual starting index will be set to the length of the collection.
+* If *index* <0, it is recalculated as *index:=index+length* (it is considered as the offset from the end of the collection).
+* If the calculated value is negative, index is set to 0.
+
+Any type of element accepted by a collection can be inserted, even another collection.
+
+#### Example
+
+```4d
+ var $col : Collection
+ $col:=New collection("a";"b";"c";"d") //$col=["a","b","c","d"]
+ $col.insert(2;"X") //$col=["a","b","X","c","d"]
+ $col.insert(-2;"Y") //$col=["a","b","X","Y","c","d"]
+ $col.insert(-10;"Hi") //$col=["Hi","a","b","X","Y","c","d"]
+```
+
+
+
+
+
+
+
+
+## .join()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.join**( *delimiter* : Text { ; *option* : Integer } ) : Text
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|delimiter|Text|->|Separator to use between elements|
+|option|Integer|->|`ck ignore null or empty`: ignore null and empty strings in the result|
+|Result|Text |<-|String containing all elements of the collection, separated by delimiter|
+
+
+
+#### Description
+
+The `.join()` function converts all elements of the collection to strings and concatenates them using the specified *delimiter* string as separator.The function returns the resulting string.
+
+>This function does not modify the original collection.
+
+By default, null or empty elements of the collection are returned in the resulting string. Pass the `ck ignore null or empty` constant in the *option* parameter if you want to remove them from the resulting string.
+
+#### Example
+
+
+```4d
+ var $c : Collection
+ var $t1;$t2 : Text
+ $c:=New collection(1;2;3;"Paris";Null;"";4;5)
+ $t1:=$c.join("|") //1|2|3|Paris|null||4|5
+ $t2:=$c.join("|";ck ignore null or empty) //1|2|3|Paris|4|5
+```
+
+
+
+
+
+
+
+## .lastIndexOf()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.lastIndexOf**( *toSearch* : expression { ; *startFrom* : Integer } ) : Integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|toSearch|expression|->|The element that is to be searched for within the collection|
+|startFrom|Integer|->|Index to start the search at|
+|Result|Integer |<-|Index of last occurrence of toSearch in the collection, -1 if not found|
+
+
+
+#### Description
+
+The `.lastIndexOf()` function searches the *toSearch* expression among collection elements and returns the index of the last occurrence, or -1 if it was not found.
+
+>This function does not modify the original collection.
+
+In *toSearch*, pass the expression to find in the collection. You can pass:
+
+* a scalar value (text, number, boolean, date),
+* the null value,
+* an object or a collection reference.
+
+*toSearch* must match exactly the element to find (the same rules as for the equality operator are applied).
+
+Optionally, you can pass the index of collection from which to start a reverse search in *startFrom*.
+
+* If *startFrom* >= the collection's length minus one (coll.length-1), the whole collection is searched (default).
+* If *startFrom* < 0, it is recalculated as *startFrom:=startFrom+length* (it is considered as the offset from the end of the collection). If the calculated value is negative, -1 is returned (the collection is not searched).
+ **Note:** Even if *startFrom* is negative, the collection is still searched from right to left.
+* If *startFrom* = 0, -1 is returned, which means the collection is not searched.
+
+#### Example
+
+
+```4d
+ var $col : Collection
+ var $pos1;$pos2;$pos3;$pos4;$pos5 : Integer
+ $col:=Split string("a,b,c,d,e,f,g,h,i,j,e,k,e";",") //$col.length=13
+ $pos1:=$col.lastIndexOf("e") //returns 12
+ $pos2:=$col.lastIndexOf("e";6) //returns 4
+ $pos3:=$col.lastIndexOf("e";15) //returns 12
+ $pos4:=$col.lastIndexOf("e";-2) //returns 10
+ $pos5:=$col.lastIndexOf("x") //returns -1
+```
+
+
+
+
+
+
+
+## .length
+
+History
+|Version|Changes|
+|---|---|
+|v16 R5|Added|
+
+
+
+**.length** : Integer
+
+
+
+#### Description
+
+The `.length` property returns the number of elements in the collection.
+
+The `.length` property is initialized when the collection is created. Adding or removing elements updates the length, if necessary. This property is **read-only** (you cannot use it to set the size of the collection).
+
+#### Example
+
+
+```4d
+ var $col : Collection //$col.length initialized to 0
+ $col:=New collection("one";"two";"three") //$col.length updated to 3
+ $col[4]:="five" //$col.length updated to 5
+ $vSize:=$col.remove(0;3).length //$vSize=2
+```
+
+
+
+
+
+
+
+## .map()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.map**( *methodName* : Text { ; *...param* : any } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|methodName|Text|->|Name of method used to transform the collection elements|
+|param|any|->|Parameter(s) for the method|
+|Result|Collection |<-|Collection of transformed values|
+
+
+
+#### Description
+
+The `.map()` function creates a new collection based upon the result of the call of the *methodName* method on each element of the original collection. Optionally, you can pass parameters to *methodName* using the *param* parameter(s). `.map()` always returns a collection with the same size as the original collection.
+
+>This function does not modify the original collection.
+
+In *methodName*, pass the name of the method to use to evaluate collection elements, along with its parameter(s) in *param* (optional). *methodName* can perform any operation, with or without the parameter(s).
+
+*methodName* receives the following parameters:
+
+* in *$1.value* (any type): element value to be mapped
+* in *$2* (any type): *param*
+* in *$N...* (any type): *paramN...*
+
+*methodName* sets the following parameter(s):
+
+
+* *$1.result* (any type): new transformed value to add to the resulting collection
+* *$1.stop* (boolean): **true** to stop the method callback. The returned value is the last calculated.
+
+#### Example
+
+
+```4d
+ var $c; $c2 : Collection
+ $c:=New collection(1;4;9;10;20)
+ $c2:=$c.map("Percentage";$c.sum())
+ //$c2=[2.27,9.09,20.45,22.73,45.45]
+```
+
+Here is the ***Percentage*** method:
+
+```4d
+ var $1 : Object
+ var $2 : Real
+ $1.result:=Round(($1.value/$2)*100;2)
+```
+
+
+
+
+
+
+
+
+
+## .max()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.max**( { *propertyPath* : Text } ) : any
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|propertyPath|Text|->|Object property path to be used for evaluation|
+|Result|Boolean, Text, Number, Collection, Object, Date |<-|Maximum value in the collection|
+
+
+
+#### Description
+
+The `.max()` function returns the element with the highest value in the collection (the last element of the collection as it would be sorted in ascending order using the [`.sort()`](#sort) function).
+
+>This function does not modify the original collection.
+
+If the collection contains different types of values, the `.max()` function will return the maximum value within the last element type in the type list order (see [`.sort()`](#sort) description).
+
+If the collection contains objects, pass the *propertyPath* parameter to indicate the object property whose maximum value you want to get.
+
+If the collection is empty, `.max()` returns *Undefined*.
+
+#### Example
+
+
+```4d
+ var $col : Collection
+ $col:=New collection(200;150;55)
+ $col.push(New object("name";"Smith";"salary";10000))
+ $col.push(New object("name";"Wesson";"salary";50000))
+ $col.push(New object("name";"Alabama";"salary";10500))
+ $max:=$col.max() //{name:Alabama,salary:10500}
+ $maxSal:=$col.max("salary") //50000
+ $maxName:=$col.max("name") //"Wesson"
+```
+
+
+
+
+
+
+
+## .min()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.min**( { *propertyPath* : Text } ) : any
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|propertyPath|Text|->|Object property path to be used for evaluation|
+|Result|Boolean, Text, Number, Collection, Object, Date |<-|Minimum value in the collection|
+
+
+
+#### Description
+
+The `.min()` function returns the element with the smallest value in the collection (the first element of the collection as it would be sorted in ascending order using the [`.sort()`](#sort) function).
+
+>This function does not modify the original collection.
+
+If the collection contains different types of values, the `.min()` function will return the minimum value within the first element type in the type list order (see [`.sort()`](#sort) description).
+
+If the collection contains objects, pass the *propertyPath* parameter to indicate the object property whose minimum value you want to get.
+
+If the collection is empty, `.min()` returns *Undefined*.
+
+#### Example
+
+
+```4d
+ var $col : Collection
+ $col:=New collection(200;150;55)
+ $col.push(New object("name";"Smith";"salary";10000))
+ $col.push(New object("name";"Wesson";"salary";50000))
+ $col.push(New object("name";"Alabama";"salary";10500))
+ $min:=$col.min() //55
+ $minSal:=$col.min("salary") //10000
+ $minName:=$col.min("name") //"Alabama"
+```
+
+
+
+
+
+
+
+## .orderBy()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.orderBy**( ) : Collection
**.orderBy**( *pathStrings* : Text ) : Collection
**.orderBy**( *pathObjects* : Collection ) : Collection
**.orderBy**( *ascOrDesc* : Integer ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+
+|pathStrings|Text|->|Property path(s) on which to order the collection|
+|pathObjects|Collection|->|Collection of criteria objects|
+|ascOrDesc|Integer|->|`ck ascending` or `ck descending` (scalar values)|
+|Result|Collection |<-|Ordered copy of the collection (shallow copy)|
+
+
+
+#### Description
+
+The `.orderBy()` function returns a new collection containing all elements of the collection in the specified order.
+
+This function returns a *shallow copy*, which means that objects or collections in both collections share the same reference. If the original collection is a shared collection, the returned collection is also a shared collection.
+
+>This function does not modify the original collection.
+
+If you pass no parameter, the function orders scalar values in the collection in ascending order (other element types such as objects or collections are returned unordered). You can modify this automatic order by passing the `ck ascending` or `ck descending` constants in the *ascOrDesc* parameter (see below).
+
+You can also pass a criteria parameter to define how the collection elements must be sorted. Three syntaxes are supported for this parameter:
+
+* *pathStrings* : Text (formula). **Syntax**: `propertyPath1 {desc or asc}, propertyPath2 {desc or asc},...` (default order: asc). *pathStrings* contains a formula made of 1 to x property paths and (optionally) sort orders, separated by commas. The order in which the properties are passed determines the sorting priority of the collection elements. By default, properties are sorted in ascending order. You can set the sort order of a property in the criteria string, separated from the property path by a single space: pass "asc" to sort in ascending order or "desc" in descending order.
+
+* *pathObjects* : Collection. You can add as many objects in the *pathObjects* collection as necessary. By default, properties are sorted in ascending order ("descending" is false). Each element of the collection contains an object structured in the following way:
+
+```4d
+{
+ "propertyPath": string,
+ "descending": boolean
+}
+```
+
+* *ascOrDesc* : Integer. You pass one of the following constants from the **Objects and collections** theme:
+
+ |Constant| Type|Value|Comment|
+ |---|---|---|---|
+ |ck ascending|Longint|0|Elements are ordered in ascending order (default)|
+ |ck descending|Longint|1|Elements are ordered in descending order
+
+ This syntax orders scalar values in the collection only (other element types such as objects or collections are returned unordered).
+
+If the collection contains elements of different types, they are first grouped by type and sorted afterwards. Types are returned in the following order:
+
+1. null
+2. booleans
+3. strings
+4. numbers
+5. objects
+6. collections
+7. dates
+
+#### Example 1
+
+Ordering a collection of numbers in ascending and descending order:
+
+```4d
+ var $c; $c2; $3 : Collection
+ $c:=New collection
+ For($vCounter;1;10)
+ $c.push(Random)
+ End for
+ $c2:=$c.orderBy(ck ascending)
+ $c3:=$c.orderBy(ck descending)
+```
+
+
+#### Example 2
+
+Ordering a collection of objects based on a text formula with property names:
+
+```4d
+ var $c; $c2 : Collection
+ $c:=New collection
+ For($vCounter;1;10)
+ $c.push(New object("id";$vCounter;"value";Random))
+ End for
+ $c2:=$c.orderBy("value desc")
+ $c2:=$c.orderBy("value desc, id")
+ $c2:=$c.orderBy("value desc, id asc")
+```
+
+Ordering a collection of objects with a property path:
+
+```4d
+ var $c; $c2 : Collection
+ $c:=New collection
+ $c.push(New object("name";"Cleveland";"phones";New object("p1";"01";"p2";"02")))
+ $c.push(New object("name";"Blountsville";"phones";New object("p1";"00";"p2";"03")))
+
+ $c2:=$c.orderBy("phones.p1 asc")
+```
+
+
+#### Example 3
+
+Ordering a collection of objects using a collection of criteria objects:
+
+```4d
+ var $crit; $c; $c2 : COllection
+ $crit:=New collection
+ $c:=New collection
+ For($vCounter;1;10)
+ $c.push(New object("id";$vCounter;"value";Random))
+ End for
+ $crit.push(New object("propertyPath";"value";"descending";True))
+ $crit.push(New object("propertyPath";"id";"descending";False))
+ $c2:=$c.orderBy($crit)
+```
+
+Ordering with a property path:
+
+```4d
+ var $crit; $c; $c2 : Collection
+ $c:=New collection
+ $c.push(New object("name";"Cleveland";"phones";New object("p1";"01";"p2";"02")))
+ $c.push(New object("name";"Blountsville";"phones";New object("p1";"00";"p2";"03")))
+ $crit:=New collection(New object("propertyPath";"phones.p2";"descending";True))
+ $c2:=$c.orderBy($crit)
+```
+
+
+
+
+
+
+
+
+
+## .orderByMethod()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.orderByMethod**( *methodName* : Text { ; ...*extraParam* : expression } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|methodName|Text|->|Name of method used to specify the sorting order|
+|extraParam|expression|->|Parameter(s) for the method |
+|Result|Collection |<-|Sorted copy of the collection (shallow copy)|
+
+
+
+#### Description
+
+The `.orderByMethod()` function returns a new collection containing all elements of the collection in the order defined through the *methodName* method.
+
+This function returns a *shallow copy*, which means that objects or collections in both collections share the same reference. If the original collection is a shared collection, the returned collection is also a shared collection.
+
+>This function does not modify the original collection.
+
+In *methodName*, pass a comparison method that compares two values and returns **true** in *$1.result* if the first value is lower than the second value. You can provide additional parameters to *methodName* if necessary.
+
+* *methodName* will receive the following parameters:
+ * $1 (object), where:
+ * *$1.value* (any type): first element value to be compared
+ * *$1.value2* (any type): second element value to be compared
+ * $2...$N (any type): extra parameters
+* *methodName* sets the following parameter:
+ * *$1.result* (boolean): **true** if *$1.value < $1.value2*, **false** otherwise
+
+#### Example 1
+
+You want to sort a collection of strings in numerical order rather than alphabetical order:
+
+```4d
+ var $c; $c2; $c3 : Collection
+ $c:=New collection
+ $c.push("33";"4";"1111";"222")
+ $c2:=$c.orderBy() //$c2=["1111","222","33","4"], alphabetical order
+ $c3:=$c.orderByMethod("NumAscending") // $c3=["4","33","222","1111"]
+```
+
+ Here is the code for ***NumAscending***:
+
+
+```4d
+ $1.result:=Num($1.value)Length(String($1.value2))
+```
+
+#### Example 3
+
+You want to sort a collection by character code or language:
+
+```4d
+var $strings1; $strings2 : Collection
+$strings1:=New collection("Alpha";"Charlie";"alpha";"bravo";"Bravo";"charlie")
+
+//using the character code:
+$strings2:=$strings1.orderByMethod("sortCollection";sk character codes)
+// result : ["Alpha","Bravo","Charlie","alpha","bravo","charlie"]
+
+//using the language:
+$strings2:=$string1s.orderByMethod("sortCollection";sk strict)
+// result : ["alpha","Alpha","bravo","Bravo","charlie","Charlie"]
+```
+
+The ***sortCollection*** method:
+
+```4d
+var$1Object
+var$2Integer // sort option
+
+$1.result:=(Compare strings($1.value;$1.value2;$2)<0)
+```
+
+
+
+
+
+
+
+
+## .pop()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+
+**.pop()** : any
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|any |<-|Last element of collection|
+
+
+
+#### Description
+
+The `.pop()` function removes the last element from the collection and returns it as the function result.
+
+>This function modifies the original collection.
+
+When applied to an empty collection, `.pop()` returns ***undefined***.
+
+#### Example
+
+`.pop()`, used in conjunction with [`.push()`](#push), can be used to implement a first-in, last-out stack feature:
+
+```4d
+ var $stack : Collection
+ $stack:=New collection //$stack=[]
+ $stack.push(1;2) //$stack=[1,2]
+ $stack.pop() //$stack=[1] Returns 2
+ $stack.push(New collection(4;5)) //$stack=[[1,[4,5]]
+ $stack.pop() //$stack=[1] Returns [4,5]
+ $stack.pop() //$stack=[] Returns 1
+```
+
+
+
+
+
+
+
+
+
+## .push()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.push**( *element* : any { ;...*elementN* } ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|element|Mixed|->|Element(s) to add to the collection|
+|Result|Collection |<-|Original collection containing added elements|
+
+
+
+#### Description
+
+The `.push()` function appends one or more *element*(s) to the end of the collection instance and returns the edited collection.
+
+>This function modifies the original collection.
+
+
+#### Example 1
+
+```4d
+ var $col : Collection
+ $col:=New collection(1;2) //$col=[1,2]
+ $col.push(3) //$col=[1,2,3]
+ $col.push(6;New object("firstname";"John";"lastname";"Smith"))
+ //$col=[1,2,3,6,{firstname:John,lastname:Smith}
+```
+
+
+
+#### Example 2
+
+You want to sort the resutling collection:
+
+```4d
+ var $col; $sortedCol : Collection
+ $col:=New collection(5;3;9) //$col=[5,3,9]
+ $sortedCol:=$col.push(7;50).sort()
+ //$col=[5,3,9,7,50]
+ //$sortedCol=[3,5,7,9,50]
+```
+
+
+
+
+
+
+
+
+
+
+## .query()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Support of querySettings|
+|v16 R6|Added|
+
+
+
+**.query**( *queryString* : Text ; *...value* : any ) : Collection
**.query**( *queryString* : Text ; *querySettings* : Object ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|queryString|Text|->|Search criteria|
+|value|Mixed|->|Value(s) to compare when using placeholder(s)|
+|querySettings|Object|->|Query options: parameters, attributes|
+|Result|Collection |<-|Element(s) matching queryString in the collection|
+
+
+
+#### Description
+
+The `.query()` function returns all elements of a collection of objects that match the search conditions defined by *queryString* and (optionally) *value* or *querySettings*. If the original collection is a shared collection, the returned collection is also a shared collection.
+
+>This function does not modify the original collection.
+
+The *queryString* parameter uses the following syntax:
+
+```4d
+propertyPath comparator value {logicalOperator propertyPath comparator value}
+```
+
+For detailed information on how to build a query using *queryString*, *value* and *querySettings* parameters, please refer to the [`dataClass.query()`](DataClassClass.md#query) function description.
+
+> Formulas are not supported by the `collection.query()` function, neither in the *queryString* parameter nor as *formula* object parameter.
+
+#### Example 1
+
+```4d
+ var $c; $c2; $c3 : Collection
+ $c:=New collection
+ $c.push(New object("name";"Cleveland";"zc";35049))
+ $c.push(New object("name";"Blountsville";"zc";35031))
+ $c.push(New object("name";"Adger";"zc";35006))
+ $c.push(New object("name";"Clanton";"zc";35046))
+ $c.push(New object("name";"Clanton";"zc";35045))
+ $c2:=$c.query("name = :1";"Cleveland") //$c2=[{name:Cleveland,zc:35049}]
+ $c3:=$c.query("zc > 35040") //$c3=[{name:Cleveland,zc:35049},{name:Clanton,zc:35046},{name:Clanton,zc:35045}]
+```
+
+
+#### Example 2
+
+
+```4d
+ var $c : Collection
+ $c:=New collection
+ $c.push(New object("name";"Smith";"dateHired";!22-05-2002!;"age";45))
+ $c.push(New object("name";"Wesson";"dateHired";!30-11-2017!))
+ $c.push(New object("name";"Winch";"dateHired";!16-05-2018!;"age";36))
+
+ $c.push(New object("name";"Sterling";"dateHired";!10-5-1999!;"age";Null))
+ $c.push(New object("name";"Mark";"dateHired";!01-01-2002!))
+```
+
+This example returns persons whose name contains "in":
+
+```4d
+ $col:=$c.query("name = :1";"@in@")
+ //$col=[{name:Winch...},{name:Sterling...}]
+```
+
+This example returns persons whose name does not begin with a string from a variable (entered by the user, for example):
+
+```4d
+ $col:=$c.query("name # :1";$aString+"@")
+ //if $astring="W"
+ //$col=[{name:Smith...},{name:Sterling...},{name:Mark...}]
+```
+
+This example returns persons whose age is not known (property set to null or undefined):
+
+```4d
+ $col:=$c.query("age=null") //placeholders not allowed with "null"
+ //$col=[{name:Wesson...},{name:Sterling...},{name:Mark...}]
+```
+
+This example returns persons hired more than 90 days ago:
+
+```4d
+ $col:=$c.query("dateHired < :1";(Current date-90))
+ //$col=[{name:Smith...},{name:Sterling...},{name:Mark...}] if today is 01/10/2018
+```
+
+
+#### Example 3
+
+More examples of queries can be found in the `dataClass.query()` page.
+
+
+
+
+
+
+
+
+## .reduce()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.reduce**( *methodName* : Text ) : any
**.reduce**( *methodName* : Text ; *initValue* : any { ; *...param* : expression } ) : any
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|methodName |Text|->|Name of the function to call to process collection elements|
+|initValue |Text, Number, Object, Collection, Date, Boolean|->|Value to use as the first argument to the first call of *methodName*|
+|param |expression|->|Parameter(s) to pass to *methodName*|
+|Result|Text, Number, Object, Collection, Date, Boolean |<-|Result of the accumulator value|
+
+
+
+#### Description
+
+
+The `.reduce()` function applies the *methodName* callback method against an accumulator and each element in the collection (from left to right) to reduce it to a single value.
+
+>This function does not modify the original collection.
+
+In *methodName*, pass the name of the method to use to evaluate collection elements, along with its parameter(s) in param (optional). *methodName* takes each collection element and performs any desired operation to accumulate the result into *$1.accumulator*, which is returned in *$1.value*.
+
+You can pass the value to initialize the accumulator in *initValue*. If omitted, *$1.accumulator* starts with *Undefined*.
+
+*methodName* receives the following parameters:
+
+* in *$1.value*: element value to be processed
+* in *$2: param*
+* in *$N...*: *paramN...*
+
+*methodName* sets the following parameter(s):
+
+* *$1.accumulator*: value to be modified by the function and which is initialized by *initValue*.
+* *$1.stop* (boolean, optional): **true** to stop the method callback. The returned value is the last calculated.
+
+
+#### Example 1
+
+
+```4d
+ C_COLLECTION($c)
+ $c:=New collection(5;3;5;1;3;4;4;6;2;2)
+ $r:=$c.reduce("Multiply";1) //returns 86400
+```
+
+With the following ***Multiply*** method:
+
+```4d
+ If(Value type($1.value)=Is real)
+ $1.accumulator:=$1.accumulator*$1.value
+ End if
+```
+
+#### Example
+
+This example allows reducing several collection elements to a single one:
+
+```4d
+ var $c;$r : Collection
+ $c:=New collection
+ $c.push(New collection(0;1))
+ $c.push(New collection(2;3))
+ $c.push(New collection(4;5))
+ $c.push(New collection(6;7))
+ $r:=$c.reduce("Flatten") //$r=[0,1,2,3,4,5,6,7]
+```
+
+With the following ***Flatten*** method:
+
+```4d
+ If($1.accumulator=Null)
+ $1.accumulator:=New collection
+ End if
+ $1.accumulator.combine($1.value)
+```
+
+
+
+
+
+
+
+## .remove()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.remove**( *index* : Integer { ; *howMany* : Integer } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|index |Integer|->|Element at which to start removal|
+|howMany |Integer|->|Number of elements to remove, or 1 element if omitted|
+|Result|Collection|<-|Original collection without removed element(s)|
+
+
+
+#### Description
+
+The `.remove()` function removes one or more element(s) from the specified *index* position in the collection and returns the edited collection.
+
+>This function modifies the original collection.
+
+In *index*, pass the position where you want the element to be removed from the collection.
+
+>**Warning**: Keep in mind that collection elements are numbered from 0. If *index* is greater than the length of the collection, actual starting index will be set to the length of the collection.
+
+* If *index* < 0, it is recalculated as *index:=index+length* (it is considered as the offset from the end of the collection).
+* If the calculated value < 0, *index* is set to 0.
+* If the calculated value > the length of the collection, *index* is set to the length.
+
+In *howMany*, pass the number of elements to remove from *index*. If *howMany* is not specified, then one element is removed.
+
+
+
+If you try to remove an element from an empty collection, the method does nothing (no error is generated).
+
+
+#### Example
+
+
+```4d
+ var $col : Collection
+ $col:=New collection("a";"b";"c";"d";"e";"f";"g";"h")
+ $col.remove(3) // $col=["a","b","c","e","f","g","h"]
+ $col.remove(3;2) // $col=["a","b","c","g","h"]
+ $col.remove(-8;1) // $col=["b","c","g","h"]
+ $col.remove(-3;1) // $col=["b","g","h"]
+```
+
+
+
+
+
+
+
+
+
+## .resize()
+
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+
+
+**.resize**( *size* : Integer { ; *defaultValue* : any } ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|size |Integer|->|New size of the collection|
+|defaultValue |Number, Text, Object, Collection, Date, Boolean|->|Default value to fill new elements|
+|Result|Collection|<-|Resized original collection|
+
+
+
+#### Description
+
+The `.resize()` function sets the collection length to the specified new size and returns the resized collection.
+
+>This function modifies the original collection.
+
+* If *size* < collection length, exceeding elements are removed from the collection.
+* If *size* > collection length, the collection length is increased to size.
+
+By default, new elements are filled will **null** values. You can specify the value to fill in added elements using the *defaultValue* parameter.
+
+#### Example
+
+
+```4d
+ var $c : Collection
+ $c:=New collection
+ $c.resize(10) // $c=[null,null,null,null,null,null,null,null,null,null]
+
+ $c:=New collection
+ $c.resize(10;0) // $c=[0,0,0,0,0,0,0,0,0,0]
+
+ $c:=New collection(1;2;3;4;5)
+ $c.resize(10;New object("name";"X")) //$c=[1,2,3,4,5,{name:X},{name:X},{name:X},{name:X},{name:X}]
+
+ $c:=New collection(1;2;3;4;5)
+ $c.resize(2) //$c=[1,2]
+
+```
+
+
+
+
+
+
+
+
+
+## .reverse()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.reverse( )** : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Collection|<-|Inverted copy of the collection|
+
+
+
+#### Description
+
+The `.reverse()` function returns a deep copy of the collection with all its elements in reverse order. If the original collection is a shared collection, the returned collection is also a shared collection.
+
+>This function does not modify the original collection.
+
+#### Example
+
+
+```4d
+ var $c; $c2 : Collection
+ $c:=New collection(1;3;5;2;4;6)
+ $c2:=$c.reverse() //$c2=[6,4,2,5,3,1]
+```
+
+
+
+
+
+
+
+
+## .shift()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.shift()** : any
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|any|<-|First element of collection|
+
+
+
+#### Description
+
+The `.shift()` function removes the first element of the collection and returns it as the function result.
+
+>This function modifies the original collection.
+
+If the collection is empty, this method does nothing.
+
+#### Example
+
+
+```4d
+ var $c : Collection
+ var $val : Variant
+ $c:=New collection(1;2;4;5;6;7;8)
+ $val:=$c.shift()
+ // $val=1
+ // $c=[2,4,5,6,7,8]
+```
+
+
+
+
+
+
+
+
+## .slice()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.slice**( *startFrom* : Integer { ; *end* : Integer } ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|startFrom |Integer |->|Index to start the search at (included)|
+|end |Integer |->|End index (not included)|
+|Result|Collection|<-|New collection containing sliced elements (shallow copy)|
+
+
+
+#### Description
+
+The `.slice()` function returns a portion of a collection into a new collection, selected from *startFrom* index to *end* index (end not included). This function returns a *shallow copy* of the collection. If the original collection is a shared collection, the returned collection is also a shared collection.
+
+>This function does not modify the original collection.
+
+The returned collection contains the element specified by *startFrom* and all subsequent elements up to, but not including, the element specified by *end*. If only the *startFrom* parameter is specified, the returned collection contains all elements from *startFrom* to the last element of the original collection.
+
+* If *startFrom* < 0, it is recalculated as *startFrom:=startFrom+length* (it is considered as the offset from the end of the collection).
+* If the calculated value < 0, *startFrom* is set to 0.
+* If *end* < 0 , it is recalculated as *end:=end+length*.
+* If *end < startFrom* (passed or calculated values), the method does nothing.
+
+#### Example
+
+
+```4d
+ var $c; $nc : Collection
+ $c:=New collection(1;2;3;4;5)
+ $nc:=$c.slice(0;3) //$nc=[1,2,3]
+ $nc:=$c.slice(3) //$nc=[4,5]
+ $nc:=$c.slice(1;-1) //$nc=[2,3,4]
+ $nc:=$c.slice(-3;-2) //$nc=[3]
+```
+
+
+
+
+
+
+
+
+## .some()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.some**( *methodName* : Text { ; *...param* : any } ) : Boolean
**.some**( *startFrom* : Integer ; *methodName* : Text { ; *...param* : any } ) : Boolean
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|startFrom |Integer |->|Index to start the test at|
+|methodName |Text |->|Name of the method to call for the test|
+|param |Mixed |->|Parameter(s) to pass to *methodName*|
+|Result|Boolean|<-|True if at least one element successfully passed the test|
+
+
+
+#### Description
+
+The `.some()` function returns true if at least one element in the collection successfully passed a test implemented in the provided *methodName* method.
+
+
+In *methodName*, pass the name of the method to use to evaluate collection elements, along with its parameter(s) in *param* (optional). *methodName* can perform any test, with or without the parameter(s). This method receives an `Object` as first parameter ($1) and must set *$1.result* to **True** for every element fulfilling the test.
+
+*methodName* receives the following parameters:
+
+* in *$1.value*: element value to be evaluated
+* in *$2*: param
+* in *$N...*: param2...paramN
+
+*methodName* sets the following parameter(s):
+
+* *$1.result* (boolean): **true** if the element value evaluation is successful, **false** otherwise.
+* *$1.stop* (boolean, optional): **true** to stop the method callback. The returned value is the last calculated.
+
+In any case, at the point where `.some()` function encounters the first collection element returning true in *$1.result*, it stops calling *methodName* and returns **true**.
+
+By default, `.some()` tests the whole collection. Optionally, you can pass the index of an element from which to start the test in *startFrom*.
+
+* If *startFrom* >= the collection's length, **False** is returned, which means the collection is not tested.
+* If *startFrom* < 0, it is considered as the offset from the end of the collection.
+* If *startFrom* = 0, the whole collection is searched (default).
+
+
+#### Example
+
+
+```4d
+ var $c : Collection
+ var $b : Boolean
+ $c:=New collection
+ $c.push(-5;-3;-1;-4;-6;-2)
+ $b:=$c.some("NumberGreaterThan0") // returns false
+ $c.push(1)
+ $b:=$c.some("NumberGreaterThan0") // returns true
+
+ $c:=New collection
+ $c.push(1;-5;-3;-1;-4;-6;-2)
+ $b:=$c.some("NumberGreaterThan0") //$b=true
+ $b:=$c.some(1;"NumberGreaterThan0") //$b=false
+```
+
+With the following *NumberGreaterThan0* method:
+
+```4d
+ $1.result:=$1.value>0
+```
+
+
+
+
+
+
+
+
+
+## .sort()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.sort**( *methodName* : Text { ; *...extraParam* : any } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|methodName |Text |->|Name of method used to specify the sorting order|
+|extraParam |any |->|Parameter(s) for the method|
+|Result|Collection|<-|Original collection sorted|
+
+
+
+#### Description
+
+The `.sort()` function sorts the elements of the original collection and also returns the sorted collection.
+
+>This function modifies the original collection.
+
+If `.sort()` is called with no parameters, only scalar values (number, text, date, booleans) are sorted. Elements are sorted by default in ascending order, according to their type.
+
+If you want to sort the collection elements in some other order or sort any type of element, you must supply in *methodName* a comparison method that compares two values and returns **true** in *$1.result* if the first value is lower than the second value. You can provide additional parameters to *methodName* if necessary.
+
+* *methodName* will receive the following parameters:
+ * $1 (object), where:
+ * *$1.value* (any type): first element value to be compared
+ * *$1.value2* (any type): second element value to be compared
+ * $2...$N (any type): extra parameters
+
+*methodName* sets the following parameter:
+ * *$1.result* (boolean): **true** if *$1.value < $1.value2*, **false** otherwise
+
+If the collection contains elements of different types, they are first grouped by type and sorted afterwards. Types are returned in the following order:
+
+1. null
+2. booleans
+3. strings
+4. numbers
+5. objects
+6. collections
+7. dates
+
+#### Example 1
+
+
+```4d
+ var $col; $col2 : Collection
+ $col:=New collection("Tom";5;"Mary";3;"Henry";1;"Jane";4;"Artie";6;"Chip";2)
+ $col2:=$col.sort() // $col2=["Artie","Chip","Henry","Jane","Mary","Tom",1,2,3,4,5,6]
+ // $col=["Artie","Chip","Henry","Jane","Mary","Tom",1,2,3,4,5,6]
+```
+
+#### Example 2
+
+```4d
+ var $col; $col2 : Collection
+ $col:=New collection(10;20)
+ $col2:=$col.push(5;3;1;4;6;2).sort() //$col2=[1,2,3,4,5,6,10,20]
+```
+
+#### Example 3
+
+```4d
+ var $col; $col2; $col3 : Collection
+ $col:=New collection(33;4;66;1111;222)
+ $col2:=$col.sort() //numerical sort: [4,33,66,222,1111]
+ $col3:=$col.sort("numberOrder") //alphabetical sort: [1111,222,33,4,66]
+```
+
+```4d
+ //numberOrder project method
+ var $1 : Object
+ $1.result:=String($1.value)
+
+
+
+
+
+## .sum()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.sum**( { *propertyPath* : Text } ) : Real
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|propertyPath |Text |->|Object property path to be used for calculation|
+|Result|Real|<-|Sum of collection values|
+
+
+
+#### Description
+
+The `.sum()` function returns the sum for all values in the collection instance.
+
+Only numerical elements are taken into account for the calculation (other element types are ignored).
+
+If the collection contains objects, pass the *propertyPath* parameter to indicate the object property to take into account.
+
+`.sum()` returns 0 if:
+
+* the collection is empty,
+* the collection does not contain numerical elements,
+* *propertyPath* is not found in the collection.
+
+#### Example 1
+
+
+```4d
+ var $col : Collection
+ var $vSum : Real
+ $col:=New collection(10;20;"Monday";True;2)
+ $vSum:=$col.sum() //32
+```
+
+#### Example 2
+
+```4d
+ var $col : Collection
+ var $vSum : Real
+ $col:=New collection
+ $col.push(New object("name";"Smith";"salary";10000))
+ $col.push(New object("name";"Wesson";"salary";50000))
+ $col.push(New object("name";"Gross";"salary";10500,5))
+ $vSum:=$col.sum("salary") //$vSum=70500,5
+```
+
+
+
+
+
+
+
+
+## .unshift()
+
+History
+|Version|Changes|
+|---|---|
+|v16 R6|Added|
+
+
+
+**.unshift**( *value* : any { ;...*valueN* : any } ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|value |Text, Number, Object, Collection, Date |->|Value(s) to insert at the beginning of the collection|
+|Result|Real|<-|Collection containing added element(s)
+|
+
+
+
+#### Description
+
+The `.unshift()` function inserts the given *value*(s) at the beginning of the collection and returns the modified collection.
+
+>This function modifies the original collection.
+
+If several values are passed, they are inserted all at once, which means that they appear in the resulting collection in the same order as in the argument list.
+
+
+#### Example
+
+
+```4d
+ var $c : Collection
+ $c:=New collection(1;2)
+ $c.unshift(4) // $c=[4,1,2]
+ $c.unshift(5) //$c=[5,4,1,2]
+ $c.unshift(6;7) // $c=[6,7,5,4,1,2]
+```
+
+
+
+
+
diff --git a/docs/API/CryptoKeyClass.md b/docs/API/CryptoKeyClass.md
new file mode 100644
index 00000000000000..f68975c8035bf8
--- /dev/null
+++ b/docs/API/CryptoKeyClass.md
@@ -0,0 +1,385 @@
+---
+id: CryptoKeyClass
+title: CryptoKey
+---
+
+
+The `CryptoKey` class in the 4D language encapsulates an asymetric encryption key pair.
+
+This class is available from the `4D` class store.
+
+### Example
+
+The following sample code signs and verifies a message using a new ECDSA key pair, for example in order to make a ES256 JSON Web token.
+
+```4d
+ // Generate a new ECDSA key pair
+$key:=4D.CryptoKey.new(New object("type";"ECDSA";"curve";"prime256v1"))
+
+ // Get signature as base64
+$message:="hello world"
+$signature:=$key.sign($message;New object("hash";"SHA256"))
+
+ // Verify signature
+$status:=$key.verify($message;$signature;New object("hash";"SHA256"))
+ASSERT($status.success)
+```
+
+
+### Summary
+||
+|---|
+|[](#4dcryptokeynew)
|
+|[](#curve)
|
+|[](#decrypt)
|
+|[](#encrypt)
|
+|[](#getprivatekey)
|
+|[](#getpublickey)
|
+|[](#sign)
|
+|[](#size)
|
+|[](#type)
|
+|[](#verify)
|
+
+
+
+
+
+
+
+
+## 4D.CryptoKey.new()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+
+**4D.CryptoKey.new**( *settings* : Object ) : 4D.CryptoKey
+
+
+|Parameter|Type||Description|
+|---|---|----|---|
+|settings|Object|->|Settings to generate or load a key pair|
+|result|4D.CryptoKey|<-|Object encapsulating an encryption key pair|
+
+
+The `4D.CryptoKey.new()` function creates a new `4D.CryptoKey` object encapsulating an encryption key pair, based upon the *settings* object parameter. It allows to generate a new RSA or ECDSA key, or to load an existing key pair from a PEM definition.
+
+#### *settings*
+
+|Property|Type|Description|
+|---|---|---|
+|[curve](#curve)|text|Name of ECDSA curve|
+|[pem](#pem)|text|PEM definition of an encryption key to load|
+|[size](#size)|integer|Size of RSA key in bits|
+|[type](#type)|text|Type of the key: "RSA", "ECDSA", or "PEM"|
+
+
+#### *CryptoKey*
+
+The returned `CryptoKey` object encapsulates an encryption key pair. It is a shared object and can therefore be used by multiple 4D processes simultaneously.
+
+
+
+
+## .curve
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+**.curve** : Text
+
+
+
+
+Defined only for ECDSA keys: the normalised curve name of the key.
+Usually "prime256v1" for ES256 (default), "secp384r1" for ES384, "secp521r1" for ES512.
+
+
+
+## .decrypt()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+
+**.decrypt**( *message* : Text ; *options* : Object ) : Object
+
+
+|Parameter|Type||Description|
+|---|---|----|---|
+|message|Text|->|Message string to be decoded using `options.encodingEncrypted` and decrypted.|
+|options|Object|->|Decoding options|
+|Result|Object|<-|Status|
+
+
+
+
+The `.decrypt()` function decrypts the *message* parameter using the **private** key. The algorithm used depends on the type of the key.
+
+The key must be a RSA key, the algorithm is RSA-OAEP (see [RFC 3447](https://tools.ietf.org/html/rfc3447)).
+
+#### *options*
+
+|Property|Type|Description|
+|---|---|---|
+|hash|text|Digest algorithm to use. For example: "SHA256", "SHA384", or "SHA512". |
+|encodingEncrypted|text|Encoding used to convert the `message` parameter into the binary representation to decrypt. Can be "Base64" or "Base64URL". Default is "Base64".|
+|encodingDecrypted|text|Encoding used to convert the binary decrypted message into the result string. Can be "UTF-8", "Base64", or "Base64URL". Default is "UTF-8".|
+
+
+#### *Result*
+
+The function returns a status object with `success` property set to `true` if the *message* could be successfully decrypted.
+
+|Property|Type|Description|
+|---|---|---|
+|success|boolean|True if the message has been successfully decrypted|
+|result|text|Message decrypted and decoded using the `options.encodingDecrypted`|
+|errors|collection|If `success` is `false`, may contain a collection of errors|
+
+
+In case the *message* couldn't be decrypted because it was not encrypted with the same key or algorithm, the `status` object being returned contains an error collection in `status.errors`.
+
+
+
+## .encrypt()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+
+**.encrypt**( *message* : Text ; *options* : Object ) : Text
+
+
+|Parameter|Type||Description|
+|---|---|----|---|
+|message|Text|->|Message string to be encoded using `options.encodingDecrypted` and encrypted.|
+|options|Object|->|Encoding options|
+|Result|Text|<-|Message encrypted and encoded using the `options.encodingEncrypted`|
+
+
+The `.encrypt()` function encrypts the *message* parameter using the **public** key. The algorithm used depends on the type of the key.
+
+The key must be a RSA key, the algorithm is RSA-OAEP (see [RFC 3447](https://tools.ietf.org/html/rfc3447)).
+
+##### *options*
+
+|Property|Type|Description|
+|---|---|---|
+|hash|text|Digest algorithm to use. For example: "SHA256", "SHA384", or "SHA512". |
+|encodingEncrypted|text|Encoding used to convert the binary encrypted message into the result string. Can be "Base64", or "Base64URL". Default is "Base64".|
+|encodingDecrypted|text|Encoding used to convert the `message` parameter into the binary representation to encrypt. Can be "UTF-8", "Base64", or "Base64URL". Default is "UTF-8".|
+
+
+#### *Result*
+
+The returned value is an encrypted message.
+
+
+
+
+
+
+## .getPrivateKey()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+
+**.getPrivateKey()** : Text
+
+
+
+|Parameter|Type||Description|
+|---|---|----|---|
+|Result|Text|<-|Private key in PEM format|
+
+
+The `.getPrivateKey()` function returns the private key of the `CryptoKey` object in PEM format, or an empty string if none is available.
+
+#### *Result*
+
+The returned value is the private key.
+
+
+
+
+
+## .getPublicKey()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+
+**.getPublicKey( )** : Text
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|Result|Text|<-|Public key in PEM format|
+
+
+
+The `.getPublicKey()` function returns the public key of the `CryptoKey` object in PEM format, or an empty string if none is available.
+
+#### *Result*
+
+The returned value is the public key.
+
+
+---
+
+## .pem
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+
+**.pem** : Text
+
+
+PEM definition of an encryption key to load. If the key is a private key, the RSA or ECDSA public key will be deduced from it.
+
+
+
+
+
+## .sign()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+.**sign** (*message* : Text ; *options* : Text) : Text
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|message|Text|->|Message string to sign|
+|options|Object|->|Signing options|
+|Result|Text|<-|Signature in Base64 or Base64URL representation, depending on "encoding" option|
+
+
+The `.sign()` function signs the utf8 representation of a *message* string using the `CryptoKey` object keys and provided *options*. It returns its signature in base64 or base64URL format, depending on the value of the `options.encoding` attribute you passed.
+
+The `CryptoKey` must contain a valid **private** key.
+
+#### *options*
+
+|Property|Type|Description|
+|---|---|---|
+|hash|text|Digest algorithm to use. For example: "SHA256", "SHA384", or "SHA512". When used to produce a JWT, the hash size must match the PS@, ES@, RS@, or PS@ algorithm size|
+|encodingEncrypted|text|Encoding used to convert the binary encrypted message into the result string. Can be "Base64", or "Base64URL". Default is "Base64".|
+|pss|boolean|Use Probabilistic Signature Scheme (PSS). Ignored if the key is not an RSA key. Pass `true` when producing a JWT for PS@ algorithm|
+|encoding|text|ERepresentation to be used for result signature. Possible values: "Base64" or "Base64URL". Default is "Base64".|
+
+
+#### *Result*
+
+The utf8 representation of the *message* string.
+
+
+
+
+## .size
+
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+**.size** : Integer
+
+
+Defined only for RSA keys: the size of the key in bits. Typically 2048 (default).
+
+
+
+## .type
+
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+
+**.type** : Text
+
+
+Name of the key type - "RSA", "ECDSA", "PEM""RSA": an RSA key pair, using `settings.size` as [.size](#size)."ECDSA": an Elliptic Curve Digital Signature Algorithm key pair, using `settings.curve` as [.curve](#curve). Note that ECDSA keys cannot be used for encryption but only for signature."PEM": a key pair definition in PEM format, using `settings.pem` as [.pem](#pem).
+
+
+
+## .verify()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added
+
+
+**.verify**( *message* : Text ; *signature* : Text ; *options* : Object) : object
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|message|Text|->|Message string that was used to produce the signature|
+|signature|Text|->|Signature to verify, in Base64 or Base64URL representation, depending on `options.encoding` value|
+|options|Object|->|Signing options|
+|Result|Object|<-|Status of the verification|
+
+
+The `.verify()` function verifies the base64 signature against the utf8 representation of *message* using the `CryptoKey` object keys and provided *options*.
+
+The `CryptoKey` must contain a valid **public** key.
+
+
+#### *options*
+
+|Property|Type|Description|
+|---|---|---|
+|hash|text|Digest algorithm to use. For example: "SHA256", "SHA384", or "SHA512". When used to produce a JWT, the hash size must match the PS@, ES@, RS@, or PS@ algorithm size|
+|pss|boolean|Use Probabilistic Signature Scheme (PSS). Ignored if the key is not an RSA key. Pass `true` when verifying a JWT for PS@ algorithm|
+|encoding|text|Representation of provided signature. Possible values are "Base64" or "Base64URL". Default is "Base64".
+
+
+#### *Result*
+
+The function returns a status object with `success` property set to `true` if `message` could be successfully verified (i.e. the signature matches).
+
+In case the signature couldn't be verified because it was not signed with the same *message*, key or algorithm, the `status` object being returned contains an error collection in `status.errors`.
+
+|Property|Type|Description|
+|---|---|---|
+|success|boolean|True if the signature matches the message|
+|errors|collection|If `success` is `false`, may contain a collection of errors|
+
+
+
diff --git a/docs/API/DataClassAttributeClass.md b/docs/API/DataClassAttributeClass.md
new file mode 100644
index 00000000000000..35e0c4088bdc8a
--- /dev/null
+++ b/docs/API/DataClassAttributeClass.md
@@ -0,0 +1,461 @@
+---
+id: DataClassAttributeClass
+title: DataClassAttribute
+---
+
+Dataclass attributes are available as properties of their respective classes. For example:
+
+```4d
+ nameAttribute:=ds.Company.name //reference to class attribute
+ revenuesAttribute:=ds.Company["revenues"] //alternate way
+```
+
+This code assigns to *nameAttribute* and *revenuesAttribute* references to the name and revenues attributes of the Company class. This syntax does NOT return values held inside of the attribute, but instead returns references to the attributes themselves. To handle values, you need to go through [**Entities**](EntityClass.md).
+
+`DataClassAttribute` objects have properties that you can read to get information about your dataclass attributes.
+
+> Dataclass attribute objects can be modified, but the underlying database structure will not be altered.
+
+### Summary
+
+||
+|---|
+|[](#autofilled) |
+|[](#exposed)
|
+|[](#fieldnumber)
|
+|[](#fieldtype)
|
+|[](#indexed)
|
+|[](#inversename)
|
+|[](#keywordindexed)
|
+|[](#kind)
|
+|[](#mandatory)
|
+|[](#name)
|
+|[](#readonly)
|
+|[](#relateddataclass)
|
+|[](#type)
|
+|[](#unique)
|
+
+
+
+
+## .autoFilled
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.autoFilled** : Boolean
+
+
+#### Description
+
+The `.autoFilled` property contains True if the attribute value is automatically filled by 4D. This property corresponds to the following 4D field properties:
+
+* "Autoincrement", for numeric type fields
+* "Auto UUID", for UUID (alpha type) fields.
+
+This property is not returned if `.kind` = "relatedEntity" or "relatedEntities".
+
+>For generic programming, you can use **Bool**(dataClassAttribute.autoFilled) to get a valid value (false) even if `.autoFilled` is not returned.
+
+
+
+
+
+## .exposed
+
+History
+|Version|Changes|
+|---|---|
+|v19 R3|Added|
+
+
+
+
+**.exposed** : Boolean
+
+
+#### Description
+
+The `.exposed` property is true if the attribute is exposed in REST.
+
+
+
+
+
+
+
+## .fieldNumber
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.fieldNumber** : Integer
+
+
+#### Description
+
+The `.fieldNumber` property contains the internal 4D field number of the attribute.
+
+This property is not returned if `.kind` = "relatedEntity" or "relatedEntities".
+
+>For generic programming, you can use **Num**(dataClassAttribute.fieldNumber) to get a valid value (0) even if `.fieldNumber` is not returned.
+
+
+
+
+
+
+
+
+## .fieldType
+
+History
+|Version|Changes|
+|---|---|
+|v19 R3|Support of computed attributes|
+
+
+
+
+**.fieldType** : Integer
+
+
+#### Description
+
+The `.fieldType` property contains the 4D database type of the attribute. It depends on the attribute kind (see [`.kind`](#kind)).
+
+**Possible values:**
+
+|dataClassAttribute.kind| fieldType|
+|---|---|
+|storage| Corresponding 4D field type, see [`Value type`](https://doc.4d.com/4dv19/help/command/en/page1509.html)|
+|relatedEntity| 38 (`Is object`) |
+|relatedEntities| 42 (`Is collection`) |
+|calculated|
scalar: corresponding 4D field type, see [`Value type`](https://doc.4d.com/4dv19/help/command/en/page1509.html)entity: 38 (`Is object`)entity selection: 42 (`Is collection)`|
+
+
+
+
+#### See also
+
+[`.type`](#type)
+
+
+## .indexed
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.indexed** : Boolean
+
+
+#### Description
+
+The `.indexed` property contains **True** if there is a B-tree or a Cluster B-tree index on the attribute.
+
+This property is not returned if `.kind` = "relatedEntity" or "relatedEntities".
+
+>For generic programming, you can use **Bool**(dataClassAttribute.indexed) to get a valid value (false) even if `.indexed` is not returned.
+
+
+
+
+
+
+
+## .inverseName
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.inverseName** : Text
+
+
+#### Description
+
+The `.inverseName` property returns the name of the attribute which is at the other side of the relation.
+
+This property is not returned if `.kind` = "storage". It must be of the "relatedEntity" or "relatedEntities" kind.
+
+>For generic programming, you can use **String**(dataClassAttribute.inverseName) to get a valid value ("") even if `.inverseName` is not returned.
+
+
+
+
+
+
+
+## .keyWordIndexed
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.keyWordIndexed** : Boolean
+
+
+#### Description
+
+The `.keyWordIndexed` property contains **True** if there is a keyword index on the attribute.
+
+This property is not returned if [`.kind`](#kind) = "relatedEntity" or "relatedEntities".
+
+>For generic programming, you can use **Bool**(dataClassAttribute.keyWordIndexed) to get a valid value (false) even if `.keyWordIndexed` is not returned.
+
+
+
+
+
+
+## .kind
+
+History
+|Version|Changes|
+|---|---|
+|v19 R3|Added "calculated"|
+
+
+
+
+**.kind** : Text
+
+
+#### Description
+
+The `.kind` property returns the category of the attribute. Returned value can be one of the following:
+
+* "storage": storage (or scalar) attribute, i.e. attribute storing a value, not a reference to another attribute
+* "calculated": computed attribute, i.e. defined through a [`get` function](ORDA/ordaClasses.md#function-get-attributename).
+* "relatedEntity": N -> 1 relation attribute (reference to an entity)
+* "relatedEntities": 1 -> N relation attribute (reference to an entity selection)
+
+
+#### Example
+
+Given the following table and relation:
+
+
+
+```4d
+ var $attKind : Text
+ $attKind:=ds.Employee.lastname.kind //$attKind="storage"
+ $attKind:=ds.Employee.manager.kind //$attKind="relatedEntity"
+ $attKind:=ds.Employee.directReports.kind //$attKind="relatedEntities"
+```
+
+
+
+
+
+
+
+
+## .mandatory
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.mandatory** : Boolean
+
+
+#### Description
+
+The `.mandatory` property contains True if Null value input is rejected for the attribute.
+
+This property is not returned if [`.kind`](#kind) = "relatedEntity" or "relatedEntities".
+
+>For generic programming, you can use **Bool**(dataClassAttribute.mandatory) to get a valid value (false) even if `.mandatory` is not returned.
+
+>**Warning**: This property corresponds to the "Reject NULL value input" field property at the 4D database level. It is unrelated to the existing "Mandatory" property which is a data entry control option for a table.
+
+
+
+
+
+
+
+## .name
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.name** : Text
+
+
+#### Description
+
+The `.name` property returns the name of the `dataClassAttribute` object as string.
+
+#### Example
+
+```4d
+ var $attName : Text
+ $attName:=ds.Employee.lastname.name //$attName="lastname"
+```
+
+
+
+
+
+
+## .readOnly
+
+History
+|Version|Changes|
+|---|---|
+|v19 R3|Added|
+
+
+
+
+
+
+**.readOnly** : Boolean
+
+
+#### Description
+
+The `.readOnly` property is true if the attribute is read-only.
+
+For example, computed attributes without [`set` function](ORDA/ordaClasses.md#function-set-attributename) are read-only.
+
+
+
+
+
+
+## .relatedDataClass
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+
+
+**.relatedDataClass** : Text
+
+
+#### Description
+
+>This property is only available with attributes of the "relatedEntity" or "relatedEntities" [`.kind`](#kind) property.
+
+The `.relatedDataClass` property returns the name of the dataclass related to the attribute.
+
+#### Example
+
+Given the following tables and relations:
+
+
+
+
+
+
+```4d
+ var $relClass1; $relClassN : Text
+ $relClass1:=ds.Employee.employer.relatedDataClass //$relClass1="Company"
+ $relClassN:=ds.Employee.directReports.relatedDataClass //$relClassN="Employee"
+```
+
+
+
+
+
+
+## .type
+
+History
+|Version|Changes|
+|---|---|
+|v19 R3|Support of computed attributes|
+
+
+
+
+**.type** : Text
+
+
+#### Description
+
+The `.type` property contains the conceptual value type of the attribute, useful for generic programming.
+
+The conceptual value type depends on the attribute [`.kind`](#kind).
+
+**Possible values:**
+
+|dataClassAttribute.kind| type| Comment|
+|---|---|---|
+|storage|"blob", "bool", "date", "image", "number", "object", or "string"| "number" is returned for any numeric types including duration. "string" is returned for uuid, alpha and text field types. "blob" attributes are [blob objects](Concepts/dt_blob.md#blob-type), they are handled using the [Blob class](BlobClass.md).|
+|relatedEntity|related dataClass name|Ex: "Companies"|
+|relatedEntities|related dataClass name + "Selection" suffix| Ex: "EmployeeSelection"|
+|calculated|storage: type ("blob", "number", etc.)entity: dataClass nameentity selection: dataClass name + "Selection"||
+
+
+
+#### See also
+
+[`.fieldType`](#fieldtype)
+
+
+
+## .unique
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.unique** : Boolean
+
+
+#### Description
+
+The `.unique` property contains True if the attribute value must be unique. This property corresponds to the "Unique" 4D field property.
+
+This property is not returned if [`.kind`](#kind) = "relatedEntity" or "relatedEntities".
+
+>For generic programming, you can use **Bool**(dataClassAttribute.unique) to get a valid value (false) even if `.unique` is not returned.
+
+
+
+
diff --git a/docs/API/DataClassClass.md b/docs/API/DataClassClass.md
new file mode 100644
index 00000000000000..346d8e2950fccd
--- /dev/null
+++ b/docs/API/DataClassClass.md
@@ -0,0 +1,1255 @@
+---
+id: DataClassClass
+title: DataClass
+---
+
+
+A [DataClass](ORDA/dsMapping.md#dataclass) provides an object interface to a database table. All dataclasses in a 4D application are available as a property of the `ds` [datastore](ORDA/dsMapping.md#datastore).
+
+
+
+### Summary
+
+||
+|---|
+|[](#attributename) |
+|[](#all)
|
+|[](#exposed)
|
+|[](#fromcollection)
|
+|[](#get)
|
+|[](#getdatastore)
|
+|[](#getinfo)
|
+|[](#new)
|
+|[](#newselection)
|
+|[](#query)
|
+
+
+
+
+## .*attributeName*
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+***.attributeName*** : DataClassAttribute
+
+
+#### Description
+
+The attributes of dataclasses are objects that are available directly as properties of these classes.
+
+The returned objects are of the [`DataClassAttribute`](DataClassAttributeClass.md) class. These objects have properties that you can read to get information about your dataclass attributes.
+
+>Dataclass attribute objects can be modified, but the underlying database structure will not be altered.
+
+#### Example 1
+
+```4d
+$salary:=ds.Employee.salary //returns the salary attribute in the Employee dataclass
+$compCity:=ds.Company["city"] //returns the city attribute in the Company dataclass
+```
+
+
+#### Example 2
+
+Considering the following database structure:
+
+
+
+
+```4d
+var $firstnameAtt;$employerAtt;$employeesAtt : Object
+
+ $firstnameAtt:=ds.Employee.firstname
+ //{name:firstname,kind:storage,fieldType:0,type:string,fieldNumber:2,indexed:true,
+ //keyWordIndexed:false,autoFilled:false,mandatory:false,unique:false}
+
+ $employerAtt:=ds.Employee.employer
+ //{name:employer,kind:relatedEntity,relatedDataClass:Company,
+ //fieldType:38,type:Company,inverseName:employees}
+ //38=Is object
+
+ $employeesAtt:=ds.Company.employees
+ //{name:employees,kind:relatedEntities,relatedDataClass:Employee,
+ //fieldType:42,type:EmployeeSelection,inverseName:employer}
+ //42=Is collection
+```
+
+#### Example 3
+
+Considering the following table properties:
+
+
+
+
+```4d
+ var $sequenceNumberAtt : Object
+ $sequenceNumberAtt=ds.Employee.sequenceNumber
+ //{name:sequenceNumber,kind:storage,fieldType:0,type:string,fieldNumber:13,
+ //indexed:true,keyWordIndexed:false,autoFilled:true,mandatory:false,unique:true}
+```
+
+
+
+
+
+
+## .all()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Support of the *settings* parameter|
+|v17|Added|
+
+
+
+
+**.all** ( { *settings* : Object } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|settings|Object|->|Build option: context|
+|Result|4D.EntitySelection|<-|References on all entities related to the Dataclass|
+
+
+
+#### Description
+
+The `.all( )` function queries the datastore to find all the entities related to the dataclass and returns them as an entity selection.
+
+The entities are returned in the default order, which is initially the order in which they were created. Note however that, if entities have been deleted and new ones added, the default order does not reflect the creation order anymore.
+
+If no corresponding entity is found, an empty entity selection is returned.
+
+Lazy loading is applied.
+
+**settings**
+
+In the optional *settings* parameter, you can pass an object containing additional options. The following property is supported:
+
+|Property| Type| Description|
+|---|---|---|
+|context|Text|Label for the optimization context applied to the entity selection. This context will be used by the code that handles the entity selection so that it can benefit from the optimization. This feature is [designed for ORDA client/server processing](ORDA/entities.md#client-server-optimization).|
+
+
+#### Example
+
+```4d
+ var $allEmp : cs.EmployeeSelection
+ $allEmp:=ds.Employee.all()
+```
+
+
+
+
+
+## .exposed
+
+History
+|Version|Changes|
+|---|---|
+|v19 R3|Added|
+
+
+
+
+**.exposed** : Boolean
+
+
+#### Description
+
+The `.exposed` property is true if the dataclass is exposed in REST.
+
+
+
+
+
+## .fromCollection()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Support of the *settings* parameter|
+|v17|Added|
+
+
+
+**.fromCollection**( *objectCol* : Collection { ; *settings* : Object } ) : 4D.EntitySelection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|objectCol |Collection|->|Collection of objects to be mapped with entities|
+|settings |Object|->|Build option: context|
+|Result|4D.EntitySelection|<-|Entity selection filled from the collection|
+
+
+
+#### Description
+
+The `.fromCollection()` function updates or creates entities in the dataclass according to the *objectCol* collection of objects, and returns the corresponding entity selection.
+
+In the *objectCol* parameter, pass a collection of objects to create new or update existing entities of the dataclass. The property names must be the same as attribute names in the dataclass. If a property name does not exist in the dataclass, it is ignored. If an attribute value is not defined in the collection, its value is null.
+
+The mapping between the objects of the collection and the entities is done on the **attribute names** and **matching types**. If an object's property has the same name as an entity's attribute but their types do not match, the entity's attribute is not filled.
+
+**Create or update mode**
+
+For each object of *objectCol*:
+
+* If the object contains a boolean property "\_\_NEW" set to false (or does not contain a boolean "\_\_NEW" property), the entity is updated or created with the corresponding values of the properties from the object. No check is performed in regards to the primary key:
+ * If the primary key is given and exists, the entity is updated. In this case, the primary key can be given as is or with a "\_\_KEY" property (filled with the primary key value).
+ * If the primary key is given (as is) and does not exist, the entity is created
+ * If the primary key is not given, the entity is created and the primary key value is assigned with respect to standard database rules.
+* If the object contains a boolean property "\_\_NEW" set to **true**, the entity is created with the corresponding values of the attributes from the object. A check is performed in regards to the primary key:
+ * If the primary key is given (as is) and exists, an error is sent
+ * If the primary key is given (as is) and does not exist, the entity is created
+ * If the primary is not given, the entity is created and the primary key value is assigned with respect to standard database rules.
+
+ >The "\_\_KEY" property containing a value is taken into account only when the "\_\_NEW" property is set to **false** (or is omitted) and a corresponding entity exists. In all other cases, the "\_\_KEY" property value is ignored, primary key value must be passed "as is".
+
+**Related entities**
+
+The objects of *objectCol* may contain one or more nested object(s) featuring one or more related entities, which can be useful to create or update links between entities.
+
+The nested objects featuring related entities must contain a "\_\_KEY" property (filled with the primary key value of the related entity) or the primary key attribute of the related entity itself. The use of a \_\_KEY property allows independence from the primary key attribute name.
+
+>The content of the related entities cannot be created / updated through this mechanism.
+
+**Stamp**
+
+If a \_\_STAMP attribute is given, a check is performed with the stamp in the datastore and an error can be returned ("Given stamp does not match current one for record# XX of table XXXX"). For more information, see [Entity locking](ORDA/entities.md#entity-locking).
+
+**settings**
+
+In the optional *settings* parameter, you can pass an object containing additional options. The following property is supported:
+
+|Property |Type| Description|
+|---|---|---|
+|context|Text|Label for the optimization context applied to the entity selection. This context will be used by the code that handles the entity selection so that it can benefit from the optimization. This feature is [designed for ORDA client/server processing](ORDA/entities.md#client-server-optimization).|
+
+
+#### Example 1
+
+We want to update an existing entity. The \_\_NEW property is not given, the employee primary key is given and exists:
+
+```4d
+ var $empsCollection : Collection
+ var $emp : Object
+ var $employees : cs.EmployeeSelection
+
+ $empsCollection:=New collection
+ $emp:=New object
+ $emp.ID:=668 //Existing PK in Employee table
+ $emp.firstName:="Arthur"
+ $emp.lastName:="Martin"
+ $emp.employer:=New object("ID";121) //Existing PK in the related dataClass Company
+ // For this employee, we can change the Company by using another existing PK in the related dataClass Company
+ $empsCollection.push($emp)
+ $employees:=ds.Employee.fromCollection($empsCollection)
+```
+
+#### Example 2
+
+We want to update an existing entity. The \_\_NEW property is not given, the employee primary key is with the \_\_KEY attribute and exists:
+
+```4d
+ var $empsCollection : Collection
+ var $emp : Object
+ var $employees : cs.EmployeeSelection
+
+ $empsCollection:=New collection
+ $emp:=New object
+ $emp.__KEY:=1720 //Existing PK in Employee table
+ $emp.firstName:="John"
+ $emp.lastName:="Boorman"
+ $emp.employer:=New object("ID";121) //Existing PK in the related dataClass Company
+ // For this employee, we can change the Company by using another existing PK in the related dataClass Company
+ $empsCollection.push($emp)
+ $employees:=ds.Employee.fromCollection($empsCollection)
+```
+
+#### Example 3
+
+We want to simply create a new entity from a collection:
+
+```4d
+ var $empsCollection : Collection
+ var $emp : Object
+ var $employees : cs.EmployeeSelection
+
+ $empsCollection:=New collection
+ $emp:=New object
+ $emp.firstName:="Victor"
+ $emp.lastName:="Hugo"
+ $empsCollection.push($emp)
+ $employees:=ds.Employee.fromCollection($empsCollection)
+```
+
+#### Example 4
+
+We want to create an entity. The \_\_NEW property is True, the employee primary key is not given:
+
+```4d
+ var $empsCollection : Collection
+ var $emp : Object
+ var $employees : cs.EmployeeSelection
+
+ $empsCollection:=New collection
+ $emp:=New object
+ $emp.firstName:="Mary"
+ $emp.lastName:="Smith"
+ $emp.employer:=New object("__KEY";121) //Existing PK in the related dataClass Company
+ $emp.__NEW:=True
+ $empsCollection.push($emp)
+ $employees:=ds.Employee.fromCollection($empsCollection)
+
+
+
+
+
+
+```
+
+#### Example 5
+
+We want to create an entity. The \_\_NEW property is omitted, the employee primary key is given and does not exist:
+
+```4d
+ var $empsCollection : Collection
+ var $emp : Object
+ var $employees : cs.EmployeeSelection
+
+ $empsCollection:=New collection
+ $emp:=New object
+ $emp.ID:=10000 //Unexisting primary key
+ $emp.firstName:="Françoise"
+ $emp.lastName:="Sagan"
+ $empsCollection.push($emp)
+ $employees:=ds.Employee.fromCollection($empsCollection)
+```
+
+#### Example 6
+
+In this example, the first entity will be created and saved but the second will fail since they both use the same primary key:
+
+```4d
+ var $empsCollection : Collection
+ var $emp; $emp2 : Object
+ var $employees : cs.EmployeeSelection
+
+ $empsCollection:=New collection
+ $emp:=New object
+ $emp.ID:=10001 // Unexisting primary key
+ $emp.firstName:="Simone"
+ $emp.lastName:="Martin"
+ $emp.__NEW:=True
+ $empsCollection.push($emp)
+
+ $emp2:=New object
+ $emp2.ID:=10001 // Same primary key, already existing
+ $emp2.firstName:="Marc"
+ $emp2.lastName:="Smith"
+ $emp2.__NEW:=True
+ $empsCollection.push($emp2)
+ $employees:=ds.Employee.fromCollection($empsCollection)
+ //first entity is created
+ //duplicated key error for the second entity
+```
+
+#### See also
+
+[**.toCollection()**](EntitySelectionClass.md#tocollection)
+
+
+
+
+
+## .get()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.get**( *primaryKey* : Integer { ; *settings* : Object } ) : 4D.Entity
**.get**( *primaryKey* : Text { ; *settings* : Object } ) : 4D.Entity
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|primaryKey |Integer OR Text|->|Primary key value of the entity to retrieve|
+|settings |Object|->|Build option: context|
+|Result|4D.Entity|<-|Entity matching the designated primary key|
+
+
+#### Description
+
+The `.get()` function queries the dataclass to retrieve the entity matching the *primaryKey* parameter.
+
+In *primaryKey*, pass the primary key value of the entity to retrieve. The value type must match the primary key type set in the datastore (Integer or Text). You can also make sure that the primary key value is always returned as Text by using the [`.getKey()`](EntityClass.md#getkey) function with the `dk key as string` parameter.
+
+If no entity is found with *primaryKey*, a **Null** entity is returned.
+
+Lazy loading is applied, which means that related data is loaded from disk only when it is required.
+
+**settings**
+
+In the optional *settings* parameter, you can pass an object containing additional options. The following property is supported:
+
+|Property| Type| Description|
+|---|---|---|
+|context| Text| Label for the automatic optimization context applied to the entity. This context will be used by the subsequent code that loads the entity so that it can benefit from the optimization. This feature is [designed for ORDA client/server processing](ORDA/entities.md#client-server-optimization).|
+
+
+
+#### Example 1
+
+```4d
+ var $entity : cs.EmployeeEntity
+ var $entity2 : cs.InvoiceEntity
+ $entity:=ds.Employee.get(167) // return the entity whose primary key value is 167
+ $entity2:=ds.Invoice.get("DGGX20030") // return the entity whose primary key value is "DGGX20030"
+```
+
+#### Example 2
+
+This example illustrates the use of the *context* property:
+
+```4d
+ var $e1; $e2; $e3; $e4 : cs.EmployeeEntity
+ var $settings; $settings2 : Object
+
+ $settings:=New object("context";"detail")
+ $settings2:=New object("context";"summary")
+
+ $e1:=ds.Employee.get(1;$settings)
+ completeAllData($e1) // In completeAllData method, an optimization is triggered and associated to context "detail"
+
+ $e2:=ds.Employee.get(2;$settings)
+ completeAllData($e2) // In completeAllData method, the optimization associated to context "detail" is applied
+
+ $e3:=ds.Employee.get(3;$settings2)
+ completeSummary($e3) //In completeSummary method, an optimization is triggered and associated to context "summary"
+
+ $e4:=ds.Employee.get(4;$settings2)
+ completeSummary($e4) //In completeSummary method, the optimization associated to context "summary" is applied
+```
+
+
+
+
+
+
+## .getDataStore()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+**.getDataStore()** : cs.DataStore
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|cs.DataStore|<-|Datastore of the dataclass|
+
+
+
+#### Description
+
+The `.getDataStore( )` function returns the datastore for the specified dataclass.
+
+The datastore can be:
+
+* the main datastore, as returned by the `ds` command.
+* a remote datastore, opened using the `Open datastore` command.
+
+
+#### Example
+
+The ***SearchDuplicate*** project method searches for duplicated values in any dataclass.
+
+```4d
+ var $pet : cs.CatsEntity
+ $pet:=ds.Cats.all().first() //get an entity
+ SearchDuplicate($pet;"Dogs")
+```
+
+```4d
+ // SearchDuplicate method
+ // SearchDuplicate(entity_to_search;dataclass_name)
+
+ #DECLARE ($pet : Object ; $dataClassName : Text)
+ var $dataStore; $duplicates : Object
+
+ $dataStore:=$pet.getDataClass().getDataStore()
+ $duplicates:=$dataStore[$dataClassName].query("name=:1";$pet.name)
+```
+
+
+
+
+
+
+## .getInfo()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+**.getInfo()** : Object
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|Object|<-|Information on the dataclass|
+
+
+
+#### Description
+
+The `.getInfo( )` function returns an object providing information about the dataclass. This function is useful for setting up generic code.
+
+**Returned object**
+
+|Property| Type| Description|
+|---|---|---|
+|name| Text |Name of the dataclass|
+|primaryKey |Text| Name of the primary key of the dataclass|
+|tableNumber|Integer| Internal 4D table number|
+
+
+
+#### Example 1
+
+```4d
+ #DECLARE ($entity : Object)
+ var $status : Object
+
+ computeEmployeeNumber($entity) //do some actions on entity
+
+ $status:=$entity.save()
+ if($status.success)
+ ALERT("Record updated in table "+$entity.getDataClass().getInfo().name)
+ End if
+```
+
+#### Example 2
+
+```4d
+ var $settings : Object
+ var $es : cs.ClientsSelection
+
+ $settings:=New object
+ $settings.parameters:=New object("receivedIds";getIds())
+ $settings.attributes:=New object("pk";ds.Clients.getInfo().primaryKey)
+ $es:=ds.Clients.query(":pk in :receivedIds";$settings)
+```
+
+#### Example 3
+
+```4d
+ var $pk : Text
+ var $dataClassAttribute : Object
+
+ $pk:=ds.Employee.getInfo().primaryKey
+ $dataClassAttribute:=ds.Employee[$pk] // If needed the attribute matching the primary key is accessible
+```
+
+
+
+
+
+
+## .new()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+**.new()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|4D.Entity|<-|New entity matching the Dataclass|
+
+
+
+#### Description
+
+The `.new( )` function creates in memory and returns a new blank entity related to the Dataclass.
+
+The entity object is created in memory and is not saved in the database until the [`.save( )`](EntityClass.md#save) function is called. If the entity is deleted before being saved, it cannot be recovered.
+
+**4D Server**: In client-server, if the primary key of the corresponding table is auto-incremented, it will be calculated when the entity is saved on the server.
+
+All attributes of the entity are initialized with the **null** value.
+
+> Attributes can be initialized with default values if the **Map NULL values to blank values** option is selected at the 4D database structure level.
+
+#### Example
+
+This example creates a new entity in the "Log" Dataclass and records information in the "info" attribute:
+
+```4d
+ var $entity : cs.LogEntity
+ $entity:=ds.Log.new() //create a reference
+ $entity.info:="New entry" //store some information
+ $entity.save() //save the entity
+```
+
+
+
+
+
+
+
+## .newSelection()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+**.newSelection**( { *keepOrder* : Integer } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|keepOrder |Integer |-> |`dk keep ordered`: creates an ordered entity selection,
`dk non ordered`: creates an unordered entity selection (default if omitted) |
+|Result|4D.EntitySelection|<-|New blank entity selection related to the dataclass|
+
+
+
+#### Description
+
+The `.newSelection( )` function creates a new, blank, non-shareable entity selection, related to the dataclass, in memory.
+
+> For information on non-shareable entity selections, please refer to [this section](ORDA/entities.md#shareable-or-non-shareable-entity-selections).
+
+
+If you want to create an ordered entity selection, pass the `dk keep ordered` selector in the *keepOrder* parameter. By default if you omit this parameter, or if you pass the `dk non ordered` selector, the method creates an unordered entity selection. Unordered entity selections are faster but you cannot rely on entity positions. For more information, please see [Ordered vs Unordered entity selections](ORDA/dsMapping.md#ordered-or-unordered-entity-selection).
+
+When created, the entity selection does not contain any entities (`mySelection.length` returns 0). This method lets you build entity selections gradually by making subsequent calls to the [`add()`](EntitySelectionClass.md#add) function.
+
+
+#### Example
+
+
+```4d
+ var $USelection; $OSelection : cs.EmployeeSelection
+ $USelection:=ds.Employee.newSelection() //create an unordered empty entity selection
+ $OSelection:=ds.Employee.newSelection(dk keep ordered) //create an ordered empty entity selection
+```
+
+
+
+
+
+
+
+## .query()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Support of Formula parameters|
+|v17 R5|Support of placeholders for values|
+|v17|Added|
+
+
+
+**.query**( *queryString* : Text { ; *...value* : any } { ; *querySettings* : Object } ) : 4D.EntitySelection
**.query**( *formula* : Object { ; *querySettings* : Object } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|queryString |Text |-> |Search criteria as string|
+|formula |Object |-> |Search criteria as formula object|
+|value|any|->|Value(s) to use for indexed placeholder(s)|
+|querySettings|Object|->|Query options: parameters, attributes, args, allowFormulas, context, queryPath, queryPlan|
+|Result|4D.EntitySelection|<-|New entity selection made up of entities from dataclass meeting the search criteria specified in *queryString* or *formula*|
+
+
+
+#### Description
+
+The `.query( )` function searches for entities that meet the search criteria specified in *queryString* or *formula* and (optionally) *value*(s), for all the entities in the dataclass, and returns a new object of type `EntitySelection` containing all the entities that are found. Lazy loading is applied.
+
+If no matching entities are found, an empty `EntitySelection` is returned.
+
+**queryString parameter**
+
+The *queryString* parameter uses the following syntax:
+
+```4d
+attributePath|formula comparator value
+ {logicalOperator attributePath|formula comparator value}
+ {order by attributePath {desc | asc}}
+```
+
+where:
+
+* **attributePath**: path of attribute on which you want to execute the query. This parameter can be a simple name (for example "country") or any valid attribute path (for example "country.name".) In case of an attribute path whose type is `Collection`, \[ ] notation is used to handle all the occurences (for example "children\[ ].age"). You can also use a **placeholder** (see below).
+
+ >*You cannot use directly attributes whose name contains special characters such as ".", "\[ ]", or "=", ">", "#"..., because they will be incorrectly evaluated in the query string. If you need to query on such attributes, you must consider using placeholders, which allow an extended range of characters in attribute paths (see* **Using placeholders** *below).*
+
+* **formula**: a valid formula passed as `Text` or `Object`. The formula will be evaluated for each processed entity and must return a boolean value. Within the formula, the entity is available through the `This` object.
+
+ * **Text**: the formula string must be preceeded by the `eval( )` statement, so that the query parser evaluates the expression correctly. For example: *"eval(length(This.lastname) >=30)"*
+ * **Object**: the [formula object](FunctionClass.md) is passed as a **placeholder** (see below). The formula must have been created using the [`Formula`](FunctionClass.md#formula) or [`Formula from string`](FunctionClass.md#formula-from-string) command.
+
+ >* Keep in mind that 4D formulas only support `&` and `|` symbols as logical operators.
+ >* If the formula is not the only search criteria, the query engine optimizer could prior process other criteria (e.g. indexed attributes) and thus, the formula could be evaluated for only a subset of entities.
+
+ Formulas in queries can receive parameters through $1. This point is detailed in the **formula parameter** paragraph below.
+
+ >* You can also pass directy a `formula` parameter object instead of the `queryString` parameter (recommended when formulas are more complex). See **formula parameter** paragraph below.
+ >* For security reasons, formula calls within `query()` methods can be disallowed. See `querySettings` parameter description.
+
+* **comparator**: symbol that compares *attributePath* and *value*. The following symbols are supported:
+
+ |Comparison| Symbol(s)| Comment|
+ |---|---|---|
+ |Equal to |=, == |Gets matching data, supports the wildcard (@), neither case-sensitive nor diacritic.|
+ ||===, IS| Gets matching data, considers the @ as a standard character, neither case-sensitive nor diacritic|
+ |Not equal to| #, != |Supports the wildcard (@)|
+ ||!==, IS NOT| Considers the @ as a standard character|
+ |Less than| <| |
+ |Greater than| > ||
+ |Less than or equal to| <=||
+ |Greater than or equal to| >= ||
+ |Included in| IN |Gets data equal to at least one of the values in a collection or in a set of values, supports the wildcard (@)|
+ |Not condition applied on a statement| NOT| Parenthesis are mandatory when NOT is used before a statement containing several operators|
+ |Contains keyword| %| Keywords can be used in attributes of string or picture type|
+
+* **value**: the value to compare to the current value of the property of each entity in the entity selection or element in the collection. It can be a **placeholder** (see **Using placeholders** below) or any expression matching the data type property.
+When using a constant value, the following rules must be respected:
+ * **text** type constant can be passed with or without simple quotes (see **Using quotes** below). To query a string within a string (a "contains" query), use the wildcard symbol (@) in value to isolate the string to be searched for as shown in this example: "@Smith@". The following keywords are forbidden for text constants: true, false.
+ * **boolean** type constants: **true** or **false** (case sensitive).
+ * **numeric** type constants: decimals are separated by a '.' (period).
+date type constants: "YYYY-MM-DD" format
+ * **null** constant: using the "null" keyword will find **null** and **undefined** properties.
+ * in case of a query with an IN comparator, value must be a collection, or values matching the type of the attribute path between \[ ] separated by commas (for strings, " characters must be escaped with "\").
+* **logicalOperator**: used to join multiple conditions in the query (optional). You can use one of the following logical operators (either the name or the symbol can be used):
+
+ |Conjunction|Symbol(s)|
+ |---|---|
+ |AND|&, &&, and|
+ |OR | |,||, or|
+
+* **order by attributePath**: you can include an order by *attributePath* statement in the query so that the resulting data will be sorted according to that statement. You can use multiple order by statements, separated by commas (e.g., order by *attributePath1* desc, *attributePath2* asc). By default, the order is ascending. Pass 'desc' to define a descending order and 'asc' to define an ascending order.
+ >*If you use this statement, the returned entity selection is ordered (for more information, please refer to [Ordered vs Unordered entity selections](ORDA/dsMapping.md#ordered-or-unordered-entity-selection)).
+
+**Using quotes**
+
+When you use quotes within queries, you must use single quotes ' ' inside the query and double quotes " " to enclose the whole query, otherwise an error is returned. For example:
+
+```4d
+"employee.name = 'smith' AND employee.firstname = 'john'"
+```
+
+>Single quotes (') are not supported in searched values since they would break the query string. For example "comp.name = 'John's pizza' " will generate an error. If you need to search on values with single quotes, you may consider using placeholders (see below).
+
+**Using parenthesis**
+
+You can use parentheses in the query to give priority to the calculation. For example, you can organize a query as follows:
+
+```4d
+"(employee.age >= 30 OR employee.age <= 65) AND (employee.salary <= 10000 OR employee.status = 'Manager')"
+```
+
+
+**Using placeholders**
+
+4D allows you to use placeholders for *attributePath*, *formula* and *value* arguments within the *queryString* parameter. A placeholder is a parameter that you insert in query strings and that is replaced by another value when the query string is evaluated. The value of placeholders is evaluated once at the beginning of the query; it is not evaluated for each element.
+
+Two types of placeholders can be used: **indexed placeholders** and **named placeholders**:
+
+|- |Indexed placeholders| Named placeholders|
+|---|---|---|
+|Definition |Parameters are inserted as :paramIndex (for example :1, :2...) in queryString and their corresponding values are provided by the sequence of value parameter(s). You can use up to 128 value parameters| Parameters are inserted as :paramName (for example :myparam) and their values are provided in the attributes and/or parameters objects in the querySettings parameter|
+|Example|$r:=class.query(":1=:2";"city";"Chicago")| $o.attributes:=New object("att";"city")
$o.parameters:=New object("name";"Chicago")
$r:=class.query(":att=:name";$o)|
+
+You can mix all argument kinds in *queryString*. A *queryString* can contain, for *attributePath*, *formula* and *value* parameters:
+
+
+* direct values (no placeholders),
+* indexed placeholders and/or named placeholders.
+
+**Using placeholders in queries is recommended** for the following reasons:
+
+1. It prevents malicious code insertion: if you directly use user-filled variables within the query string, a user could modifiy the query conditions by entering additional query arguments. For example, imagine a query string like:
+
+ ```4d
+ $vquery:="status = 'public' & name = "+myname //user enters their name
+ $result:=$col.query($vquery)
+ ```
+
+ This query seems secured since non-public data are filtered. However, if the user enters in the *myname* area something like *"smith OR status='private'*, the query string would be modified at the interpretation step and could return private data.
+
+ When using placeholders, overriding security conditions is not possible:
+
+ ```4d
+ $result:=$col.query("status='public' & name=:1";myname)
+ ```
+
+ In this case if the user enters *smith OR status='private'* in the *myname* area, it will not be interpreted in the query string, but only passed as a value. Looking for a person named "smith OR status='private'" will just fail.
+
+2. It prevents having to worry about formatting or character issues, especially when handling *attributePath* or *value* parameters that might contain non-alphanumeric characters such as ".", "['...
+
+3. It allows the use of variables or expressions in query arguments. Examples:
+
+ ```4d
+ $result:=$col.query("address.city = :1 & name =:2";$city;$myVar+"@")
+ $result2:=$col.query("company.name = :1";"John's Pizzas")
+ ```
+
+**Looking for null values**
+
+When you look for null values, you cannot use the placeholder syntax because the query engine considers null as an unexpected comparison value. For example, if you execute the following query:
+
+```4d
+$vSingles:=ds.Person.query("spouse = :1";Null) // will NOT work
+```
+
+You will not get the expected result because the null value will be evaluated by 4D as an error resulting from the parameter evaluation (for example, an attribute coming from another query). For these kinds of queries, you must use the direct query syntax:
+
+```4d
+ $vSingles:=ds.Person.query("spouse = null") //correct syntax
+```
+
+
+**Linking collection attribute query arguments**
+
+When searching in collections within object attributes using multiple query arguments joined by the AND operator, you may want to make sure that only entities containing elements that match all arguments are returned, and not entities where arguments can be found in different elements. To do this, you need to link query arguments to collection elements, so that only single elements containing linked arguments are found.
+
+For example, with the following two entities:
+
+```
+Entity 1:
+ds.People.name: "martin"
+ds.People.places:
+ { "locations" : [ {
+ "kind":"home",
+ "city":"paris"
+ } ] }
+
+Entity 2:
+ds.People.name: "smith"
+ds.People.places:
+ { "locations" : [ {
+ "kind":"home",
+ "city":"lyon"
+ } , {
+ "kind":"office",
+ "city":"paris"
+ } ] }
+```
+
+You want to find people with a "home" location kind in the city "paris". If you write:
+
+```4d
+ds.People.query("places.locations[].kind= :1 and places.locations[].city= :2";"home";"paris")
+```
+
+... the query will return "martin" **and** "smith" because "smith" has a "locations" element whose "kind" is "home" and a "locations" element whose "city" is "paris", even though they are different elements.
+
+If you want to only get entities where matching arguments are in the same collection element, you need to **link arguments**. To link query arguments:
+
+- Add a letter between the \[] in the first path to link and repeat the same letter in all linked arguments. For example: `locations[a].city and locations[a].kind`. You can use any letter of the Latin alphabet (not case sensitive).
+- To add different linked criteria in the same query, use another letter. You can create up to 26 combinations of criteria in a single query.
+
+With the above entities, if you write:
+
+```4d
+ds.People.query("places.locations[a].kind= :1 and places.locations[a].city= :2";"home";"paris")
+```
+
+... the query will only return "martin" because it has a "locations" element whose "kind" is "home" and whose "city" is "paris". The query will not return "smith" because the values "home" and "paris" are not in the same collection element.
+
+
+
+**formula parameter**
+
+As an alternative to formula insertion within the *queryString* parameter (see above), you can pass directly a formula object as a boolean search criteria. Using a formula object for queries is **recommended** since you benefit from tokenization, and code is easier to search/read.
+
+The formula must have been created using the `Formula` or `Formula from string` command. In this case:
+
+* the *formula* is evaluated for each entity and must return true or false. During the execution of the query, if the formula's result is not a boolean, it is considered as false.
+* within the *formula*, the entity is available through the `This` object.
+* if the `Formula` object is **null**, the errror 1626 ("Expecting a text or formula") is generated, that you call intercept using a method installed with `ON ERR CALL`.
+
+ >For security reasons, formula calls within `query(`) member methods can be disallowed. See *querySettings* parameter description.
+
+**Passing parameters to formulas**
+
+Any *formula* called by the `query()` class function can receive parameters:
+
+* Parameters must be passed through the **args** property (object) of the *querySettings* parameter.
+* The formula receives this **args** object as a **$1** parameter.
+
+This small code shows the principles of how parameter are passed to methods:
+
+```4d
+ $settings:=New object("args";New object("exclude";"-")) //args object to pass parameters
+ $es:=ds.Students.query("eval(checkName($1.exclude))";$settings) //args is received in $1
+```
+
+Additional examples are provided in example 3.
+
+**4D Server**: In client/server, formulas are executed on the server. In this context, only the `querySettings.args` object is sent to the formulas.
+
+
+
+**querySettings parameter**
+
+In the *querySettings* parameter, you can pass an object containing additional options. The following properties are supported:
+
+|Property| Type| Description|
+|---|---|---|
+|parameters|Object|**Named placeholders for values** used in the *queryString* or *formula*. Values are expressed as property / value pairs, where property is the placeholder name inserted for a value in the *queryString* or *formula* (":placeholder") and value is the value to compare. You can mix indexed placeholders (values directly passed in value parameters) and named placeholder values in the same query.|
+|attributes|Object|**Named placeholders for attribute paths** used in the *queryString* or *formula*. Attributes are expressed as property / value pairs, where property is the placeholder name inserted for an attribute path in the *queryString* or *formula* (":placeholder"), and value can be a string or a collection of strings. Each value is a path that can designate either a scalar or a related attribute of the dataclass or a property in an object field of the dataclass
| Type | Description |
|---|
| String | attributePath expressed using the dot notation, e.g. "name" or "user.address.zipCode" |
| Collection of strings | Each string of the collection represents a level of attributePath, e.g. \["name"] or \["user","address","zipCode"]. Using a collection allows querying on attributes with names that are not compliant with dot notation, e.g. \["4Dv17.1","en/fr"] |
You can mix indexed placeholders (values directly passed in *value* parameters) and named placeholder values in the same query.|
+|args|Object|Parameter(s) to pass to formulas, if any. The **args** object will be received in $1 within formulas and thus its values will be available through *$1.property* (see example 3).|
+|allowFormulas| Boolean|True to allow the formula calls in the query (default). Pass false to disallow formula execution. If set to false and `query()` is given a formula, an error is sent (1278 - Formula not allowed in this member method).|
+|context|Text|Label for the automatic optimization context applied to the entity selection. This context will be used by the code that handles the entity selection so that it can benefit from the optimization. This feature is designed for client/server processing; for more information, please refer to the **Client/server optimization** section.|
+|queryPlan| Boolean |In the resulting entity selection, returns or does not return the detailed description of the query just before it is executed, i.e. the planned query. The returned property is an object that includes each planned query and subquery (in the case of a complex query). This option is useful during the development phase of an application. It is usually used in conjunction with queryPath. Default if omitted: false. **Note**: This property is supported only by the `entitySelection.query( )` and `dataClass.query( )` functions.|
+|queryPath|Boolean| In the resulting entity selection, returns or does not return the detailed description of the query as it is actually performed. The returned property is an object that contains the actual path used for the query (usually identical to that of the queryPlan, but may differ if the engine manages to optimize the query), as well as the processing time and the number of records found. This option is useful during the development phase of an application. Default if omitted: false. **Note**: This property is supported only by the `entitySelection.query( )` and `dataClass.query( )` functions.|
+
+**About queryPlan and queryPath**
+
+The information recorded in `queryPlan`/`queryPath` include the query type (indexed and sequential) and each necessary subquery along with conjunction operators. Query paths also contain the number of entities found and the time required to execute each search criterion. You may find it useful to analyze this information while developing your application(s). Generally, the description of the query plan and its path are identical but they can differ because 4D can implement dynamic optimizations when a query is executed in order to improve performance. For example, the 4D engine can dynamically convert an indexed query into a sequential one if it estimates that it is faster. This particular case can occur when the number of entities being searched for is low.
+
+For example, if you execute the following query:
+
+```4d
+ $sel:=ds.Employee.query("salary < :1 and employer.name = :2 or employer.revenues > :3";\
+ 50000;"Lima West Kilo";10000000;New object("queryPath";True;"queryPlan";True))
+```
+
+queryPlan:
+
+```4d
+{Or:[{And:[{item:[index : Employee.salary ] < 50000},
+ {item:Join on Table : Company : Employee.employerID = Company.ID,
+ subquery:[{item:[index : Company.name ] = Lima West Kilo}]}]},
+ {item:Join on Table : Company : Employee.employerID = Company.ID,
+ subquery:[{item:[index : Company.revenues ] > 10000000}]}]}
+```
+
+queryPath:
+
+```4d
+{steps:[{description:OR,time:63,recordsfounds:1388132,
+ steps:[{description:AND,time:32,recordsfounds:131,
+ steps:[{description:[index : Employee.salary ] < 50000,time:16,recordsfounds:728260},{description:Join on Table : Company : Employee.employerID = Company.ID,time:0,recordsfounds:131,
+ steps:[{steps:[{description:[index : Company.name ] = Lima West Kilo,time:0,recordsfounds:1}]}]}]},{description:Join on Table : Company : Employee.employerID = Company.ID,time:31,recordsfounds:1388132,
+ steps:[{steps:[{description:[index : Company.revenues ] > 10000000,time:0,recordsfounds:933}]}]}]}]}
+```
+
+#### Example 1
+
+This section provides various examples of queries.
+
+Query on a string:
+
+```4d
+$entitySelection:=ds.Customer.query("firstName = 'S@'")
+```
+
+Query with a NOT statement:
+
+```4d
+$entitySelection:=ds.Employee.query("not(firstName=Kim)")
+```
+
+Queries with dates:
+
+```4d
+$entitySelection:=ds.Employee.query("birthDate > :1";"1970-01-01")
+$entitySelection:=ds.Employee.query("birthDate <= :1";Current date-10950)
+```
+
+Query with indexed placeholders for values:
+
+```4d
+$entitySelection:=ds.Customer.query("(firstName = :1 or firstName = :2) and (lastName = :3 or lastName = :4)";"D@";"R@";"S@";"K@")
+```
+
+Query with indexed placeholders for values on a related dataclass:
+
+```4d
+$entitySelection:=ds.Employee.query("lastName = :1 and manager.lastName = :2";"M@";"S@")
+```
+
+Query with indexed placeholder including a descending order by statement:
+
+```4d
+$entitySelection:=ds.Student.query("nationality = :1 order by campus.name desc, lastname";"French")
+```
+
+Query with named placeholders for values:
+
+```4d
+var $querySettings : Object
+var $managedCustomers : cs.CustomerSelection
+$querySettings:=New object
+$querySettings.parameters:=New object("userId";1234;"extraInfo";New object("name";"Smith"))
+$managedCustomers:=ds.Customer.query("salesperson.userId = :userId and name = :extraInfo.name";$querySettings)
+```
+
+Query that uses both named and indexed placeholders for values:
+
+```4d
+var $querySettings : Object
+var $managedCustomers : cs.CustomerSelection
+$querySettings.parameters:=New object("userId";1234)
+$managedCustomers:=ds.Customer.query("salesperson.userId = :userId and name=:1";"Smith";$querySettings)
+```
+
+Query with queryPlan and queryPath objects:
+
+```4d
+$entitySelection:=ds.Employee.query("(firstName = :1 or firstName = :2) and (lastName = :3 or lastName = :4)";"D@";"R@";"S@";"K@";New object("queryPlan";True;"queryPath";True))
+
+ //you can then get these properties in the resulting entity selection
+var $queryPlan; $queryPath : Object
+$queryPlan:=$entitySelection.queryPlan
+$queryPath:=$entitySelection.queryPath
+```
+
+Query with an attribute path of Collection type:
+
+```4d
+$entitySelection:=ds.Employee.query("extraInfo.hobbies[].name = :1";"horsebackriding")
+```
+
+Query with an attribute path of Collection type and linked attributes:
+
+```4d
+$entitySelection:=ds.Employee.query("extraInfo.hobbies[a].name = :1 and extraInfo.hobbies[a].level=:2";"horsebackriding";2)
+```
+
+Query with an attribute path of Collection type and multiple linked attributes:
+
+```4d
+$entitySelection:=ds.Employee.query("extraInfo.hobbies[a].name = :1 and
+ extraInfo.hobbies[a].level = :2 and extraInfo.hobbies[b].name = :3 and
+ extraInfo.hobbies[b].level = :4";"horsebackriding";2;"Tennis";5)
+```
+
+Query with an attribute path of Object type:
+
+```4d
+$entitySelection:=ds.Employee.query("extra.eyeColor = :1";"blue")
+```
+
+Query with an IN statement:
+
+```4d
+$entitySelection:=ds.Employee.query("firstName in :1";New collection("Kim";"Dixie"))
+```
+
+Query with a NOT (IN) statement:
+
+```4d
+$entitySelection:=ds.Employee.query("not (firstName in :1)";New collection("John";"Jane"))
+```
+
+Query with indexed placeholders for attributes:
+
+```4d
+var $es : cs.EmployeeSelection
+$es:=ds.Employee.query(":1 = 1234 and :2 = 'Smith'";"salesperson.userId";"name")
+ //salesperson is a related entity
+```
+
+Query with indexed placeholders for attributes and named placeholders for values:
+
+```4d
+var $es : cs.EmployeeSelection
+var $querySettings : Object
+$querySettings:=New object
+$querySettings.parameters:=New object("customerName";"Smith")
+$es:=ds.Customer.query(":1 = 1234 and :2 = :customerName";"salesperson.userId";"name";$querySettings)
+ //salesperson is a related entity
+```
+
+Query with indexed placeholders for attributes and values:
+
+
+```4d
+var $es : cs.EmployeeSelection
+$es:=ds.Clients.query(":1 = 1234 and :2 = :3";"salesperson.userId";"name";"Smith")
+ //salesperson is a related entity
+```
+
+#### Example 2
+
+This section illustrates queries with named placeholders for attributes.
+
+Given an Employee dataclass with 2 entities:
+
+Entity 1:
+
+```4d
+name: "Marie"
+number: 46
+softwares:{
+"Word 10.2": "Installed",
+"Excel 11.3": "To be upgraded",
+"Powerpoint 12.4": "Not installed"
+}
+```
+
+Entity 2:
+
+```4d
+name: "Sophie"
+number: 47
+softwares:{
+"Word 10.2": "Not installed",
+"Excel 11.3": "To be upgraded",
+"Powerpoint 12.4": "Not installed"
+}
+```
+
+Query with named placeholders for attributes:
+
+```4d
+ var $querySettings : Object
+ var $es : cs.EmployeeSelection
+ $querySettings:=New object
+ $querySettings.attributes:=New object("attName";"name";"attWord";New collection("softwares";"Word 10.2"))
+ $es:=ds.Employee.query(":attName = 'Marie' and :attWord = 'Installed'";$querySettings)
+ //$es.length=1 (Employee Marie)
+```
+
+Query with named placeholders for attributes and values:
+
+```4d
+ var $querySettings : Object
+ var $es : cs.EmployeeSelection
+ var $name : Text
+ $querySettings:=New object
+ //Named placeholders for values
+ //The user is asked for a name
+ $name:=Request("Please enter the name to search:")
+ If(OK=1)
+ $querySettings.parameters:=New object("givenName";$name)
+ //Named placeholders for attribute paths
+ $querySettings.attributes:=New object("attName";"name")
+ $es:=ds.Employee.query(":attName= :givenName";$querySettings)
+ End if
+```
+
+#### Example 3
+
+These examples illustrate the various ways to use formulas with or without parameters in your queries.
+
+The formula is given as text with `eval()` in the *queryString* parameter:
+
+```4d
+ var $es : cs.StudentsSelection
+ $es:=ds.Students.query("eval(length(This.lastname) >=30) and nationality='French'")
+```
+
+The formula is given as a `Formula` object through a placeholder:
+
+```4d
+ var $es : cs.StudentsSelection
+ var $formula : Object
+ $formula:=Formula(Length(This.lastname)>=30)
+ $es:=ds.Students.query(":1 and nationality='French'";$formula)
+```
+
+Only a `Formula` object is given as criteria:
+
+```4d
+ var $es : cs.StudentsSelection
+ var $formula : Object
+ $formula:=Formula(Length(This.lastname)>=30)
+ $es:=ds.Students.query($formula)
+```
+
+Several formulas can be applied:
+
+```4d
+ var $formula1; $1; $formula2 ;$0 : Object
+ $formula1:=$1
+ $formula2:=Formula(Length(This.firstname)>=30)
+ $0:=ds.Students.query(":1 and :2 and nationality='French'";$formula1;$formula2)
+```
+
+
+A text formula in *queryString* receives a parameter:
+
+```4d
+ var $es : cs.StudentsSelection
+ var $settings : Object
+ $settings:=New object()
+ $settings.args:=New object("filter";"-")
+ $es:=ds.Students.query("eval(checkName($1.filter)) and nationality=:1";"French";$settings)
+```
+
+```4d
+ //checkName method
+ #DECLARE($exclude : Text) -> $result : Boolean
+ $result:=(Position($exclude;This.lastname)=0)
+```
+
+Using the same ***checkName*** method, a `Formula` object as placeholder receives a parameter:
+
+```4d
+ var $es : cs.StudentsSelection
+ var $settings; $formula : Object
+ $formula:=Formula(checkName($1.filter))
+ $settings:=New object()
+ $settings.args:=New object("filter";"-")
+ $es:=ds.Students.query(":1 and nationality=:2";$formula;"French";$settings)
+ $settings.args.filter:="*" // change the parameters without updating the $formula object
+ $es:=ds.Students.query(":1 and nationality=:2";$formula;"French";$settings)
+```
+
+We want to disallow formulas, for example when the user enters their query:
+
+```4d
+ var $es : cs.StudentsSelection
+ var $settings : Object
+ var $queryString : Text
+ $queryString:=Request("Enter your query:")
+ if(OK=1)
+ $settings:=New object("allowFormulas";False)
+ $es:=ds.Students.query($queryString;$settings) //An error is raised if $queryString contains a formula
+ End if
+```
+
+#### See also
+
+[`.query()`](EntitySelectionClass.md#query) for entity selections
+
+
+
diff --git a/docs/API/DataStoreClass.md b/docs/API/DataStoreClass.md
new file mode 100644
index 00000000000000..395532bb574ce5
--- /dev/null
+++ b/docs/API/DataStoreClass.md
@@ -0,0 +1,877 @@
+---
+id: DataStoreClass
+title: DataStore
+---
+
+A [Datastore](ORDA/dsMapping.md#datastore) is the interface object provided by ORDA to reference and access a database. `Datastore` objects are returned by the following commands:
+
+* [ds](#ds): a shortcut to the main datastore
+* [Open datastore](#open-datastore): to open any remote datastore
+
+### Summary
+
+||
+|---|
+|[](#canceltransaction) |
+|[](#dataclassname)
|
+|[](#encryptionstatus)
|
+|[](#getinfo)
|
+|[](#getrequestlog)
|
+|[](#makeselectionsalterable)
|
+|[](#providedatakey)
|
+|[](#setadminprotection)
|
+|[](#startrequestlog)
|
+|[](#starttransaction)
|
+|[](#stoprequestlog)
|
+|[](#validatetransaction)
|
+
+
+
+
+
+## ds
+
+History
+|Version|Changes|
+|---|---|
+|v18|Support of localID parameter|
+|v17|Added|
+
+
+
+**ds** { ( *localID* : Text ) } : cs.DataStore
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|localID|Text|->|Local ID of the remote datastore to return|
+|Result |cs.DataStore|<-|Reference to the datastore|
+
+
+
+#### Description
+
+The `ds` command returns a reference to the datastore matching the current 4D database or the database designated by *localID*.
+
+If you omit the *localID* parameter (or pass an empty string ""), the command returns a reference to the datastore matching the local 4D database (or the 4D Server database in case of opening a remote database on 4D Server). The datastore is opened automatically and available directly through `ds`.
+
+You can also get a reference on an open remote datastore by passing its local id in the *localID* parameter. The datastore must have been previously opened with the [`Open datastore`](#open-datastore) command by the current database (host or component). The local id is defined when using this command.
+
+>The scope of the local id is the database where the datastore has been opened.
+
+If no *localID* datastore is found, the command returns **Null**.
+
+Objects available in the `cs.Datastore` are mapped from the target database with respect to the [ORDA general rules](Concepts/dsMapping.md#general-rules).
+
+#### Example 1
+
+Using the main datastore on the 4D database:
+
+```4d
+ $result:=ds.Employee.query("firstName = :1";"S@")
+```
+
+#### Example 2
+
+```4d
+ var $connectTo; $firstFrench; $firstForeign : Object
+
+ var $frenchStudents; $foreignStudents : cs.DataStore
+
+ $connectTo:=New object("type";"4D Server";"hostname";"192.168.18.11:8044")
+ $frenchStudents:=Open datastore($connectTo;"french")
+
+ $connectTo.hostname:="192.168.18.11:8050"
+ $foreignStudents:=Open datastore($connectTo;"foreign")
+ //...
+ //...
+ $firstFrench:=getFirst("french";"Students")
+ $firstForeign:=getFirst("foreign";"Students")
+```
+
+```4d
+ //getFirst method
+ //getFirst(localID;dataclass) -> entity
+ #DECLARE( $localId : Text; $dataClassName : Text ) -> $entity : 4D.Entity
+
+ $0:=ds($localId)[$dataClassName].all().first()
+```
+
+
+
+
+## Open datastore
+
+History
+|Version|Changes|
+|---|---|
+|v18|Added|
+
+
+
+**Open datastore**( *connectionInfo* : Object ; *localID* : Text ) : cs.DataStore
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|connectionInfo|Object|->|Connection properties used to reach the remote datastore|
+|localID |Text|->|Id to assign to the opened datastore on the local application (mandatory)|
+|Result |cs.DataStore|<-|Datastore object|
+
+
+
+#### Description
+
+The `Open datastore` command connects the application to the 4D database identified by the *connectionInfo* parameter and returns a matching `cs.DataStore` object associated with the *localID* local alias.
+
+The *connectionInfo* 4D database must be available as a remote datastore, i.e.:
+
+* its web server must be launched with http and/or https enabled,
+* its [**Expose as REST server**](REST/configuration.md#starting-the-rest-server) option must be checked,
+* at least one client license is available.
+
+If no matching database is found, `Open datastore` returns **Null**.
+
+*localID* is a local alias for the session opened on remote datastore. If *localID* already exists on the application, it is used. Otherwise, a new *localID* session is created when the datastore object is used.
+
+Objects available in the `cs.Datastore` are mapped from the target database with respect to the [ORDA general rules](Concepts/dsMapping.md#general-rules).
+
+Once the session is opened, the following statements become equivalent and return a reference on the same datastore object:
+
+```4d
+ $myds:=Open datastore(connectionInfo;"myLocalId")
+ $myds2:=ds("myLocalId")
+ //$myds and $myds2 are equivalent
+```
+
+Pass in *connectionInfo* an object describing the remote datastore you want to connect to. It can contain the following properties (all properties are optional except *hostname*):
+
+|Property| Type| Description|
+|---|---|---|
+|hostname|Text|Name or IP address of the remote database + ":" + port number (port number is mandatory)|
+|user|Text|User name
+|password|Text|User password
+|idleTimeout|Longint|Inactivity session timeout (in minutes), after which the session is automatically closed by 4D. If omitted, default value is 60 (1h). The value cannot be < 60 (if a lower value is passed, the timeout is set to 60). For more information, see **Closing sessions**.|
+|tls|Boolean|Use secured connection(*). If omitted, false by default. Using a secured connection is recommended whenever possible.|
+|type |Text |Must be "4D Server"|
+
+(*) If tls is true, the HTTPS protocol is used if:
+
+* HTTPS is enabled on the remote datastore
+* the given port is the right HTTPS port configured in the database settings
+* a valid certificate and private encryption key are installed in the database. Otherwise, error "1610 - A remote request to host xxx has failed" is raised
+
+#### Example 1
+
+Connection to a remote datastore without user / password:
+
+```4d
+ var $connectTo : Object
+ var $remoteDS : cs.DataStore
+ $connectTo:=New object("type";"4D Server";"hostname";"192.168.18.11:8044")
+ $remoteDS:=Open datastore($connectTo;"students")
+ ALERT("This remote datastore contains "+String($remoteDS.Students.all().length)+" students")
+```
+
+#### Example 2
+
+Connection to a remote datastore with user / password / timeout / tls:
+
+```4d
+ var $connectTo : Object
+ var $remoteDS : cs.DataStore
+ $connectTo:=New object("type";"4D Server";"hostname";\"192.168.18.11:4443";\
+ "user";"marie";"password";$pwd;"idleTimeout";70;"tls";True)
+ $remoteDS:=Open datastore($connectTo;"students")
+ ALERT("This remote datastore contains "+String($remoteDS.Students.all().length)+" students")
+```
+
+#### Example 3
+
+Working with several remote datastores:
+
+```4d
+ var $connectTo : Object
+ var $frenchStudents; $foreignStudents : cs.DataStore
+ $connectTo:=New object("hostname";"192.168.18.11:8044")
+ $frenchStudents:=Open datastore($connectTo;"french")
+ $connectTo.hostname:="192.168.18.11:8050"
+ $foreignStudents:=Open datastore($connectTo;"foreign")
+ ALERT("They are "+String($frenchStudents.Students.all().length)+" French students")
+ ALERT("They are "+String($foreignStudents.Students.all().length)+" foreign students")
+```
+
+#### Error management
+
+In case of error, the command returns **Null**. If the remote datastore cannot be reached (wrong address, web server not started, http and https not enabled...), error 1610 "A remote request to host XXX has failed" is raised. You can intercept this error with a method installed by `ON ERR CALL`.
+
+
+
+
+## *.dataclassName*
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+***.dataclassName*** : 4D.DataClass
+
+
+#### Description
+
+Each dataclass in a datastore is available as a property of the [DataStore object](ORDA/dsMapping.md#datastore)data. The returned object contains a description of the dataclass.
+
+
+#### Example
+
+```4d
+ var $emp : cs.Employee
+ var $sel : cs.EmployeeSelection
+ $emp:=ds.Employee //$emp contains the Employee dataclass
+ $sel:=$emp.all() //gets an entity selection of all employees
+
+ //you could also write directly:
+ $sel:=ds.Employee.all()
+```
+
+
+
+
+
+
+
+
+## .cancelTransaction()
+
+History
+|Version|Changes|
+|---|---|
+|v18|Added|
+
+
+
+
+**.cancelTransaction()**
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.cancelTransaction()` function cancels the transaction opened by the [`.startTransaction()`](#starttransaction) function at the corresponding level in the current process for the specified datastore.
+
+The `.cancelTransaction()` function cancels any changes made to the data during the transaction.
+
+You can nest several transactions (sub-transactions). If the main transaction is cancelled, all of its sub-transactions are also cancelled, even if they were validated individually using the [`.validateTransaction()`](#validatetransactions) function.
+
+
+#### Example
+
+See example for the [`.startTransaction()`](#starttransaction) function.
+
+
+
+
+
+
+
+## .encryptionStatus()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+**.encryptionStatus()**: Object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Object|<-|Information about the encryption of the current datastore and of each table|
+
+
+
+#### Description
+
+The `.encryptionStatus()` function returns an object providing the encryption status for the current data file (i.e., the data file of the `ds` datastore). The status for each table is also provided.
+
+>Use the `Data file encryption status` command to determine the encryption status of any other data file.
+
+
+**Returned value**
+
+The returned object contains the following properties:
+
+|Property | | |Type |Description|
+|---|---|---|---|---|
+|isEncrypted|||Boolean|True if the data file is encrypted|
+|keyProvided|||Boolean|True if the encryption key matching the encrypted data file is provided(*).|
+|tables| ||Object| Object containing as many properties as there are encryptable or encrypted tables.|
+||*tableName*|| Object| Encryptable or Encrypted table|
+|||name |Text| Name of the table|
+|||num| Number |Table number|
+|||isEncryptable|Boolean|True if the table is declared encryptable in the structure file|
+|||isEncrypted| Boolean| True if the records of the table are encrypted in the data file|
+
+(*) The encryption key can be provided:
+
+* with the `.provideDataKey()` command,
+* at the root of a connected device before opening the datastore,
+* with the `Discover data key` command.
+
+#### Example
+
+You want to know the number of encrypted tables in the current data file:
+
+```4d
+ var $status : Object
+
+ $status:=dataStore.encryptionStatus()
+
+ If($status.isEncrypted) //the database is encrypted
+ C_LONGINT($vcount)
+ C_TEXT($tabName)
+ For each($tabName;$status.tables)
+ If($status.tables[$tabName].isEncrypted)
+ $vcount:=$vcount+1
+ End if
+ End for each
+ ALERT(String($vcount)+" encrypted table(s) in this datastore.")
+ Else
+ ALERT("This database is not encrypted.")
+ End if
+```
+
+
+
+
+
+
+## .getInfo()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.getInfo()**: Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Object|<-|Datastore properties|
+
+
+#### Description
+
+The `.getInfo()` function returns an object providing information about the datastore. This function is useful for setting up generic code.
+
+**Returned object**
+
+|Property |Type |Description|
+|---|---|---|
+|type| string |
"4D": main datastore, available through ds "4D Server": remote datastore, open with Open datastore|
+|networked |boolean|True: the datastore is reached through a network connection.False: the datastore is not reached through a network connection (local database)|
+|localID| text| ID of the datastore on the machine. Corresponds to the localId string given with the `Open datastore` command. Empty string ("") for main datastore.|
+|connection |object|Object describing the remote datastore connection (not returned for main datastore). Available properties:| Property | Type | Description |
|---|
| hostname | text | IP address or name of the remote datastore + ":" + port number |
| tls | boolean | True if secured connection is used with the remote datastore |
| idleTimeout | number | Session inactivity timeout (in minutes) |
| user | text | User authenticated on the remote datastore |
|
+
+* If the `.getInfo()` function is executed on a 4D Server or 4D single-user, `networked` is False.
+* If the `.getInfo()` function is executed on a remote 4D, `networked` is True
+
+
+#### Example 1
+
+```4d
+ var $info : Object
+
+ $info:=ds.getInfo() //Executed on 4D Server or 4D
+ //{"type":"4D","networked":false,"localID":""}
+
+ $info:=ds.getInfo() // Executed on 4D remote
+ //{"type":"4D","networked":true,"localID":""}
+```
+
+#### Example 2
+
+On a remote datastore:
+
+```4d
+ var $remoteDS : cs.DataStore
+ var $info; $connectTo : Object
+
+ $connectTo:=New object("hostname";"111.222.33.44:8044";"user";"marie";"password";"aaaa")
+ $remoteDS:=Open datastore($connectTo;"students")
+ $info:=$remoteDS.getInfo()
+
+ //{"type":"4D Server",
+ //"localID":"students",
+ //"networked":true,
+ //"connection":{hostname:"111.222.33.44:8044","tls":false,"idleTimeout":2880,"user":"marie"}}
+```
+
+
+
+
+
+
+
+## .getRequestLog()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Added|
+
+
+
+**.getRequestLog()** : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Collection|<-|Collection of objects, where each object describes a request|
+
+
+
+#### Description
+
+The `.getRequestLog()` function returns the ORDA requests logged in memory on the client side. The ORDA request logging must have previously been enabled using the [`.startRequestLog()`](#startrequestlog) function.
+
+This function must be called on a remote 4D, otherwise it returns an empty collection. It is designed for debugging purposes in client/server configurations.
+
+**Returned value**
+
+Collection of stacked request objects. The most recent request has index 0.
+
+For a description of the ORDA request log format, please refer to the [**ORDA client requests**](https://doc.4d.com/4Dv18/4D/18/Description-of-log-files.300-4575486.en.html#4385373) section.
+
+
+#### Example
+
+See Example 2 of [`.startRequestLog()`](#startrequestlog).
+
+
+
+
+
+## .isAdminProtected()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added|
+
+
+
+**.isAdminProtected()** : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Boolean|<-|True if the Data Explorer access is disabled, False if it is enabled (default)|
+
+
+
+#### Description
+
+The `.isAdminProtected()` function returns `True` if [Data Explorer](Admin/dataExplorer.md) access has been disabled for the working session.
+
+By default, the Data Explorer access is granted for `webAdmin` sessions, but it can be disabled to prevent any data access from administrators (see the [`.setAdminProtection()`](#setadminprotection) function).
+
+#### See also
+
+[`.setAdminProtection()`](#setadminprotection)
+
+
+
+
+
+
+
+## .makeSelectionsAlterable()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+**.makeSelectionsAlterable()**
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.makeSelectionsAlterable()` function sets all entity selections as alterable by default in the current application datastores (including [remote datastores](ORDA/remoteDatastores.md)). It is intended to be used once, for example in the `On Startup` database method.
+
+When this function is not called, new entity selections can be shareable, depending on the nature of their "parent", or [how they are created](ORDA/entities.md#shareable-or-non-shareable-entity-selections).
+
+> This function does not modify entity selections created by [`.copy()`](#copy) or `OB Copy` when the explicit `ck shared` option is used.
+
+
+> **Compatibility**: This function must only be used in projects converted from 4D versions prior to 4D v18 R5 and containing [.add()](EntitySelectionClass.md#add) calls. In this context, using `.makeSelectionsAlterable()` can save time by restoring instantaneously the previous 4D behavior in existing projects.
+On the other hand, using this method in new projects created in 4D v18 R5 and higher **is not recommended**, since it prevents entity selections to be shared, which provides greater performance and scalabitlity.
+
+
+
+
+
+
+## .provideDataKey()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+**.provideDataKey**( *curPassPhrase* : Text ) : Object
**.provideDataKey**( *curDataKey* : Object ) : Object
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|curPassPhrase |Text|->|Current encryption passphrase|
+|curDataKey |Object|->|Current data encryption key|
+|Result|Object|<-|Result of the encryption key matching|
+
+
+
+#### Description
+
+The `.provideDataKey()` function allows providing a data encryption key for the current data file of the datastore and detects if the key matches the encrypted data. This function can be used when opening an encrypted database, or when executing any encryption operation that requires the encryption key, such as re-encrypting the data file.
+
+>* The `.provideDataKey()` function must be called in an encrypted database. If it is called in a non-encrypted database, the error 2003 (the encryption key does not match the data.) is returned. Use the `Data file encryption status` command to determine if the database is encrypted.
+>* The `.provideDataKey()` function cannot be called from a remote 4D or an encrypted remote datastore.
+
+If you use the *curPassPhrase* parameter, pass the string used to generate the data encryption key. When you use this parameter, an encryption key is generated.
+
+If you use the *curDataKey* parameter, pass an object (with *encodedKey* property) that contains the data encryption key. This key may have been generated with the `New data key` command.
+
+If a valid data encryption key is provided, it is added to the *keyChain* in memory and the encryption mode is enabled:
+
+* all data modifications in encryptable tables are encrypted on disk (.4DD, .journal. 4Dindx files)
+* all data loaded from encryptable tables is decrypted in memory
+
+
+**Result**
+
+The result of the command is described in the returned object:
+
+|Property| |Type| Description|
+|---|---|---|---|
+|success| | Boolean| True if the provided encryption key matches the encrypted data, False otherwise|
+||||Properties below are returned only if success is *FALSE*|
+|status | |Number| Error code (4 if the provided encryption key is wrong)|
+|statusText| | Text| Error message|
+|errors | |Collection| Stack of errors. The first error has the highest index|
+||\[ ].componentSignature| Text| Internal component name|
+||\[ ].errCode |Number |Error number|
+||\[ ].message |Text| Error message|
+
+If no *curPassphrase* or *curDataKey* is given, `.provideDataKey()` returns **null** (no error is generated).
+
+
+
+#### Example
+
+```4d
+ var $keyStatus : Object
+ var $passphrase : Text
+
+ $passphrase:=Request("Enter the passphrase")
+ If(OK=1)
+ $keyStatus:=ds.provideDataKey($passphrase)
+ If($keyStatus.success)
+ ALERT("You have provided a valid encryption key")
+ Else
+ ALERT("You have provided an invalid encryption key, you will not be able to work with encrypted data")
+ End if
+ End if
+```
+
+
+
+
+
+
+## .setAdminProtection()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added|
+
+
+**.setAdminProtection**( *status* : Boolean )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|status|Boolean|->|True to disable Data Explorer access to data on the `webAdmin` port, False (default) to grant access|
+
+
+
+#### Description
+
+The `.setAdminProtection()` function allows disabling any data access on the [web admin port](Admin/webAdmin.md#http-port), including for the [Data Explorer](Admin/dataExplorer.md) in `WebAdmin` sessions.
+
+By default when the function is not called, access to data is always granted on the web administration port for a session with `WebAdmin` privilege using the Data Explorer. In some configurations, for example when the application server is hosted on a third-party machine, you might not want the administrator to be able to view your data, although they can edit the server configuration, including the [access key](Admin/webAdmin.md#access-key) settings.
+
+In this case, you can call this function to disable the data access from Data Explorer on the web admin port of the machine, even if the user session has the `WebAdmin` privilege. When this function is executed, the data file is immediately protected and the status is stored on disk: the data file will be protected even if the application is restarted.
+
+
+#### Example
+
+You create a *protectDataFile* project method to call before deployments for example:
+
+```4d
+ ds.setAdminProtection(True) //Disables the Data Explorer data access
+```
+
+#### See also
+
+[`.isAdminProtected()`](#isadminprotected)
+
+
+
+
+
+## .startRequestLog()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Added|
+
+
+
+**.startRequestLog**()
**.startRequestLog**( *file* : 4D.File )
**.startRequestLog**( *reqNum* : Integer )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|file |4D.File|->|File object|
+|reqNum |Integer|->|Number of requests to keep in memory|
+
+
+
+#### Description
+
+The `.startRequestLog()` function starts the logging of ORDA requests on the client side.
+
+This function must be called on a remote 4D, otherwise it does nothing. It is designed for debugging purposes in client/server configurations.
+
+The ORDA request log can be sent to a file or to memory, depending on the parameter type:
+
+* If you passed a *file* object created with the `File` command, the log data is written in this file as a collection of objects (JSON format). Each object represents a request.
If the file does not already exist, it is created. Otherwise if the file already exists, the new log data is appended to it.
+If `.startRequestLog( )` is called with a file while a logging was previously started in memory, the memory log is stopped and emptied.
+ >A \] character must be manually appended at the end of the file to perform a JSON validation
+
+* If you passed a *reqNum* integer, the log in memory is emptied (if any) and a new log is initialized. It will keep *reqNum* requests in memory until the number is reached, in which case the oldest entries are emptied (FIFO stack).
If `.startRequestLog()` is called with a *reqNum* while a logging was previously started in a file, the file logging is stopped.
+
+* If you did not pass any parameter, the log is started in memory. If `.startRequestLog()` was previously called with a *reqNum* (before a `.stopRequestLog()`), the log data is stacked in memory until the next time the log is emptied or `.stopRequestLog()` is called.
+
+For a description of the ORDA request log format, please refer to the [**ORDA client requests**](https://doc.4d.com/4Dv18/4D/18/Description-of-log-files.300-4575486.en.html#4385373) section.
+
+#### Example 1
+
+You want to log ORDA client requests in a file and use the log sequence number:
+
+```4d
+ var $file : 4D.File
+ var $e : cs.PersonsEntity
+
+ $file:=File("/LOGS/ORDARequests.txt") //logs folder
+
+ SET DATABASE PARAMETER(Client Log Recording;1) //to trigger the global log sequence number
+ ds.startRequestLog($file)
+ $e:=ds.Persons.get(30001) //send a request
+ ds.stopRequestLog()
+ SET DATABASE PARAMETER(Client Log Recording;0)
+```
+
+#### Example 2
+
+You want to log ORDA client requests in memory:
+
+```4d
+ var $es : cs.PersonsSelection
+ var $log : Collection
+
+ ds.startRequestLog(3) //keep 3 requests in memory
+
+ $es:=ds.Persons.query("name=:1";"Marie")
+ $es:=ds.Persons.query("name IN :1";New collection("Marie"))
+ $es:=ds.Persons.query("name=:1";"So@")
+
+ $log:=ds.getRequestLog()
+ ALERT("The longest request lasted: "+String($log.max("duration"))+" ms")
+```
+
+
+
+
+
+
+
+## .startTransaction()
+
+History
+|Version|Changes|
+|---|---|
+|v18|Added|
+
+
+
+**.startTransaction()**
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.startTransaction()` function starts a transaction in the current process on the database matching the datastore to which it applies. Any changes made to the datastore's entities in the transaction's process are temporarily stored until the transaction is either validated or cancelled.
+
+>If this method is called on the main datastore (i.e. the datastore returned by the `ds` command), the transaction is applied to all operations performed on the main datastore and on the underlying database, thus including ORDA and classic languages.
+
+You can nest several transactions (sub-transactions). Each transaction or sub-transaction must eventually be cancelled or validated. Note that if the main transaction is cancelled, all of its sub-transactions are also cancelled even if they were validated individually using the `.validateTransaction()` function.
+
+
+#### Example
+
+
+```4d
+ var $connect; $status : Object
+ var $person : cs.PersonsEntity
+ var $ds : cs.DataStore
+ var $choice : Text
+ var $error : Boolean
+
+ Case of
+ :($choice="local")
+ $ds:=ds
+ :($choice="remote")
+ $connect:=New object("hostname";"111.222.3.4:8044")
+ $ds:=Open datastore($connect;"myRemoteDS")
+ End case
+
+ $ds.startTransaction()
+ $person:=$ds.Persons.query("lastname=:1";"Peters").first()
+
+ If($person#Null)
+ $person.lastname:="Smith"
+ $status:=$person.save()
+ End if
+ ...
+ ...
+ If($error)
+ $ds.cancelTransaction()
+ Else
+ $ds.validateTransaction()
+ End if
+```
+
+
+
+
+
+
+
+
+
+## .stopRequestLog()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Added|
+
+
+
+**.stopRequestLog()**
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.stopRequestLog()` function stops any logging of ORDA requests on the client side (in file or in memory). It is particularly useful when logging in a file, since it actually closes the opened document on disk.
+
+This function must be called on a remote 4D, otherwise it does nothing. It is designed for debugging purposes in client/server configurations.
+
+
+#### Example
+
+See examples for [`.startRequestLog()`](#startrequestlog).
+
+
+
+
+
+
+
+## .validateTransaction()
+
+History
+|Version|Changes|
+|---|---|
+|v18|Added|
+
+
+
+**.validateTransaction()**
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.validateTransaction()` function accepts the transaction that was started with [`.startTransaction()`](#starttransaction) at the corresponding level on the specified datastore.
+
+The function saves the changes to the data on the datastore that occurred during the transaction.
+
+You can nest several transactions (sub-transactions). If the main transaction is cancelled, all of its sub-transactions are also cancelled, even if they were validated individually using this function.
+
+
+#### Example
+
+See example for [`.startTransaction()`](#starttransaction).
+
+
+
+
diff --git a/docs/API/Directory.md b/docs/API/Directory.md
new file mode 100644
index 00000000000000..ec153062535780
--- /dev/null
+++ b/docs/API/Directory.md
@@ -0,0 +1,705 @@
+---
+id: Directory
+title: Directory Class
+---
+
+## Description
+
+
+
+## .creationDate
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.creationDate** : Date
+
+
+#### Description
+
+The `.creationDate` property returns the creation date of the folder.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .creationTime
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.creationTime** : Time
+
+
+
+#### Description
+
+The `.creationTime` property returns the creation time of the folder (expressed as a number of seconds beginning at 00:00).
+
+This property is **read-only**.
+
+
+
+---
+
+
+
+## .exists
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.exists** : Boolean
+
+
+#### Description
+
+The `.exists` property returns true if the folder exists on disk, and false otherwise.
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+
+## .extension
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.extension** : Text
+
+
+#### Description
+
+The `.extension` property returns the extension of the folder name (if any). An extension always starts with ".". The property returns an empty string if the folder name does not have an extension.
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+## .fullName
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.fullName** : Text
+
+
+#### Description
+
+The `.fullName` property returns the full name of the folder, including its extension (if any).
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+## .hidden
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.hidden** : Boolean
+
+
+#### Description
+
+The `.hidden` property returns true if the folder is set as "hidden" at the system level, and false otherwise.
+
+This property is **read-only**.
+
+
+
+---
+
+
+
+## .isAlias
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isAlias** : Boolean
+
+
+
+#### Description
+
+The `.isAlias` property returns always **false** for a `Folder` object.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .isFile
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isFile** : Boolean
+
+
+#### Description
+
+The `.isFile` property returns always **false** for a folder.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .isFolder
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isFolder** : Boolean
+
+
+#### Description
+
+The `.isFolder` property returns always **true** for a folder.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .isPackage
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isPackage** : Boolean
+
+
+#### Description
+
+The `.isPackage` property returns true if the folder is a package on macOS (and exists on disk). Otherwise, it returns false.
+
+On Windows, `.isPackage` always returns **false**.
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+## .modificationDate
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.modificationDate** : Date
+
+
+#### Description
+
+The `.modificationDate` property returns the date of the folder's last modification.
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+## .modificationTime
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.modificationTime** : Time
+
+
+#### Description
+
+The `.modificationTime` property returns the time of the folder's last modification (expressed as a number of seconds beginning at 00:00).
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .name
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+
+**.name** : Text
+
+
+#### Description
+
+The `.name` property returns the name of the folder, without extension (if any).
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .original
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.original** : 4D.Folder
+
+
+#### Description
+
+The `.original` property returns the same Folder object as the folder.
+
+This property is **read-only**.
+
+>This property is available on folders to allow generic code to process folders or files.
+
+
+
+---
+
+
+
+## .parent
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.parent** : 4D.Folder
+
+
+#### Description
+
+The `.parent` property returns the parent folder object of the folder. If the path represents a system path (e.g., "/DATA/"), the system path is returned.
+
+If the folder does not have a parent (root), the null value is returned.
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+## .path
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.path** : Text
+
+
+#### Description
+
+The `.path` property returns the POSIX path of the folder. If the path represents a filesystem (e.g., "/DATA/"), the filesystem is returned.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .platformPath
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.platformPath** : Text
+
+
+#### Description
+
+The `.platformPath` property returns the path of the folder expressed with the current platform syntax.
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+
+
+
+
+## .copyTo()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.copyTo**( *destinationFolder* : 4D.Folder { ; *newName* : Text } { ; *overwrite* : Integer } ) : 4D Folder
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|destinationFolder |4D.Folder |->|Destination folder|
+|newName|Text|->|Name for the copy|
+|overwrite|Integer|->|`fk overwrite` to replace existing elements|
+|Result|4D.Folder|<-|Copied file or folder|
+
+
+
+#### Description
+
+The `.copyTo()` function copies the `Folder` object into the specified *destinationFolder*.
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+By default, the folder is copied with the name of the original folder. If you want to rename the copy, pass the new name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+If a folder with the same name already exists in the *destinationFolder*, by default 4D generates an error. You can pass the `fk overwrite` constant in the *overwrite* parameter to ignore and overwrite the existing file
+
+|Constant|Value|Comment|
+|---|---|---|
+|`fk overwrite`|4|Overwrite existing elements, if any|
+
+
+**Returned value**
+
+The copied `Folder` object.
+
+#### Example
+
+You want to copy a Pictures *folder* from the user's Document folder to the Database folder:
+
+```4d
+var $userImages; $copiedImages : 4D.Folder
+$userImages:=Folder(fk documents folder+"/Pictures/")
+$copiedImages:=$userImages.copyTo(Folder(fk database folder);fk overwrite)
+```
+
+
+
+---
+
+
+
+## .file()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.file**( *path* : Text ) : 4D.File
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|path|Text|->|Relative POSIX file pathname|
+|Result|4D.File|<-|`File` object (null if invalid path)|
+
+
+#### Description
+
+The `.file()` function creates a `File` object inside the `Folder` object and returns its reference.
+
+In *path*, pass a relative POSIX path to designate the file to return. The path will be evaluated from the parent folder as root.
+
+**Returned value**
+
+A `File` object or null if *path* is invalid.
+
+#### Example
+
+```4d
+var $myPDF : 4D.File
+$myPDF:=Folder(fk documents folder).file("Pictures/info.pdf")
+```
+
+
+
+---
+
+
+## .files()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.files**( { *options* : Integer } ) : Collection
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|options|Integer|->|File list options|
+|Result|Collection|<-|Collection of children file objects|
+
+
+#### Description
+
+The `.files()` function returns a collection of `File` objects contained in the folder.
+
+>Aliases or symbolic links are not resolved.
+
+By default, if you omit the *options* parameter, only the files at the first level of the folder are returned in the collection, as well as invisible files or folders. You can modify this by passing, in the *options* parameter, one or more of the following constants:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`fk recursive`|1|The collection contains files or folders of the specified folder and its subfolders|
+|`fk ignore invisible`| 8|Invisible files or folders are not listed|
+
+**Returned value**
+
+Collection of `File` objects.
+
+#### Example 1
+
+You want to know if there are invisible files in the Database folder:
+
+```4d
+ var $all; $noInvisible : Collection
+ $all:=Folder(fk database folder).files()
+ $noInvisible:=Folder(fk database folder).files(fk ignore invisible)
+ If($all.length#$noInvisible.length)
+ ALERT("Database folder contains hidden files.")
+ End if
+```
+
+#### Example 2
+
+You want to get all files that are not invisible in the Documents folder:
+
+```4d
+ var $recursive : Collection
+ $recursive:=Folder(fk documents folder).files(fk recursive+fk ignore invisible)
+```
+
+
+
+---
+
+
+## .folder()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.folder**( *path* : Text ) : 4D.Folder
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|path|Text|->|Relative POSIX file pathname|
+|Result|4D.Folder|<-|Created folder object (null if invalid *path*)|
+
+
+#### Description
+
+The `.folder()` function creates a `Folder` object inside the parent `Folder` object and returns its reference.
+
+In *path*, pass a relative POSIX path to designate the folder to return. The path will be evaluated from the parent folder as root.
+
+**Returned value**
+
+A `Folder` object or null if *path* is invalid.
+
+#### Example
+
+```4d
+ var $mypicts : 4D.Folder
+ $mypicts:=Folder(fk documents folder).folder("Pictures")
+```
+
+
+
+---
+
+
+## .folders()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.folders**( { *options* : Integer } ) : Collection
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|options|Integer|->|Folder list options|
+|Result|Collection|<-|Collection of children folder objects|
+
+
+#### Description
+
+The `.folders()` function returns a collection of `Folder` objects contained in the parent folder.
+
+By default, if you omit the *options* parameter, only the folders at the first level of the folder are returned in the collection. You can modify this by passing, in the *options* parameter, one or more of the following constants:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`fk recursive`| 1|The collection contains files or folders of the specified folder and its subfolders|
+|`fk ignore invisible`| 8|Invisible files or folders are not listed|
+
+**Returned value**
+
+Collection of `Folder` objects.
+
+#### Example
+
+You want the collection of all folders and subfolders of the database folder:
+
+```4d
+ var $allFolders : Collection
+ $allFolders:=Folder("/PACKAGE").folders(fk recursive)
+```
+
+
+
+---
+
+
+## .getIcon()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.getIcon**( { *size* : Integer } ) : Picture
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|size|Integer|->|Side length for the returned picture (pixels)|
+|Result|Picture|<-|Icon|
+
+
+
+#### Description
+
+The `.getIcon()` function returns the icon of the folder.
+
+The optional *size* parameter specifies the dimensions in pixels of the returned icon. This value actually represents the length of the side of the square containing the icon. Icons are usually defined in 32x32 pixels ("large icons") or 16x16 pixels ("small icons"). If you pass 0 or omit this parameter, the "large icon" version is returned.
+
+If the folder does not exist on disk, a default blank icon is returned.
+
+**Returned value**
+
+Folder icon [picture](Concepts/dt_picture.md).
+
+
+
+
+
diff --git a/docs/API/Document.md b/docs/API/Document.md
new file mode 100644
index 00000000000000..26caf6ac82bbd0
--- /dev/null
+++ b/docs/API/Document.md
@@ -0,0 +1,676 @@
+---
+id: Document
+title: Document Class
+---
+
+## Description
+
+
+
+## .creationDate
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.creationDate** : Date
+
+
+#### Description
+
+The `.creationDate` property returns the creation date of the file.
+
+This property is **read-only**.
+
+
+
+
+
+## .creationTime
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.creationTime** : Time
+
+
+#### Description
+
+The `.creationTime` property returns the creation time of the file (expressed as a number of seconds beginning at 00:00).
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .exists
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.exists** : Boolean
+
+
+#### Description
+
+The `.exists` property returns true if the file exists on disk, and false otherwise.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+
+
+## .extension
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.extension** : Text
+
+#### Description
+
+The `.extension` property returns the extension of the file name (if any). An extension always starts with ".". The property returns an empty string if the file name does not have an extension.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+
+## .fullName
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.fullName** : Text
+
+#### Description
+
+The `.fullName` property returns the full name of the file, including its extension (if any).
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .hidden
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.hidden** : Boolean
+
+
+#### Description
+
+The `.hidden` property returns true if the file is set as "hidden" at the system level, and false otherwise.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .isAlias
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isAlias** : Boolean
+
+
+#### Description
+
+The `.isAlias` property returns true if the file is an alias, a shortcut, or a symbolic link, and false otherwise.
+
+This property is **read-only**.
+
+
+
+
+
+
+## .isFile
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isFile** : Boolean
+
+
+#### Description
+
+The `.isFile` property returns always true for a file.
+
+This property is **read-only**.
+
+
+
+
+
+
+## .isFolder
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isFolder** : Boolean
+
+
+#### Description
+
+The `.isFolder` property returns always false for a file.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .isWritable
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.isWritable** : Boolean
+
+
+#### Description
+
+The `.isWritable` property returns true if the file exists on disk and is writable.
+
+>The property checks the ability of the 4D application to write on the disk (access rights), it does not solely rely on the *writable* attribute of the file.
+
+This property is **read-only**.
+
+**Example**
+
+```4d
+ $myFile:=File("C:\\Documents\\Archives\\ReadMe.txt";fk platform path)
+ If($myFile.isWritable)
+ $myNewFile:=$myFile.setText("Added text")
+ End if
+```
+
+
+
+
+
+
+
+## .modificationDate
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.modificationDate** : Date
+
+
+#### Description
+
+The `.modificationDate` property returns the date of the file's last modification.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .modificationTime
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.modificationTime** : Time
+
+
+##### Description
+
+The `.modificationTime` property returns the time of the file's last modification (expressed as a number of seconds beginning at 00:00).
+
+This property is **read-only**.
+
+
+
+
+
+
+## .name
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.name** : Text
+
+
+#### Description
+
+The `.name` property returns the name of the file without extension (if any).
+
+This property is **read-only**.
+
+
+
+
+
+## .original
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.original** : 4D.File
**.original** : 4D.Folder
+
+
+#### Description
+
+The `.original` property returns the target element for an alias, a shortcut, or a symbolic link file. The target element can be:
+
+* a file object
+* a folder object
+
+For non-alias files, the property returns the same file object as the file.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .parent
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.parent** : 4D.Folder
+
+
+#### Description
+
+The `.parent` property returns the parent folder object of the file. If the path represents a system path (e.g., "/DATA/"), the system path is returned.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .path
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.path** : Text
+
+
+#### Description
+
+The `.path` property returns the POSIX path of the file. If the path represents a filesystem (e.g., "/DATA/"), the filesystem is returned.
+
+This property is **read-only**.
+
+
+
+
+
+
+## .platformPath
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.platformPath** : Text
+
+
+#### Description
+
+The `.platformPath` property returns the path of the file expressed with the current platform syntax.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+## .size
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.size** : Real
+
+
+#### Description
+
+The `.size` property returns the size of the file expressed in bytes. If the file does not exist on disk, the size is 0.
+
+This property is **read-only**.
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .copyTo()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.copyTo**( *destinationFolder* : 4D.Folder { ; *newName* : Text } { ; *overwrite* : Integer } ) : 4D.File
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|destinationFolder | 4D.Folder |->|Destination folder|
+|newName|Text|->|Name for the copy|
+|overwrite|Integer|->|`fk overwrite` to replace existing elements|
+|Result|4D.File|<-|Copied file|
+
+
+
+#### Description
+
+The `.copyTo()` function copies the `File` object into the specified *destinationFolder* .
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+By default, the file is copied with the name of the original file. If you want to rename the copy, pass the new name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+If a file with the same name already exists in the *destinationFolder*, by default 4D generates an error. You can pass the `fk overwrite` constant in the *overwrite* parameter to ignore and overwrite the existing file
+
+|Constant|Value|Comment|
+|---|---|---|
+|`fk overwrite`|4|Overwrite existing elements, if any|
+
+
+**Returned value**
+
+The copied `File` object.
+
+#### Example
+
+You want to copy a picture *file* from the user's document folder to the application folder:
+
+```4d
+var $source; $copy : Object
+$source:=Folder(fk documents folder).file("Pictures/photo.png")
+$copy:=$source.copyTo(Folder("/PACKAGE");fk overwrite)
+```
+
+
+
+
+
+
+## .getContent()
+
+History
+|Version|Changes|
+|---|---|
+|v19 R2|Returns 4D.Blob
+|v17 R5|Added
+
+
+
+**.getContent( )** : 4D.Blob
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|Result | 4D.Blob |<-|File content|
+
+
+
+#### Description
+
+The `.getContent()` function returns a `4D.Blob` object containing the entire content of a file. For information on BLOBs, please refer to the [BLOB](Concepts/dt_blob.md) section.
+
+**Returned value**
+
+A `4D.Blob` object.
+
+#### Example
+
+To save a document's contents in a `BLOB` field:
+
+```4d
+ var $vPath : Text
+ $vPath:=Select document("";"*";"Select a document";0)
+ If(OK=1) //If a document has been chosen
+ [aTable]aBlobField:=File($vPath;fk platform path).getContent()
+ End if
+```
+
+
+
+
+
+
+## .getIcon()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.getIcon**( { *size* : Integer } ) : Picture
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|size|Integer|->|Side length for the returned picture (pixels)|
+|Result|Picture|<-|Icon|
+
+
+
+#### Description
+
+The `.getIcon()` function returns the icon of the file.
+
+The optional *size* parameter specifies the dimensions in pixels of the returned icon. This value actually represents the length of the side of the square containing the icon. Icons are usually defined in 32x32 pixels (“large iconsâ€) or 16x16 pixels (“small iconsâ€). If you pass 0 or omit this parameter, the "large icon" version is returned.
+
+If the file does not exist on disk, a default blank icon is returned.
+
+**Returned value**
+
+File icon [picture](../Concepts/picture.html).
+
+
+
+
+
+
+
+
+## .getText()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.getText**( { *charSetName* : Text } { ; } { *breakMode* : integer} ) : Text
**.getText**( { *charSetNum* : integer } { ; } { *breakMode* : integer} ) : Text
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|charSetName |Text |-> |Name of character set|
+|charSetNum |Integer |-> |Number of character set|
+|breakMode|Integer |-> |Processing mode for line breaks|
+|Result |Text |<- |Text from the document|
+
+
+
+#### Description
+The `.getText()` function returns the contents of the file as text .
+
+Optionally, you can designate the character set to be used for reading the contents. You can pass either:
+
+- in *charSetName*, a string containing the standard set name (for example "ISO-8859-1" or ""UTF-8"),
+- or in *charSetNum*, the MIBEnum ID (number) of the standard set name.
+
+> For the list of character sets supported by 4D, refer to the description of the `CONVERT FROM TEXT` command.
+
+If the document contains a Byte Order Mark (BOM), 4D uses the character set that it has set instead of the one specified in *charSetName* or *charSetNum* (this parameter is then ignored).
+If the document does not contain a BOM and if *charSetName* or *charSetNum* is omitted, by default 4D uses the "UTF-8" character set.
+
+In *breakMode*, you can pass a number indicating the processing to apply to end-of-line characters in the document. The following constants of the "System Documents" theme are available:
+
+|Constant | Value| Comment|
+|---|---|---|
+|`Document unchanged`|0|No processing|
+|`Document with native format`|1|(Default) Line breaks are converted to the native format of the operating system: CR (carriage return) under OS X, CRLF (carriage return + line feed) under Windows|
+|`Document with CRLF`|2|Line breaks are converted to Windows format: CRLF (carriage return + line feed)|
+|`Document with CR`|3|Line breaks are converted to OS X format: CR (carriage return)|
+|`Document with LF`|4|Line breaks are converted to Unix format: LF (line feed)|
+
+By default, when you omit the *breakMode* parameter, line breaks are processed in native mode (1).
+
+**Returned value**
+
+Text of the file.
+
+#### Example
+
+Given the following text document (fields are separated by tabs):
+
+```4d
+id name price vat
+3 thé 1.06€ 19.6
+2 café 1.05€ 19.6
+```
+
+When you execute this code:
+
+
+```4d
+ $myFile:=Folder(fk documents folder).file("Billing.txt") //UTF-8 by default
+ $txt:=$myFile.getText()
+```
+... you get:
+
+```4d
+ // $Text = "id name price vat\r\n3 thé 1.06€\t19.6\r\n2\tcafé\t1.05€\t19.6"
+ // \t = tab
+ // \r = CR
+```
+
+
+
+
+
+
+
+
diff --git a/docs/API/EmailObjectClass.md b/docs/API/EmailObjectClass.md
new file mode 100644
index 00000000000000..d8de339c65a4b2
--- /dev/null
+++ b/docs/API/EmailObjectClass.md
@@ -0,0 +1,665 @@
+---
+id: EmailObjectClass
+title: Email
+---
+
+Creating, sending or receiving emails in 4D is done by handling an `Email` object.
+
+`Email` objects are created when receiving mails through a *transporter* class function:
+
+- IMAP - [`.getMail()`](IMAPTransporterClass.md#getmail) and [`.getMails()`](IMAPTransporterClass.md#getmails) functions to get emails from an IMAP server
+- POP3 - [`.getMail()`](POP3TransporterClass.md#getmail) function to get an email from a POP3 server.
+
+> You can also create a new, blank `Email` object by calling the [`New object`](https://doc.4d.com/4dv18/help/command/en/page1471.html) 4D command, and then fill it with [Email object properties](#email-object).
+
+You send `Email` objects using the SMTP [`.send()`](SMTPTransporterClass.md#send) function.
+
+[`MAIL Convert from MIME`](#mail-convert-from-mime) and [`MAIL Convert to MIME`](#mail-convert-to-mime) commands can be used to convert `Email` objects to and from MIME contents.
+
+
+### Email Object
+
+Email objects provide the following properties:
+
+> 4D follows the [JMAP specification](https://jmap.io/spec-mail.html) to format the Email object.
+
+||
+|---|
+|[](#attachments) |
+|[](#bcc)
|
+|[](#bodystructure)
|
+|[](#bodyvalues)
|
+|[](#cc)
|
+|[](#comments)
|
+|[](#from)
|
+|[](#headers)
|
+|[](#htmlbody)
|
+|[](#id)
|
+|[](#inreplyto)
|
+|[](#keywords)
|
+|[](#messageid)
|
+|[](#receivedat)
|
+|[](#references)
|
+|[](#replyto)
|
+|[](#sendat)
|
+|[](#sender)
|
+|[](#size)
|
+|[](#subject)
|
+|[](#textbody)
|
+|[](#to)
|
+
+
+### Email Addresses
+
+All properties that contain email addresses ([`from`](#from), [`cc`](#cc), [`bcc`](#bcc), [`to`](#to), [`sender`](#sender), [`replyTo`](#replyto)) accept a value of text, object, or collection type.
+
+#### Text
+
+- single email: "somebody@domain.com"
+- single display name+email: "Somebody "
+- several emails: "Somebody ,me@home.org"
+
+#### Object
+
+An object with two properties:
+
+|Property|Type|Description|
+|---|---|---|
+|name|Text|Display name (can be null)|
+|email|Text|Email address|
+
+#### Collection
+
+A collection of address objects.
+
+### Handling body part
+
+The [`textBody`](#textbody) and [`htmlBody`](#htmlbody) properties are only used with the [SMTP.send()](SMTPTransporterClass.md#send) function to allow sending simple mails. When both property are filled, the MIME content-type multipart/alternative is used. The email client should then recognize the multipart/alternative part and display the text part or html part as necessary.
+
+[`bodyStructure`](#bodystructure) and [`bodyValues`](#bodyvalues) are used for [SMTP](SMTPTransporterClass.md) when the [Email object](email-object) is built from a MIME document, e.g. when generated by the `MAIL Convert from MIME` command. In this case, both `bodyStructure` and `bodyValues` properties must be passed together, and it is not recommended to use `textBody` and `htmlBody`.
+
+#### Example of bodyStructure and bodyValues objects
+
+```json
+"bodyStructure": {
+ "type": "multipart/mixed",
+ "subParts": [
+ {
+ "partId": "p0001",
+ "type": "text/plain"
+ },
+ {
+ "partId": "p0002",
+ "type": "text/html"
+ }
+ ]
+},
+"bodyValues": {
+ "p0001": {
+ "value": "I have the most brilliant plan. Let me tell you all about it."
+ },
+ "p0002": {
+ "value": "I have the most brilliant plan. Let me tell you all about it.
"
+ }
+}
+```
+
+
+
+
+
+
+## .attachments
+
+
+**.attachments** : Collection
+
+
+#### Description
+
+
+
+The `.attachments` property contains a collection of `4D.MailAttachment` object(s).
+
+Attachment objects are defined through the [`MAIL New attachment`](MailAttachmentClass.md#mail-new-attachment) command. Attachment objects have specific [properties and functions](MailAttachmentClass.md).
+
+
+
+
+## .bcc
+
+
+**.bcc** : Text
**.bcc** : Object
**.bcc** : Collection
+
+
+#### Description
+
+The `.bcc` property contains the Blind Carbon Copy (BCC) hidden email recipient [addresse(s)](#email-addresses) of the email.
+
+
+
+
+## .bodyStructure
+
+
+**.bodyStructure** : Object
+
+
+#### Description
+
+The `.bodyStructure` property contains the *EmailBodyPart* object, i.e. the full MIME structure of the message body (optional). See [Handling body part](#handling-body-part) section.
+
+The `.bodyStructure` object contains the following properties:
+
+|Property|Type|Value|
+|---|---|---|
+|partID|Text|Identifies the part uniquely within the email|
+|type|Text|(mandatory) Value of the Content-Type header field of the part|
+|charset|Text|Value of the charset parameter of the Content-Type header field|
+|encoding|Text|If `isEncodingProblem=true`, the Content-Transfer-Encoding value is added (by default undefined)|
+|disposition|Text|Value of the Content-Disposition header field of the part|
+|language|Collection of texts|List of language tags, as defined in [RFC3282](https://tools.ietf.org/html/rfc3282), in the Content-Language header field of the part, if present.|
+|location|Text|URI, as defined in [RFC2557](https://tools.ietf.org/html/rfc2557), in the Content-Location header field of the part, if present.|
+|subParts|Collection of objects|Body parts of each child (collection of *EmailBodyPart* objects)|
+|headers|Collection of objects|List of all header fields in the part, in the order they appear in the message (collection of *EmailHeader* objects, see [headers](#headers-) property)|
+
+
+
+
+## .bodyValues
+
+
+**.bodyValues** : Object
+
+
+#### Description
+
+The `.bodyValues` property contains the *EmailBodyValue* object, containing an object for each \ of `bodyStructure` (optional). See [Handling body part](#handling-body-part) section.
+
+The `.bodyValues` object contains the following properties:
+
+|Property|Type|Value|
+|---|---|---|
+|*partID*.value|text|Value of the body part|
+|*partID*.isEncodingProblem|boolean|True if malformed sections are found while decoding the charset, or unknown charset, or unknown content transfer-encoding. False by default|
+
+
+
+
+## .cc
+
+
+**.cc** : Text
**.cc** : Object
**.cc** : Collection
+
+
+#### Description
+
+The `.cc` property contains the Carbon Copy (CC) additional email recipient [addresse(s)](#email-addresses) of the email.
+
+
+
+
+
+
+## .comments
+
+
+**.comments** : Text
+
+
+#### Description
+
+The `.comments` property contains an additional comments header.
+
+Comments only appear within the header section of the message (keeping the message's body untouched).
+
+For specific formatting requirements, please consult the [RFC#5322](https://tools.ietf.org/html/rfc5322).
+
+
+
+
+## .from
+
+
+**.from** : Text
**.from** : Object
**.from** : Collection
+
+
+#### Description
+
+The `.from` property contains the Originating [address(es)](#email-addresses) of the email.
+
+
+Each email you send out has both the [sender](#sender) and **from** addresses:
+
+- the sender domain is what the receiving email server gets when opening the session,
+- the from address is what the recipient(s) will see.
+
+For better deliverability, it is recommended to use the same from and sender addresses.
+
+
+
+
+## .headers
+
+
+**.headers** : Collection
+
+
+#### Description
+
+The `.headers` property contains a collection of `EmailHeader` objects, in the order they appear in the message. This property allows users to add extended (registered) headers or user-defined (not registered, starting with "X") headers.
+
+> If an `EmailHeader` object property defines a header such as "from" or "cc" which is already set as a property at the mail level, the `EmailHeader` property is ignored.
+
+Every object of the headers collection can contain the following properties:
+
+|Property|Type|Value|
+|---|---|---|
+|[].name|text|(mandatory) Header field name as defined in [RFC#5322](https://tools.ietf.org/html/rfc5322). If null or undefined, the header field is not added to the MIME header.|
+|[].value|text|Header field values as defined in [RFC#5322](https://tools.ietf.org/html/rfc5322)|
+
+
+
+
+
+
+
+## .htmlBody
+
+
+**.htmlBody** : Text
+
+
+#### Description
+
+The `.htmlBody` property contains the HTML representation of the email message (default charset is UTF-8) (optional, SMTP only). See [Handling body part](#handling-body-part) section.
+
+
+
+
+
+
+
+## .id
+
+
+**.id** : Text
+
+
+#### Description
+
+[IMAP transporter](IMAPTransporterClass.md) only.
+
+The `.id` property contains the unique ID from the IMAP server.
+
+
+
+
+
+
+## .inReplyTo
+
+
+**.inReplyTo** : Text
+
+
+#### Description
+
+The `.inReplyTo` property contains the message identifier(s) of the original message(s) to which the current message is a reply.
+
+For specific formatting requirements, please consult the [RFC#5322](https://tools.ietf.org/html/rfc5322).
+
+
+
+
+
+
+## .keywords
+
+
+**.keywords** : Object
+
+
+#### Description
+
+The `.keywords` property contains a set of keywords as an object, where each property name is a keyword and each value is true.
+
+This property is the "keywords" header (see [RFC#4021](https://tools.ietf.org/html/rfc4021)).
+
+|Property|Type|Value|
+|---|---|---|
+|.\|boolean|Keyword to set (value must be true)|
+
+Reserved keywords:
+* $draft - Indicates a message is a draft
+* $seen - Indicates a message has been read
+* $flagged - Indicates a message needs special attention (e.g., Urgent)
+* $answered - Indicates a message has been replied to
+* $deleted - Indicates a message to delete
+
+#### Example
+
+```
+ $mail.keywords["$flagged"]:=True
+ $mail.keywords["4d"]:=True
+```
+
+
+
+
+## .messageId
+
+
+**.messageId** : Text
+
+
+#### Description
+
+The `.messageId` property contains a message identifier header ("message-id").
+
+This header is usually "lettersOrNumbers@domainname", e.g. "abcdef.123456@4d.com". This unique ID is used in particular on forums or public mailing lists. In general, mail servers automatically add this header to the messages they send.
+
+
+
+## .receivedAt
+
+
+**.receivedAt** : Text
+
+
+#### Description
+
+[IMAP transporter](IMAPTransporterClass.md) only.
+
+The `.receivedAt` property contains the timestamp of the email's arrival on the IMAP server in ISO 8601 UTC format (ex: 2020-09-13T16:11:53Z).
+
+
+
+
+
+
+## .references
+
+
+**.references** : Collection
+
+
+#### Description
+
+The `.references` property contains the Collection of all message-ids of messages in the preceding reply chain.
+
+For specific formatting requirements, please consult the [RFC#5322](https://tools.ietf.org/html/rfc5322).
+
+
+
+
+## .replyTo
+
+
+**.replyTo** : Text
**.replyTo** : Object
**.replyTo** : Collection
+
+
+#### Description
+
+The `.replyTo` property contains the [addresse(s)](#email-addresses) for responses.
+
+
+
+
+
+## .sendAt
+
+
+**.sendAt** : Text
+
+
+#### Description
+
+The `.sendAt` property contains the Email timestamp in ISO 8601 UTC format.
+
+
+
+
+## .sender
+
+
+**.sender** : Text
**.sender** : Object
**.sender** : Collection
+
+
+#### Description
+
+The `.sender` property contains the email source [addresse(s)](#email-addresses) of the email.
+
+
+Each email you send out has both the **sender** and **[from](#from)** addresses:
+
+- the sender domain is what the receiving email server gets when opening the session,
+- the from address is what the recipient(s) will see.
+
+For better deliverability, it is recommended to use the same from and sender addresses.
+
+
+
+
+## .size
+
+
+**.size** : Integer
+
+
+#### Description
+
+[IMAP transporter](IMAPTransporterClass.md) only.
+
+The `.size` property contains the size (expressed in bytes) of the Email object returned by the IMAP server.
+
+
+
+
+## .subject
+
+
+**.subject** : Text
+
+
+#### Description
+
+The `.subject` property contains the description of topic.
+
+
+
+
+
+## .textBody
+
+
+**.textBody** : Text
+
+
+#### Description
+
+The `.textBody` property contains the Plain text representation of the email message (default charset is UTF-8) (optional, SMTP only). See [Handling body part](#handling-body-part) section.
+
+
+
+## .to
+
+
+**.to** : Text
**.to** : Object
**.to** : Collection
+
+
+#### Description
+
+The `.to` property contains the primary recipient [addresse(s)](#email-addresses) of the email.
+
+
+## MAIL Convert from MIME
+
+History
+|Version|Changes|
+|---|---|
+|v18|Added|
+
+
+
+**MAIL Convert from MIME**( *mime* : Blob ) : Object
**MAIL Convert from MIME**( *mime* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mime|Blob, Text|->|Email in MIME|
+|Result|Object|<-|Email object|
+
+
+#### Description
+
+The `MAIL Convert from MIME` command converts a MIME document into a valid email object.
+
+>4D follows the [JMAP specification](https://jmap.io/spec-mail.html) to format the returned email object.
+
+Pass in *mime* a valid MIME document to convert. It can be provided by any mail server or application. You can pass a BLOB or a text *mime* parameter. If the MIME comes from a file, it is recommended to use a BLOB parameter to avoid issues related to charset and line break conversions.
+
+#### Returned object
+
+Email object.
+
+#### Example 1
+
+You want to load a mail template saved as MIME in a text document and send an email:
+
+```4d
+C_BLOB($mime)
+C_OBJECT($mail;$server;$transporter;$status)
+
+$mime:=File("/PACKAGE/Mails/templateMail.txt").getContent())
+
+$mail:=[#current_title_incode]($mime)
+$mail.to:="smith@mail.com"
+$mail.subject:="Hello world"
+
+$server:=New object
+$server.host:="smtp.gmail.com"
+$server.port:=465
+$server.user:="test@gmail.com"
+$server.password:="XXXX"
+
+$transporter:=SMTP New transporter($server)
+$status:=$transporter.send($mail)
+```
+
+#### Example 2
+
+In this example, you send directly a 4D Write Pro document containing pictures:
+
+```4d
+C_TEXT($mime)
+C_OBJECT($email;$server;$transporter;$status)
+
+// Mime export of the 4D Write Pro document
+WP EXPORT VARIABLE(WParea;$mime;wk mime html)
+
+// convert 4D Write Pro Mime variable in mail object
+$email:=MAIL Convert from MIME($mime)
+
+// Fill your mail object headers
+$email.subject:="4D Write Pro HTML body"
+$email.from:="YourEmail@gmail.com"
+$email.to:="RecipientEmail@mail.com"
+
+$server:=New object
+$server.host:="smtp.gmail.com"
+$server.port:=465
+$server.user:="YourEmail@gmail.com"
+$server.password:="XXXX"
+
+$transporter:=SMTP New transporter($server)
+$status:=$transporter.send($email)
+```
+
+
+
+
+
+## MAIL Convert to MIME
+
+History
+|Version|Changes|
+|---|---|
+|v17 R4|Added|
+|v17 R5|Modified|
+
+
+
+**MAIL Convert to MIME**( *mail* : Object { ; *options* : Object } ) : Text
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mail|Object|->|Email object|
+|options|Object|->|Charset and encoding mail options|
+|Result|Text|<-|Email object converted to MIME|
+
+
+#### Description
+
+The `MAIL Convert to MIME` command converts an email object into MIME text. This command is called internally by [SMTP_transporter.send( )](API/SMTPTransporterClass.md#send) to format the email object before sending it. It can be used to analyze the MIME format of the object.
+
+In *mail*, pass the content and the structure details of the email to convert. This includes information such as the email addresses (sender and recipient(s)), the message itself, and the type of display for the message.
+
+>4D follows the [JMAP specification](https://jmap.io/spec-mail.html) to format the email object.
+
+In *options*, you can set a specific charset and encoding configuration for the mail. The following properties are available:
+
+|Property| Type| Description|
+|---|---|---|
+|headerCharset| Text|Charset and encoding used for the following parts of the email: subject, attachment filenames, and email name attribute(s). Possible values:| Constant | Value | Comment |
|---|
| mail mode ISO2022JP | US-ASCII_ISO-2022-JP_UTF8_QP | - headerCharset: US-ASCII if possible, Japanese (ISO-2022-JP) & Quoted-printable if possible, otherwise UTF-8 & Quoted-printable
- bodyCharset: US-ASCII if possible, Japanese (ISO-2022-JP) & 7-bit if possible, otherwise UTF-8 & Quoted-printable
|
| mail mode ISO88591 | ISO-8859-1 | - headerCharset: ISO-8859-1 & Quoted-printable
- bodyCharset: ISO-8859-1 & 8-bit
|
| mail mode UTF8 | US-ASCII_UTF8_QP | headerCharset & bodyCharset: US-ASCII if possible, otherwise UTF-8 & Quoted-printable (**default value**) |
| mail mode UTF8 in base64 | US-ASCII_UTF8_B64 | headerCharset & bodyCharset: US-ASCII if possible, otherwise UTF-8 & base64 |
|
+|bodyCharset| Text| Charset and encoding used for the html and text body contents of the email. Possible values: Same as for headerCharset (see above)|
+
+If the *options* parameter is omitted, the mail mode UTF8 configuration is used for header and body parts.
+
+
+#### Example
+
+```4d
+C_OBJECT($mail)
+C_TEXT($mime)
+$mail:=New object
+
+// Creation of a mail
+$mail.from:="tsales@massmarket.com"
+$mail.subject:="Terrific Sale! This week only!"
+$mail.textBody:="Text format email"
+$mail.htmlBody:="HTML format email"
+$mail.to:=New collection
+$mail.to.push(New object ("email";"noreply@4d.com"))
+$mail.to.push(New object ("email";"test@4d.com"))
+
+// transform the mail object in MIME
+$mime:=[#current_title_incode]($mail)
+
+// Contents of $mime:
+// MIME-Version: 1.0
+// Date: Thu, 11 Oct 2018 15:42:25 GMT
+// Message-ID: <7CA5D25B2B5E0047A36F2E8CB30362E2>
+// Sender: tsales@massmarket.com
+// From: tsales@massmarket.com
+// To: noreply@4d.com
+// To: test@4d.com
+// Content-Type: multipart/alternative; boundary="E0AE5773D5E95245BBBD80DD0687E218"
+// Subject: Terrific Sale! This week only!
+//
+// --E0AE5773D5E95245BBBD80DD0687E218
+// Content-Type: text/plain; charset="UTF-8"
+// Content-Transfer-Encoding: quoted-printable
+//
+// Text format email
+// --E0AE5773D5E95245BBBD80DD0687E218
+// Content-Type: text/html; charset="UTF-8"
+// Content-Transfer-Encoding: quoted-printable
+//
+// HTML format email
+// --E0AE5773D5E95245BBBD80DD0687E218--
+```
+
+
+
diff --git a/docs/API/EntityClass.md b/docs/API/EntityClass.md
new file mode 100644
index 00000000000000..5553bb4f7630f5
--- /dev/null
+++ b/docs/API/EntityClass.md
@@ -0,0 +1,1757 @@
+---
+id: EntityClass
+title: Entity
+---
+
+An [entity](ORDA/dsMapping.md#entity) is an instance of a [Dataclass](ORDA/dsMapping.md#dataclass), like a record of the table matching the dataclass in its associated datastore. It contains the same attributes as the dataclass as well as the data values and specific properties and functions.
+
+
+### Summary
+
+||
+|---|
+|[](#attributename) |
+|[](#clone)
|
+|[](#diff)
|
+|[](#drop)
|
+|[](#first)
|
+|[](#fromobject)
|
+|[](#getdataclass)
|
+|[](#getkey)
|
+|[](#getselection)
|
+|[](#getstamp)
|
+|[](#indexof)
|
+|[](#isnew)
|
+|[](#last)
|
+|[](#lock)
|
+|[](#next)
|
+|[](#previous)
|
+|[](#reload)
|
+|[](#save)
|
+|[](#toobject)
|
+|[](#touched)
|
+|[](#touchedattributes)
|
+|[](#unlock)
|
+
+
+
+
+
+
+
+## .*attributeName*
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+***.attributeName*** : any
+
+
+#### Description
+
+Any dataclass attribute is available as a property of an entity, which stores the attribute value for the entity.
+
+>Dataclass attributes can also be reached using the alternate syntax with \[ ].
+
+The attribute value type depends on the attribute [kind](DataClassAttributeClass.md#kind) (relation or storage):
+
+* If *attributeName* kind is **storage**:
+`.attributeName` returns a value of the same type as *attributeName*.
+* If *attributeName* kind is **relatedEntity**:
+`.attributeName` returns the related entity. Values of the related entity are directly available through cascading properties, for example "myEntity.employer.employees\[0].lastname".
+* If *attributeName* kind is **relatedEntities**:
+`.attributeName` returns a new entity selection of related entities. Duplications are removed (an unordered entity selection is returned).
+
+
+#### Example
+
+```4d
+ var $myEntity : cs.EmployeeEntity
+ $myEntity:=ds.Employee.new() //Create a new entity
+ $myEntity.name:="Dupont" // assign 'Dupont' to the 'name' attribute
+ $myEntity.firstname:="John" //assign 'John' to the 'firstname' attribute
+ $myEntity.save() //save the entity
+```
+
+
+
+
+
+
+
+## .clone()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.clone()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|<-|New entity referencing the record|
+
+
+
+#### Description
+
+The `.clone()` function creates in memory a new entity referencing the same record as the original entity. This function allows you to update entities separately.
+
+>Keep in mind that any modifications done to entities will be saved in the referenced record only when the [`.save( )`](#save) function is executed.
+
+This function can only be used with entities already saved in the database. It cannot be called on a newly created entity (for which [`.isNew()`](#isnew) returns **True**).
+
+
+#### Example
+
+```4d
+ var $emp; $empCloned : cs.EmployeeEntity
+ $emp:=ds.Employee.get(672)
+ $empCloned:=$emp.clone()
+
+ $emp.lastName:="Smith" //Updates done on $emp are not done on $empCloned
+
+```
+
+
+
+
+
+
+
+
+## .diff()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+**.diff**( *entityToCompare* : 4D.Entity { ; *attributesToCompare* : Collection } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entityToCompare|4D.Entity|->|Entity to be compared with the original entity|
+|attributesToCompare|Collection|-> |Name of attributes to be compared |
+|Result|Collection|<-|Differences between the entities|
+
+
+
+#### Description
+
+The `.diff()` function compares the contents of two entities and returns their differences.
+
+In *entityToCompare*, pass the entity to be compared to the original entity.
+
+In *attributesToCompare*, you can designate specific attributes to compare. If provided, the comparison is done only on the specified attributes. If not provided, all differences between the entities are returned.
+
+The differences are returned as a collection of objects whose properties are:
+
+|Property name| Type| Description|
+|---|---|---|
+|attributeName| String| Name of the attribute
+|value|any - Depends on attribute type |Value of the attribute in the entity|
+|otherValue|any - Depends on attribute type|Value of the attribute in *entityToCompare*|
+
+Only attributes with different values are included in the collection. If no differences are found, `.diff()` returns an empty collection.
+
+The function applies for properties whose [kind](DataClassAttributeClass.md#kind) is **storage** or **relatedEntity**. In case a related entity has been updated (meaning the foreign key), the name of the related entity and its primary key name are returned as *attributeName* properties (*value* and *otherValue* are empty for the related entity name).
+
+If one of the compared entities is **Null**, an error is raised.
+
+#### Example 1
+
+
+```4d
+ var $diff1; $diff2 : Collection
+ employee:=ds.Employee.query("ID=1001").first()
+ $clone:=employee.clone()
+ employee.firstName:="MARIE"
+ employee.lastName:="SOPHIE"
+ employee.salary:=500
+ $diff1:=$clone.diff(employee) // All differences are returned
+ $diff2:=$clone.diff(employee;New collection"firstName";"lastName"))
+ // Only differences on firstName and lastName are returned
+```
+
+$diff1:
+
+```4d
+[
+ {
+ "attributeName": "firstName",
+ "value": "Natasha",
+ "otherValue": "MARIE"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Locke",
+ "otherValue": "SOPHIE"
+ },
+ {
+ "attributeName": "salary",
+ "value": 66600,
+ "otherValue": 500
+ }
+]
+$diff2:
+
+[
+ {
+ "attributeName": "firstName",
+ "value": "Natasha",
+ "otherValue": "MARIE"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Locke",
+ "otherValue": "SOPHIE"
+ }
+]
+```
+
+#### Example 2
+
+```4d
+ var vCompareResult1; vCompareResult2; vCompareResult3; $attributesToInspect : Collection
+ vCompareResult1:=New collection
+ vCompareResult2:=New collection
+ vCompareResult3:=New collection
+ $attributesToInspect:=New collection
+
+ $e1:=ds.Employee.get(636)
+ $e2:=ds.Employee.get(636)
+
+ $e1.firstName:=$e1.firstName+" update"
+ $e1.lastName:=$e1.lastName+" update"
+
+ $c:=ds.Company.get(117)
+ $e1.employer:=$c
+ $e2.salary:=100
+
+ $attributesToInspect.push("firstName")
+ $attributesToInspect.push("lastName")
+
+ vCompareResult1:=$e1.diff($e2)
+ vCompareResult2:=$e1.diff($e2;$attributesToInspect)
+ vCompareResult3:=$e1.diff($e2;$e1.touchedAttributes())
+```
+
+vCompareResult1 (all differences are returned):
+
+```4d
+[
+ {
+ "attributeName": "firstName",
+ "value": "Karla update",
+ "otherValue": "Karla"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Marrero update",
+ "otherValue": "Marrero"
+ },
+ {
+ "attributeName": "salary",
+ "value": 33500,
+ "otherValue": 100
+ },
+ {
+ "attributeName": "employerID",
+ "value": 117,
+ "otherValue": 118
+ },
+ {
+ "attributeName": "employer",
+ "value": "[object Entity]",// Entity 117 from Company
+ "otherValue": "[object Entity]"// Entity 118 from Company
+ }
+]
+```
+
+vCompareResult2 (only differences on $attributesToInspect are returned)
+
+```4d
+[
+ {
+ "attributeName": "firstName",
+ "value": "Karla update",
+ "otherValue": "Karla"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Marrero update",
+ "otherValue": "Marrero"
+ }
+]
+```
+
+vCompareResult3 (only differences on $e1 touched attributes are returned)
+
+```4d
+[
+ {
+ "attributeName": "firstName",
+ "value": "Karla update",
+ "otherValue": "Karla"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Marrero update",
+ "otherValue": "Marrero"
+ },
+ {
+ "attributeName": "employerID",
+ "value": 117,
+ "otherValue": 118
+ },
+ {
+ "attributeName": "employer",
+ "value": "[object Entity]",// Entity 117 from Company
+ "otherValue": "[object Entity]"// Entity 118 from Company
+
+ }
+]
+```
+
+
+
+
+
+
+
+## .drop()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.drop**( {*mode* : Integer} ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|Integer|->|`dk force drop if stamp changed`: Forces the drop even if the stamp has changed|
+|Result|Object|<-|Result of drop operation|
+
+
+#### Description
+
+The `.drop()` function deletes the data contained in the entity from the datastore, from the table related to its Dataclass. Note that the entity remains in memory.
+
+In a multi-user or multi-process application, the `.drop()` function is executed under an ["optimistic lock"](ORDA/entities.md#entity-locking) mechanism, wherein an internal locking stamp is automatically incremented each time the record is saved.
+
+By default, if the *mode* parameter is omitted, the function will return an error (see below) if the same entity was modified (i.e. the stamp has changed) by another process or user in the meantime.
+
+Otherwise, you can pass the `dk force drop if stamp changed` option in the *mode* parameter: in this case, the entity is dropped even if the stamp has changed (and the primary key is still the same).
+
+**Result**
+
+The object returned by `.drop( )` contains the following properties:
+
+|Property| | Type |Description|
+|---|---|--- |---|
+|success|| boolean|true if the drop action is successful, false otherwise.|
+||||***Available only in case of error:***|
+|status(*) || number| Error code, see below|
+|statusText(*)|| text| Description of the error, see below|
+||||***Available only in case of pessimistic lock error:***|
+|LockKindText|| text| "Locked by record"|
+|lockInfo|| object| Information about the lock origin|
+||task_id| number| Process id|
+||user_name| text| Session user name on the machine|
+||user4d_alias| text| User alias if defined by `SET USER ALIAS`, otherwise user name in the 4D directory|
+||host_name |text| Machine name|
+||task_name| text| Process name|
+||client_version |text|
+||||***Available only in case of serious error (serious error can be trying to duplicate a primary key, disk full...):***|
+|errors || collection of objects| |
+||message| text| Error message|
+||component signature| text| internal component signature (e.g. "dmbg" stands for the database component)|
+||errCode |number |Error code|
+
+(\*) The following values can be returned in the *status* and *statusText* properties of *Result* object in case of error:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`dk status entity does not exist anymore`|5|The entity no longer exists in the data. This error can occur in the following cases:
the entity has been dropped (the stamp has changed and the memory space is now free)the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space). When using entity.drop( ), this error can be returned when dk force drop if stamp changed option is used. When using entity.lock( ), this error can be returned when dk reload if stamp changed option is used**Associated statusText**: "Entity does not exist anymore"|
+|`dk status locked`|3|The entity is locked by a pessimistic lock.
**Associated statusText**: "Already locked"|
+|`dk status serious error`| 4| A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc.
**Associated statusText**: "Other error"
+|`dk status stamp has changed`| 2|The internal stamp value of the entity does not match the one of the entity stored in the data (optimistic lock).
with `.save( )`: error only if the `dk auto merge` option is not usedwith `.drop( )`: error only if the `dk force drop if stamp changed` option is not usedwith `.lock( )`: error only if the `dk reload if stamp changed` option is not used**Associated statusText**: "Stamp has changed"|
+
+
+#### Example 1
+
+Example without `dk force drop if stamp changed` option:
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ var $status : Object
+ $employees:=ds.Employee.query("lastName=:1";"Smith")
+ $employee:=$employees.first()
+ $status:=$employee.drop()
+ Case of
+ :($status.success)
+ ALERT("You have dropped "+$employee.firstName+" "+$employee.lastName) //The dropped entity remains in memory
+ :($status.status=dk status stamp has changed)
+ ALERT($status.statusText)
+ End case
+```
+
+#### Example 2
+
+Example with `dk force drop if stamp changed` option:
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ var $status : Object
+ $employees:=ds.Employee.query("lastName=:1";"Smith")
+ $employee:=$employees.first()
+ $status:=$employee.drop(dk force drop if stamp changed)
+ Case of
+ :($status.success)
+ ALERT("You have dropped "+$employee.firstName+" "+$employee.lastName) //The dropped entity remains in memory
+ :($status.status=dk status entity does not exist anymore)
+ ALERT($status.statusText)
+ End case
+```
+
+
+
+
+
+
+
+## .first()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.first()**: 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|<-|Reference to first entity of an entity selection (Null if not found)|
+
+
+#### Description
+
+The `.first()` function returns a reference to the entity in first position of the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection( )](#getselection) returns Null), the function returns a Null value.
+
+#### Example
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee; $firstEmployee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@") //This entity selection contains 3 entities
+ $employee:=$employees[2]
+ $firstEmployee:=$employee.first() //$firstEmployee is the first entity of the $employees entity selection
+```
+
+
+
+
+
+
+## .fromObject()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.fromObject**( *filler* : Object )
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|filler|Object|->|Object from which to fill the entity|
+
+
+#### Description
+
+The `.fromObject()` function fills an entity with the *filler* content.
+
+>This function modifies the original entity.
+
+The mapping between the object and the entity is done on the attribute names:
+
+* If a property of the object does not exist in the dataclass, it is ignored.
+* Data types must be equivalent. If there is a type mismatch between the object and dataclass, 4D tries to convert the data whenever possible (see [`Converting data types`](Concepts/data-types.md#converting-data-types)), otherwise the attribute is left untouched.
+* The primary key can be given as is or with a "__KEY" property (filled with the primary key value). If it does not already exist in the dataclass, the entity is created with the given value when [.save()](#save) is called. If the primary key is not given, the entity is created and the primary key value is assigned with respect to database rules. The auto-increment is only computed if the primary key is null.
+
+*filler* can handle a related entity under the following conditions:
+
+* *filler* contains the foreign key itself, or
+* *filler* contains a property object with the same name as the related entity, containing a single property named "\_\_KEY".
+* if the related entity does not exist, it is ignored.
+
+#### Example
+
+With the following $o object:
+
+```4d
+{
+ "firstName": "Mary",
+ "lastName": "Smith",
+ "salary": 36500,
+ "birthDate": "1958-10-27T00:00:00.000Z",
+ "woman": true,
+ "managerID": 411,// relatedEntity given with PK
+ "employerID": 20 // relatedEntity given with PK
+}
+```
+
+
+The following code will create an entity with manager and employer related entities.
+
+
+```4d
+ var $o : Object
+ var $entity : cs.EmpEntity
+ $entity:=ds.Emp.new()
+ $entity.fromObject($o)
+ $entity.save()
+```
+
+
+You could also use a related entity given as an object:
+
+```4d
+
+{
+ "firstName": "Marie",
+ "lastName": "Lechat",
+ "salary": 68400,
+ "birthDate": "1971-09-03T00:00:00.000Z",
+ "woman": false,
+ "employer": {// relatedEntity given as an object
+ "__KEY": "21"
+ },
+ "manager": {// relatedEntity given as an object
+ "__KEY": "411"
+ }
+}
+```
+
+
+
+
+
+
+
+
+## .getDataClass()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.getDataClass()** : 4D.DataClass
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.DataClass|<-|DataClass object to which the entity belongs|
+
+
+#### Description
+
+The `.getDataClass()` function returns the dataclass of the entity. This function is useful when writing generic code.
+
+
+#### Example
+
+The following generic code duplicates any entity:
+
+```4d
+ //duplicate_entity method
+ //duplicate_entity($entity)
+
+ #DECLARE($entity : 4D.Entity)
+ var $entityNew : 4D.Entity
+ var $status : Object
+
+ $entityNew:=$entity.getDataClass().new() //create a new entity in the parent dataclass
+ $entityNew.fromObject($entity.toObject()) //get all attributes
+ $entityNew[$entity.getDataClass().getInfo().primaryKey]:=Null //reset the primary key
+ $status:=$entityNew.save() //save the duplicated entity
+```
+
+
+
+
+
+
+
+## .getKey()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.getKey**( { *mode* : Integer } ) : Text
**.getKey**( { *mode* : Integer } ) : Integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|Integer|->|`dk key as string`: primary key is returned as a string, no matter the primary key type|
+|Result|Text|<-|Value of the text primary key of the entity|
+|Result|Integer|<-|Value of the numeric primary key of the entity|
+
+
+
+#### Description
+
+The `.getKey()` function returns the primary key value of the entity.
+
+Primary keys can be numbers (Integer) or strings. You can "force" the returned primary key value to be a string, no matter the actual primary key type, by passing the `dk key as string` option in the *mode* parameter.
+
+#### Example
+
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName=:1";"Smith")
+ $employee:=$employees[0]
+ ALERT("The primary key is "+$employee.getKey(dk key as string))
+```
+
+
+
+
+
+
+
+## .getSelection()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.getSelection()**: 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.EntitySelection|<-|Entity selection to which the entity belongs (Null if not found)|
+
+
+#### Description
+
+The `.getSelection()` function returns the entity selection which the entity belongs to.
+
+If the entity does not belong to an entity selection, the function returns Null.
+
+#### Example
+
+
+```4d
+ var $emp : cs.EmployeeEntity
+ var $employees; $employees2 : cs.EmployeeSelection
+ $emp:=ds.Employee.get(672) // This entity does not belong to any entity selection
+ $employees:=$emp.getSelection() // $employees is Null
+
+ $employees2:=ds.Employee.query("lastName=:1";"Smith") //This entity selection contains 6 entities
+ $emp:=$employees2[0] // This entity belongs to an entity selection
+
+ ALERT("The entity selection contains "+String($emp.getSelection().length)+" entities")
+```
+
+
+
+
+
+
+## .getStamp()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.getStamp()** : Integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Integer|<-|Stamp of the entity (0 if entity has just been created)|
+
+
+#### Description
+
+The `.getStamp()` function returns the current value of the stamp of the entity.
+
+The internal stamp is automatically incremented by 4D each time the entity is saved. It manages concurrent user access and modifications to the same entities (see [**Entity locking**](ORDA/entities.md#entity-locking)).
+
+>For a new entity (never saved), the function returns 0. To know if an entity has just been created, it is recommended to use [.isNew()](#isnew).
+
+
+#### Example
+
+
+```4d
+ var $entity : cs.EmployeeEntity
+ var $stamp : Integer
+
+ $entity:=ds.Employee.new()
+ $entity.lastname:="Smith"
+ $entity.save()
+ $stamp:=$entity.getStamp() //$stamp=1
+
+ $entity.lastname:="Wesson"
+ $entity.save()
+ $stamp:=$entity.getStamp() //$stamp=2
+```
+
+
+
+
+
+
+
+## .indexOf()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.indexOf**( { *entitySelection* : 4D.EntitySelection } ) : Integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entitySelection|4D.EntitySelection|->|Position of the entity is given according to this entity selection|
+|Result|Integer|<-|Position of the entity in an entity selection|
+
+
+#### Description
+
+The `.indexOf()` function returns the position of the entity in an entity selection.
+
+By default if the *entitySelection* parameter is omitted, the function returns the entity's position within its own entity selection. Otherwise, it returns the position of the entity within the specified *entitySelection*.
+
+The resulting value is included between 0 and the length of the entity selection -1.
+
+* If the entity does not have an entity selection or does not belong to *entitySelection*, the function returns -1.
+* If *entitySelection* is Null or does not belong to the same dataclass as the entity, an error is raised.
+
+#### Example
+
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@") //This entity selection contains 3 entities
+ $employee:=$employees[1] //This entity belongs to an entity selection
+ ALERT("The index of the entity in its own entity selection is "+String($employee.indexOf())) //1
+
+ $employee:=ds.Employee.get(725) //This entity does not belong to an entity selection
+ ALERT("The index of the entity is "+String($employee.indexOf())) // -1
+```
+
+
+
+
+
+
+
+## .isNew()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.isNew()** : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Boolean|<-|True if entity has just been created and not yet saved. Otherwise, False.|
+
+
+#### Description
+
+The `.isNew()` function returns True if the entity to which it is applied has just been created and has not yet been saved in the datastore. Otherwise, it returns False.
+
+
+#### Example
+
+
+```4d
+ var $emp : cs.EmployeeEntity
+
+ $emp:=ds.Employee.new()
+
+ If($emp.isNew())
+ ALERT("This is a new entity")
+ End if
+```
+
+
+
+
+
+
+## .last()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.last()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|<-|Reference to last entity of an entity selection (Null if not found)|
+
+
+#### Description
+
+The `.last()` function returns a reference to the entity in last position of the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection( )](#getselection) returns Null), the function returns a Null value.
+
+
+#### Example
+
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee; $lastEmployee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@") //This entity selection contains 3 entities
+ $employee:=$employees[0]
+ $lastEmployee:=$employee.last() //$lastEmployee is the last entity of the $employees entity selection
+```
+
+
+
+
+
+
+## .lock()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.lock**( { *mode* : Integer } ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|Integer|->|`dk reload if stamp changed`: Reload before locking if stamp changed|
+|Result|Object|<-|Result of lock operation|
+
+
+#### Description
+
+The `.lock()` function puts a pessimistic lock on the record referenced by the entity. The [lock is set](ORDA/entities.md#entity-locking) for a record and all the references of the entity in the current process.
+
+Other processes will see this record as locked (the `result.success` property will contain False if they try to lock the same entity using this function). Only functions executed in the "locking" session are allowed to edit and save the attributes of the entity. The entity can be loaded as read-only by other sessions, but they will not be able to enter and save values.
+
+A locked record is unlocked:
+
+* when the [`unlock()`](#unlock) function is called on a matching entity in the same process
+* automatically, when it is no longer referenced by any entities in memory. For example, if the lock is put only on one local reference of an entity, the entity is unlocked when the function ends. As long as there are references to the entity in memory, the record remains locked.
+
+By default, if the *mode* parameter is omitted, the function will return an error (see below) if the same entity was modified (i.e. the stamp has changed) by another process or user in the meantime.
+
+Otherwise, you can pass the `dk reload if stamp changed` option in the *mode* parameter: in this case, no error is returned and the entity is reloaded when the stamp has changed (if the entity still exists and the primary key is still the same).
+
+**Result**
+
+The object returned by `.lock( )` contains the following properties:
+
+|Property| | Type| Description|
+|---|---|---|---|
+|success| | boolean| true if the lock action is successful (or if the entity is already locked in the current process), false otherwise.|
+||||***Available only if `dk reload if stamp changed` option is used:***|
+|**wasReloaded**| |boolean|true if the entity was reloaded with success, false otherwise.|
+||||***Available only in case of error:***|
+|status(\*)| |number| Error code, see below|
+|statusText(\*)|| text| Description of the error, see below|
+||||***Available only in case of pessimistic lock error:***|
+|lockKindText| | text| "Locked by record"|
+|lockInfo| | object| Information about the lock origin|
+||task_id| number| Process ID|
+||user_name |text| Session user name on the machine|
+||user4d_alias| text| Name or alias of the 4D user|
+||user4d_id |number |User id in the 4D database directory|
+||host_name| text| Machine name
+||task_name |text |Process name|
+||client_version| text ||
+||||***Available only in case of serious error*** (primary key already exists, disk full...):|
+|errors || collection of objects ||
+||message |text |Error message|
+||component signature| text| internal component signature (e.g. "dmbg" stands for the database component)|
+||errCode |number |Error code|
+
+
+(\*) The following values can be returned in the *status* and *statusText* properties of the *Result* object in case of error:
+
+|Constant |Value| Comment|
+|---|---|---|
+|`dk status entity does not exist anymore`| 5 |The entity no longer exists in the data. This error can occur in the following cases:the entity has been dropped (the stamp has changed and the memory space is now free)the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space). When using `.drop( )`, this error can be returned when dk force drop if stamp changed option is used. When using `.lock( )`, this error can be returned when `dk reload if stamp changed` option is used
**Associated statusText**: "Entity does not exist anymore"|
+|`dk status locked`| 3 |The entity is locked by a pessimistic lock.**Associated statusText**: "Already locked"
+|`dk status serious error`| 4 |A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc.
**Associated statusText**: "Other error"|
+|`dk status stamp has changed`|2|The internal stamp value of the entity does not match the one of the entity stored in the data (optimistic lock).
with `.save( )`: error only if the `dk auto merge` option is not usedwith `.drop( )`: error only if the `dk force drop if stamp changed` option is not usedwith `.lock( )`: error only if the `dk reload if stamp changed` option is not used
**Associated statusText**: "Stamp has changed"|
+
+
+#### Example 1
+
+Example with error:
+
+```4d
+ var $employee : cs.EmployeeEntity
+ var $status : Object
+ $employee:=ds.Employee.get(716)
+ $status:=$employee.lock()
+ Case of
+ :($status.success)
+ ALERT("You have locked "+$employee.firstName+" "+$employee.lastName)
+ :($status.status=dk status stamp has changed)
+ ALERT($status.statusText)
+ End case
+```
+
+
+#### Example 2
+
+Example with `dk reload if stamp changed` option:
+
+```4d
+ var $employee : cs.EmployeeEntity
+ var $status : Object
+ $employee:=ds.Employee.get(717)
+ $status:=$employee.lock(dk reload if stamp changed)
+ Case of
+ :($status.success)
+ ALERT("You have locked "+$employee.firstName+" "+$employee.lastName)
+ :($status.status=dk status entity does not exist anymore)
+ ALERT($status.statusText)
+ End case
+```
+
+
+
+
+
+
+## .next()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.next()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|<-|Reference to next entity in the entity selection (Null if not found)|
+
+
+#### Description
+
+The `.next()` function returns a reference to the next entity in the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection()](#getselection) returns Null), the function returns a Null value.
+
+If there is no valid next entity in the entity selection (i.e. you are on the last entity of the selection), the function returns Null. If the next entity has been dropped, the function returns the next valid entity (and eventually Null).
+
+
+#### Example
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee; $nextEmployee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@") //This entity selection contains 3 entities
+ $employee:=$employees[0]
+ $nextEmployee:=$employee.next() //$nextEmployee is the second entity of the $employees entity selection
+
+```
+
+
+
+
+
+## .previous()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.previous()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|<-|Reference to previous entity in the entity selection (Null if not found)|
+
+
+#### Description
+
+The `.previous()` function returns a reference to the previous entity in the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection()](#getselection) returns Null), the function returns a Null value.
+
+If there is no valid previous entity in the entity selection (i.e. you are on the first entity of the selection), the function returns Null. If the previous entity has been dropped, the function returns the previous valid entity (and eventually Null).
+
+
+#### Example
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee; $previousEmployee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@") //This entity selection contains 3 entities
+ $employee:=$employees[1]
+ $previousEmployee:=$employee.previous() //$previousEmployee is the first entity of the $employees entity selection
+```
+
+
+
+
+
+
+## .reload( )
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.reload()** : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Object|<-|Status object|
+
+
+#### Description
+
+The `.reload()` function reloads the content of the entity in memory, according to information stored in the table related to the dataclass in the datastore. The reload is done only if the entity still exists with the same primary key.
+
+**Result**
+
+The object returned by `.reload( )` contains the following properties:
+
+|Property |Type| Description|
+|---|---|---|
+|success|boolean| True if the reload action is successful, False otherwise.***Available only in case of error***:|
+|status(\*)|number|Error code, see below|
+|statusText(\*)|text|Description of the error, see below|
+
+(\*) The following values can be returned in the *status* and *statusText* properties of *Result* object in case of error:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`dk status entity does not exist anymore`|5|The entity no longer exists in the data. This error can occur in the following cases:
the entity has been dropped (the stamp has changed and the memory space is now free)the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space). When using `.drop( )`, this error can be returned when `dk force drop if stamp changed` option is used. When using `.lock( )`, this error can be returned when `dk reload if stamp changed` option is used
***Associated statusText***: "Entity does not exist anymore"|
+|`dk status serious error`|4| A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc.
***Associated statusText***: "Other error"|
+
+
+#### Example
+
+```4d
+ var $employee : cs.EmployeeEntity
+ var $employees : cs.EmployeeSelection
+ var $result : Object
+
+ $employees:=ds.Employee.query("lastName=:1";"Hollis")
+ $employee:=$employees[0]
+ $employee.firstName:="Mary"
+ $result:=$employee.reload()
+ Case of
+ :($result.success)
+ ALERT("Reload has been done")
+ :($result.status=dk status entity does not exist anymore)
+ ALERT("The entity has been dropped")
+ End case
+```
+
+
+
+
+
+## .save()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.save**( { *mode* : Integer } ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|Integer|->|`dk auto merge`: Enables the automatic merge mode|
+|Result|Object|<-|Result of save operation|
+
+
+#### Description
+
+The `.save()` function saves the changes made to the entity in the table related to its dataClass. You must call this method after creating or modifying an entity if you want to save the changes made to it.
+
+The save operation is executed only if at least one entity attribute has been "touched" (see the [`.touched()`](#touched) and [`.touchedAttributes()`](#touchedattributes) functions). Otherwise, the function does nothing (the trigger is not called).
+
+In a multi-user or multi-process application, the `.save()` function is executed under an ["optimistic lock"](ORDA/entities.md#entity-locking) mechanism, wherein an internal locking stamp is automatically incremented each time the record is saved.
+
+By default, if the *mode* parameter is omitted, the method will return an error (see below) whenever the same entity has been modified by another process or user in the meantime, no matter the modified attribute(s).
+
+Otherwise, you can pass the `dk auto merge` option in the *mode* parameter: when the automatic merge mode is enabled, a modification done concurrently by another process/user on the same entity but on a different attribute will not result in an error. The resulting data saved in the entity will be the combination (the "merge") of all non-concurrent modifications (if modifications were applied to the same attribute, the save fails and an error is returned, even with the auto merge mode).
+
+>The automatic merge mode is not available for attributes of Picture, Object, and Text type when stored outside of the record. Concurrent changes in these attributes will result in a `dk status stamp has changed` error.
+
+**Result**
+
+The object returned by `.save()` contains the following properties:
+
+|Property | |Type| Description|
+|---|---|---|---|
+|success| |boolean|True if the save action is successful, False otherwise.|
+||||***Available only if `dk auto merge` option is used***:|
+|autoMerged| |boolean|True if an auto merge was done, False otherwise.|
+||||***Available only in case of error***:|
+|status| |number|Error code, [see below](#status-and-statustext)|
+|statusText| |text|Description of the error, [see below](#status-and-statustext)|
+||||***Available only in case of pessimistic lock error***:|
+|lockKindText| |text|"Locked by record"|
+|lockInfo| |object|Information about the lock origin|
+||task_id| number| Process id|
+||user_name |text| Session user name on the machine|
+||user4d_alias| text| User alias if defined by `SET USER ALIAS`, otherwise user name in the 4D directory|
+||host_name |text |Machine name|
+||task_name| text| Process name|
+||client_version| text||
+||||***Available only in case of serious error*** (serious error - can be trying to duplicate a primary key, disk full...):|
+|errors || collection of objects||
+||message| text |Error message|
+||componentSignature| text| Internal component signature (e.g. "dmbg" stands for the database component)|
+||errCode| number| Error code|
+
+##### status and statusText
+
+The following values can be returned in the `status` and `statusText` properties of Result object in case of error:
+
+|Constant| Value |Comment|
+|---|---|---|
+|`dk status automerge failed`| 6| (Only if the `dk auto merge` option is used) The automatic merge option failed when saving the entity.**Associated statusText**: "Auto merge failed"|
+|`dk status entity does not exist anymore`| 5| The entity no longer exists in the data. This error can occur in the following cases:
the entity has been dropped (the stamp has changed and the memory space is now free)the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space). When using `.drop( )`, this error can be returned when `dk force drop if stamp changed` option is used. When using `.lock( )`, this error can be returned when `dk reload if stamp changed` option is used
**Associated statusText**: "Entity doesnot exist anymore"|
+|`dk status locked`| 3| The entity is locked by a pessimistic lock.**Associated statusText**: "Already locked"
+|`dk status serious error`|4|A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc.
**Associated statusText**: "Other error"|
+|`dk status stamp has changed`|2|The internal stamp value of the entity does not match the one of the entity stored in the data (optimistic lock).
with `.save( )`: error only if the `dk auto merge` option is not usedwith `.drop( )`: error only if the `dk force drop if stamp changed` option is not usedwith `.lock( )`: error only if the `dk reload if stamp changed` option is not used
**Associated statusText**: "Stamp has changed"
+
+
+#### Example 1
+
+Creating a new entity:
+
+```4d
+ var $status : Object
+ var $employee : cs.EmployeeEntity
+ $employee:=ds.Employee.new()
+ $employee.firstName:="Mary"
+ $employee.lastName:="Smith"
+ $status:=$employee.save()
+ If($status.success)
+ ALERT("Employee created")
+ End if
+```
+
+#### Example 2
+
+Updating an entity without `dk auto merge` option:
+
+```4d
+ var $status : Object
+ var $employee : cs.EmployeeEntity
+ var $employees : cs.EmployeeSelection
+ $employees:=ds.Employee.query("lastName=:1";"Smith")
+ $employee:=$employees.first()
+ $employee.lastName:="Mac Arthur"
+ $status:=$employee.save()
+ Case of
+ :($status.success)
+ ALERT("Employee updated")
+ :($status.status=dk status stamp has changed)
+ ALERT($status.statusText)
+ End case
+```
+
+#### Example 3
+
+Updating an entity with `dk auto merge` option:
+
+```4d
+ var $status : Object
+
+ var $employee : cs.EmployeeEntity
+ var $employees : cs.EmployeeSelection
+
+ $employees:=ds.Employee.query("lastName=:1";"Smith")
+ $employee:=$employees.first()
+ $employee.lastName:="Mac Arthur"
+ $status:=$employee.save(dk auto merge)
+ Case of
+ :($status.success)
+ ALERT("Employee updated")
+ :($status.status=dk status automerge failed)
+ ALERT($status.statusText)
+ End case
+```
+
+
+
+
+
+
+## .toObject()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.toObject**() : Object
**.toObject**( *filterString* : Text { ; *options* : Integer} ) : Object
**.toObject**( *filterCol* : Collection { ; *options* : Integer } ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|filterString |Text |->|Attribute(s) to extract (comma-separated string)|
+|filterCol |Collection |->|Collection of attribute(s) to extract|
+|options|Integer|->|`dk with primary key`: adds the \_KEY property;
`dk with stamp`: adds the \_STAMP property|
+|Result|Object|<-|Object built from the entity|
+
+
+#### Description
+
+The `.toObject()` function returns an object which has been built from the entity. Property names in the object match attribute names of the entity.
+
+If no filter is specified, or if the *filterString* parameter contains an empty string or "*", the returned object will contain:
+
+* all storage entity attributes
+* attributes of the `relatedEntity` [kind](DataClassAttributeClass.md#kind): you get a property with the same name as the related entity (name of the many-to-one link). Attribute is extracted with the simple form.
+* attributes of the `relatedEntities` [kind](DataClassAttributeClass.md#kind): attribute is not returned.
+
+
+In the first parameter, you pass the entity attribute(s) to extract. You can pass:
+
+* *filterString*: a string with property paths separated with commas: "propertyPath1, propertyPath2, ...", or
+* *filterCol*: a collection of strings: \["propertyPath1","propertyPath2";...]
+
+If a filter is specified for attributes of the relatedEntity [kind](DataClassAttributeClass.md#kind):
+
+* propertyPath = "relatedEntity" -> it is extracted with simple form: an object with property \_\_KEY (primary key).
+* propertyPath = "relatedEntity.*" -> all the properties are extracted
+* propertyPath = "relatedEntity.propertyName1; relatedEntity.propertyName2; ..." -> only those properties are extracted
+
+If a filter is specified for attributes of the relatedEntities [kind](DataClassAttributeClass.md#kind):
+
+* propertyPath = "relatedEntities.*" -> all the properties are extracted
+* propertyPath = "relatedEntities.propertyName1; relatedEntities.propertyName2; ..." -> only those properties are extracted
+
+In the *options* parameter, you can pass the `dk with primary key` and/or` dk with stamp` selector(s) to add the entity's primary keys and/or stamps in extracted objects.
+
+
+#### Example 1
+
+The following structure will be used throughout all examples of this section:
+
+
+
+
+Without filter parameter:
+
+```4d
+employeeObject:=employeeSelected.toObject()
+```
+
+Returns:
+
+```4d
+{
+ "ID": 413,
+ "firstName": "Greg",
+ "lastName": "Wahl",
+ "salary": 0,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": { // relatedEntity extracted with simple form
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+}
+```
+
+
+
+#### Example 2
+
+Extracting the primary key and the stamp:
+
+```4d
+employeeObject:=employeeSelected.toObject("";dk with primary key+dk with stamp)
+```
+
+Returns:
+
+```4d
+{
+ "__KEY": 413,
+ "__STAMP": 1,
+ "ID": 413,
+ "firstName": "Greg",
+ "lastName": "Wahl",
+ "salary": 0,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+}
+```
+
+#### Example 3
+
+Expanding all the properties of `relatedEntities`:
+
+```4d
+employeeObject:=employeeSelected.toObject("directReports.*")
+```
+
+```4d
+{
+ "directReports": [
+ {
+ "ID": 418,
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "salary": 44800,
+ "birthDate": "1970-10-02T00:00:00.000Z",
+ "woman": true,
+ "managerID": 413,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 413
+ }
+ },
+ {
+ "ID": 419,
+ "firstName": "Drew",
+ "lastName": "Caudill",
+ "salary": 41000,
+ "birthDate": "2030-01-12T00:00:00.000Z",
+ "woman": false,
+ "managerID": 413,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 413
+ }
+ },
+ {
+ "ID": 420,
+ "firstName": "Nathan",
+ "lastName": "Gomes",
+ "salary": 46300,
+ "birthDate": "2010-05-29T00:00:00.000Z",
+ "woman": false,
+ "managerID": 413,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 413
+ }
+ }
+ ]
+}
+```
+
+#### Example 4
+
+Extracting some properties of `relatedEntities`:
+
+```4d
+ employeeObject:=employeeSelected.toObject("firstName, directReports.lastName")
+```
+
+Returns:
+
+```4d
+{
+ "firstName": "Greg",
+ "directReports": [
+ {
+ "lastName": "Boothe"
+ },
+ {
+ "lastName": "Caudill"
+ },
+ {
+ "lastName": "Gomes"
+ }
+ ]
+}
+```
+
+#### Example 5
+
+Extracting a `relatedEntity` with simple form:
+
+```4d
+ $coll:=New collection("firstName";"employer")
+ employeeObject:=employeeSelected.toObject($coll)
+```
+
+Returns:
+
+```4d
+{
+ "firstName": "Greg",
+ "employer": {
+ "__KEY": 20
+ }
+}
+```
+
+#### Example 6
+
+Extracting all the properties of a `relatedEntity`:
+
+```4d
+ employeeObject:=employeeSelected.toObject("employer.*")
+```
+
+Returns:
+
+```4d
+{
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+}
+```
+
+#### Example 7
+
+Extracting some properties of a `relatedEntity`:
+
+```4d
+ $col:=New collection
+ $col.push("employer.name")
+ $col.push("employer.revenues")
+ employeeObject:=employeeSelected.toObject($col)
+```
+
+Returns:
+
+```4d
+{
+ "employer": {
+ "name": "India Astral Secretary",
+ "revenues": 12000000
+ }
+}
+```
+
+
+
+
+
+
+## .touched( )
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.touched()** : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Boolean|<-|True if at least one entity attribute has been modified and not yet saved, else False|
+
+
+#### Description
+
+The `.touched()` function tests whether or not an entity attribute has been modified since the entity was loaded into memory or saved.
+
+If an attribute has been modified or calculated, the function returns True, else it returns False. You can use this function to determine if you need to save the entity.
+
+This function returns False for a new entity that has just been created (with [`.new( )`](DataClassClass.md#new)). Note however that if you use a function which calculates an attribute of the entity, the `.touched()` function will then return True. For example, if you call [`.getKey()`](#getkey) to calculate the primary key, `.touched()` returns True.
+
+#### Example
+
+In this example, we check to see if it is necessary to save the entity:
+
+```4d
+ var $emp : cs.EmployeeEntity
+ $emp:=ds.Employee.get(672)
+ $emp.firstName:=$emp.firstName //Even if updated with the same value, the attribute is marked as touched
+
+ If($emp.touched()) //if at least one of the attributes has been changed
+ $emp.save()
+ End if // otherwise, no need to save the entity
+```
+
+
+
+
+
+## .touchedAttributes( )
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.touchedAttributes()** : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Collection|<-|Names of touched attributes, or empty collection|
+
+
+#### Description
+
+The `.touchedAttributes()` function returns the names of the attributes that have been modified since the entity was loaded into memory.
+
+This applies for attributes of the [kind](DataClassAttributeClass.md#kind) `storage` or `relatedEntity`.
+
+In the case of a related entity having been touched (i.e., the foreign key), the name of the related entity and its primary key's name are returned.
+
+If no entity attribute has been touched, the method returns an empty collection.
+
+#### Example 1
+
+
+```4d
+ var $touchedAttributes : Collection
+ var $emp : cs.EmployeeEntity
+
+ $touchedAttributes:=New collection
+ $emp:=ds.Employee.get(725)
+ $emp.firstName:=$emp.firstName //Even if updated with the same value, the attribute is marked as touched
+ $emp.lastName:="Martin"
+ $touchedAttributes:=$emp.touchedAttributes()
+ //$touchedAttributes: ["firstName","lastName"]
+```
+
+
+#### Example 2
+
+
+```4d
+ var $touchedAttributes : Collection
+ var $emp : cs.EmployeeEntity
+ var $company : cs.CompanyEntity
+
+ $touchedAttributes:=New collection
+
+ $emp:=ds.Employee.get(672)
+ $emp.firstName:=$emp.firstName
+ $emp.lastName:="Martin"
+
+ $company:=ds.Company.get(121)
+ $emp.employer:=$company
+
+ $touchedAttributes:=$emp.touchedAttributes()
+
+ //collection $touchedAttributes: ["firstName","lastName","employer","employerID"]
+```
+
+In this case:
+
+* firstName and lastName have a `storage` kind
+* employer has a `relatedEntity` kind
+* employerID is the foreign key of the employer related entity
+
+
+
+
+
+## .unlock()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.unlock()** : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Object|<-|Status object|
+
+
+#### Description
+
+The `.unlock()` function removes the pessimistic lock on the record matching the entity in the datastore and table related to its dataclass.
+
+> For more information, please refer to [Entity locking](ORDA/entities.md#entity-locking) section.
+
+A record is automatically unlocked when it is no longer referenced by any entities in the locking process (for example: if the lock is put only on one local reference of an entity, the entity and thus the record is unlocked when the process ends).
+
+>When a record is locked, it must be unlocked from the locking process and on the entity reference which put the lock. For example:
+
+```4d
+ $e1:=ds.Emp.all()[0]
+ $e2:=ds.Emp.all()[0]
+ $res:=$e1.lock() //$res.success=true
+ $res:=$e2.unlock() //$res.success=false
+ $res:=$e1.unlock() //$res.success=true
+```
+
+**Result**
+
+The object returned by `.unlock()` contains the following property:
+
+|Property| Type |Description|
+|---|---|---|
+|success| Boolean| True if the unlock action is successful, False otherwise. If the unlock is done on a dropped entity, on a non locked record, or on a record locked by another process or entity, success is False.|
+
+#### Example
+
+```4d
+ var $employee : cs.EmployeeEntity
+ var $status : Object
+
+ $employee:=ds.Employee.get(725)
+ $status:=$employee.lock()
+ ... //processing
+ $status:=$employee.unlock()
+ If($status.success)
+ ALERT("The entity is now unlocked")
+ End if
+```
+
+
+
+
+
+
diff --git a/docs/API/EntitySelectionClass.md b/docs/API/EntitySelectionClass.md
new file mode 100644
index 00000000000000..610577c1ad871b
--- /dev/null
+++ b/docs/API/EntitySelectionClass.md
@@ -0,0 +1,2423 @@
+---
+id: EntitySelectionClass
+title: EntitySelection
+---
+
+
+An entity selection is an object containing one or more reference(s) to [entities](ORDA/dsMapping.md#entity) belonging to the same [Dataclass](ORDA/dsMapping.md#dataclass). An entity selection can contain 0, 1 or X entities from the dataclass -- where X can represent the total number of entities contained in the dataclass.
+
+Entity selections can be created from existing selections using various functions of the [`DataClass` class](DataClassClass.md) such as [`.all()`](DataClassClass.md#all) or [`.query()`](DataClassClass.md#query), or functions of the `EntityClass` class itself, such as [`.and()`](#and) or [`orderBy()`](#orderby). You can also create blank entity selections using the [`dataClass.newSelection()`](DataClassClass.md#newselection) function or the [`Create new selection`](#create-new-selection) command.
+
+### Summary
+
+||
+|---|
+|[](#91index93) |
+|[](#attributename)
|
+|[](#add)
|
+|[](#and)
|
+|[](#average)
|
+|[](#contains)
|
+|[](#count)
|
+|[](#distinct)
|
+|[](#drop)
|
+|[](#extract)
|
+|[](#first)
|
+|[](#getdataclass)
|
+|[](#isalterable)
|
+|[](#isordered)
|
+|[](#last)
|
+|[](#length)
|
+|[](#max)
|
+|[](#min)
|
+|[](#minus)
|
+|[](#or)
|
+|[](#orderby)
|
+|[](#orderbyformula)
|
+|[](#query)
|
+|[](#querypath)
|
+|[](#queryplan)
|
+|[](#refresh)
|
+|[](#selected)
|
+|[](#slice)
|
+|[](#sum)
|
+|[](#tocollection)
|
+
+
+
+## Create entity selection
+
+
+**Create entity selection** ( *dsTable* : Table { ; *settings* : Object } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|dsTable|Table|->|Table in the 4D database whose current selection will be used to build the entity selection|
+|settings|Object|->|Build option: context |
+|Result|4D.EntitySelection|<-|Entity selection matching the dataclass related to the given table|
+
+
+
+#### Description
+
+The `Create entity selection` command builds and returns a new, [alterable](ORDA/entities.md#shareable-or-alterable-entity-selections) entity selection related to the dataclass matching the given *dsTable*, according to the current selection of this table.
+
+If the current selection is sorted, an [ordered](ORDA/dsMapping.md#ordered-or-unordered-entity-selection) entity selection is created (the order of the current selection is kept). If the current selection is unsorted, an unordered entity selection is created.
+
+If the *dsTable* is not exposed in [`ds`](API/DataStoreClass.md#ds), an error is returned. This command cannot be used with a Remote datastore.
+
+In the optional *settings* parameter, you can pass an object containing the following property:
+
+|Property|Type|Description|
+|---|---|---|
+|context|Text|Label for the [optimization context](ORDA/entities.md#clientserver-optimization) applied to the entity selection.|
+
+
+#### Example
+
+```4d
+var $employees : cs.EmployeeSelection
+ALL RECORDS([Employee])
+$employees:=Create entity selection([Employee])
+// The $employees entity selection now contains a set of reference
+// on all entities related to the Employee dataclass
+```
+
+#### See also
+
+[`dataClass.newSelection()`](DataClassClass.md#newselection)
+
+
+## [*index*]
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+***[index]*** : 4D.Entity
+
+
+#### Description
+
+The `EntitySelection[index]` notation allows you to access entities within the entity selection using the standard collection syntax: pass the position of the entity you want to get in the *index* parameter.
+
+Note that the corresponding entity is reloaded from the datastore.
+
+*index* can be any number between 0 and `.length`-1.
+
+* If *index* is out of range, an error is returned.
+* If *index* corresponds to a dropped entity, a Null value is returned.
+
+>**Warning**: `EntitySelection[index]` is a non assignable expression, which means that it cannot be used as en editable entity reference with methods like [`.lock()`](EntityClass.md#lock) or [`.save()`](EntityClass.md#save). To work with the corresponding entity, you need to assign the returned expression to an assignable expression, such as a variable. Examples:
+
+```4d
+ $sel:=ds.Employee.all() //create the entity selection
+ //invalid statements:
+ $result:=$sel[0].lock() //will NOT work
+ $sel[0].lastName:="Smith" //will NOT work
+ $result:=$sel[0].save() //will NOT work
+ //valid code:
+ $entity:=$sel[0] //OK
+ $entity.lastName:="Smith" //OK
+ $entity.save() //OK
+```
+
+#### Example
+
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@")
+ $employee:=$employees[2] // The 3rd entity of the $employees entity selection is reloaded from the database
+```
+
+
+
+
+
+
+
+## .*attributeName*
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+***.attributeName*** : Collection
***.attributeName*** : 4D.EntitySelection
+
+
+#### Description
+
+Any dataclass attribute can be used as a property of an entity selection to return a "projection" of values for the attribute in the entity selection. Projected values can be a collection or a new entity selection, depending on the [kind](DataClassAttributeClass.md#kind) (`storage` or `relation`) of the attribute.
+
+* If *attributeName* kind is `storage`:
+`.attributeName` returns a collection of values of the same type as *attributeName*.
+* If *attributeName* kind is `relatedEntity`:
+`.attributeName` returns a new entity selection of related values of the same type as *attributeName*. Duplications are removed (an unordered entity selection is returned).
+* If *attributeName* kind is `relatedEntities`:
+`.attributeName` returns a new entity selection of related values of the same type as *attributeName*. Duplications are removed (an unordered entity selection is returned).
+
+
+When a relation attribute is used as a property of an entity selection, the result is always another entity selection, even if only one entity is returned. In this case, if no entities are returned, the result is an empty entity selection.
+
+If the attribute does not exist in the entity selection, an error is returned.
+
+
+
+
+
+
+#### Example 1
+
+Projection of storage values:
+
+
+
+
+```4d
+ var $firstNames : Collection
+ $entitySelection:=ds.Employee.all()
+ $firstNames:=$entitySelection.firstName // firstName type is string
+```
+
+The resulting collection is a collection of strings, for example:
+
+```4d
+[
+ "Joanna",
+ "Alexandra",
+ "Rick"
+]
+```
+
+#### Example 2
+
+Projection of related entity:
+
+```4d
+ var $es; $entitySelection : cs.EmployeeSelection
+ $entitySelection:=ds.Employee.all()
+ $es:=$entitySelection.employer // employer is related to a Company dataClass
+```
+
+The resulting object is an entity selection of Company with duplications removed (if any).
+
+#### Example 3
+
+Projection of related entities:
+
+```4d
+ var $es : cs.EmployeeSelection
+ $es:=ds.Employee.all().directReports // directReports is related to Employee dataclass
+```
+
+The resulting object is an entity selection of Employee with duplications removed (if any).
+
+
+
+
+
+
+
+## .add()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Only supports alterable entity selections|
+|v17|Added|
+
+
+
+
+**.add**( *entity* : 4D.Entity ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity|4D.Entity|->|Entity to be added to the entity selection|
+|Result|4D.EntitySelection|->|Entity selection including the added *entity*|
+
+
+
+#### Description
+
+The `.add()` function adds the specified *entity* to the entity selection and returns the modified entity selection.
+
+>This function modifies the original entity selection.
+
+**Warning:** The entity selection must be *alterable*, i.e. it has been created for example by [`.newSelection()`](DataClassClass.md#newselection) or `Create entity selection`, otherwise `.add()` will return an error. Shareable entity selections do not accept the addition of entities. For more information, please refer to the [Shareable or alterable entity selections](ORDA/entities.md#shareable-or-alterable-entity-selections) section.
+
+
+* If the entity selection is ordered, *entity* is added at the end of the selection. If a reference to the same entity already belongs to the entity selection, it is duplicated and a new reference is added.
+* If the entity selection is unordered, *entity* is added anywhere in the selection, with no specific order.
+
+>For more information, please refer to the [Ordered or unordered entity selection](ORDA/dsMapping.md#ordered-or-unordered-entity-selection) section.
+
+The modified entity selection is returned by the function, so that function calls can be chained.
+
+An error occurs if *entity* and the entity selection are not related to the same Dataclass. If *entity* is Null, no error is raised.
+
+#### Example 1
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"S@")
+ $employee:=ds.Employee.new()
+ $employee.lastName:="Smith"
+ $employee.save()
+ $employees.add($employee) //The $employee entity is added to the $employees entity selection
+```
+
+#### Example 2
+
+Calls to the function can be chained:
+
+```4d
+ var $sel : cs.ProductSelection
+ var $p1;$p2;$p3 : cs.ProductEntity
+
+ $p1:=ds.Product.get(10)
+ $p2:=ds.Product.get(11)
+ $p3:=ds.Product.get(12)
+ $sel:=ds.Product.query("ID > 50")
+ $sel:=$sel.add($p1).add($p2).add($p3)
+```
+
+
+
+
+
+## .and()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+**.and**( *entity* : 4D.Entity ) : 4D.EntitySelection
**.and**( *entitySelection* : 4D.EntitySelection ) : 4D.EntitySelection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity |4D.Entity|->|Entity to intersect with|
+|entitySelection |4D.EntitySelection|->|Entity selection to intersect with|
+|Result|4D.EntitySelection|<-|New entity selection with the result of intersection with logical AND operator|
+
+
+
+#### Description
+
+The `.and()` function combines the entity selection with an *entity* or *entitySelection* parameter using the logical AND operator; it returns a new, unordered entity selection that contains only the entities that are referenced in both the entity selection and the parameter.
+
+* If you pass *entity* as parameter, you combine this entity with the entity selection. If the entity belongs to the entity selection, a new entity selection containing only the entity is returned. Otherwise, an empty entity selection is returned.
+* If you pass *entitySelection* as parameter, you combine both entity selections. A new entity selection that contains only the entities that are referenced in both selections is returned. If there is no intersecting entity, an empty entity selection is returned.
+
+>You can compare [ordered and/or unordered entity selections](ORDA/dsMapping.md#ordered-or-unordered-entity-selection). The resulting selection is always unordered.
+
+If the original entity selection or the *entitySelection* parameter is empty, or if the *entity* is Null, an empty entity selection is returned.
+
+If the original entity selection and the parameter are not related to the same dataclass, an error is raised.
+
+
+#### Example 1
+
+
+```4d
+ var $employees; $result : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@")
+ //The $employees entity selection contains the entity
+ //with primary key 710 and other entities
+ //for ex. "Colin Hetrick" / "Grady Harness" / "Sherlock Holmes" (primary key 710)
+ $employee:=ds.Employee.get(710) // Returns "Sherlock Holmes"
+
+ $result:=$employees.and($employee) //$result is an entity selection containing
+ //only the entity with primary key 710 ("Sherlock Holmes")
+```
+
+
+#### Example 2
+
+We want to have a selection of employees named "Jones" who live in New York:
+
+```4d
+ var $sel1; $sel2; $sel3 : cs.EmployeeSelection
+ $sel1:=ds.Employee.query("name =:1";"Jones")
+ $sel2:=ds.Employee.query("city=:1";"New York")
+ $sel3:=$sel1.and($sel2)
+```
+
+
+
+
+
+## .average()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Returns undefined if empty entity selection|
+|v17|Added|
+
+
+
+
+**.average**( *attributePath* : Text ) : Real
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |Text|->|Attribute path to be used for calculation|
+|Result|Real|<-|Arithmetic mean (average) of entity attribute values (Undefined if empty entity selection)|
+
+
+#### Description
+
+The `.average()` function returns the arithmetic mean (average) of all the non-null values of *attributePath* in the entity selection.
+
+Pass in the *attributePath* parameter the attribute path to evaluate.
+
+Only numerical values are taken into account for the calculation. Note however that, if the *attributePath* of the entity selection contains mixed value types, `.average()` takes all scalar elements into account to calculate the average value.
+
+>Date values are converted to numerical values (seconds) and used to calculate the average.
+
+`.average()` returns **undefined** if the entity selection is empty or *attributePath* does not contain numerical values.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* designates an attribute that does not exist in the entity selection dataclass.
+
+
+#### Example
+
+We want to obtain a list of employees whose salary is higher than the average salary:
+
+```4d
+ var $averageSalary : Real
+ var $moreThanAv : cs.EmployeeSelection
+ $averageSalary:=ds.Employee.all().average("salary")
+ $moreThanAv:=ds.Employee.query("salary > :1";$averageSalary)
+```
+
+
+
+
+
+
+## .contains()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.contains**( *entity* : 4D.Entity ) : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity|4D.Entity|->|Entity to evaluate|
+|Result|Boolean|<-|True if the entity belongs to the entity selection, else False|
+
+
+#### Description
+
+The `.contains()` function returns true if entity reference belongs to the entity selection, and false otherwise.
+
+In *entity*, specify the entity to search for in the entity selection. If entity is Null, the function will return false.
+
+If *entity* and the entity selection do not belong to the same dataclass, an error is raised.
+
+#### Example
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+
+ $employees:=ds.Employee.query("lastName=:1";"H@")
+ $employee:=ds.Employee.get(610)
+
+ If($employees.contains($employee))
+ ALERT("The entity with primary key 610 has a last name beginning with H")
+ Else
+ ALERT("The entity with primary key 610 does not have a last name beginning with H")
+ End if
+```
+
+
+
+
+
+
+## .count()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.count**( *attributePath* : Text ) : Real
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |Text|->|Path of the attribute to be used for calculation|
+|Result|Real|<-|Number of non null *attributePath* values in the entity selection|
+
+
+#### Description
+
+The `.count()` function returns the number of entities in the entity selection with a non-null value in *attributePath*.
+
+>Only scalar values are taken into account. Object or collection type values are considered as null values.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* is not found in the entity selection dataclass.
+
+#### Example
+
+We want to find out the total number of employees for a company without counting any whose job title has not been specified:
+
+```4d
+ var $sel : cs.EmployeeSelection
+ var $count : Real
+
+ $sel:=ds.Employee.query("employer = :1";"Acme, Inc")
+ $count:=$sel.count("jobtitle")
+```
+
+
+
+
+
+## .copy()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+
+**.copy**( { *option* : Integer } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|option |Integer|->|`ck shared`: return a shareable entity selection|
+|Result|4D.EntitySelection|<-|Copy of the entity selection|
+
+
+#### Description
+
+The `.copy()` function returns a copy of the original entity selection.
+
+> This function does not modify the original entity selection.
+
+By default, if the *option* parameter is omitted, the function returns a new, alterable entity selection (even if the function is applied to a shareable entity selection). Pass the `ck shared` constant in the *option* parameter if you want to create a shareable entity selection.
+
+> For information on the shareable property of entity selections, please refer to the [Shareable or alterable entity selections](ORDA/entities.md#shareable-or-alterable-entity-selections) section.
+
+#### Example
+
+You create a new, empty entity selection of products when the form is loaded:
+
+```4d
+ Case of
+ :(Form event code=On Load)
+ Form.products:=ds.Products.newSelection()
+ End case
+
+```
+
+Then this entity selection is updated with products and you want to share the products between several processes. You copy the Form.products entity selection as a shareable one:
+
+```4d
+ ...
+ // The Form.products entity selection is updated
+ Form.products.add(Form.selectedProduct)
+
+ Use(Storage)
+ If(Storage.products=Null)
+ Storage.products:=New shared object()
+ End if
+
+ Use(Storage.products)
+ Storage.products:=Form.products.copy(ck shared)
+ End use
+ End use
+```
+
+
+
+
+
+## .distinct()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.distinct**( *attributePath* : Text { ; *option* : Integer } ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath|Text|->|Path of attribute whose distinct values you want to get|
+|option|Integer|->|`dk diacritical`: diacritical evaluation ("A" # "a" for example)|
+|Result|Collection|<-|Collection with only distinct values|
+
+
+#### Description
+
+The `.distinct()` function returns a collection containing only distinct (different) values from the *attributePath* in the entity selection.
+
+The returned collection is automatically sorted. **Null** values are not returned.
+
+In the *attributePath* parameter, pass the entity attribute whose distinct values you want to get. Only scalar values (text, number, boolean, or date) can be handled. If the *attributePath* leads to an object property that contains values of different types, they are first grouped by type and sorted afterwards. Types are returned in the following order:
+
+1. booleans
+2. strings
+3. numbers
+4. dates
+
+You can use the `[]` notation to designate a collection when *attributePath* is a path within an object (see examples).
+
+By default, a non-diacritical evaluation is performed. If you want the evaluation to be case sensitive or to differentiate accented characters, pass the `dk diacritical` constant in the *option* parameter.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* is not found in the entity selection dataclass.
+
+#### Examples
+
+You want to get a collection containing a single element per country name:
+
+```4d
+ var $countries : Collection
+ $countries:=ds.Employee.all().distinct("address.country")
+```
+
+`nicknames` is a collection and `extra` is an object attribute:
+
+```4d
+$values:=ds.Employee.all().distinct("extra.nicknames[].first")
+```
+
+
+
+
+
+
+## .drop()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.drop**( { *mode* : Integer } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|Integer|->|`dk stop dropping on first error`: stops method execution on first non-droppable entity|
+|Result|4D.EntitySelection|<-|Empty entity selection if successful, else entity selection containing non-droppable entity(ies)
+|
+
+
+#### Description
+
+The `.drop()` function removes the entities belonging to the entity selection from the table related to its dataclass within the datastore. The entity selection remains in memory.
+
+>Removing entities is permanent and cannot be undone. It is recommended to call this action in a transaction in order to have a rollback option.
+
+If a locked entity is encountered during the execution of `.drop()`, it is not removed. By default, the method processes all entities of the entity selection and returns non-droppable entities in the entity selection. If you want the method to stop execution at the first encountered non-droppable entity, pass the `dk stop dropping on first error` constant in the *mode* parameter.
+
+#### Example
+
+Example without the `dk stop dropping on first error` option:
+
+```4d
+ var $employees; $notDropped : cs.EmployeeSelection
+ $employees:=ds.Employee.query("firstName=:1";"S@")
+ $notDropped:=$employees.drop() // $notDropped is an entity selection containing all the not dropped entities
+ If($notDropped.length=0) //The delete action is successful, all the entities have been deleted
+ ALERT("You have dropped "+String($employees.length)+" employees") //The dropped entity selection remains in memory
+ Else
+ ALERT("Problem during drop, try later")
+ End if
+```
+
+Example with the `dk stop dropping on first error` option:
+
+```4d
+ var $employees; $notDropped : cs.EmployeeSelection
+ $employees:=ds.Employee.query("firstName=:1";"S@")
+ $notDropped:=$employees.drop(dk stop dropping on first error) //$notDropped is an entity selection containing the first not dropped entity
+ If($notDropped.length=0) //The delete action is successful, all the entities have been deleted
+ ALERT("You have dropped "+String($employees.length)+" employees") //The dropped entity selection remains in memory
+ Else
+ ALERT("Problem during drop, try later")
+ End if
+```
+
+
+
+
+
+
+
+## .extract()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R3|Added|
+
+
+
+
+**.extract**( *attributePath* : Text { ; *option* : Integer } ) : Collection
**.extract**( *attributePath* { ; *targetPath* } { ; *...attributePathN* : Text ; *targetPathN* : Text } ) : Collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |Text|->|Attribute path whose values must be extracted to the new collection |
+|targetPath|Text|->|Target attribute path or attribute name|
+|option|Integer|->|`ck keep null`: include null attributes in the returned collection (ignored by default)|
+|Result|Collection|<-|Collection containing extracted values|
+
+
+#### Description
+
+The `.extract()` function returns a collection containing *attributePath* values extracted from the entity selection.
+
+*attributePath* can refer to:
+
+* a scalar dataclass attribute,
+* related entity,
+* related entities.
+
+If *attributePath* is invalid, an empty collection is returned.
+
+This function accepts two syntaxes.
+
+**.extract( attributePath : Text { ; option : Integer } ) : Collection**
+
+With this syntax, `.extract()` populates the returned collection with the *attributePath* values of the entity selection.
+
+By default, entities for which *attributePath* is *null* or undefined are ignored in the resulting collection. You can pass the `ck keep null` constant in the *option* parameter to include these values as **null** elements in the returned collection.
+
+* Dataclass attributes with [.kind](DataClassAttributeClass.md#kind) = "relatedEntity" are extracted as a collection of entities (duplications are kept).
+* Dataclass attributes with [.kind](DataClassAttributeClass.md#kind) = "relatedEntities" are extracted as a collection of entity selections.
+
+
+**.extract ( attributePath ; targetPath { ; ...attributePathN ; ... targetPathN}) : Collection**
+
+With this syntax, `.extract()` populates the returned collection with the *attributePath* properties. Each element of the returned collection is an object with *targetPath* properties filled with the corresponding *attributePath* properties. Null values are kept (*option* parameter is ignored with this syntax).
+
+If several *attributePath* are given, a *targetPath* must be given for each. Only valid pairs \[*attributePath*, *targetPath*] are extracted.
+
+* Dataclass attributes with [.kind](DataClassAttributeClass.md#kind) = "relatedEntity" are extracted as an entity.
+* Dataclass attributes with [.kind](DataClassAttributeClass.md#kind) = "relatedEntities" are extracted as an entity selection.
+
+> Entities of a collection of entities accessed by \[ ] are not reloaded from the database.
+
+
+#### Example
+
+Given the following table and relation:
+
+
+
+```4d
+ var $firstnames; $addresses; $mailing; $teachers : Collection
+ //
+ //
+ //$firstnames is a collection of Strings
+
+
+ $firstnames:=ds.Teachers.all().extract("firstname")
+ //
+ //$addresses is a collection of entities related to dataclass Address
+ //Null values for address are extracted
+ $addresses:=ds.Teachers.all().extract("address";ck keep null)
+ //
+ //
+ //$mailing is a collection of objects with properties "who" and "to"
+ //"who" property content is String type
+ //"to" property content is entity type (Address dataclass)
+ $mailing:=ds.Teachers.all().extract("lastname";"who";"address";"to")
+ //
+ //
+ //$mailing is a collection of objects with properties "who" and "city"
+ //"who" property content is String type
+ //"city" property content is String type
+ $mailing:=ds.Teachers.all().extract("lastname";"who";"address.city";"city")
+ //
+ //$teachers is a collection of objects with properties "where" and "who"
+ //"where" property content is String
+ //"who" property content is an entity selection (Teachers dataclass)
+ $teachers:=ds.Address.all().extract("city";"where";"teachers";"who")
+ //
+ //$teachers is a collection of entity selections
+ $teachers:=ds.Address.all().extract("teachers")
+```
+
+
+
+
+
+
+## .first()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.first()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|<-|Reference to the first entity of the entity selection (Null if selection is empty)|
+
+
+#### Description
+
+The `.first()` function returns a reference to the entity in the first position of the entity selection.
+
+The result of this function is similar to:
+
+```4d
+ $entity:=$entitySel[0]
+```
+
+There is, however, a difference between both statements when the selection is empty:
+
+
+```4d
+ var $entitySel : cs.EmpSelection
+ var $entity : cs.EmpEntity
+ $entitySel:=ds.Emp.query("lastName = :1";"Nonexistentname") //no matching entity
+ //entity selection is then empty
+ $entity:=$entitySel.first() //returns Null
+ $entity:=$entitySel[0] //generates an error
+```
+
+#### Example
+
+
+```4d
+ var $entitySelection : cs.EmpSelection
+ var $entity : cs.EmpEntity
+ $entitySelection:=ds.Emp.query("salary > :1";100000)
+ If($entitySelection.length#0)
+ $entity:=$entitySelection.first()
+ End if
+```
+
+
+
+
+
+
+## .getDataClass()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+
+**.getDataClass()** : 4D.DataClass
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.DataClass|<-|Dataclass object to which the entity selection belongs|
+
+
+#### Description
+
+The `.getDataClass()` function returns the dataclass of the entity selection.
+
+This function is mainly useful in the context of generic code.
+
+#### Example
+
+The following generic code duplicates all entities of the entity selection:
+
+```4d
+ //duplicate_entities method
+ //duplicate_entities($entity_selection)
+
+ #DECLARE ( $entitySelection : 4D.EntitySelection )
+ var $dataClass : 4D.DataClass
+ var $entity; $duplicate : 4D.Entity
+ var $status : Object
+ $dataClass:=$entitySelection.getDataClass()
+ For each($entity;$entitySelection)
+ $duplicate:=$dataClass.new()
+ $duplicate.fromObject($entity.toObject())
+ $duplicate[$dataClass.getInfo().primaryKey]:=Null //reset the primary key
+ $status:=$duplicate.save()
+ End for each
+```
+
+
+
+
+
+## .isAlterable()
+
+History
+
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+
+**.isAlterable()** : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Boolean|<-|True if the entity selection is alterable, False otherwise|
+
+
+#### Description
+
+The `.isAlterable()` function returns True if the entity selection is alterable, and False if the entity selection is not alterable.
+
+For more information, please refer to [Shareable or alterable entity selections](ORDA/entities.md#shareable-or-alterable-entity-selections).
+
+#### Example
+
+You are about to display `Form.products` in a [list box](FormObjects/listbox_overview.md) to allow the user to add new products. You want to make sure it is alterable so that the user can add new products without error:
+
+```4d
+If (Not(Form.products.isAlterable()))
+ Form.products:=Form.products.copy()
+End if
+...
+Form.products.add(Form.product)
+```
+
+
+
+
+
+
+## .isOrdered()
+
+History
+
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.isOrdered()** : Boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Boolean|<-|True if the entity selection is ordered, False otherwise|
+
+
+#### Description
+
+The `.isOrdered()` function returns True if the entity selection is ordered, and False if it is unordered.
+
+>This function always returns True when the entity selection comes from a remote datastore.
+
+For more information, please refer to [Ordered or unordered entity selection](ORDA/dsMapping.md#ordered-or-unordered-entity-selection).
+
+
+#### Example
+
+
+```4d
+ var $employees : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ var $isOrdered : Boolean
+ $employees:=ds.Employee.newSelection(dk keep ordered)
+ $employee:=ds.Employee.get(714) // Gets the entity with primary key 714
+
+ //In an ordered entity selection, we can add the same entity several times (duplications are kept)
+ $employees.add($employee)
+ $employees.add($employee)
+ $employees.add($employee)
+
+ $isOrdered:=$employees.isOrdered()
+ If($isOrdered)
+ ALERT("The entity selection is ordered and contains "+String($employees.length)+" employees")
+ End if
+```
+
+
+
+
+
+
+
+## .last()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.last()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity |<-|Reference to the last entity of the entity selection (Null if empty entity selection)|
+
+
+#### Description
+
+The `.last()` function returns a reference to the entity in last position of the entity selection.
+
+The result of this function is similar to:
+
+```4d
+ $entity:=$entitySel[length-1]
+```
+
+If the entity selection is empty, the function returns Null.
+
+
+#### Example
+
+
+```4d
+ var $entitySelection : cs.EmpSelection
+ var $entity : cs.EmpEntity
+ $entitySelection:=ds.Emp.query("salary < :1";50000)
+ If($entitySelection.length#0)
+ $entity:=$entitySelection.last()
+ End if
+```
+
+
+
+
+
+
+## .length
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.length** : Integer
+
+
+#### Description
+
+The `.length` property returns the number of entities in the entity selection. If the entity selection is empty, it returns 0.
+
+Entity selections always have a `.length` property.
+
+
+#### Example
+
+```4d
+ var $vSize : Integer
+ $vSize:=ds.Employee.query("gender = :1";"male").length
+ ALERT(String(vSize)+" male employees found.")
+```
+
+
+
+
+
+
+## .max()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+|v18 R6|Returns undefined if empty entity selection|
+
+
+
+
+**.max**( *attributePath* : Text ) : any
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |Text|->|Path of the attribute to be used for calculation|
+|Result|any|<-|Highest value of attribute|
+
+
+#### Description
+
+The `.max()` function returns the highest (or maximum) value among all the values of *attributePath* in the entity selection. It actually returns the value of the last entity of the entity selection as it would be sorted in ascending order using the [`.orderBy()`](#orderby) function.
+
+If you pass in *attributePath* a path to an object property containing different types of values, the `.max()` function will return the maximum value within the first scalar type in the default 4D type list order (see [`.sort()`](CollectionClass.md#sort) description).
+
+`.max()` returns **undefined** if the entity selection is empty or *attributePath* is not found in the object attribute.
+
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* designates an attribute that does not exist in the entity selection dataclass.
+
+
+
+#### Example
+
+We want to find the highest salary among all the female employees:
+
+```4d
+ var $sel : cs.EmpSelection
+ var $maxSalary : Real
+ $sel:=ds.Employee.query("gender = :1";"female")
+ $maxSalary:=$sel.max("salary")
+```
+
+
+
+
+
+## .min()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+|v18 R6|Returns undefined if empty entity selection|
+
+
+
+
+
+**.min**( *attributePath* : Text ) : any
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |Text|->|Path of the attribute to be used for calculation|
+|Result|any|<-|Lowest value of attribute|
+
+
+#### Description
+
+The `.min()` function returns the lowest (or minimum) value among all the values of attributePath in the entity selection. It actually returns the first entity of the entity selection as it would be sorted in ascending order using the [`.orderBy()`](#orderby) function (excluding **null** values).
+
+If you pass in *attributePath* a path to an object property containing different types of values, the `.min()` function will return the minimum value within the first scalar value type in the type list order (see [`.sort()`](CollectionClass.md#sort) description).
+
+`.min()` returns **undefined** if the entity selection is empty or *attributePath* is not found in the object attribute.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* designates an attribute that does not exist in the entity selection dataclass.
+
+
+#### Example
+
+In this example, we want to find the lowest salary among all the female employees:
+
+```4d
+ var $sel : cs.EmpSelection
+ var $minSalary : Real
+ $sel:=ds.Employee.query("gender = :1";"female")
+ $minSalary:=$sel.min("salary")
+```
+
+
+
+
+
+## .minus()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.minus**( *entity* : 4D.Entity ) : 4D.EntitySelection
**.minus**( *entitySelection* : 4D.EntitySelection ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity |4D.Entity|->|Entity to substract|
+|entitySelection|4D.EntitySelection|->|Entity selection to substract|
+|Result|4D.EntitySelection|<-|New entity selection or a new reference on the existing entity selection|
+
+
+#### Description
+
+The `.minus()` function excludes from the entity selection to which it is applied the *entity* or the entities of *entitySelection* and returns the resulting entity selection.
+
+* If you pass *entity* as parameter, the function creates a new entity selection without *entity* (if *entity* belongs to the entity selection). If *entity* was not included in the original entity selection, a new reference to the entity selection is returned.
+* If you pass *entitySelection* as parameter, the function returns an entity selection containing the entities belonging to the original entity selection without the entities belonging to *entitySelection*.
+
+>You can compare [ordered and/or unordered entity selections](ORDA/dsMapping.md#ordered-or-unordered-entity-selection). The resulting selection is always unordered.
+
+If the original entity selection or both the original entity selection and the *entitySelection* parameter are empty, an empty entity selection is returned.
+
+If *entitySelection* is empty or if *entity* is Null, a new reference to the original entity selection is returned.
+
+If the original entity selection and the parameter are not related to the same dataclass, an error is raised.
+
+
+#### Example 1
+
+```4d
+ var $employees; $result : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+
+ $employees:=ds.Employee.query("lastName = :1";"H@")
+ // The $employees entity selection contains the entity with primary key 710 and other entities
+ // for ex. "Colin Hetrick", "Grady Harness", "Sherlock Holmes" (primary key 710)
+
+ $employee:=ds.Employee.get(710) // Returns "Sherlock Holmes"
+
+ $result:=$employees.minus($employee) //$result contains "Colin Hetrick", "Grady Harness"
+```
+
+
+#### Example 2
+
+We want to have a selection of female employees named "Jones" who live in New York :
+
+```4d
+ var $sel1; $sel2; $sel3 : cs.EmployeeSelection
+ $sel1:=ds.Employee.query("name =:1";"Jones")
+ $sel2:=ds.Employee.query("city=:1";"New York")
+ $sel3:=$sel1.and($sel2).minus(ds.Employee.query("gender='male'"))
+```
+
+
+
+
+
+## .or()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.or**( *entity* : 4D.Entity ) : 4D.EntitySelection
**.or**( *entitySelection* : 4D.EntitySelection ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity|4D.Entity|->|Entity to intersect with|
+|entitySelection|4D.EntitySelection|->|Entity selection to intersect with|
+|Result|4D.EntitySelection|<-|New entity selection or new reference to the original entity selection|
+
+
+#### Description
+
+The `.or()` function combines the entity selection with the *entity* or *entitySelection* parameter using the logical (not exclusive) OR operator; it returns a new, unordered entity selection that contains all the entities from the entity selection and the parameter.
+
+* If you pass *entity* as parameter, you compare this entity with the entity selection. If the entity belongs to the entity selection, a new reference to the entity selection is returned. Otherwise, a new entity selection containing the original entity selection and the entity is returned.
+* If you pass *entitySelection* as parameter, you compare entity selections. A new entity selection containing the entities belonging to the original entity selection or *entitySelection* is returned (or is not exclusive, entities referenced in both selections are not duplicated in the resulting selection).
+
+>You can compare [ordered and/or unordered entity selections](ORDA/dsMapping.md#ordered-or-unordered-entity-selection). The resulting selection is always unordered.
+
+If the original entity selection and the *entitySelection* parameter are empty, an empty entity selection is returned. If the original entity selection is empty, a reference to *entitySelection* or an entity selection containing only *entity* is returned.
+
+If *entitySelection* is empty or if *entity* is Null, a new reference to the original entity selection is returned.
+
+If the original entity selection and the parameter are not related to the same dataclass, an error is raised.
+
+
+#### Example 1
+
+```4d
+ var $employees1; $employees2; $result : cs.EmployeeSelection
+ $employees1:=ds.Employee.query("lastName = :1";"H@") //Returns "Colin Hetrick","Grady Harness"
+ $employees2:=ds.Employee.query("firstName = :1";"C@") //Returns "Colin Hetrick", "Cath Kidston"
+ $result:=$employees1.or($employees2) //$result contains "Colin Hetrick", "Grady Harness","Cath Kidston"
+```
+
+#### Example 2
+
+```4d
+ var $employees; $result : cs.EmployeeSelection
+ var $employee : cs.EmployeeEntity
+ $employees:=ds.Employee.query("lastName = :1";"H@") // Returns "Colin Hetrick","Grady Harness", "Sherlock Holmes"
+ $employee:=ds.Employee.get(686) //the entity with primary key 686 does not belong to the $employees entity selection
+ //It matches the employee "Mary Smith"
+
+ $result:=$employees.or($employee) //$result contains "Colin Hetrick", "Grady Harness", "Sherlock Holmes", "Mary Smith"
+```
+
+
+
+
+
+## .orderBy()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.orderBy**( *pathString* : Text ) : 4D.EntitySelection
**.orderBy**( *pathObjects* : Collection ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|pathString |Text |->|Attribute path(s) and sorting instruction(s) for the entity selection|
+|pathObjects |Collection |->|Collection of criteria objects|
+|Result|4D.EntitySelection|<-|New entity selection in the specified order|
+
+
+#### Description
+
+The `.orderBy()` function returns a new ordered entity selection containing all entities of the entity selection in the order specified by *pathString* or *pathObjects* criteria.
+
+>* This method does not modify the original entity selection.
+* For more information, please refer to the [Ordered or unordered entity selection](ORDA/dsMapping.md#ordered-or-unordered-entity-selection) section.
+
+You must use a criteria parameter to define how the entities must be sorted. Two different parameters are supported:
+
+* *pathString* (Text) : This parameter contains a formula made of 1 to x attribute paths and (optionally) sort orders, separated by commas. The syntax is:
+
+```4d
+"attributePath1 {desc or asc}, attributePath2 {desc or asc},..."
+```
+
+The order in which the attributes are passed determines the sorting priority of the entities. By default, attributes are sorted in ascending order. You can set the sort order of a property in the criteria string, separated from the property path by a single space: pass "asc" to sort in ascending order or "desc" in descending order.
+
+* *pathObjects* (collection): each element of the collection contains an object structured in the following way:
+
+```4d
+{
+ "propertyPath": string,
+ "descending": boolean
+}
+```
+
+By default, attributes are sorted in ascending order ("descending" is false).
+
+You can add as many objects in the criteria collection as necessary.
+
+>Null values are evaluated as less than other values.
+
+#### Example
+
+
+```4d
+// order by formula
+ $sortedEntitySelection:=$entitySelection.orderBy("firstName asc, salary desc")
+ $sortedEntitySelection:=$entitySelection.orderBy("firstName")
+
+ // order by collection with or without sort orders
+ $orderColl:=New collection
+ $orderColl.push(New object("propertyPath";"firstName";"descending";False))
+ $orderColl.push(New object("propertyPath";"salary";"descending";True))
+ $sortedEntitySelection:=$entitySelection.orderBy($orderColl)
+
+ $orderColl:=New collection
+ $orderColl.push(New object("propertyPath";"manager.lastName"))
+ $orderColl.push(New object("propertyPath";"salary"))
+ $sortedEntitySelection:=$entitySelection.orderBy($orderColl)
+```
+
+
+
+
+
+
+## .orderByFormula( )
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Added|
+
+
+
+
+**.orderByFormula**( *formulaString* : Text { ; *sortOrder* : Integer } { ; *settings* : Object} ) : 4D.EntitySelection
**.orderByFormula**( *formulaObj* : Object { ; *sortOrder* : Integer } { ; *settings* : Object} ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|formulaString|Text|->|Formula string|
+|formulaObj|Object|->|Formula object|
+|sortOrder |Integer|->|`dk ascending` (default) or `dk descending`|
+|settings|Object|->|Parameter(s) for the formula|
+|Result|4D.EntitySelection|<-|New ordered entity selection|
+
+
+#### Description
+
+The `.orderByFormula()` function returns a new, ordered entity selection containing all entities of the entity selection in the order defined through the *formulaString* or *formulaObj* and, optionally, *sortOrder* and *settings* parameters.
+
+>This function does not modify the original entity selection.
+
+You can use either a *formulaString* or a *formulaObj* parameter:
+
+- *formulaString*: you pass a 4D expression such as "Year of(this.birthDate)".
+- *formulaObj*: pass a valid formula object created using the `Formula` or `Formula from string` command.
+
+The *formulaString* or *formulaObj* is executed for each entity of the entity selection and its result is used to define the position of the entity in the returned entity selection. The result must be of a sortable type (boolean, date, number, text, time, null).
+
+>A null result is always the smallest value.
+
+By default if you omit the *sortOrder* parameter, the resulting entity selection is sorted in ascending order. Optionnally, you can pass one of the following values in the *sortOrder* parameter:
+
+|Constant| Value| Comment|
+|---|---|---|
+|dk ascending| 0 |Ascending sort order (default)|
+|dk descending| 1 |Descending sort order|
+
+Within the *formulaString* or *formulaObj*, the processed entity and thus its attributes are available through the `This` command (for example, `This.lastName`).
+
+You can pass parameter(s) to the formula using the `args` property (object) of the `settings` parameter: the formula receives the `settings.args` object in $1.
+
+#### Example 1
+
+Sorting students using a formula provided as text:
+
+```4d
+ var $es1; $es2 : cs.StudentsSelection
+ $es1:=ds.Students.query("nationality=:1";"French")
+ $es2:=$es1.orderByFormula("length(this.lastname)") //ascending by default
+ $es2:=$es1.orderByFormula("length(this.lastname)";dk descending)
+```
+
+Same sort order but using a formula object:
+
+```4d
+ var $es1; $es2 : cs.StudentsSelection
+ var $formula : Object
+ $es1:=ds.Students.query("nationality=:1";"French")
+ $formula:=Formula(Length(This.lastname))
+ $es2:=$es1.orderByFormula($formula) // ascending by default
+ $es2:=$es1.orderByFormula($formula;dk descending)
+```
+
+
+#### Example 2
+
+A formula is given as a formula object with parameters; `settings.args` object is received as $1 in the ***computeAverage*** method.
+
+In this example, the "marks" object field in the **Students** dataClass contains students' grades for each subject. A single formula object is used to compute a student's average grade with different coefficients for schoolA and schoolB.
+
+```4d
+ var $es1; $es2 : cs.StudentsSelection
+ var $formula; $schoolA; $schoolB : Object
+ $es1:=ds.Students.query("nationality=:1";"French")
+ $formula:=Formula(computeAverage($1))
+
+ $schoolA:=New object() //settings object
+ $schoolA.args:=New object("english";1;"math";1;"history";1) // Coefficients to compute an average
+
+ //Order students according to school A criteria
+ $es2:=$es1.entitySelection.orderByFormula($formula;$schoolA)
+
+ $schoolB:=New object() //settings object
+ $schoolB.args:=New object("english";1;"math";2;"history";3) // Coefficients to compute an average
+
+ //Order students according to school B criteria
+ $es2:=$es1.entitySelection.orderByFormula($formula;dk descending;$schoolB)
+```
+
+```4d
+ //
+ // computeAverage method
+ // -----------------------------
+ #DECLARE ($coefList : Object) -> $result : Integer
+ var $subject : Text
+ var $average; $sum : Integer
+
+ $average:=0
+ $sum:=0
+
+ For each($subject;$coefList)
+ $sum:=$sum+$coefList[$subject]
+ End for each
+
+ For each($subject;This.marks)
+ $average:=$average+(This.marks[$subject]*$coefList[$subject])
+ End for each
+
+ $result:=$average/$sum
+```
+
+
+
+
+
+
+## .query()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Support of Formula parameters|
+|v17 R5|Support of placeholders for values|
+|v17|Added|
+
+
+
+
+**.query**( *queryString* : Text { ; *...value* : any } { ; *querySettings* : Object } ) : 4D.EntitySelection
**.query**( *formula* : Object { ; *querySettings* : Object } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|queryString |Text |-> |Search criteria as string|
+|formula |Object |-> |Search criteria as formula object|
+|value|any|->|Value(s) to use for indexed placeholder(s)|
+|querySettings|Object|->|Query options: parameters, attributes, args, allowFormulas, context, queryPath, queryPlan|
+|Result|4D.EntitySelection|<-|New entity selection made up of entities from entity selection meeting the search criteria specified in *queryString* or *formula*|
+
+#### Description
+
+The `.query()` function searches for entities that meet the search criteria specified in *queryString* or *formula* and (optionally) *value*(s) among all the entities in the entity selection, and returns a new object of type `EntitySelection` containing all the entities that are found. Lazy loading is applied.
+
+>This function does not modify the original entity selection.
+
+If no matching entities are found, an empty `EntitySelection` is returned.
+
+For detailed information on how to build a query using *queryString*, *value*, and *querySettings* parameters, please refer to the DataClass [`.query()`](DataClassClass.md#query) function description.
+
+>By default if you omit the **order by** statement in the *queryString*, the returned entity selection is [not ordered](ORDA/dsMapping.md#ordered-or-unordered-entity-selection). Note however that, in Client/Server mode, it behaves like an ordered entity selection (entities are added at the end of the selection).
+
+#### Example 1
+
+
+```4d
+ var $entitySelectionTemp : cs.EmployeeSelection
+ $entitySelectionTemp:=ds.Employee.query("lastName = :1";"M@")
+ Form.emps:=$entitySelectionTemp.query("manager.lastName = :1";"S@")
+```
+
+
+#### Example 2
+
+More examples of queries can be found in the DataClass [`.query()`](DataClassClass.md#query) page.
+
+#### See also
+
+[`.query()`](DataClassClass.md#query) for dataclass
+
+
+
+
+
+
+## .queryPath
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.queryPath** : Text
+
+
+#### Description
+
+The `.queryPath` property contains a detailed description of the query as it was actually performed by 4D. This property is available for `EntitySelection` objects generated through queries if the `"queryPath":true` property was passed in the *querySettings* parameter of the [`.query()`](#query) function.
+
+For more information, refer to the **querySettings parameter** paragraph in the Dataclass[`.query()`](DataClassClass.html#query) page.
+
+
+
+
+
+
+## .queryPlan
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.queryPlan** : Text
+
+
+#### Description
+
+The `.queryPlan` property contains a detailed description of the query just before it is executed (i.e., the planned query). This property is available for `EntitySelection` objects generated through queries if the `"queryPlan":true` property was passed in the *querySettings* parameter of the [`.query()`](#query) function.
+
+For more information, refer to the **querySettings parameter** paragraph in the Dataclass[`.query()`](DataClassClass.html#query) page.
+
+
+
+
+
+## .refresh()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R3|Added|
+
+
+
+
+**.refresh()**
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+||||Does not require any parameters|
+
+
+#### Description
+
+>This function only works with a remote datastore (client / server or `Open datastore` connection).
+
+The `.refresh()` function immediately "invalidates" the entity selection data in the local ORDA cache so that the next time 4D requires the entity selection, it will be reloaded from the database.
+
+By default, the local ORDA cache is invalidated after 30 seconds. In the context of client / server applications using both ORDA and the classic language, this method allows you to make sure a remote application will always work with the latest data.
+
+#### Example 1
+
+In this example, classic and ORDA code modify the same data simultaneously:
+
+```4d
+ //On a 4D remote
+
+ var $selection : cs.StudentsSelection
+ var $student : cs.StudentsEntity
+
+ $selection:=ds.Students.query("lastname=:1";"Collins")
+ //The first entity is loaded in the ORDA cache
+ $student:=$selection.first()
+
+ //Update with classic 4D, ORDA cache is not aware of if
+ QUERY([Students];[Students]lastname="Collins")
+ [Students]lastname:="Colin"
+ SAVE RECORD([Students])
+
+ //to get the latest version, the ORDA cache must be invalidated
+ $selection.refresh()
+ // Even if cache is not expired, the first entity is reloaded from disk
+ $student:=$selection.first()
+
+ //$student.lastname contains "Colin"
+```
+
+
+#### Example 2
+
+A list box displays the Form.students entity selection and several clients work on it.
+
+```4d
+// Form method:
+ Case of
+ :(Form event code=On Load)
+ Form.students:=ds.Students.all()
+ End case
+ //
+ //
+ // On client #1, the user loads, updates, and saves the first entity
+ // On client #2, the user loads, updates, and saves the same entity
+ //
+ //
+ // On client #1:
+ Form.students.refresh() // Invalidates the ORDA cache for the Form.students entity selection
+ // The list box content is refreshed from the database with update made by client #2
+```
+
+
+
+
+
+
+## .selected()
+
+History
+|Version|Changes|
+|---|---|
+|v19 R3|Added|
+
+
+
+
+**.selected**( *selectedEntities* : 4D.EntitySelection ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|selectedEntities |4D.EntitySelection|->|Entity selection with entities for which to know the rank in the entity selection|
+|Result|Object|<-|Range(s) of selected entities in entity selection|
+
+
+#### Description
+
+The `.selected()` function returns an object describing the position(s) of *selectedEntities* in the original entity selection.
+
+>This function does not modify the original entity selection.
+
+Pass in the *selectedEntities* parameter an entity selection containing entities for which you want to know the position in the original entity selection. *selectedEntities* must be an entity selection belonging to the same dataclass as the original entity selection, otherwise an error 1587 - "The entity selection comes from an incompatible dataclass" is raised.
+
+#### Result
+
+The returned object contains the following properties:
+
+|Property|Type|Description
+|---|---|---|
+|ranges|Collection|Collection of range objects|
+|ranges[].start|Integer|First entity index in the range|
+|ranges[].end|Integer|Last entity index in the range|
+
+If a `ranges` property contains a single entity, `start` = `end`. Index starts at 0.
+
+The function returns an empty collection in the `ranges` property if the original entity selection or the *selectedEntities* entity selection is empty.
+
+#### Example
+
+```4d
+var $invoices; $cashSel; $creditSel : cs.Invoices
+var $result1; $result2 : Object
+
+$invoices:=ds.Invoices.all()
+
+$cashSelection:=ds.Invoices.query("payment = :1"; "Cash")
+$creditSel:=ds.Invoices.query("payment IN :1"; New collection("Cash"; "Credit Card"))
+
+$result1:=$invoices.selected($cashSelection)
+$result2:=$invoices.selected($creditSel)
+
+//$result1 = {ranges:[{start:0;end:0},{start:3;end:3},{start:6;end:6}]}
+//$result2 = {ranges:[{start:0;end:1},{start:3;end:4},{start:6;end:7}]}
+
+```
+
+
+
+
+
+
+
+
+
+
+## .slice()
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.slice**( *startFrom* : Integer { ; *end* : Integer } ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|startFrom |Integer |->|Index to start the operation at (included) |
+|end |Integer|->|End index (not included)|
+|Result|4D.EntitySelection|<-|New entity selection containing sliced entities (shallow copy)|
+
+
+#### Description
+
+The `.slice()` function returns a portion of an entity selection into a new entity selection, selected from the *startFrom* index to the *end* index (*end* is not included) or to the last entity of the entity selection. This method returns a shallow copy of the entity selection (it uses the same entity references).
+
+>This function does not modify the original entity selection.
+
+The returned entity selection contains the entities specified by *startFrom* and all subsequent entities up to, but not including, the entity specified by *end*. If only the *startFrom* parameter is specified, the returned entity selection contains all entities from *startFrom* to the last entity of the original entity selection.
+
+* If *startFrom* < 0, it is recalculated as *startFrom:=startFrom+length* (it is considered as the offset from the end of the entity selection). If the calculated value < 0, *startFrom* is set to 0.
+* If *startFrom >= length*, the function returns an empty entity selection.
+* If *end* < 0, it is recalculated as *end:=end+length*.
+* If *end < startFrom* (passed or calculated values), the method does nothing.
+
+If the entity selection contains entities that were dropped in the meantime, they are also returned.
+
+#### Example 1
+
+You want to get a selection of the first 9 entities of the entity selection:
+
+```4d
+var $sel; $sliced : cs.EmployeeSelection
+$sel:=ds.Employee.query("salary > :1";50000)
+$sliced:=$sel.slice(0;9) //
+```
+
+
+#### Example 2
+
+Assuming we have ds.Employee.all().length = 10
+
+```4d
+var $slice : cs.EmployeeSelection
+$slice:=ds.Employee.all().slice(-1;-2) //tries to return entities from index 9 to 8, but since 9 > 8, returns an empty entity selection
+
+```
+
+
+
+
+
+## .sum( )
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+
+**.sum**( *attributePath* : Text ) : Real
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |Text|->|Path of the attribute to be used for calculation|
+|Result|Real|<-|Sum of entity selection values|
+
+
+#### Description
+
+
+The `.sum()` function returns the sum for all *attributePath* values in the entity selection.
+
+`.sum()` returns 0 if the entity selection is empty.
+
+The sum can only be done on values of number type. If the *attributePath* is an object property, only numerical values are taken into account for the calculation (other value types are ignored). In this case, if *attributePath* leads to a property that does not exist in the object or does not contain any numeric values, `.sum()` returns 0.
+
+An error is returned if:
+
+* *attributePath* is not a numerical or an object attribute,
+* *attributePath* is a related attribute,
+* *attributePath* is not found in the entity selection dataclass.
+
+
+
+#### Example
+
+```4d
+var $sel : cs.EmployeeSelection
+var $sum : Real
+
+$sel:=ds.Employee.query("salary < :1";20000)
+$sum:=$sel.sum("salary")
+```
+
+
+
+
+
+## .toCollection( )
+
+History
+|Version|Changes|
+|---|---|
+|v17|Added|
+
+
+
+
+**.toCollection**( { *options* : Integer { ; *begin* : Integer { ; *howMany* : Integer } } ) : *Collection*
**.toCollection**( *filterString* : Text {; *options* : Integer { ; *begin* : Integer { ; *howMany* : Integer }}} ) : *Collection*
**.toCollection**( *filterCol* : Collection {; *options* : Integer { ; *begin* : Integer { ; *howMany* : Integer }}} ) : *Collection*
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|filterString |Text|->|String with entity attribute path(s) to extract|
+|filterCol |Collection|->|Collection of entity attribute path(s) to extract|
+|options|Integer|->|`dk with primary key`: adds the primary key
`dk with stamp`: adds the stamp|
+|begin|Integer| ->|Designates the starting index|
+|howMany|Integer|->|Number of entities to extract|
+|Result|Collection|<-|Collection of objects containing attributes and values of entity selection|
+
+
+#### Description
+
+The `.toCollection()` function creates and returns a collection where each element is an object containing a set of properties and values corresponding to the attribute names and values for the entity selection.
+
+If no filter parameter is passed or the first parameter contains an empty string or "*", all the attributes are extracted. Attributes with [kind](DataClassAttributeClass.md#kind) property as "relatedEntity" are extracted with the simple form: an object with property \_\_KEY (primary key). Attributes with kind property as "relatedEntities" are not extracted.
+
+Or, you can designate the entity attributes to extract using a filter parameter. You can use one of these two filters:
+
+* *filterString* --a string with property paths separated with commas: "propertyPath1, propertyPath2, ...".
+* *filterCol* --a collection of strings containing property paths: \["propertyPath1","propertyPath2",...]
+
+
+If a filter is specified for an attribute of the `relatedEntity` kind:
+
+* propertyPath = "relatedEntity" -> it is extracted with simple form
+* propertyPath = "relatedEntity.*" -> all the properties are extracted
+* propertyPath = "relatedEntity.propertyName1, relatedEntity.propertyName2, ..." -> only those properties are extracted
+
+
+If a filter is specified for an attribute of the `relatedEntities` kind:
+
+* propertyPath = "relatedEntities.*" -> all the properties are extracted
+* propertyPath = "relatedEntities.propertyName1, relatedEntities.propertyName2, ..." -> only those properties are extracted
+
+
+
+In the *options* parameter, you can pass the `dk with primary key` and/or `dk with stamp` selector(s) to add the entity's primary keys and/or stamps in extracted objects.
+
+The *begin* parameter allows you to indicate the starting index of the entities to extract. You can pass any value between 0 and entity selection length-1.
+
+The *howMany* parameter lets you specify the number of entities to extract, starting with the one specified in *begin*. Dropped entities are not returned but are taken into account according to *howMany*. For example, if *howMany*= 3 and there is 1 dropped entity, only 2 entities are extracted.
+
+If *howMany* > length of the entity selection, the method returns (length - *begin*) objects.
+
+An empty collection is returned if:
+
+* the entity selection is empty, or
+* *begin* is greater than the length of the entity selection.
+
+
+#### Example 1
+
+The following structure will be used throughout all examples of this section:
+
+
+
+
+Example without filter or options parameter:
+
+```4d
+ var $employeesCollection : Collection
+ var $employees : cs.EmployeeSelection
+
+ $employeesCollection:=New collection
+ $employees:=ds.Employee.all()
+ $employeesCollection:=$employees.toCollection()
+```
+
+Returns:
+
+```4d
+[
+ {
+ "ID": 416,
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "salary": 79100,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+
+ }
+ },
+ {
+ "ID": 417,
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "salary": 47000,
+ "birthDate": "1992-06-16T00:00:00.000Z",
+ "woman": true,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+ }
+]
+```
+
+#### Example 2
+
+Example with options:
+
+```4d
+var $employeesCollection : Collection
+var $employees : cs.EmployeeSelection
+
+$employeesCollection:=New collection
+$employees:=ds.Employee.all()
+$employeesCollection:=$employees.toCollection("";dk with primary key+dk with stamp)
+```
+
+Returns:
+
+```4d
+[
+ {
+ "__KEY": 416,
+ "__STAMP": 1,
+ "ID": 416,
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "salary": 79100,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+ },
+ {
+ "__KEY": 417,
+ "__STAMP": 1,
+ "ID": 417,
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "salary": 47000,
+ "birthDate": "1992-06-16T00:00:00.000Z",
+ "woman": true,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+ }]
+```
+
+#### Example 3
+
+Example with slicing and filtering on properties:
+
+```4d
+var $employeesCollection; $filter : Collection
+var $employees : cs.EmployeeSelection
+
+$employeesCollection:=New collection
+$filter:=New collection
+$filter.push("firstName")
+$filter.push("lastName")
+
+$employees:=ds.Employee.all()
+$employeesCollection:=$employees.toCollection($filter;0;0;2)
+```
+
+Returns:
+
+```4d
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl"
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham"
+ }
+]
+
+```
+
+
+#### Example 4
+
+Example with `relatedEntity` type with simple form:
+
+
+```4d
+var $employeesCollection : Collection
+$employeesCollection:=New collection
+$employeesCollection:=$employees.toCollection("firstName,lastName,employer")
+```
+
+returns:
+
+```4d
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "employer": {
+ "__KEY": 20
+ }
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "employer": {
+ "__KEY": 20
+ }
+ },
+ {
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "employer": {
+ "__KEY": 20
+ }
+ }
+ ]
+```
+
+#### Example 5
+
+Example with *filterCol* parameter:
+
+```4d
+var $employeesCollection; $coll : Collection
+$employeesCollection:=New collection
+$coll:=New collection("firstName";"lastName")
+$employeesCollection:=$employees.toCollection($coll)
+```
+
+Returns:
+
+```4d
+[
+ {
+ "firstName": "Joanna",
+ "lastName": "Cabrera"
+ },
+ {
+ "firstName": "Alexandra",
+ "lastName": "Coleman"
+ }
+]
+```
+
+#### Example 6
+
+Example with extraction of all properties of a relatedEntity:
+
+```4d
+var $employeesCollection; $coll : Collection
+$employeesCollection:=New collection
+$coll:=New collection
+$coll.push("firstName")
+$coll.push("lastName")
+$coll.push("employer.*")
+$employeesCollection:=$employees.toCollection($coll)
+```
+
+Returns:
+
+```4d
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+ },
+ {
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+ }
+ ]
+```
+
+#### Example 7
+
+Example with extraction of some properties of a relatedEntity:
+
+```4d
+var $employeesCollection : Collection
+$employeesCollection:=New collection
+$employeesCollection:=$employees.toCollection("firstName, lastName, employer.name")
+```
+
+```4d
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+
+ "employer": {
+ "name": "India Astral Secretary"
+ }
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "employer": {
+ "name": "India Astral Secretary"
+ }
+ },
+ {
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "employer": {
+ "name": "India Astral Secretary"
+ }
+ }]
+```
+
+#### Example 8
+
+Example with extraction of some properties of `relatedEntities`:
+
+```4d
+ var $employeesCollection : Collection
+ $employeesCollection:=New collection
+ $employeesCollection:=$employees.toCollection("firstName, lastName, directReports.firstName")
+```
+
+Returns:
+
+```4d
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "directReports": []
+ },
+ {
+ "firstName": "Mike",
+ "lastName": "Phan",
+ "directReports": [
+ {
+ "firstName": "Gary"
+ },
+ {
+ "firstName": "Sadie"
+ },
+ {
+ "firstName": "Christie"
+ }
+ ]
+ },
+ {
+ "firstName": "Gary",
+
+ "lastName": "Reichert",
+ "directReports": [
+ {
+ "firstName": "Rex"
+ },
+ {
+ "firstName": "Jenny"
+ },
+ {
+ "firstName": "Lowell"
+ }
+ ]
+ }]
+```
+
+#### Example 9
+
+Example with extraction of all properties of `relatedEntities`:
+
+```4d
+var $employeesCollection : Collection
+$employeesCollection:=New collection
+$employeesCollection:=$employees.toCollection("firstName, lastName, directReports.*")
+
+```
+
+```4d
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "directReports": []
+ },
+ {
+ "firstName": "Mike",
+ "lastName": "Phan",
+ "directReports": [
+ {
+ "ID": 425,
+ "firstName": "Gary",
+ "lastName": "Reichert",
+ "salary": 65800,
+ "birthDate": "1957-12-23T00:00:00.000Z",
+ "woman": false,
+ "managerID": 424,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 424
+ }
+ },
+ {
+ "ID": 426,
+ "firstName": "Sadie",
+ "lastName": "Gallant",
+ "salary": 35200,
+ "birthDate": "2022-01-03T00:00:00.000Z",
+ "woman": true,
+ "managerID": 424,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 424
+ }
+ }
+ ]
+ },
+ {
+ "firstName": "Gary",
+ "lastName": "Reichert",
+ "directReports": [
+ {
+ "ID": 428,
+ "firstName": "Rex",
+ "lastName": "Chance",
+ "salary": 71600,
+ "birthDate": "1968-08-09T00:00:00.000Z",
+ "woman": false,
+
+ "managerID": 425,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 425
+ }
+ },
+ {
+ "ID": 429,
+ "firstName": "Jenny",
+ "lastName": "Parks",
+ "salary": 51300,
+ "birthDate": "1984-05-25T00:00:00.000Z",
+ "woman": true,
+ "managerID": 425,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 425
+ }
+ }
+ ]
+ }
+]
+```
+
+
+
+
+
+
+
+
diff --git a/docs/API/FileClass.md b/docs/API/FileClass.md
new file mode 100644
index 00000000000000..cfbb0c8c755a87
--- /dev/null
+++ b/docs/API/FileClass.md
@@ -0,0 +1,745 @@
+---
+id: FileClass
+title: File
+---
+
+`File` objects are created with the [`File`](#file) command. They contain references to disk files that may or may not actually exist on disk. For example, when you execute the `File` command to create a new file, a valid `File` object is created but nothing is actually stored on disk until you call the [`file.create( )`](#create) function.
+
+### Example
+
+The following example creates a preferences file in the project folder:
+
+```code4d
+var $created : Boolean
+$created:=File("/PACKAGE/SpecialPrefs/"+Current user+".myPrefs").create()
+```
+
+### File object
+
+||
+|---|
+|[](#copyto)
|
+|[](#create)
|
+|[](#createalias)
|
+|[](#creationdate)
|
+|[](#creationtime)
|
+|[](#delete)
|
+|[](#exists)
|
+|[](#extension)
|
+|[](#fullname)
|
+|[](#getappinfo)
|
+|[](#getcontent)
|
+|[](#geticon)
|
+|[](#gettext)
|
+|[](#hidden)
|
+|[](#isalias)
+|[](#isfile)
|
+|[](#isfolder)
|
+|[](#iswritable)
|
+|[](#modificationdate)
|
+|[](#modificationtime)
|
+|[](#moveto)
|
+|[](#name)
|
+|[](#original)
|
+|[](#parent)
|
+|[](#path)
|
+|[](#platformpath)
|
+|[](#rename)
|
+|[](#setappinfo)
|
+|[](#setcontent)
|
+|[](#settext)
|
+|[](#size)
|
+
+
+
+## File
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+**File** ( *path* : Text { ; *pathType* : Integer }{ ; *\** } ) : 4D.File
**File** ( *fileConstant* : Integer { ; *\** } ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|path|Text|->|File path|
+|fileConstant|Integer|->|4D file constant|
+|pathType|Integer|->|`fk posix path` (default) or `fk platform path`|
+|*||->|* to return file of host database|
+|Result|4D.File|<-|New file object|
+
+
+
+#### Description
+
+The `File` command creates and returns a new object of the `4D.File` type. The command accepts two syntaxes:
+
+**File ( path { ; pathType } { ; \* })**
+
+In the *path* parameter, pass a file path string. You can use a custom string or a filesystem (e.g., "/DATA/myfile.txt").
+
+> Only absolute pathnames are supported with the `File` command.
+
+By default, 4D expects a path expressed with the POSIX syntax. If you work with platform pathnames (Windows or macOS), you must declare it using the *pathType* parameter. The following constants are available:
+
+|Constant|Value|Comment|
+|---|---|---|
+|fk platform path|1|Path expressed with a platform-specific syntax (mandatory in case of platform pathname)|
+|fk posix path|0|Path expressed with POSIX syntax (default)
+
+**File ( fileConstant { ; \* } )**
+
+In the *fileConstant* parameter, pass a 4D built-in or system file, using one of the following constants:
+
+|Constant|Value|Comment|
+|---|---|---|
+|Backup history file|19|Backup history file (see Configuration and trace files). Stored in the backup destination folder. |
+|Backup log file|13|Current backup journal file. Stored in the application Logs folder.|
+|Backup settings file|1|Default backup.4DSettings file (xml format), stored in the Settings folder of the project|
+|Backup settings file for data|17|backup.4DSettings file (xml format) for the data file, stored in the Settings folder of the data folder|
+|Build application log file|14|Current log file in xml format of the application builder. Stored in the Logs folder. |
+|Build application settings file|20|Default settings file of the application builder ("buildApp.4DSettings"). Stored in the Settings folder of the project.|
+|Compacting log file|6|Log file of the most recent compacting done with the Compact data file command or the Maintenance and security center. Stored in the Logs folder.|
+|Current backup settings file|18|backup.4DSettings file currently used by the application. It can be the backup settings file (default) or a custom user backup settings file defined for the data file|
+|Debug log file|12|Log file created by the `SET DATABASE PARAMETER(Debug log recording)` command. Stored in the Logs folder. |
+|Diagnostic log file|11|Log file created by the `SET DATABASE PARAMETER(Diagnostic log recording)` command. Stored in the Logs folder. |
+|Directory file|16|directory.json file, containing the description of users and groups (if any) for the project application. It can be located either in the user settings folder (default, global to the project), or in the data settings folder (specific to a data file). |
+|HTTP debug log file|9|Log file created by the `WEB SET OPTION(Web debug log)` command. Stored in the Logs folder. |
+|HTTP log file|8|Log file created by the `WEB SET OPTION(Web log recording)` command. Stored in Logs folder.|
+|IMAP Log file|23|Log file created by the `SET DATABASE PARAMETER(IMAP Log)` command. Stored in the Logs folder.|
+|Last backup file|2|Last backup file, named \[bkpNum].4BK, stored at a custom location.|
+|Last journal integration log file|22|Full pathname of the last journal integration log file (stored in the Logs folder of the restored application), if any. This file is created, in auto-repair mode, as soon as a log file integration occurred|
+|Repair log file|7|Log file of database repairs made on the database in the Maintenance and Security Center (MSC). Stored in the Logs folder.|
+|Request log file|10|Standard client/server request log file (excluding Web requests) created by the `SET DATABASE PARAMETER(4D Server log recording)` or `SET DATABASE PARAMETER(Client log recording)` commands. If executed on the server, the server log file is returned (stored in the Logs folder on the server). If executed on the client, the client log file is returned (stored in the client local Logs folder). |
+|SMTP log file|15|Log file created by the `SET DATABASE PARAMETER(SMTP Log)` command. Stored in the Logs folder. |
+|User settings file|3|settings.4DSettings file for all data files, stored in Preferences folder next to structure file if enabled.|
+|User settings file for data|4|settings.4DSettings file for current data file, stored in Preferences folder next to the data file.|
+|Verification log file|5|Log files created by the `VERIFY CURRENT DATA FILE` and `VERIFY DATA FILE` commands or the Maintenance and Security Center (MSC). Stored in the Logs folder. |
+
+If the target *fileConstant* does not exist, a null object is returned. No errors are raised.
+
+If the command is called from a component, pass the optional * parameter to get the path of the host database. Otherwise, if you omit the * parameter, a null object is always returned.
+
+
+## 4D.File.new()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added
+
+
+**4D.File.new** ( *path* : Text { ; *pathType* : Integer }{ ; *\** } ) : 4D.File
**4D.File.new** ( *fileConstant* : Integer { ; *\** } ) : 4D.File
+
+
+#### Description
+
+The `4D.File.new()` function creates and returns a new object of the `4D.File` type. It is identical to the [`File`](#file) command (shortcut).
+
+> It is recommended to use the [`File`](#file) shortcut command instead of `4D.File.new()`.
+
+
+
+
+
+
+
+## .create()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**Not available for ZIP archives**
+
+
+
+**.create()** : Boolean
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|Boolean|<-|True if the file was created successfully, false otherwise|
+
+
+#### Description
+
+The `.create()` function creates a file on disk according to the properties of the `File` object.
+
+If necessary, the function creates the folder hierachy as described in the [platformPath](#platformpath) or [path](#path) properties. If the file already exists on disk, the function does nothing (no error is thrown) and returns false.
+
+**Returned value**
+
+* **True** if the file is created successfully;
+* **False** if a file with the same name already exists or if an error occured.
+
+#### Example
+
+Creation of a preferences file in the database folder:
+
+```4d
+ var $created : Boolean
+ $created:=File("/PACKAGE/SpecialPrefs/"+Current user+".myPrefs").create()
+```
+
+
+
+
+
+
+
+## .createAlias()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+**.createAlias**( *destinationFolder* : 4D.Folder ; *aliasName* : Text { ; *aliasType* : Integer } ) : 4D.File
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|destinationFolder|4D.Folder|->|Destination folder for the alias or shortcut|
+|aliasName|Text|->|Name of the alias or shortcut|
+|aliasType|Integer|->|Type of the alias link|
+|Result|4D.File|<-|Alias or shortcut file reference|
+
+
+
+#### Description
+
+The `.createAlias()` function creates an alias (macOS) or a shortcut (Windows) to the file with the specified *aliasName* name in the folder designated by the *destinationFolder* object.
+
+Pass the name of the alias or shortcut to create in the *aliasName* parameter.
+
+By default on macOS, the function creates a standard alias. You can also create a symbolic link by using the *aliasType* parameter. The following constants are available:
+
+|Constant|Value|Comment|
+|--------|-----|-------|
+|`fk alias link`|0|Alias link (default)|
+|`fk symbolic link`|1|Symbolic link (macOS only)|
+
+On Windows, a shortcut (.lnk file) is always created (the *aliasType* parameter is ignored).
+
+
+**Returned object**
+
+A `4D.File` object with the `isAlias` property set to **true**.
+
+#### Example
+
+You want to create an alias to a file in your database folder:
+
+```4d
+ $myFile:=Folder(fk documents folder).file("Archives/ReadMe.txt")
+ $aliasFile:=$myFile.createAlias(File("/PACKAGE");"ReadMe")
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .delete()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+**.delete( )**
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+| | ||Does not require any parameters|
+
+
+
+#### Description
+
+The `.delete()` function deletes the file.
+
+If the file is currently open, an error is generated.
+
+If the file does not exist on disk, the function does nothing (no error is generated).
+
+>**WARNING**: `.delete( )` can delete any file on a disk. This includes documents created with other applications, as well as the applications themselves. `.delete( )` should be used with extreme caution. Deleting a file is a permanent operation and cannot be undone.
+
+#### Example
+
+You want to delete a specific file in the database folder:
+
+```4d
+ $tempo:=File("/PACKAGE/SpecialPrefs/"+Current user+".prefs")
+ If($tempo.exists)
+ $tempo.delete()
+ ALERT("User preference file deleted.")
+ End if
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .getAppInfo()
+
+History
+|Version|Changes|
+|---|---|
+|v19|Added
+
+
+
+**.getAppInfo**() : Object
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|Object|<-|Contents of .exe/.dll version resource or .plist file|
+
+
+
+#### Description
+
+The `.getAppInfo()` function returns the contents of a **.exe**, **.dll** or **.plist** file information as an object.
+
+The function must be used with an existing .exe, .dll or .plist file. If the file does not exist on disk or is not a valid .exe, .dll or .plist file, the function returns an empty object (no error is generated).
+
+> The function only supports .plist files in xml format (text-based). An error is returned if it is used with a .plist file in binary format.
+
+**Returned object with a .exe or .dll file**
+
+> Reading a .exe or .dll is only possible on Windows.
+
+All property values are Text.
+
+|Property|Type|
+|---|---|
+|InternalName|Text|
+|ProductName|Text|
+|CompanyName|Text|
+|LegalCopyright|Text|
+|ProductVersion|Text|
+|FileDescription|Text|
+|FileVersion|Text|
+|OriginalFilename|Text|
+
+**Returned object with a .plist file**
+
+The xml file contents is parsed and keys are returned as properties of the object, preserving their types (text, boolean, number). `.plist dict` is returned as a JSON object and `.plist array` is returned as a JSON array.
+
+#### Example
+
+```4d
+ // display copyright info of application .exe file (windows)
+var $exeFile : 4D.File
+var $info : Object
+$exeFile:=File(Application file; fk platform path)
+$info:=$exeFile.getAppInfo()
+ALERT($info.LegalCopyright)
+
+ // display copyright info of an info.plist (any platform)
+var $infoPlistFile : 4D.File
+var $info : Object
+$infoPlistFile:=File("/RESOURCES/info.plist")
+$info:=$infoPlistFile.getAppInfo()
+ALERT($info.Copyright)
+```
+
+#### See also
+
+[.setAppInfo()](#setappinfo)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .moveTo()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+**.moveTo**( *destinationFolder* : 4D.Folder { ; *newName* : Text } ) : 4D.File
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|destinationFolder|4D.Folder|->|Destination folder|
+|newName|Text|->|Full name for the moved file|
+|Result|4D.File|<-|Moved file|
+
+
+
+#### Description
+
+The `.moveTo()` function moves or renames the `File` object into the specified *destinationFolder*.
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+By default, the file retains its name when moved. If you want to rename the moved file, pass the new full name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+
+**Returned object**
+
+The moved `File` object.
+
+#### Example
+
+
+```4d
+$DocFolder:=Folder(fk documents folder)
+$myFile:=$DocFolder.file("Current/Infos.txt")
+$myFile.moveTo($DocFolder.folder("Archives");"Infos_old.txt")
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .rename()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+**.rename**( *newName* : Text ) : 4D.File
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|newName|Text|->|New full name for the file|
+|Result|4D.File|<-|Renamed file|
+
+
+#### Description
+
+The `.rename()` function renames the file with the name you passed in *newName* and returns the renamed `File` object.
+
+The *newName* parameter must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned. If a file with the same name already exists, an error is returned.
+
+Note that the function modifies the full name of the file, i.e. if you do not pass an extension in *newName*, the file will have a name without an extension.
+
+
+**Returned object**
+
+The renamed `File` object.
+
+#### Example
+
+You want to rename "ReadMe.txt" in "ReadMe_new.txt":
+
+```4d
+ $toRename:=File("C:\\Documents\\Archives\\ReadMe.txt";fk platform path)
+ $newName:=$toRename.rename($toRename.name+"_new"+$toRename.extension)
+```
+
+
+
+
+## .setAppInfo()
+
+History
+|Version|Changes|
+|---|---|
+|v19|Added
+
+
+
+**.setAppInfo**( *info* : Object )
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|info|Object|->|Properties to write in .exe/.dll version resource or .plist file|
+
+
+
+#### Description
+
+The `.setAppInfo()` function writes the *info* properties as information contents of a **.exe**, **.dll** or **.plist** file.
+
+The function must be used with an existing .exe, .dll or .plist file. If the file does not exist on disk or is not a valid .exe, .dll or .plist file, the function does nothing (no error is generated).
+
+> The function only supports .plist files in xml format (text-based). An error is returned if it is used with a .plist file in binary format.
+
+***info* parameter object with a .exe or .dll file**
+
+> Writing a .exe or .dll file information is only possible on Windows.
+
+Each valid property set in the *info* object parameter is written in the version resource of the .exe or .dll file. Available properties are (any other property will be ignored):
+
+|Property|Type|
+|---|---|
+|InternalName|Text|
+|ProductName|Text|
+|CompanyName|Text|
+|LegalCopyright|Text|
+|ProductVersion|Text|
+|FileDescription|Text|
+|FileVersion|Text|
+|OriginalFilename|Text|
+
+If you pass a null or empty text as value, an empty string is written in the property. If you pass a value type different from text, it is stringified.
+
+
+***info* parameter object with a .plist file**
+
+Each valid property set in the *info* object parameter is written in the .plist file as a key. Any key name is accepted. Value types are preserved when possible.
+
+If a key set in the *info* parameter is already defined in the .plist file, its value is updated while keeping its original type. Other existing keys in the .plist file are left untouched.
+
+> To define a Date type value, the format to use is a json timestamp string formated in ISO UTC without milliseconds ("2003-02-01T01:02:03Z") like in the Xcode plist editor.
+
+#### Example
+
+```4d
+ // set copyright and version of a .exe file (Windows)
+var $exeFile : 4D.File
+var $info : Object
+$exeFile:=File(Application file; fk platform path)
+$info:=New object
+$info.LegalCopyright:="Copyright 4D 2021"
+$info.ProductVersion:="1.0.0"
+$exeFile.setAppInfo($info)
+```
+
+```4d
+ // set some keys in an info.plist file (all platforms)
+var $infoPlistFile : 4D.File
+var $info : Object
+$infoPlistFile:=File("/RESOURCES/info.plist")
+$info:=New object
+$info.Copyright:="Copyright 4D 2021" //text
+$info.ProductVersion:=12 //integer
+$info.ShipmentDate:="2021-04-22T06:00:00Z" //timestamp
+$infoPlistFile.setAppInfo($info)
+```
+
+#### See also
+
+[.getAppInfo()](#getappinfo)
+
+
+## .setContent()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+**.setContent** ( *content* : Blob )
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|content|BLOB|->|New contents for the file|
+
+
+
+#### Description
+
+The `.setContent( )` function rewrites the entire content of the file using the data stored in the *content* BLOB. For information on BLOBs, please refer to the [BLOB](Concepts/dt_blob.md) section.
+
+
+#### Example
+
+```4d
+ $myFile:=Folder(fk documents folder).file("Archives/data.txt")
+ $myFile.setContent([aTable]aBlobField)
+```
+
+
+
+
+
+
+
+## .setText()
+
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+**.setText** ( *text* : Text {; *charSetName* : Text { ; *breakMode* : Integer } } )
**.setText** ( *text* : Text {; *charSetNum* : Integer { ; *breakMode* : Integer } } )
+
+
+
+|Parameter|Type||Description|
+|---------|----|---|--------|
+|text|Text|->|Text to store in the file|
+|charSetName|Text|->|Name of character set|
+|charSetNum|Integer|->|Number of character set|
+|breakMode|Integer|->|Processing mode for line breaks|
+
+#### Description
+
+The `.setText()` function writes *text* as the new contents of the file.
+
+If the file referenced in the `File` object does not exist on the disk, it is created by the function. When the file already exists on the disk, its prior contents are erased, except if it is already open, in which case, its contents are locked and an error is generated.
+
+In *text*, pass the text to write to the file. It can be a literal ("my text"), or a 4D text field or variable.
+
+Optionally, you can designate the character set to be used for writing the contents. You can pass either:
+
+- in *charSetName*, a string containing the standard set name (for example "ISO-8859-1" or ""UTF-8"),
+- or in *charSetNum*, the MIBEnum ID (number) of the standard set name.
+
+> For the list of character sets supported by 4D, refer to the description of the `CONVERT FROM TEXT` command.
+
+If a Byte Order Mark (BOM) exists for the character set, 4D inserts it into the file unless the character set used contains the suffix "-no-bom" (e.g. "UTF-8-no-bom"). If you do not specify a character set, by default 4D uses the "UTF-8" character set.
+
+In *breakMode*, you can pass a number indicating the processing to apply to end-of-line characters before saving them in the file. The following constants, found in the **System Documents** theme, are available:
+
+|Constant|Value|Comment|
+|--------|-----|-------|
+|`Document unchanged`|0|No processing|
+|`Document with native format`|1|(Default) Line breaks are converted to the native format of the operating system: LF (carriage return) on macOS, CRLF (carriage return + line feed) on Windows|
+|`Document with CRLF`|2|Line breaks are converted to CRLF (carriage return + line feed), the default Windows format|
+|`Document with CR`|3|Line breaks are converted to CR (carriage return), the default Classic Mac OS format|
+|`Document with LF`|4|Line breaks are converted to LF (line feed), the default Unix and macOS format|
+
+By default, when you omit the *breakMode* parameter, line breaks are processed in native mode (1).
+
+> **Compatibility Note**: compatibility options are available for EOL and BOM management. See [Compatibility page](https://doc.4d.com/4dv19R/help/title/en/page3239.html) on doc.4d.com.
+
+#### Example
+
+```4d
+$myFile:=File("C:\\Documents\\Hello.txt";fk platform path)
+$myFile.setText("Hello world")
+```
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/API/FolderClass.md b/docs/API/FolderClass.md
new file mode 100644
index 00000000000000..87e82bded1b0b6
--- /dev/null
+++ b/docs/API/FolderClass.md
@@ -0,0 +1,488 @@
+---
+id: FolderClass
+title: Folder
+---
+
+
+
+`Folder` objects are created with the [`Folder`](#folder) command. They contain references to folders that may or may not actually exist on disk. For example, when you execute the `Folder` command to create a new folder, a valid `Folder` object is created but nothing is actually stored on disk until you call the [`folder.create( )`](#create-) function.
+
+### Example
+
+The following example creates a "JohnSmith" folder:
+
+```code4d
+Form.curfolder:=Folder(fk database folder)
+Form.curfolder:=Folder("C:\\Users\\JohnSmith\\";fk platform path)
+```
+
+### Folder object
+
+||
+|---|
+|[](#copyto) |
+|[](#create)
|
+|[](#createalias)
|
+|[](#creationdate)
|
+|[](#creationtime)
|
+|[](#delete)
|
+|[](#exists)
|
+|[](#extension)
|
+|[](#fullname)
|
+|[](#geticon)
|
+|[](#hidden)
|
+|[](#isalias)
|
+|[](#isfile)
|
+|[](#isfolder)
|
+|[](#ispackage)
|
+|[](#modificationdate)
|
+|[](#modificationtime)
|
+|[](#name)
|
+|[](#original)
|
+|[](#parent)
|
+|[](#path)
|
+|[](#platformpath)
|
+|[](#moveto)
|
+|[](#rename)
|
+
+
+
+## Folder
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added|
+
+
+
+**Folder** ( *path* : Text { ; *pathType* : Integer }{ ; *\** } ) : 4D.Folder
**Folder** ( *folderConstant* : Integer { ; *\** } ) : 4D.Folder
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|path|Text|->|Folder path|
+|folderConstant|Integer|->|4D folder constant|
+|pathType|Integer|->|`fk posix path` (default) or `fk platform path`|
+|*||->|* to return folder of host database|
+|Result|4D.Folder|<-|New folder object|
+
+
+
+#### Description
+
+The `Folder` command creates and returns a new object of the `4D.Folder` type. The command accepts two syntaxes:
+
+**Folder ( path { ; pathType } { ; \* } )**
+
+In the *path* parameter, pass a folder path string. You can use a custom string or a filesystem (e.g., "/DATA").
+
+> Only absolute pathnames are supported with the `Folder` command.
+
+By default, 4D expects a path expressed with the POSIX syntax. If you work with platform pathnames (Windows or macOS), you must declare it using the *pathType* parameter. The following constants are available:
+
+|Constant|Value|Comment|
+|---|---|---|
+|fk platform path|1|Path expressed with a platform-specific syntax (mandatory in case of platform pathname)|
+|fk posix path|0|Path expressed with POSIX syntax (default)
+
+**Folder ( folderConstant { ; \* } )**
+
+In the *folderConstant* parameter, pass a 4D built-in or system folder, using one of the following constants:
+
+|Constant|Value|Comment|
+|---|---|---|
+|fk applications folder|116||
+|fk data folder|9|Associated filesystem: "/DATA"|
+|fk database folder|4|Associated filesystem: "/PACKAGE"|
+|fk desktop folder|115||
+|fk documents folder|117|Document folder of the user|
+|fk licenses folder|1|Folder containing the machine's 4D license files|
+|fk logs folder|7|Associated filesystem: "/LOGS"|
+|fk mobileApps folder|10|Associated filesystem: "/DATA"|
+|fk remote database folder|3|4D database folder created on each 4D remote machine|
+|fk resources folder|6|Associated filesystem: "/RESOURCES"|
+|fk system folder|100||
+|fk user preferences folder|0|4D folder that stores user preference files within the \ directory.|
+|fk web root folder|8|Current Web root folder of the database: if within the package "/PACKAGE/path", otherwise full path|
+
+If the command is called from a component, pass the optional * parameter to get the path of the host database. Otherwise, if you omit the * parameter, a null object is always returned.
+
+
+## 4D.Folder.new()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added
+
+
+**4D.Folder.new** ( *path* : Text { ; *pathType* : Integer }{ ; *\** } ) : 4D.Folder
**4D.Folder.new** ( *folderConstant* : Integer { ; *\** } ) : 4D.Folder
+
+
+#### Description
+
+The `4D.Folder.new()` function creates and returns a new object of the `4D.Folder` type. It is identical to the [`Folder`](#folder) command (shortcut).
+
+> It is recommended to use the [`Folder`](#folder) shortcut command instead of `4D.Folder.new()`.
+
+
+
+
+
+
+
+## .create()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+
+**.create()** : Boolean
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|Boolean|<-|True if the folder was created successfully, false otherwise|
+
+
+
+
+#### Description
+
+The `.create()` function creates a folder on disk according to the properties of the `Folder` object.
+
+If necessary, the function creates the folder hierachy as described in the [platformPath](#platformpath) or [path](#path) properties. If the folder already exists on disk, the function does nothing (no error is thrown) and returns false.
+
+**Returned value**
+
+* **True** if the folder is created successfully;
+* **False** if a folder with the same name already exists or if an error occured.
+
+#### Example 1
+
+Create an empty folder in the database folder:
+
+```4d
+var $created : Boolean
+$created:=Folder("/PACKAGE/SpecialPrefs").create()
+```
+
+#### Example 2
+
+Creation of the "/Archives2019/January/" folder in the database folder:
+
+```4d
+$newFolder:=Folder("/PACKAGE/Archives2019/January")
+If($newFolder.create())
+ ALERT("The "+$newFolder.name+" folder was created.")
+Else
+ ALERT("Impossible to create a "+$newFolder.name+" folder.")
+End if
+```
+
+
+
+
+
+
+
+## .createAlias()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+
+**.createAlias**( *destinationFolder* : 4D.Folder ; *aliasName* : Text { ; *aliasType* : Integer } ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|destinationFolder|4D.Folder|->|Destination folder for the alias or shortcut|
+|aliasName|Text|->|Name of the alias or shortcut|
+|aliasType|Integer|->|Type of the alias link|
+|Result|4D.File|<-|Alias or shortcut reference|
+
+
+
+#### Description
+
+The `.createAlias()` function creates an alias (macOS) or a shortcut (Windows) to the folder with the specified *aliasName* name in the folder designated by the *destinationFolder* object.
+
+Pass the name of the alias or shortcut to create in the *aliasName* parameter.
+
+By default on macOS, the function creates a standard alias. You can also create a symbolic link by using the *aliasType* parameter. The following constants are available:
+
+|Constant|Value|Comment|
+|--------|-----|-------|
+|`fk alias link`|0|Alias link (default)|
+|`fk symbolic link`|1|Symbolic link (macOS only)|
+
+On Windows, a shortcut (.lnk file) is always created (the *aliasType* parameter is ignored).
+
+**Returned object**
+
+A `4D.File` object with the `isAlias` property set to **true**.
+
+#### Example
+
+You want to create an alias to an archive folder in your database folder:
+
+```4d
+$myFolder:=Folder("C:\\Documents\\Archives\\2019\\January";fk platform path)
+$aliasFile:=$myFolder.createAlias(Folder("/PACKAGE");"Jan2019")
+```
+
+
+
+
+
+
+
+
+
+
+
+## .delete()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+
+**.delete**( { *option* : Integer } )
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|option |Integer|->|Folder deletion option|
+
+
+
+
+#### Description
+
+The `.delete()` function deletes the folder.
+
+By default, for security reasons, if you omit the option parameter, `.delete( )` only allows empty folders to be deleted. If you want the command to be able to delete folders that are not empty, you must use the option parameter with one of the following constants:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`Delete only if empty`| 0| Deletes folder only when it is empty|
+|`Delete with contents`| 1| Deletes folder along with everything it contains|
+
+When `Delete only if empty` is passed or if you omit the option parameter:
+
+* The folder is only deleted if it is empty; otherwise, the command does nothing and an error -47 is generated.
+* If the folder does not exist, the error -120 is generated.
+
+When `Delete with contents` is passed:
+
+* The folder, along with all of its contents, is deleted.
+**Warning**: Even when this folder and/or its contents are locked or set to read-only, if the current user has suitable access rights, the folder (and contents) is still deleted.
+* If this folder, or any of the files it contains, cannot be deleted, deletion is aborted as soon as the first inaccessible element is detected, and an error(*) is returned. In this case, the folder may be only partially deleted. When deletion is aborted, you can use the `GET LAST ERROR STACK` command to retrieve the name and path of the offending file.
+* If the folder does not exist, the command does nothing and no error is returned.
+(*) Windows: -54 (Attempt to open locked file for writing)
+macOS: -45 (The file is locked or the pathname is not correct)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .moveTo()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+
+**.moveTo**( *destinationFolder* : 4D.Folder { ; *newName* : Text } ) : 4D.Folder
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|destinationFolder|4D.Folder|->|Destination folder|
+|newName|Text|->|Full name for the moved folder|
+|Result|4D.Folder|<-|Moved folder|
+
+
+
+#### Description
+
+The `.moveTo( )` function moves or renames the `Folder` object (source folder) into the specified *destinationFolder*.
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+By default, the folder retains its name when moved. If you want to rename the moved folder, pass the new full name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+**Returned object**
+
+The moved `Folder` object.
+
+#### Example
+
+You want to move and rename a folder:
+
+```4d
+ var $tomove; $moved : Object
+ $docs:=Folder(fk documents folder)
+ $tomove:=$docs.folder("Pictures")
+ $tomove2:=$tomove.moveTo($docs.folder("Archives");"Pic_Archives")
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .rename()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R5|Added
+
+
+
+**.rename**( *newName* : Text ) : 4D.Folder
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|newName|Text|->|New full name for the folder|
+|Result|4D.Folder|<-|Renamed folder|
+
+
+
+
+#### Description
+
+The `.rename()` function renames the folder with the name you passed in *newName* and returns the renamed `Folder` object.
+
+The *newName* parameter must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned. If a file with the same name already exists, an error is returned.
+
+
+**Returned object**
+
+The renamed `Folder` object.
+
+#### Example
+
+
+```4d
+ var $toRename : 4D.Folder
+ $toRename:=Folder("/RESOURCES/Pictures").rename("Images")
+```
+
+
+
diff --git a/docs/API/FunctionClass.md b/docs/API/FunctionClass.md
new file mode 100644
index 00000000000000..cb0e8fad0251ef
--- /dev/null
+++ b/docs/API/FunctionClass.md
@@ -0,0 +1,445 @@
+---
+id: FunctionClass
+title: Formula
+---
+
+
+
+The [Formula](#formula) and [Formula from string](#formula-from-string) commands allow you to create native [`4D.Function` objects](#about-4dfunction-objects) to execute any 4D expression or code expressed as text.
+
+
+### Formula Objects
+
+Formula objects can be encapsulated in object properties:
+
+```4d
+ var $f : 4D.Function
+ $f:=New object
+ $f.message:=Formula(ALERT("Hello world"))
+```
+
+This property is an "object function", i.e. a function which is bound to its parent object. To execute a function stored in an object property, use the **()** operator after the property name, such as:
+
+```4d
+ $f.message() //displays "Hello world"
+```
+
+Syntax with brackets is also supported:
+
+```4d
+ $f["message"]() //displays "Hello world"
+```
+
+Note that, even if it does not have parameters (see below), an object function to be executed must be called with ( ) parenthesis. Calling only the object property will return a new reference to the formula (and will not execute it):
+
+```4d
+ $o:=$f.message //returns the formula object in $o
+```
+
+You can also execute a function using the [`apply()`](#apply) and [`call()`](#call) functions:
+
+```4d
+ $f.message.apply() //displays "Hello world"
+```
+
+#### Passing parameters
+
+You can pass parameters to your formulas using the [sequential parameter syntax](Concepts/parameters.md#sequential-parameters) based upon $1, $2...$n. For example, you can write:
+
+```4d
+ var $f : Object
+ $f:=New object
+ $f.message:=Formula(ALERT("Hello "+$1))
+ $f.message("John") //displays "Hello John"
+```
+
+Or using the [.call()](#call) function:
+
+```4d
+ var $f : Object
+ $f:=Formula($1+" "+$2)
+ $text:=$f.call(Null;"Hello";"World") //returns "Hello World"
+ $text:=$f.call(Null;"Welcome to";String(Year of(Current date))) //returns "Welcome to 2019" (for example)
+```
+
+#### Parameters to a single method
+
+For more convenience, when the formula is made of a single project method, parameters can be omitted in the formula object initialization. They can just be passed when the formula is called. For example:
+
+```4d
+ var $f : 4D.Function
+
+ $f:=Formula(myMethod)
+ //Writing Formula(myMethod($1;$2)) is not necessary
+ $text:=$f.call(Null;"Hello";"World") //returns "Hello World"
+ $text:=$f.call() //returns "How are you?"
+
+ //myMethod
+ #DECLARE ($param1 : Text; $param2 : Text)->$return : Text
+ If(Count parameters=2)
+ $return:=$param1+" "+$param2
+ Else
+ $return:="How are you?"
+ End if
+```
+
+Parameters are received within the method, in the order they are specified in the call.
+
+### About 4D.Function objects
+
+A `4D.Function` object contains a piece of code that can be executed from an object, either using the `()` operator, or using the [`apply()`](#apply) and [`call()`](#call) functions. 4D proposes three kinds of Function objects:
+
+- native functions, i.e. built-in functions from various 4D classes such as `collection.sort()` or `file.copyTo()`.
+- user functions, created in user [classes](Concepts/classes.md) using the [Function keyword](Concepts/classes.md#function).
+- formula functions, i.e. functions that can execute any 4D formula.
+
+
+
+### Summary
+
+
+||
+|---|
+|[](#apply) |
+|[](#call)
|
+|[](#source)
|
+
+
+
+
+## Formula
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Renamed (New formula -> Formula)|
+|v17 R3|Added|
+
+
+
+**Formula** ( *formulaExp* : Expression ) : 4D.Function
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|formulaExp|Expression|->|Formula to be returned as object|
+|Result|4D.Function|<-|Native function encapsulating the formula|
+
+
+
+#### Description
+
+The `Formula` command creates a `4D Function` object based upon the *formulaExp* expression. *formulaExp* can be as simple as a single value or complex, such as a project method with parameters.
+
+Having a formula as an object allows it to be passed as a parameter (calculated attribute) to commands or methods or to be executed from various components without needing to declare them as "shared by components and host database". When called, the formula object is evaluated within the context of the database or component that created it.
+
+The returned formula can be called with:
+
+* [`.call()`](#call) or [`.apply()`](#apply) methods, or
+* object notation syntax (see [formula object](#formula-object)).
+
+```4d
+ var $f : 4D.Function
+ $f:=Formula(1+2)
+ $o:=New object("myFormula";$f)
+
+ //three different ways to call the formula
+ $f.call($o) //returns 3
+ $f.apply($o) //returns 3
+ $o.myFormula() //returns 3
+```
+
+You can pass [parameters](#passing-parameters) to the `Formula`, as seen below in [example 4](#example-4).
+
+You can specify the object on which the formula is executed, as seen in [example 5](#example-5). The properties of the object can then be accessed via the `This` command.
+
+If *formulaExp* uses local variables, their values are copied and stored in the returned formula object when it is created. When executed, the formula uses these copied values rather than the current value of the local variables. Note that using arrays as local variables is not supported.
+
+The object created by `Formula` can be saved, for example, in a database field or in a blob document.
+
+
+#### Example 1
+
+A simple formula:
+
+```4d
+ var $f : 4D.Function
+ $f:=Formula(1+2)
+
+ var $o : Object
+ $o:=New object("f";$f)
+
+ $result:=$o.f() // returns 3
+```
+
+#### Example 2
+
+A formula using local variables:
+
+```4d
+
+
+ $value:=10
+ $o:=New object("f";Formula($value))
+ $value:=20
+
+ $result:=$o.f() // returns 10
+```
+
+
+#### Example 3
+
+A simple formula using parameters:
+
+```4d
+ $o:=New object("f";Formula($1+$2))
+ $result:=$o.f(10;20) //returns 30
+```
+
+
+#### Example 4
+
+A formula using a project method with parameters:
+
+```4d
+ $o:=New object("f";Formula(myMethod))
+ $result:=$o.f("param1";"param2") // equivalent to $result:=myMethod("param1";"param2")
+```
+
+
+#### Example 5
+
+Using `This`:
+
+```4d
+ $o:=New object("fullName";Formula(This.firstName+" "+This.lastName))
+ $o.firstName:="John"
+ $o.lastName:="Smith"
+ $result:=$o.fullName() //returns "John Smith"
+```
+
+#### Example 6
+
+Calling a formula using object notation:
+
+```4d
+ var $feta; $robot : Object
+ var $calc : 4D.Function
+ $robot:=New object("name";"Robot";"price";543;"quantity";2)
+ $feta:=New object("name";"Feta";"price";12.5;"quantity";5)
+
+ $calc:=Formula(This.total:=This.price*This.quantity)
+
+ //sets the formula to object properties
+ $feta.calc:=$calc
+ $robot.calc:=$calc
+
+ //call the formula
+ $feta.calc() // $feta={name:Feta,price:12.5,quantity:5,total:62.5,calc:"[object Formula]"}
+ $robot.calc() // $robot={name:Robot,price:543,quantity:2,total:1086,calc:"[object Formula]"}
+```
+
+
+
+
+## Formula from string
+
+History
+|Version|Changes|
+|---|---|
+|v17 R6|Renamed New formula from string -> Formula from string|
+|v17 R3|Added|
+
+
+
+**Formula from string**( *formulaString* : Text ) : 4D.Function
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|formulaString|Text|->|Text formula to be returned as object|
+|Result|4D.Function|<-|Native object encapsulating the formula|
+
+
+
+#### Description
+
+The `Formula from string` command creates a 4D.Function object based upon the *formulaString*. *formulaString* can be as simple as a single value or complex, such as a project method with parameters.
+
+This command is similar to [`Formula`](#formula), except that it handles a text-based formula. In most cases, it is recommended to use the `Formula` command. `Formula from string` should only be used when the original formula was expressed as text (e.g., stored externally in a JSON file). In this context, using syntax with tokens is highly advised.
+
+>Because local variable contents can not be accessed by name in compiled mode, they can not be used in *formulaString*. An attempt to access a local variable with `Formula from string` will result in an error (-10737).
+
+
+#### Example
+
+The following code will create a dialog accepting a formula in text format:
+
+```4d
+ var $textFormula : Text
+ var $f : 4D.Function
+ $textFormula:=Request("Please type a formula")
+ If(ok=1)
+ $f:=Formula from string($textFormula)
+ ALERT("Result = "+String($f.call()))
+ End if
+```
+
+
+
+
+...and execute the formula:
+
+
+
+
+
+
+
+
+
+
+## .apply()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R3|Added|
+
+
+
+**.apply**() : any
**.apply**( *thisObj* : Object { ; *formulaParams* : Collection } ) : any
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|thisObj|Object|->|Object to be returned by the This command in the formula|
+|formulaParams |Collection|->|Collection of values to be passed as $1...$n when `formula` is executed|
+|Result|any|<-|Value from formula execution|
+
+
+
+#### Description
+
+The `.apply()` function executes the `formula` object to which it is applied and returns the resulting value. The formula object can be created using the `Formula` or `Formula from string` commands.
+
+
+In the *thisObj* parameter, you can pass a reference to the object to be used as `This` within the formula.
+
+You can also pass a collection to be used as $1...$n parameters in the formula using the optional *formulaParams* parameter.
+
+Note that `.apply()` is similar to [`.call()`](#call) except that parameters are passed as a collection. This can be useful for passing calculated results.
+
+
+#### Example 1
+
+```4d
+ var $f : 4D.Function
+ $f:=Formula($1+$2+$3)
+
+ $c:=New collection(10;20;30)
+ $result:=$f.apply(Null;$c) // returns 60
+```
+
+
+#### Example 2
+
+```4d
+ var $calc : 4D.Function
+ var $feta; $robot : Object
+ $robot:=New object("name";"Robot";"price";543;"quantity";2)
+ $feta:=New object("name";"Feta";"price";12.5;"quantity";5)
+
+ $calc:=Formula(This.total:=This.price*This.quantity)
+
+ $calc.apply($feta) // $feta={name:Feta,price:12.5,quantity:5,total:62.5}
+ $calc.apply($robot) // $robot={name:Robot,price:543,quantity:2,total:1086}
+```
+
+
+
+
+
+## .call()
+
+History
+|Version|Changes|
+|---|---|
+|v17 R3|Added|
+
+
+
+**.call**() : any
**.call**( *thisObj* : Object { ; ...*params* : any } ) : any
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|thisObj|Object|->|Object to be returned by the This command in the formula|
+|params |any|->|Value(s) to be passed as $1...$n when formula is executed|
+|Result|any|<-|Value from formula execution|
+
+
+
+#### Description
+
+The `.call()` function executes the `formula` object to which it is applied and returns the resulting value. The formula object can be created using the `Formula` or `Formula from string` commands.
+
+In the *thisObj* parameter, you can pass a reference to the object to be used as `This` within the formula.
+
+You can also pass values to be used as *$1...$n* parameters in the formula using the optional *params* parameter(s).
+
+Note that `.call()` is similar to [`.apply()`](#apply) except that parameters are passed directly.
+
+#### Example 1
+
+```4d
+ var $f : 4D.Function
+ $f:=Formula(Uppercase($1))
+ $result:=$f.call(Null;"hello") // returns "HELLO"
+```
+
+#### Example 2
+
+```4d
+ $o:=New object("value";50)
+ $f:=Formula(This.value*2)
+ $result:=$f.call($o) // returns 100
+```
+
+
+
+
+
+
+## .source
+
+History
+|Version|Changes|
+|---|---|
+|v18 R2|Added|
+
+
+
+**.source** : Text
+
+
+#### Description
+
+The `.source` property contains the source expression of the `formula` as text.
+
+This property is **read-only**.
+
+#### Example
+
+```4d
+ var $of : 4D.Function
+ var $tf : Text
+ $of:=Formula(String(Current time;HH MM AM PM))
+ $tf:=$of.source //"String(Current time;HH MM AM PM)"
+```
+
+
+
+
+
+
diff --git a/docs/API/IMAPTransporterClass.md b/docs/API/IMAPTransporterClass.md
new file mode 100644
index 00000000000000..bbc39432f47e72
--- /dev/null
+++ b/docs/API/IMAPTransporterClass.md
@@ -0,0 +1,1963 @@
+---
+id: IMAPTransporterClass
+title: IMAPTransporter
+---
+
+The `IMAPTransporter` class allows you to retrieve messages from a IMAP email server.
+
+
+### IMAP Transporter object
+
+IMAP Transporter objects are instantiated with the [IMAP New transporter](#imap-new-transporter) command. They provide the following properties and functions:
+
+||
+|---|
+|[](#acceptunsecureconnection)
|
+|[](#addflags)
|
+|[](#append)
|
+|[](#authenticationmode)
|
+|[](#checkconnection)
|
+|[](#checkconnectiondelay)
|
+|[](#connectiontimeout)
|
+|[](#copy)
|
+|[](#createbox)
|
+|[](#delete)
|
+|[](#deletebox)
|
+|[](#expunge)
|
+|[](#getboxinfo)
|
+|[](#getboxlist)
|
+|[](#getdelimiter)
|
+|[](#getmail)
|
+|[](#getmails)
|
+|[](#getmimeasblob)
|
+|[](#host)
|
+|[](#logfile)
|
+|[](#move)
|
+|[](#numtoid)
|
+|[](#removeflags)
|
+|[](#renamebox)
|
+|[](#port)
|
+|[](#searchmails)
|
+|[](#selectbox)
|
+|[](#subscribe)
|
+|[](#unsubscribe)
|
+|[](#user)
|
+
+
+
+## IMAP New transporter
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added|
+
+
+
+**IMAP New transporter**( *server* : Object ) : 4D.IMAPTransporter
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|server|Object|->|Mail server information|
+|Result|4D.IMAPTransporter|<-|[IMAP transporter object](#imap-transporter-object)|
+
+
+
+#### Description
+
+The `IMAP New transporter` command configures a new IMAP connection according to the *server* parameter and returns a new *transporter* object. The returned transporter object will then usually be used to receive emails.
+
+In the *server* parameter, pass an object containing the following properties:
+
+|*server*|Default value (if omitted)|
+|---|---|
+|[](#acceptunsecureconnection)
|False|
+|.**accessTokenOAuth2**: Text
.**accessTokenOAuth2**: Object
Text string or token object representing OAuth2 authorization credentials. Used only with OAUTH2 `authenticationMode`. If `accessTokenOAuth2` is used but `authenticationMode` is omitted, the OAuth 2 protocol is used (if allowed by the server). Not returned in *[IMAP transporter](#imap-transporter-object)* object.|none|
+|[](#authenticationmode)
|the most secure authentication mode supported by the server is used|
+|[](#checkconnectiondelay)
|300|
+|[](#connectiontimeout)
|30|
+|[](#host)
|*mandatory*
+|[](#logfile)
|none|
+|.**password** : Text
User password for authentication on the server. Not returned in *[IMAP transporter](#imap-transporter-object)* object.|none|
+|[](#port)
|993|
+|[](#user)
|none|
+
+
+>**Warning**: Make sure the defined timeout is lower than the server timeout, otherwise the client timeout will be useless.
+
+
+#### Result
+
+The function returns an [**IMAP transporter object**](#imap-transporter-object). All returned properties are **read-only**.
+
+>The IMAP connection is automatically closed when the transporter object is destroyed.
+
+#### Example
+
+```4d
+$server:=New object
+$server.host:="imap.gmail.com" //Mandatory
+$server.port:=993
+$server.user:="4d@gmail.com"
+$server.password:="XXXXXXXX"
+$server.logFile:="LogTest.txt" //log to save in the Logs folder
+
+var $transporter : 4D.IMAPTransporter
+ $transporter:=IMAP New transporter($server)
+
+$status:=$transporter.checkConnection()
+If(Not($status.success))
+ ALERT("An error occurred: "+$status.statusText)
+End if
+```
+
+
+## 4D.IMAPTransporter.new()
+
+
+
+**4D.IMAPTransporter.new**( *server* : Object ) : 4D.IMAPTransporter
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|server|Object|->|Mail server information|
+|Result|4D.IMAPTransporter|<-|[IMAP transporter object](#imap-transporter-object)|
+
+
+#### Description
+
+The `4D.IMAPTransporter.new()` function creates and returns a new object of the `4D.IMAPTransporter` type. It is identical to the [`IMAP New transporter`](#imap-new-transporter) command (shortcut).
+
+
+
+
+
+## .addFlags()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added|
+
+
+
+**.addFlags**( *msgIDs* : Collection ; *keywords* : Object ) : Object
**.addFlags**( *msgIDs* : Text ; *keywords* : Object ) : Object
**.addFlags**( *msgIDs* : Longint ; *keywords* : Object ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgIDs|Collection|->|Collection of strings: Message unique IDs (text)
Text: Unique ID of a message
Longint (IMAP all): All messages in the selected mailbox|
+|keywords|Object|->|Keyword flags to add|
+|Result|Object|<-|Status of the addFlags operation|
+
+
+
+#### Description
+
+The `.addFlags()` function adds flags to the `msgIDs` for the specified `keywords`.
+
+In the `msgIDs` parameter, you can pass either:
+
+* a *collection* containing the unique IDs of specific messages or
+* the unique ID (*text*) of a single message or
+* the following constant (*longint*) for all messages in the selected mailbox:
+
+ |Constant |Value |Comment|
+ |---|---|---|
+ |IMAP all |1 |Select all messages in the selected mailbox|
+
+The `keywords` parameter lets you pass an object with keyword values for specific flags to add to `msgIDs`. You can pass any of the following keywords:
+
+|Parameter|Type|Description|
+|---|---|---|
+|$draft |Boolean |True to add the "draft" flag to the message |
+|$seen |Boolean |True to add the "seen" flag to the message|
+|$flagged |Boolean |True to add the "flagged" flag to the message|
+|$answered |Boolean |True to add the "answered" flag to the message|
+|$deleted |Boolean | True to add the "deleted" flag to the message|
+
+>* False values are ignored.
+>* The interpretation of keyword flags may vary per mail client.
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+#### Example
+
+```4d
+var $options;$transporter;$boxInfo;$status : Object
+
+$options:=New object
+$options.host:="imap.gmail.com"
+$options.port:=993
+$options.user:="4d@gmail.com"
+$options.password:="xxxxx"
+
+// Create transporter
+$transporter:=IMAP New transporter($options)
+
+// Select mailbox
+$boxInfo:=$transporter.selectBox("INBOX")
+
+// Mark all messages from INBOX as read/seen
+$flags:=New object
+$flags["$seen"]:=True
+$status:=$transporter.addFlags(IMAP all;$flags)
+```
+
+
+
+
+
+## .append()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added|
+
+
+
+**.append**( *mailObj* : Object ; *destinationBox* : Text ; *options* : Object ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mailObj|Object|->|Email object|
+|destinationBox|Text|->|Mailbox to receive Email object|
+|options|Object|->|Object containing charset info |
+|Result|Object|<-|Status of the delete operation|
+
+
+
+#### Description
+
+The `.append()` function appends a `mailObj` to the `destinationBox`.
+
+In the `mailObj` parameter, pass an Email object. For a comprehensive description of mail properties, see [Email object](emails.html#email-object). The `.append()` function supports keyword tags in the Email object's `keywords` attribute.
+
+The optional `destinationBox` parameter lets you pass the name of a mailbox where the `mailObj` will be appended. If omitted, the current mailbox is used.
+
+In the optional `options` parameter, you can pass an object to define the charset and encoding for specific parts of the email. Available properties:
+
+|Property|Type|Description|
+|---|---|---|
+|headerCharset|Text|Charset and encoding used for the following parts of the email: subject, attachment filenames, and email name attribute(s). Possible values: See possible charsets table below|
+|bodyCharset|Text|Charset and encoding used for the html and text body contents of the email. Possible values: See possible charsets table below |
+
+Possible charsets:
+
+|Constant|Value|Comment|
+|---|---|---|
+|mail mode ISO2022JP|US-ASCII_ISO-2022-JP_UTF8_QP|
- headerCharset: US-ASCII if possible, Japanese (ISO-2022-JP) & Quoted-printable if possible, otherwise UTF-8 & Quoted-printable
- bodyCharset: US-ASCII if possible, Japanese (ISO-2022-JP) & 7-bit if possible, otherwise UTF-8 & Quoted-printable
|
+|mail mode ISO88591|ISO-8859-1|- headerCharset: ISO-8859-1 & Quoted-printable
- bodyCharset: ISO-8859-1 & 8-bit
|
+|mail mode UTF8|US-ASCII_UTF8_QP|headerCharset & bodyCharset: US-ASCII if possible, otherwise UTF-8 & Quoted-printable (**default value**)|
+|mail mode UTF8 in base64|US-ASCII_UTF8_B64|headerCharset & bodyCharset: US-ASCII if possible, otherwise UTF-8 & base64|
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+#### Example
+
+To save an email in the Drafts mailbox:
+
+```4d
+var $settings; $status; $msg; $imap: Object
+
+$settings:=New object("host"; "domain.com"; "user"; "xxxx"; "password"; "xxxx"; "port"; 993)
+
+$imap:=IMAP New transporter($settings)
+
+$msg:=New object
+$msg.from:="xxxx@domain.com"
+$msg.subject:="Lorem Ipsum"
+$msg.textBody:="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+$msg.keywords:=New object
+$msg.keywords["$seen"]:=True//flag the message as read
+$msg.keywords["$draft"]:=True//flag the message as a draft
+
+$status:=$imap.append($msg; "Drafts")
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .checkConnectionDelay
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added|
+
+
+
+**.checkConnectionDelay** : Integer
+
+
+#### Description
+
+The `.checkConnectionDelay` property contains the maximum time (in seconds) allowed prior to checking the connection to the server. If this time is exceeded between two method calls, the connection to the server will be checked. By default, if the property has not been set in the *server* object, the value is 300.
+
+>**Warning**: Make sure the defined timeout is lower than the server timeout, otherwise the client timeout will be useless.
+
+
+
+
+
+
+
+
+## .copy()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+**.copy**( *msgsIDs* : Collection ; *destinationBox* : Text ) : Object
**.copy**( *allMsgs* : Integer ; *destinationBox* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgsIDs|Collection|->|Collection of message unique IDs (strings)|
+|allMsgs|Integer|->|`IMAP all`: All messages in the selected mailbox|
+|destinationBox|Text|->|Mailbox to receive copied messages|
+|Result|Object|<-|Status of the copy operation|
+
+
+
+#### Description
+
+The `.copy()` function copies the messages defined by *msgsIDs* or *allMsgs* to the *destinationBox* on the IMAP server.
+
+You can pass:
+
+- in the *msgsIDs* parameter, a collection containing the unique IDs of the specific messages to copy, or
+- in the *allMsgs* parameter, the `IMAP all` constant (integer) to copy all messages in the selected mailbox.
+
+The *destinationBox* parameter allows you to pass a text value with the name of the mailbox where the copies of messages will be placed.
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+
+
+#### Example 1
+
+To copy a selection of messages:
+
+
+```4d
+ var $server;$boxInfo;$status : Object
+ var $mailIds : Collection
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("inbox")
+
+ //get collection of message unique IDs
+ $mailIds:=$transporter.searchMails("subject \"4D new feature:\"")
+
+ // copy found messages to the "documents" mailbox
+ $status:=$transporter.copy($mailIds;"documents")
+```
+
+#### Example 2
+
+To copy all messages in the current mailbox:
+
+
+```4d
+ var $server;$boxInfo;$status : Object
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+
+ $boxInfo:=$transporter.selectBox("inbox")
+
+ // copy all messages to the "documents" mailbox
+ $status:=$transporter.copy(IMAP all;"documents")
+```
+
+
+
+
+
+## .createBox()
+
+History
+|Version|Changes|
+|---|---|
+|v19|Added|
+
+
+
+**.createBox**( *name* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|Text|->|Name of the new mailbox|
+|Result|Object|<-|Status of the mailbox creation operation|
+
+
+
+#### Description
+
+The `.createBox()` function creates a mailbox with the given `name`. If the IMAP server’s hierarchy separator character appears elsewhere in the mailbox name, the IMAP server will create any parent names needed to create the given mailbox.
+
+In other words, an attempt to create "Projects/IMAP/Doc" on a server in which "/" is the hierarchy separator character will create:
+
+* Only the "Doc" mailbox if "Projects" & "IMAP" already exist.
+* "IMAP" & "Doc" mailboxes if only “Projects†already exists.
+* "Projects" & “IMAP†& "Doc" mailboxes, if they do not already exist.
+
+In the `name` parameter, pass the name of the new mailbox.
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+
+
+#### Example
+
+To create a new “Invoices†mailbox:
+
+
+```4d
+var $pw : text
+var $options; $transporter; $status : object
+
+$options:=New object
+
+$pw:=Request("Please enter your password:")
+If(OK=1)
+$options.host:="imap.gmail.com"
+$options.user:="test@gmail.com"
+$options.password:=$pw
+
+$transporter:=IMAP New transporter($options)
+
+$status:=$transporter.createBox("Invoices")
+
+If ($status.success)
+ALERT("Mailbox creation successful!")
+Else
+ALERT("Error: "+$status.statusText)
+End if
+End if
+```
+
+
+
+
+
+
+
+
+
+## .delete()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+**.delete**( *msgsIDs* : Collection ) : Object
**.delete**( *allMsgs* : Integer ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgsIDs|Collection|->|Collection of message unique IDs (strings)|
+|allMsgs|Integer|->|`IMAP all`: All messages in the selected mailbox|
+|Result|Object|<-|Status of the delete operation|
+
+
+
+#### Description
+
+The `.delete()` function sets the "deleted" flag for the messages defined in `msgsIDs` or `allMsgs`.
+
+You can pass:
+
+- in the `msgsIDs` parameter, a collection containing the unique IDs of the specific messages to delete, or
+- in the `allMsgs` parameter, the `IMAP all` constant (integer) to delete all messages in the selected mailbox.
+
+Executing this function does not actually remove messages. Messages with the "delete" flag can still be found by the [.searchMails()](#searchmails) function. Flagged messages are deleted from the IMAP server with the [`.expunge()`](#expunge) function or by selecting another mailbox or when the [transporter object](#imap-transporter-object) (created with [IMAP New transporter](#imap-new-transporter)) is destroyed.
+
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+
+
+#### Example 1
+
+To delete a selection of messages:
+
+
+```4d
+ var $server;$boxInfo;$status : Object
+ var $mailIds : Collection
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("Inbox")
+
+ //get collection of message unique IDs
+ $mailIds:=$transporter.searchMails("subject \"Reports\"")
+
+ // Delete selected messages
+ $status:=$transporter.delete($mailIds)
+```
+
+#### Example 2
+
+To delete all messages in the current mailbox:
+
+
+```4d
+ var $server;$boxInfo;$status : Object
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("Junk Email")
+
+ // delete all messages in the current mailbox
+ $status:=$transporter.delete(IMAP all)
+```
+
+
+
+
+
+## .deleteBox()
+
+History
+|Version|Changes|
+|---|---|
+|v19|Added|
+
+
+
+**.deleteBox**( *name* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|Text|->|Name of the mailbox to delete|
+|Result|Object|<-|Status of the mailbox deletion operation|
+
+
+
+#### Description
+
+The `.deleteBox()` function permanently removes the mailbox with the given `name` from the IMAP server. Attempting to delete an INBOX or a mailbox that does not exist will generate an error.
+
+In the `name` parameter, pass the name of the mailbox to delete.
+
+>* The function cannot delete a mailbox that has child mailboxes if the parent mailbox has the "\Noselect" attribute.
+>* All messages in the deleted mailbox will also be deleted.
+>* The ability to delete a mailbox depends on the mail server.
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+
+
+#### Example
+
+To delete the "Nova Orion Industries" child mailbox from the "Bills" mailbox hierarchy:
+
+```4d
+var $pw; $name : text
+var $options; $transporter; $status : object
+
+$options:=New object
+
+$pw:=Request("Please enter your password:")
+
+If(OK=1) $options.host:="imap.gmail.com"
+$options.user:="test@gmail.com"
+$options.password:=$pw
+
+$transporter:=IMAP New transporter($options)
+
+// delete mailbox
+$name:="Bills"+$transporter.getDelimiter()+"Nova Orion Industries"
+$status:=$transporter.deleteBox($name)
+
+If ($status.success)
+ ALERT("Mailbox deletion successful!")
+ Else
+ ALERT("Error: "+$status.statusText)
+ End if
+End if
+```
+
+
+
+
+
+
+
+
+
+
+## .expunge()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added|
+
+
+
+**.expunge()** : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|Object|<-|Status of the expunge operation |
+
+
+#### Description
+
+The `.expunge()` function removes all messages with the "deleted" flag from the IMAP mail server. The "deleted" flag can be set with the [`.delete()`](#delete) or [`.addFlags()`](#addflags) methods.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+#### Example
+
+```4d
+var $options;$transporter;$boxInfo;$status : Object
+var $ids : Collection
+
+$options:=New object
+$options.host:="imap.gmail.com"
+$options.port:=993
+$options.user:="4d@gmail.com"
+$options.password:="xxxxx"
+
+// Create transporter
+$transporter:=IMAP New transporter($options)
+
+// Select mailbox
+$boxInfo:=$transporter.selectBox("INBOX")
+
+// Find and delete all seen messages in INBOX
+$ids:=$transporter.searchMails("SEEN")
+$status:=$transporter.delete($ids)
+
+// Purge all messages flagged as deleted
+$status:=$transporter.expunge()
+```
+
+
+
+
+
+## .getBoxInfo()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|name is optional|
+|v18 R4|Added|
+
+
+
+**.getBoxInfo**( { *name* : Text }) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|Text|->|Name of the mailbox|
+|Result|Object|<-|boxInfo object|
+
+
+
+#### Description
+
+The `.getBoxInfo()` function returns a `boxInfo` object corresponding to the mailbox *name*. This function returns the same information as [`.selectBox()`](#selectbox) without changing the current mailbox.
+
+In the optional *name* parameter, pass the name of the mailbox to access. The name represents an unambiguous left-to-right hierarchy with levels separated by a specific delimiter character. The delimiter can be found with the [`.getDelimiter()`](#getdelimiter) function.
+
+
+**Returned object**
+
+The `boxInfo` object returned contains the following properties:
+
+|Property| Type| Description|
+|---|---|---|
+|name|text|Name of the mailbox
+|mailCount| number| Number of messages in the mailbox|
+|mailRecent| number| Number of messages with the "recent" flag (indicating new messages)|
+
+
+
+#### Example
+
+```4d
+ var $transporter : 4D.IMAPTransporter
+ $transporter:=IMAP New transporter($server)
+
+ $info:=$transporter.getBoxInfo("INBOX")
+ ALERT("INBOX contains "+String($info.mailRecent)+" recent emails.")
+```
+
+
+
+
+
+
+
+## .getBoxList()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added|
+|v19|Add `isSubscribed` parameter|
+
+
+
+**.getBoxList**( { *parameters* : Object } ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|parameters|Object|->|Parameter object|
+|Result|Collection|<-|Collection of mailbox objects|
+
+
+
+#### Description
+
+The `.getBoxList()` function returns a collection of mailboxes describing all of the available mailboxes. This function allows you to locally manage the list of messages located on the IMAP mail server.
+
+In the optional `parameters` parameter, pass an object containing values to filter the returned mailboxes. You can pass:
+
+|Property | Type| Description |
+|---|---|---|
+|isSubscribed| Boolean |**True** to return only subscribed mailboxes **False** to return all available mailboxes|
+
+#### Result
+
+Each object of the returned collection contains the following properties:
+
+|Property| Type|Description |
+|---|---|---|
+|\[].name|text|Name of the mailbox |
+|\[].selectable |boolean |Indicates whether or not the access rights allow the mailbox to be selected: - true - the mailbox can be selected
- false - the mailbox can not be selected
|
+|\[].inferior |boolean |Indicates whether or not the access rights allow creating a lower hierachy in the mailbox: - true - a lower level can be created
- false - a lower level can not be created
|
+|\[].interesting |boolean |Indicates if the mailbox has been marked "interesting" by the server: - true - The mailbox has been marked "interesting" by the server. For example, it may contain new messages.
- false - The mailbox has not been marked "interesting" by the server.
|
+
+
+If the account does not contain any mailboxes, an empty collection is returned.
+
+>* If there is no open connection, `.getBoxList()` will open a connection.
+>* If the connection has not been used since the designated connection delay (see `IMAP New transporter`), the `.checkConnection( )` function is automatically called.
+
+
+#### Example
+
+
+```4d
+ var $transporter : 4D.IMAPTransporter
+ $transporter:=IMAP New transporter($server)
+
+ $boxList:=$transporter.getBoxList()
+
+ For each($box;$boxList)
+ If($box.interesting)
+ $split:=Split string($box.name;$transporter.getDelimiter())
+ ALERT("New emails are available in the box: "+$split[$split.length-1])
+ End if
+ End for each
+```
+
+
+
+
+
+
+## .getDelimiter()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added|
+
+
+
+**.getDelimiter()** : Text
+
+
+|Parameter|Type||Description|
+|-----|--- |:---:|------|
+|Result|Text|<-|Hierarchy delimiter character|
+
+
+
+#### Description
+
+The `.getDelimiter()` function returns the character used to delimit levels of hierarchy in the mailbox name.
+
+The delimiter is a character which can be used to:
+
+* create lower level (inferior) mailboxes
+* search higher or lower within the mailbox hierarchy
+
+
+#### Result
+
+Mailbox name delimiter character.
+
+
+>* If there is no open connection, `.getDelimiter()` will open a connection.
+>* If the connection has not been used since the [designated connection delay](#checkconnectiondelay), the [`.checkConnection()`](#checkconnection) function is automatically called.
+
+
+
+#### Example
+
+
+```4d
+ var $transporter : 4D.IMAPTransporter
+ $transporter:=IMAP New transporter($server)
+
+ $boxList:=$transporter.getBoxList()
+
+ For each($box;$boxList)
+ If($box.interesting)
+ $split:=Split string($box.name;$transporter.getDelimiter())
+ ALERT("New emails are available in the box: "+$split[$split.length-1])
+ End if
+ End for each
+```
+
+
+
+
+
+
+## .getMail()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added|
+
+
+
+**.getMail**( *msgNumber*: Integer { ; *options* : Object } ) : Object
**.getMail**( *msgID*: Text { ; *options* : Object } ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgNumber|Integer|->|Sequence number of the message|
+|msgID|Text|->|Unique ID of the message|
+|options|Object|->|Message handling instructions|
+|Result|Object|<-|[Email object](EmailObjectClass.md#email-object)|
+
+
+
+#### Description
+
+The `.getMail()` function returns the `Email` object corresponding to the *msgNumber* or *msgID* in the mailbox designated by the `IMAP_transporter`. This function allows you to locally handle the email contents.
+
+In the first parameter, you can pass either:
+
+* *msgNumber*, an *integer* value indicating the sequence number of the message to retrieve or
+* *msgID*, a *text* value indicating the unique ID of the message to retrieve.
+
+The optional *options* parameter allows you pass an object defining additional instructions for handling the message. The following properties are available:
+
+|Property | Type | Description |
+|---|---|---|
+|updateSeen|boolean|If True, the message is marked as "seen" in the mailbox. If False, the message is not marked as "seen". Default value: True|
+|withBody|boolean | Pass True to return the body of the message. If False, only the message header is returned. Default value: True|
+
+>* The function generates an error and returns **Null** if *msgID* designates a non-existing message,
+>* If no mailbox is selected with the [`.selectBox()`](#selectbox) function, an error is generated,
+>* If there is no open connection, `.getMail()` will open a connection the last mailbox specified with [`.selectBox()`](#selectbox)`.
+
+
+
+#### Result
+
+`.getMail()` returns an [`Email` object](EmailObjectClass.md#email-object) with the following specific IMAP properties: *id*, *receivedAt*, and *size*.
+
+#### Example
+
+You want to get the message with ID = 1:
+
+```4d
+ var $server : Object
+ var $info; $mail; $boxInfo : Variant
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ //create transporter
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("Inbox")
+
+ //get Email object with ID 1
+ $mail:=$transporter.getMail(1)
+```
+
+
+
+
+
+
+## .getMails()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+**.getMails**( *ids* : Collection { ; *options* : Object } ) : Object
**.getMails**( *startMsg* : Integer ; *endMsg* : Integer { ; *options* : Object } ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|ids |Collection|->|Collection of message ID|
+|startMsg|Integer|->|Sequence number of the first message|
+|endMsg |Integer|->|Sequence number of the last message|
+|options|Object|->|Message handling instructions|
+|Result|Object|<-|Object containing:
- a collection of [Email objects](EmailObjectClass.md#email-object) and
- a collection of IDs or numbers for missing messages, if any
|
+
+
+
+#### Description
+
+The `.getMails()` function returns an object containing a collection of `Email` objects.
+
+**First Syntax:**
+
+***.getMails( ids { ; options } ) -> result***
+
+The first syntax allows you to retrieve messages based on their IDs.
+
+In the *ids* parameter, pass a collection of IDs for the messages to return. You can get the IDs with [`.getMail()`](#getmail).
+
+The optional *options* parameter allows you to define the parts of the messages to be returned. See the **Options** table below for a description of the available properties.
+
+**Second syntax:**
+
+ ***.getMails( startMsg ; endMsg { ; options } ) -> result***
+
+The second syntax allows you to retrieve messages based on a sequential range. The values passed represent the position of the messages in the mailbox.
+
+In the *startMsg* parameter, pass an *integer* value corresponding to the number of the first message in a sequential range. If you pass a negative number (*startMsg* <= 0), the first message of the mailbox will be used as the beginning of the sequence.
+
+In the *endMsg* parameter, pass an *integer* value corresponding to the number of the last message to be included in a sequential range. If you pass a negative number (*endMsg* <= 0), the last message of the mailbox will be used as the end of the sequence.
+
+The optional *options* parameter allows you to define the parts of the messages to be returned.
+
+**Options**
+
+|Property | Type| Description |
+|---|---|---|
+|updateSeen | Boolean | If True, the specified messages are marked as "seen" in the mailbox. If False, the messages are not marked as "seen". Default value: True |
+|withBody | Boolean | Pass True to return the body of the specified messages. If False, only the message headers are returned. Default value: True|
+
+>* If no mailbox is selected with the [`.selectBox()`](#selectbox) command, an error is generated.
+>* If there is no open connection, `.getMails()` will open a connection the last mailbox specified with [`.selectBox()`](#selectbox).
+
+
+#### Result
+
+`.getMails()` returns an object containing the following collections:
+
+
+|Property | Type | Description |
+|---|---|---|
+|list |Collection |Collection of [`Email` objects](EmailObjectClass.md#email-object). If no Email objects are found, an empty collection is returned.|
+
+|notFound |Collection| Collection of:
- first syntax - previously passed message IDs that do not exist
- second syntax - sequence numbers of messages between startMsg and endMsg that do not exist
An empty collection is returned if all messages are found.|
+
+
+#### Example
+
+You want to retrieve the 20 most recent emails without changing their "seen" status:
+
+```4d
+ var $server,$boxInfo,$result : Object
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ //create transporter
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("INBOX")
+
+ If($boxInfo.mailCount>0)
+ // retrieve the headers of the last 20 messages without marking them as read
+ $result:=$transporter.getMails($boxInfo.mailCount-20;$boxInfo.mailCount;\
+ New object("withBody";False;"updateSeen";False))
+ For each($mail;$result.list)
+ // ...
+ End for each
+ End if
+```
+
+
+
+
+
+
+## .getMIMEAsBlob()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added|
+
+
+
+**.getMIMEAsBlob**( *msgNumber* : Integer { ; *updateSeen* : Boolean } ) : Blob
**.getMIMEAsBlob**( *msgID* : Text { ; *updateSeen* : Boolean } ) : Blob
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgNumber|Integer|-> |Sequence number of the message|
+|msgID|Text|-> |Unique ID of the message|
+|updateSeen|Boolean|->|If True, the message is marked "seen" in the mailbox. If False the message is left untouched.|
+|Result|BLOB|<-|Blob of the MIME string returned from the mail server|
+
+
+
+
+
+#### Description
+
+The `.getMIMEAsBlob()` function returns a BLOB containing the MIME contents for the message corresponding to the *msgNumber* or *msgID* in the mailbox designated by the `IMAP_transporter`.
+
+In the first parameter, you can pass either:
+
+* *msgNumber*, an *integer* value indicating the sequence number of the message to retrieve or
+* *msgID*, a *text* value indicating the unique ID of the message to retrieve.
+
+The optional *updateSeen* parameter allows you to specify if the message is marked as "seen" in the mailbox. You can pass:
+
+* **True** - to mark the message as "seen" (indicating the message has been read)
+* **False** - to leave the message's "seen" status untouched
+
+
+>* The function returns an empty BLOB if *msgNumber* or msgID* designates a non-existing message,
+>* If no mailbox is selected with the [`.selectBox()`](#selectbox) command, an error is generated,
+>* If there is no open connection, `.getMIMEAsBlob()` will open a connection the last mailbox specified with `.selectBox()`.
+
+
+#### Result
+
+`.getMIMEAsBlob()` returns a `BLOB` which can be archived in a database or converted to an [`Email` object](EmailObjectClass.md#email-object) with the `MAIL Convert from MIME` command.
+
+
+#### Example
+
+
+```4d
+ var $server : Object
+ var $boxInfo : Variant
+ var $blob : Blob
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com"
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ //create transporter
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("Inbox")
+
+ //get BLOB
+ $blob:=$transporter.getMIMEAsBlob(1)
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .move()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+
+**.move**( *msgsIDs* : Collection ; *destinationBox* : Text ) : Object
**.move**( *allMsgs* : Integer ; *destinationBox* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgsIDs|Collection|->|Collection of message unique IDs (strings)|
+|allMsgs|Integer|->|`IMAP all`: All messages in the selected mailbox|
+|destinationBox|Text|->|Mailbox to receive moved messages|
+|Result|Object|<-|Status of the move operation|
+
+
+
+#### Description
+
+The `.move()` function moves the messages defined by *msgsIDs* or *allMsgs* to the *destinationBox* on the IMAP server.
+
+You can pass:
+
+- in the *msgsIDs* parameter, a collection containing the unique IDs of the specific messages to move, or
+- in the *allMsgs* parameter, the `IMAP all` constant (integer) to move all messages in the selected mailbox.
+
+The *destinationBox* parameter allows you to pass a text value with the name of the mailbox where the messages will be moved.
+
+> This function is only supported by IMAP servers compliant with RFC [8474](https://tools.ietf.org/html/rfc8474).
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+
+
+#### Example 1
+
+To move a selection of messages:
+
+```4d
+ var $server;$boxInfo;$status : Object
+ var $mailIds : Collection
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("inbox")
+
+ //get collection of message unique IDs
+ $mailIds:=$transporter.searchMails("subject \"4D new feature:\"")
+
+ // Move found messages from the current mailbox to the "documents" mailbox
+ $status:=$transporter.move($mailIds;"documents")
+```
+
+#### Example 2
+
+To move all messages in the current mailbox:
+
+
+```4d
+ var $server;$boxInfo;$status : Object
+ var $transporter : 4D.IMAPTransporter
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("inbox")
+
+ // move all messages in the current mailbox to the "documents" mailbox
+ $status:=$transporter.move(IMAP all;"documents")
+```
+
+
+
+
+
+
+## .numToID()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+**.numToID**( *startMsg* : Integer ; *endMsg* : Integer ) : Collection
+
+
+|Parameter|Type||Description|
+|-----|--- |:---:|------|
+|startMsg|Integer|-> |Sequence number of the first message|
+|endMsg|Integer|->|Sequence number of the last message|
+|Result|Collection|<-|Collection of unique IDs|
+
+
+
+#### Description
+
+The `.numToID()` function converts the sequence numbers to IMAP unique IDs for the messages in the sequential range designated by *startMsg* and *endMsg* in the currently selected mailbox.
+
+In the *startMsg* parameter, pass an integer value corresponding to the number of the first message in a sequential range. If you pass a negative number (*startMsg* <= 0), the first message of the mailbox will be used as the beginning of the sequence.
+
+In the *endMsg* parameter, pass an integer value corresponding to the number of the last message to be included in a sequential range. If you pass a negative number (*endMsg* <= 0), the last message of the mailbox will be used as the end of the sequence.
+
+
+#### Result
+
+The function returns a collection of strings (unique IDs).
+
+#### Example
+
+
+```4d
+ var $transporter : 4D.IMAPTransporter
+ var $server;$boxInfo;$status : Object
+ var $mailIds : Collection
+
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.port:=993
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ $transporter:=IMAP New transporter($server)
+
+ //select mailbox
+ $boxInfo:=$transporter.selectBox("inbox")
+
+ //get IDs for 5 last messages received
+ $mailIds:=$transporter.numToID(($boxInfo.mailCount-5);$boxInfo.mailCount)
+
+ //delete the messages from the current mailbox
+ $status:=$transporter.delete($mailIds)
+```
+
+
+
+
+
+## .removeFlags()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R6|Added|
+
+
+
+**.removeFlags**( *msgIDs* : Collection ; *keywords* : Object ) : Object
**.removeFlags**( *msgIDs* : Text ; *keywords* : Object ) : Object
**.removeFlags**( *msgIDs* : Longint ; *keywords* : Object ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgIDs|Collection|->|Collection of strings: Message unique IDs (text)
Text: Unique ID of a message
Longint (IMAP all): All messages in the selected mailbox|
+|keywords|Object|->|Keyword flags to remove|
+|Result|Object|<-|Status of the removeFlags operation|
+
+
+
+#### Description
+
+The `.removeFlags()` function removes flags from the `msgIDs` for the specified `keywords`.
+
+In the `msgIDs` parameter, you can pass either:
+
+* a *collection* containing the unique IDs of specific messages or
+* the unique ID (*text*) of a single message or
+* the following constant (*longint*) for all messages in the selected mailbox:
+
+ |Constant |Value |Comment|
+ |---|---|---|
+ |IMAP all |1 |Select all messages in the selected mailbox|
+
+The `keywords` parameter lets you pass an object with keyword values for specific flags to remove from `msgIDs`. You can pass any of the following keywords:
+
+|Parameter|Type|Description|
+|---|---|---|
+|$draft |Boolean |True to remove the "draft" flag from the message |
+|$seen |Boolean |True to remove the "seen" flag from the message|
+|$flagged |Boolean |True to remove the "flagged" flag from the message|
+|$answered |Boolean |True to remove the "answered" flag from the message|
+|$deleted |Boolean | True to remove the "deleted" flag from the message|
+
+Note that False values are ignored.
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+#### Example
+
+```4d
+var $options;$transporter;$boxInfo;$status : Object
+
+$options:=New object
+$options.host:="imap.gmail.com"
+$options.port:=993
+$options.user:="4d@gmail.com"
+$options.password:="xxxxx"
+
+// Create transporter
+$transporter:=IMAP New transporter($options)
+
+// Select mailbox
+$boxInfo:=$transporter.selectBox("INBOX")
+
+// Mark all messages from INBOX as unseen
+$flags:=New object
+$flags["$seen"]:=True
+$status:=$transporter.removeFlags(IMAP all;$flags)
+```
+
+
+
+
+
+## .renameBox()
+
+History
+|Version|Changes|
+|---|---|
+|v19|Added|
+
+
+
+**.renameBox**( *currentName* : Text ; *newName* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|currentName|Text|->|Name of the current mailbox|
+|newName|Text|->|New mailbox name|
+|Result|Object|<-|Status of the renaming operation|
+
+
+
+#### Description
+
+The `.renameBox()` function changes the name of a mailbox on the IMAP server. Attempting to rename a mailbox from a mailbox name that does not exist or to a mailbox name that already exists will generate an error.
+
+In the `currentName` parameter, pass the name of the mailbox to be renamed.
+
+Pass the new name for the mailbox in the `newName` parameter.
+
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+#### Example
+
+To to rename your “Invoices†mailbox to “Billsâ€:
+
+```4d
+var $pw : text
+var $options; $transporter; $status : object
+
+$options:=New object
+
+$pw:=Request("Please enter your password:")
+
+If(OK=1) $options.host:="imap.gmail.com"
+$options.user:="test@gmail.com"
+$options.password:=$pw
+
+$transporter:=IMAP New transporter($options)
+
+// rename mailbox
+$status:=$transporter.renameBox("Invoices"; "Bills")
+
+If ($status.success)
+ ALERT("Mailbox renaming successful!")
+ Else
+ ALERT("Error: "+$status.statusText)
+ End if
+End if
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .searchMails()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R5|Added|
+
+
+
+**.searchMails**( *searchCriteria* : Text ) : Collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|searchCriteria|Text|-> |Search criteria|
+|Result|Collection|<-|Collection of message numbers|
+
+
+
+#### Description
+
+> This function is based upon the specification for the [IMAP protocol](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol).
+
+The `.searchMails()` function searches for messages that match the given *searchCriteria* in the current mailbox. *searchCriteria* consists of one or more search keys.
+
+*searchCriteria* is a text parameter listing one or more search keys (see [Authorized search-keys](#authorized-search-keys) below) associated or not with values to look for. A search key may be a single or multiple items. For example:
+
+```
+SearchKey1 = FLAGGED
+SearchKey2 = NOT FLAGGED
+SearchKey3 = FLAGGED DRAFT
+```
+
+> Matching is usually not case-sensitive
+
+- If the *searchCriteria* is a null string, the search will be equivalent to a “select allâ€.
+- If the *searchCriteria* includes multiple search keys, the result is the intersection (AND function) of all the messages that match those keys.
+
+```
+searchCriteria = FLAGGED FROM "SMITH"
+```
+... returns all messages with \Flagged flag set AND sent by Smith.
+- You can use the **OR** or **NOT** operators as follows:
+
+```
+searchCriteria = OR SEEN FLAGGED
+```
+... returns all messages with \Seen flag set OR \Flagged flag set
+
+```
+searchCriteria = NOT SEEN
+```
+... returns all messages with \Seen flag not set.
+
+```
+searchCriteria = HEADER CONTENT-TYPE "MIXED" NOT HEADER CONTENT-TYPE "TEXT"...
+```
+... returns message whose content-type header contains “Mixed†and does not contain “Textâ€.
+
+```
+searchCriteria = HEADER CONTENT-TYPE "E" NOT SUBJECT "o" NOT HEADER CONTENT-TYPE "MIXED"
+```
+... returns message whose content-type header contains “ e †and whose Subject header does not contain “ o †and whose content-type header is not “ Mixed â€.
+
+As concerns the last two examples, notice that the result of the search is different when you remove the parentheses of the first search key list.
+
+- The *searchCriteria* may include the optional \[CHARSET] specification. This consists of the "CHARSET" word followed by a registered \[CHARSET] (US ASCII, ISO-8859). It indicates the charset of the *searchCriteria* string. Therefore, you must convert the *searchCriteria* string into the specified charset if you use the \[CHARSET] specification (see the `CONVERT FROM TEXT` or `Convert to text` commands).
+By default, 4D encodes in Quotable Printable the searchCriteria string if it contains extended characters.
+
+```
+searchCriteria = CHARSET "ISO-8859" BODY "Help"
+```
+... means the search criteria uses the charset iso-8859 and the server will have to convert the search criteria before searching, if necessary.
+
+
+#### Search value types
+
+Search-keys may request the value to search for:
+
+- **Search-keys with a date value**: the date is a string that must be formatted as follows: *date-day+"-"+date-month+"-"+date-year* where date-day indicates the number of the day of the month (max. 2 characters), date-month indicates the name of the month (Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Dec) and date-year indicates the year (4 characters).
+Example: `searchCriteria = SENTBEFORE 1-Feb-2020` (a date does not usually need to be quoted since it does not contain any special characters)
+
+- **Search-keys with a string value**: the string may contain any character and must be quoted. If the string does not contain any special characters, like the space character for instance, it does not need to be quoted. Quoting such strings will ensure that your string value will be correctly interpreted.
+Example: `searchCriteria = FROM "SMITH"`
+For all search keys that use strings, a message matches the key if the string is a substring of the field. Matching is not case-sensitive.
+
+- **Search-keys with a field-name value**: the field-name is the name of a header field.
+Example: `searchCriteria = HEADER CONTENT-TYPE "MIXED"`
+
+- **Search-keys with a flag value**: the flag may accept one or several keywords (including standard flags), separated by spaces.
+Example: `searchCriteria = KEYWORD \Flagged \Draft`
+
+- **Search-keys with a message set value**: Identifies a set of messages. For message sequence numbers, these are consecutive numbers from 1 to the total number of messages in the mailbox. A comma delimits individual numbers; a colon delimits between two numbers inclusive.
+Examples:
+`2,4:7,9,12:*` is `2,4,5,6,7,9,12,13,14,15` for a mailbox with 15 messages.
+`searchCriteria = 1:5 ANSWERED` search in message selection from message sequence number 1 to 5 for messages which have the \Answered flag set.
+`searchCriteria= 2,4 ANSWERED` search in the message selection (message numbers 2 and 4) for messages which have the \Answered flag set.
+
+
+#### Authorized search-keys
+
+**ALL**: All messages in the mailbox.
+**ANSWERED**: Messages with the \Answered flag set.
+**UNANSWERED**: Messages that do not have the \Answered flag set.
+**DELETED**: Messages with the \Deleted flag set.
+**UNDELETED**: Messages that do not have the \Deleted flag set.
+**DRAFT**: Messages with the \Draft flag set.
+**UNDRAFT**: Messages that do not have the \Draft flag set.
+**FLAGGED**: Messages with the \Flagged flag set.
+**UNFLAGGED**: Messages that do not have the \Flagged flag set.
+**RECENT**: Messages that have the \Recent flag set.
+**OLD**: Messages that do not have the \Recent flag set.
+**SEEN**: Messages that have the \Seen flag set.
+**UNSEEN**: Messages that do not have the \Seen flag set.
+**NEW**: Messages that have the \Recent flag set but not the \Seen flag. This is functionally equivalent to “(RECENT UNSEEN)â€.
+**KEYWORD** : Messages with the specified keyword set.
+**UNKEYWORD** : Messages that do not have the specified keyword set.
+**BEFORE** : Messages whose internal date is earlier than the specified date.
+**ON** : Messages whose internal date is within the specified date.
+**SINCE** : Messages whose internal date is within or later than the specified date.
+**SENTBEFORE** : Messages whose Date header is earlier than the specified date.
+**SENTON** : Messages whose Date header is within the specified date.
+**SENTSINCE** : Messages whose Date header is within or later than the specified date.
+**TO** : Messages that contain the specified string in the TO header.
+**FROM** : Messages that contain the specified string in the FROM header.
+**CC** : Messages that contain the specified string in the CC header.
+**BCC** : Messages that contain the specified string in the BCC header.
+**SUBJECT** : Messages that contain the specified string in the Subject header.
+**BODY** : Messages that contain the specified string in the message body.
+**TEXT** : Messages that contain the specified string in the header or in the message body.
+**HEADER** : Messages that have a header with the specified field-name and that contain the specified string in the field-body.
+**UID** : Messages with unique identifiers corresponding to the specified unique identifier set.
+**LARGER** : Messages with a size larger than the specified number of bytes.
+**SMALLER** : Messages with a size smaller than the specified number of bytes.
+**NOT** : Messages that do not match the specified search key.
+**OR** : Messages that match either search key.
+
+
+
+
+
+
+## .selectBox()
+
+History
+|Version|Changes|
+|---|---|
+|v18 R4|Added|
+
+
+
+**.selectBox**( *name* : Text { ; *state* : Integer } ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|Text|-> |Name of the mailbox|
+|state|Integer|->|Mailbox access status|
+|Result|Object|<-|boxInfo object|
+
+
+
+#### Description
+
+The `.selectBox()` function selects the `name` mailbox as the current mailbox. This function allows you to retrieve information about the mailbox.
+
+>To get the information from a mailbox without changing the current mailbox, use [`.getBoxInfo()`](#getboxinfo).
+
+In the `name` parameter, pass the name of the mailbox to access. The name represents an unambiguous left-to-right hierarchy with levels separated by a specific delimiter character. The delimiter can be found with the [`.getDelimiter()`](#getdelimiter) function.
+
+The optional `state` parameter defines the type of access to the mailbox. The possible values are:
+
+|Constant| Value| Comment|
+|---|---|---|
+|IMAP read only state|1|The selected mailbox is accessed with read only privileges. Messages with a "recent" flag (indicating new messages) remain unchanged.|
+|IMAP read write state|0|The selected mailbox is accessed with read and write privileges. Messages are considered "seen" and lose the "recent" flag (indicating new messages). (Default value)|
+
+
+>* The function generates an error and returns **Null** if name designates a non-existing mailbox.
+
+>* If there is no open connection, `.selectBox()` will open a connection.
+>* If the connection has not been used since the designated connection delay (see `IMAP New transporter`), the [`.checkConnection()`](#checkconnection) function is automatically called.
+
+**Returned object**
+
+The `boxInfo` object returned contains the following properties:
+
+|Property | Type | Description |
+|---|---|---|
+|name| Text|Name of the mailbox|
+|mailCount|number|Number of messages in the mailbox|
+|mailRecent|number|Number of messages with the "recent" flag |
+
+
+#### Example
+
+
+```4d
+ var $server; $boxinfo : Object
+ $server:=New object
+ $server.host:="imap.gmail.com" //Mandatory
+ $server.user:="4d@gmail.com"
+ $server.password:="XXXXXXXX"
+
+ var $transporter : 4D.IMAPTransporter
+ $transporter:=IMAP New transporter($server)
+ $boxInfo:=$transporter.selectBox("INBOX")
+```
+
+
+
+
+
+
+## .subscribe()
+
+History
+|Version|Changes|
+|---|---|
+|v19|Added|
+
+
+
+**.subscribe**( *name* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|Text|-> |Name of the mailbox|
+|Result|Object|<-|Status of the subscribe operation|
+
+
+
+#### Description
+
+The `.subscribe()` function allows adding or removing of the specified mailbox to/from the IMAP server’s set of “subscribed†mailboxes. As such, you can choose to narrow down a large list of available mailboxes by subscribing to those you usually want to see.
+
+In the `name` parameter, pass the name of the mailbox to add (subscribe) to your "subscribed" mailboxes.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+
+#### Example
+
+To subscribe to the "Atlas Corp†mailbox in the "Bills" hierarchy:
+
+```4d
+var $pw; $name : text
+var $options; $transporter; $status : object
+
+$options:=New object
+
+$pw:=Request("Please enter your password:")
+
+If(OK=1) $options.host:="imap.gmail.com"
+$options.user:="test@gmail.com"
+$options.password:=$pw
+
+$transporter:=IMAP New transporter($options)
+
+$name:="Bills"+$transporter.getDelimiter()+"Atlas Corp"
+$status:=$transporter.subscribe($name)
+
+If ($status.success)
+ ALERT("Mailbox subscription successful!")
+ Else
+ ALERT("Error: "+$status.statusText)
+ End if
+End if
+```
+
+
+
+
+
+## .unsubscribe()
+
+History
+|Version|Changes|
+|---|---|
+|v19|Added|
+
+
+
+**.unsubscribe**( *name* : Text ) : Object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|Text|-> |Name of the mailbox|
+|Result|Object|<-|Status of the unsubscribe operation|
+
+
+
+#### Description
+
+The `.unsubscribe()` function removes a mailbox from a set of subscribed mailboxes. This allows you reduce the number of mailboxes you usually see.
+
+In the `name` parameter, pass the name of the mailbox to remove (unsubscribe) from your active mailboxes.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||Boolean|True if the operation is successful, False otherwise
+|statusText || Text|Status message returned by the IMAP server, or last error returned in the 4D error stack |
+|errors ||Collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| 4D error code|
+| |\[].message|Text|Description of the 4D error |
+| |\[].componentSignature|Text|Signature of the internal component which returned the error|
+
+
+
+#### Example
+
+To unsubscribe from the "Atlas Corp†mailbox in the "Bills" hierarchy:
+
+```4d
+var $pw; $name : text
+var $options; $transporter; $status : object
+
+$options:=New object
+
+$pw:=Request("Please enter your password:")
+
+If(OK=1) $options.host:="imap.gmail.com"
+$options.user:="test@gmail.com"
+$options.password:=$pw
+
+$transporter:=IMAP New transporter($options)
+
+$name:="Bills"+$transporter.getDelimiter()+"Atlas Corp"
+$status:=$transporter.unsubscribe($name)
+
+If ($status.success)
+ ALERT("Mailbox unsubscription successful!")
+ Else
+ ALERT("Error: "+$status.statusText)
+ End if
+End if
+```
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/API/MailAttachmentClass.md b/docs/API/MailAttachmentClass.md
new file mode 100644
index 00000000000000..bd50975666f870
--- /dev/null
+++ b/docs/API/MailAttachmentClass.md
@@ -0,0 +1,295 @@
+---
+id: MailAttachmentClass
+title: MailAttachment
+---
+
+Attachment objects allow referencing files within a [`Email`](EmailObjectClass.md) object. Attachment objects are created using the [`MAIL New attachment`](#mail-new-attachment) command.
+
+
+### Attachment Object
+
+Attachment objects provide the following read-only properties and functions:
+
+
+||
+|---|
+|[](#cid) |
+|[](#disposition)
|
+|[](#getcontent)
|
+|[](#name)
|
+|[](#path)
|
+|[](#platformpath)
|
+|[](#type)
|
+
+
+## MAIL New attachment
+
+History
+|Version|Changes|
+|---|---|
+|v19 R2|Accepts 4D.File, 4D.ZipFile, 4D.Blob
+
+
+
+**MAIL New attachment**( *file* : 4D.File { ; *name* : Text {; *cid* : Text{ ; *type* : Text { ; *disposition* :Text } } } } ) : 4D.MailAttachment
**MAIL New attachment**( *zipFile* : 4D.ZipFile { ; *name* : Text {; *cid* : Text{ ; *type* : Text { ; *disposition* :Text } } } } ) : 4D.MailAttachment
**MAIL New attachment**( *blob* : 4D.Blob { ; *name* : Text {; *cid* : Text{ ; *type* : Text { ; *disposition* :Text } } } } ) : 4D.MailAttachment
**MAIL New attachment**( *path* : Text { ; *name* : Text {; *cid* : Text{ ; *type* : Text { ; *disposition* :Text } } } } ) : 4D.MailAttachment
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|file|4D.File|->|Attachment file|
+|zipFile|4D.ZipFile|->|Attachment Zipfile|
+|blob|4D.Blob|->|BLOB containing the attachment|
+|path|Text|->|Path of the attachment file|
+|name|Text|->|Name + extension used by the mail client to designate the attachment|
+|cid|Text|->|ID of attachment (HTML messages only), or " " if no cid is required|
+|type|Text|->|Value of the content-type header|
+|disposition|Text|->|Value of the content-disposition header: "inline" or "attachment".|
+|Result|4D.MailAttachment|<-|Attachment object|
+
+
+
+#### Description
+
+The `MAIL New attachment` command allows you to create an attachment object that you can add to an [Email object](EmailObjectClass.md#email-object).
+
+To define the attachment, you can use:
+
+- a *file*, pass a `4D.File` object containing the attachment file.
+- a *zipfile*, pass a `4D.ZipFile` object containing the attachment file.
+- a *blob*, pass a `4D.Blob` object containing the attachment itself.
+- a *path*, pass a **text** value containing the path of the attachment file, expressed with the system syntax. You can pass a full path name or a simple file name (in which case 4D will search for the file in the same directory as the project file).
+
+The optional *name* parameter lets you pass the name and extension to be used by the mail client to designate the attachment. If *name* is omitted and:
+
+* you passed a file path, the name and extension of the file is used,
+* you passed a BLOB, a random name without extension is automatically generated.
+
+The optional *cid* parameter lets you pass an internal ID for the attachment. This ID is the value of the `Content-Id` header, it will be used in HTML messages only. The cid associates the attachment with a reference defined in the message body using an HTML tag such as `\
`. This means that the contents of the attachment (e.g., a picture) should be displayed within the message on the mail client. The final result may vary depending on the mail client. You can pass an empty string in *cid* if you do not want to use this parameter.
+
+You can use the optional *type* parameter to explicitly set the `content-type` of the attachment file. For example, you can pass a string defining a MIME type ("video/mpeg"). This content-type value will be set for the attachment, regardless of its extension. For more information about MIME types, please refer to the [MIME type page on Wikipedia](https://en.wikipedia.org/wiki/MIME).
+
+By default, if the *type* parameter is omitted or contains an empty string, the `content-type` of the attachment file is based on its extension. The following rules are applied for the main MIME types:
+
+|Extension| Content Type|
+|---|---|
+|jpg, jpeg| image/jpeg|
+|png| image/png|
+|gif| image/gif|
+|pdf| application/pdf|
+|doc| application/msword|
+|xls| application/vnd.ms-excel|
+|ppt| application/vnd.ms-powerpoint|
+|zip| application/zip|
+|gz| application/gzip|
+|json| application/json|
+|js| application/javascript|
+|ps| application/postscript|
+|xml| application/xml|
+|htm, html| text/html|
+|mp3| audio/mpeg|
+|*other*| application/octet-stream|
+
+The optional *disposition* parameter lets you pass the `content-disposition` header of the attachment. You can pass one of the following constants from the "Mail" constant theme:
+
+|Constant|Value|Comment|
+|---|---|---|
+|mail disposition attachment|"attachment"|Set the Content-disposition header value to "attachment", which means that the attachment file must be provided as a link in the message.|
+|mail disposition inline|"inline"|Set the Content-disposition header value to "inline", which means that the attachment must be rendered within the message contents, at the "cid" location. The rendering depends on the mail client.|
+
+By default, if the *disposition* parameter is omitted:
+
+* if the *cid* parameter is used, the `Content-disposition` header is set to "inline",
+* if the *cid* parameter is not passed or empty, the `Content-disposition` header is set to "attachment".
+
+#### Example 1
+
+You want to send an email with a user-selected file as an attachment and an image embedded in the HTML body:
+
+```4d
+$doc:=Select document("";"*";"Please select a file to attach";0)
+If (OK=1) //If a document was selected
+
+C_OBJECT($email;$server;$transporter)
+
+$server:=New object
+$server.host:="smtp.mail.com"
+$server.user:="test_user@mail.com"
+$server.password:="p@ssw@rd"
+$transporter:=SMTP New transporter($server)
+
+$email:=New object
+$email.from:="test_user@mail.com"
+$email.to:="test_user@mail.com"
+$email.subject:="This is a test message with attachments"
+
+//add a link to download file
+$email.attachments:=New collection(MAIL New attachment(Document))
+//insert an inline picture (use a cid)
+$email.attachments[1]:=MAIL New attachment("c:\\Pictures\\4D.jpg";"";"4D")
+
+$email.htmlBody:=""+\
+"
Hello World!"+\
+"
"+\
+""+\
+""+\
+"