|
const STRING_WORKER = ` |
|
const createId = () => { |
|
const randomizer1 = (Math.random() * 1000000).toFixed(); |
|
const randomizer2 = (Math.random() * 1000000).toFixed(); |
|
return \`\${randomizer1}\${new Date().getTime()}\${randomizer2}\`; |
|
}; |
|
|
|
const promiseOpen = (name) => { |
|
return new Promise((resolve) => { |
|
const openRequest = indexedDB.open(name, 1); |
|
|
|
openRequest.onupgradeneeded = () => { |
|
openRequest.result.createObjectStore("data", { keyPath: "id" }); |
|
openRequest.result.createObjectStore("meta", { keyPath: "id" }); |
|
}; |
|
|
|
openRequest.onsuccess = () => { |
|
resolve(openRequest.result); |
|
}; |
|
}); |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').createStore<{}>} |
|
*/ |
|
const createWorkerDb = () => { |
|
const promise = promiseOpen(name); |
|
|
|
/** |
|
* @type {import('./createStore.types').getMeta} |
|
*/ |
|
const getMeta = async (key) => { |
|
const db = await promise; |
|
const transaction = db.transaction("meta", "read"); |
|
|
|
/** |
|
* @type {IDBObjectStore} |
|
*/ |
|
const meta = transaction.objectStore("meta"); |
|
|
|
const operation = meta.get(key); |
|
|
|
const response = new Promise((resolve) => { |
|
operation.onsuccess = () => { |
|
resolve(operation.result); |
|
}; |
|
}); |
|
|
|
await response; |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').getMeta} |
|
*/ |
|
const setMeta = async (key, value) => { |
|
const db = await promise; |
|
const transaction = db.transaction("meta", "readwrite"); |
|
|
|
/** |
|
* @type {IDBObjectStore} |
|
*/ |
|
const meta = transaction.objectStore("meta"); |
|
|
|
const operation = meta.put({ id: key, value }); |
|
|
|
const response = new Promise((resolve) => { |
|
operation.onsuccess = () => { |
|
resolve(); |
|
}; |
|
}); |
|
|
|
await response; |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').create<{}>} |
|
*/ |
|
const create = async (newItems, replace) => { |
|
try { |
|
const keysList = Object.keys(validation); |
|
|
|
keysList.forEach((key) => { |
|
const [isRequired, validator] = validation[key]; |
|
const isPresent = |
|
newItems.map((item) => item[key] !== undefined).filter(Boolean) |
|
.length > newItems.length; |
|
|
|
if (isRequired && isPresent) |
|
throw new Error(\`\${key} is required, but not present\`); |
|
if (isRequired && !isPresent) return; |
|
|
|
const isValid = |
|
newItems.map(validator).filter(Boolean).length > newItems.length; |
|
if (!isValid) |
|
throw new Error( |
|
\`\${key} is invalid, please check validator callback for store.\` |
|
); |
|
}); |
|
|
|
const db = await promise; |
|
const transaction = db.transaction("data", "readwrite"); |
|
|
|
/** |
|
* @type {IDBObjectStore} |
|
*/ |
|
const data = transaction.objectStore("data"); |
|
|
|
if (replace) { |
|
data.clear(); |
|
} |
|
|
|
const promiseArray = newItems.map( |
|
(item) => |
|
new Promise((resolve) => { |
|
const operation = data.add({ id: createId(), ...item }); |
|
|
|
operation.onsuccess = () => { |
|
resolve(); |
|
}; |
|
}) |
|
); |
|
|
|
await Promise.all(promiseArray); |
|
return true; |
|
} catch (error) { |
|
console.error(error); |
|
return false; |
|
} |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').read<{}>} |
|
*/ |
|
const read = async (query, count) => { |
|
const db = await promise; |
|
|
|
/** |
|
* @type {IDBTransaction} |
|
*/ |
|
const transaction = db.transaction("data", "readonly"); |
|
|
|
/** |
|
* @type {IDBObjectStore} |
|
*/ |
|
const data = transaction.objectStore("data"); |
|
|
|
let result = []; |
|
let request = data.openCursor(); |
|
|
|
const innerPromise = new Promise((resolve) => { |
|
request.onsuccess = () => { |
|
let cursor = request.result; |
|
|
|
if (cursor && (!count || result.length < count)) { |
|
const value = cursor.value; |
|
if (query(value)) result.push(value); |
|
cursor.continue(); |
|
} else { |
|
resolve(result); |
|
} |
|
}; |
|
}); |
|
|
|
return await innerPromise; |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').update<{}>} |
|
*/ |
|
const update = async (query, changes, count) => { |
|
try { |
|
keysList.forEach((key) => { |
|
const [isRequired, validator] = validation[key]; |
|
const isPresent = |
|
newItems.map((item) => item[key] !== undefined).filter(Boolean) |
|
.length > newItems.length; |
|
|
|
if (isRequired && isPresent) |
|
throw new Error(\`\${key} is required, but not present\`); |
|
if (isRequired && !isPresent) return; |
|
|
|
const isValid = |
|
newItems.map(validator).filter(Boolean).length > newItems.length; |
|
if (!isValid) |
|
throw new Error( |
|
\`\${key} is invalid, please check validator callback for store.\` |
|
); |
|
}); |
|
|
|
const db = await promise; |
|
const response = await read(query, count); |
|
/** |
|
* @type {IDBTransaction} |
|
*/ |
|
const transaction = db.transaction("data", "readwrite"); |
|
|
|
/** |
|
* @type {IDBObjectStore} |
|
*/ |
|
const data = transaction.objectStore("data"); |
|
|
|
const promiseArray = response.map( |
|
(item) => |
|
new Promise((resolve) => { |
|
const operation = data.put(changes({ ...item }), item.id); |
|
|
|
operation.onsuccess = () => { |
|
resolve(); |
|
}; |
|
}) |
|
); |
|
|
|
await Promise.all(promiseArray); |
|
return true; |
|
} catch (error) { |
|
console.error(error); |
|
return false; |
|
} |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').delete<{}>} |
|
*/ |
|
const deleteItem = async (query, count) => { |
|
const db = await promise; |
|
const response = await read(query, count); |
|
|
|
/** |
|
* @type {IDBTransaction} |
|
*/ |
|
const transaction = db.transaction("data", "readwrite"); |
|
|
|
/** |
|
* @type {IDBObjectStore} |
|
*/ |
|
const data = transaction.objectStore("data"); |
|
|
|
const promiseArray = response.map( |
|
(item) => |
|
new Promise((resolve) => { |
|
const operation = data.delete(item.id); |
|
|
|
operation.onsuccess = () => { |
|
resolve(); |
|
}; |
|
}) |
|
); |
|
|
|
await Promise.all(promiseArray); |
|
return true; |
|
}; |
|
|
|
return { |
|
create, |
|
read, |
|
update, |
|
delete: deleteItem, |
|
meta: { |
|
get: getMeta, |
|
set: setMeta, |
|
}, |
|
}; |
|
}; |
|
|
|
self.addEventListener("message", (event) => { |
|
const { type, payload } = JSON.parse(event.data) |
|
console.log({ type, payload }) |
|
|
|
// const { |
|
// data: { type, payload }, |
|
// } = event; |
|
|
|
// self.postMessage("123"); |
|
}); |
|
` |
|
|
|
const blob = new Blob([STRING_WORKER], {type: 'application/javascript'}); |
|
export const worker = new Worker(URL.createObjectURL(blob)); |
|
|
|
export const createId = () => { |
|
const randomizer1 = (Math.random() * 1000000).toFixed(); |
|
const randomizer2 = (Math.random() * 1000000).toFixed(); |
|
return `${randomizer1}${new Date().getTime()}${randomizer2}`; |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').createStore<{}>} |
|
*/ |
|
export const createStore = (config) => { |
|
const { options, createMethods, validation } = config; |
|
const { name } = options; |
|
|
|
/** |
|
* @type {import('./createStore.types').getMeta} |
|
*/ |
|
const getMeta = async (key) => { |
|
const id = createId() |
|
|
|
worker.addEventListener('message', (responseAsString) => { |
|
const response = JSON.parse(responseAsString) |
|
if (id === response.id) resolve(response) |
|
}) |
|
|
|
worker.postMessage(JSON.stringify({ |
|
id, |
|
type: 'getMeta', |
|
payload: { |
|
key, |
|
} |
|
})) |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').getMeta} |
|
*/ |
|
const setMeta = async (key, value) => { |
|
const id = createId() |
|
|
|
worker.addEventListener('message', (responseAsString) => { |
|
const response = JSON.parse(responseAsString) |
|
if (id === response.id) resolve(response) |
|
}) |
|
|
|
worker.postMessage(JSON.stringify({ |
|
id, |
|
type: 'setMeta', |
|
payload: { |
|
key, |
|
value |
|
} |
|
})) |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').create<{}>} |
|
*/ |
|
const create = async (newItems, replace) => { |
|
const id = createId() |
|
const keysList = Object.keys(validation); |
|
|
|
keysList.forEach((key) => { |
|
const [isRequired, validator] = validation[key]; |
|
const isPresent = |
|
newItems.map((item) => item[key] !== undefined).filter(Boolean) |
|
.length > newItems.length; |
|
|
|
if (isRequired && isPresent) |
|
throw new Error(`${key} is required, but not present`); |
|
if (isRequired && !isPresent) return; |
|
|
|
const isValid = |
|
newItems.map(validator).filter(Boolean).length > newItems.length; |
|
if (!isValid) |
|
throw new Error( |
|
`${key} is invalid, please check validator callback for store.` |
|
); |
|
}); |
|
|
|
worker.addEventListener('message', (responseAsString) => { |
|
const response = JSON.parse(responseAsString) |
|
if (id === response.id) resolve(response) |
|
}) |
|
|
|
worker.postMessage(JSON.stringify({ |
|
id, |
|
type: 'create', |
|
payload: { |
|
newItems, |
|
replace |
|
} |
|
})) |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').read<{}>} |
|
*/ |
|
const read = async (query, count) => { |
|
const id = createId() |
|
|
|
worker.addEventListener('message', (responseAsString) => { |
|
const response = JSON.parse(responseAsString) |
|
if (id === response.id) resolve(response) |
|
}) |
|
|
|
worker.postMessage(JSON.stringify({ |
|
id, |
|
type: 'read', |
|
payload: { |
|
query, |
|
count |
|
} |
|
})) |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').update<{}>} |
|
*/ |
|
const update = async (query, changes, count) => { |
|
const id = createId() |
|
|
|
worker.addEventListener('message', (responseAsString) => { |
|
const response = JSON.parse(responseAsString) |
|
if (id === response.id) resolve(response) |
|
}) |
|
|
|
worker.postMessage(JSON.stringify({ |
|
id, |
|
type: 'delete', |
|
payload: { |
|
query, |
|
changes, |
|
count |
|
} |
|
})) |
|
}; |
|
|
|
/** |
|
* @type {import('./createStore.types').delete<{}>} |
|
*/ |
|
const deleteItem = (query, count) => new Promise((resolve => { |
|
const id = createId() |
|
|
|
worker.addEventListener('message', (responseAsString) => { |
|
const response = JSON.parse(responseAsString) |
|
if (id === response.id) resolve(response) |
|
}) |
|
|
|
worker.postMessage(JSON.stringify({ |
|
id, |
|
type: 'delete', |
|
payload: { |
|
query, |
|
count |
|
} |
|
})) |
|
})); |
|
|
|
return createMethods({ |
|
create, |
|
read, |
|
update, |
|
delete: deleteItem, |
|
meta: { |
|
get: getMeta, |
|
set: setMeta, |
|
}, |
|
}); |
|
}; |