From b7a83b1469e62dc22091561ba6c23ee47a74c88d Mon Sep 17 00:00:00 2001 From: oliver-oloughlin Date: Fri, 8 Mar 2024 01:14:32 +0100 Subject: [PATCH] chore: added jsdoc for zod kv schemas --- deno.json | 3 +- deno.lock | 22 ++++++++- ext/migrate.ts | 14 ++++++ ext/zod.ts | 4 ++ src/collection.ts | 5 +- src/types.ts | 15 +++--- tests/collection/properties.test.ts | 47 +++++++++++++++++- tests/indexable_collection/properties.test.ts | 48 ++++++++++++++++++- .../serialized_collection/properties.test.ts | 48 ++++++++++++++++++- .../properties.test.ts | 48 +++++++++++++++++++ 10 files changed, 237 insertions(+), 17 deletions(-) diff --git a/deno.json b/deno.json index 7239f30..8580386 100644 --- a/deno.json +++ b/deno.json @@ -10,7 +10,8 @@ "check": "deno check mod.ts src/*.ts ext/*.ts tests/*.ts tests/**/*.ts benchmarks/**/*ts", "test": "deno test --allow-write --allow-read --unstable-kv --trace-ops", "bench": "deno bench --unstable-kv", - "prep": "deno task check && deno fmt && deno lint && deno publish --dry-run && deno task test" + "prep": "deno task check && deno fmt && deno lint && deno publish --dry-run && deno task test", + "cache": "deno cache -r mod.ts && deno cache -r ext/zod.ts && deno cache -r ext/migrate.ts" }, "fmt": { "semiColons": false diff --git a/deno.lock b/deno.lock index 1d31a22..bc7fd9f 100644 --- a/deno.lock +++ b/deno.lock @@ -10,6 +10,7 @@ "jsr:@std/fmt@^0.217.0": "jsr:@std/fmt@0.217.0", "jsr:@std/ulid@^0.217": "jsr:@std/ulid@0.217.0", "npm:@types/node": "npm:@types/node@18.16.19", + "npm:superjson@1.13.3": "npm:superjson@1.13.3", "npm:zod@^3.22": "npm:zod@3.22.4" }, "jsr": { @@ -43,11 +44,30 @@ "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dependencies": {} }, + "copy-anything@3.0.5": { + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "is-what@4.1.16" + } + }, + "is-what@4.1.16": { + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dependencies": {} + }, + "superjson@1.13.3": { + "integrity": "sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==", + "dependencies": { + "copy-anything": "copy-anything@3.0.5" + } + }, "zod@3.22.4": { "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dependencies": {} } } }, - "remote": {} + "remote": { + "https://deno.land/std@0.203.0/dotenv/load.ts": "0636983549b98f29ab75c9a22a42d9723f0a389ece5498fe971e7bb2556a12e2", + "https://deno.land/std@0.203.0/dotenv/mod.ts": "1da8c6d0e7f7d8a5c2b19400b763bc11739df24acec235dda7ea2cfd3d300057" + } } diff --git a/ext/migrate.ts b/ext/migrate.ts index 7a68151..241b032 100644 --- a/ext/migrate.ts +++ b/ext/migrate.ts @@ -65,6 +65,7 @@ if (import.meta.main) { }) } +/** Options for migrating entries from a source KV instance to a target KV instance */ export type MigrateOptions = { /** Source KV. */ source: Deno.Kv @@ -83,6 +84,19 @@ export type MigrateOptions = { /** * Migrate entries from a source KV instance to a target KV instance. * + * @example + * ```ts + * import { migrate } from "jsr:@olli/kvdex/ext/migrate" + * + * const source = await Deno.openKv("./source.sqlite3") + * const target = await Deno.openKv("./target.sqlite3") + * + * await migrate({ + * source, + * target, + * }) + * ``` + * * @param options - Migrate options */ export async function migrate({ diff --git a/ext/zod.ts b/ext/zod.ts index 3fbfa3d..c551870 100644 --- a/ext/zod.ts +++ b/ext/zod.ts @@ -73,12 +73,14 @@ const LazyKvArraySchema = z.lazy(() => KvArraySchema) const LazyKvObjectSchema = z.lazy(() => KvObjectSchema) +/** Zod schema for KvId type */ export const KvIdSchema: z.ZodType = z.string() .or(z.number()) .or(z.bigint()) .or(z.boolean()) .or(z.instanceof(Uint8Array)) +/** Zod schema for KvValue type */ export const KvValueSchema: z.ZodType = z.undefined() .or(z.null()) .or(z.string()) @@ -107,8 +109,10 @@ export const KvValueSchema: z.ZodType = z.undefined() .or(z.instanceof(DataView)) .or(z.instanceof(Error)) +/** Zod schema for KvArray type */ export const KvArraySchema: z.ZodType = z.array(KvValueSchema) +/** Zod schema for KvObject type */ export const KvObjectSchema: z.ZodType = z.record( z.string().or(z.number()), KvValueSchema, diff --git a/src/collection.ts b/src/collection.ts index 6e8d4e7..d410e9f 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -38,7 +38,6 @@ import type { UpdateOneOptions, UpdateOptions, UpdateStrategy, - UpsertOptions, WatchOptions, } from "./types.ts" import { @@ -862,7 +861,7 @@ export class Collection< * @returns A promise resolving to either CommitResult or CommitError. */ async upsert< - const TUpsertOptions extends UpsertOptions, + const TUpsertOptions extends UpdateOptions, >( input: IdUpsert, options?: TUpsertOptions, @@ -910,7 +909,7 @@ export class Collection< */ async upsertByPrimaryIndex< const TIndex extends PrimaryIndexKeys, - const TUpsertOptions extends UpsertOptions, + const TUpsertOptions extends UpdateOptions, >( input: PrimaryIndexUpsert< TInput, diff --git a/src/types.ts b/src/types.ts index 6550eb2..502e513 100644 --- a/src/types.ts +++ b/src/types.ts @@ -312,7 +312,7 @@ export type ParseInputType = TInput extends /** * Model describing the input and output type of data. - * Contains a parse function, and optionally a __validate function used instead of parse upon reading data. + * Contains a parse function, and optionally a `__validate()` function used instead of parse upon reading data. */ export type Model = { /** A parse function that takes an input type and returns an output type */ @@ -580,9 +580,6 @@ export type PrimaryIndexUpsert< update: UpdateData } -/** Options to upserting */ -export type UpsertOptions = UpdateOptions - /********************/ /* */ /* SCHEMA TYPES */ @@ -678,21 +675,21 @@ export type KvEnqueueOptions = NonNullable< /** Type of versionstamp */ export type KvVersionstamp = Deno.KvEntry["versionstamp"] -/** Entry key */ +/** An entry or collection key */ export type KvKey = [Deno.KvKeyPart, ...Deno.KvKey] -/** Document id */ +/** An entry ID */ export type KvId = Exclude> -/** KV Object */ +/** An object containing only KV values, and is itself a KV value. */ export type KvObject = { [K: string | number]: KvValue } -/** KV Array */ +/** An array containing only KV values, and is itself a KV value. */ export type KvArray = KvValue[] -/** KV Value */ +/** Defines all valid KV value types */ export type KvValue = | undefined | null diff --git a/tests/collection/properties.test.ts b/tests/collection/properties.test.ts index c7b40ce..70110a8 100644 --- a/tests/collection/properties.test.ts +++ b/tests/collection/properties.test.ts @@ -2,9 +2,10 @@ import { collection, Document, kvdex, model } from "../../mod.ts" import { ID_KEY_PREFIX, KVDEX_KEY_PREFIX } from "../../src/constants.ts" import { extendKey, keyEq } from "../../src/utils.ts" import { assert } from "../test.deps.ts" -import { mockUser1 } from "../mocks.ts" +import { mockUser1, mockUser2, mockUser3 } from "../mocks.ts" import { User } from "../models.ts" import { generateUsers, useDb, useKv } from "../utils.ts" +import { sleep } from "../utils.ts" Deno.test("collection - properties", async (t) => { await t.step("Keys should have the correct prefixes", async () => { @@ -210,6 +211,50 @@ Deno.test("collection - properties", async (t) => { }) }) + await t.step("Should select limited by database reads", async () => { + await useDb(async (db) => { + const cr1 = await db.users.add(mockUser1) + await sleep(10) + const cr2 = await db.users.add(mockUser2) + await sleep(10) + const cr3 = await db.users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.users.getMany({ + limit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.every((doc) => doc.value.username === mockUser2.username)) + }) + }) + + await t.step("Should select limited by result count", async () => { + await useDb(async (db) => { + const cr1 = await db.users.add(mockUser1) + await sleep(10) + const cr2 = await db.users.add(mockUser2) + await sleep(10) + const cr3 = await db.users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.users.getMany({ + resultLimit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.length === 2) + assert(result.some((doc) => doc.value.username === mockUser2.username)) + assert(result.some((doc) => doc.value.username === mockUser3.username)) + }) + }) + await t.step("Should correctly infer type of document", async () => { await useDb(async (db) => { const doc = await db.users.find("") diff --git a/tests/indexable_collection/properties.test.ts b/tests/indexable_collection/properties.test.ts index 8206d75..52fd359 100644 --- a/tests/indexable_collection/properties.test.ts +++ b/tests/indexable_collection/properties.test.ts @@ -9,7 +9,9 @@ import { extendKey, keyEq } from "../../src/utils.ts" import { assert } from "../test.deps.ts" import { mockUser1 } from "../mocks.ts" import { User } from "../models.ts" -import { generateUsers, useDb, useKv } from "../utils.ts" +import { generateUsers, sleep, useDb, useKv } from "../utils.ts" +import { mockUser2 } from "../mocks.ts" +import { mockUser3 } from "../mocks.ts" Deno.test("indexable_collection - properties", async (t) => { await t.step("Keys should have the correct prefixes", async () => { @@ -306,6 +308,50 @@ Deno.test("indexable_collection - properties", async (t) => { }) }) + await t.step("Should select limited by database reads", async () => { + await useDb(async (db) => { + const cr1 = await db.i_users.add(mockUser1) + await sleep(10) + const cr2 = await db.i_users.add(mockUser2) + await sleep(10) + const cr3 = await db.i_users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.i_users.getMany({ + limit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.every((doc) => doc.value.username === mockUser2.username)) + }) + }) + + await t.step("Should select limited by result count", async () => { + await useDb(async (db) => { + const cr1 = await db.i_users.add(mockUser1) + await sleep(10) + const cr2 = await db.i_users.add(mockUser2) + await sleep(10) + const cr3 = await db.i_users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.i_users.getMany({ + resultLimit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.length === 2) + assert(result.some((doc) => doc.value.username === mockUser2.username)) + assert(result.some((doc) => doc.value.username === mockUser3.username)) + }) + }) + await t.step("Should correctly infer type of document", async () => { await useDb(async (db) => { const doc = await db.i_users.find("") diff --git a/tests/serialized_collection/properties.test.ts b/tests/serialized_collection/properties.test.ts index f7ec0f4..d561c3e 100644 --- a/tests/serialized_collection/properties.test.ts +++ b/tests/serialized_collection/properties.test.ts @@ -6,9 +6,11 @@ import { } from "../../src/constants.ts" import { extendKey, keyEq } from "../../src/utils.ts" import { assert } from "../test.deps.ts" -import { mockUser1 } from "../mocks.ts" +import { mockUser1, mockUser2 } from "../mocks.ts" import { User } from "../models.ts" import { generateLargeUsers, useDb, useKv } from "../utils.ts" +import { mockUser3 } from "../mocks.ts" +import { sleep } from "../utils.ts" Deno.test("serialized_collection - properties", async (t) => { await t.step("Keys should have the correct prefixes", async () => { @@ -215,6 +217,50 @@ Deno.test("serialized_collection - properties", async (t) => { }) }) + await t.step("Should select limited by database reads", async () => { + await useDb(async (db) => { + const cr1 = await db.s_users.add(mockUser1) + await sleep(10) + const cr2 = await db.s_users.add(mockUser2) + await sleep(10) + const cr3 = await db.s_users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.s_users.getMany({ + limit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.every((doc) => doc.value.username === mockUser2.username)) + }) + }) + + await t.step("Should select limited by result count", async () => { + await useDb(async (db) => { + const cr1 = await db.s_users.add(mockUser1) + await sleep(10) + const cr2 = await db.s_users.add(mockUser2) + await sleep(10) + const cr3 = await db.s_users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.s_users.getMany({ + resultLimit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.length === 2) + assert(result.some((doc) => doc.value.username === mockUser2.username)) + assert(result.some((doc) => doc.value.username === mockUser3.username)) + }) + }) + await t.step("Should correctly infer type of document", async () => { await useDb(async (db) => { const doc = await db.s_users.find("") diff --git a/tests/serialized_indexable_collection/properties.test.ts b/tests/serialized_indexable_collection/properties.test.ts index 928bd5a..c69c8d3 100644 --- a/tests/serialized_indexable_collection/properties.test.ts +++ b/tests/serialized_indexable_collection/properties.test.ts @@ -9,6 +9,10 @@ import { extendKey, keyEq } from "../../src/utils.ts" import { assert } from "../test.deps.ts" import { User } from "../models.ts" import { generateLargeUsers, useDb, useKv } from "../utils.ts" +import { mockUser1 } from "../mocks.ts" +import { mockUser2 } from "../mocks.ts" +import { mockUser3 } from "../mocks.ts" +import { sleep } from "../utils.ts" const [user] = generateLargeUsers(1) @@ -310,6 +314,50 @@ Deno.test("serialized_indexable_collection - properties", async (t) => { }) }) + await t.step("Should select limited by database reads", async () => { + await useDb(async (db) => { + const cr1 = await db.is_users.add(mockUser1) + await sleep(10) + const cr2 = await db.is_users.add(mockUser2) + await sleep(10) + const cr3 = await db.is_users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.is_users.getMany({ + limit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.every((doc) => doc.value.username === mockUser2.username)) + }) + }) + + await t.step("Should select limited by result count", async () => { + await useDb(async (db) => { + const cr1 = await db.is_users.add(mockUser1) + await sleep(10) + const cr2 = await db.is_users.add(mockUser2) + await sleep(10) + const cr3 = await db.is_users.add(mockUser3) + + assert(cr1.ok) + assert(cr2.ok) + assert(cr3.ok) + + const { result } = await db.is_users.getMany({ + resultLimit: 2, + filter: (doc) => doc.value.username !== mockUser1.username, + }) + + assert(result.length === 2) + assert(result.some((doc) => doc.value.username === mockUser2.username)) + assert(result.some((doc) => doc.value.username === mockUser3.username)) + }) + }) + await t.step("Should correctly infer type of document", async () => { await useDb(async (db) => { const doc = await db.is_users.find("")