ORT-NODEJS(1)

NAME
ort-nodejs - generate node.js module

SYNOPSIS
ort-nodejs [config...]

DESCRIPTION
Accepts ort(5) config files, defaulting to standard input, and generates a Node module in TypeScript.

The output requires only the "typescript" and "better-sqlite3" dependencies. The output is documented using the typedoc(1) language.

  Module
A ort-nodejs context is initialised using the ort(dbname: string): ortdb function from the application as follows.

    const db: ortdb = ort('path/to/data.db');

The path/to/data.db must be an existing SQLite3 database usually created with ort-sql(1). There should be one ortdb module instanced per application. It has the following members:

connect(): ortctx
    Connect to the database. This should be invoked for each request. In applications not having a request, this corresponds to a single operator sequence.

version: string
    The version of ort-nodejs used to create the module.

vstamp: number
    The numeric version of ort-nodejs used to create the module.

Database manipulation occurs with ortctx objects, which consist of the following:

db_role(newrole: string): void
    If roles are enabled, move from the current role to newrole. If the role is the same as the current role, this does nothing. Roles may only transition to ancestor roles, not descendant roles or siblings, or any other non-ancestor roles. The only exception is when leaving "default" or entering "none". This does not return failure: on role violation, it invokes process.abort().

db_role_open_current(id: number): string
    If roles are enabled, get the currently-assigned role. If db_role() hasn't yet been called, this will be "default". Throws an exception on database error.

db_trans_open_immediate(id: number): void
    Open a transaction with a unique identifier id. This is the preferred way of creating database transactions. The transaction immediately enters unshared lock mode (single writer, readers allowed). Throws an exception on database error.

db_trans_open_deferred(id: number): void
    Open a transaction with a unique identifier id. The transaction locks the database on first access with shared locks (no writes allowed, reads allowed) on queries and unshared locks (single writer, reads allowed) on modification. Throws an exception on database error.

db_trans_exclusive(id: number): void
    Open a transaction with a unique identifier id. The transaction locks exclusively, preventing all other access. Throws an exception on database error.

db_trans_commit(id: number): void
    Commit a transaction opened by db_trans_open_immediate(), db_trans_open_deferred(), or db_trans_open_exclusive() with identifier id. Throws an exception on database error.

db_trans_rollback(id: number): void
    Roll-back a transaction opened by db_trans_open_immediate(), db_trans_open_deferred(), or db_trans_open_exclusive() with identifier id. Throws an exception on database error.

  Data access
Each structure has a number of operations available in ortctx. These are all stipulated as count, delete, insert, iterate, list, search, and update statements in the configuration. Let "foo" be the name of the exemplar structure. All names are expressed in lowercase unless as explicitly noted. All of these functions will throw an exception on database error.

db_foo_delete_xxxx(ARGS): void
    Run the named delete function "xxxx". The ARGS passed to this function are the fields that constrain which rows are deleted. Parameters are only specified for operations for binary-operator constraints, i.e., those not checking for null status.

db_foo_delete_by_yy_op(ARGS): void
    Like db_foo_delete_xxxx(), but using an un-named delete statement constrained by "yy" with operation "op".

db_foo_get_xxxx(ARGS): ortns.foo|null
    The search statement named "xxxx". The function accepts variables for all binary-operator fields to check (i.e., all except for those checking for null).

db_foo_get_by_xxxx_op1_yy_zz_op2(ARGS): ortns.foo|null
    Like db_foo_get_xxxx(), but for (possibly-nested) structures. In the given example, "xxxx" is a field in the given structure with operation "op1" and "yy_zz" means a field "zz" in the nested structure "yy" with operation "op2".

db_foo_insert(ARGS): BigInt
    Insert a row and return its identifier or -1 on constraint failure. This accepts all native fields ARGS as parameters excluding rowid, which is automatically set by the database. If any fields are specified as null, they are passed into this functions as pointers. The null values must then be specified as NULL pointers. This function is only generated if the insert statement is specified for the given structure.

db_foo_iterate(ARGS, cb): void
    Like db_foo_iterate_xxxx() but iterating over all rows.

db_foo_iterate_xxxx(ARGS, cb): void
    Like db_foo_get_xxxx(), but invoking a function callback per row. The cb callback accepts a single parameter of type ortns.foo and does not have a return value.

void db_foo_iterate_by_xxxx_op1_yy_zz_op2(ARGS, cb): void
    Like db_foo_get_by_xxxx_op1_yy_zz_op2(), but invoking a function callback for each retrieved result.

db_foo_count(): BigInt
    Like db_foo_count_xxxx() but returning a count of all rows.

db_foo_count_xxxx(ARGS): BigInt
    Like db_foo_get_xxxx(), but returning a count of responses.

db_foo_count_by_xxxx_op1_yy_zz_op2(ARGS): BigInt
    Like db_foo_get_by_xxxx_op1_yy_zz_op2(), but returning a count of responses.

