Repository: maxnowack/signaldb
Pull request #135 (2023-09-07 09:24:07)

feat: add PersistentCollection helper class

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/maxnowack/signaldb/pull/135?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack) Report
> Merging [#135](https://app.codecov.io/gh/maxnowack/signaldb/pull/135?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack) (4151bf2) into [main](https://app.codecov.io/gh/maxnowack/signaldb/commit/1d364d63c08d1f9039deed409b67315b80ed3748?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack) (1d364d6) will **increase** coverage by `0.24%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main     #135      +/-   ##
==========================================
+ Coverage   96.92%   97.17%   +0.24%     
==========================================
  Files          19       20       +1     
  Lines         684      709      +25     
  Branches      173      177       +4     
==========================================
+ Hits          663      689      +26     
+ Misses         21       20       -1     
```


| [Files Changed](https://app.codecov.io/gh/maxnowack/signaldb/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack) | Coverage Δ | |
|---|---|---|
| [src/Collection/index.ts](https://app.codecov.io/gh/maxnowack/signaldb/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack#diff-c3JjL0NvbGxlY3Rpb24vaW5kZXgudHM=) | `98.26% <100.00%> (ø)` | |
| [src/PersistentCollection.ts](https://app.codecov.io/gh/maxnowack/signaldb/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack#diff-c3JjL1BlcnNpc3RlbnRDb2xsZWN0aW9uLnRz) | `100.00% <100.00%> (ø)` | |
| [src/index.ts](https://app.codecov.io/gh/maxnowack/signaldb/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack#diff-c3JjL2luZGV4LnRz) | `100.00% <100.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/maxnowack/signaldb/pull/135/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Max+Nowack)



Patch:
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index ef4d159..9fda43d 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -32,6 +32,14 @@ import { Collection } from 'signaldb'
 const posts = new Collection()
 ```
 
+but normally you want to persist your data. Persistence in SignalDB is achieved by using [persistence adapters](/data-persistence/) but to make thing easier for you, there is a helper class called `PersistentCollection` that configures that for you. The `PersistentCollection` chooses automatically where to store the data and uses `localStorage` in the browser and a JSON file on the serverside.
+
+```js
+import { PersistentCollection } from 'signaldb'
+
+const posts = new PersistentCollection('posts')
+```
+
 That's all you have to do. There a also some optional configuration options you find here: [collections reference](/collections/)
 
 
diff --git a/src/Collection/index.ts b/src/Collection/index.ts
index 0dc6ae7..7878a79 100644
--- a/src/Collection/index.ts
+++ b/src/Collection/index.ts
@@ -11,7 +11,7 @@ import randomId from '../utils/randomId'
 import Cursor from './Cursor'
 import type { BaseItem, FindOptions, Transform } from './types'
 
-export type { Transform, SortSpecifier, FieldSpecifier, FindOptions } from './types'
+export type { BaseItem, Transform, SortSpecifier, FieldSpecifier, FindOptions } from './types'
 export type { CursorOptions } from './Cursor'
 export type { ObserveCallbacks } from './Observer'
 
diff --git a/src/PersistentCollection.ts b/src/PersistentCollection.ts
new file mode 100644
index 0000000..963916c
--- /dev/null
+++ b/src/PersistentCollection.ts
@@ -0,0 +1,24 @@
+import type { BaseItem, CollectionOptions } from './Collection'
+import Collection from './Collection'
+import createLocalStorageAdapter from './persistence/createLocalStorageAdapter'
+import createFilesystemAdapter from './persistence/createFilesystemAdapter'
+
+function createAdapter<
+  T extends { id: I } & Record<string, any>,
+  I,
+>(name: string) {
+  if (typeof window === 'undefined') { // server side
+    return createFilesystemAdapter<T, I>(`persistent-collection-${name}.json`)
+  }
+  return createLocalStorageAdapter<T, I>(name)
+}
+
+// eslint-disable-next-line max-len
+export default class PersistentCollection<T extends BaseItem<I> = BaseItem, I = any, U = T> extends Collection<T, I, U> {
+  constructor(name: string, options?: CollectionOptions<T, I, U>) {
+    super({
+      persistence: createAdapter<T, I>(name),
+      ...options,
+    })
+  }
+}
diff --git a/src/index.ts b/src/index.ts
index aec594d..4fa014a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,6 +11,7 @@ export type {
 } from './Collection'
 
 export { default as Collection } from './Collection'
+export { default as PersistentCollection } from './PersistentCollection'
 export { default as createLocalStorageAdapter } from './persistence/createLocalStorageAdapter'
 export { default as createFilesystemAdapter } from './persistence/createFilesystemAdapter'
 export { default as createPersistenceAdapter } from './persistence/createPersistenceAdapter'


Test patch:
diff --git a/__tests__/PersistentCollection.client.spec.ts b/__tests__/PersistentCollection.client.spec.ts
new file mode 100644
index 0000000..29f55ae
--- /dev/null
+++ b/__tests__/PersistentCollection.client.spec.ts
@@ -0,0 +1,19 @@
+// @vitest-environment happy-dom
+import { describe, it, expect } from 'vitest'
+import { PersistentCollection } from '../src/index'
+import waitForEvent from '../src/utils/waitForEvent'
+
+describe('PersistentCollection', () => {
+  it('should create a persistent collection on the clientside', async () => {
+    const collection = new PersistentCollection('test')
+    await waitForEvent(collection, 'persistence.init')
+    collection.insert({ id: '1', name: 'John' })
+    collection.insert({ id: '2', name: 'Jane' })
+    await waitForEvent(collection, 'persistence.transmitted')
+
+    const item = collection.findOne({ name: 'John' })
+    expect(item).toEqual({ id: '1', name: 'John' })
+
+    expect(!!localStorage.getItem('signaldb-collection-test')).toBe(true)
+  })
+})
diff --git a/__tests__/PersistentCollection.spec.ts b/__tests__/PersistentCollection.spec.ts
new file mode 100644
index 0000000..3894e34
--- /dev/null
+++ b/__tests__/PersistentCollection.spec.ts
@@ -0,0 +1,24 @@
+import fs from 'fs'
+import { describe, it, expect, afterAll } from 'vitest'
+import { PersistentCollection } from '../src/index'
+import waitForEvent from '../src/utils/waitForEvent'
+
+describe('PersistentCollection', () => {
+  it('should create a persistent collection on the serverside', async () => {
+    const collection = new PersistentCollection('test')
+    await waitForEvent(collection, 'persistence.init')
+    collection.insert({ id: '1', name: 'John' })
+    collection.insert({ id: '2', name: 'Jane' })
+    await waitForEvent(collection, 'persistence.transmitted')
+
+    const item = collection.findOne({ name: 'John' })
+    expect(item).toEqual({ id: '1', name: 'John' })
+
+    expect(fs.existsSync('./persistent-collection-test.json')).toBe(true)
+  })
+
+  afterAll(async () => {
+    await new Promise((resolve) => { setTimeout(resolve, 100) })
+    await fs.promises.unlink('./persistent-collection-test.json').catch(() => {})
+  })
+})