From 24083813135094bf89c5388bb3bad9e694d51df6 Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Wed, 18 Mar 2026 19:05:17 +0100 Subject: [PATCH 01/11] first edits --- docs/Concepts/classes.md | 183 ++++++++++++++++++++++++++++++++++----- docs/ORDA/ordaClasses.md | 82 ------------------ 2 files changed, 161 insertions(+), 104 deletions(-) diff --git a/docs/Concepts/classes.md b/docs/Concepts/classes.md index 8aa04d329703d9..b86173b3eed46a 100644 --- a/docs/Concepts/classes.md +++ b/docs/Concepts/classes.md @@ -40,7 +40,7 @@ $hello:=$person.sayHello() //"Hello John Doe" ## Managing classes -### Class definition +### Architecture A user class in 4D is defined by a specific [method](methods.md) file (.4dm), stored in the `/Project/Sources/Classes/` folder. The name of the file is the class name. @@ -60,14 +60,6 @@ Project folder Polygon.4dm ``` - -### Deleting a class - -To delete an existing class, you can: - -- on your disk, remove the .4dm class file from the "Classes" folder, -- in the 4D Explorer, select the class and click ![](../assets/en/Users/MinussNew.png) or choose **Move to Trash** from the contextual menu. - ### Using the 4D interface Class files are automatically stored at the appropriate location when created through the 4D interface, either via the **File** menu or the Explorer. @@ -100,6 +92,13 @@ In the various 4D windows (code editor, compiler, debugger, runtime explorer), c - **Search references** on class function declaration searches for the function used as object member; for example, "Function f" will find "$o.f()". - In the Runtime explorer and Debugger, class functions are displayed with the `` constructor or `.` format. +#### Deleting a class + +To delete an existing class, select it in the Explorer and click ![](../assets/en/Users/MinussNew.png) or choose **Move to Trash** from the contextual menu. + +You can also remove the .4dm class file from the "Classes" folder on your disk. + + ## Class stores Available classes are accessible from their class stores. Two class stores are available: @@ -108,7 +107,7 @@ Available classes are accessible from their class stores. Two class stores are a - [`4D`](../commands/4d) for built-in class store -### `cs` +#### `cs` **cs** : Object @@ -133,7 +132,7 @@ You want to create a new instance of an object of `myClass`: $instance:=cs.myClass.new() ``` -### `4D` +#### `4D` **4D** : Object @@ -159,7 +158,7 @@ $key:=4D.CryptoKey.new(New object("type";"ECDSA";"curve";"prime256v1")) You want to list 4D built-in classes: ```4d - var $keys : collection + var $keys : Collection $keys:=OB Keys(4D) ALERT("There are "+String($keys.length)+" built-in classes.") ``` @@ -203,7 +202,7 @@ Specific 4D keywords can be used in class definitions: #### Syntax ```4d -{shared} Function ({$parameterName : type; ...}){->$parameterName : type} +{local | server} {shared} Function ({$parameterName : type; ...}){->$parameterName : type} // code ``` @@ -215,6 +214,8 @@ There is no ending keyword for function code. The 4D language automatically dete Class functions are specific properties of the class. They are objects of the [4D.Function](API/FunctionClass.md) class. In the class definition file, function declarations use the `Function` keyword followed by the function name. +In the context of a client/server application, the `local` or `server` keyword allows you to specify on which machine the function must be executed. These keywords can only be used with ORDA data model functions and shared/session singleton functions. For more information, refer to the [local/server functions](#local-server-functions) paragraph below. + If the function is declared in a [shared class](#shared-classes), you can use the `shared` keyword so that the function could be called without [`Use...End use` structure](shared.md#useend-use). For more information, refer to the [Shared functions](#shared-functions) paragraph below. The function name must be compliant with [object naming rules](Concepts/identifiers.md#object-properties). @@ -691,13 +692,6 @@ $val:=$o.f() //8 For more details, see the [`This`](../commands/this) command description. - - -## Class commands - - -Several commands of the 4D language allows you to handle class features. - ### `OB Class` #### `OB Class ( object ) -> Object | Null` @@ -926,7 +920,152 @@ $myList := cs.ItemInventory.me.itemList -#### See also +:::tip Related blog posts + +[Singletons in 4D](https://blog.4d.com/singletons-in-4d) +[Session Singletons](https://blog.4d.com/introducing-session-singletons) + +::: + + + +## `local` and `server` + +In [client/server architecture](../Desktop/clientServer.md), `local` and `server` keywords allow you to specify where you want the function to be executed: client-side, or server-side. It is useful for performance reasons or to implement business logic features. + +The formal syntax is: + +```4d +// declare a function to execute on a client in client/server +local Function +``` +```4d +// declare a function to execute on the server in client/server +server Function +``` + + +### Contexts + +`local` and `server` keywords are only available with: + +- [ORDA data model functions](../ORDA/ordaClasses.md) +- [shared or session singleton functions](#singleton-classes) + +If `local` and `server` keywords are used in another context, they are ignored and an error is returned by the compiler. + + +You can add the keyword to modify the execution location for a function, or to make the code more explicit. + +|Functions|Default execution if no keyword|Execution keywords| +|---|---|---| +|[ORDA data model functions](../ORDA/ordaClasses.md)|Server|`server` (default), `local`| +|[Shared or session singleton functions](#singleton-classes)|Client|`server`, `local` (default)| + + + + +:::note + +For a detailed description of where code is actually executed in client/server, please refer to [this page]. + +:::: + +### `local` + +The `local` keyword specifies that the function must be executed **on the client side**. -[Singletons in 4D](https://blog.4d.com/singletons-in-4d) (blog post)
[Session Singletons](https://blog.4d.com/introducing-session-singletons) (blog post). +#### Shared or session singleton functions +The `local` keyword is useless for [shared or session singleton functions](#singleton-classes), which are executed on the client by default (but can be used for clarity). + +#### ORDA data model functions + +By default, [ORDA data model functions](../ORDA/ordaClasses.md) are executed on the server. It usually provides the best performance since only the function request and the result are sent over the network. + +However, it could happen that a function is fully executable on the client side (e.g., when it processes data that's already in the local cache). In this case, you can save requests to the server and thus, enhance the application performance by using the `local` keyword. + +Note that the function will work even if it eventually requires to access the server (for example if the ORDA cache is expired). However, it is highly recommended to make sure that the local function does not access data on the server, otherwise the local execution could not bring any performance benefit. A local function that generates many requests to the server is less efficient than a function executed on the server that would only return the resulting values. For example, consider the following function on the Schools entity class: + +```4d +// Get the youngest students +// Inappropriate use of local keyword +local Function getYoungest + var $0 : Object + $0:=This.students.query("birthDate >= :1"; !2000-01-01!).orderBy("birthDate desc").slice(0; 5) +``` +- **without** the `local` keyword, the result is given using a single request +- **with** the `local` keyword, 4 requests are necessary: one to get the Schools entity students, one for the `query()`, one for the `orderBy()`, and one for the `slice()`. In this example, using the `local` keyword is inappropriate. + + +### `server` + +The `server` keyword specifies that the function must be executed **on the server side**. + +#### Singleton functions + +By default, shared or session singleton functions are executed on the client. + +In the context of [remote user sessions](../Desktop/sessions.md#remote-user-sessions), you can implement the business logic in a [session singleton](#shared-or-session-singleton-functions) to share it accross all the processes of the session, thus extending the functionalities of the [`Session`](../commands/session) command. In this case, you might want the relevant business logic to be executed **on the server** so that all the session information is gathered on the server. + + +#### ORDA data model functions + +The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClasses.md), which are executed on the server by default (but can be used for clarity). + + + +### Examples + +#### Calculating age + +Given an entity with a *birthDate* attribute, we want to define an `age()` function that would be called in a list box. This function can be executed on the client, which avoids triggering a request to the server for each line of the list box. + +On the *StudentsEntity* class: + +```4d +Class extends Entity + +local Function age() -> $age: Variant + +If (This.birthDate#!00-00-00!) + $age:=Year of(Current date)-Year of(This.birthDate) +Else + $age:=Null +End if +``` + +#### Checking attributes + +We want to check the consistency of the attributes of an entity loaded on the client and updated by the user before requesting the server to save them. + +On the *StudentsEntity* class, the local `checkData()` function checks the Student's age: + +```4d +Class extends Entity + +local Function checkData() -> $status : Object + +$status:=New object("success"; True) +Case of + : (This.age()=Null) + $status.success:=False + $status.statusText:="The birthdate is missing" + + :((This.age() <15) | (This.age()>30) ) + $status.success:=False + $status.statusText:="The student must be between 15 and 30 - This one is "+String(This.age()) +End case +``` + +Calling code: + +```4d +var $status : Object + +//Form.student is loaded with all its attributes and updated on a Form +$status:=Form.student.checkData() +If ($status.success) + $status:=Form.student.save() // call the server +End if +``` diff --git a/docs/ORDA/ordaClasses.md b/docs/ORDA/ordaClasses.md index 4abb6bc20a4016..5d462357ee94a1 100644 --- a/docs/ORDA/ordaClasses.md +++ b/docs/ORDA/ordaClasses.md @@ -1132,88 +1132,6 @@ IP:port/rest/Products/getThumbnail?$params='["Yellow Pack",200,200]' ``` -## Local functions - -By default in client/server architecture, ORDA data model functions are executed **on the server**. It usually provides the best performance since only the function request and the result are sent over the network. - -However, it could happen that a function is fully executable on the client side (e.g., when it processes data that's already in the local cache). In this case, you can save requests to the server and thus, enhance the application performance by inserting the `local` keyword. The formal syntax is: - -```4d -// declare a function to execute locally in client/server -local Function -``` - -With this keyword, the function will always be executed on the client side. - -> The `local` keyword can only be used with data model class functions. If used with a [regular user class](Concepts/classes.md) function, it is ignored and an error is returned by the compiler. - -Note that the function will work even if it eventually requires to access the server (for example if the ORDA cache is expired). However, it is highly recommended to make sure that the local function does not access data on the server, otherwise the local execution could not bring any performance benefit. A local function that generates many requests to the server is less efficient than a function executed on the server that would only return the resulting values. For example, consider the following function on the Schools entity class: - -```4d -// Get the youngest students -// Inappropriate use of local keyword -local Function getYoungest - var $0 : Object - $0:=This.students.query("birthDate >= :1"; !2000-01-01!).orderBy("birthDate desc").slice(0; 5) -``` -- **without** the `local` keyword, the result is given using a single request -- **with** the `local` keyword, 4 requests are necessary: one to get the Schools entity students, one for the `query()`, one for the `orderBy()`, and one for the `slice()`. In this example, using the `local` keyword is inappropriate. - - -### Examples - -#### Calculating age - -Given an entity with a *birthDate* attribute, we want to define an `age()` function that would be called in a list box. This function can be executed on the client, which avoids triggering a request to the server for each line of the list box. - -On the *StudentsEntity* class: - -```4d -Class extends Entity - -local Function age() -> $age: Variant - -If (This.birthDate#!00-00-00!) - $age:=Year of(Current date)-Year of(This.birthDate) -Else - $age:=Null -End if -``` - -#### Checking attributes - -We want to check the consistency of the attributes of an entity loaded on the client and updated by the user before requesting the server to save them. - -On the *StudentsEntity* class, the local `checkData()` function checks the Student's age: - -```4d -Class extends Entity - -local Function checkData() -> $status : Object - -$status:=New object("success"; True) -Case of - : (This.age()=Null) - $status.success:=False - $status.statusText:="The birthdate is missing" - - :((This.age() <15) | (This.age()>30) ) - $status.success:=False - $status.statusText:="The student must be between 15 and 30 - This one is "+String(This.age()) -End case -``` - -Calling code: - -```4d -var $status : Object - -//Form.student is loaded with all its attributes and updated on a Form -$status:=Form.student.checkData() -If ($status.success) - $status:=Form.student.save() // call the server -End if -``` From b72d4c873a1dec4b39bda9b582b2eeb37289c02c Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Fri, 20 Mar 2026 14:27:05 +0100 Subject: [PATCH 02/11] edited class page, added propeties to some classes --- docs/API/FileHandleClass.md | 7 ++- docs/API/WebServerClass.md | 7 ++- docs/Concepts/classes.md | 110 ++++++++++++++++++++++++++++-------- 3 files changed, 96 insertions(+), 28 deletions(-) diff --git a/docs/API/FileHandleClass.md b/docs/API/FileHandleClass.md index f66157df84c0be..c59394ad1782fa 100644 --- a/docs/API/FileHandleClass.md +++ b/docs/API/FileHandleClass.md @@ -17,7 +17,6 @@ Object resources, such as documents, are released when no more references exist ::: - ### Example ```code4d @@ -57,6 +56,12 @@ End while ``` +### Properties + +- **Streamable**: no +- **Sharable**: no + + ### FileHandle object File handle objects cannot be shared. diff --git a/docs/API/WebServerClass.md b/docs/API/WebServerClass.md index 785b2366d46eb4..62c22e5283db3d 100644 --- a/docs/API/WebServerClass.md +++ b/docs/API/WebServerClass.md @@ -6,14 +6,17 @@ title: WebServer The `WebServer` class API allows you to start and monitor a web server for the main (host) application as well as each hosted component (see the [Web Server object](WebServer/webServerObject.md) overview). This class is available from the `4D` class store. +### Properties + +- **Streamable**: no +- **Sharable**: no + ### Web Server object Web server objects are instantiated with the [`WEB Server`](../commands/web-server) command. They provide the following properties and functions: -### Summary - || |---| |[](#accesskeydefined)
| diff --git a/docs/Concepts/classes.md b/docs/Concepts/classes.md index b86173b3eed46a..a5e8ad0914ace7 100644 --- a/docs/Concepts/classes.md +++ b/docs/Concepts/classes.md @@ -214,10 +214,11 @@ There is no ending keyword for function code. The 4D language automatically dete Class functions are specific properties of the class. They are objects of the [4D.Function](API/FunctionClass.md) class. In the class definition file, function declarations use the `Function` keyword followed by the function name. -In the context of a client/server application, the `local` or `server` keyword allows you to specify on which machine the function must be executed. These keywords can only be used with ORDA data model functions and shared/session singleton functions. For more information, refer to the [local/server functions](#local-server-functions) paragraph below. - If the function is declared in a [shared class](#shared-classes), you can use the `shared` keyword so that the function could be called without [`Use...End use` structure](shared.md#useend-use). For more information, refer to the [Shared functions](#shared-functions) paragraph below. +In the context of a client/server application, the `local` or `server` keyword allows you to specify on which machine the function must be executed. These keywords can only be used with ORDA data model functions and shared/session singleton functions. For more information, refer to the [local and server functions](#local-and-server) paragraph below. + + The function name must be compliant with [object naming rules](Concepts/identifiers.md#object-properties). :::note @@ -525,12 +526,12 @@ $o.age:="Smith" //error with check syntax #### Syntax ```4d -{shared} Function get ()->$result : type +{local | server} {shared} Function get ()->$result : type // code ``` ```4d -{shared} Function set ($parameterName : type) +{local | server} {shared} Function set ($parameterName : type) // code ``` @@ -557,6 +558,9 @@ When both functions are defined, the computed property is **read-write**. If onl If the functions are declared in a [shared class](#shared-classes), you can use the `shared` keyword with them so that they could be called without [`Use...End use` structure](shared.md#useend-use). For more information, refer to the [Shared functions](#shared-functions) paragraph below. +In the context of a client/server application, the `local` or `server` keyword allows you to specify on which machine the function must be executed. These keywords can only be used with ORDA data model functions and shared/session singleton functions. For more information, refer to the [local and server functions](#local-and-server) paragraph below. + + The type of the computed property is defined by the `$return` type declaration of the *getter*. It can be of any [valid property type](dt_object.md). > Assigning *undefined* to an object property clears its value while preserving its type. In order to do that, the `Function get` is first called to retrieve the value type, then the `Function set` is called with an empty value of that type. @@ -931,7 +935,7 @@ $myList := cs.ItemInventory.me.itemList ## `local` and `server` -In [client/server architecture](../Desktop/clientServer.md), `local` and `server` keywords allow you to specify where you want the function to be executed: client-side, or server-side. It is useful for performance reasons or to implement business logic features. +In [client/server architecture](../Desktop/clientServer.md), `local` and `server` keywords allow you to specify where you want the function to be executed: client-side, or server-side. Controlling the execution location is useful for performance reasons or to implement business logic features. The formal syntax is: @@ -944,30 +948,23 @@ local Function server Function ``` +Note that [supported functions](#supported-functions) have a **default execution location** (server or client) when no location keyword is used. You can nevertheless insert a `local` or `server` keyword to modify the execution location, or to make the code more explicit. -### Contexts +### Supported functions -`local` and `server` keywords are only available with: +`local` and `server` keywords are only available with the following functions: -- [ORDA data model functions](../ORDA/ordaClasses.md) -- [shared or session singleton functions](#singleton-classes) +|Functions|Default execution location| +|---|---| +|[ORDA data model functions](../ORDA/ordaClasses.md)|Server| +|[Shared or session singleton functions](#singleton-classes)|Client| If `local` and `server` keywords are used in another context, they are ignored and an error is returned by the compiler. - - -You can add the keyword to modify the execution location for a function, or to make the code more explicit. - -|Functions|Default execution if no keyword|Execution keywords| -|---|---|---| -|[ORDA data model functions](../ORDA/ordaClasses.md)|Server|`server` (default), `local`| -|[Shared or session singleton functions](#singleton-classes)|Client|`server`, `local` (default)| - - :::note -For a detailed description of where code is actually executed in client/server, please refer to [this page]. +For a overall description of where code is actually executed in client/server, please refer to [this page]. :::: @@ -1000,13 +997,29 @@ local Function getYoungest ### `server` -The `server` keyword specifies that the function must be executed **on the server side**. +The `server` keyword specifies that the function must be executed **on the server side**. + +`server` function parameters and result must be **streamable**, that is, they can be saved into a file or sent over a network. For example, [ORDA Data model](../ORDA/ordaClasses.md), [File handle](../API/FileHandleClass.md), or [WebServer](../API/WebServerClass.md) are non-streamable classes. + +This feature is particularly useful in the context of [remote user sessions](../Desktop/sessions.md#remote-user-sessions), allowing you to implement the business logic in a [session singleton](#shared-or-session-singleton-functions) to share it accross all the processes of the session, thus extending the functionalities of the [`Session`](../commands/session) command. In this case, you might want the relevant business logic to be executed **on the server** so that all the session information is gathered on the server. + + +#### Shared or session singleton functions + +By default, shared or session singleton functions are executed on the client. Adding the `server` keyword in the class function definition makes 4D use the singleton instance on the server. Note that this can result of an instantiation of the singleton on the server if no instance exists yet. + +For [sessions singletons](#singleton-classes), the function is executed on the server on the corresponding singleton instance, i.e. the instance of the singleton for the current session. + +Note that when you declare a `server Function` in a shared singleton and: + +- instanciate singleton *S1* on the client (named *s1*) +- run *s1.function()* on the client + +Since there is no instance of *S1* on the server at this moment, *S1* is instanciated on the server (the constructor is executed) and the *function()* is run on this server instance. As a consequence, you can have two instances of *S1* (client-side and server-side) with distincts property values. However in this case, *s1.property* is always accessed locally. It cannot be accessed on the server, for example in code running on the server,with a direct access using dot notation (an error is returned). + -#### Singleton functions -By default, shared or session singleton functions are executed on the client. -In the context of [remote user sessions](../Desktop/sessions.md#remote-user-sessions), you can implement the business logic in a [session singleton](#shared-or-session-singleton-functions) to share it accross all the processes of the session, thus extending the functionalities of the [`Session`](../commands/session) command. In this case, you might want the relevant business logic to be executed **on the server** so that all the session information is gathered on the server. #### ORDA data model functions @@ -1015,7 +1028,7 @@ The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClas -### Examples +### Examples (ORDA data model functions) #### Calculating age @@ -1069,3 +1082,50 @@ If ($status.success) $status:=Form.student.save() // call the server End if ``` + +### Example (Shared or session singleton functions) + +In a client/server application, the *Authentication* session singleton is used to handle user session data: + +```4d +// Authentication Class + +property _salesPeopleId : Integer + +session singleton Class constructor() + +If (This._salesPeopleId=Null) + This.initSalesPeopleId() +End if + +// Executed on the client (default location) +shared Function initSalesPeopleId() + This._salesPeopleId:=ds.getSalesPeopleId() // Call to the server + +// Executed on the client +Function get salesPeople() : cs.SalesPeopleEntity + return ds.SalesPeople.get(This._salesPeopleId) + + +// Functions to be executed on the server + +server Function get sessionStorage() : Object + return Session.storage.clientData + +server Function putInSessionStorage($content : Object) + var $prop : Text + // $content is like: {"searchCritreria":{min: 10; max: 20}} + Use (Session.storage.clientData) + For each ($prop; $content) + Session.storage.clientData[$prop]:=OB Copy($content[$prop]; ck shared) + End for each + End use + +server Function clearSession() + Session.clearPrivileges() + Use (Session.storage) + Session.storage.salesInfo:=New shared object() + Session.storage.clientData:=New shared object() + End use + +``` \ No newline at end of file From 20107031ebac0fe4ad71c2e5e7bb3e9119be301a Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Fri, 20 Mar 2026 14:50:23 +0100 Subject: [PATCH 03/11] added links from ORDA section --- docs/ORDA/ordaClasses.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/ORDA/ordaClasses.md b/docs/ORDA/ordaClasses.md index 5d462357ee94a1..1a54bd94a19549 100644 --- a/docs/ORDA/ordaClasses.md +++ b/docs/ORDA/ordaClasses.md @@ -66,6 +66,7 @@ Also, object instances from ORDA data model user classes benefit from their pare |Release|Changes| |---|---| +|21 R3|Support for the `server` keyword. |19 R4|Alias attributes in the Entity Class |19 R3|Computed attributes in the Entity Class |18 R5|Data model class functions are not exposed to REST by default. New `exposed` and `local` keywords. @@ -303,7 +304,7 @@ When creating or editing data model classes, you must pay attention to the follo When compiled, data model class functions are executed: - in **preemptive or cooperative processes** (depending on the calling process) in single-user applications, -- in **preemptive processes** in client/server applications (except if the [`local`](#local-functions) keyword is used, in which case it depends on the calling process like in single-user). +- in **preemptive processes** in client/server applications (except if the [`local`](../Concepts/classes.md#local) keyword is used, in which case it depends on the calling process like in single-user). If your project is designed to run in client/server, make sure your data model class function code is thread-safe. If thread-unsafe code is called, an error will be thrown at runtime (no error will be thrown at compilation time since cooperative execution is supported in single-user applications). @@ -369,9 +370,7 @@ The `Class constructor` function is triggered by the following commands and feat #### Remote configurations -When using a remote configurations, you need to pay attention to the following principles: - -- In **client/server** the function can be called on the client or on the server, depending on the location of the calling code. When it is called on the client, it is not triggered again when the client attempts to save the new entity and sends an update request to the server to create in memory on the server. +When using a remote configurations, you need to pay attention to the following principle: in **client/server** the function can be called on the client or on the server, depending on the location of the calling code. When it is called on the client, it is not triggered again when the client attempts to save the new entity and sends an update request to the server to create in memory on the server. :::warning @@ -499,7 +498,7 @@ Within computed attribute functions, [`This`](Concepts/classes.md#this) designat > ORDA computed attributes are not [**exposed**](#exposed-vs-non-exposed-functions) by default. You expose a computed attribute by adding the `exposed` keyword to the **get function** definition. -> **get and set functions** can have the [**local**](#local-functions) property to optimize client/server processing. +> **get and set functions** can have the [`local`](../Concepts/classes.md#local) property to optimize client/server processing. ### `Function get ` @@ -507,7 +506,7 @@ Within computed attribute functions, [`This`](Concepts/classes.md#this) designat #### Syntax ```4d -{local} {exposed} Function get ({$event : Object}) -> $result : type +{local | server} {exposed} Function get ({$event : Object}) -> $result : type // code ``` The *getter* function is mandatory to declare the *attributeName* computed attribute. Whenever the *attributeName* is accessed, 4D evaluates the `Function get` code and returns the *$result* value. @@ -532,6 +531,12 @@ The *$event* parameter contains the following properties: |kind|Text|"get"| |result|Variant|Optional. Add this property with Null value if you want a scalar attribute to return Null| +:::note + +For more information about the `local` and `server` keywords, please refer to the [local and server](../Concepts/classes.md#local-and-server) section. + +::: + #### Examples @@ -578,7 +583,7 @@ Function get coWorkers($event : Object)-> $result: cs.EmployeeSelection ```4d -{local} Function set ($value : type {; $event : Object}) +{local | server} Function set ($value : type {; $event : Object}) // code ``` @@ -595,6 +600,12 @@ The *$event* parameter contains the following properties: |kind|Text|"set"| |value|Variant|Value to be handled by the computed attribute| +:::note + +For more information about the `local` and `server` keywords, please refer to the [local and server](../Concepts/classes.md#local-and-server) section. + +::: + #### Example ```4d From 72d7466c8563e7ae709ae8314859f1ebdb5772ab Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Fri, 20 Mar 2026 16:58:29 +0100 Subject: [PATCH 04/11] after MS proofreading --- docs/Concepts/classes.md | 74 +++++++++++++++++++++++++++--------- docs/Desktop/clientServer.md | 22 +++++++++++ 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/docs/Concepts/classes.md b/docs/Concepts/classes.md index a5e8ad0914ace7..b2beb7ae281463 100644 --- a/docs/Concepts/classes.md +++ b/docs/Concepts/classes.md @@ -948,23 +948,24 @@ local Function server Function ``` -Note that [supported functions](#supported-functions) have a **default execution location** (server or client) when no location keyword is used. You can nevertheless insert a `local` or `server` keyword to modify the execution location, or to make the code more explicit. +`local` and `server` keywords are only available for the functions of the [ORDA data model](../ORDA/ordaClasses.md) and [Shared or session singleton](#singleton-classes) classes. -### Supported functions -`local` and `server` keywords are only available with the following functions: +### Overview -|Functions|Default execution location| -|---|---| -|[ORDA data model functions](../ORDA/ordaClasses.md)|Server| -|[Shared or session singleton functions](#singleton-classes)|Client| +Supported functions have a **default execution location** when no location keyword is used. You can nevertheless insert a `local` or `server` keyword to modify the execution location, or to make the code more explicit. + +|Supported functions|Executed by default on|`local` keyword|`server` keyword| +|---|---|---|---| +|[ORDA data model](../ORDA/ordaClasses.md)|Server|*Supported*
The function is executed on the client|Allowed to make the code more explicit but no impact| +|[Shared or session singleton](#singleton-classes)|Client|Allowed to make the code more explicit but no impact|*Supported*
The function is executed on the server on the instance of the singleton on the server.
If there is no instance of the singleton on the server, it is created. | If `local` and `server` keywords are used in another context, they are ignored and an error is returned by the compiler. :::note -For a overall description of where code is actually executed in client/server, please refer to [this page]. +For a overall description of where code is actually executed in client/server, please refer to [this section](../Desktop/clientServer.md#code-execution-location). :::: @@ -972,15 +973,15 @@ For a overall description of where code is actually executed in client/server, p The `local` keyword specifies that the function must be executed **on the client side**. -#### Shared or session singleton functions +:::note Reminder -The `local` keyword is useless for [shared or session singleton functions](#singleton-classes), which are executed on the client by default (but can be used for clarity). +The `local` keyword is useless for [shared or session singleton functions](#singleton-classes), which are executed on the client by default, but can be added for clarity. -#### ORDA data model functions +::: By default, [ORDA data model functions](../ORDA/ordaClasses.md) are executed on the server. It usually provides the best performance since only the function request and the result are sent over the network. -However, it could happen that a function is fully executable on the client side (e.g., when it processes data that's already in the local cache). In this case, you can save requests to the server and thus, enhance the application performance by using the `local` keyword. +However, it could happen that a function is fully executable on the client side (e.g., when it processes data that's already in the local cache). In this case, you can send requests to the server and thus, enhance the application performance by using the `local` keyword. Note that the function will work even if it eventually requires to access the server (for example if the ORDA cache is expired). However, it is highly recommended to make sure that the local function does not access data on the server, otherwise the local execution could not bring any performance benefit. A local function that generates many requests to the server is less efficient than a function executed on the server that would only return the resulting values. For example, consider the following function on the Schools entity class: @@ -999,12 +1000,18 @@ local Function getYoungest The `server` keyword specifies that the function must be executed **on the server side**. -`server` function parameters and result must be **streamable**, that is, they can be saved into a file or sent over a network. For example, [ORDA Data model](../ORDA/ordaClasses.md), [File handle](../API/FileHandleClass.md), or [WebServer](../API/WebServerClass.md) are non-streamable classes. -This feature is particularly useful in the context of [remote user sessions](../Desktop/sessions.md#remote-user-sessions), allowing you to implement the business logic in a [session singleton](#shared-or-session-singleton-functions) to share it accross all the processes of the session, thus extending the functionalities of the [`Session`](../commands/session) command. In this case, you might want the relevant business logic to be executed **on the server** so that all the session information is gathered on the server. +:::note Reminder + +The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClasses.md), which are executed on the server by default, but it can be added for clarity. + +::: -#### Shared or session singleton functions +`server` function parameters and result must be **streamable**, that is, they can be saved into a file or sent over a network. For example, [4D.Datastore](../API/DataStoreClass.md), [File handle](../API/FileHandleClass.md), or [WebServer](../API/WebServerClass.md) are non-streamable classes. + +This feature is particularly useful in the context of [remote user sessions](../Desktop/sessions.md#remote-user-sessions), allowing you to implement the business logic in a [session singleton](#shared-or-session-singleton-functions) to share it accross all the processes of the session, thus extending the functionalities of the [`Session`](../commands/session) command. In this case, you might want the relevant business logic to be executed **on the server** so that all the session information is gathered on the server. + By default, shared or session singleton functions are executed on the client. Adding the `server` keyword in the class function definition makes 4D use the singleton instance on the server. Note that this can result of an instantiation of the singleton on the server if no instance exists yet. @@ -1017,16 +1024,47 @@ Note that when you declare a `server Function` in a shared singleton and: Since there is no instance of *S1* on the server at this moment, *S1* is instanciated on the server (the constructor is executed) and the *function()* is run on this server instance. As a consequence, you can have two instances of *S1* (client-side and server-side) with distincts property values. However in this case, *s1.property* is always accessed locally. It cannot be accessed on the server, for example in code running on the server,with a direct access using dot notation (an error is returned). +For example: +```4d +// mySingleton class + +property executedOn : Integer + +shared singleton Class constructor + This.executedOn:=Application type() + +Function generateDataLocally() : Object + var $result:={} + $result.message:="I have been executed on "+String(This.executedOn) + return $result +       +server Function generateDataOnServer() : Object + var $result:={} +  $result.message:="I have been executed on "+String(This.executedOn) +return $result +``` +Code running on the client: +```4d +var $singleton : cs.MySingleton +var $info : Object +ASSERT(Application type()=4D Remote mode) +// The singleton is instanciated on the client +$singleton:=cs.MySingleton.me -#### ORDA data model functions - -The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClasses.md), which are executed on the server by default (but can be used for clarity). +// The generateDataLocally() function is executed locally +$info:=$singleton.generateDataLocally() +ASSERT($info.message="I have been executed on 4") +// The generateDataOnServer() function is executed on the server +// Thus an instance of the singleton is created on the server and the constructor is triggered +$info:=$singleton.generateDataOnServer() +ASSERT($info.message="I have been executed on 5") +``` ### Examples (ORDA data model functions) diff --git a/docs/Desktop/clientServer.md b/docs/Desktop/clientServer.md index 5e02cfc9cef10d..0911a03a623e85 100644 --- a/docs/Desktop/clientServer.md +++ b/docs/Desktop/clientServer.md @@ -128,3 +128,25 @@ This feature is designed for small-size development teams who are used to work o [Developing Concurrently on 4D Server in Project Mode](https://blog.4d.com/developing-concurrently-on-4d-server-in-project-mode/) ::: + + +## Code execution location + +In a client/server application, it is important to know where your code will be actually executed: **server-side** or **client-side**. Execution location is crucial when you want to implement user session-related code, share information between processes, access data, etc. + +The following table summarizes where the code is executed by default and how to switch its execution location (if allowed): + +|Code|Default execution|How to switch|Comment| +|---|---|---|---| +|[ORDA data model function](../ORDA/ordaClasses.md)|Server|use `local` keyword in function definition|| +|[User function](../Concepts/classes.md#function)|Client|-|| +|[Computed property function](../Concepts/classes.md#function-get-and-function-set)|Client|use `server` keyword in function definition|| +|[Shared or session singleton function](../Concepts/classes.md#singleton-classes)|Client|use `server` keyword in function definition|| +|Trigger|Server|-|| +|Project method called from a client|Client|*Execute on server* option|If on server, the code is executed in the twin process of the [user session process](./sessions.md#remote-user-sessions-remote-user-sessions)| +|Project method called from a client|Client|[`Execute on server`](../commands/execute-on-server) command|If on server, the code is executed in the [Stored procedures session](./sessions.md#stored-procedure-sessions-stored-procedure-sessions) | +|Project method called from a stored procedure on the server|Server|[`EXECUTE ON CLIENT`](../commands/execute-on-client) command|The target client must have been [registered](../commands/register-client) | +|Object method|Client|-|| +|Database methods:
  • On Server Startup
  • On Server Shutdown
  • On Server Open Connection
  • On Server Close Connection
|Server||| +|Database methods:
  • On Web Authentication
  • On Web Connection
  • On SQL Authentication
  • On Backup Startup
  • On Backup Shutdown
|Server or Client|-|Depends on the calling context| +|Database methods:
  • On Startup
  • On Exit
  • On Drop
|Client|-|| \ No newline at end of file From 999cfa27c20cbbe7990912eb58a591ab29790973 Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Mon, 23 Mar 2026 11:07:44 +0100 Subject: [PATCH 05/11] Update classes.md --- docs/Concepts/classes.md | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/Concepts/classes.md b/docs/Concepts/classes.md index b2beb7ae281463..94636e7fa9cfd7 100644 --- a/docs/Concepts/classes.md +++ b/docs/Concepts/classes.md @@ -948,7 +948,7 @@ local Function server Function ``` -`local` and `server` keywords are only available for the functions of the [ORDA data model](../ORDA/ordaClasses.md) and [Shared or session singleton](#singleton-classes) classes. +`local` and `server` keywords are only available for the functions of the [ORDA data model](../ORDA/ordaClasses.md) and [shared or session singleton](#singleton-classes) classes. ### Overview @@ -971,7 +971,7 @@ For a overall description of where code is actually executed in client/server, p ### `local` -The `local` keyword specifies that the function must be executed **on the client side**. +In a [client/server architecture](../Desktop/clientServer.md), the `local` keyword specifies that the function must be executed **on the client side**. :::note Reminder @@ -998,12 +998,12 @@ local Function getYoungest ### `server` -The `server` keyword specifies that the function must be executed **on the server side**. +In a [client/server architecture](../Desktop/clientServer.md), the `server` keyword specifies that the function must be executed **on the server side**. :::note Reminder -The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClasses.md), which are executed on the server by default, but it can be added for clarity. +The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClasses.md), which are executed on the server by default, but it can be added for clarity. ::: @@ -1029,20 +1029,26 @@ For example: ```4d // mySingleton class -property executedOn : Integer +property instantiatedOn : Integer shared singleton Class constructor - This.executedOn:=Application type() + This.instantiatedOn:=Application type() +// Function generateDataLocally() : Object var $result:={} - $result.message:="I have been executed on "+String(This.executedOn) + + $result.message:="I have been executed on "+String(Application type())+\ + " and instantiated on "+String(This.instantiatedOn) return $result -       + +// server Function generateDataOnServer() : Object var $result:={} -  $result.message:="I have been executed on "+String(This.executedOn) -return $result + + $result.message:="I have been executed on "+String(Application type())+\ + " and instantiated on "+String(This.instantiatedOn) + return $result ``` Code running on the client: @@ -1058,12 +1064,12 @@ $singleton:=cs.MySingleton.me // The generateDataLocally() function is executed locally $info:=$singleton.generateDataLocally() -ASSERT($info.message="I have been executed on 4") +ASSERT($info.message="I have been executed on 4 and instantiated on 4") // The generateDataOnServer() function is executed on the server -// Thus an instance of the singleton is created on the server and the constructor is triggered +// Thus an instance of the singleton is created on the server $info:=$singleton.generateDataOnServer() -ASSERT($info.message="I have been executed on 5") +ASSERT($info.message="I have been executed on 5 and instantiated on 5") ``` ### Examples (ORDA data model functions) @@ -1121,7 +1127,7 @@ If ($status.success) End if ``` -### Example (Shared or session singleton functions) +### Example (Session singleton function) In a client/server application, the *Authentication* session singleton is used to handle user session data: From 0e0e7174e91681e1a85a7a4ba690410d22450703 Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Wed, 25 Mar 2026 18:39:07 +0100 Subject: [PATCH 06/11] streamable work --- docs/API/BlobClass.md | 6 ++++ docs/API/CollectionClass.md | 5 +++ docs/API/EmailObjectClass.md | 7 ++++ docs/API/FileClass.md | 8 +++++ docs/API/FileHandleClass.md | 5 --- docs/API/FolderClass.md | 8 +++++ docs/API/FormulaClass.md | 6 ++++ docs/API/MailAttachmentClass.md | 6 ++++ docs/API/MethodClass.md | 6 ++++ docs/API/VectorClass.md | 4 +++ docs/Concepts/dt_object.md | 36 ++++++++++++++++++- docs/language-legacy/BLOB/variable-to-blob.md | 11 ++---- .../current.json | 2 +- 13 files changed, 95 insertions(+), 15 deletions(-) diff --git a/docs/API/BlobClass.md b/docs/API/BlobClass.md index 22262b92458de5..d566062e79e41b 100644 --- a/docs/API/BlobClass.md +++ b/docs/API/BlobClass.md @@ -5,6 +5,12 @@ title: Blob The Blob class lets you create and manipulate [blob objects](../Concepts/dt_blob.md#blob-types) (`4D.Blob`). +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: + ### Summary || diff --git a/docs/API/CollectionClass.md b/docs/API/CollectionClass.md index 5c99145d5a36d1..b2db873835367f 100644 --- a/docs/API/CollectionClass.md +++ b/docs/API/CollectionClass.md @@ -9,6 +9,11 @@ The Collection class manages [Collection](Concepts/dt_collection.md) type expres A collection is initialized with the [`New collection`](../commands/new-collection) or [`New shared collection`](../commands/new-shared-collection) commands. +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: ### Example diff --git a/docs/API/EmailObjectClass.md b/docs/API/EmailObjectClass.md index 6181d150547830..e7d4a6297ebaa7 100644 --- a/docs/API/EmailObjectClass.md +++ b/docs/API/EmailObjectClass.md @@ -16,6 +16,13 @@ You send `Email` objects using the SMTP [`.send()`](SMTPTransporterClass.md#send [`MAIL Convert from MIME`](../commands/mail-convert-from-mime) and [`MAIL Convert to MIME`](../commands/mail-convert-to-mime) commands can be used to convert `Email` objects to and from MIME contents. +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: + + ### Email Object Email objects provide the following properties: diff --git a/docs/API/FileClass.md b/docs/API/FileClass.md index b5bbde2d330e9a..ac8ef5a0e60f46 100644 --- a/docs/API/FileClass.md +++ b/docs/API/FileClass.md @@ -5,6 +5,14 @@ title: File `File` objects are created with the [`File`](../commands/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. + +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: + + ### Example The following example creates a preferences file in the project folder: diff --git a/docs/API/FileHandleClass.md b/docs/API/FileHandleClass.md index c59394ad1782fa..8de8f41b394a7a 100644 --- a/docs/API/FileHandleClass.md +++ b/docs/API/FileHandleClass.md @@ -56,11 +56,6 @@ End while ``` -### Properties - -- **Streamable**: no -- **Sharable**: no - ### FileHandle object diff --git a/docs/API/FolderClass.md b/docs/API/FolderClass.md index 417f6c5dd687b7..4c949b0d93a25e 100644 --- a/docs/API/FolderClass.md +++ b/docs/API/FolderClass.md @@ -7,6 +7,14 @@ title: Folder `Folder` objects are created with the [`Folder`](../commands/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. + +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: + + ### Example The following example creates a "JohnSmith" folder: diff --git a/docs/API/FormulaClass.md b/docs/API/FormulaClass.md index b8494a9407c9e9..836e36c673f64c 100644 --- a/docs/API/FormulaClass.md +++ b/docs/API/FormulaClass.md @@ -13,6 +13,12 @@ title: Formula See examples in the [Executing code in Function objects](../API/FunctionClass.md#executing-code-in-function-objects) paragraph. +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: + ### Passing parameters to formulas diff --git a/docs/API/MailAttachmentClass.md b/docs/API/MailAttachmentClass.md index 3049a31be4f620..2a0a4fd0362514 100644 --- a/docs/API/MailAttachmentClass.md +++ b/docs/API/MailAttachmentClass.md @@ -5,6 +5,12 @@ title: MailAttachment Attachment objects allow referencing files within a [`Email`](EmailObjectClass.md) object. Attachment objects are created using the [`MAIL New attachment`](../commands/mail-new-attachment) command. +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: + ### Attachment Object diff --git a/docs/API/MethodClass.md b/docs/API/MethodClass.md index 759994eb2b48cc..b2771ef01eccc8 100644 --- a/docs/API/MethodClass.md +++ b/docs/API/MethodClass.md @@ -21,6 +21,12 @@ See examples in the [Executing code in Function objects](../API/FunctionClass.md ::: +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. + +::: + ### Examples diff --git a/docs/API/VectorClass.md b/docs/API/VectorClass.md index 7a9965b582de13..2181018c84adc0 100644 --- a/docs/API/VectorClass.md +++ b/docs/API/VectorClass.md @@ -10,7 +10,11 @@ The `Vector` class allows you to handle **vectors** and to execute distance and In the world of AIs, a vector is a sequence of numbers that enables a machine to understand and manipulate complex data. For a detailed overview of the role of vectors with AIs, you can refer to [this page](https://aiforsocialgood.ca/blog/understanding-the-role-of-vectors-in-artificial-intelligence-a-comprehensive-guide). +:::info + +This class is [**streamable**](../Concepts/dt_object.md#binary-streaming-variable-to-blob) in binary. +::: ### Understanding the different vector computations diff --git a/docs/Concepts/dt_object.md b/docs/Concepts/dt_object.md index 21f6f7067c8b5c..13e3e68512db76 100644 --- a/docs/Concepts/dt_object.md +++ b/docs/Concepts/dt_object.md @@ -18,7 +18,7 @@ Variables, fields or expressions of the Object type can contain various types of - picture(2) - collection -(1) **Non-streamable objects** such as ORDA objects ([entities](ORDA/dsMapping.md#entity), [entity selections](ORDA/dsMapping.md#entity-selection), etc.), [file handles](../API/FileHandleClass.md), [web server](../API/WebServerClass.md)... cannot be stored in **object fields**. An error is returned if you try to do it; however, they are fully supported in **object variables** in memory. +(1) [**Non-streamable objects**](#streaming-support) such as ORDA objects ([entities](ORDA/dsMapping.md#entity), [entity selections](ORDA/dsMapping.md#entity-selection), etc.), [file handles](../API/FileHandleClass.md), [web server](../API/WebServerClass.md)... cannot be stored in **object fields**. An error is returned if you try to do it; however, they are fully supported in **object variables** in memory. (2) When exposed as text in the debugger or exported to JSON, picture object properties print "[object Picture]". @@ -272,6 +272,40 @@ $doc:=Null // free resources occupied by $doc ``` +## Classes + +Objects can belong to classes. Using a class allows to predefine an object behaviour and structure with associated properties and functions. + +The 4D language proposes several [native classes](../category/class-API-reference/) that you can use to handle objects. You can also define and use your own [user classes](./classes.md) to organize your code. + + + +## Streaming support + +A streamable class (or *serializable* class) is a class whose objects can be converted into a sequence of bytes (text or binary) in order to write them in a file, to send them as parameters, or to be able to store and rebuild them afterwards. + +### Text streaming (`JSON Stringify`) + +JSON commands that stringify contents such as [`JSON Stringify`](../commands/json-stringify) and the [`Execute on server`](../commands/execute-on-server) command allow you to convert objects to json (text). They support objects, collections, and user classes. + +However, text streaming of objects has the following limitations: + +- circular references (i.e. objects containing themselves as a property) are not supported and return an error, +- a class object loses its class when it is stringified, +- native 4D class objects such as [Entity](../API/EntityClass.md) cannot be represented as JSON and are returned as "[object \]", for example "[object Entity]". + +### Binary streaming (`VARIABLE TO BLOB`) + +4D also implements a built-in binary streaming feature through the [`VARIABLE TO BLOB`](../commands/variable-to-blob) command. This feature allows you to get rid of most of text streaming limitations regarding objects (see above): + +- circular references are supported, +- objects keep their class, +- an extended range of objects are streamable: [4D Write Pro](../WritePro/user-legacy/presentation.md) documents, pictures as objects, [blobs as objects](dt_blob.md#blob-types), and pointers as objects, +- several native 4D class objects can be streamed, for example [`File`](../API/FileClass.md), [`Folder`](../API/FolderClass.md), or [`Vector`](../API/VectorClass.md). However, only a few native 4D classes are streamable. Unless explicitely stated that "This class is **streamable** in binary", consider that a native 4D class is NOT streamable. + + + + ## Examples Using object notation simplifies the 4D code while handling objects. Note however that the command-based notation is still fully supported. diff --git a/docs/language-legacy/BLOB/variable-to-blob.md b/docs/language-legacy/BLOB/variable-to-blob.md index 073d7fc4368113..d108862982628b 100644 --- a/docs/language-legacy/BLOB/variable-to-blob.md +++ b/docs/language-legacy/BLOB/variable-to-blob.md @@ -42,10 +42,7 @@ If you pass the *offset* variable parameter, the variable is written at the offs After the call, the *offset* variable parameter is returned, incremented by the number of bytes that have been written. Therefore, you can reuse that same variable with another BLOB writing command to write another variable or list. -VARIABLE TO BLOB accepts any type of variable (including other BLOBs), except the following: - -* Pointer -* Array of pointers +VARIABLE TO BLOB accepts any type of variable, including other BLOBs and [**streamable objects**](../../Concepts/dt_object.md#streaming-support). Note that: @@ -54,17 +51,15 @@ Note that: **WARNING:** If you use a BLOB for storing variables, you must later use the command [BLOB TO VARIABLE](../commands/blob-to-variable) for reading back the contents of the BLOB, because variables are stored in BLOBs using a 4D internal format. -After the call, if the variable has been successfully stored, the OK variable is set to 1\. If the operation could not be performed, the OK variable is set to 0; for example, there was not enough memory. - **Note regarding Platform Independence:** VARIABLE TO BLOB and [BLOB TO VARIABLE](../commands/blob-to-variable) use a 4D internal format for handling variables stored in BLOBs. As a benefit, you do not need to worry about byte swapping between platforms while using these two commands. In other words, a BLOB created on Windows using either of these commands can be reused on Macintosh, and vice-versa. ### Note -**Compatiblity note:** Since this command alters the blob passed as a parameter, it does not support blob objects (4D.Blob type). See [Passing blobs and blob objects to 4D commands](../../Concepts/dt_blob.md#passing-blobs-and-blob-objects-to-4d-commands). +**Compatibility note:** Since this command alters the blob passed as a parameter, it does not support blob objects (4D.Blob type). See [Passing blobs and blob objects to 4D commands](../../Concepts/dt_blob.md#passing-blobs-and-blob-objects-to-4d-commands). ## System variables and sets -The OK variable is set to 1 if the variable has been successfully stored, otherwise it is set to 0. +The OK variable is set to 1 if the variable has been successfully stored, otherwise it is set to 0, for example if there was not enough memory. ## Example 1 diff --git a/i18n/en/docusaurus-plugin-content-docs/current.json b/i18n/en/docusaurus-plugin-content-docs/current.json index 45e0497d631443..3a1cdf5afc2bcd 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current.json +++ b/i18n/en/docusaurus-plugin-content-docs/current.json @@ -348,7 +348,7 @@ "description": "The label for category Classes in sidebar docs" }, "sidebar.docs.category.Classes.link.generated-index.title": { - "message": "Class Functions", + "message": "Classes", "description": "The generated-index page title for category Classes in sidebar docs" }, "sidebar.docs.category.Classes.link.generated-index.description": { From 06d69b827226a632b7860ee18be45b4fffbb6733 Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Wed, 25 Mar 2026 18:51:19 +0100 Subject: [PATCH 07/11] Update classes.md --- docs/Concepts/classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Concepts/classes.md b/docs/Concepts/classes.md index 94636e7fa9cfd7..24407b6832ff26 100644 --- a/docs/Concepts/classes.md +++ b/docs/Concepts/classes.md @@ -1008,7 +1008,7 @@ The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClas ::: -`server` function parameters and result must be **streamable**, that is, they can be saved into a file or sent over a network. For example, [4D.Datastore](../API/DataStoreClass.md), [File handle](../API/FileHandleClass.md), or [WebServer](../API/WebServerClass.md) are non-streamable classes. +`server` function parameters and result must be [**streamable**](./dt_object.md#streaming-support). For example, [4D.Datastore](../API/DataStoreClass.md), [File handle](../API/FileHandleClass.md), or [WebServer](../API/WebServerClass.md) are non-streamable classes but [4D.File](../API/FileClass.md) is streamable. This feature is particularly useful in the context of [remote user sessions](../Desktop/sessions.md#remote-user-sessions), allowing you to implement the business logic in a [session singleton](#shared-or-session-singleton-functions) to share it accross all the processes of the session, thus extending the functionalities of the [`Session`](../commands/session) command. In this case, you might want the relevant business logic to be executed **on the server** so that all the session information is gathered on the server. From d8940a6033130206906fa0001a04e75356aaa118 Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Fri, 10 Apr 2026 19:07:51 +0200 Subject: [PATCH 08/11] added examples --- docs/API/SessionClass.md | 2 +- docs/Concepts/classes.md | 269 ++++++++++++++-------- docs/Desktop/sessions.md | 2 +- docs/language-legacy/Processes/session.md | 2 +- 4 files changed, 170 insertions(+), 105 deletions(-) diff --git a/docs/API/SessionClass.md b/docs/API/SessionClass.md index eaaf75353442f3..2322d6e6794d85 100644 --- a/docs/API/SessionClass.md +++ b/docs/API/SessionClass.md @@ -12,7 +12,7 @@ Session objects are returned by the [`Session`](../commands/session) command. Th - [Scalable sessions for advanced web applications](https://blog.4d.com/scalable-sessions-for-advanced-web-applications/) - [Permissions: Inspect Session Privileges for Easy Debugging](https://blog.4d.com/permissions-inspect-session-privileges-for-easy-debugging/) - [Generate, share and use web sessions One-Time Passcodes (OTP)](https://blog.4d.com/connect-your-web-apps-to-third-party-systems/) -- [Client / server – Handle a session when working on a 4D client](https://blog.4d.com/client-server-handle-a-session-when-working-on-a-4d-client) +- [Forget server-side wrappers, use 4D Sessions from the client](https://blog.4d.com/forget-server-side-wrappers-use-4d-sessions-from-the-client) ::: diff --git a/docs/Concepts/classes.md b/docs/Concepts/classes.md index 24407b6832ff26..fe95c8ae6e8fcd 100644 --- a/docs/Concepts/classes.md +++ b/docs/Concepts/classes.md @@ -948,9 +948,17 @@ local Function server Function ``` -`local` and `server` keywords are only available for the functions of the [ORDA data model](../ORDA/ordaClasses.md) and [shared or session singleton](#singleton-classes) classes. +`local` and `server` keywords are only available for the functions of the following classes: +- [ORDA data model](../ORDA/ordaClasses.md) classes +- [shared or session singleton](#singleton-classes) classes. +:::tip Related blog post + +[A new way to execute business logic on the server](https://blog.4d.com/a-new-way-to-execute-business-logic-on-the-server) + +::: + ### Overview Supported functions have a **default execution location** when no location keyword is used. You can nevertheless insert a `local` or `server` keyword to modify the execution location, or to make the code more explicit. @@ -996,6 +1004,60 @@ local Function getYoungest - **with** the `local` keyword, 4 requests are necessary: one to get the Schools entity students, one for the `query()`, one for the `orderBy()`, and one for the `slice()`. In this example, using the `local` keyword is inappropriate. +#### Example: Calculating age + +Given an entity with a *birthDate* attribute, we want to define an `age()` function that would be called in a list box. This function can be executed on the client, which avoids triggering a request to the server for each line of the list box. + +On the *StudentsEntity* class: + +```4d +Class extends Entity + +local Function age() -> $age: Variant + +If (This.birthDate#!00-00-00!) + $age:=Year of(Current date)-Year of(This.birthDate) +Else + $age:=Null +End if +``` + +#### Example: Checking attributes + +We want to check the consistency of the attributes of an entity loaded on the client and updated by the user before requesting the server to save them. + +On the *StudentsEntity* class, the local `checkData()` function checks the Student's age: + +```4d +Class extends Entity + +local Function checkData() -> $status : Object + +$status:=New object("success"; True) +Case of + : (This.age()=Null) + $status.success:=False + $status.statusText:="The birthdate is missing" + + :((This.age() <15) | (This.age()>30) ) + $status.success:=False + $status.statusText:="The student must be between 15 and 30 - This one is "+String(This.age()) +End case +``` + +Calling code: + +```4d +var $status : Object + +//Form.student is loaded with all its attributes and updated on a Form +$status:=Form.student.checkData() +If ($status.success) + $status:=Form.student.save() // call the server +End if +``` + + ### `server` In a [client/server architecture](../Desktop/clientServer.md), the `server` keyword specifies that the function must be executed **on the server side**. @@ -1024,152 +1086,155 @@ Note that when you declare a `server Function` in a shared singleton and: Since there is no instance of *S1* on the server at this moment, *S1* is instanciated on the server (the constructor is executed) and the *function()* is run on this server instance. As a consequence, you can have two instances of *S1* (client-side and server-side) with distincts property values. However in this case, *s1.property* is always accessed locally. It cannot be accessed on the server, for example in code running on the server,with a direct access using dot notation (an error is returned). -For example: +#### Example: Administration singleton -```4d -// mySingleton class +The *Administration* shared singleton has a "server" function running the [`Process activity`](../commands/process-activity) command. This singleton is instantiated on a remote 4D but the function returns the server activity on the server. -property instantiatedOn : Integer +```4d + // Administration class shared singleton Class constructor - This.instantiatedOn:=Application type() - -// -Function generateDataLocally() : Object - var $result:={} - $result.message:="I have been executed on "+String(Application type())+\ - " and instantiated on "+String(This.instantiatedOn) - return $result + // This function is executed on the server +server Function processActivity() : Object + return Process activity -// -server Function generateDataOnServer() : Object - var $result:={} - $result.message:="I have been executed on "+String(Application type())+\ - " and instantiated on "+String(This.instantiatedOn) - return $result +Function localProcessActivity() : Object + return Process activity ``` Code running on the client: ```4d -var $singleton : cs.MySingleton -var $info : Object +var $localActivity; $serverActivity : Object +var $administration : cs.Administration -ASSERT(Application type()=4D Remote mode) -// The singleton is instanciated on the client -$singleton:=cs.MySingleton.me +// The Administration singleton is instantiated on the 4D Client +$administration:=cs.Administration.me -// The generateDataLocally() function is executed locally -$info:=$singleton.generateDataLocally() +// Get processes running on the remote 4D +$localActivity:=$administration.localProcessActivity() -ASSERT($info.message="I have been executed on 4 and instantiated on 4") +// Get processes and sessions running on 4D Server +$serverActivity:=$administration.processActivity() -// The generateDataOnServer() function is executed on the server -// Thus an instance of the singleton is created on the server -$info:=$singleton.generateDataOnServer() -ASSERT($info.message="I have been executed on 5 and instantiated on 5") ``` -### Examples (ORDA data model functions) -#### Calculating age +#### Example: ProductCreation shared singleton -Given an entity with a *birthDate* attribute, we want to define an `age()` function that would be called in a list box. This function can be executed on the client, which avoids triggering a request to the server for each line of the list box. - -On the *StudentsEntity* class: +A shared singleton can be used to store in memory on the server the current number of products created since the application has been started. Because a shared singleton has a single instance on the 4D server, all the 4D clients will read and update the same property when creating products. ```4d -Class extends Entity +// ProductCreation shared singleton Class -local Function age() -> $age: Variant +property _nbProducts : Integer -If (This.birthDate#!00-00-00!) - $age:=Year of(Current date)-Year of(This.birthDate) -Else - $age:=Null -End if -``` - -#### Checking attributes +shared singleton Class constructor() +This._nbProducts:=0 + -We want to check the consistency of the attributes of an entity loaded on the client and updated by the user before requesting the server to save them. +// Get the current number of created products +server Function get nbProducts() : Integer +return This._nbProducts + +// When a product is saved, the number of products is incremented on the server +shared server Function saveProduct($product : cs.ProductsEntity) : Integer + +var $status : Object + +$status:=$product.save() + +If ($status.success) + This._nbProducts+=1 +End if + +return This._nbProducts +``` -On the *StudentsEntity* class, the local `checkData()` function checks the Student's age: +Calling code on the remote 4D: ```4d -Class extends Entity +// The singleton is instanciated on the 4D Client +Form.productCreation:=cs.ProductCreation.me -local Function checkData() -> $status : Object +// Starting creating a new product +Form.product:=ds.Products.new() -$status:=New object("success"; True) -Case of - : (This.age()=Null) - $status.success:=False - $status.statusText:="The birthdate is missing" +// ...Fill the product attributes - :((This.age() <15) | (This.age()>30) ) - $status.success:=False - $status.statusText:="The student must be between 15 and 30 - This one is "+String(This.age()) -End case +Form.nbProducts:=Form.productCreation.saveProduct(Form.product) ``` -Calling code: +#### Example: Session singleton -```4d -var $status : Object - -//Form.student is loaded with all its attributes and updated on a Form -$status:=Form.student.checkData() -If ($status.success) - $status:=Form.student.save() // call the server -End if -``` +In this example, the user creates products, which requires several steps. Each step needs to be validated to enter the next one. -### Example (Session singleton function) - -In a client/server application, the *Authentication* session singleton is used to handle user session data: +For each user, the current step of the product creation workflow is stored in memory on the server thanks to a session singleton. ```4d -// Authentication Class +// UserJourney session singleton class -property _salesPeopleId : Integer +property _currentStep : Integer session singleton Class constructor() -If (This._salesPeopleId=Null) - This.initSalesPeopleId() +shared server Function start() : Integer +This._currentStep:=1 +return This._currentStep + +shared server Function nextStep($product : cs.ProductsEntity) : Object +var $result:={} + Case of + : (This._currentStep=1) + If ($product.Name="") + $result.message:="The product name is mandatory" + Else + If (ds.Products.query("Name = :1"; $product.Name).length>=1) + $result.message:="This product name already exists" + End if + End if + : (This._currentStep=2) + If ($product.RetailPrice=0) + $result.message:="The product retail price is mandatory" + End if + End case + +If ($result.message=Null) + This._currentStep+=1 End if -// Executed on the client (default location) -shared Function initSalesPeopleId() - This._salesPeopleId:=ds.getSalesPeopleId() // Call to the server +$result.currentStep:=This._currentStep + +return $result +``` + +Code running on a remote 4D when the user starts creating a product: + +```4d +// The session singleton is instanciated on the client +Form.userJourney:=cs.UserJourney.me + +// Form.currentStep is 1 +Form.currentStep:=Form.userJourney.start() -// Executed on the client -Function get salesPeople() : cs.SalesPeopleEntity - return ds.SalesPeople.get(This._salesPeopleId) +// ...going on with step #1 +``` +Further, to complete the step #1, a check must be done on the server to prevent duplicate product names. This is done in the *nextStep()* function. Code running on a remote 4D when the user goes to the next step: -// Functions to be executed on the server +```4d +var $test : Object -server Function get sessionStorage() : Object - return Session.storage.clientData +$test:=Form.userJourney.nextStep(Form.product) -server Function putInSessionStorage($content : Object) - var $prop : Text - // $content is like: {"searchCritreria":{min: 10; max: 20}} - Use (Session.storage.clientData) - For each ($prop; $content) - Session.storage.clientData[$prop]:=OB Copy($content[$prop]; ck shared) - End for each - End use - -server Function clearSession() - Session.clearPrivileges() - Use (Session.storage) - Session.storage.salesInfo:=New shared object() - Session.storage.clientData:=New shared object() - End use - -``` \ No newline at end of file +If ($test.message=Null) + // Going on with the next step + Form.currentStep:=$test.currentStep +Else + // Error message - Stay on the current step + Form.message:=$test.message +End if + +``` diff --git a/docs/Desktop/sessions.md b/docs/Desktop/sessions.md index 48ab463b74d6ee..23b20d594de91d 100644 --- a/docs/Desktop/sessions.md +++ b/docs/Desktop/sessions.md @@ -71,7 +71,7 @@ On the client side, two distinct local storage objects are available: :::tip Related blog posts - [4D remote session object with Client/Server connection and Stored procedure](https://blog.4d.com/new-4D-remote-session-object-with-client-server-connection-and-stored-procedure). -- [Client / server – Handle a session when working on a 4D client](https://blog.4d.com/client-server-handle-a-session-when-working-on-a-4d-client). +- [Forget server-side wrappers, use 4D Sessions from the client](https://blog.4d.com/forget-server-side-wrappers-use-4d-sessions-from-the-client). ::: diff --git a/docs/language-legacy/Processes/session.md b/docs/language-legacy/Processes/session.md index ea551463022f66..540214e6309334 100644 --- a/docs/language-legacy/Processes/session.md +++ b/docs/language-legacy/Processes/session.md @@ -62,7 +62,7 @@ The `Session` object of a remote user session is available: - On the server, from code running in the user context, such as project methods that have the [Execute on Server](../../Project/project-method-properties.md#execute-on-server) attribute (they are executed in the "twinned" process of the client process) or ORDA [data model functions](../../ORDA/ordaClasses.md). - On the client, from code running locally, such as in project methods or ORDA data model functions with the *local* property. -For more information, see [the "Availability" paragraph](../../Desktop/sessions.md#availability). +For more information, see the ["Remote user sessions" paragraph in the Desktop sessions](../../Desktop/sessions.md#remote-user-sessions) page. From 7386a4cfdd8d5b7b5917a0ddb6403316db2c2638 Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Tue, 14 Apr 2026 14:55:58 +0200 Subject: [PATCH 09/11] server execution after closure --- docs/Concepts/classes.md | 196 +++++------------------- docs/Desktop/clientServer.md | 35 +++-- docs/ORDA/client-server-optimization.md | 52 +++++++ docs/ORDA/orda-events.md | 12 +- 4 files changed, 113 insertions(+), 182 deletions(-) diff --git a/docs/Concepts/classes.md b/docs/Concepts/classes.md index fe95c8ae6e8fcd..0e2fa4b2645d14 100644 --- a/docs/Concepts/classes.md +++ b/docs/Concepts/classes.md @@ -963,12 +963,12 @@ server Function Supported functions have a **default execution location** when no location keyword is used. You can nevertheless insert a `local` or `server` keyword to modify the execution location, or to make the code more explicit. -|Supported functions|Executed by default on|`local` keyword|`server` keyword| +|Supported functions|Default execution|with `local` keyword|with `server` keyword| |---|---|---|---| -|[ORDA data model](../ORDA/ordaClasses.md)|Server|*Supported*
The function is executed on the client|Allowed to make the code more explicit but no impact| -|[Shared or session singleton](#singleton-classes)|Client|Allowed to make the code more explicit but no impact|*Supported*
The function is executed on the server on the instance of the singleton on the server.
If there is no instance of the singleton on the server, it is created. | +|[ORDA data model](../ORDA/ordaClasses.md)|on Server|The function is executed on the client if called on the client|| +|[Shared or session singleton](#singleton-classes)|Local||The function is executed on the server on the server instance of the singleton.
If there is no instance of the singleton on the server, it is created. | -If `local` and `server` keywords are used in another context, they are ignored and an error is returned by the compiler. +If `local` and `server` keywords are used in another context, an error is returned. :::note @@ -979,29 +979,16 @@ For a overall description of where code is actually executed in client/server, p ### `local` -In a [client/server architecture](../Desktop/clientServer.md), the `local` keyword specifies that the function must be executed **on the client side**. +In a [client/server architecture](../Desktop/clientServer.md), the `local` keyword specifies that the function must be executed **on the machine from where it is called**. :::note Reminder -The `local` keyword is useless for [shared or session singleton functions](#singleton-classes), which are executed on the client by default, but can be added for clarity. +The `local` keyword is useless for [shared or session singleton functions](#singleton-classes), which are executed locally by default. ::: -By default, [ORDA data model functions](../ORDA/ordaClasses.md) are executed on the server. It usually provides the best performance since only the function request and the result are sent over the network. +By default, [ORDA data model functions](../ORDA/ordaClasses.md) are executed on the server. It usually provides the best performance since only the function request and the result are sent over the network. However, [for optimization reasons](../ORDA/client-server-optimization.md#using-the-local-keyword), you could want to execute a data model function on client. You can then use the `local` keyword. -However, it could happen that a function is fully executable on the client side (e.g., when it processes data that's already in the local cache). In this case, you can send requests to the server and thus, enhance the application performance by using the `local` keyword. - -Note that the function will work even if it eventually requires to access the server (for example if the ORDA cache is expired). However, it is highly recommended to make sure that the local function does not access data on the server, otherwise the local execution could not bring any performance benefit. A local function that generates many requests to the server is less efficient than a function executed on the server that would only return the resulting values. For example, consider the following function on the Schools entity class: - -```4d -// Get the youngest students -// Inappropriate use of local keyword -local Function getYoungest - var $0 : Object - $0:=This.students.query("birthDate >= :1"; !2000-01-01!).orderBy("birthDate desc").slice(0; 5) -``` -- **without** the `local` keyword, the result is given using a single request -- **with** the `local` keyword, 4 requests are necessary: one to get the Schools entity students, one for the `query()`, one for the `orderBy()`, and one for the `slice()`. In this example, using the `local` keyword is inappropriate. #### Example: Calculating age @@ -1022,40 +1009,6 @@ Else End if ``` -#### Example: Checking attributes - -We want to check the consistency of the attributes of an entity loaded on the client and updated by the user before requesting the server to save them. - -On the *StudentsEntity* class, the local `checkData()` function checks the Student's age: - -```4d -Class extends Entity - -local Function checkData() -> $status : Object - -$status:=New object("success"; True) -Case of - : (This.age()=Null) - $status.success:=False - $status.statusText:="The birthdate is missing" - - :((This.age() <15) | (This.age()>30) ) - $status.success:=False - $status.statusText:="The student must be between 15 and 30 - This one is "+String(This.age()) -End case -``` - -Calling code: - -```4d -var $status : Object - -//Form.student is loaded with all its attributes and updated on a Form -$status:=Form.student.checkData() -If ($status.success) - $status:=Form.student.save() // call the server -End if -``` ### `server` @@ -1065,7 +1018,7 @@ In a [client/server architecture](../Desktop/clientServer.md), the `server` keyw :::note Reminder -The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClasses.md), which are executed on the server by default, but it can be added for clarity. +The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClasses.md), which are executed on the server by default. ::: @@ -1075,16 +1028,20 @@ The `server` keyword is useless for [ORDA data model functions](../ORDA/ordaClas This feature is particularly useful in the context of [remote user sessions](../Desktop/sessions.md#remote-user-sessions), allowing you to implement the business logic in a [session singleton](#shared-or-session-singleton-functions) to share it accross all the processes of the session, thus extending the functionalities of the [`Session`](../commands/session) command. In this case, you might want the relevant business logic to be executed **on the server** so that all the session information is gathered on the server. -By default, shared or session singleton functions are executed on the client. Adding the `server` keyword in the class function definition makes 4D use the singleton instance on the server. Note that this can result of an instantiation of the singleton on the server if no instance exists yet. +By default, shared or session singleton functions are executed locally. Adding the `server` keyword in the class function definition makes 4D use the singleton instance on the server. Note that this can result of an instantiation of the singleton on the server if no instance exists yet. -For [sessions singletons](#singleton-classes), the function is executed on the server on the corresponding singleton instance, i.e. the instance of the singleton for the current session. +For [sessions singletons](#singleton-classes), the function is executed on the server in the corresponding singleton instance, i.e. the instance of the singleton for the current session. -Note that when you declare a `server Function` in a shared singleton and: +:::note + +If you declare a `server Function` in a shared singleton, then: -- instanciate singleton *S1* on the client (named *s1*) -- run *s1.function()* on the client +- you instantiate a singleton *S1* on the client (named *s1*), +- you run *s1.function()* on the client. -Since there is no instance of *S1* on the server at this moment, *S1* is instanciated on the server (the constructor is executed) and the *function()* is run on this server instance. As a consequence, you can have two instances of *S1* (client-side and server-side) with distincts property values. However in this case, *s1.property* is always accessed locally. It cannot be accessed on the server, for example in code running on the server,with a direct access using dot notation (an error is returned). +If no instance of *S1* exists on the server at that moment, *S1* is instantiated on the server (the constructor is executed), and *function()* runs on that server instance. As a result, two instances of *S1* can coexist (client-side and server-side), with distinct property values. In this case, *s1.property* is always accessed locally. It cannot be accessed on the server, for example from server-side code using direct dot notation (an error is returned). + +::: #### Example: Administration singleton @@ -1122,119 +1079,38 @@ $serverActivity:=$administration.processActivity() ``` -#### Example: ProductCreation shared singleton - -A shared singleton can be used to store in memory on the server the current number of products created since the application has been started. Because a shared singleton has a single instance on the 4D server, all the 4D clients will read and update the same property when creating products. - -```4d -// ProductCreation shared singleton Class - -property _nbProducts : Integer - -shared singleton Class constructor() -This._nbProducts:=0 - - -// Get the current number of created products -server Function get nbProducts() : Integer -return This._nbProducts - -// When a product is saved, the number of products is incremented on the server -shared server Function saveProduct($product : cs.ProductsEntity) : Integer - -var $status : Object - -$status:=$product.save() - -If ($status.success) - This._nbProducts+=1 -End if - -return This._nbProducts -``` - -Calling code on the remote 4D: - -```4d -// The singleton is instanciated on the 4D Client -Form.productCreation:=cs.ProductCreation.me - -// Starting creating a new product -Form.product:=ds.Products.new() - -// ...Fill the product attributes - -Form.nbProducts:=Form.productCreation.saveProduct(Form.product) -``` #### Example: Session singleton -In this example, the user creates products, which requires several steps. Each step needs to be validated to enter the next one. - -For each user, the current step of the product creation workflow is stored in memory on the server thanks to a session singleton. +You store your users in a Users table and handle a custom authentication. You use a session singleton for the authentication: ```4d -// UserJourney session singleton class - -property _currentStep : Integer +// UserSession session singleton class -session singleton Class constructor() - -shared server Function start() : Integer -This._currentStep:=1 -return This._currentStep +server Function checkUser($credentials : Object) : Boolean -shared server Function nextStep($product : cs.ProductsEntity) : Object -var $result:={} - Case of - : (This._currentStep=1) - If ($product.Name="") - $result.message:="The product name is mandatory" - Else - If (ds.Products.query("Name = :1"; $product.Name).length>=1) - $result.message:="This product name already exists" - End if - End if - : (This._currentStep=2) - If ($product.RetailPrice=0) - $result.message:="The product retail price is mandatory" - End if - End case +var $user : cs.UsersEntity +var $result:=False -If ($result.message=Null) - This._currentStep+=1 +If ($credentials#Null) + $user:=ds.Users.query("Email === :1"; $credentials.identifier).first() + + If (($user#Null) && (Verify password hash($credentials.password; $user.Password))) + Use (Session.storage) + Session.storage.userInfo:=New shared object("userId"; $user.ID) + End use + + $result:=True + End if End if -$result.currentStep:=This._currentStep - return $result ``` -Code running on a remote 4D when the user starts creating a product: +To provide the current user to 4D clients, the singleton exposes a user computed property got from the server: ```4d -// The session singleton is instanciated on the client -Form.userJourney:=cs.UserJourney.me - -// Form.currentStep is 1 -Form.currentStep:=Form.userJourney.start() - -// ...going on with step #1 +server Function get user() : cs.UsersEntity + return ds.Users.get(Session.storage.userInfo.userId) ``` -Further, to complete the step #1, a check must be done on the server to prevent duplicate product names. This is done in the *nextStep()* function. Code running on a remote 4D when the user goes to the next step: - -```4d -var $test : Object - -$test:=Form.userJourney.nextStep(Form.product) - -If ($test.message=Null) - // Going on with the next step - Form.currentStep:=$test.currentStep -Else - // Error message - Stay on the current step - Form.message:=$test.message -End if - -``` diff --git a/docs/Desktop/clientServer.md b/docs/Desktop/clientServer.md index 0911a03a623e85..fd25b4d95b2b80 100644 --- a/docs/Desktop/clientServer.md +++ b/docs/Desktop/clientServer.md @@ -134,19 +134,22 @@ This feature is designed for small-size development teams who are used to work o In a client/server application, it is important to know where your code will be actually executed: **server-side** or **client-side**. Execution location is crucial when you want to implement user session-related code, share information between processes, access data, etc. -The following table summarizes where the code is executed by default and how to switch its execution location (if allowed): - -|Code|Default execution|How to switch|Comment| -|---|---|---|---| -|[ORDA data model function](../ORDA/ordaClasses.md)|Server|use `local` keyword in function definition|| -|[User function](../Concepts/classes.md#function)|Client|-|| -|[Computed property function](../Concepts/classes.md#function-get-and-function-set)|Client|use `server` keyword in function definition|| -|[Shared or session singleton function](../Concepts/classes.md#singleton-classes)|Client|use `server` keyword in function definition|| -|Trigger|Server|-|| -|Project method called from a client|Client|*Execute on server* option|If on server, the code is executed in the twin process of the [user session process](./sessions.md#remote-user-sessions-remote-user-sessions)| -|Project method called from a client|Client|[`Execute on server`](../commands/execute-on-server) command|If on server, the code is executed in the [Stored procedures session](./sessions.md#stored-procedure-sessions-stored-procedure-sessions) | -|Project method called from a stored procedure on the server|Server|[`EXECUTE ON CLIENT`](../commands/execute-on-client) command|The target client must have been [registered](../commands/register-client) | -|Object method|Client|-|| -|Database methods:
  • On Server Startup
  • On Server Shutdown
  • On Server Open Connection
  • On Server Close Connection
|Server||| -|Database methods:
  • On Web Authentication
  • On Web Connection
  • On SQL Authentication
  • On Backup Startup
  • On Backup Shutdown
|Server or Client|-|Depends on the calling context| -|Database methods:
  • On Startup
  • On Exit
  • On Drop
|Client|-|| \ No newline at end of file +The following table summarizes where the code is executed by default and how to switch its execution location (if allowed). Note that **local** means that the code will be executed on the machine from where it is actually called. + +|Code|Default execution|How to switch| +|---|---|---| +|[ORDA data model functions](../ORDA/ordaClasses.md)|server|use `local` keyword in function definition| +|ORDA computed attribute functions [`get()`](../ORDA/ordaClasses.md#function-get-attributename), [`set()`](../ORDA/ordaClasses.md#function-set-attributename)|server|use `local` keyword in function definition| +|ORDA computed attribute functions [`query()`](../ORDA/ordaClasses.md#function-query-attributename), [`orderBy()`](../ORDA/ordaClasses.md#function-orderby-attributename)|server|n/a| +|ORDA event functions [(general)](../ORDA/orda-events.md)|server|n/a| +|ORDA event function [`constructor()`](../ORDA/ordaClasses.md#class-constructor-1)|local|n/a| +|ORDA event function [`event touched()`](../ORDA/orda-events.md#function-event-touched)|server|use `local` keyword in function definition| +|[User class functions](../Concepts/classes.md#function)|local|n/a| +|[Shared or session singleton function](../Concepts/classes.md#singleton-classes)|local|use `server` keyword in function definition| +|Trigger|server|n/a| +|Project method called from a client|client|check [**Execute on server** option](../Project/project-method-properties.md#execute-on-server). The code is executed in the twin process of the [user session process](./sessions.md#remote-user-sessions-remote-user-sessions)| +|||call [`Execute on server`](../commands/execute-on-server) command. The code is executed in the [Stored procedures session](./sessions.md#stored-procedure-sessions-stored-procedure-sessions) | +|Project method called from a stored procedure on the server|server|call [`EXECUTE ON CLIENT`](../commands/execute-on-client) command. The target client must have been [registered](../commands/register-client) | +|Object method|local|n/a| +|Database methods:
  • On Backup Shutdown
  • On Backup Startup
  • On Server Close Connection
  • On Server Open Connection
  • On Server Shutdown
  • On Server Startup
  • On SQL Authentication
  • On Web Authentication
  • On Web Connection
|server|n/a| +|Database methods:
  • On Startup
  • On Exit
  • On Drop
|client|n/a| \ No newline at end of file diff --git a/docs/ORDA/client-server-optimization.md b/docs/ORDA/client-server-optimization.md index 4c02de52577fc8..3c7d23d02bbf1f 100644 --- a/docs/ORDA/client-server-optimization.md +++ b/docs/ORDA/client-server-optimization.md @@ -147,3 +147,55 @@ By default, the ORDA cache is transparently handled by 4D. However, you can cont * [dataClass.getRemoteCache()](../API/DataClassClass.md#getremotecache) * [dataClass.clearRemoteCache()](../API/DataClassClass.md#clearremotecache) +### Using the `local` keyword + +By default, [ORDA data model functions](../ORDA/ordaClasses.md) are executed on the server, which usually provides the best performance since only the function request and the result are sent over the network. However, it could happen that a function processes data that's already in the local cache and is fully executable on the client side. In this case, you can save requests to the server and thus, enhance the application performance by [using the `local` keyword in the function definition](../Concepts/classes.md#local). + +Note that the function will work even if it eventually requires to access the server (for example if the ORDA cache is expired). However, it is highly recommended to make sure that the local function does not access data on the server, otherwise the local execution could not bring any performance benefit. A local function that generates many requests to the server is less efficient than a function executed on the server that would only return the resulting values. For example, consider the following function on the Schools entity class: + +```4d +// Get the youngest students +// Inappropriate use of local keyword +local Function getYoungest() : Object + return This.students.query("birthDate >= :1"; !2000-01-01!).orderBy("birthDate desc").slice(0; 5) +``` +- **without** the `local` keyword, the result is given using a single request +- **with** the `local` keyword, 4 requests are necessary: one to get the Schools entity students, one for the `query()`, one for the `orderBy()`, and one for the `slice()`. In this example, using the `local` keyword is inappropriate. + + +#### Example: Checking attributes + +We want to check the consistency of the attributes of an entity loaded on the client and updated by the user before requesting the server to save them. + +On the *StudentsEntity* class, the local `checkData()` function checks the Student's age: + +```4d +Class extends Entity + +local Function checkData() -> $status : Object + +$status:=New object("success"; True) +Case of + : (This.age()=Null) + $status.success:=False + $status.statusText:="The birthdate is missing" + + :((This.age() <15) | (This.age()>30) ) + $status.success:=False + $status.statusText:="The student must be between 15 and 30 - This one is "+String(This.age()) +End case +``` + +Calling code: + +```4d +var $status : Object + +//Form.student is loaded with all its attributes and updated on a Form +$status:=Form.student.checkData() +If ($status.success) + $status:=Form.student.save() // call the server +End if +``` + + diff --git a/docs/ORDA/orda-events.md b/docs/ORDA/orda-events.md index 06a73f3c95956e..245737a6bb5b75 100644 --- a/docs/ORDA/orda-events.md +++ b/docs/ORDA/orda-events.md @@ -52,11 +52,11 @@ You can also define the same event at both attribute and entity levels. The attr Usually, ORDA events are executed on the server. -In client/server configuration however, the `touched()` event function can be executed on the **server or the client**, depending on the use of [`local`](./ordaClasses.md#local-functions) keyword. A specific implementation on the client side allows the triggering of the event on the client. +In client/server configuration however, the `touched()` event function can be executed on the **server or the client**, depending on the use of [`local`](../Concepts/classes.md#local) keyword. A specific implementation on the client side allows the triggering of the event on the client. :::note -ORDA [`constructor()`](./ordaClasses.md#class-constructor) functions are always executed on the client. +ORDA [`constructor()`](./ordaClasses.md#class-constructor) functions are always executed locally. ::: @@ -67,11 +67,11 @@ With other remote configurations (i.e. [Qodly applications](https://developer.4d The following table lists ORDA events along with their rules. -| Event | Level | Function name | (C/S) Executed on |Can stop action by returning an error +| Event | Level | Function name | (C/S) Execution |Can stop action by returning an error | :------- |:------- | :----- | :-----: |---| -| Entity instantiation | Entity | [`constructor()`](./ordaClasses.md#class-constructor-1) | client | no| -| Attribute touched | Attribute | `event touched ()` | Depends on [`local`](../ORDA/ordaClasses.md#local-functions) keyword | no| -| | Entity | `event touched()` | Depends on [`local`](../ORDA/ordaClasses.md#local-functions) keyword | no| +| Entity instantiation | Entity | [`constructor()`](./ordaClasses.md#class-constructor-1) | local | no| +| Attribute touched | Attribute | `event touched ()` | Depends on [`local`](../Concepts/classes.md#local) keyword | no| +| | Entity | `event touched()` | Depends on [`local`](../Concepts/classes.md#local) keyword | no| |Before saving an entity|Attribute|`validateSave ()`|server|yes| ||Entity|`validateSave()`|server|yes| |When saving an entity|Attribute|`saving ()`|server|yes| From 2c842cd38e7d68e27fcbbde6e3d15eb803667e5a Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Tue, 14 Apr 2026 15:02:15 +0200 Subject: [PATCH 10/11] Update ordaClasses.md --- docs/ORDA/ordaClasses.md | 53 ---------------------------------------- 1 file changed, 53 deletions(-) diff --git a/docs/ORDA/ordaClasses.md b/docs/ORDA/ordaClasses.md index 1a54bd94a19549..cb2f7ef7e0c37b 100644 --- a/docs/ORDA/ordaClasses.md +++ b/docs/ORDA/ordaClasses.md @@ -1142,56 +1142,3 @@ It can be called by the following HTTP GET request: IP:port/rest/Products/getThumbnail?$params='["Yellow Pack",200,200]' ``` - - - - -## Support in 4D IDE - - -### Class files - -An ORDA data model user class is defined by adding, at the [same location as regular class files](../Concepts/classes.md#class-definition) (*i.e.* in the `/Sources/Classes` folder of the project folder), a .4dm file with the name of the class. For example, an entity class for the `Utilities` dataclass will be defined through a `UtilitiesEntity.4dm` file. - - -### Creating classes - -4D automatically pre-creates empty classes in memory for each available data model object. - -![](../assets/en/ORDA/ORDA_Classes-3.png) - - -> By default, empty ORDA classes are not displayed in the Explorer. To show them you need to select **Show all data classes** from the Explorer's options menu: -![](../assets/en/ORDA/showClass.png) - -ORDA user classes have a different icon from regular classes. Empty classes are dimmed: - - -![](../assets/en/ORDA/classORDA2.png) - -To create an ORDA class file, you just need to double-click on the corresponding predefined class in the Explorer. 4D creates the class file and add the `extends` code. For example, for an Entity class: - -``` -Class extends Entity -``` - -Once a class is defined, its name is no longer dimmed in the Explorer. - - -### Editing classes - -To open a defined ORDA class in the 4D Code Editor, select or double-click on an ORDA class name and use **Edit...** from the contextual menu/options menu of the Explorer window: - -![](../assets/en/ORDA/classORDA4.png) - -For ORDA classes based upon the local datastore (`ds`), you can directly access the class code from the 4D Structure window: - -![](../assets/en/ORDA/classORDA5.png) - - -### Code Editor - -In the 4D Code Editor, variables typed as an ORDA class automatically benefit from autocompletion features. Example with an Entity class variable: - -![](../assets/en/ORDA/AutoCompletionEntity.png) - From 58076d1fb883716735f838a1415e285c176e2358 Mon Sep 17 00:00:00 2001 From: arnaud-4d Date: Tue, 14 Apr 2026 15:21:49 +0200 Subject: [PATCH 11/11] Update updates.md --- docs/Notes/updates.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Notes/updates.md b/docs/Notes/updates.md index 8f2b4fc9b32906..58764306fba65c 100644 --- a/docs/Notes/updates.md +++ b/docs/Notes/updates.md @@ -12,6 +12,7 @@ title: Release Notes - Ability to use a custom certificate from the macOS keychain instead of a local certificates folder in [`HTTPRequest`](../API/HTTPRequestClass.md#4dhttprequestnew) and [`HTTPAgent`](../API/HTTPAgentClass.md#4dhttpagentnew) classes. - New [`4D.Method` class](../API/MethodClass.md) to create and execute a 4D method code from text source. [`METHOD Get path`](../commands/method-get-path) and [`METHOD RESOLVE PATH`](../commands/method-resolve-path) commands support a new `path volatile method` constant (128). - Remote [session](../API/SessionClass.md) objects are now [available client-side](../Desktop/sessions.md#availability). +- Support of [`server` keyword](../Concepts/classes.md#server) for ORDA data model functions and shared/session singleton functions. #### Support of Liquid glass on macOS