Skip to content

Repository files navigation

WASH

Overview

WASH is an experimental data-modeling and application development platform, conceived in the spirit of 4GL and low-code development platforms, with the goal of enabling relatively simple data-driven applications to be developed simply. Its core offering is a set of primitives oriented around the storage and retrieval of JSON documents (similar to those you might find in many existing document databases), and a declarative data-definition language for constructing an application in terms of these primitives.

This repository provides a very basic reference implementation of a WASH runtime for purposes of experimentation and research. It is implemented in .NET and embeds Microsoft's ESE database for persistence.

Note

NOT INTENDED FOR PRODUCTION USE

The runtime is an early-stage prototype that I built to explore my interests in DSLs and databases. It provides no authentication, authorization, or request-level security, and likely contains bugs and incomplete error handling. Many features are incomplete.

Concepts

Primitives

The following table summarizes the primitives provided by WASH. (For those more familiar with the world of relational databases, the equivalent or most closely analogous relational concept is also shown.)

WASH primitive RDB analogy
document record
document class table schema
document collection table
field column
default value column default value
formula computed column
sequence sequence
constraint constraint
view materialized view
index index
query query
method stored procedure
application model database schema

A more detailed discussion of these is available here.

Data Definition Language

WASH application models are specified using a declarative data-definition language. The language makes use of embedded JavaScript fragments to express some aspects of application logic.

For a sense of what the language looks like, here's an example definition of a document class that represents a "Patient" in an electronic medical record application:

document {
	// MRN = medical record number (i.e. patient ID)
	field mrn string required sequence(function(state) {
		return (state.i ? ++state.i : (state.i = 100000)).toString();
	});

	field name composite {
		field lastName string required;
		field firstName string required;
		field middleName string;
	} required;
	
	field fullName string formula(function(doc) {
		if(!doc.name) return null;
		return doc.name.lastName + '^' + doc.name.firstName
			+ (doc.name.middleName ? '^' + doc.name.middleName : '');
	});
	
	field birthDate date;
	
	field addresses list[composite {
			field type enum AddressType required;
			field street string;
			field city string;
			field province string;
			field country string;
		} required default({ type: "home", country: "CA", province: "ON" })
	] required default([]);
	
	field phones list[composite {
			field type enum PhoneType required;
			field countryCode string;
			field areaCode string;
			field number string;
			field extension string;
		} required default({ type: "home", countryCode: "1" })
	] required default([]);
	
	constraint mustHaveAtLeastOneAddress 
		when(function(doc) { return doc.active; })
		assert(function(doc) { 
			return [doc.addresses.length > 0,
				"Patient must have at least one address."];
		});
		
	constraint mustHaveAtLeastOnePhone 
		when(function(doc) { return doc.active; })
		assert(function(doc) { 
			return [doc.phones.length > 0,
				"Patient must have at least one phone number."];
		});
	
	index mrn unique {
		field mrn asc;
	}
	
	index fullName {
		field fullName asc;
	}
	
	query byMrn mrn
		seek mrn ~ @mrn;
	
	query byName fullName
		seek fullName ~ @name;
}

Runtime

The WASH runtime provided in this repository runs on .NET and consists of the following components:

  • Wash.dll: Core library that provides the WASH runtime functionality.
  • Wash.Cli.exe: A basic CLI application for creating and interacting with local WASH applications. (This is provided primarily for testing and development purposes, and does not reflect how WASH would be used in a real-world scenario.)
  • Wash.Web.exe: A mini "application server" for WASH applications that exposes application functionality over a set of HTTP-REST endpoints. This gets closer to demonstrating how WASH is envisioned being used in a real-world scenario.

Quickstart

Note

The runtime embeds Microsoft's ESE database to provide persistent storage, and therefore only runs on Windows (unfortunately). This may change in future.

Prerequisites: .NET SDK (tested with .NET 10).

From the repository root run:

dotnet build

Note that the application reads configuration from src/appsettings.shared.json and src/appsettings.json. The locations of application model files and storage databases are configured there.

