diff --git a/Enterprise/package.json b/Enterprise/package.json
new file mode 100644
index 0000000..3dbc1ca
--- /dev/null
+++ b/Enterprise/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/Enterprise/static/404.html b/Enterprise/static/404.html
new file mode 100644
index 0000000..586632d
--- /dev/null
+++ b/Enterprise/static/404.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Error 404: File not found
+
+
+
+ File not found
+
+
diff --git a/Enterprise/static/application.js b/Enterprise/static/application.js
new file mode 100644
index 0000000..116e39c
--- /dev/null
+++ b/Enterprise/static/application.js
@@ -0,0 +1,68 @@
+import { Database } from './database.js';
+import { UserRepository, UserService } from './user.js';
+
+class Logger {
+ #output;
+
+ constructor(outputId) {
+ this.#output = document.getElementById(outputId);
+ }
+
+ log(...args) {
+ const lines = args.map(Logger.#serialize);
+ this.#output.textContent += lines.join(' ') + '\n';
+ this.#output.scrollTop = this.#output.scrollHeight;
+ }
+
+ static #serialize(x) {
+ return typeof x === 'object' ? JSON.stringify(x, null, 2) : x;
+ }
+}
+
+const logger = new Logger('output');
+
+const action = (id, handler) => {
+ const element = document.getElementById(id);
+ if (element) element.onclick = handler;
+};
+
+const db = new Database('EnterpriseApplication', 1, (db) => {
+ if (!db.objectStoreNames.contains('user')) {
+ db.createObjectStore('user', { keyPath: 'id', autoIncrement: true });
+ }
+});
+await db.connect();
+const userRepository = new UserRepository(db, 'user');
+const userService = new UserService(userRepository);
+
+action('add', async () => {
+ const name = prompt('Enter user name:');
+ const age = parseInt(prompt('Enter age:'), 10);
+ if (!name || !Number.isInteger(age)) return;
+ const user = await userService.createUser(name, age);
+ logger.log('Added:', user);
+});
+
+action('get', async () => {
+ const users = await userRepository.getAll();
+ logger.log('Users:', users);
+});
+
+action('update', async () => {
+ try {
+ const user = await userService.incrementAge(1);
+ logger.log('Updated:', user);
+ } catch (err) {
+ logger.log(err.message);
+ }
+});
+
+action('delete', async () => {
+ await userRepository.delete(2);
+ logger.log('Deleted user with id=2');
+});
+
+action('adults', async () => {
+ const adults = await userService.findAdults();
+ logger.log('Adults:', adults);
+});
diff --git a/Enterprise/static/core.js b/Enterprise/static/core.js
new file mode 100644
index 0000000..90bdce7
--- /dev/null
+++ b/Enterprise/static/core.js
@@ -0,0 +1,50 @@
+export class Repository {
+ constructor(database, storeName) {
+ this.db = database;
+ this.storeName = storeName;
+ }
+
+ insert(record) {
+ return this.db.exec(this.storeName, 'readwrite', (store) =>
+ store.add(record),
+ );
+ }
+
+ getAll() {
+ return this.db.exec(this.storeName, 'readonly', (store) => {
+ const req = store.getAll();
+ return new Promise((resolve, reject) => {
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => reject(req.error);
+ });
+ });
+ }
+
+ get(id) {
+ return this.db.exec(this.storeName, 'readonly', (store) => {
+ const req = store.get(id);
+ return new Promise((resolve, reject) => {
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => reject(req.error);
+ });
+ });
+ }
+
+ update(record) {
+ return this.db.exec(this.storeName, 'readwrite', (store) =>
+ store.put(record),
+ );
+ }
+
+ delete(id) {
+ return this.db.exec(this.storeName, 'readwrite', (store) =>
+ store.delete(id),
+ );
+ }
+}
+
+export class Service {
+ constructor(repository) {
+ this.repository = repository;
+ }
+}
diff --git a/Enterprise/static/database.js b/Enterprise/static/database.js
new file mode 100644
index 0000000..ebc1a6b
--- /dev/null
+++ b/Enterprise/static/database.js
@@ -0,0 +1,46 @@
+export class Database {
+ #db;
+
+ constructor(name, version = 1, upgradeCallback) {
+ this.name = name;
+ this.version = version;
+ this.upgradeCallback = upgradeCallback;
+ }
+
+ async connect() {
+ return new Promise((resolve, reject) => {
+ const request = indexedDB.open(this.name, this.version);
+
+ request.onupgradeneeded = (event) => {
+ const db = event.target.result;
+ if (this.upgradeCallback) this.upgradeCallback(db);
+ };
+
+ request.onsuccess = () => {
+ this.#db = request.result;
+ resolve();
+ };
+
+ request.onerror = () => reject(request.error);
+ });
+ }
+
+ transaction(storeName, mode = 'readonly') {
+ const tx = this.#db.transaction(storeName, mode);
+ return tx.objectStore(storeName);
+ }
+
+ exec(storeName, mode, operation) {
+ return new Promise((resolve, reject) => {
+ try {
+ const tx = this.#db.transaction(storeName, mode);
+ const store = tx.objectStore(storeName);
+ const result = operation(store);
+ tx.oncomplete = () => resolve(result);
+ tx.onerror = () => reject(tx.error);
+ } catch (err) {
+ reject(err);
+ }
+ });
+ }
+}
diff --git a/Enterprise/static/favicon.ico b/Enterprise/static/favicon.ico
new file mode 100644
index 0000000..1b75888
Binary files /dev/null and b/Enterprise/static/favicon.ico differ
diff --git a/Enterprise/static/index.html b/Enterprise/static/index.html
new file mode 100644
index 0000000..bd10db2
--- /dev/null
+++ b/Enterprise/static/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ IndexedDB Exampe
+
+
+
+ IndexedDB Repository Example
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Enterprise/static/styles.css b/Enterprise/static/styles.css
new file mode 100644
index 0000000..194793e
--- /dev/null
+++ b/Enterprise/static/styles.css
@@ -0,0 +1,37 @@
+body {
+ font-family: system-ui, sans-serif;
+ padding: 1rem;
+ background: #f4f4f4;
+ color: #222;
+}
+
+h1 {
+ margin-bottom: 1rem;
+}
+
+.controls {
+ margin-bottom: 1rem;
+}
+
+button {
+ margin-right: 0.5rem;
+ padding: 0.5rem 1rem;
+ font-size: 1rem;
+ cursor: pointer;
+}
+
+pre#output {
+ position: fixed;
+ top: 10rem;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow: auto;
+ margin: 0;
+ padding: 1rem;
+ background: #111;
+ color: #0f0;
+ font-family: monospace;
+ font-size: 0.9rem;
+ white-space: pre-wrap;
+}
diff --git a/Enterprise/static/user.js b/Enterprise/static/user.js
new file mode 100644
index 0000000..5ef9126
--- /dev/null
+++ b/Enterprise/static/user.js
@@ -0,0 +1,41 @@
+import { Repository, Service } from './core.js';
+
+export class UserModel {
+ constructor(name, age) {
+ if (typeof name !== 'string' || name.trim().length === 0) {
+ throw new Error('Invalid name');
+ }
+ if (!Number.isInteger(age) || age < 0) {
+ throw new Error('Invalid age');
+ }
+ this.name = name.trim();
+ this.age = age;
+ }
+}
+
+export class UserRepository extends Repository {
+ constructor(database) {
+ super(database, 'user');
+ }
+}
+
+export class UserService extends Service {
+ async createUser(name, age) {
+ const user = new UserModel(name, age);
+ await this.repository.insert(user);
+ return user;
+ }
+
+ async incrementAge(id) {
+ const user = await this.repository.get(id);
+ if (!user) throw new Error('User with id=1 not found');
+ user.age += 1;
+ await this.repository.update(user);
+ return user;
+ }
+
+ async findAdults() {
+ const users = await this.repository.getAll();
+ return users.filter((user) => user.age >= 18);
+ }
+}
diff --git a/Enterprise/test/core.js b/Enterprise/test/core.js
new file mode 100644
index 0000000..c716dc6
--- /dev/null
+++ b/Enterprise/test/core.js
@@ -0,0 +1,41 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import 'fake-indexeddb/auto';
+import { Database } from '../static/database.js';
+import { Repository, Service } from '../static/core.js';
+
+test('Enterprise: Repository', async () => {
+ const db = new Database('RepoTestDB', 1, (db) => {
+ db.createObjectStore('items', { keyPath: 'id', autoIncrement: true });
+ });
+ await db.connect();
+
+ const repo = new Repository(db, 'items');
+
+ const item = { name: 'Item1' };
+ await repo.insert(item);
+
+ const items = await repo.getAll();
+ assert.equal(items.length, 1);
+ assert.equal(items[0].name, 'Item1');
+
+ const id = items[0].id;
+ const one = await repo.get(id);
+ assert.equal(one.name, 'Item1');
+
+ one.name = 'Item1Updated';
+ await repo.update(one);
+
+ const updated = await repo.get(id);
+ assert.equal(updated.name, 'Item1Updated');
+
+ await repo.delete(id);
+ const afterDelete = await repo.getAll();
+ assert.equal(afterDelete.length, 0);
+});
+
+test('Enterprise: Service', () => {
+ const fakeRepo = { insert: () => {}, get: () => {} };
+ const service = new Service(fakeRepo);
+ assert.equal(service.repository, fakeRepo);
+});
diff --git a/Enterprise/test/database.js b/Enterprise/test/database.js
new file mode 100644
index 0000000..c0e22e7
--- /dev/null
+++ b/Enterprise/test/database.js
@@ -0,0 +1,34 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import 'fake-indexeddb/auto';
+import { Database } from '../static/database.js';
+import { Repository, Service } from '../static/core.js';
+import { UserModel, UserRepository, UserService } from '../static/user.js';
+
+test('Enterprise: Database CRUD + queries', async () => {
+ const db = new Database('TestDB', 1, (db) => {
+ if (!db.objectStoreNames.contains('user')) {
+ db.createObjectStore('user', { keyPath: 'id', autoIncrement: true });
+ }
+ });
+ await db.connect();
+
+ const repo = new Repository(db, 'user');
+ const marcus = new UserModel('Marcus', 28);
+
+ await repo.insert(marcus);
+ const allUsers = await repo.getAll();
+ assert.equal(allUsers.length, 1);
+ assert.equal(allUsers[0].name, 'Marcus');
+
+ const user = await repo.get(1);
+ user.age = 29;
+ await repo.update(user);
+
+ const updated = await repo.get(1);
+ assert.equal(updated.age, 29);
+
+ await repo.delete(1);
+ const empty = await repo.getAll();
+ assert.equal(empty.length, 0);
+});
diff --git a/Enterprise/test/user.js b/Enterprise/test/user.js
new file mode 100644
index 0000000..bacdd92
--- /dev/null
+++ b/Enterprise/test/user.js
@@ -0,0 +1,42 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import 'fake-indexeddb/auto';
+import { Database } from '../static/database.js';
+import { Repository, Service } from '../static/core.js';
+import { UserModel, UserRepository, UserService } from '../static/user.js';
+
+test('Enterprise: UserModel validation', async () => {
+ assert.throws(() => new UserModel('', 25), /Invalid name/);
+ assert.throws(() => new UserModel('Faustina', -1), /Invalid age/);
+ assert.throws(() => new UserModel('Lucius', 3.14), /Invalid age/);
+
+ const user = new UserModel('Titus', 42);
+ assert.ok(user instanceof UserModel);
+ assert.strictEqual(user.name, 'Titus');
+ assert.strictEqual(user.age, 42);
+});
+
+test('Enterprise: UserService, UserRepository', async () => {
+ const db = new Database('ServiceTestDB', 1, (db) => {
+ if (!db.objectStoreNames.contains('user')) {
+ db.createObjectStore('user', { keyPath: 'id', autoIncrement: true });
+ }
+ });
+ await db.connect();
+
+ const userRepo = new UserRepository(db, 'user');
+ const userService = new UserService(userRepo);
+
+ await userService.createUser('Lucius', 17);
+ await userService.createUser('Antoninus', 33);
+ await userService.createUser('Faustina', 18);
+
+ const adults = await userService.findAdults();
+ assert.equal(adults.length, 2);
+ assert.ok(adults.some((user) => user.name === 'Antoninus'));
+
+ const updatedUser = await userService.incrementAge(2);
+ assert.equal(updatedUser.age, 34);
+
+ await assert.rejects(() => userService.incrementAge(999), /User with id=1 not found/);
+});
diff --git a/Native/package.json b/Native/package.json
new file mode 100644
index 0000000..3dbc1ca
--- /dev/null
+++ b/Native/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/Native/static/404.html b/Native/static/404.html
new file mode 100644
index 0000000..586632d
--- /dev/null
+++ b/Native/static/404.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Error 404: File not found
+
+
+
+ File not found
+
+
diff --git a/Native/static/application.js b/Native/static/application.js
new file mode 100644
index 0000000..e8735bb
--- /dev/null
+++ b/Native/static/application.js
@@ -0,0 +1,92 @@
+class Logger {
+ #output;
+
+ constructor(outputId) {
+ this.#output = document.getElementById(outputId);
+ }
+
+ log(...args) {
+ const lines = args.map(Logger.#serialize);
+ this.#output.textContent += lines.join(' ') + '\n';
+ this.#output.scrollTop = this.#output.scrollHeight;
+ }
+
+ static #serialize(x) {
+ return typeof x === 'object' ? JSON.stringify(x, null, 2) : x;
+ }
+}
+
+const logger = new Logger('output');
+
+const db = await new Promise((resolve, reject) => {
+ const request = indexedDB.open('Example', 1);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ if (!db.objectStoreNames.contains('user')) {
+ db.createObjectStore('user', { keyPath: 'id', autoIncrement: true });
+ }
+ };
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+});
+
+document.getElementById('add').onclick = () => {
+ const name = prompt('Enter user name:');
+ if (!name) return;
+ const age = parseInt(prompt('Enter age:'), 10);
+ if (!Number.isInteger(age)) return;
+ const tx = db.transaction('user', 'readwrite');
+ tx.objectStore('user').add({ name, age });
+ tx.oncomplete = () => logger.log('Added:', { name, age });
+ tx.onerror = () => logger.log('Add failed');
+};
+
+document.getElementById('get').onclick = () => {
+ const tx = db.transaction('user', 'readonly');
+ const store = tx.objectStore('user');
+ const req = store.getAll();
+ req.onsuccess = () => logger.log('Users:', req.result);
+ req.onerror = () => logger.log('Get failed');
+};
+
+document.getElementById('update').onclick = () => {
+ const tx = db.transaction('user', 'readwrite');
+ const store = tx.objectStore('user');
+ const req = store.get(1);
+ req.onsuccess = () => {
+ const user = req.result;
+ if (!user) {
+ logger.log('User with id=1 not found');
+ return;
+ }
+ user.age += 1;
+ store.put(user);
+ tx.oncomplete = () => logger.log('Updatued:', user);
+ };
+ req.onerror = () => logger.log('Update failed');
+};
+
+document.getElementById('delete').onclick = () => {
+ const tx = db.transaction('user', 'readwrite');
+ tx.objectStore('user').delete(2);
+ tx.oncomplete = () => logger.log('Deleted user with id=2');
+ tx.onerror = () => logger.log('Delete failed');
+};
+
+document.getElementById('adults').onclick = () => {
+ const tx = db.transaction('user', 'readonly');
+ const store = tx.objectStore('user');
+ const req = store.openCursor();
+ const adults = [];
+ req.onsuccess = (event) => {
+ const cursor = event.target.result;
+ if (!cursor) {
+ logger.log('Adults:', adults);
+ return;
+ }
+ const user = cursor.value;
+ if (user.age >= 18) adults.push(user);
+ cursor.continue();
+ };
+ req.onerror = () => logger.log('Adult query failed');
+};
diff --git a/Native/static/favicon.ico b/Native/static/favicon.ico
new file mode 100644
index 0000000..1b75888
Binary files /dev/null and b/Native/static/favicon.ico differ
diff --git a/Native/static/index.html b/Native/static/index.html
new file mode 100644
index 0000000..bd10db2
--- /dev/null
+++ b/Native/static/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ IndexedDB Exampe
+
+
+
+ IndexedDB Repository Example
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Native/static/styles.css b/Native/static/styles.css
new file mode 100644
index 0000000..194793e
--- /dev/null
+++ b/Native/static/styles.css
@@ -0,0 +1,37 @@
+body {
+ font-family: system-ui, sans-serif;
+ padding: 1rem;
+ background: #f4f4f4;
+ color: #222;
+}
+
+h1 {
+ margin-bottom: 1rem;
+}
+
+.controls {
+ margin-bottom: 1rem;
+}
+
+button {
+ margin-right: 0.5rem;
+ padding: 0.5rem 1rem;
+ font-size: 1rem;
+ cursor: pointer;
+}
+
+pre#output {
+ position: fixed;
+ top: 10rem;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow: auto;
+ margin: 0;
+ padding: 1rem;
+ background: #111;
+ color: #0f0;
+ font-family: monospace;
+ font-size: 0.9rem;
+ white-space: pre-wrap;
+}
diff --git a/Pragmatic/static/application.js b/Pragmatic/static/application.js
index c5e0372..3ee086b 100644
--- a/Pragmatic/static/application.js
+++ b/Pragmatic/static/application.js
@@ -31,20 +31,20 @@ const actions = {
const age = parseInt(prompt('Enter age:'), 10);
if (!Number.isInteger(age)) return;
const user = { name, age };
- await db.insert('user', user);
+ await db.insert({ store: 'user', record: user });
logger.log('Added:', user);
},
get: async () => {
- const users = await db.select('user');
+ const users = await db.select({ store: 'user' });
logger.log('Users:', users);
},
update: async () => {
- const user = await db.get('user', { id: 1 });
+ const user = await db.get({ store: 'user', id: 1 });
if (user) {
user.age += 1;
- await db.update('user', user);
+ await db.update({ store: 'user', record: user });
logger.log('Updated:', user);
} else {
logger.log('User with id=1 not found');
@@ -52,14 +52,15 @@ const actions = {
},
delete: async () => {
- await db.delete('user', { id: 2 });
+ await db.delete({ store: 'user', id: 2 });
logger.log('Deleted user with id=2');
},
adults: async () => {
- const users = await db.select('user', {
+ const users = await db.select({
+ store: 'user',
filter: (user) => user.age >= 18,
- order: 'name asc',
+ order: { name: 'asc' },
limit: 10,
});
logger.log('Adults:', users);
diff --git a/Pragmatic/static/storage.js b/Pragmatic/static/storage.js
index be5f884..dc24382 100644
--- a/Pragmatic/static/storage.js
+++ b/Pragmatic/static/storage.js
@@ -1,14 +1,14 @@
class Database {
#name;
- #version = 1;
- #schemas = null;
+ #version;
+ #schemas;
#instance = null;
#active = false;
- constructor(name, { version, schemas }) {
+ constructor(name, { version = 1, schemas = {} } = {}) {
this.#name = name;
- if (version) this.#version = version;
- if (schemas) this.#schemas = schemas;
+ this.#version = version;
+ this.#schemas = schemas;
return this.#open();
}
@@ -17,14 +17,15 @@ class Database {
const request = indexedDB.open(this.#name, this.#version);
request.onupgradeneeded = (event) => this.#upgrade(event.target.result);
request.onsuccess = (event) => {
- this.#active = true;
this.#instance = event.target.result;
+ this.#active = true;
resolve();
};
request.onerror = (event) => {
- let { error } = event.target;
- if (!error) error = new Error(`IndexedDB: can't open ${this.#name}`);
- reject(error);
+ reject(
+ event.target.error ??
+ new Error(`IndexedDB: can't open ${this.#name}`),
+ );
};
});
return this;
@@ -34,79 +35,80 @@ class Database {
for (const [name, schema] of Object.entries(this.#schemas)) {
if (!db.objectStoreNames.contains(name)) {
const store = db.createObjectStore(name, schema);
- const indexes = schema.indexes || [];
- for (const index of Object.entries(indexes)) {
- store.createIndex(index.name, index.keyPath, index.options);
+ const indexes = schema.indexes ?? [];
+ for (const { name: idxName, keyPath, options } of indexes) {
+ store.createIndex(idxName, keyPath, options);
}
}
}
}
- #exec(entity, operation, mode = 'readwrite') {
+ #exec(store, operation, mode = 'readwrite') {
if (!this.#active) {
return Promise.reject(new Error('Database not connected'));
}
return new Promise((resolve, reject) => {
try {
- const tx = this.#instance.transaction(entity, mode);
- const store = tx.objectStore(entity);
- const result = operation(store);
+ const tx = this.#instance.transaction(store, mode);
+ const objectStore = tx.objectStore(store);
+ const result = operation(objectStore);
tx.oncomplete = () => resolve(result);
- tx.onerror = () => reject(tx.error || new Error('Transaction error'));
+ tx.onerror = () => reject(tx.error ?? new Error('Transaction error'));
} catch (error) {
reject(error);
}
});
}
- async insert(entity, record) {
- return this.#exec(entity, (store) => store.add(record));
+ insert({ store, record }) {
+ return this.#exec(store, (s) => s.add(record));
}
- async update(entity, record) {
- return this.#exec(entity, (store) => store.put(record));
+ update({ store, record }) {
+ return this.#exec(store, (s) => s.put(record));
}
- async delete(entity, { id }) {
- return this.#exec(entity, (store) => store.delete(id));
+ delete({ store, id }) {
+ return this.#exec(store, (s) => s.delete(id));
}
- async get(entity, { id }) {
+ get({ store, id }) {
return this.#exec(
- entity,
- (store) => {
- const req = store.get(id);
+ store,
+ (s) => {
+ const req = s.get(id);
return new Promise((resolve, reject) => {
req.onsuccess = () => resolve(req.result);
- req.onerror = () => reject(req.error || new Error(`Can't get ${id}`));
+ req.onerror = () => reject(req.error ?? new Error(`Can't get ${id}`));
});
},
'readonly',
);
}
- async select(entity, { where, limit, offset, order, filter, sort } = {}) {
+ select({ store, where, limit, offset, order, filter, sort }) {
return this.#exec(
- entity,
- (store) => {
+ store,
+ (s) => {
const results = [];
let skipped = 0;
return new Promise((resolve, reject) => {
- const req = store.openCursor();
+ const req = s.openCursor();
req.onerror = () => reject(req.error);
req.onsuccess = (event) => {
const cursor = event.target.result;
if (!cursor) {
const filtered = filter ? results.filter(filter) : results;
- const sorted = sort
- ? filtered.toSorted(sort)
- : Database.#order(filtered, order);
- return void resolve(sorted);
+ return void resolve(
+ sort
+ ? filtered.toSorted(sort)
+ : Database.#order(filtered, order),
+ );
}
const record = cursor.value;
const match =
!where ||
- Object.entries(where).every(([key, val]) => record[key] === val);
+ Object.entries(where).every(([k, v]) => record[k] === v);
if (match) {
if (!offset || skipped >= offset) {
results.push(record);
@@ -127,7 +129,7 @@ class Database {
static #order(arr, order) {
if (!order || typeof order !== 'object') return arr;
- const [[field, dir]] = Object.entries(order);
+ const [[field, dir = 'asc']] = Object.entries(order);
const sign = dir === 'desc' ? -1 : 1;
return [...arr].sort((a, b) => {
if (a[field] === b[field]) return 0;
diff --git a/Pragmatic/test/database.js b/Pragmatic/test/database.js
index 3abe1fd..396e534 100644
--- a/Pragmatic/test/database.js
+++ b/Pragmatic/test/database.js
@@ -3,28 +3,72 @@ import assert from 'node:assert/strict';
import 'fake-indexeddb/auto';
import { Database } from '../static/storage.js';
-test('Database basic CRUD', async () => {
- const schemas = {
- user: { keyPath: 'id', autoIncrement: true },
- };
- const db = await new Database('TestDatabase', { version: 1, schemas });
+test('Pragmatic: Database CRUD + DSL', async () => {
+ const db = await new Database('TestDB', {
+ version: 1,
+ schemas: {
+ user: { keyPath: 'id', autoIncrement: true },
+ },
+ });
- await db.insert('user', { name: 'Marcus', age: 30 });
- await db.insert('user', { name: 'Lucius', age: 20 });
+ // Insert
+ await db.insert({ store: 'user', record: { name: 'Marcus', age: 20 } });
+ await db.insert({ store: 'user', record: { name: 'Lucius', age: 20 } });
+ await db.insert({ store: 'user', record: { name: 'Antoninus', age: 40 } });
- const users = await db.select('user');
- assert.equal(users.length, 2);
+ // Select all
+ const allUsers = await db.select({ store: 'user' });
+ assert.equal(allUsers.length, 3);
- const record = await db.get('user', { id: 1 });
- assert.equal(record.name, 'Marcus');
+ // Get
+ const marcus = await db.get({ store: 'user', id: 1 });
+ assert.equal(marcus.name, 'Marcus');
- record.age++;
- await db.update('user', record);
-
- const updated = await db.get('user', { id: 1 });
+ // Update
+ marcus.age = 31;
+ await db.update({ store: 'user', record: marcus });
+ const updated = await db.get({ store: 'user', id: 1 });
assert.equal(updated.age, 31);
- await db.delete('user', { id: 2 });
- const remaining = await db.select('user');
- assert.equal(remaining.length, 1);
+ // Delete
+ await db.delete({ store: 'user', id: 2 });
+ const afterDelete = await db.select({ store: 'user' });
+ assert.equal(afterDelete.length, 2);
+
+ // Select with where
+ const list = await db.select({ store: 'user', where: { name: 'Marcus' } });
+ assert.equal(list.length, 1);
+ assert.equal(list[0].age, 31);
+
+ // Select with filter
+ const adults = await db.select({
+ store: 'user',
+ filter: (u) => u.age >= 30,
+ });
+ assert.equal(adults.length, 2);
+ assert.equal(adults[0].name, 'Marcus');
+
+ // Select with order
+ const ordered = await db.select({
+ store: 'user',
+ order: { age: 'desc' },
+ });
+ assert.equal(ordered[0].name, 'Antoninus');
+ assert.equal(ordered[1].name, 'Marcus');
+
+ // Select with offset
+ const skipped = await db.select({
+ store: 'user',
+ offset: 1,
+ order: { name: 'asc' },
+ });
+ assert.equal(skipped.length, 1);
+ assert.equal(skipped[0].name, 'Antoninus');
+
+ // Select with limit
+ const limited = await db.select({
+ store: 'user',
+ limit: 1,
+ });
+ assert.equal(limited.length, 1);
});
diff --git a/eslint.config.js b/eslint.config.js
index 4275f34..e970e96 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -6,7 +6,7 @@ module.exports = init;
module.exports = [
...init,
{
- files: ['Pragmatic/**/*.js'],
+ files: ['Enterprise/**/*.js', 'Native/**/*.js', 'Pragmatic/**/*.js'],
languageOptions: {
sourceType: 'module',
globals: {