Skip to content

Commit 68a1354

Browse files
committed
Add indexedDB enterprise API example
1 parent 69ee37e commit 68a1354

File tree

12 files changed

+393
-0
lines changed

12 files changed

+393
-0
lines changed

Enterprise/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "module"
3+
}

Enterprise/static/404.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Error 404: File not found</title>
6+
<link rel="stylesheet" href="styles/styles.css">
7+
</head>
8+
<body>
9+
<h1>File not found</h1>
10+
</body>
11+
</html>

Enterprise/static/application.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { Database } from './database.js';
2+
import { UserRepository, UserService } from './user.js';
3+
4+
class Logger {
5+
#output;
6+
7+
constructor(outputId) {
8+
this.#output = document.getElementById(outputId);
9+
}
10+
11+
log(...args) {
12+
const lines = args.map(Logger.#serialize);
13+
this.#output.textContent += lines.join(' ') + '\n';
14+
this.#output.scrollTop = this.#output.scrollHeight;
15+
}
16+
17+
static #serialize(x) {
18+
return typeof x === 'object' ? JSON.stringify(x, null, 2) : x;
19+
}
20+
}
21+
22+
const logger = new Logger('output');
23+
24+
const action = (id, handler) => {
25+
const element = document.getElementById(id);
26+
if (element) element.onclick = handler;
27+
};
28+
29+
const db = new Database('EnterpriseApplication', 1, (db) => {
30+
if (!db.objectStoreNames.contains('user')) {
31+
db.createObjectStore('user', { keyPath: 'id', autoIncrement: true });
32+
}
33+
});
34+
await db.connect();
35+
const userRepository = new UserRepository(db, 'user');
36+
const userService = new UserService(userRepository);
37+
38+
action('add', async () => {
39+
const name = prompt('Enter user name:');
40+
const age = parseInt(prompt('Enter age:'), 10);
41+
if (!name || !Number.isInteger(age)) return;
42+
const user = await userService.createUser(name, age);
43+
logger.log('Added:', user);
44+
});
45+
46+
action('get', async () => {
47+
const users = await userRepository.getAll();
48+
logger.log('Users:', users);
49+
});
50+
51+
action('update', async () => {
52+
try {
53+
const user = await userService.incrementAge(1);
54+
logger.log('Updated:', user);
55+
} catch (err) {
56+
logger.log(err.message);
57+
}
58+
});
59+
60+
action('delete', async () => {
61+
await userRepository.delete(2);
62+
logger.log('Deleted user with id=2');
63+
});
64+
65+
action('adults', async () => {
66+
const adults = await userService.findAdults();
67+
logger.log('Adults:', adults);
68+
});

Enterprise/static/core.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export class Repository {
2+
constructor(database, storeName) {
3+
this.db = database;
4+
this.storeName = storeName;
5+
}
6+
7+
insert(record) {
8+
return this.db.exec(this.storeName, 'readwrite', (store) =>
9+
store.add(record),
10+
);
11+
}
12+
13+
getAll() {
14+
return this.db.exec(this.storeName, 'readonly', (store) => {
15+
const req = store.getAll();
16+
return new Promise((resolve, reject) => {
17+
req.onsuccess = () => resolve(req.result);
18+
req.onerror = () => reject(req.error);
19+
});
20+
});
21+
}
22+
23+
get(id) {
24+
return this.db.exec(this.storeName, 'readonly', (store) => {
25+
const req = store.get(id);
26+
return new Promise((resolve, reject) => {
27+
req.onsuccess = () => resolve(req.result);
28+
req.onerror = () => reject(req.error);
29+
});
30+
});
31+
}
32+
33+
update(record) {
34+
return this.db.exec(this.storeName, 'readwrite', (store) =>
35+
store.put(record),
36+
);
37+
}
38+
39+
delete(id) {
40+
return this.db.exec(this.storeName, 'readwrite', (store) =>
41+
store.delete(id),
42+
);
43+
}
44+
}
45+
46+
export class Service {
47+
constructor(repository) {
48+
this.repository = repository;
49+
}
50+
}

Enterprise/static/database.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export class Database {
2+
#db;
3+
4+
constructor(name, version = 1, upgradeCallback) {
5+
this.name = name;
6+
this.version = version;
7+
this.upgradeCallback = upgradeCallback;
8+
}
9+
10+
async connect() {
11+
return new Promise((resolve, reject) => {
12+
const request = indexedDB.open(this.name, this.version);
13+
14+
request.onupgradeneeded = (event) => {
15+
const db = event.target.result;
16+
if (this.upgradeCallback) this.upgradeCallback(db);
17+
};
18+
19+
request.onsuccess = () => {
20+
this.#db = request.result;
21+
resolve();
22+
};
23+
24+
request.onerror = () => reject(request.error);
25+
});
26+
}
27+
28+
transaction(storeName, mode = 'readonly') {
29+
const tx = this.#db.transaction(storeName, mode);
30+
return tx.objectStore(storeName);
31+
}
32+
33+
exec(storeName, mode, operation) {
34+
return new Promise((resolve, reject) => {
35+
try {
36+
const tx = this.#db.transaction(storeName, mode);
37+
const store = tx.objectStore(storeName);
38+
const result = operation(store);
39+
tx.oncomplete = () => resolve(result);
40+
tx.onerror = () => reject(tx.error);
41+
} catch (err) {
42+
reject(err);
43+
}
44+
});
45+
}
46+
}