Run the CLI application with the registry list command, just to verify that it runs and is able to load the config files:

dotnet run --project src/Wash.Cli -- registry list

Note: when running through the dotnet host, arguments intended for the application must follow a standalone double dash --.

If successful, you should see output similar to the following:

Using launch settings from src\Wash.Cli\Properties\launchSettings.json...
Loading config files from: C:\path\to\code\wash\src\Wash.Cli\bin\Debug\net10.0\
Framework script root: C:\path\to\code\wash\src\js\framework
App script root: C:\path\to\code\wash\samples\apps
Meta storage: C:\path\to\code\wash\temp\meta
App storage: C:\path\to\code\wash\temp\storage

Example Application: Electronic Medical Record (EMR)

The repository includes a sample application model for a toy Electronic Medical Record system at samples/apps/emr. WASH object definitions live in .w files that are loaded by the runtime when an application model is deployed.

1. Create an EMR application instance

To deploy an application model, you must create an application instance:

dotnet run --project src/Wash.Cli -- registry add emr1 emr

This creates an application instance named emr1 based on the emr application model.

2. Populate the emr1 database with some sample data

dotnet run --project src/Wash.Cli -- import emr1 ..\..\samples\apps\emr\backup.txt

3. Start an interactive session with emr1

dotnet run --project src/Wash.Cli -- app emr1

Try running some commands in the interactive session:

  1. Insert a new patient.

    a. First run list Patient to obtain a list of the existing patients.

    b. Copy the JSON document for an existing patient to the clipboard.

    c. Type insert Patient, and paste the copied content into the terminal, but don’t press Enter yet.

    d. Use the arrow keys to modify some aspects of the JSON document. Change the name and delete the mrn.

    e. Press Enter to submit the command.

  2. Insert a new order by following the same steps as in #1.

  3. Use the Patient.anyMrn view to lookup a patient by one of its alternate MRNs:

    query Patient.anyMrn byMrn { "mrn" : "12333" }

  4. Use the Order.procedure view to find ordered procedures of a specific type:

    query Order.procedure byProcedureCode { "code" : "MR-1022" }

  5. Call the mergePatients method to merge "Janet Doe" into "Jane Doe":
    a. Enter the following command:

    call mergePatients { "sourceId": "88f2581bbb2f49d999379065e02e7d43", "destId": "e6adfe2d7b204aa69ef15230154982d0" }

    b. Use the list and/or get commands to inspect the source and dest patients, and their associated orders.

  6. Type quit to exit the interactive session.

  7. The utils/reset_emr1.bat file can be used to reset the application to its initial state with only the imported data.

Commands available in the interactive session are documented here.

Using the web application server

Run the following command to start the web server:

dotnet run --project src/Wash.Web

In the terminal output, look for the URL where the server is listening e.g.

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5089

Use your favourite HTTP client tool (e.g. Postman, curl) to send requests to the web server.

The HTTP API endpoints are documented here.

Documentation

Future work

  • JavaScript: Migrate to a modern version of JavaScript.
  • Fields:
    • Expand the number of supported data types.
    • Introduce an immutable modifier that prevents a field from being modified.
  • Indexes: Increase the flexibility of indexes (e.g. support indexing of sub-fields in composites and lists).
  • Queries: Support more powerful query capabilities (e.g. paging, ordering, aggregations).
  • Definition Language:
    • Offer more concise ways to achieve common use cases (e.g. simply marking a field as "queryable" rather than having to explicitly define an index and a query).
    • Promote reuse by allowing objects such as composites, sequences, and formulas to be defined outside of a document scope.
  • Database:
    • Replace ESE with a database that works across platforms.
    • Allow applications more control over transactions and concurrency.
  • Internals: The project goals have evolved since its inception, and some of the internal architecture reflects earlier needs that are no longer relevant. Reworking some of these internals would simplify the code and allow it to evolve more cleanly.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

An experimental data-modeling and application development platform

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages