The Qworum script, specification version 1

Summary

Qworum is a variant of the World Wide Web that is especially suitable for applications.

This specification is intended for the developers of Qworum applications and services.

1. Introduction

The World Wide Web ♦︎ was initially conceived as a decentralised information system consisting of interlinked documents, yet its potential as a platform for applications soon became apparent. The ongoing effort that has been undertaken by the ICT community since the Web's early days in order to better support Web applications has sofar produced various client-side and server-side upgrades to the initial Web, such as JavaScript and application servers.

Yet there is room for improvement, particularly regarding the decentralisation of the front-ends of Web applications. In today's Web, an application is contained within a single Web origin (example: https://example.com) ♦︎, and frequently even within a single Web page. This limits the amount of functionality that can be shared amongst application developer teams, and results in considerable duplication of effort. (One notable exception to this picture is OAuth-based services which can be called remotely by applications.)

Qworum enables new gains in productivity for the software community by making it possible to build Web applications that are composed of interactive services ("Qworum services") that are hosted anywhere on the Web and that are callable remotely.

It is worth noting that Qworum services are of practical use even if the caller of a service has the same Web origin as the called service. This is because Qworum services can be inserted in any user flow without disrupting it, similarly to functions in conventional programming. For example, with Qworum an application no longer needs to pass a return path to the login dialog when it requires the end-user to sign in before accessing a restricted page. Again, this is similar to conventional programming where a function does not know how the execution proceeds when its own execution finishes.

As a foretaste, here is how an e-commerce site might call a remote shopping cart service:


<!-- Qworum script received by the Web browser from an e-commerce site -->
<sequence xmlns="https://qworum.net/ns/v1/instruction/">

  <!-- 1. Show the user's shopping cart-->
  <call object='["@", "shopping cart"]' href="https://shopping-cart.example/view/" />

  <!-- 2. Go back to the e-commerce site -->
  <goto href="/home/" />
</sequence>
      
Figure 1.1: Example of a Qworum script received by a Web browser.

2. Terminology

2.1. Phase

An HTTP(S) ♦︎ request-response pair.

Phase
Figure 2.1.1: Phase.

2.2. Single-Phase Service Call

A call to a Web API that consists of a single phase. Calls to Web APIs that conform to conventional specifications such as REST ♦︎ or XML-RPC ♦︎ are always single-phase calls.

Single-Phase Service Call
Figure 2.2.1: Single-Phase Service Call.

2.3. Multi-Phase Service Call

A call to a Web API that consists of one or more phases. Calls to Web APIs that conform to the Qworum specification are multi-phase calls.

During a multi-phase call, the first request that is sent by the user agent to the server contains 0, 1 or more call arguments. The service then returns a response that:

  • contains a call result (in which case the call is a single-phase call), or
  • allows the user agent to initiate a second phase for the current call.

In this manner, each phase of a multi-phase call can choose to return a result or continue the current call with another phase.

Figure 2.3.1: Example of a Multi-Phase Service Call.

Qworum mandates that each phase of a particular multi-phase call have the same Web origin, otherwise an * origin fault will be raised during run-time.

2.4. The Qworum Script

Qworum scripts contain instructions that are to be executed by user agents such as Web browsers. The root element of Qworum scripts must be an instruction. Qworum scripts can also contain data values.

2.4.1 XML scripts

Users agents can receive Qworum scripts from Web servers in XML documents ♦︎ that have the content type application/xml or text/xml.


<!-- Script received by the Web browser from an e-commerce site. -->
<sequence xmlns="https://qworum.net/ns/v1/instruction/">

  <!-- Show the user's shopping cart. -->
  <call object='["@", "shopping cart"]' href="https://shopping-cart.example/view/" />

  <!-- Go back to the e-commerce site. -->
  <goto href="/home/" />
</sequence>
        
Figure 2.4.1.1: Example of a Qworum script.

2.4.2 Generating Qworum scripts on the client

Web pages can also use JavaScript for generating and executing Qworum scripts. To this end, Qworum makes the qworum-for-web-pages.mjs ♦︎ JavaScript library available to Web developers.

Generating and executing Qworum scripts on the client in this way is equivalent to the user agent receiving an XML Qworum script from the URL of the HTML page.


// Add an article to a shopping cart.
import { QworumScript, Qworum } from 'https://esm.sh/gh/doga/qworum-for-web-pages@1.7.0/mod.mjs';

