Skip to content

Add Native API and Enterprise examples #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Enterprise/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
11 changes: 11 additions & 0 deletions Enterprise/static/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Error 404: File not found</title>
<link rel="stylesheet" href="styles/styles.css">
</head>
<body>
<h1>File not found</h1>
</body>
</html>
68 changes: 68 additions & 0 deletions Enterprise/static/application.js
Original file line number Diff line number Diff line change
@@ -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);
});
50 changes: 50 additions & 0 deletions Enterprise/static/core.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
46 changes: 46 additions & 0 deletions Enterprise/static/database.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
}
}
Binary file added Enterprise/static/favicon.ico
Binary file not shown.
20 changes: 20 additions & 0 deletions Enterprise/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>IndexedDB Exampe</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<h1>IndexedDB Repository Example</h1>
<div class="controls">
<button id="add">Add User</button>
<button id="get">Get All Users</button>
<button id="update">Update User 1</button>
<button id="delete">Delete User 2</button>
<button id="adults">Find Adults</button>
</div>
<pre id="output"></pre>
<script type="module" src="./application.js"></script>
</body>
</html>
37 changes: 37 additions & 0 deletions Enterprise/static/styles.css
Original file line number Diff line number Diff line change
@@ -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;
}
41 changes: 41 additions & 0 deletions Enterprise/static/user.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
41 changes: 41 additions & 0 deletions Enterprise/test/core.js
Original file line number Diff line number Diff line change
@@ -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);
});
34 changes: 34 additions & 0 deletions Enterprise/test/database.js
Original file line number Diff line number Diff line change
@@ -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);
});
Loading