Enterprise/static/favicon.ico

1.12 KB
Binary file not shown.

Enterprise/static/index.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>IndexedDB Exampe</title>
6+
<link rel="stylesheet" href="styles.css" />
7+
</head>
8+
<body>
9+
<h1>IndexedDB Repository Example</h1>
10+
<div class="controls">
11+
<button id="add">Add User</button>
12+
<button id="get">Get All Users</button>
13+
<button id="update">Update User 1</button>
14+
<button id="delete">Delete User 2</button>
15+
<button id="adults">Find Adults</button>
16+
</div>
17+
<pre id="output"></pre>
18+
<script type="module" src="./application.js"></script>
19+
</body>
20+
</html>

Enterprise/static/styles.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
body {
2+
font-family: system-ui, sans-serif;
3+
padding: 1rem;
4+
background: #f4f4f4;
5+
color: #222;
6+
}
7+
8+
h1 {
9+
margin-bottom: 1rem;
10+
}
11+
12+
.controls {
13+
margin-bottom: 1rem;
14+
}
15+
16+
button {
17+
margin-right: 0.5rem;
18+
padding: 0.5rem 1rem;
19+
font-size: 1rem;
20+
cursor: pointer;
21+
}
22+
23+
pre#output {
24+
position: fixed;
25+
top: 10rem;
26+
bottom: 0;
27+
left: 0;
28+
right: 0;
29+
overflow: auto;
30+
margin: 0;
31+
padding: 1rem;
32+
background: #111;
33+
color: #0f0;
34+
font-family: monospace;
35+
font-size: 0.9rem;
36+
white-space: pre-wrap;
37+
}

Enterprise/static/user.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Repository, Service } from './core.js';
2+
3+
export class UserModel {
4+
constructor(name, age) {
5+
if (typeof name !== 'string' || name.trim().length === 0) {
6+
throw new Error('Invalid name');
7+
}
8+
if (!Number.isInteger(age) || age < 0) {
9+
throw new Error('Invalid age');
10+
}
11+
this.name = name.trim();
12+
this.age = age;
13+
}
14+
}
15+
16+
export class UserRepository extends Repository {
17+
constructor(database) {
18+
super(database, 'user');
19+
}
20+
}
21+
22+
export class UserService extends Service {
23+
async createUser(name, age) {
24+
const user = new UserModel(name, age);
25+
await this.repository.insert(user);
26+
return user;
27+
}
28+
29+
async incrementAge(id) {
30+
const user = await this.repository.get(id);
31+
if (!user) throw new Error('User with id=1 not found');
32+
user.age += 1;
33+
await this.repository.update(user);
34+
return user;
35+
}
36+
37+
async findAdults() {
38+
const users = await this.repository.getAll();
39+
return users.filter((user) => user.age >= 18);
40+
}
41+
}

Enterprise/test/core.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import 'fake-indexeddb/auto';
4+
import { Database } from '../static/database.js';
5+
import { Repository, Service } from '../static/core.js';
6+
7+
test('Enterprise: Repository', async () => {
8+
const db = new Database('RepoTestDB', 1, (db) => {
9+
db.createObjectStore('items', { keyPath: 'id', autoIncrement: true });
10+
});
11+
await db.connect();
12+
13+
const repo = new Repository(db, 'items');
14+
15+
const item = { name: 'Item1' };
16+
await repo.insert(item);
17+
18+
const items = await repo.getAll();
19+
assert.equal(items.length, 1);
20+
assert.equal(items[0].name, 'Item1');
21+
22+
const id = items[0].id;
23+
const one = await repo.get(id);
24+
assert.equal(one.name, 'Item1');
25+
26+
one.name = 'Item1Updated';
27+
await repo.update(one);
28+
29+
const updated = await repo.get(id);
30+
assert.equal(updated.name, 'Item1Updated');
31+
32+
await repo.delete(id);
33+
const afterDelete = await repo.getAll();
34+
assert.equal(afterDelete.length, 0);
35+
});
36+
37+
test('Enterprise: Service', () => {
38+
const fakeRepo = { insert: () => {}, get: () => {} };
39+
const service = new Service(fakeRepo);
40+
assert.equal(service.repository, fakeRepo);
41+
});

0 commit comments

Comments
 (0)