Skip to content

Commit

Permalink
feat: export unstable store
Browse files Browse the repository at this point in the history
  • Loading branch information
bjoerge committed Oct 1, 2023
1 parent 362615b commit c2310ef
Show file tree
Hide file tree
Showing 17 changed files with 210 additions and 119 deletions.
9 changes: 5 additions & 4 deletions examples/localStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
set,
setIfMissing,
} from '@bjoerge/mutiny'
import {createStore} from '../src/store/store'
import {createStore} from '@bjoerge/mutiny/_unstable_apply'

const localStore = createStore([{_id: 'hello', _type: 'hello'}])
console.log('initial', localStore.get('hello'))
Expand Down Expand Up @@ -39,9 +39,10 @@ localStore.apply([
patch('drafts.blog-post-1', at('title', set('ready for publishing'))),
])

const d = localStore.get('drafts.blog-post-1')
if (d) {
localStore.apply([createOrReplace(d), del('drafts.blog-post-1')])
const doc = localStore.get('drafts.blog-post-1')

if (doc) {
localStore.apply([createOrReplace(doc), del('drafts.blog-post-1')])
}
console.log('---all documents---')
for (const [id, document] of localStore.entries()) {
Expand Down
14 changes: 7 additions & 7 deletions src/apply/applyInCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import type {
DeleteMutation,
Mutation,
PatchMutation,
SanityDocument,
SanityDocumentBase,
} from '../mutations/types'

export function applyInCollection<Doc extends SanityDocument>(
export function applyInCollection<Doc extends SanityDocumentBase>(
collection: Doc[],
mutations: Mutation | Mutation[],
) {
Expand All @@ -37,7 +37,7 @@ export function applyInCollection<Doc extends SanityDocument>(
}, collection)
}

function createIn<Doc extends SanityDocument>(
function createIn<Doc extends SanityDocumentBase>(
collection: Doc[],
mutation: CreateMutation<Doc>,
) {
Expand All @@ -50,7 +50,7 @@ function createIn<Doc extends SanityDocument>(
return collection.concat(mutation.document)
}

function createIfNotExistsIn<Doc extends SanityDocument>(
function createIfNotExistsIn<Doc extends SanityDocumentBase>(
collection: Doc[],
mutation: CreateIfNotExistsMutation<Doc>,
) {
Expand All @@ -60,7 +60,7 @@ function createIfNotExistsIn<Doc extends SanityDocument>(
return currentIdx === -1 ? collection.concat(mutation.document) : collection
}

function createOrReplaceIn<Doc extends SanityDocument>(
function createOrReplaceIn<Doc extends SanityDocumentBase>(
collection: Doc[],
mutation: CreateOrReplaceMutation<Doc>,
) {
Expand All @@ -72,15 +72,15 @@ function createOrReplaceIn<Doc extends SanityDocument>(
: splice(collection, currentIdx, 1, [mutation.document])
}

function deleteIn<Doc extends SanityDocument>(
function deleteIn<Doc extends SanityDocumentBase>(
collection: Doc[],
mutation: DeleteMutation,
) {
const currentIdx = collection.findIndex(doc => doc._id === mutation.id)
return currentIdx === -1 ? collection : splice(collection, currentIdx, 1)
}

function patchIn<Doc extends SanityDocument>(
function patchIn<Doc extends SanityDocumentBase>(
collection: Doc[],
mutation: PatchMutation,
): Doc[] {
Expand Down
94 changes: 58 additions & 36 deletions src/apply/applyInIndex.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import {ulid} from 'ulid'
import {assignId, hasId} from './store/utils'
import {applyPatchMutation} from './applyPatchMutation'
import type {RequiredSelect} from './store/store'
import type {
CreateIfNotExistsMutation,
CreateMutation,
CreateOrReplaceMutation,
DeleteMutation,
Mutation,
PatchMutation,
SanityDocument,
SanityDocumentBase,
} from '../mutations/types'

export type DocumentIndex<Doc> = {[id: string]: Doc}
export type DocumentIndex<Doc extends SanityDocumentBase> = {[id: string]: Doc}

export function applyInIndex<Doc extends SanityDocument>(
index: DocumentIndex<SanityDocument>,
mutations: Mutation<Doc>[],
) {
export function applyInIndex<
Doc extends SanityDocumentBase,
Index extends DocumentIndex<ToStored<Doc>>,
>(index: Index, mutations: Mutation<Doc>[]): Index {
return mutations.reduce((prev, mutation) => {
if (mutation.type === 'create') {
return createIn(prev, mutation)
Expand All @@ -36,54 +39,73 @@ export function applyInIndex<Doc extends SanityDocument>(
}, index)
}

function createIn<Doc extends SanityDocument>(
collection: DocumentIndex<SanityDocument>,
mutation: CreateMutation<Doc>,
) {
if (mutation.document._id in collection) {
export type ToStored<Doc extends SanityDocumentBase> = Doc &
Required<SanityDocumentBase>

export type ToIdentified<Doc extends SanityDocumentBase> = RequiredSelect<
Doc,
'_id'
>

export type StoredDocument = ToStored<SanityDocumentBase>

function createIn<
Index extends DocumentIndex<Doc>,
Doc extends SanityDocumentBase,
>(index: Index, mutation: CreateMutation<Doc>): Index {
const document = assignId(mutation.document, ulid)

if (document._id in index) {
throw new Error('Document already exist')
}
return {...collection, [mutation.document._id]: mutation.document}
return {...index, [document._id]: mutation.document}
}

function createIfNotExistsIn<Doc extends SanityDocument>(
collection: DocumentIndex<SanityDocument>,
mutation: CreateIfNotExistsMutation<Doc>,
) {
return mutation.document._id in collection
? collection
: {...collection, [mutation.document._id]: mutation.document}
function createIfNotExistsIn<
Index extends DocumentIndex<Doc>,
Doc extends SanityDocumentBase,
>(index: Index, mutation: CreateIfNotExistsMutation<Doc>): Index {
if (!hasId(mutation.document)) {
throw new Error('Cannot createIfNotExists on document without _id')
}
return mutation.document._id in index
? index
: {...index, [mutation.document._id]: mutation.document}
}

function createOrReplaceIn<Doc extends SanityDocument>(
collection: DocumentIndex<SanityDocument>,
mutation: CreateOrReplaceMutation<Doc>,
) {
return {...collection, [mutation.document._id]: mutation.document}
function createOrReplaceIn<
Index extends DocumentIndex<Doc>,
Doc extends SanityDocumentBase,
>(index: Index, mutation: CreateOrReplaceMutation<Doc>): Index {
if (!hasId(mutation.document)) {
throw new Error('Cannot createIfNotExists on document without _id')
}

return {...index, [mutation.document._id]: mutation.document}
}

function deleteIn(
collection: DocumentIndex<SanityDocument>,
function deleteIn<Index extends DocumentIndex<SanityDocumentBase>>(
index: Index,
mutation: DeleteMutation,
) {
if (mutation.id in collection) {
const copy = {...collection}
): Index {
if (mutation.id in index) {
const copy = {...index}
delete copy[mutation.id]
return copy
} else {
return collection
return index
}
}

function patchIn<Doc extends SanityDocument>(
collection: DocumentIndex<Doc>,
function patchIn<Index extends DocumentIndex<SanityDocumentBase>>(
index: Index,
mutation: PatchMutation,
): DocumentIndex<Doc> {
if (!(mutation.id in collection)) {
): Index {
if (!(mutation.id in index)) {
throw new Error('Cannot apply patch on nonexistent document')
}
const current = collection[mutation.id]!
const current = index[mutation.id]!
const next = applyPatchMutation(current, mutation)

return next === current ? collection : {...collection, [mutation.id]: next}
return next === current ? index : {...index, [mutation.id]: next}
}
4 changes: 2 additions & 2 deletions src/apply/applyPatchMutation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {applyPatches} from './patch/applyPatch'
import type {PatchMutation, SanityDocument} from '../mutations/types'
import type {PatchMutation, SanityDocumentBase} from '../mutations/types'

export function applyPatchMutation<
Doc extends SanityDocument,
Doc extends SanityDocumentBase,
Mutation extends PatchMutation,
>(document: Doc, mutation: Mutation): Doc {
if (
Expand Down
8 changes: 6 additions & 2 deletions src/apply/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ export * from './patch/applyPatch'
export * from './applyPatchMutation'
export * from './applyInCollection'
export * from './applyInIndex'
export * from './patch/applyOp'
export * from './store'

/** Required support types */
export type {NodePatch, PatchOptions} from '../mutations/types'
export type * from '../path'
export type * from '../mutations/operations/types'
export type * from './patch/typings/applyPatch'
export type * from './patch/typings/applyOp'
export type {
CreateIfNotExistsMutation,
CreateMutation,
CreateOrReplaceMutation,
DeleteMutation,
Mutation,
PatchMutation,
SanityDocument,
SanityDocumentBase,
} from '../mutations/types'
export type {Optional} from '../utils/typeUtils'
export type {Optional, MergeObject, ArrayElement} from '../utils/typeUtils'
10 changes: 5 additions & 5 deletions src/apply/patch/applyOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export type SimpleObject = {
[K in string]: any
}

type AnyOp = SetOp<any> | SetIfMissingOp<any> | UnsetOp
type NumberOps = IncOp<any> | DecOp<any>
type StringOps = DiffMatchPatchOp
type ObjectOps = AssignOp<SimpleObject> | UnassignOp<any>
type ArrayOps =
export type AnyOp = SetOp<any> | SetIfMissingOp<any> | UnsetOp
export type NumberOps = IncOp<any> | DecOp<any>
export type StringOps = DiffMatchPatchOp
export type ObjectOps = AssignOp<SimpleObject> | UnassignOp<any>
export type ArrayOps =
| InsertOp<any, any, any>
| UpsertOp<any, any, any>
| ReplaceOp<any, any>
Expand Down
4 changes: 1 addition & 3 deletions src/apply/patch/typings/applyOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import type {
UnsetOp,
} from '../../../mutations/operations/types'
import type {Apply, N} from 'hotscript'
import type {MergeObject} from '../../../utils/typeUtils'

type ArrayElement<A> = A extends readonly (infer T)[] ? T : never
import type {ArrayElement, MergeObject} from '../../../utils/typeUtils'

export type ApplyOp<O extends Operation, Current> = Current extends never
? never
Expand Down
15 changes: 11 additions & 4 deletions src/apply/patch/typings/applyPatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {MergeObject} from '../../../utils/typeUtils'
import type {ApplyOp} from './applyOp'
import type {NodePatch} from '../../../mutations/types'

type PickOrUndef<T, Head> = Head extends keyof T ? T[Head] : undefined
export type PickOrUndef<T, Head> = Head extends keyof T ? T[Head] : undefined

export type SetAtPath<P extends Path, O extends SetOp<any>, Node> = P extends [
infer Head,
Expand Down Expand Up @@ -114,7 +114,7 @@ export type SetIfMissingAtPath<
: Node
: Node

type UnsetAtPath<P extends Path, O extends UnsetOp, T> = P extends [
export type UnsetAtPath<P extends Path, O extends UnsetOp, T> = P extends [
infer Head,
...infer Tail,
]
Expand Down Expand Up @@ -147,9 +147,16 @@ export type ApplyInObject<
? DecAtPath<P, O, T>
: T

type ApplyInArray<P extends Path, O extends Operation, T extends any[]> = T // @todo
export type ApplyInArray<
P extends Path,
O extends Operation,
T extends any[],
> = T // @todo

type ApplyAtPrimitive<P extends Path, O extends Operation, T> = ApplyOp<O, T>
export type ApplyAtPrimitive<P extends Path, O extends Operation, T> = ApplyOp<
O,
T
>

export type ApplyPatches<Patches, Doc> = Patches extends [
infer Head,
Expand Down
2 changes: 2 additions & 0 deletions src/apply/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './store'
export * from './utils'
61 changes: 61 additions & 0 deletions src/apply/store/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {ulid} from 'ulid'
import {arrify} from '../../utils/arrify'
import {applyInIndex} from '../applyInIndex'
import {assignId} from './utils'
import type {DocumentIndex, MergeObject} from '../../apply'
import type {ToIdentified, ToStored} from '../applyInIndex'
import type {Mutation, SanityDocumentBase} from '../../mutations/types'

export type RequiredSelect<T, K extends keyof T> = Omit<T, K> & {
[P in K]-?: T[P]
}

function update<Doc extends ToIdentified<SanityDocumentBase>>(
doc: Doc,
revision: string,
): ToStored<Doc> {
return {
...doc,
_rev: revision,
_createdAt: doc._createdAt || new Date().toISOString(),
_updatedAt: new Date().toISOString(),
}
}

const empty: DocumentIndex<any> = {}

export const createStore = <Doc extends SanityDocumentBase>(
initialEntries?: Doc[],
) => {
let version = 0

let index: DocumentIndex<MergeObject<ToStored<Doc & SanityDocumentBase>>> =
initialEntries && initialEntries?.length > 0
? Object.fromEntries(
initialEntries.map(entry => {
const doc = update(assignId(entry, ulid), ulid())
return [doc._id, doc]
}),
)
: empty

return {
get version() {
return version
},
// todo: support listening for changes
entries: () => Object.entries(index),
get: <Id extends string>(
id: Id,
): MergeObject<
Omit<(typeof index)[keyof typeof index], '_id'> & {_id: Id}
> => index[id] as any,
apply: (mutations: Mutation[] | Mutation) => {
const nextIndex = applyInIndex(index, arrify(mutations))
if (nextIndex !== index) {
index = nextIndex
version++
}
},
}
}
12 changes: 12 additions & 0 deletions src/apply/store/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type {StoredDocument} from '../applyInIndex'
import type {SanityDocumentBase} from '../../mutations/types'

export function hasId(doc: SanityDocumentBase): doc is StoredDocument {
return '_id' in doc
}
export function assignId<Doc extends SanityDocumentBase>(
doc: Doc,
generateId: () => string,
): Doc & {_id: string} {
return hasId(doc) ? doc : {...doc, _id: generateId()}
}
Loading

0 comments on commit c2310ef

Please sign in to comment.