Skip to content

Commit 57ef9aa

Browse files
committed
Encapsulate indexed db to prevent server side rendering issues
1 parent d0dad48 commit 57ef9aa

File tree

3 files changed

+172
-161
lines changed

3 files changed

+172
-161
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111
},
1212
"dependencies": {
1313
"vite": "^6.1.0"
14+
},
15+
"scripts": {
16+
"build": "lerna run build"
1417
}
1518
}

packages/stencil-library/src/components/pid-component/pid-component.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Component, h, Host, Prop, State, Watch } from '@stencil/core';
22
import { GenericIdentifierType } from '../../utils/GenericIdentifierType';
33
import { FoldableItem } from '../../utils/FoldableItem';
44
import { FoldableAction } from '../../utils/FoldableAction';
5-
import { getEntity } from '../../utils/IndexedDBUtil';
5+
import { Database } from '../../utils/IndexedDBUtil';
66
import { clearCache } from '../../utils/DataCache';
77

88
@Component({
@@ -186,7 +186,13 @@ export class PidComponent {
186186
});
187187

188188
// Get the renderer for the value
189-
this.identifierObject = await getEntity(this.value, settings);
189+
try {
190+
const db = new Database();
191+
this.identifierObject = await db.getEntity(this.value, settings);
192+
} catch (e) {
193+
console.error('Failed to get entity from db', e);
194+
return;
195+
}
190196

191197
// Generate items and actions if subcomponents should be shown
192198
if (!this.hideSubcomponents) {

packages/stencil-library/src/utils/IndexedDBUtil.ts

Lines changed: 161 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -41,176 +41,178 @@ export interface PIDComponentDB extends DBSchema {
4141
};
4242
}
4343

44-
/**
45-
* Opens the indexedDB database for the PID component and creates the object stores and indexes if they do not exist.
46-
* @type {Promise<IDBPDatabase<PIDComponentDB>>}
47-
* @const
48-
*/
49-
const dbPromise = openDB<PIDComponentDB>(dbName, dbVersion, {
50-
upgrade(db) {
51-
const entityStore = db.createObjectStore('entities', {
52-
keyPath: 'value',
53-
});
54-
entityStore.createIndex('by-context', 'context', { unique: false });
55-
56-
const relationStore = db.createObjectStore('relations', {
57-
autoIncrement: true,
58-
});
59-
60-
// Create indexes for the relations
61-
relationStore.createIndex('by-start', 'start', { unique: false });
62-
relationStore.createIndex('by-description', 'description', { unique: false });
63-
relationStore.createIndex('by-end', 'end', { unique: false });
64-
},
65-
});
66-
67-
/**
68-
* Adds an entity to the database.
69-
* @param {GenericIdentifierType} renderer The renderer to add to the database.
70-
*/
71-
export async function addEntity(renderer: GenericIdentifierType) {
72-
const context = document.documentURI;
73-
const db = await dbPromise;
74-
75-
// Add the entity to the entities object store
76-
await db
77-
.add('entities', {
78-
value: renderer.value,
79-
rendererKey: renderer.getSettingsKey(),
80-
context: context,
81-
lastAccess: new Date(),
82-
lastData: renderer.data,
83-
})
84-
.catch(reason => {
85-
if (reason.name === 'ConstraintError') {
86-
console.debug('Entity already exists', reason);
87-
} else console.error('Could not add entity', reason);
88-
});
89-
console.debug('added entity', renderer);
90-
91-
// Add the relations to the relations object store
92-
// Start a new transaction
93-
const tx = db.transaction('relations', 'readwrite');
94-
const promises = [];
95-
96-
for (const item of renderer.items) {
97-
// Create a relation object
98-
const relation = {
99-
start: renderer.value,
100-
description: item.keyTitle,
101-
end: item.value,
102-
};
103-
// Check if the relation already exists
104-
const index = tx.store.index('by-start');
105-
let cursor = await index.openCursor();
106-
while (cursor) {
107-
if (cursor.value.start === relation.start && cursor.value.end === relation.end && cursor.value.description === relation.description) {
108-
// relation already exists
109-
return;
44+
export class Database {
45+
/**
46+
* Opens the indexedDB database for the PID component and creates the object stores and indexes if they do not exist.
47+
* @type {Promise<IDBPDatabase<PIDComponentDB>>}
48+
* @const
49+
*/
50+
dbPromise = openDB<PIDComponentDB>(dbName, dbVersion, {
51+
upgrade(db) {
52+
const entityStore = db.createObjectStore('entities', {
53+
keyPath: 'value',
54+
});
55+
entityStore.createIndex('by-context', 'context', { unique: false });
56+
57+
const relationStore = db.createObjectStore('relations', {
58+
autoIncrement: true,
59+
});
60+
61+
// Create indexes for the relations
62+
relationStore.createIndex('by-start', 'start', { unique: false });
63+
relationStore.createIndex('by-description', 'description', { unique: false });
64+
relationStore.createIndex('by-end', 'end', { unique: false });
65+
},
66+
});
67+
68+
/**
69+
* Adds an entity to the database.
70+
* @param {GenericIdentifierType} renderer The renderer to add to the database.
71+
*/
72+
async addEntity(renderer: GenericIdentifierType) {
73+
const context = document.documentURI;
74+
const db = await this.dbPromise;
75+
76+
// Add the entity to the entities object store
77+
await db
78+
.add('entities', {
79+
value: renderer.value,
80+
rendererKey: renderer.getSettingsKey(),
81+
context: context,
82+
lastAccess: new Date(),
83+
lastData: renderer.data,
84+
})
85+
.catch(reason => {
86+
if (reason.name === 'ConstraintError') {
87+
console.debug('Entity already exists', reason);
88+
} else console.error('Could not add entity', reason);
89+
});
90+
console.debug('added entity', renderer);
91+
92+
// Add the relations to the relations object store
93+
// Start a new transaction
94+
const tx = db.transaction('relations', 'readwrite');
95+
const promises = [];
96+
97+
for (const item of renderer.items) {
98+
// Create a relation object
99+
const relation = {
100+
start: renderer.value,
101+
description: item.keyTitle,
102+
end: item.value,
103+
};
104+
// Check if the relation already exists
105+
const index = tx.store.index('by-start');
106+
let cursor = await index.openCursor();
107+
while (cursor) {
108+
if (cursor.value.start === relation.start && cursor.value.end === relation.end && cursor.value.description === relation.description) {
109+
// relation already exists
110+
return;
111+
}
112+
cursor = await cursor.continue();
110113
}
111-
cursor = await cursor.continue();
114+
// Add the relation to the relations object store if it does not exist
115+
promises.push(tx.store.add(relation));
112116
}
113-
// Add the relation to the relations object store if it does not exist
114-
promises.push(tx.store.add(relation));
117+
promises.push(tx.done);
118+
await Promise.all(promises);
119+
console.debug('added relations', promises);
115120
}
116-
promises.push(tx.done);
117-
await Promise.all(promises);
118-
console.debug('added relations', promises);
119-
}
120121

121-
/**
122-
* Gets an entity from the database. If the entity does not exist, it is created.
123-
* @returns {Promise<GenericIdentifierType>} The renderer for the entity.
124-
* @param {string} value The stringified value of the entity, e.g. the PID.
125-
* @param {{type: string, values: {name: string, value: any}[]}[]} settings The settings for all renderers.
126-
*/
127-
export const getEntity = async function (
128-
value: string,
129-
settings: {
130-
type: string;
131-
values: {
132-
name: string;
133-
value: any;
134-
}[];
135-
}[],
136-
): Promise<GenericIdentifierType> {
137-
// Try to get the entity from the database
138-
try {
139-
const db = await dbPromise;
140-
const entity:
141-
| {
142-
value: string;
143-
rendererKey: string;
144-
context: string;
145-
lastAccess: Date;
146-
lastData: any;
122+
/**
123+
* Gets an entity from the database. If the entity does not exist, it is created.
124+
* @returns {Promise<GenericIdentifierType>} The renderer for the entity.
125+
* @param {string} value The stringified value of the entity, e.g. the PID.
126+
* @param {{type: string, values: {name: string, value: any}[]}[]} settings The settings for all renderers.
127+
*/
128+
async getEntity(
129+
value: string,
130+
settings: {
131+
type: string;
132+
values: {
133+
name: string;
134+
value: any;
135+
}[];
136+
}[],
137+
): Promise<GenericIdentifierType> {
138+
// Try to get the entity from the database
139+
try {
140+
const db = await this.dbPromise;
141+
const entity:
142+
| {
143+
value: string;
144+
rendererKey: string;
145+
context: string;
146+
lastAccess: Date;
147+
lastData: any;
148+
}
149+
| undefined = await db.get('entities', value);
150+
151+
if (entity !== undefined) {
152+
// If the entity was found, check if the TTL has expired
153+
console.debug('Found entity for value in db', entity, value);
154+
const entitySettings = settings.find(value => value.type === entity.rendererKey)?.values;
155+
const ttl = entitySettings?.find(value => value.name === 'ttl');
156+
157+
if (ttl != undefined && ttl.value != undefined && (new Date().getTime() - entity.lastAccess.getTime() > ttl.value || ttl.value === 0)) {
158+
// If the TTL has expired, delete the entity from the database and move on to creating a new one (down below)
159+
console.log('TTL expired! Deleting entry in db', ttl.value, new Date().getTime() - entity.lastAccess.getTime());
160+
await this.deleteEntity(value);
161+
} else {
162+
// If the TTL has not expired, get a new renderer and return it
163+
console.log('TTL not expired or undefined', new Date().getTime() - entity.lastAccess.getTime());
164+
const renderer = new (renderers.find(renderer => renderer.key === entity.rendererKey).constructor)(value, entitySettings);
165+
renderer.settings = entitySettings;
166+
await renderer.init(entity.lastData);
167+
return renderer;
147168
}
148-
| undefined = await db.get('entities', value);
149-
150-
if (entity !== undefined) {
151-
// If the entity was found, check if the TTL has expired
152-
console.debug('Found entity for value in db', entity, value);
153-
const entitySettings = settings.find(value => value.type === entity.rendererKey)?.values;
154-
const ttl = entitySettings?.find(value => value.name === 'ttl');
155-
156-
if (ttl != undefined && ttl.value != undefined && (new Date().getTime() - entity.lastAccess.getTime() > ttl.value || ttl.value === 0)) {
157-
// If the TTL has expired, delete the entity from the database and move on to creating a new one (down below)
158-
console.log('TTL expired! Deleting entry in db', ttl.value, new Date().getTime() - entity.lastAccess.getTime());
159-
await deleteEntity(value);
160-
} else {
161-
// If the TTL has not expired, get a new renderer and return it
162-
console.log('TTL not expired or undefined', new Date().getTime() - entity.lastAccess.getTime());
163-
const renderer = new (renderers.find(renderer => renderer.key === entity.rendererKey).constructor)(value, entitySettings);
164-
renderer.settings = entitySettings;
165-
await renderer.init(entity.lastData);
166-
return renderer;
167169
}
170+
} catch (error) {
171+
console.error('Could not get entity from db', error);
168172
}
169-
} catch (error) {
170-
console.error('Could not get entity from db', error);
173+
174+
// If no entity was found, create a new one, initialize it and it to the database
175+
console.debug('No valid entity found for value in db', value);
176+
const renderer = await Parser.getBestFit(value, settings);
177+
renderer.settings = settings.find(value => value.type === renderer.getSettingsKey())?.values;
178+
await renderer.init();
179+
await this.addEntity(renderer);
180+
console.debug('added entity to db', value, renderer);
181+
return renderer;
171182
}
172183

173-
// If no entity was found, create a new one, initialize it and it to the database
174-
console.debug('No valid entity found for value in db', value);
175-
const renderer = await Parser.getBestFit(value, settings);
176-
renderer.settings = settings.find(value => value.type === renderer.getSettingsKey())?.values;
177-
await renderer.init();
178-
await addEntity(renderer);
179-
console.debug('added entity to db', value, renderer);
180-
return renderer;
181-
};
184+
/**
185+
* Deletes an entity from the database.
186+
* @param value The value of the entity to delete.
187+
*/
188+
async deleteEntity(value: string) {
189+
const db = await this.dbPromise;
182190

183-
/**
184-
* Deletes an entity from the database.
185-
* @param value The value of the entity to delete.
186-
*/
187-
export async function deleteEntity(value: string) {
188-
const db = await dbPromise;
189-
190-
// Delete the entity
191-
await db.delete('entities', value);
192-
193-
// Delete all relations for the entity
194-
const tx = db.transaction('relations', 'readwrite');
195-
const index = tx.store.index('by-start');
196-
let cursor = await index.openCursor();
197-
while (cursor) {
198-
if (cursor.value.start === value || cursor.value.end === value) {
199-
await tx.store.delete(cursor.primaryKey);
191+
// Delete the entity
192+
await db.delete('entities', value);
193+
194+
// Delete all relations for the entity
195+
const tx = db.transaction('relations', 'readwrite');
196+
const index = tx.store.index('by-start');
197+
let cursor = await index.openCursor();
198+
while (cursor) {
199+
if (cursor.value.start === value || cursor.value.end === value) {
200+
await tx.store.delete(cursor.primaryKey);
201+
}
202+
cursor = await cursor.continue();
200203
}
201-
cursor = await cursor.continue();
204+
console.log('deleted entity', value);
205+
await tx.done;
202206
}
203-
console.log('deleted entity', value);
204-
await tx.done;
205-
}
206207

207-
/**
208-
* Clears all entities from the database.
209-
* @returns {Promise<void>} A promise that resolves when all entities have been deleted.
210-
*/
211-
export async function clearEntities() {
212-
const db = await dbPromise;
213-
await db.clear('entities');
214-
await db.clear('relations');
215-
console.log('cleared entities');
208+
/**
209+
* Clears all entities from the database.
210+
* @returns {Promise<void>} A promise that resolves when all entities have been deleted.
211+
*/
212+
async clearEntities() {
213+
const db = await this.dbPromise;
214+
await db.clear('entities');
215+
await db.clear('relations');
216+
console.log('cleared entities');
217+
}
216218
}

0 commit comments

Comments
 (0)