const
Script   = QworumScript.Script.build,
Sequence = QworumScript.Sequence.build,
Call     = QworumScript.Call.build,
Goto     = QworumScript.Goto.build,
Json     = QworumScript.Json.build,
article  = {
  "id"   : "8b1d5802",
  "title": "Classic ankle boots",
  "price": {"EUR": 29.99}
},
script = Script(
  Sequence(
    Call(
      ['@', 'shopping cart'], 'https://shopping-cart.example/add-article/',
      {name : 'article', value: Json({article})}
    ),
    Goto('index.html')
  )
),
button = document.getElementById('add-to-cart-button');

button.addEventListener('click', async () => {
  await Qworum.eval(script);
});
        
Figure 2.4.2.1: Using JavaScript for generating a Qworum script in a Web page.

2.5. Service Composition

The mechanism by which multi-phase calls perform other nested multi-phase calls during their execution. Nested calls are performed between two consecutive phases of the call that initiated the nested call.

When a call is to perform a nested call, one of its phases returns a script that contains a call instruction. These nested calls are then performed by the user agent between two consecutive phases of the call that initiated the nested call.

Figure 2.5.1: Example of a nested service call.

2.6. Interactive Service

A multi-phase service that can interact with the end-user through HTML ♦︎ pages during its execution.

Figure 2.6.1: An interactive service call.

For example, a shopping cart service may provide a method that shows shopping cart contents to the end-user by offering this type of interactivity.

2.7. Execution State

In Qworum, each Web browser tab has its own execution state, which consists of:

  • A call stack that contains one or more call frames. The topmost call frame pertains to the call currently being executed; the remaining call frames contain the states of calls whose executions are currently suspended. Call frames are used for storing call states in the form of datas (which point to data) and objects.
  • A main object that is attached to the browser tab. The main object is the owner of the first call frame in the call stack.

A regular browsing session on the World Wide Web can be thought of as a multi-phase service call that doesn't use the Qworum-related functionality that the user agent provides (except that browsing different Web origins within the same browser tab does not raise an * origin fault). In particular:

  • regular Web browsing happens within the bounds of the first call frame in the call stack, and
  • the main object remains unused during a regular Web browsing session.
Figure 2.7.1: The execution state of a browser tab.

2.8. Qworum Object

In conventional object-oriented programming, objects are used for storing state that is shared between multiple calls to one or more functions, which are the object's methods. Qworum is porting this concept into the Web environment in the form of Qworum objects.

A Qworum object, then, is a container for run-time state that is shared between different calls. Qworum objects are used for storing datas (which point to data) and other Qworum objects.

Qworum objects are needed for storing the states of services such as shopping carts that must remember their contents across different calls incoming from an e-commerce site for example. In the absence of Qworum objects, Qworum would only be able to support services where each incoming call is independent from one another, which is the case for user authentication services and payment processing services among others.

Here is how Qworum objects are used in practice:

  • Each multi-phase call has a Qworum object that is the owner of the call. When initiating a call, the call's owner object is created by the user agent if it does not already exist in the browser tab's execution state.
  • Each Qworum object has a Web origin which is determined when the object is first used as the owner of a call. All phases of all calls which are owned by a Qworum object must have the object's origin, otherwise an * origin fault will be raised during run-time.
  • Qworum objects can be stored in call frames, or as the main object of an execution state.
Figure 2.8.1: The Qworum object.

2.9. Qworum Class

Qworum does not mandate that all Qworum objects stored in an execution state have different origins. It follows that calls to the same URL can be made using different owner objects, even within the same browser tab. This is how the concept of Qworum classes emerges; not as a browser feature, but rather as an aid for designing Qworum-based applications and services.

Formally, we can define Qworum objects to be instances of Qworum classes, and also that each Web origin can host any number of Qworum classes.

Figure 2.9.1: Qworum classes and objects.

Going back to our shopping cart example, we can imagine an e-commerce site that allows its users to create several shopping carts on the same shopping cart service, for example one "Home" cart, one "Work" cart and one "New year's eve party" cart.

3. Instructions

This section lists the instructions that are available to Qworum scripts. When evaluated by the user agent, all instructions will yield a value (which is data), except for the goto and fault instructions.

Qworum instructions that conform to this specification must have the XML namespace ♦︎ https://qworum.net/ns/v1/instruction/.

Unless otherwise stated, in the XML code examples below all elements whose tags don't have a prefix have the namespace https://qworum.net/ns/v1/instruction/, and all elements whose tags have the prefix data have the namespace https://qworum.net/ns/v1/data/.

