UNPKG

@aurios/jason

Version:

A simple, lightweight, and embeddable JSON document database built on Bun.

359 lines (336 loc) 11.5 kB
import type { BadArgument, PlatformError, SystemError } from "@effect/platform/Error"; import { Effect, Schema } from "effect"; import type { DatabaseError, JsonError } from "../core/errors.js"; import type { ParseSchemaString, SchemaOrString, StandardSchemaV1 } from "./schema.js"; import type { ParseError } from "effect/ParseResult"; import type { FilterExpression } from "./query.js"; /** * A filter object used to specify criteria for querying documents in a collection. */ export type Filter<Doc> = FilterExpression<Doc>; interface OrderBy<Doc> { /** * The field to order by. * This should be a key of the document type `Doc`. */ field: keyof Doc; /** * The order direction, either ascending (`asc`) or descending (`desc`). */ order: "asc" | "desc"; } export interface QueryOptions<Doc> { /** * The filter criteria to apply to the query. * * @example * db.collections.user.find({ * where: {age: 20} * }) */ where: Filter<Doc>; /** * The ordering criteria to apply to the query. * * @example * db.collections.user.find({ * where: { age: 20 } * order_by: { field: 'age', order: 'asc' } * }) */ order_by?: OrderBy<Doc>; /** * The number of documents to skip in the query results. */ skip?: number; /** * The maximum number of documents to return in the query results. */ limit?: number; } /** * The result of a batch operation. */ export interface BatchResult { /** The number of successful operations. */ success: number; /** A list of failed operations with their index or ID and error message. */ failures: Array<{ index?: number; id?: string; error: string }>; } /** * Represents batch operations on a collection, returning `Effect` computations. */ export interface BatchOperationsEffect<Doc> { /** * Inserts multiple documents into the collection. * @param docs The array of documents to insert. * @returns An `Effect` that resolves to a `BatchResult`. */ insert: (docs: Doc[]) => Effect.Effect<BatchResult, DatabaseError>; /** * Deletes multiple documents matching the filter. * @param filter The filter to match documents for deletion. * @returns An `Effect` that resolves to a `BatchResult`. */ delete: (filter: Filter<Doc>) => Effect.Effect<BatchResult, DatabaseError>; /** * Updates multiple documents matching the filter. * @param filter The filter to match documents for update. * @param data The partial data to update in matching documents. * @returns An `Effect` that resolves to a `BatchResult`. */ update: ( filter: Filter<Doc>, data: Partial<Omit<Doc, "id">> ) => Effect.Effect<BatchResult, DatabaseError>; } /** * Represents batch operations on a collection. */ export interface BatchOperations<Doc> { /** * Inserts multiple documents into the collection. * @param docs The array of documents to insert. * @returns A promise that resolves to a `BatchResult`. */ insert: (docs: Doc[]) => Promise<BatchResult>; /** * Deletes multiple documents matching the filter. * @param filter The filter to match documents for deletion. * @returns A promise that resolves to a `BatchResult`. */ delete: (filter: Filter<Doc>) => Promise<BatchResult>; /** * Updates multiple documents matching the filter. * @param filter The filter to match documents for update. * @param data The partial data to update in matching documents. * @returns A promise that resolves to a `BatchResult`. */ update: ( filter: Filter<Doc>, data: Partial<Omit<Doc, "id">> ) => Promise<BatchResult>; } /** * Represents a collection of documents, with methods that return `Effect` computations. * This interface is for users who prefer to work within the `Effect` ecosystem for handling asynchronous operations and errors. */ export interface CollectionEffect<Doc> { /** * Batch operations for the collection. */ batch: BatchOperationsEffect<Doc>; /** * Creates a new document in the collection. * @param data The data for the new document, excluding the 'id'. * @returns An `Effect` that resolves to the created document or fails with an `Error`. */ create: (data: Doc) => Effect.Effect<Doc, DatabaseError>; /** * Retrieves a document by its ID. * @param id The ID of the document to retrieve. * @returns An `Effect` that resolves to the document or `undefined` if not found, and can fail with an `Error`. */ findById: ( id: string ) => Effect.Effect< Doc | undefined, BadArgument | SystemError | JsonError | ParseError >; /** * Updates a document by its ID with partial data. * @param id The ID of the document to update. * @param data The partial data to update in the document. * @returns An `Effect` that resolves to the updated document or `undefined` if not found, and can fail with a `DatabaseError`. */ update: ( id: string, data: Partial<Doc> ) => Effect.Effect<Doc | undefined, DatabaseError>; /** * Deletes a document by its ID. * @param id The ID of the document to delete. * @returns An `Effect` that resolves to `true` if the document was deleted, `false` otherwise, and can fail with a `DatabaseError`. */ delete: (id: string) => Effect.Effect<boolean, DatabaseError>; /** * Finds documents based on query options. * @param options Query options for filtering, ordering, and pagination. * @returns An `Effect` that resolves to an array of documents and can fail with an `Error`. */ find: (options: QueryOptions<Doc>) => Effect.Effect<Doc[], Error>; /** * Checks if a document with the given ID exists. * @param id The ID of the document to check. * @returns An `Effect` that resolves to `true` if the document exists, `false` otherwise, and can fail with a `PlatformError`. */ has: (id: string) => Effect.Effect<boolean, PlatformError>; /** * Finds a single document based on query options. * @param options Query options for filtering and ordering. * @returns An `Effect` that resolves to a single document or `undefined` if not found, and can fail with a `PlatformError`. */ findOne: ( options: QueryOptions<Doc> ) => Effect.Effect<Doc | undefined, PlatformError>; } /** * Represents a collection of documents in the database. */ export interface Collection<Doc> { /** * Batch operations for the collection. */ batch: BatchOperations<Doc>; /** * Creates a new document in the collection. * @param data - The data to be stored in the document. * @returns The created document. */ create: (data: Doc) => Promise<Doc>; /** * Retrieves a document by its id. * @param id - The id of the document to retrieve. * @returns The retrieved document, or `undefined` if not found. */ findById: (id: string) => Promise<Doc | undefined>; /** * Updates a document by its id. * @param id - The id of the document to update. * @param data - The data to update in the document. * @returns The updated document, or `undefined` if the document with the given id was not found. */ update: ( id: string, data: Partial<Omit<Doc, "id">> ) => Promise<Doc | undefined>; /** * Deletes a document by its id. * @param id - The id of the document to delete. * @returns A promise that resolves to `true` if the document was deleted, `false` otherwise. */ delete: (id: string) => Promise<boolean>; /** * Finds documents based on the provided query options. * @param options - Query options including filtering, ordering, skipping, and limiting. * @returns A promise that resolves to an array of documents. */ find: (options: QueryOptions<Doc>) => Promise<Doc[]>; /** * Checks if a document with the given id exists in the collection. * @param id The id of the document to check. * @returns A promise that resolves to `true` if the document exists, `false` otherwise. */ has: (id: string) => Promise<boolean>; /** * Finds a single document based on the provided query options. * @param options - Query options including filtering and ordering. * @returns A promise that resolves to a single document or undefined if not found. */ findOne: (options: QueryOptions<Doc>) => Promise<Doc | undefined>; } /** * A utility type that infers the document types for all collections defined in the database configuration. * It maps over the `collections` object and resolves each schema (whether a `Schema` object or a schema string) * to its corresponding TypeScript type. * * @template T - The type of the `collections` configuration object. */ export type InferCollections<T extends Record<string, SchemaOrString>> = { [K in keyof T]: T[K] extends Schema.Schema<infer A, any> ? A : T[K] extends StandardSchemaV1<any, infer A> ? A : T[K] extends string ? ParseSchemaString<T[K]> : any; }; /** * Configuration options for creating a JasonDB instance. */ export interface JasonDBConfig<T extends Record<string, SchemaOrString>> { /** * The base path where the database files will be stored. * * @example * const db = await createJasonDB({ * base_path: "db_dir", * }); */ base_path: string; /** * A record defining the schemas for the database collections. * * You can define schemas using `Effect.Schema` objects or a convenient string-based syntax. * The types for your collections are automatically inferred from these definitions. * * Example of defining collections with schema strings * ```ts * const db = await createJasonDB({ * base_path: "db_dir", * collections: { * user: "@id;name;age:number;email;isManager:boolean", * post: "@id;title;author;*tags" * } * }); * ``` * * Accessing a collection * ```ts * const { user } = db.collections; * ``` * * ### Schema String Syntax * * The string syntax is a shorthand for defining fields and indexes. * Fields are separated by semicolons `;`. * * #### Field Types * * Specify a type by appending a colon `:` followed by the type name. * If no type is specified, it defaults to `string`. * * - `fieldName:string` = `string` * - `fieldName:number` = `number` * - `fieldName:boolean` = `boolean` * - `fieldName:date` = `Date` * - `fieldName:array<type>` = `type[]` (e.g., `items:array<string>`) * - `fieldName:record<key, value>` = `Record<key, value>` (e.g., `props:record<string, number>`) * * #### Index Modifiers * * You can prefix a field name with a symbol to create an index. * * - `@id`: Primary key (UUID). * - `++id`: Primary key (auto-incrementing number). * - `&name`: A unique index on the `name` field. * - `*tags`: A multi-entry index, ideal for array fields. The field type will be inferred as an array (e.g., `*tags:string` becomes `tags: string[]`). * - `[name+email]`: A compound index on `name` and `email`. * * All defined schemas are validated at runtime using `Effect.Schema`. */ collections: T; /** * Optional configuration for caching. */ cache?: { /** * Maximum number of documents to cache per collection. * @default 1000 */ document_capacity?: number; /** * Maximum number of B-Tree nodes to cache per index. * @default 1000 */ index_capacity?: number; }; }