- WASH
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.
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.
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;
}
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.
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 buildNote 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 listNote: 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
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.
To deploy an application model, you must create an application instance:
dotnet run --project src/Wash.Cli -- registry add emr1 emrThis creates an application instance named emr1 based on the
emr application model.
dotnet run --project src/Wash.Cli -- import emr1 ..\..\samples\apps\emr\backup.txtdotnet run --project src/Wash.Cli -- app emr1Try running some commands in the interactive session:
-
Insert a new patient.
a. First run
list Patientto 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
nameand delete themrn.e. Press Enter to submit the command.
-
Insert a new order by following the same steps as in #1.
-
Use the
Patient.anyMrnview to lookup a patient by one of its alternate MRNs:query Patient.anyMrn byMrn { "mrn" : "12333" } -
Use the
Order.procedureview to find ordered procedures of a specific type:query Order.procedure byProcedureCode { "code" : "MR-1022" } -
Call the
mergePatientsmethod to merge "Janet Doe" into "Jane Doe":
a. Enter the following command:call mergePatients { "sourceId": "88f2581bbb2f49d999379065e02e7d43", "destId": "e6adfe2d7b204aa69ef15230154982d0" }b. Use the
listand/orgetcommands to inspect the source and dest patients, and their associated orders. -
Type
quitto exit the interactive session. -
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.
Run the following command to start the web server:
dotnet run --project src/Wash.WebIn 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.
- JavaScript: Migrate to a modern version of JavaScript.
- Fields:
- Expand the number of supported data types.
- Introduce an
immutablemodifier 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.
This project is licensed under the MIT License - see the LICENSE file for details.