3.1. data

The data instruction allows writing to, and reading from, a data container that is stored in an execution state. The current call has read/write access to data in the following locations:

  • the current call frame,
  • the current call's owner object,
  • nested objects contained in the current call's owner object.

The location of the data container that is being accessed is determined by data's mandatory path attribute. This attribute's value is a JSON-encoded array of strings. In these strings, the leading and trailing whitespace characters are ignored.

Within this array, some strings are treated differently by the user agent:

  • any string that starts with @ denotes the current call's owner object, and
  • strings starting with $ or # are reserved and should not be used at this time.

3.1.1. Reading from a data container

Here is an example of reading data from a data container:


<!-- Read the "shopping cart line items" data that is stored locally in the current call. -->
<data path='["shopping cart line items"]' />
        
Figure 3.1.1.1: Reading data that is stored in the current call.

Another example:


<!-- Read the "shopping cart line items" data that is stored in the current call's owner object. -->
<data path='["@", "shopping cart line items"]' />
        
Figure 3.1.1.2: Reading data that is stored in the current call's owner object.

And another:


<!-- Read the "line items" data that is stored in the "shopping cart" object -->
<!-- that is in turn stored in the current call's owner object. -->
<data path='["@", "shopping cart", "line items"]' />
        
Figure 3.1.1.3: Reading data that is stored in the current call's owner object.

When trying to read from a data container that is located inside a Qworum object, if the Qworum object does not exist then a * reference fault will be raised at run-time.

If a data container does not contain a data value (that is, the container has not yet been initialised), then trying to read from it will raise a * reference fault.

3.1.2. Writing to a data container

When writing to a data container, the data instruction must contain one instruction or data value. Here is an example:


<!-- Clear the "shopping cart line items" data that is stored locally in the current call. -->
<data path='["shopping cart line items"]'>
  <data:json>[]</data:json>
</data>
        
Figure 3.1.2.1: Writing data to a data container.

And another:


<!-- Write the line items received from the shopping cart service -->
<!-- to the "shopping cart line items" data that is stored in the current call. -->
<data path='["shopping cart line items"]'>
  <call object='["@", "shopping cart"]' href='https://shopping-cart.example/view/' />
</data>
        
Figure 3.1.2.2: Writing a call result to a data.

When trying to write to a data container that is located in a Qworum object, if the Qworum object does not exist then a * reference fault will be raised at run-time.

3.2. call

The call instruction initiates a multi-phase service call. It has two optional attributes:

  • The object attribute specifies the path of the object that owns the call within the execution state. The value of the object attribute is a JSON-encoded array of strings (some string values are reserved). If this attribute is omitted, then the new call's owner will be the owner of the current call.
  • The href attribute specifies the URL of the call's first phase. If this attribute is omitted, then this URL will be the browser tab's current URL.

<call object='["@", "shopping cart"]' href='view/' />
        
Figure 3.2.1: A call instruction.

Some of the faults that service calls can raise are * origin and * reference.

Service calls generate an HTTP(S) GET request when evaluated by the browser.

Service calls can have data arguments and/or object arguments.

3.2.1. Calls with data arguments

Service calls can have named data arguments, each argument containing one instruction or data. If a data argument contains an instruction, then the instruction will be evaluated before executing the call.

Data arguments will be will be made available to the new call on the client side as local data with the same names as the data arguments.


<call object='["@","shopping cart"]' href='/add-line-items/index.xml'>
  <data-args>
    <data-arg name='line items'> 
      <data:json>
        [{
          "title"   : "Classic ankle boots",
          "price"   : {"EUR": 29.99},
          "quantity": 1
        }]
      </data:json>
    </data-arg>
  </data-args>
</call>
      
Figure 3.2.1.1: A call instruction with arguments.

3.2.2. Calls with object arguments

Service calls can receive Qworum objects as arguments. These object arguments will be made available to the new call on the client side as local objects that have the same names as the object arguments.


<call object='["@","shopping cart"]' href='/add-line-items/index.xml'>
  <object-args>
    <!-- The caller is making its logger available to the service it is calling. -->
    <object-arg name='logger' object='["@","logger"]' /> 
  </object-args>
</call>
      
Figure 3.2.2.1: A call instruction that contains object arguments.

3.3. return

The return instruction returns the current multi-phase service call. This instruction must contain one instruction or data element.


