Skip to content

Commit

Permalink
Merge pull request #103 from oliver-oloughlin/patch/typing-fix
Browse files Browse the repository at this point in the history
Patch/typing fix
  • Loading branch information
oliver-oloughlin authored Nov 11, 2023
2 parents 401199b + 7c51029 commit b9bd80f
Show file tree
Hide file tree
Showing 27 changed files with 471 additions and 186 deletions.
70 changes: 59 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ possible, like atomic operations and queue listeners.
- [With checking](#with-checking)
- [Document Methods](#document-methods)
- [flat()](#flat)
- [Extensions](#extensions)
- [Zod](#zod)
- [zodModel()](#zodmodel)
- [Development](#development)
- [License](#license)

Expand Down Expand Up @@ -107,7 +110,7 @@ import { z } from "https://deno.land/x/zod/mod.ts"

type User = z.infer<typeof UserModel>

const UserModel = z.object({
const UserSchema = z.object({
username: z.string(),
age: z.number(),
activities: z.array(z.string()),
Expand Down Expand Up @@ -139,7 +142,7 @@ const kv = await Deno.openKv()
const db = kvdex(kv, {
numbers: collection(model<number>()),
largeStrings: largeCollection(model<string>()),
users: indexableCollection(UserModel, {
users: indexableCollection(UserSchema, {
indices: {
username: "primary" // unique
age: "secondary" // non-unique
Expand Down Expand Up @@ -227,7 +230,9 @@ const result = await db.users.add({
},
})

console.log(result.id) // f897e3cf-bd6d-44ac-8c36-d7ab97a82d77
if (result.ok) {
console.log(result.id) // f897e3cf-bd6d-44ac-8c36-d7ab97a82d77
}
```

### addMany()
Expand All @@ -237,10 +242,10 @@ Upon completion, a list of CommitResult objects will be returned.

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

// Only adds the first entry, as "username" is defined as a primary index and cannot have duplicates
await commitResult = await db.users.addMany([
await result = await db.users.addMany([
{
username: "oli",
age: 24
Expand All @@ -262,7 +267,9 @@ collection, the operation will fail.
```ts
const result = await db.numbers.set("id_1", 2048)

console.log(result.id) // id_1
if (result.ok) {
console.log(result.id) // id_1
}
```

### write()
Expand All @@ -279,9 +286,7 @@ const result1 = await db.numbers.write("id_1", 1024)
const result2 = await db.numbers.write("id_1", 2048)
const doc = await db.numbers.find("id_1")

console.log(result1.ok, result1.id) // true id_1
console.log(result2.ok, result2.id) // true id_1
console.log(doc.value) // 2048
console.log(doc?.value) // 2048
```

### update()
Expand All @@ -297,10 +302,10 @@ will fail.

```ts
// Updates the document with a new value
const result1 = await db.numbers.update("num1", 42)
const result = await db.numbers.update("num1", 42)

// Partial update using deep merge, only updates the age field
const result2 = await db.users.update("user1", {
const result = await db.users.update("user1", {
age: 67,
}, {
mergeType: "deep",
Expand Down Expand Up @@ -869,6 +874,49 @@ const flattened = doc.flat()
// }
```

## Extensions

Additional features outside of the basic functionality provided by `kvdex` .
While the basic functions are dependency-free, extended features may rely on
some specific dependenices to enhance integration. All extensions are found in
the kvdex/ext sub-path.

### Zod

#### zodModel()

Adds additional compatibility when using zod schemas as models. While zod
schemas can be used as models directly, `zodModel()` properly parses a model
from a zod schema, recognizing default fields as optional.

```ts
import { z } from "https://deno.land/x/zod/mod.ts"
import { zodModel } from "https://deno.land/x/kvdex/ext/zod.ts"
import { collection, kvdex } from "https://deno.land/x/kvdex/mod.ts"

const UserSchema = z.object({
username: z.string(),
gender: z.string().default("not given"),
})

const kv = await Deno.openKv()

const db = kvdex(kv, {
users_basic: collection(UserSchema),
users_zod: collection(zodModel(UserSchema)),
})

// Produces a type error for missing "gender" field.
const result = await db.users_basic.add({
username: "oliver",
})

// No type error for missing "gender" field.
const result = await db.users_zod.add({
username: "oliver",
})
```

## Development

Any contributions are welcomed and appreciated. How to contribute:
Expand Down
3 changes: 3 additions & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
},
"test": {
"include": ["./tests"]
},
"imports": {
"#/": "./src/"
}
}
1 change: 1 addition & 0 deletions ext/zod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./zod/zod_model.ts"
7 changes: 7 additions & 0 deletions ext/zod/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type {
TypeOf,
ZodDefault,
ZodObject,
ZodRawShape,
ZodType,
} from "https://deno.land/x/[email protected]/mod.ts"
64 changes: 64 additions & 0 deletions ext/zod/zod_model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type {
TypeOf,
ZodDefault,
ZodObject,
ZodRawShape,
ZodType,
} from "./deps.ts"
import type {
KeysOfThatDontExtend,
KeysOfThatExtend,
KvValue,
Model,
} from "#/types.ts"

/*****************/
/* */
/* TYPES */
/* */
/*****************/

export type ZodInsertModel<T extends ZodType<KvValue>> = T extends
ZodDefault<infer Z extends ZodType> ? (
(
Z extends ZodObject<ZodRawShape> ? ZodInsertModel<Z>
: TypeOf<Z>
) | undefined
)
: T extends ZodObject<infer U> ? ZodObjectInsertModel<U>
: TypeOf<T>

export type ZodObjectInsertModel<T extends ZodRawShape> =
& {
[K in KeysOfThatExtend<T, ZodDefault<ZodType>>]?: ZodInsertModel<
T[K]
>
}
& {
[K in KeysOfThatDontExtend<T, ZodDefault<ZodType>>]: ZodInsertModel<T[K]>
}

/*****************/
/* */
/* FUNCTIONS */
/* */
/*****************/

/**
* Create a model from a Zod schema.
*
* Correctly parses the insert model from the given schema.
*
* @param schema - Zod schema.
* @returns A model with base type and insert model.
*/
export function zodModel<
const T1 extends KvValue,
const T2 extends ZodType<T1>,
>(
schema: T2,
): Model<T1, ZodInsertModel<T2>> {
return {
parse: (data) => schema.parse(data),
}
}
24 changes: 14 additions & 10 deletions src/atomic_builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Collection } from "./collection.ts"
import { InvalidAtomicBuilderCollectionError } from "./errors.ts"
import { IndexableCollection } from "./indexable_collection.ts"
import { LargeCollection } from "./large_collection.ts"
import type { Collection } from "#/collection.ts"
import { InvalidAtomicBuilderCollectionError } from "#/errors.ts"
import { IndexableCollection } from "#/indexable_collection.ts"
import { LargeCollection } from "#/large_collection.ts"
import type {
AtomicCheck,
AtomicMutation,
Expand All @@ -17,7 +17,7 @@ import type {
QueueValue,
Schema,
SchemaDefinition,
} from "./types.ts"
} from "#/types.ts"
import {
allFulfilled,
deleteIndices,
Expand All @@ -26,7 +26,7 @@ import {
keyEq,
prepareEnqueue,
setIndices,
} from "./utils.ts"
} from "#/utils.ts"

/**
* Builder object for creating and executing atomic operations in the KV store.
Expand All @@ -37,11 +37,12 @@ import {
export class AtomicBuilder<
const T1 extends Schema<SchemaDefinition>,
const T2 extends KvValue,
const T3 extends T2,
> {
private kv: Deno.Kv
private schema: T1
private operations: Operations
private collection: Collection<T2, CollectionOptions<T2>>
private collection: Collection<T2, T3, CollectionOptions<T2>>

/**
* Create a new AtomicBuilder for building and executing atomic operations in the KV store.
Expand All @@ -54,7 +55,7 @@ export class AtomicBuilder<
constructor(
kv: Deno.Kv,
schema: T1,
collection: Collection<T2, CollectionOptions<T2>>,
collection: Collection<T2, T3, CollectionOptions<T2>>,
operations?: Operations,
) {
// Check for large collection
Expand Down Expand Up @@ -122,7 +123,7 @@ export class AtomicBuilder<
* @param options - Set options, optional.
* @returns Current AtomicBuilder instance.
*/
add(value: T2, options?: AtomicSetOptions) {
add(value: T3, options?: AtomicSetOptions) {
// Perform set operation with generated id.
return this.set(null, value, options)
}
Expand All @@ -145,7 +146,7 @@ export class AtomicBuilder<
* @param options - Set options, optional.
* @returns Current AtomicBuilder instance.
*/
set(id: KvId | null, value: T2, options?: AtomicSetOptions) {
set(id: KvId | null, value: T3, options?: AtomicSetOptions) {
// Create id key from collection id key and id
const collection = this.collection
const parsed = collection._model.parse(value)
Expand All @@ -172,6 +173,7 @@ export class AtomicBuilder<
_data,
this.operations.atomic,
this.collection as unknown as IndexableCollection<
KvObject,
KvObject,
IndexableCollectionOptions<KvObject>
>,
Expand Down Expand Up @@ -421,6 +423,7 @@ export class AtomicBuilder<
mut.value as KvObject,
this.operations.atomic,
this.collection as unknown as IndexableCollection<
KvObject,
KvObject,
IndexableCollectionOptions<KvObject>
>,
Expand Down Expand Up @@ -543,6 +546,7 @@ export class AtomicBuilder<
data,
atomic,
this.collection as unknown as IndexableCollection<
KvObject,
KvObject,
IndexableCollectionOptions<KvObject>
>,
Expand Down
6 changes: 3 additions & 3 deletions src/atomic_wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ATOMIC_OPERATION_MUTATION_LIMIT } from "./constants.ts"
import type { SetOptions } from "./types.ts"
import { clamp } from "./utils.ts"
import { ATOMIC_OPERATION_MUTATION_LIMIT } from "#/constants.ts"
import type { SetOptions } from "#/types.ts"
import { clamp } from "#/utils.ts"

export class AtomicWrapper implements Deno.AtomicOperation {
private kv: Deno.Kv
Expand Down
Loading

0 comments on commit b9bd80f

Please sign in to comment.