UNPKG

@aurios/jason

Version:

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

231 lines (186 loc) 6.69 kB
import { Effect, Schema } from "effect"; import type { IndexDefinition } from "./types/metadata.js"; import type { StandardSchemaV1 } from "./types/schema.js"; /** * Detects if a schema is a Standard Schema compliant validator. * @param schema The schema to check. */ export function isStandardSchema(schema: any): schema is StandardSchemaV1 { return schema !== null && typeof schema === "object" && "~standard" in schema; } const schema_map = { string: Schema.String, number: Schema.Number, boolean: Schema.Boolean, date: Schema.Date, any: Schema.Any, unknown: Schema.Unknown, null: Schema.Null, bigint: Schema.BigInt }; interface ParsedFields { field_name: string; type_name: string; is_multi_entry: boolean; is_primary_key_auto_inc: boolean; is_primary_key_uuid: boolean; is_unique: boolean; is_indexed: boolean; compound_path: string[] | undefined; } export const initializeDir = () => Effect.gen(function* () {}); export function parseSchemaString(schema_string: string) { if (!schema_string.trim()) return []; const parts = schema_string.split(";").map((t) => t.trim()); const parsed_fields: ParsedFields[] = []; for (const part of parts) { if (!part) continue; let field_definition = part; let type_name = "string"; // default type if (part.includes(":")) { const [def, type] = part.split(":", 2); field_definition = def; type_name = type; } const is_multi_entry = field_definition.startsWith("*"); const is_primary_key_auto_inc = field_definition.startsWith("++"); const is_primary_key_uuid = field_definition.startsWith("@"); const is_unique = field_definition.startsWith("&") || is_primary_key_auto_inc || is_primary_key_uuid; const is_indexed = field_definition.startsWith("^") || is_unique || is_multi_entry; const is_compound = field_definition.startsWith("[") && field_definition.endsWith("]"); let clean_field_name = field_definition.replace(/^[++@&*^]/, ""); let compound_path: string[] | undefined = undefined; if (is_compound) { clean_field_name = field_definition; compound_path = field_definition.slice(1, -1).split("+"); } parsed_fields.push({ field_name: clean_field_name, type_name, is_multi_entry, is_primary_key_auto_inc, is_primary_key_uuid, is_unique, is_indexed, compound_path }); } return parsed_fields; } export function buildIndexDefinitions(parsed_fields: ParsedFields[]) { const definitions: Record<string, IndexDefinition> = {}; for (const field of parsed_fields) { definitions[field.field_name] = { indexed: field.is_indexed, unique: field.is_unique, multi_entry: field.is_multi_entry, compound_path: field.compound_path, auto_increment: field.is_primary_key_auto_inc, uuid: field.is_primary_key_uuid, primary_key: field.is_primary_key_auto_inc || field.is_primary_key_uuid }; } return definitions; } export function buildSchema(parsed_fields: ParsedFields[]) { const fields: Record<string, Schema.Schema<any, any>> = {}; for (const field of parsed_fields) { if (field.compound_path) continue; let type_name = field.type_name as keyof typeof schema_map; if (field.is_primary_key_auto_inc) type_name = "number"; if (field.is_primary_key_uuid) type_name = "string"; let field_schema = schema_map[type_name] ?? Schema.Any; if (field.is_multi_entry) field_schema = Schema.Array(field_schema); fields[field.field_name] = field_schema; } return Schema.Struct(fields); } export function generateSchemaFromDefinitions( definitions: Record<string, IndexDefinition> ) { const fields: Record<string, Schema.Schema<any, any>> = {}; for (const field in definitions) { const definition = definitions[field]; let field_definition = field; let type_name = "string" as keyof typeof schema_map; // default type if (field.includes(":")) { const [def, type] = field.split(":", 2); field_definition = def; type_name = type as any; } if (definition.compound_path) continue; if (definition.auto_increment) type_name = "number"; if (definition.uuid) type_name = "string"; let field_schema = schema_map[type_name] ?? Schema.Any; if (definition.multi_entry) field_schema = Schema.Array(field_schema); fields[field_definition] = field_schema; } return Schema.Struct(fields); } /** * Parses an index definition string into a structured format. * Example input: "email:unique,age" * @param index_string The index definition string. * @returns A record of index definitions. */ export function extractIndexDefinitions(index_string: string) { const definitions: Record<string, IndexDefinition> = {}; if (typeof index_string !== "string") { return definitions; } if (!index_string.trim()) { return definitions; } const parts = index_string.split(";").map((part) => part.trim()); for (const part of parts) { let field_name = part; const definition: IndexDefinition = { indexed: true, unique: false, multi_entry: false }; // case 1: compound index (ex: "[field1+field2]") if (part.startsWith("[") && part.endsWith("]")) { field_name = part; const compound_path = part.slice(1, -1).split("+"); definition.compound_path = compound_path; // can be marked as unique with the "&" prefix if (part.startsWith("&[")) { definition.unique = true; field_name = part.substring(2); } // case 2: primary key with auto-increment (ex: "++id") } else if (part.startsWith("++")) { field_name = part.substring(2); definition.primary_key = true; definition.unique = true; definition.auto_increment = true; // case 3: primary key with UUID (ex: "@id") } else if (part.startsWith("@")) { field_name = part.substring(1); definition.primary_key = true; definition.unique = true; definition.uuid = true; // case 4: unique index (ex: "&email") } else if (part.startsWith("&")) { field_name = part.substring(1); definition.unique = true; // case 5: multi-entry index (ex: "*tags") } else if (part.startsWith("*")) { field_name = part.substring(1); definition.multi_entry = true; } else if (part.startsWith("^")) { field_name = part.substring(1); } if (/[&*[\]+@^]/g.test(field_name)) { throw new Error(`Invalid characters in index definition: ${part}`); } definitions[field_name] = definition; } return definitions; }