Skip to content

Instantly share code, notes, and snippets.

@danielepolencic
Created November 27, 2021 07:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielepolencic/22fcc52b21eda2d2e4237562f292117e to your computer and use it in GitHub Desktop.
Save danielepolencic/22fcc52b21eda2d2e4237562f292117e to your computer and use it in GitHub Desktop.
import { LevelUp } from 'levelup'
import { Transform } from 'stream'
export function Document(db: LevelUp) {
return { createCollection }
function createCollection<T>({ collection }: { collection: string }) {
return <
Indexers extends {
[name: string]: {
prefix: string
reducer: (value: T) => string | number | Array<string | number>
}
}
>(
indexers: Indexers,
): Record<keyof Indexers, { createReadStream: LevelUp['createReadStream'] }> & {
put: (key: string, value: T) => Promise<void>
get: (key: string) => Promise<T | void>
del: (key: string) => Promise<void>
} => {
return {
batch: async (
ops: Array<{ type: 'put'; key: string; value: T } | { type: 'del'; key: string }>,
): Promise<void> => {
let readyToDelete = [] as { type: 'del'; key: string }[]
const putOps = ops.filter(it => it.type === 'put') as { type: 'put'; key: string; value: T }[]
const delOps = ops.filter(it => it.type === 'del') as { type: 'del'; key: string }[]
let items = (await (db as any).getMany(delOps.map(it => it.key))) as T[]
if (items.length !== delOps.length) {
for (const delOp of delOps) {
const item = await db.get(delOp.key)
typeof item !== undefined ? del(delOp.key, item).forEach(it => readyToDelete.push(it)) : null
}
} else {
items.forEach((it, i) => del(delOps[i]!.key, it).forEach(it => readyToDelete.push(it)))
}
return db.batch([...putOps.flatMap(it => add(it.key, it.value)), ...readyToDelete])
},
put: async (key: string, value: T): Promise<void> => {
// delete the old indexes
return db.batch(add(key, value))
},
get: async (key: string): Promise<T | undefined> => {
return db.get(key)
},
del: async (key: string): Promise<void> => {
const item = (await db.get(key)) as T | undefined
if (!item) {
return
}
return db.batch(del(key, item))
},
...Object.keys(indexers).reduce((acc, it) => {
const reducer = indexers[it]!
acc[it as keyof Indexers] = { createReadStream }
return acc
function createReadStream(args: {
gt?: string
gte?: string
lt?: string
lte?: string
reverse?: boolean
limit?: number
}) {
const templatedArgs = ['gt', 'gte', 'lt', 'lte'].reduce(
(acc, it) => {
if (it in args) {
acc[it as keyof typeof acc] = `${collection}!${reducer.prefix}!${
args[it as 'gt' | 'gte' | 'lt' | 'lte']
}`
}
return acc
},
{} as {
gt?: string
gte?: string
lt?: string
lte?: string
},
)
const fetch = new Transform({
readableObjectMode: true,
writableObjectMode: true,
transform(chunk, encoding, callback) {
db.get(chunk.value, (err, value) => {
if (err) return callback(err)
callback(null, { key: chunk.value.replace(`${collection}!default!`, ''), value })
})
},
})
return db
.createReadStream({
...args,
...templatedArgs,
})
.pipe(fetch)
}
}, {} as Record<keyof Indexers, { createReadStream: LevelUp['createReadStream'] }>),
}
function add(key: string, value: T): { type: 'put'; key: string; value: unknown }[] {
return [
{ type: 'put', key: `${collection}!default!${key}`, value },
...Object.values(indexers).flatMap(it => {
const reducedValue = it.reducer(value)
const reducedValues = Array.isArray(reducedValue) ? reducedValue : [reducedValue]
return reducedValues.map(value => {
return {
type: 'put' as const,
key: `${collection}!${it.prefix}!${value}`,
value: `${collection}!default!${key}`,
}
})
}),
]
}
function del(key: string, item: T): { type: 'del'; key: string }[] {
return [
{ type: 'del', key: `${collection}!default!${key}` },
...Object.values(indexers).flatMap(it => {
const reducedValue = it.reducer(item!)
const reducedValues = Array.isArray(reducedValue) ? reducedValue : [reducedValue]
return reducedValues.map(value => {
return {
type: 'del' as const,
key: `${collection}!${it.prefix}!${value}`,
}
})
}),
]
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment