Skip to content

alt-javascript/jsnosqlc

Repository files navigation

JSNOSLQC — JDBC-Inspired NoSQL Access for JavaScript

Language License: MIT CI npm version

A uniform, async NoSQL access facade for JavaScript inspired by Java's JDBC — and by jsdbc, its SQL counterpart. Write database code once against jsnosqlc interfaces, then plug in any supported driver — in-memory, MongoDB, DynamoDB, Firestore, Cosmos DB, Redis, Cassandra, and more.

Part of the @alt-javascript ecosystem.

Why jsnosqlc?

JavaScript's NoSQL ecosystem is as fragmented as its SQL ecosystem. Every database — MongoDB, DynamoDB, Firestore, Redis, Cassandra — has its own incompatible API. jsnosqlc provides:

  • One API across databases. Switch from MongoDB to DynamoDB by changing a URL string
  • Chainable filter builder. Filter.where('age').gt(18).and('status').eq('active') — no raw query objects, no vendor-specific syntax
  • Driver auto-registration. Import a driver package and it registers itself — no manual setup
  • All-async. Every operation returns a Promise, idiomatic async/await throughout
  • Async iteration. Cursors support for await...of natively

Packages

Package Description Backend
@alt-javascript/jsnosqlc-core Interfaces: Driver, Client, Collection, Cursor, Filter, DriverManager Any
@alt-javascript/jsnosqlc-memory In-memory driver — zero dependencies, for testing and dev In-process
@alt-javascript/jsnosqlc-localstorage localStorage / sessionStorage driver — browser and Node.js (via MockStorage injection) Web Storage
@alt-javascript/jsnosqlc-mongodb MongoDB driver via mongodb MongoDB
@alt-javascript/jsnosqlc-dynamodb DynamoDB driver via @aws-sdk/client-dynamodb AWS DynamoDB
@alt-javascript/jsnosqlc-firestore Google Firestore driver via @google-cloud/firestore GCP Firestore
@alt-javascript/jsnosqlc-cosmosdb Azure Cosmos DB driver via @azure/cosmos Azure Cosmos DB
@alt-javascript/jsnosqlc-redis Redis driver via ioredis Redis
@alt-javascript/jsnosqlc-cassandra Apache Cassandra driver via cassandra-driver Cassandra

Quick Start: In-Memory Driver

npm install @alt-javascript/jsnosqlc-core @alt-javascript/jsnosqlc-memory
import { DriverManager, Filter } from '@alt-javascript/jsnosqlc-core';
import '@alt-javascript/jsnosqlc-memory'; // self-registers

const client = await DriverManager.getClient('jsnosqlc:memory:');
const users = client.getCollection('users');

// Key-value ops
await users.store('u1', { name: 'Alice', age: 30, status: 'active' });
await users.store('u2', { name: 'Bob', age: 25, status: 'inactive' });

const alice = await users.get('u1');   // { name: 'Alice', age: 30, status: 'active' }
await users.delete('u2');

// Insert (auto-generated id)
const id = await users.insert({ name: 'Charlie', age: 35, status: 'active' });
const charlie = await users.get(id);

// Patch update (non-destructive)
await users.update('u1', { age: 31 });

// Find with filter
const filter = Filter.where('status').eq('active').and('age').gt(25).build();
const cursor = await users.find(filter);

// Cursor iteration options:
const docs = cursor.getDocuments(); // all at once
// or:
for await (const doc of cursor) {
  console.log(doc.name);
}

await client.close();

Quick Start: MongoDB Driver

npm install @alt-javascript/jsnosqlc-core @alt-javascript/jsnosqlc-mongodb
import { DriverManager, Filter } from '@alt-javascript/jsnosqlc-core';
import '@alt-javascript/jsnosqlc-mongodb'; // self-registers

const client = await DriverManager.getClient('jsnosqlc:mongodb://localhost:27017/myapp');
const products = client.getCollection('products');

await products.store('p1', { name: 'Widget', price: 9.99, tags: ['sale', 'new'] });

const filter = Filter.where('price').lt(20).and('tags').contains('sale').build();
const cursor = await products.find(filter);
const onSale = cursor.getDocuments();

await client.close();

Quick Start: DynamoDB Driver

npm install @alt-javascript/jsnosqlc-core @alt-javascript/jsnosqlc-dynamodb
import { DriverManager, Filter } from '@alt-javascript/jsnosqlc-core';
import '@alt-javascript/jsnosqlc-dynamodb'; // self-registers

// Real AWS DynamoDB (uses AWS SDK credential chain — env vars, ~/.aws/credentials, etc.)
const client = await DriverManager.getClient('jsnosqlc:dynamodb:us-east-1');

// DynamoDB Local (for development/testing)
const localClient = await DriverManager.getClient('jsnosqlc:dynamodb:us-east-1', {
  endpoint: 'http://localhost:8000',
  accessKeyId: 'local',
  secretAccessKey: 'local',
});

const orders = localClient.getCollection('orders');  // table created automatically

await orders.store('order-1', { customerId: 'c1', total: 99.50, status: 'pending' });
await orders.insert({ customerId: 'c2', total: 45.00, status: 'shipped' });

const filter = Filter.where('status').eq('pending').and('total').gt(50).build();
const cursor = await orders.find(filter);
const pendingOrders = cursor.getDocuments();

await localClient.close();

Filter Operators

Operator Example Description
eq .eq('Alice') Equal
ne .ne('inactive') Not equal
gt .gt(18) Greater than
gte .gte(18) Greater than or equal
lt .lt(100) Less than
lte .lte(100) Less than or equal
contains .contains('js') Array contains element, or string contains substring
in .in(['a', 'b', 'c']) Field is one of the values
nin .nin(['x', 'y']) Field is not one of the values
exists .exists(true) Field is present (true) or absent (false)

Compound filters — AND (implicit chaining):

Filter.where('age').gt(18)
  .and('status').eq('active')
  .and('country').eq('AU')
  .build()

OR compound:

Filter.or(
  Filter.where('status').eq('active').build(),
  Filter.where('status').eq('pending').build()
)
// { type: 'or', conditions: [ ...active..., ...pending... ] }

NOT negation:

Filter.where('status').eq('inactive').not()
// { type: 'not', condition: { type: 'condition', field: 'status', op: 'eq', value: 'inactive' } }

jsnosqlc URL Scheme

jsnosqlc:<subprotocol>:<connection-details>
URL Driver
jsnosqlc:memory: In-memory (no connection)
jsnosqlc:mongodb://host:port/database MongoDB
jsnosqlc:dynamodb:us-east-1 AWS DynamoDB
jsnosqlc:firestore:my-gcp-project Google Firestore
jsnosqlc:cosmosdb:local Azure Cosmos DB (local emulator)
jsnosqlc:cosmosdb:https://account.documents.azure.com:443/ Azure Cosmos DB (real)
jsnosqlc:redis://localhost:6379 Redis
jsnosqlc:cassandra:localhost:9042/keyspace Apache Cassandra

Running Tests

npm install

# CI-safe — core + memory, no external dependencies
npm test

# Integration — requires MongoDB and DynamoDB Local (or real AWS)
npm run test:integration

# Start local services via Docker
docker run --rm -d -p 27017:27017 mongo:7
docker run --rm -d -p 8000:8000 amazon/dynamodb-local:latest
docker run --rm -d -e FIRESTORE_PROJECT_ID=jsnosqlc-test -e PORT=8080 -p 8080:8080 mtlynch/firestore-emulator
docker run --rm -d -p 8081:8081 mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview
docker run --rm -d -p 6379:6379 redis:7
docker run --rm -d -p 9042:9042 cassandra:4

Quick Start: Google Firestore

npm install @alt-javascript/jsnosqlc-core @alt-javascript/jsnosqlc-firestore
import { DriverManager, Filter } from '@alt-javascript/jsnosqlc-core';
import '@alt-javascript/jsnosqlc-firestore'; // self-registers

// Emulator — set env var before importing the driver
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';

// Real GCP — set GOOGLE_APPLICATION_CREDENTIALS or pass keyFilename
const client = await DriverManager.getClient('jsnosqlc:firestore:my-gcp-project');
const users = client.getCollection('users');

await users.store('u1', { name: 'Alice', age: 30, tags: ['admin'] });
const filter = Filter.where('age').gt(25).and('tags').contains('admin').build();
const cursor = await users.find(filter);
const admins = cursor.getDocuments();

await client.close();

Quick Start: Azure Cosmos DB

npm install @alt-javascript/jsnosqlc-core @alt-javascript/jsnosqlc-cosmosdb
import { DriverManager, Filter } from '@alt-javascript/jsnosqlc-core';
import '@alt-javascript/jsnosqlc-cosmosdb'; // self-registers

// Local emulator (vnext-preview serves HTTP on 8081)
const client = await DriverManager.getClient('jsnosqlc:cosmosdb:local');

// Real Azure — provide your account endpoint and key
const prodClient = await DriverManager.getClient(
  'jsnosqlc:cosmosdb:https://myaccount.documents.azure.com:443/',
  { key: 'your-account-key', database: 'myapp' }
);

const items = client.getCollection('items');
await items.store('i1', { name: 'Widget', price: 9.99, category: 'tools' });

const filter = Filter.where('price').lt(20).and('category').eq('tools').build();
const cursor = await items.find(filter);
const cheap = cursor.getDocuments();

await client.close();

Quick Start: Redis