<return>
  <data path='["@","line items"]' />
</return>
        
Figure 3.3.1: Returning data as a result of the current call.

If the current call is the main call of the current browser tab, then this instruction will terminate the tab's execution.

3.4. goto

The goto instruction starts a new phase for the current multi-phase service call. It has one optional attribute:

  • The href attribute specifies the URL of the new phase. If this attribute is omitted, then this URL will be the browser tab's current URL.

<goto href='return.xml' />
        
Figure 3.4.1: A goto instruction that issues a GET request.

The goto instruction will raise an * origin fault if the object that the current call belongs to has a different origin ♦︎ than this goto phase's origin. This is because all phases of all service calls belonging to an object must have the same origin URL.

3.5. sequence

The sequence instruction contains one or more instructions or data elements, each of which is evaluated in turn. This instruction will yield the evaluation result of the last instruction/data in the sequence.


<!-- A shopping cart service directs the user to a remote payment processor. -->
<sequence>

  <!-- 1. Call the payment processing service. -->
  <!-- 2. Store the returned transaction details. -->
  <data path='["@","latest transaction"]'>
    <call object='["@","payment service"]' href="https://payment-processor.example/pay/">
      <data-args>
        <data-arg name="amount to pay">
          <data:json>
            {
              "amount"  : 98.99,
              "currency": "EUR",
            }
          </data:json>
        </data-arg>
      </data-args>
    </call>
  </data>

  <!-- 3. Empty the shopping cart, because payment has succeeded (otherwise a fault would have been raised). -->
  <data path='["@","line items"]'>
    <data:json>[]</data:json>
  </data>

  <!-- 4. Go to the next phase of the current shopping cart method. -->
  <goto href="paid.html" />

</sequence>
        
Figure 3.5.1: A sequence example.

The sequence will not yield a result in the following cases:

  • the sequence contains a goto, or
  • a fault is raised when evaluating one of the sequence's instructions, or
  • the sequence contains a fault instruction.

3.6. fault

In computer programming, exceptions are used for disrupting the normal course of execution, because an exceptional event has occurred which prevents the program to proceed as intended. Exceptions are called "faults" in Qworum parlance.

The fault instruction has one optional attribute:

  • The type attribute specifies the type of the raised fault. If this attribute is omitted, then the fault type will be * service-specific. If specified, the type value must not start with *.

<fault type='payment cancelled' />
        
Figure 3.6.1: A fault.

The type attribute's value has the following equivalent forms:

  • Case-insensitive — payment cancelled and Payment Cancelled are equivalent.
  • Separator whitespace is collapsed into one space character — payment cancelled and payment   cancelled are equivalent.
  • Whitespace at the beginning and the end are ignored — payment cancelled and     payment cancelled    are equivalent.

Faults are split into two categories:

  • Platform faults are faults whose type value starts with *. These can only be raised by the Qworum runtime itself. This means that a Qworum script must not contain a fault instruction whose type is explicitly specified as being a platform fault.
  • Service-specific faults are faults that are defined by the Qworum services themselves for their specific use-cases (Example: login aborted ). Qworum scripts are allowed to contain and explicitly raise such faults. The type value of such faults must not start with *.

3.6.1. Fault types

Here is a visual overview of the predefined faults:

Figure 3.6.1.1: Qworum fault hierarchy.

Service-specific faults:

  • argument is the only predefined service-specific fault. A service call can raise this fault if a call argument is non-conformant or absent.

Platform faults:

  • * entitlement — Raised when a Qworum service tries to use a Qworum feature that it isn't entitled to. These faults are typically raised when evaluating a call instruction.
  • * origin — Raised when a phase of a Qworum object does not have the same origin ♦︎ as the Qworum object itself.
  • * platform fault in script — Raised when an XML Qworum script contains a platform fault. Note that this fault isn't raised for Qworum scripts that are generated in web pages using the official Qworum JavaScript library, in which case a TypeError is thrown instead.
  • * platform entitlement — Raised when a service that is being called isn't part of Qworum's Service Web. This may be because the service is not subscribed to a Qworum platform plan, or the current subscription isn't sufficient and needs to be upgraded.
  • * reference — Raised when the path of a data container or Qworum object can't be resolved.
  • * runtime — Raised when an unexpected error occurs in Qworum's browser runtime.
  • * script — Raised when a Qworum script is non-conformant.
  • * service — Parent type of all "user space"♦︎ faults that are caused by a Qworum service in a Qworum session.
  • * service entitlement — The service that is being called is a paid service, and the caller isn't subscribed to the called service.
  • * service-specific — The parent type of all service-specific faults.
  • * syntax — Raised when a Qworum script has syntax errors.
  • * user agent — Parent type of all "kernel space"♦︎ faults that occur in the user agent.

