Skip to content

Commit

Permalink
Merge pull request #99 from Terreii/fix-set-is-not-iterable
Browse files Browse the repository at this point in the history
Fix set is not iterable
  • Loading branch information
Terreii authored Apr 13, 2023
2 parents d61ae28 + 09bb0f2 commit 0a85279
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 145 deletions.
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,14 @@ module.exports = {
'plugin:react-hooks/recommended',
'plugin:jest/recommended',
],
rules: {
'@typescript-eslint/ban-types': [
'error',
{
types: {
'{}': false,
},
},
],
},
}
9 changes: 3 additions & 6 deletions src/context.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PouchDB from 'pouchdb-core'
import memory from 'pouchdb-adapter-memory'

import { Provider, useContext } from './context'
import { sleep } from './test-utils'

PouchDB.plugin(memory)

Expand Down Expand Up @@ -53,9 +54,7 @@ test('should unsubscribe all when the database changes', async () => {

rerender()

await new Promise(resolve => {
setTimeout(resolve, 10)
})
await sleep(10)

expect(unsubscribe).toHaveBeenCalled()

Expand All @@ -81,9 +80,7 @@ test('should unsubscribe all when a database gets destroyed', async () => {

await myPouch.destroy()

await new Promise(resolve => {
setTimeout(resolve, 10)
})
await sleep(10)

expect(unsubscribe).toHaveBeenCalled()
})
Expand Down
2 changes: 1 addition & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ declare module 'pouchdb-utils' {
}

declare module 'pouchdb-selector-core' {
export function matchesSelector<T extends Record<string, unknown>>(
export function matchesSelector<T extends {}>(
doc: PouchDB.Core.Document<T>,
selector: PouchDB.Find.Selector
): boolean
Expand Down
71 changes: 56 additions & 15 deletions src/subscription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import mapReduce from 'pouchdb-mapreduce'

import SubscriptionManager from './subscription'

import { sleep } from './test-utils'

PouchDB.plugin(memory)
PouchDB.plugin(mapReduce)

Expand Down Expand Up @@ -106,6 +108,55 @@ test('should only subscribe once to document updates', () => {
expect(callback3).not.toHaveBeenCalled()
})

test('should handle unsubscribing during an doc update', () => {
const { changes, get } = myPouch

const doc = {
_id: 'test',
_rev: '1-fb7e8b3df19087a905ab792366bd118a',
value: 42,
}

let callback: (
change: PouchDB.Core.ChangesResponseChange<Record<string, unknown>>
) => void = () => {
console.error('should not be called')
}

;(myPouch as unknown as { changes: unknown }).changes = () => ({
on(_type: string, callFn: (c: unknown) => void) {
callback = callFn
return {
cancel: jest.fn(),
}
},
})

const subscriptionManager = new SubscriptionManager(myPouch)
const unsubscribe = subscriptionManager.subscribeToDocs(['test'], jest.fn())

let getCallback: (doc: unknown) => void = jest.fn()
;(myPouch as unknown as { get: unknown }).get = jest.fn(() => {
return {
then(fn: (doc: unknown) => void) {
getCallback = fn
return { catch: jest.fn() }
},
}
})

callback({
id: 'test',
seq: 1,
changes: [{ rev: doc._rev }],
doc,
})
unsubscribe()
myPouch.changes = changes
myPouch.get = get
expect(() => getCallback(doc)).not.toThrow()
})

test('should subscribe to view updates', () => {
const changesObject = {
on: jest.fn(() => changesObject),
Expand Down Expand Up @@ -227,9 +278,7 @@ test('should call the callback to documents with a document and to views with an
value: 42,
})

await new Promise(resolve => {
setTimeout(resolve, 50)
})
await sleep(50)

expect(docCallback).toHaveBeenCalled()
expect(typeof docCallback.mock.calls[0]).toBe('object')
Expand All @@ -249,9 +298,7 @@ test('should call the callback to documents with a document and to views with an
value: 'and the question is:',
})

await new Promise(resolve => {
setTimeout(resolve, 10)
})
await sleep(10)

expect(docCallback).toHaveBeenCalledTimes(1)
expect(viewCallback).toHaveBeenCalledTimes(1)
Expand Down Expand Up @@ -295,9 +342,7 @@ test('should have a unsubscribeAll method', async () => {
value: 42,
})

await new Promise(resolve => {
setTimeout(resolve, 50)
})
await sleep(50)

expect(docCallback).not.toHaveBeenCalled()
expect(allDocCallback).not.toHaveBeenCalled()
Expand Down Expand Up @@ -346,9 +391,7 @@ test('should clone the documents that are passed to document callbacks', async (
value: 42,
})

await new Promise(resolve => {
setTimeout(resolve, 10)
})
await sleep(10)
;(docs[0] as PouchDB.Core.IdMeta & { value: number }).value = 43

expect(docs).toHaveLength(2)
Expand All @@ -375,9 +418,7 @@ test('should subscribe to all docs if null is passed to doc subscription', async
}
await myPouch.bulkDocs(docs)

await new Promise(resolve => {
setTimeout(resolve, 50)
})
await sleep(50)

expect(callback).toHaveBeenCalledTimes(15)

Expand Down
73 changes: 27 additions & 46 deletions src/subscription.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { clone } from 'pouchdb-utils'

export type DocsCallback<T extends Record<string, unknown>> = (
export type DocsCallback<T extends {}> = (
deleted: boolean,
id: PouchDB.Core.DocumentId,
doc?: PouchDB.Core.Document<T>
) => void

interface DocsSubscription {
changesFeed: PouchDB.Core.Changes<Record<string, unknown>>
all: Set<DocsCallback<Record<string, unknown>>>
ids: Map<PouchDB.Core.DocumentId, Set<DocsCallback<Record<string, unknown>>>>
changesFeed: PouchDB.Core.Changes<{}>
all: Set<DocsCallback<{}>>
ids: Map<PouchDB.Core.DocumentId, Set<DocsCallback<{}>>>
}

export type ViewCallback = (id: PouchDB.Core.DocumentId) => void
Expand All @@ -19,10 +19,10 @@ export type subscribeToView = (
) => () => void

interface SubscriptionToAView {
feed: PouchDB.Core.Changes<Record<string, unknown>>
feed: PouchDB.Core.Changes<{}>
callbacks: Set<ViewCallback>
}
export type subscribeToDocs = <T extends Record<string, unknown>>(
export type subscribeToDocs = <T extends {}>(
ids: PouchDB.Core.DocumentId[] | null,
callback: DocsCallback<T>
) => () => void
Expand All @@ -44,7 +44,7 @@ export default class SubscriptionManager {
pouch.once('destroyed', this.#destroyListener)
}

subscribeToDocs<T extends Record<string, unknown>>(
subscribeToDocs<T extends {}>(
ids: PouchDB.Core.DocumentId[] | null,
callback: DocsCallback<T>
): () => void {
Expand All @@ -63,19 +63,15 @@ export default class SubscriptionManager {
if (isIds) {
for (const id of ids ?? []) {
if (this.#docsSubscription.ids.has(id)) {
this.#docsSubscription.ids
.get(id)
?.add(callback as DocsCallback<Record<string, unknown>>)
this.#docsSubscription.ids.get(id)?.add(callback as DocsCallback<{}>)
} else {
const set: Set<DocsCallback<Record<string, unknown>>> = new Set()
set.add(callback as DocsCallback<Record<string, unknown>>)
const set: Set<DocsCallback<{}>> = new Set()
set.add(callback as DocsCallback<{}>)
this.#docsSubscription.ids.set(id, set)
}
}
} else {
this.#docsSubscription.all.add(
callback as DocsCallback<Record<string, unknown>>
)
this.#docsSubscription.all.add(callback as DocsCallback<{}>)
}

let didUnsubscribe = false
Expand All @@ -86,16 +82,14 @@ export default class SubscriptionManager {
if (isIds) {
for (const id of ids ?? []) {
const set = this.#docsSubscription?.ids.get(id)
set?.delete(callback as DocsCallback<Record<string, unknown>>)
set?.delete(callback as DocsCallback<{}>)

if (set?.size === 0) {
this.#docsSubscription?.ids.delete(id)
}
}
} else {
this.#docsSubscription?.all.delete(
callback as DocsCallback<Record<string, unknown>>
)
this.#docsSubscription?.all.delete(callback as DocsCallback<{}>)
}

if (
Expand Down Expand Up @@ -172,47 +166,34 @@ function createDocSubscription(pouch: PouchDB.Database): DocsSubscription {
live: true,
})
.on('change', change => {
const hasAll =
docsSubscription?.all != null && docsSubscription.all.size > 0
const hasId = docsSubscription && docsSubscription.ids.has(change.id)
const hasAll = (docsSubscription?.all.size ?? 0) > 0
const idSubscriptions = docsSubscription?.ids.get(change.id)

if (change.deleted) {
if (hasAll) {
const subscription = docsSubscription as DocsSubscription
notify(subscription.all, true, change.id)
if (hasAll && docsSubscription) {
notify(docsSubscription.all, true, change.id)
}
if (hasId) {
const subscription = docsSubscription as DocsSubscription
notify(
subscription.ids.get(change.id) as Set<
DocsCallback<Record<string, unknown>>
>,
true,
change.id
)
if (idSubscriptions) {
notify(idSubscriptions, true, change.id)
}
} else {
pouch
.get(change.id)
.then(doc => {
if (hasAll) {
const subscription = docsSubscription as DocsSubscription
if (hasAll && docsSubscription) {
notify(
subscription.all,
docsSubscription.all,
false,
change.id,
doc as unknown as PouchDB.Core.Document<Record<string, unknown>>
doc as unknown as PouchDB.Core.Document<{}>
)
}
if (hasId) {
const subscription = docsSubscription as DocsSubscription
if (idSubscriptions) {
notify(
subscription.ids.get(change.id) as Set<
DocsCallback<Record<string, unknown>>
>,
idSubscriptions,
false,
change.id,
doc as unknown as PouchDB.Core.Document<Record<string, unknown>>
doc as unknown as PouchDB.Core.Document<{}>
)
}
})
Expand All @@ -230,10 +211,10 @@ function createDocSubscription(pouch: PouchDB.Database): DocsSubscription {
}

function notify(
set: Set<DocsCallback<Record<string, unknown>>>,
set: Set<DocsCallback<{}>>,
deleted: boolean,
id: PouchDB.Core.DocumentId,
doc?: PouchDB.Core.Document<Record<string, unknown>>
doc?: PouchDB.Core.Document<{}>
) {
for (const subscription of set) {
try {
Expand Down
6 changes: 6 additions & 0 deletions src/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,9 @@ export async function waitForLoadingChange(
expect(result.current.loading).toBe(desiredState)
})
}

export async function sleep(milliseconds: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, milliseconds)
})
}
Loading

0 comments on commit 0a85279

Please sign in to comment.