npm install @alt-javascript/jsnosqlc-core @alt-javascript/jsnosqlc-redis
import { DriverManager, Filter } from '@alt-javascript/jsnosqlc-core';
import '@alt-javascript/jsnosqlc-redis'; // self-registers

const client = await DriverManager.getClient('jsnosqlc:redis://localhost:6379');
const cache = client.getCollection('sessions');

await cache.store('sess-1', { userId: 'u1', token: 'abc123', active: true });

const filter = Filter.where('active').eq(true).build();
const cursor = await cache.find(filter); // ⚠ full scan — see docs
const activeSessions = cursor.getDocuments();

await client.close();

Redis find() limitation: The Redis driver performs a full collection scan and filters results in-memory. This is suitable for development and small datasets. For production filter queries, use RediSearch.

Quick Start: Apache Cassandra

npm install @alt-javascript/jsnosqlc-core @alt-javascript/jsnosqlc-cassandra
import { DriverManager, Filter } from '@alt-javascript/jsnosqlc-core';
import '@alt-javascript/jsnosqlc-cassandra'; // self-registers

// Keyspace is created automatically if it doesn't exist
const client = await DriverManager.getClient('jsnosqlc:cassandra:localhost:9042/myapp');
const events = client.getCollection('events');

await events.store('evt-1', { type: 'login', userId: 'u1', score: 10 });
await events.insert({ type: 'click', userId: 'u2', score: 5 });

const filter = Filter.where('score').gt(7).build();
const cursor = await events.find(filter); // full scan + in-memory filter
const highScore = cursor.getDocuments();

await client.close();

Cassandra find() limitation: Documents are stored as JSON in a single column. Non-pk field filtering is done client-side after a full table scan. For production, model your tables to support your access patterns natively in CQL.

Browser Quick-Start

jsnosqlc includes ESM bundles for @alt-javascript/jsnosqlc-memory and @alt-javascript/jsnosqlc-localstorage that work directly in the browser via <script type="module"> — no bundler or build step required.

LocalStorage driver (persistent browser storage)

<script type="module">
  import { DriverManager, Filter } from
    'https://unpkg.com/@alt-javascript/jsnosqlc-localstorage/dist/jsnosqlc-localstorage.esm.js';

  const client = await DriverManager.getClient('jsnosqlc:localstorage:');
  const users = client.getCollection('users');

  await users.store('u1', { name: 'Alice', age: 30 });
  const alice = await users.get('u1');   // { name: 'Alice', age: 30 }

  const id = await users.insert({ name: 'Bob', age: 25 });

  const filter = Filter.where('age').gte(25).build();
  const cursor = await users.find(filter);
  console.log(cursor.getDocuments()); // [Alice, Bob]

  await client.close();
</script>

Documents survive page reloads — stored in localStorage with a namespaced key scheme (clientId:collection:docKey).

SessionStorage driver

Replace the URL — documents are cleared when the tab closes:

const client = await DriverManager.getClient('jsnosqlc:sessionstorage:');

In-memory driver (browser — no persistence)

<script type="module">
  import { DriverManager } from
    'https://unpkg.com/@alt-javascript/jsnosqlc-memory/dist/jsnosqlc-memory.esm.js';

  const client = await DriverManager.getClient('jsnosqlc:memory:');
  // same API — data is lost on page reload
</script>

Isomorphic usage (Node.js and browser, same code)

In Node.js, inject a MockStorage in place of the browser's localStorage:

import { DriverManager, MockStorage } from '@alt-javascript/jsnosqlc-localstorage';

const client = await DriverManager.getClient('jsnosqlc:localstorage:', {
  storageBackend: new MockStorage(), // compatible in Node.js, Jest, Mocha, etc.
});
// identical API to the browser version

Filter Operators

Writing a jsnosqlc Driver

See the Driver Guide and @alt-javascript/jsnosqlc-memory for a complete minimal example. The pattern:

  1. Extend Driver, implement acceptsURL(url) and connect(url, props): Promise<Client>
  2. Extend Client, implement _getCollection(name): Collection and _close()
  3. Extend Collection, implement _get, _store, _delete, _insert, _update, _find
  4. At the bottom of your driver file: DriverManager.registerDriver(new MyDriver())

Run runCompliance(clientFactory) from @alt-javascript/jsnosqlc-core/test/driverCompliance.js to verify your driver against the full compliance suite.

Documentation

Contributing

git clone https://github.com/alt-javascript/jsnosqlc.git
cd jsnosqlc
npm install

npm test                  # CI-safe: core + memory + localstorage (106 tests, no external deps)
npm run build:browser     # build ESM bundles for core, memory, and localstorage
npm run test:browser      # Playwright browser integration test (requires Chrome)
npm run test:integration  # all server drivers (requires Docker)

See Driver Guide for instructions on contributing a new driver.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors