Skip to content

Instantly share code, notes, and snippets.

@kissarat
Created August 10, 2021 14:46
Show Gist options
  • Save kissarat/1fb7d5dbd03831b7f365ac07b52b9f86 to your computer and use it in GitHub Desktop.
Save kissarat/1fb7d5dbd03831b7f365ac07b52b9f86 to your computer and use it in GitHub Desktop.
// import { EventEmitter } from "common.partover/src/emitter"
const reduce = (items = [], reducer = (item, i) => ({ number: i, ...item }), initial = {}) => items.reduce((acc, { name, ...item }, i) => {
acc[name] = reducer(item, i, name)
return acc
}, initial)
const produce = (object, getKeys = Object.keys(object)) => getKeys(object).map(name => ({
name,
...object[name]
}))
const RepositoryStatus = {
Created: 'created',
Upgrade: 'upgrade',
Ready: 'ready'
}
class Repository {
constructor(schema, stores = {}) {
this.schema = schema
this.stores = stores || {}
this.init()
}
get name() {
return this.schema.name || 'test'
}
get version() {
return this.schema.version || 1
}
addStore(store) {
this.stores[store.name] = store
}
eachStore(cb) {
for (const name in this.stores) {
cb(this.stores[name], name)
}
}
createStore(schema) {
const store = new RepositoryStore(schema)
}
open() {
return new Promise((resolve, reject) => {
this.request = indexedDB.open(this.name, this.version)
this.request.onerror = reject
this.request.onupgradeneeded = () => {
this.upgrade(this.request.result, this.version)
}
this.onsuccess = () => {
this.database = this.request.database
this.request = null
resolve(this)
}
})
}
init() {
if (this.schema.stores) {
for (const name in this.schema.stores) {
if (!this.stores[name]) {
this.createStore(name, this.schema.stores)
}
}
}
}
upgrade(database, version) {
this.eachStore(store => store.upgrade(this, database, version))
}
ready() {
this.eachStore(store => store.ready(this))
}
static async open(schema, stores = {}) {
try {
const repository = new Repository(schema, stores)
await repository.open()
repository.ready()
Repository.instance = repository
return repository
} catch (error) {
Repository.error = error
throw error
}
}
}
const RepositoryStoreSchema = {
type: 'object',
required: [],
indexes: {},
additionalProperties: false,
properties: {}
}
const createRepositoryStoreSchema = (properties = [], extra = { indexes: [] }) => {
const indexesDicts = reduce(indexes)
const requiredPropeperties = []
const columns = reduce(properties, ({ required, primary = false, index, spare = false, unique = false, ...item }, i, name) => {
if (required) {
requiredPropeperties.push(name)
}
if (true === index) {
indexesDicts[name] = { unique, spare, primary }
}
return {
...item,
number: i
}
})
return ({
type: 'object',
required: requiredPropeperties,
indexes: indexesDicts,
properties: columns,
...extra
})
}
class RepositoryStore {
constructor(name, schema = createRepositoryStoreSchema(), records = []) {
this.name = name
this.schema = schema,
this.records = records
this.fire(RepositoryStatus.Created)
}
fire(status) {
this.status = status
// this.emit(status, { target: this })
}
get indexNames() {
return Object.keys(this.schema.indexes)
}
get indexSchema() {
return produce(this.schema.indexes)
}
get propertySchema() {
return produce(this.schema.properties)
}
upgrade(repository, database = repository.request.database, version = repository.version) {
this.fire(RepositoryStatus.Upgrade)
const indexSchema = this.indexSchema
this.store = database.createObjectStore(this.name, { keyPath: indexSchema.find(index => index.primary).name })
this.indexes = {}
for (const { name, primary, unique } of indexSchema) {
if (!primary) {
this.indexes[name] = this.store.createIndex(name, name, { unique })
}
}
this.write(this.records)
}
ready(repository) {
this.fire(RepositoryStatus.Ready, { target: this, repository })
}
write(records) {
for (const record of records) {
store.put(record)
}
}
}
class RepositoryTransaction {
constructor(repository, storeName, readonly = false) {
this.repository = repository
if (!storeName) {
this.storeName = storeName
}
this.readonly = readonly
}
store(storeName = this.storeName) {
if (!this.transaction) {
this.start(storeName)
}
return this.transaction.objectStore(storeName)
}
index(key = this.key, storeName = this.objectStore) {
return this
.store(storeName || this.objectStore)
.index(key)
}
derive(options) {
return Object.assign(Object.create(this), options)
}
start(storeName = this.storeName, readonly = this.readonly) {
this.transaction = this.repository.database.transaction(storeName, readonly ? 'readonly' : 'readwrite')
return this.transaction
}
walk(key, cb) {
return new Promise((resolve, reject) => {
const request = index.openCursor(IDBKeyRange.only(key))
request.onsuccess = () => {
try {
const cursor = request.result
if (cursor) {
cb(cursor)
} else {
resolve(request)
}
} catch (err) {
cursor.close()
reject(err)
}
}
request.onerror = reject
})
}
async find(key = 'id', predicate = item => true, next = () => { }) {
await this.walk(key, cursor => {
if (predicate(cursor.value)) {
next(cursor.value)
}
})
}
async findBy(key, value, next) {
const indexSchema = this.store().schema.indexes[key]
if (indexSchema.unique) {
this.index(key)
}
await this.find(
'id',
item => item[key] === value,
next
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment