The Qworum script, specification version 1

Summary

Qworum is:

  • a module system for Web applications, where modules may be located anywhere on the Web.
  • a distributed Web application technology.
  • a platform for interactive microservices which bring their own full-page UIs.
  • the Service Web that complements the HTML-based Document Web and the RDF-based Semantic Web.
  • an upgrade to the World Wide Web for improved application support.
  • an advanced Web browser feature that websites can use.

Qworum defines:

  • a new top-level Web format called the Qworum script. These contain executable code rather than content as HTML does. Web browsers can receive Qworum scripts from Web servers in an XML document. Web pages can also generate Qworum scripts using JavaScript.
  • an interpreter for Qworum scripts. This client-side software can be implemented by Web browsers natively or as a browser extension.
  • a new client-side storage for storing the execution state of each browser tab. This storage can use IndexedDB behind the scenes.

Qworum is suitable for both:

  • Jamstack ♦︎ frontends that are made up of static files, and
  • the more legacy Web frontends that are dynamically generated on the server for each user.

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>
      
1.1: Example of a Qworum script received by a Web browser

2. Terminology

2.1. Phase

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

Phase
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
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.

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>
        
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.


// Use Qworum
import { Qworum } from "path/to/qworum-for-web-pages.mjs";
const
// Qworum script
Script       = Qworum.Script,
// Qworum instructions
Call         = Qworum.Call,
Goto         = Qworum.Goto,
Return       = Qworum.Return,
Sequence     = Qworum.Sequence,
Fault        = Qworum.Fault,
Try          = Qworum.Try,
Data         = Qworum.Data,
// Qworum data value types
Json         = Qworum.Json,
SemanticData = Qworum.SemanticData;

// Example 1: Call the `home` end-point
Qworum.eval(
  Script(Call('@', 'home/'))
);

// Example 2: Return from the current call
/* 
Qworum.eval(
  Script(
    Return(Json(['a', 'b']))
  )
);
*/
        
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.

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.

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.
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.
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.

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"]' />
        
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"]' />
        
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"]' />
        
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 path 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 data 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>
        
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>
        
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 path 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/' />
        
3.2.1: A call instruction

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

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>
      
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>
      
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>
        
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' />
        
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>
        
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.

In Qworum, exceptions are called "faults". A fault can be raised either explicitly by a Qworum application or service (that is, the current Qworum script contains a fault instruction which is evaluated), or implicitly by the Qworum runtime.

When a fault is raised during the execution of an instruction, the current call is terminated unless it is caught by an enclosing try instruction in the current Qworum script. When a fault terminates the current call, the parent call in turn has a chance of catching the fault. If none of the calls in the call stack have caught the fault, then this will terminate the Qworum application.

fault 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.

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

3.6.1. Fault types

3.6.1.1. Service faults

service faults are faults that are raised due to programming errors in Qworum applications and services, or they are faults whose handling is the responsibility of Qworum applications and services.

This fault type has the following subtypes:

  • script — The Qworum script has syntax errors or it contains a platform fault.
  • origin — A phase of a Qworum object does not have the same origin ♦︎ as the Qworum object itself. (The origin of a Qworum object is the origin of the first call that is performed on the Qworum object.)
  • data — Reading data did not succeed because the data container has not yet been initialised; in this case the data container can be initialised by catching this fault.
  • path — The path of a data container or a Qworum object could not be resolved, because one of the strings (except the last one) in the path points to a non-existing Qworum object. This could indicate that a Qworum service that should have been called beforehand wasn't (remember that Qworum objects are only created when performing a service call).
  • entitlement — Raised when a website tries using a Qworum feature which it isn't entitled to. These faults are typically raised when evaluating a call instruction.
  • service-specific — Used for faults which are specific to a particular Qworum service or application. Subtypes of this type are prefixed with *. The use of subtypes is encouraged.

The entitlement fault type has the following subtypes:

  • platform entitlement — The 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.
  • service entitlement — The service that is being called is a paid service, and the caller isn't subscribed to the called service.
3.6.1.2. User-agent faults

user-agent faults are raised when an unexpected run-time error occurs in the user agent. This fault type has the following subtype:

  • runtime — Implementation error of the Qworum specification by the user agent.

3.6.2. Platform faults vs service-specific faults

Scripts are only allowed to contain faults whose type is service-specific or a subtype of service-specific.

All faults that are not service-specific are called platform faults, and these are raised by the Qworum runtime itself when evaluating a script.

The evaluation of a script will raise a script fault at run-time if it explicitly contains:

  • a platform fault, or
  • a fault that is neither a platform fault nor a service-specific fault.

<sequence>
  <!-- BUG -->
  <fault type='origin' />

  <!-- BUG -->
  <fault type='unknown' />

  <!-- Correct -->
  <fault type='* payment cancelled' />
</sequence>
        
3.6.2.1: This non-conforming script will raise a script fault

Although scripts can only contain service-specific faults, they can catch both platform faults and service-specific faults by using the try instruction.

3.7. try

The try instruction is used for catching faults that are raised when evaluating the instruction it contains (it can also contain a data value, which never raises a fault).

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='["data"]'>
    <!-- Initialise the data. -->
    <data path='["shopping cart line items"]'>
      <data:json>[]</data:json>
    </data>
  </catch>
</try>
        
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>
        
4.1.1: Example of json data

4.2. semantic

Represents semantic 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>
4.2.1: Example of semantic data written in Turtle/TriG

<!-- The end-user's shopping cart contents (2 items). -->
<data:semantic>
  <![CDATA[

    # Line items
    _:lineItems <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/ItemList> .
    _:lineItems <https://schema.org/itemListElement> _:lineItem1 .
    _:lineItems <https://schema.org/itemListElement> _:lineItem2 .

    # Line item 1
    _:lineItem1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Product> .
    _:lineItem1 <https://schema.org/name> 'Alpha trainers' .
    _:lineItem1 <https://schema.org/productID> "1" .
    _:lineItem1 <https://schema.org/offers> _:lineItemPrice1 .

    # Line item 2
    _:lineItem2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Product> .
    _:lineItem2 <https://schema.org/name> "XYZ Boots" .
    _:lineItem2 <https://schema.org/productID> "2" .
    _:lineItem2 <https://schema.org/offers> _:lineItemPrice2 .

    # Prices
    _:lineItemPrice1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Offer> .
    _:lineItemPrice1 <https://schema.org/price> "45.95" .
    _:lineItemPrice1 <https://schema.org/priceCurrency> "EUR" .

    _:lineItemPrice2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Offer> .
    _:lineItemPrice2 <https://schema.org/price> "75.95" .
    _:lineItemPrice2 <https://schema.org/priceCurrency> "EUR" .

  ]]>
</data:semantic>
4.2.2: Example of semantic data written in N-Triples/N-Quads

Semantic data is data that uses the vocabularies that are defined by the Semantic Web ♦︎. In these vocabularies each term has a precise meaning that is understood by any software that understands those particular vocabularies.

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/>
5.1.1: Incorrect way of implementing hyperlinks in a Qworum session

<!-- Correct -->
<button onclick="window.location.replace('new_url')">
  A hyperlink
<button />
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>
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>
5.2.2: Correct way of sending forms during a Qworum session

6. References