Note there must be whitespace after *.

3.7. try

When an instruction in a Qworum script raises a fault, then the current call will terminate unless the fault is caught by a try instruction.

The try instruction contains one or more catch clauses, each of which contains one or more instructions or data elements. Catch clauses have one optional attribute:

  • The faults attribute is a JSON-encoded array of strings that indicate the fault types that are caught by the clause. If this attribute is omitted or if the faults array is empty, then the clause will catch all faults.

<!-- Yield the value of a data; initialise it if needed. -->
<try>
  <data path='["shopping cart line items"]' />

  <catch faults='["* reference"]'>
    <!-- Initialise the data. -->
    <data path='["shopping cart line items"]'>
      <data:json>[]</data:json>
    </data>
  </catch>
</try>
        
Figure 3.7.1: A try instruction used to initialise a data .

The try instruction will yield the result of the last instruction or data value it evaluates.

4. Data values

An XML element represents a data value if:

  • its namespace is https://qworum.net/ns/v1/data/, and the XML element conforms to this specification.

4.1. json

Represents JSON-encoded data ♦︎.


<!-- E-shop sends a new item to add to the end-user's shopping cart. -->
<data:json>
  {
    "productID": "2",
    "name"     : "XYZ Boots",
    "offers"   : {
      "price"        : "75.95",
      "priceCurrency": "EUR"
    }
  }
</data:json>
        
Figure 4.1.1: Example of json data.

4.2. semantic

Represents semantic RDF♦︎ data. This data can be specified in one of the following formats:


<!-- E-shop sends a new item to add to the end-user's shopping cart. -->
<data:semantic>
  <![CDATA[

    PREFIX : <https://schema.org/>

    []
      a :ItemList;
      :itemListElement [
        a :Product;
        :productID "2";
        :name "XYZ Boots";
        :offers [
          a :Offer;
          :price "75.95";
          :priceCurrency "EUR"
        ]
      ].

  ]]>
</data:semantic>
Figure 4.2.1: Example of semantic data written in Turtle/TriG.

5. Workarounds for browser limitations

The developers of Qworum applications and services must observe some simple rules for their Qworum-based software to work properly on browsers. These rules are necessary because Qworum is implemented as a browser extension, and web browsers impose certain constraints on what extensions can do. One notable restriction is that extensions are not allowed to prevent the end-user from going back in the tab history.

The following programming constraints will prevent the application UI from going out of sync with the application's session state.

In HTML pages, use window.location.replace() for hyperlinks.


<!-- BUG -->
<a href="new_url">
  A hyperlink
<a/>
Figure 5.1.1: Incorrect way of implementing hyperlinks in a Qworum session.

<!-- Correct -->
<button onclick="window.location.replace('new_url')">
  A hyperlink
<button />
Figure 5.1.2: Correct way of implementing hyperlinks in a Qworum session.

5.2. Forms

In HTML pages, use AJAX rather than web forms for sending data to servers.


<!-- BUG -->
<form action="/my-handling-form-page" method="post">
  <ul>
    <li>
      <label for="name">Name:</label>
      <input type="text" id="name" name="user_name" />
    </li>
    <li>
      <label for="mail">E-mail:</label>
      <input type="email" id="mail" name="user_email" />
    </li>
    <li>
      <label for="msg">Message:</label>
      <textarea id="msg" name="user_message"></textarea>
    </li>
    <li class="button">
      <button type="submit">Send your message</button>
    </li>
  </ul>
</form>
Figure 5.2.1: Incorrect way of sending forms during a Qworum session.

<!-- Correct -->
<ul>
  <li>
    <label for="name">Name:</label>
    <input type="text" id="name" name="user_name" />
  </li>
  <li>
    <label for="mail">E-mail:</label>
    <input type="email" id="mail" name="user_email" />
  </li>
  <li>
    <label for="msg">Message:</label>
    <textarea id="msg" name="user_message"></textarea>
  </li>
  <li class="button">
    <button onclick="submit()">Send your message</button>
  </li>
</ul>
Figure 5.2.2: Correct way of sending forms during a Qworum session.

6. References