@aurios/jason
Version:
A simple, lightweight, and embeddable JSON document database built on Bun.
231 lines (186 loc) • 6.69 kB
text/typescript
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;
}