mirror of
https://github.com/pockethost/pockethost.git
synced 2025-05-31 19:26:40 +00:00
enh: SqliteService async serialization enhancements
This commit is contained in:
parent
c419a1f692
commit
63ad32e054
@ -4,10 +4,10 @@ import {
|
|||||||
mkSingleton,
|
mkSingleton,
|
||||||
SingletonBaseConfig,
|
SingletonBaseConfig,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import Bottleneck from 'bottleneck'
|
|
||||||
import { Database as SqliteDatabase, open } from 'sqlite'
|
import { Database as SqliteDatabase, open } from 'sqlite'
|
||||||
import { Database } from 'sqlite3'
|
import { Database } from 'sqlite3'
|
||||||
import { JsonObject } from 'type-fest'
|
import { JsonObject } from 'type-fest'
|
||||||
|
import { serialAsyncExecutionGuard } from '../../util/serialAsyncExecutionGuard'
|
||||||
|
|
||||||
export type SqliteUnsubscribe = () => void
|
export type SqliteUnsubscribe = () => void
|
||||||
export type SqliteChangeHandler<TRecord extends JsonObject> = (
|
export type SqliteChangeHandler<TRecord extends JsonObject> = (
|
||||||
@ -35,21 +35,25 @@ export type SqliteService = ReturnType<typeof sqliteService>
|
|||||||
export const sqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
export const sqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
||||||
const { logger } = config
|
const { logger } = config
|
||||||
const { dbg, trace } = logger.create(`sqliteService`)
|
const { dbg, trace } = logger.create(`sqliteService`)
|
||||||
const connections: { [_: string]: Promise<SqliteServiceApi> } = {}
|
const connections: { [_: string]: SqliteServiceApi } = {}
|
||||||
|
|
||||||
const cm = createCleanupManager()
|
const cm = createCleanupManager()
|
||||||
|
|
||||||
const limiter = new Bottleneck({ maxConcurrent: 1 })
|
/*
|
||||||
|
This function
|
||||||
|
*/
|
||||||
|
const _unsafe_getDatabase = async (
|
||||||
|
filename: string
|
||||||
|
): Promise<SqliteServiceApi> => {
|
||||||
|
const _dbLogger = logger.create(`SqliteService`)
|
||||||
|
_dbLogger.breadcrumb(filename)
|
||||||
|
const { dbg, error, abort } = _dbLogger
|
||||||
|
|
||||||
const getDatabase = async (filename: string): Promise<SqliteServiceApi> => {
|
trace(`Fetching database`, connections)
|
||||||
const _dbLogger = logger.create(`${filename}`)
|
|
||||||
const { dbg } = _dbLogger
|
|
||||||
|
|
||||||
trace(`Fetching database for ${filename}`, connections)
|
|
||||||
if (!connections[filename]) {
|
if (!connections[filename]) {
|
||||||
dbg(`${filename} is not yet opened`)
|
dbg(`Not yet opened`)
|
||||||
|
|
||||||
connections[filename] = new Promise<SqliteServiceApi>(async (resolve) => {
|
const api = await (async () => {
|
||||||
const db = await open({ filename, driver: Database })
|
const db = await open({ filename, driver: Database })
|
||||||
dbg(`Database opened`)
|
dbg(`Database opened`)
|
||||||
db.db.addListener(
|
db.db.addListener(
|
||||||
@ -60,20 +64,23 @@ export const sqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
|||||||
table: string,
|
table: string,
|
||||||
rowId: number
|
rowId: number
|
||||||
) => {
|
) => {
|
||||||
dbg(`Got a raw change event`, { eventType, database, table, rowId })
|
trace(`Got a raw change event`, {
|
||||||
|
eventType,
|
||||||
|
database,
|
||||||
|
table,
|
||||||
|
rowId,
|
||||||
|
})
|
||||||
if (eventType === 'delete') return // Not supported
|
if (eventType === 'delete') return // Not supported
|
||||||
|
|
||||||
await limiter.schedule(async () => {
|
const record = await db.get(
|
||||||
const record = await db.get(
|
`select * from ${table} where rowid = '${rowId}'`
|
||||||
`select * from ${table} where rowid = '${rowId}'`
|
)
|
||||||
)
|
const e: SqliteChangeEvent<any> = {
|
||||||
const e: SqliteChangeEvent<any> = {
|
table,
|
||||||
table,
|
action: eventType,
|
||||||
action: eventType,
|
record,
|
||||||
record,
|
}
|
||||||
}
|
fireChange(e)
|
||||||
fireChange(e)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -92,15 +99,22 @@ export const sqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
|||||||
exec: db.exec.bind(db),
|
exec: db.exec.bind(db),
|
||||||
subscribe: onChange,
|
subscribe: onChange,
|
||||||
}
|
}
|
||||||
resolve(api)
|
return api
|
||||||
})
|
})().catch(error)
|
||||||
|
if (!api) {
|
||||||
|
throw new Error(`Unable to connect to SQLite`)
|
||||||
|
}
|
||||||
|
connections[filename] = api
|
||||||
}
|
}
|
||||||
return connections[filename]!
|
return connections[filename]!
|
||||||
}
|
}
|
||||||
|
const getDatabase = serialAsyncExecutionGuard(
|
||||||
|
_unsafe_getDatabase,
|
||||||
|
(fileName) => fileName
|
||||||
|
)
|
||||||
|
|
||||||
const shutdown = async () => {
|
const shutdown = async () => {
|
||||||
dbg(`Shutting down sqlite service`)
|
dbg(`Shutting down sqlite service`)
|
||||||
await limiter.stop()
|
|
||||||
await cm.shutdown()
|
await cm.shutdown()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
26
packages/daemon/src/util/serialAsyncExecutionGuard.ts
Normal file
26
packages/daemon/src/util/serialAsyncExecutionGuard.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { logger } from '@pockethost/common'
|
||||||
|
import { uniqueId } from '@s-libs/micro-dash'
|
||||||
|
import Bottleneck from 'bottleneck'
|
||||||
|
import { SetReturnType } from 'type-fest'
|
||||||
|
|
||||||
|
const limiters: { [lane: string]: Bottleneck } = {}
|
||||||
|
export const serialAsyncExecutionGuard = <
|
||||||
|
T extends (...args: any[]) => Promise<any>
|
||||||
|
>(
|
||||||
|
cb: T,
|
||||||
|
lane?: SetReturnType<T, string>
|
||||||
|
): T => {
|
||||||
|
const uuid = uniqueId()
|
||||||
|
const _lane = lane || (() => uuid)
|
||||||
|
const wrapper = (...args: Parameters<T>) => {
|
||||||
|
const { dbg } = logger()
|
||||||
|
const key = _lane(...args)
|
||||||
|
if (!limiters[key]) {
|
||||||
|
dbg(`New singleton limiter with key ${key}`)
|
||||||
|
limiters[key] = new Bottleneck({ maxConcurrent: 1 })
|
||||||
|
}
|
||||||
|
const limiter = limiters[key]!
|
||||||
|
return limiter.schedule(() => cb(...args)) as unknown as ReturnType<T>
|
||||||
|
}
|
||||||
|
return wrapper as unknown as T
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user