Skip to content

Instantly share code, notes, and snippets.

@josephg
Last active October 29, 2021 06:02
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 josephg/c433d3bcc078d911e7696c6d64381bf1 to your computer and use it in GitHub Desktop.
Save josephg/c433d3bcc078d911e7696c6d64381bf1 to your computer and use it in GitHub Desktop.
shelf
type Item = null | string | number | boolean | Item[] | {[k: string]: Item};
// Must match the shape of the data
type Version = number | [number, Version[]] | [number, {[k: string]: Version}];
type Path = (string | number)[]
interface Doc {
data: Item,
versions: Version,
nextSeq: number,
}
const isObject = (x: any): x is {[k: string]: Item} => (typeof x === 'object' && !Array.isArray(x) && x != null)
const isVObj = (v: Version): v is [number, {[k: string]: Version}] => !(typeof v === 'number') && isObject(v[1])
const isVArr = (v: Version): v is [number, Version[]] => !(typeof v === 'number') && Array.isArray(v[1])
const setInner = (data: Item, versions: Version, nextSeq: number, path: Path, newVal: Item): Doc => {
if (path.length === 0) {
return {
data: newVal,
versions: isObject(newVal) ? [nextSeq, {}]
: Array.isArray(newVal) ? [nextSeq, []]
: nextSeq,
nextSeq: nextSeq + 1,
}
} else {
let p = path[0]
if (typeof p === 'string') {
if (!isObject(data)) throw Error('invalid path component for data')
if (!isVObj(versions)) throw Error('invalid versions data')
let inner = setInner(data[p], versions[1][p], nextSeq, path.slice(1), newVal)
inner.data = {
...(data.data as any),
[p]: inner.data
}
inner.versions = [versions[0], {
...versions[1],
[p]: inner.versions
}]
return inner
} else {
if (typeof p !== 'number') throw Error('invalid path component')
if (!Array.isArray(data)) throw Error('invalid path component for data')
if (!isVArr(versions)) throw Error('invalid versions data')
let inner = setInner(data[p], versions[1][p], nextSeq, path.slice(1), newVal)
let d = data.slice()
d[p] = inner.data
inner.data = d
let v: [number, Version[]] = [versions[0], versions[1].slice()]
v[1][p] = inner.versions
inner.versions = v
return inner
}
}
}
export const set = (doc: Doc, path: Path, newVal: Item): Doc => {
return setInner(doc.data, doc.versions, doc.nextSeq, path, newVal)
}
;(() => {
// ****************** Usage example
let doc: Doc = {
data: null,
versions: 0,
nextSeq: 1,
}
doc = set(doc, [], {})
console.log(doc)
doc = set(doc, ['x'], 'hi there')
console.log(doc)
doc = set(doc, [], [])
console.log(doc)
doc = set(doc, [0], 123)
console.log(doc)
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment