Skip to content

Commit

Permalink
Merge pull request #25 from oliver-oloughlin/feature/add-many
Browse files Browse the repository at this point in the history
Feature/add many
  • Loading branch information
oliver-oloughlin authored May 29, 2023
2 parents bd2ce2a + a3d7755 commit 506653c
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 5 deletions.
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Optional and nullable properties are allowed. If you wish to use Zod, you can
create your Zod object schema and use its type as your model.

```ts
import type { Model } from "https://deno.land/x/kvdex@v0.2.0/mod.ts"
import type { Model } from "https://deno.land/x/kvdex@v0.2.1/mod.ts"

interface User extends Model {
username: string
Expand All @@ -35,7 +35,7 @@ The "createDb" function is used for creating a new database instance. It takes a
Deno KV instance and a schema builder function as arguments.

```ts
import { createDb } from "https://deno.land/x/kvdex@v0.2.0/mod.ts"
import { createDb } from "https://deno.land/x/kvdex@v0.2.1/mod.ts"

const kv = await Deno.openKv()

Expand Down Expand Up @@ -118,6 +118,30 @@ const result = await db.users.add({
console.log(result.id) // f897e3cf-bd6d-44ac-8c36-d7ab97a82d77
```

### Add Many

The "addMany" method is used to add multiple document entries to the KV store in
a single operation. For Indexable Collection, any entries that violate the
constraint of primary indices will cause the operation to fail Returns a
Deno.KvCommitResult or Deno.KvCommitError upon completion.

```ts
// Adds 5 new document entries to the KV store.
await result = await db.numbers.add(1, 2, 3, 4, 5)

// Will fail, as "username" is defined as a primary index and cannot have duplicates
await result = await db.indexableUsers.add(
{
username: "oli",
age: 24
},
{
username: "oli",
age: 56
}
)
```

### Set

The "set" method is very similar to the "add" method, and is used to add a new
Expand Down Expand Up @@ -400,7 +424,7 @@ result will be an object containing: id, versionstamp and all the entries in the
document value.

```ts
import { flatten } from "https://deno.land/x/kvdex@v0.2.0/mod.ts"
import { flatten } from "https://deno.land/x/kvdex@v0.2.1/mod.ts"

// We assume the document exists in the KV store
const doc = await db.users.find(123n)
Expand Down
42 changes: 42 additions & 0 deletions src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,48 @@ export class Collection<const T extends KvValue> {
}
}

/**
* Adds multiple documents to the KV store.
*
* **Example:**
* ```ts
* // Adds 5 new document entries to the KV store.
* await result = await db.numbers.add(1, 2, 3, 4, 5)
*
* // Will fail, as "username" is defined as a primary index and cannot have duplicates
* await result = await db.users.add(
* {
* username: "oli",
* age: 24
* },
* {
* username: "oli",
* age: 56
* }
* )
* ```
*
* @param entries - Data entries to be added.
* @returns A promise that resolves to Deno.KvCommitResult or Deno.KvCommitError
*/
async addMany(...entries: T[]) {
let atomic = this.kv.atomic()

entries.forEach((data) => {
const id = crypto.randomUUID()
const key = extendKey(this.collectionIdKey, id)

atomic = atomic
.check({
key,
versionstamp: null,
})
.set(key, data)
})

return await atomic.commit()
}

/**
* Deletes multiple documents from the KV store according to the given options.
*
Expand Down
33 changes: 33 additions & 0 deletions src/indexable_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,39 @@ export class IndexableCollection<
return commitResult
}

async addMany<const TEntries extends [T1, ...T1[]]>(
...entries: TEntries
): Promise<Deno.KvCommitResult | Deno.KvCommitError> {
for (const index of this.primaryIndexList) {
const indexValues = entries.map((data) => JSON.stringify(data[index]))
const indexValuesSet = new Set(indexValues)

if (indexValues.length !== indexValuesSet.size) {
return {
ok: false,
}
}
}

let atomic = this.kv.atomic()

entries.forEach((data) => {
const id = generateId()
const idKey = extendKey(this.collectionIdKey, id)

atomic = atomic
.check({
key: idKey,
versionstamp: null,
})
.set(idKey, data)

atomic = setIndices(id, data, atomic, this)
})

return await atomic.commit()
}

/**
* Find a document by index value.
* Note that selecting an index that was not defined when creating the collection will always return null.
Expand Down
19 changes: 18 additions & 1 deletion test/tests/collection.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Document, flatten } from "../../mod.ts"
import { db, type Person, reset, testPerson } from "../config.ts"
import { db, type Person, reset, testPerson, testPerson2 } from "../config.ts"
import { assert } from "../../deps.ts"

Deno.test({
Expand Down Expand Up @@ -234,6 +234,23 @@ Deno.test({
},
})

// Test "addMany" method
await t.step("addMany", async (t2) => {
await t2.step("Should add all document entries", async () => {
await reset()

const cr = await db.people.addMany(testPerson, testPerson2)

assert(cr.ok)

const people = await db.people.getMany()

assert(people.length === 2)
assert(people.some((doc) => doc.value.name === testPerson.name))
assert(people.some((doc) => doc.value.name === testPerson2.name))
})
})

// Test "delete" method
await t.step({
name: "delete",
Expand Down
68 changes: 67 additions & 1 deletion test/tests/indexable_collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,73 @@ Deno.test("indexable_collection", async (t1) => {
)
})

// Test "findByIndex" method
// Test "addMany" method
await t1.step("addMany", async (t2) => {
await t2.step(
"Should add all document entries and index entries",
async () => {
await reset()

const cr = await db.indexablePeople.addMany(testPerson, testPerson2)

assert(cr.ok)

const people = await db.indexablePeople.getMany()

assert(people.length === 2)
assert(people.some((doc) => doc.value.name === testPerson.name))
assert(people.some((doc) => doc.value.name === testPerson2.name))

const byName1 = await db.indexablePeople.findByPrimaryIndex({
name: testPerson.name,
})

const byName2 = await db.indexablePeople.findByPrimaryIndex({
name: testPerson2.name,
})

assert(byName1 !== null)
assert(byName2 !== null)

const byAge = await db.indexablePeople.findBySecondaryIndex({
age: 24,
})

assert(byAge.length === 2)
assert(byAge.some((doc) => doc.value.name === testPerson.name))
assert(byAge.some((doc) => doc.value.name === testPerson2.name))
},
)

await t2.step(
"Should not add document entries with duplicate indices",
async () => {
await reset()

const cr = await db.indexablePeople.addMany(testPerson, testPerson)

assert(!cr.ok)

const people = await db.indexablePeople.getMany()

assert(people.length === 0)

const byName = await db.indexablePeople.findByPrimaryIndex({
name: testPerson.name,
})

assert(byName === null)

const byAge = await db.indexablePeople.findBySecondaryIndex({
age: 24,
})

assert(byAge.length === 0)
},
)
})

// Test "findByPrimaryIndex" method
await t1.step("findByPrimaryIndex", async (t2) => {
await t2.step("Should find document by primary index", async () => {
await reset()
Expand Down

0 comments on commit 506653c

Please sign in to comment.