db_foo_list(): ortns.foo[]
    Like db_foo_list_xxxx() but allocating and filling a queue of all rows.

db_foo_list_xxxx(ARGS): ortns.foo[]
    Like db_foo_get_xxxx(), but producing an array of responses.

db_foo_list_by_xxxx_op1_yy_zz_op2(ARGS): ortns.foo[]
    Like db_foo_get_by_xxxx_op1_yy_zz_op2(), but producing a queue of responses.

db_foo_update_xxxx(ARGS): boolean
    Run the named update function "xxxx". The update functions are specified with update statements. The parameters passed to this function are first the fields to modify, then the fields that constrain which rows are updated. Update fields are only specified for operations for binary-operator constraints, i.e., those not checking for null status. Returns true on success, false on constraint failure.

db_foo_update_xx_mod_by_yy_op(ARGS): boolean
    Like db_foo_update_xxxx(), but using an un-named update statement modifying "xx" with modifier "mod" constrained by "yy" with operation "op". Either or both modifiers and constraints may be empty. If modifiers are empty, all fields are modified by setting. If constraints are empty, they and the preceding "by" are omitted.

The data objects returned by these functions are in the ortns namespace and are named as in the configuration. Letting "foo" be an exemplar structure name, the object consists of the following.

obj: ortns.fooData
    The read-only data itself.

export(): any
    Create an exportable object. Export rules are governed by the role in which the object was created. This is usually used with JSON.stringify() to output JSON objects.

The exported object, when converted into a string, is readable by applications using the ort-javascript(1) tool.

  Data structures
Assuming the exemplar structure "foo", the data in Data access may be manipulated with the ortns.fooData obj object interface in ortns.foo. All names (field names, structures, enumerations, etc.) are expressed in lowercase unless as explicitly noted. This consists of all fields in the structure with types mapped as follows:
bit | BigInt
date | BigInt
epoch | BigInt
int | BigInt
real | number
blob | Buffer
text | string
email | string
password | string
bits | BigInt

Structures are mapped to their interfaces, such as ortns.barData for a structure named "bar". Enumerations are mapped to enumerations defined similarly, such as ortns.baz for an enumeration named "baz". The enumeration values are all string literals of their numeric value.

If a field is marked as null, it will also be given the null type.

EXIT STATUS

EXAMPLES
The following example is a full web-server running on port 3000 using the Node framework. It uses the "express", framework for web requests, "bcrypt" for passwords, and "better-sqlite3" for the database. It mandates the use of TypeScript instead of JavaScript. It needs only the npm(1) system installed and (depending on the operating system) a C/C++ compiler for "better-sqlite3".

Begin a project (if not already begun) as follows:

    % cd myproject
    % npm init -y
    % npm install typescript better-sqlite3 express bcrypt
    % npm install @types/express @types/bcrypt @types/better-sqlite3
    % npx tsc --init

If installing "better-sqlite3" or "bcrypt" on OpenBSD, you may need to specify an alternate compiler:

    % CXX=/usr/local/bin/clang++ \
      CC=/usr/local/bin/clang \
      npm install better-sqlite3 bcrypt

Modify package.json to mandate the use of TypeScript instead of JavaScript:

    [...]
    "main": "index.ts",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "tsc": "tsc"
    }
    [...]

Next, modify tsconfig.json to use a more up-to-date output type for JavaScript, otherwise many TypeScript security idioms will not be available.

    "target": "es2015",

Now use the following toy ort(5) configuration installed as myproject.ort:

    roles {
      role user;
    };
    struct user {
      field name text;
      field id int rowid;
      insert;
      search id: name id;
      roles default { all; };
    };

Compile the configuration as a module:

    % mkdir modules
    % ort-nodejs myproject.ort > modules/ort.ts

Use the following simple application:

    import express, { Request, Response } from 'express';
    import { ort, ortns, ortctx, ortdb } from './modules/ort';
    const app = express();
    const db: ortdb = ort('test.db');
    app.get("/put", function(req: Request, res: Response) {
      const ctx: ortctx = db.connect();
      const id: BigInt = ctx.db_user_insert('name');
      res.send(id.toString());
    });
    app.get("/get", function(req: Request, res: Response) {
      const ctx: ortctx = db.connect();
      const obj: ortns.user|null =
        ctx.db_user_get_id(BigInt(1));
      if (obj !== null)
        res.send(JSON.stringify(obj.export()));
      else
        res.send('not found');
    });
    app.listen(3000, function() {
      console.log('Server is running.');
    });

Compile the application. This will create index.js.

    % npm run tsc

Make sure that the database exists. This should only be run once.

    % ort-sql db.ort | sqlite3 test.db

Lastly, run the project itself:

    % node index.js
    Server is running.

Making an HTTP request to "localhost:3000" will result in a display of the created user's identifier.

SEE ALSO
node(1), npm(1), ort(5)