Skip to content

Commit

Permalink
Do not allow to read filter until loading by types
Browse files Browse the repository at this point in the history
  • Loading branch information
ai committed Sep 2, 2023
1 parent ab66c00 commit 0d280be
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 39 deletions.
21 changes: 13 additions & 8 deletions create-filter/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ export interface FilterOptions {
listChangesOnly?: boolean
}

export interface FilterStore<
Value extends SyncMapValues = any
> extends MapStore<{
isEmpty: boolean
isLoading: boolean
list: LoadedSyncMapValue<Value>[]
stores: Map<string, SyncMapStore<Value>>
}> {
export type LoadedFilterValue<Value extends SyncMapValues> = {
isEmpty: boolean
isLoading: false
list: LoadedSyncMapValue<Value>[]
stores: Map<string, SyncMapStore<Value>>
}

export type FilterValue<Value extends SyncMapValues> =
| { isLoading: true }
| LoadedFilterValue<Value>

export interface FilterStore<Value extends SyncMapValues = any>
extends MapStore<FilterValue<Value>> {
/**
* While store is loading initial data from server or log.
*/
Expand Down
22 changes: 13 additions & 9 deletions create-filter/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
createFilter,
createSyncMap,
deleteSyncMapById,
ensureLoaded,
loadValue,
syncMapTemplate,
TestClient
} from '../index.js'
Expand Down Expand Up @@ -48,7 +50,7 @@ function cachedIds(Template: any): string[] {
}

function getSize(filterStore: FilterStore): number {
return filterStore.get().stores.size
return ensureLoaded(filterStore.get()).stores.size
}

it('caches filters', () => {
Expand Down Expand Up @@ -102,16 +104,15 @@ it('looks for already loaded stores', async () => {
authorId: '10',
projectId: '100'
})
posts.listen(() => {})

expect(posts.get().isLoading).toBe(true)
expect(posts.get().stores).toEqual(
expect((posts.get() as any).stores).toEqual(
new Map([
['1', post1],
['2', post2]
])
)
expect(posts.get().list).toEqual([
expect((posts.get() as any).list).toEqual([
{ authorId: '10', id: '1', isLoading: false, projectId: '100' },
{ authorId: '10', id: '2', isLoading: false, projectId: '100' }
])
Expand Down Expand Up @@ -196,7 +197,7 @@ it('does not subscribe if server did it for client', async () => {
type: 'posts/changed'
})
await allTasks()
expect(posts.get().list).toEqual([
expect(ensureLoaded(posts.get()).list).toEqual([
{ id: '1', isLoading: false, title: 'A' },
{ id: '2', isLoading: false, title: 'B' }
])
Expand Down Expand Up @@ -269,7 +270,10 @@ it('loads store from the log for offline stores', async () => {
posts.listen(() => {})
await posts.loading
expect(posts.get().isLoading).toBe(false)
expect(Array.from(posts.get().stores.keys()).sort()).toEqual(['4', '5'])
expect(Array.from(ensureLoaded(posts.get()).stores.keys()).sort()).toEqual([
'4',
'5'
])
await delay(1020)
expect(cachedIds(LocalPost)).toEqual(['4', '5'])
})
Expand Down Expand Up @@ -412,7 +416,7 @@ it('supports both offline and remote stores', async () => {
await allTasks()
expect(posts.get().isLoading).toBe(false)
expect(getSize(posts)).toBe(1)
expect(Array.from(posts.get().stores.keys())).toEqual(['ID'])
expect(Array.from(ensureLoaded(posts.get()).stores.keys())).toEqual(['ID'])
})

it('keeps stores in memory and unsubscribes on destroy', async () => {
Expand Down Expand Up @@ -795,15 +799,15 @@ it('has shortcut to check size', async () => {

let posts = createFilter(client, Post, { authorId: '10' })
posts.listen(() => {})
expect(posts.get().isEmpty).toBe(true)
expect((await loadValue(posts)).isEmpty).toBe(true)

await createSyncMap(client, Post, {
authorId: '10',
id: '1',
projectId: '20',
title: '1'
})
expect(posts.get().isEmpty).toBe(false)
expect(ensureLoaded(posts.get()).isEmpty).toBe(false)
})

it('clean filters', () => {
Expand Down
6 changes: 4 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export {
createFilter,
Filter,
FilterOptions,
FilterStore
FilterStore,
FilterValue,
LoadedFilterValue
} from './create-filter/index.js'
export { CrossTabClient } from './cross-tab-client/index.js'
export { encryptActions } from './encrypt-actions/index.js'
Expand All @@ -41,7 +43,7 @@ export {
deleteSyncMapById,
ensureLoaded,
LoadedSyncMapValue,
loadSyncValue,
loadValue,
SyncMapStore,
syncMapTemplate,
SyncMapTemplate,
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export {
deleteSyncMap,
deleteSyncMapById,
ensureLoaded,
loadSyncValue,
loadValue,
syncMapTemplate
} from './sync-map-template/index.js'
export { TestClient } from './test-client/index.js'
Expand Down
16 changes: 12 additions & 4 deletions preact/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoguxNotFoundError } from '@logux/actions'
import { LoguxNotFoundError, type SyncMapValues } from '@logux/actions'
import { act, cleanup, render, screen } from '@testing-library/preact'
import { delay } from 'nanodelay'
import { restoreAll, spyOn } from 'nanospy'
Expand All @@ -19,6 +19,8 @@ import {
type ChannelError,
type ChannelNotFoundError,
createSyncMap,
type FilterValue,
type LoadedFilterValue,
LoguxUndoError,
syncMapTemplate,
type SyncMapTemplate,
Expand All @@ -34,6 +36,12 @@ import {
useSync
} from './index.js'

export function asLoaded<Value extends SyncMapValues>(
value: FilterValue<Value>
): LoadedFilterValue<Value> {
return value as LoadedFilterValue<Value>
}

function getCatcher(cb: () => void): [string[], FC] {
let errors: string[] = []
let Catcher: FC = () => {
Expand Down Expand Up @@ -350,13 +358,13 @@ it('renders filter', async () => {
let renders: string[] = []
let TestList: FC = () => {
let posts = useFilter(LocalPost, { projectId: '1' })
expect(posts.stores.size).toEqual(posts.list.length)
expect(asLoaded(posts).stores.size).toEqual(asLoaded(posts).list.length)
renders.push('list')
return h(
// @ts-expect-error
'ul',
{ 'data-testid': 'test' },
posts.list.map((post, index) => {
asLoaded(posts).list.map((post, index) => {
renders.push(post.id)
return h('li', {}, ` ${index}:${post.title}`)
})
Expand Down Expand Up @@ -429,7 +437,7 @@ it('recreating filter on args changes', async () => {
// @ts-expect-error
'ul',
{ 'data-testid': 'test' },
posts.list.map((post, index) => {
asLoaded(posts).list.map((post, index) => {
renders.push(post.id)
return h('li', { key: index }, ` ${index}:${post.title}`)
})
Expand Down
5 changes: 3 additions & 2 deletions prepare-for-test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { afterEach, expect, it } from 'vitest'
import {
createFilter,
emptyInTest,
ensureLoaded,
prepareForTest,
syncMapTemplate,
TestClient
Expand Down Expand Up @@ -61,7 +62,7 @@ it('works with filters', () => {
users1.listen(() => {})

expect(users1.get().isLoading).toBe(false)
expect(users1.get().list).toEqual([
expect(ensureLoaded(users1.get()).list).toEqual([
{ id: 'users:1', isLoading: false, name: 'Test 1' },
{ id: 'users:2', isLoading: false, name: 'Test 2' }
])
Expand All @@ -78,5 +79,5 @@ it('marks empty', () => {
users1.listen(() => {})

expect(users1.get().isLoading).toBe(false)
expect(users1.get().list).toEqual([])
expect(ensureLoaded(users1.get()).list).toEqual([])
})
14 changes: 11 additions & 3 deletions react/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoguxNotFoundError } from '@logux/actions'
import { LoguxNotFoundError, type SyncMapValues } from '@logux/actions'
import { act, cleanup, render, screen } from '@testing-library/react'
import { delay } from 'nanodelay'
import { restoreAll, spyOn } from 'nanospy'
Expand All @@ -19,6 +19,8 @@ import {
type ChannelError,
type ChannelNotFoundError,
createSyncMap,
type FilterValue,
type LoadedFilterValue,
LoguxUndoError,
syncMapTemplate,
type SyncMapTemplate,
Expand All @@ -34,6 +36,12 @@ import {
useSync
} from './index.js'

export function asLoaded<Value extends SyncMapValues>(
value: FilterValue<Value>
): LoadedFilterValue<Value> {
return value as LoadedFilterValue<Value>
}

function getCatcher(cb: () => void): [string[], FC] {
let errors: string[] = []
let Catcher: FC = () => {
Expand Down Expand Up @@ -350,7 +358,7 @@ it('renders filter', async () => {
return h(
'ul',
{ 'data-testid': 'test' },
posts.list.map((post, index) => {
asLoaded(posts).list.map((post, index) => {
renders.push(post.id)
return h('li', { key: post.id }, ` ${index}:${post.title}`)
})
Expand Down Expand Up @@ -423,7 +431,7 @@ it('recreating filter on args changes', async () => {
h(
'ul',
{ 'data-testid': 'test' },
posts.list.map((post, index) => {
asLoaded(posts).list.map((post, index) => {
renders.push(post.id)
return h('li', { key: index }, ` ${index}:${post.title}`)
})
Expand Down
14 changes: 12 additions & 2 deletions sync-map-template/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import type { Action, Meta } from '@logux/core'
import type { MapCreator, MapStore } from 'nanostores'

import type { Client } from '../client/index.js'
import type {
FilterStore,
FilterValue,
LoadedFilterValue
} from '../create-filter/index.js'

interface SyncMapStoreExt {
/**
Expand Down Expand Up @@ -275,7 +280,9 @@ export function deleteSyncMap(store: SyncMapStore): Promise<void>
export function ensureLoaded<Value extends SyncMapValues>(
value: SyncMapValue<Value>
): LoadedSyncMapValue<Value>

export function ensureLoaded<Value extends SyncMapValues>(
value: FilterValue<Value>
): LoadedFilterValue<Value>

/**
* Return store’s value if store is loaded or wait until store will be loaded
Expand All @@ -289,6 +296,9 @@ export function ensureLoaded<Value extends SyncMapValues>(
*
* @param store Store to load.
*/
export async function loadSyncValue<Value extends SyncMapValues>(
export async function loadValue<Value extends SyncMapValues>(
store: SyncMapStore<Value>
): Promise<LoadedSyncMapValue<Value>>
export async function loadValue<Value extends SyncMapValues>(
store: FilterStore<Value>
): Promise<LoadedFilterValue<Value>>
2 changes: 1 addition & 1 deletion sync-map-template/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ export function ensureLoaded(value) {
return value
}

export async function loadSyncValue(store) {
export async function loadValue(store) {
let unbind = store.listen(() => {})
if (store.get().isLoading) await store.loading
let value = store.get()
Expand Down
6 changes: 3 additions & 3 deletions sync-map-template/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
deleteSyncMap,
deleteSyncMapById,
ensureLoaded,
loadSyncValue,
loadValue,
syncMapTemplate,
type SyncMapValue,
TestClient
Expand Down Expand Up @@ -775,7 +775,7 @@ it('has helper to load value', async () => {
})

let post1 = LocalPost('1', client)
expect(await loadSyncValue(post1)).toEqual({
expect(await loadValue(post1)).toEqual({
id: '1',
isLoading: false,
title: 'A'
Expand All @@ -787,7 +787,7 @@ it('has helper to load value', async () => {
})
let post2 = LocalPost('2', client)
await post2.loading
expect(await loadSyncValue(post2)).toEqual({
expect(await loadValue(post2)).toEqual({
id: '2',
isLoading: false,
title: 'B'
Expand Down
19 changes: 15 additions & 4 deletions vue/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { LoguxNotFoundError } from '@logux/actions'
import { LoguxNotFoundError, type SyncMapValues } from '@logux/actions'
import { cleanup, render, screen } from '@testing-library/vue'
import { delay } from 'nanodelay'
import { restoreAll, spyOn } from 'nanospy'
import { atom, cleanStores, map, type MapStore, onMount } from 'nanostores'
import { afterEach, expect, it } from 'vitest'
import {
type Component,
type DeepReadonly,
defineComponent,
h,
isReadonly,
Expand All @@ -17,6 +18,8 @@ import {
import {
changeSyncMapById,
createSyncMap,
type FilterValue,
type LoadedFilterValue,
LoguxUndoError,
syncMapTemplate,
type SyncMapTemplate,
Expand All @@ -33,6 +36,12 @@ import {
useSync
} from './index.js'

export function asLoaded<Value extends SyncMapValues>(
value: DeepReadonly<FilterValue<Value>>
): DeepReadonly<LoadedFilterValue<Value>> {
return value as DeepReadonly<LoadedFilterValue<Value>>
}

function getCatcher(cb: () => void): [string[], Component] {
let errors: string[] = []
let Catcher = defineComponent(() => {
Expand Down Expand Up @@ -373,13 +382,15 @@ it('renders filter', async () => {
let renders: string[] = []
let TestList = defineComponent(() => {
let posts = useFilter(LocalPostStore, { projectId: '1' })
expect(posts.value.stores.size).toEqual(posts.value.list.length)
expect(asLoaded(posts.value).stores.size).toEqual(
asLoaded(posts.value).list.length
)
return () => {
renders.push('list')
return h(
'ul',
{ 'data-testid': 'test' },
posts.value.list.map((post, index) => {
asLoaded(posts.value).list.map((post, index) => {
renders.push(post.id)
return h('li', ` ${index}:${post.title}`)
})
Expand Down Expand Up @@ -460,7 +471,7 @@ it('recreates filter on args changes', async () => {
h(
'ul',
{ 'data-testid': 'test' },
posts.value.list.map((post, index) => {
asLoaded(posts.value).list.map((post, index) => {
renders.push(post.id)
return h('li', ` ${index}:${post.title}`)
})
Expand Down

0 comments on commit 0d280be

Please sign in to comment.