### Comprehensive Logging Setup with Multiple Hooks Source: https://doba.karolbroda.com/docs/debugging Combine `onTransform`, `onStep`, and `onWarning` hooks within `createRegistry` for a robust logging setup. This provides visibility into transformation status, individual step progress, and any warnings encountered during migration. ```javascript const registry = createRegistry({ schemas: { /* ... */ }, migrations: { /* ... */ }, hooks: { onTransform: ({ from, to, ok, durationMs, path }) => { const status = ok ? 'ok' : 'FAIL' const route = path ? path.join(' -> ') : 'no path' console.log(`[transform] ${from} -> ${to} [${status}] ${durationMs.toFixed(1)}ms (${route})`) }, onStep: ({ from, to, index, total, label, durationMs }) => { const tag = label ? ` \"${label}\"` : '' console.log( ` [step ${index + 1}/${total}] ${from} -> ${to}${tag} ${durationMs.toFixed(1)}ms`, ) }, onWarning: (msg, from, to) => { console.log(` [warn] ${from} -> ${to}: ${msg}`) }, }, }) ``` -------------------------------- ### Full Example: Versioned API with Schema Identification Source: https://doba.karolbroda.com/docs/identify This comprehensive example demonstrates setting up a registry with multiple schema versions, defining migrations between them, and configuring schema identification using `match` helpers. It shows how to use `identifyAndTransform` with unknown data and access the transformation results and metadata. ```typescript import { createRegistry, match } from 'dobajs' import { z } from 'zod' const v1User = z.object({ name: z.string(), admin: z.boolean() }) const v2User = z.object({ firstName: z.string(), lastName: z.string(), role: z.enum(['admin', 'user']) }) const v3User = z.object({ displayName: z.string(), role: z.enum(['admin', 'user']), email: z.string() }) const registry = createRegistry({ schemas: { v1: v1User, v2: v2User, v3: v3User }, migrations: { 'v1->v2': (u) => ({ firstName: u.name.split(' ')[0] ?? u.name, lastName: u.name.split(' ')[1] ?? '', role: u.admin ? 'admin' as const : 'user' as const, }), 'v2->v3': (u) => ({ displayName: `${u.firstName} ${u.lastName}`.trim(), role: u.role, email: 'unknown@example.com', }), }, identify: { v1: match.field('admin'), v2: match.fields('firstName', 'lastName'), v3: match.field('displayName'), }, }) // Unknown data comes in from an API const data: unknown = { name: 'Alice Smith', admin: true } const result = await registry.identifyAndTransform(data, 'v3') if (result.ok) { result.value } // value: { // displayName: string; // role: "admin" | "user"; // email: string; // } // result.meta.from // `from: "v1" | "v2" | "v3"` // the source schema detected by identify. // result.meta.path // `path: readonly ("v1" | "v2" | "v3")[]` // ordered list of schema keys traversed, e.g. `['v1', 'v2', 'v3']`. // } ``` -------------------------------- ### Install Doba with Bun Source: https://doba.karolbroda.com/docs/installation Use this command to add the dobajs package to your project using the Bun package manager. ```bash bun add dobajs ``` -------------------------------- ### Install ArkType with Bun Source: https://doba.karolbroda.com/docs/installation Use this command to add the ArkType schema library to your project using the Bun package manager. ```bash bun add arktype ``` -------------------------------- ### Install Valibot with Bun Source: https://doba.karolbroda.com/docs/installation Use this command to add the Valibot schema library to your project using the Bun package manager. ```bash bun add valibot ``` -------------------------------- ### Install Doba with Yarn Source: https://doba.karolbroda.com/docs/installation Use this command to add the dobajs package to your project using the Yarn package manager. ```bash yarn add dobajs ``` -------------------------------- ### Install Valibot with npm Source: https://doba.karolbroda.com/docs/installation Use this command to add the Valibot schema library to your project using the npm package manager. ```bash npm install valibot ``` -------------------------------- ### Install Valibot with pnpm Source: https://doba.karolbroda.com/docs/installation Use this command to add the Valibot schema library to your project using the pnpm package manager. ```bash pnpm add valibot ``` -------------------------------- ### Install Doba with pnpm Source: https://doba.karolbroda.com/docs/installation Use this command to add the dobajs package to your project using the pnpm package manager. ```bash pnpm add dobajs ``` -------------------------------- ### Install Doba with npm Source: https://doba.karolbroda.com/docs/installation Use this command to add the dobajs package to your project using the npm package manager. ```bash npm install dobajs ``` -------------------------------- ### Explain Migration Path with explain() Source: https://doba.karolbroda.com/docs/debugging Use `registry.explain(from, to)` to get a detailed breakdown of the migration path between two schema versions without executing any migrations. It returns an object with a summary, total cost, and per-step information. ```javascript const info = registry.explain('v1', 'v3') const info: ExplainResult<"v1" | "v2" | "v3", "v1", "v3"> console.log(info.summary) // Path: v1 -> v2 -> v3 (2 steps, total cost: 0) // 1. v1 -> v2 (cost: 0) [v1-to-v2-upgrade] // 2. v2 -> v3 (cost: 0) [v2-to-v3-upgrade] ``` -------------------------------- ### Install Valibot with Yarn Source: https://doba.karolbroda.com/docs/installation Use this command to add the Valibot schema library to your project using the Yarn package manager. ```bash yarn add valibot ``` -------------------------------- ### Install Zod with Bun Source: https://doba.karolbroda.com/docs/installation Use this command to add the Zod schema library to your project using the Bun package manager. ```bash bun add zod ``` -------------------------------- ### Install ArkType with pnpm Source: https://doba.karolbroda.com/docs/installation Use this command to add the ArkType schema library to your project using the pnpm package manager. ```bash pnpm add arktype ``` -------------------------------- ### Create Schema Registry with Valibot Source: https://doba.karolbroda.com/docs Define schemas using Valibot and set up migrations. This example demonstrates migrating from a 'database' schema to a 'frontend' schema. ```typescript import { createRegistry } from 'dobajs' import * as v from 'valibot' const registry = createRegistry({ schemas: { database: v.object({ id: v.string(), email: v.string(), passwordHash: v.string() }), frontend: v.object({ id: v.string(), email: v.string() }), }, migrations: { 'database->frontend': (user) => ({ id: user.id, email: user.email }), }, }) ``` -------------------------------- ### Install ArkType with Yarn Source: https://doba.karolbroda.com/docs/installation Use this command to add the ArkType schema library to your project using the Yarn package manager. ```bash yarn add arktype ``` -------------------------------- ### Install ArkType with npm Source: https://doba.karolbroda.com/docs/installation Use this command to add the ArkType schema library to your project using the npm package manager. ```bash npm install arktype ``` -------------------------------- ### Install Zod with pnpm Source: https://doba.karolbroda.com/docs/installation Use this command to add the Zod schema library to your project using the pnpm package manager. ```bash pnpm add zod ``` -------------------------------- ### Create a Doba Registry Source: https://doba.karolbroda.com/docs/installation This snippet shows how to import and create a basic Doba registry, including placeholders for your schemas and migrations. Ensure you have a schema library like Zod, Valibot, or ArkType installed. ```typescript import { createRegistry } from 'dobajs' const registry = createRegistry({ schemas: { /* your schemas */ }, migrations: { /* your migrations */ }, }) ``` -------------------------------- ### Install Zod with Yarn Source: https://doba.karolbroda.com/docs/installation Use this command to add the Zod schema library to your project using the Yarn package manager. ```bash yarn add zod ``` -------------------------------- ### Install Zod with npm Source: https://doba.karolbroda.com/docs/installation Use this command to add the Zod schema library to your project using the npm package manager. ```bash npm install zod ``` -------------------------------- ### Debug Mode Console Output Example Source: https://doba.karolbroda.com/docs/debugging When debug mode is enabled, the registry logs detailed information about schema migrations, including warnings, step timings, and the overall transformation path. ```text [doba] warn v1->v2: defaulted email: generated from name [doba] step 1/2 v1->v2 "add-email" [ok] 0.5ms [doba] step 2/2 v2->v3 "rename-fields" [ok] 0.0ms [doba] transform v1->v3 [ok] 4.6ms (v1 -> v2 -> v3) ``` -------------------------------- ### Create Schema Registry with ArkType Source: https://doba.karolbroda.com/docs Define schemas using ArkType and configure migrations. This example illustrates a migration from a 'database' schema to a 'frontend' schema. ```typescript import { createRegistry } from 'dobajs' import { type } from 'arktype' const registry = createRegistry({ schemas: { database: type({ id: 'string', email: 'string', passwordHash: 'string' }), frontend: type({ id: 'string', email: 'string' }), }, migrations: { 'database->frontend': (user) => ({ id: user.id, email: user.email }), }, }) ``` -------------------------------- ### Define Schemas with Valibot Source: https://doba.karolbroda.com/docs/schemas Create a schema registry using Valibot for data validation. This example defines 'database' and 'frontend' schemas. ```typescript import { createRegistry } from 'dobajs' import * as v from 'valibot' const registry = createRegistry({ schemas: { database: v.object({ id: v.string(), email: v.string(), passwordHash: v.string(), role: v.picklist(['admin', 'user']), }), frontend: v.object({ id: v.string(), email: v.string(), role: v.picklist(['admin', 'user']), }), }, migrations: { /* ... */ }, }) ``` -------------------------------- ### Create Schema Registry with Zod Source: https://doba.karolbroda.com/docs Define schemas using Zod and set up migrations between them. This example shows a migration from a 'database' schema to a 'frontend' schema. ```typescript import { createRegistry } from 'dobajs' import { z } from 'zod' const registry = createRegistry({ schemas: { database: z.object({ id: z.string(), email: z.string(), passwordHash: z.string() }), frontend: z.object({ id: z.string(), email: z.string() }), }, migrations: { 'database->frontend': (user) => ({ id: user.id, email: user.email }), }, }) ``` -------------------------------- ### Define Schemas with ArkType Source: https://doba.karolbroda.com/docs/schemas Create a schema registry using ArkType for data validation. This example defines 'database' and 'frontend' schemas. ```typescript import { createRegistry } from 'dobajs' import { type } from 'arktype' const registry = createRegistry({ schemas: { database: type({ id: 'string', email: 'string', passwordHash: 'string', role: "'admin' | 'user'", }), frontend: type({ id: 'string', email: 'string', role: "'admin' | 'user'", }), }, migrations: { /* ... */ }, }) ``` -------------------------------- ### Define Schemas with Zod Source: https://doba.karolbroda.com/docs/schemas Create a schema registry using Zod for data validation. This example defines 'database' and 'frontend' schemas. ```typescript import { createRegistry } from 'dobajs' import { z } from 'zod' const registry = createRegistry({ schemas: { database: z.object({ id: z.string(), email: z.string(), passwordHash: z.string(), role: z.enum(['admin', 'user']), }), frontend: z.object({ id: z.string(), email: z.string(), role: z.enum(['admin', 'user']), }), }, migrations: { /* ... */ }, }) ``` -------------------------------- ### Perform Data Transformations with Doba Registry Source: https://doba.karolbroda.com/docs/examples/reversible This snippet shows how to use the Doba registry to perform data transformations. It includes examples of forward (database to frontend), backward (frontend to database), and multi-hop transformations. The backward transformation demonstrates how to handle default values and access metadata about applied defaults. The multi-hop transformation shows how to trace the path of transformations. ```typescript const dbUser = { id: 'user-123', email: 'alice@example.com', passwordHash: 'hashed_abc', createdAt: '2024-01-15T10:30:00Z', role: 'admin' as const, } // Forward: database -> frontend const frontend = await registry.transform(dbUser, 'database', 'frontend') // Backward: frontend -> database const feUser = { id: 'user-123', email: 'alice@example.com', createdAt: '2024-01-15T10:30:00Z', role: 'admin' as const, } const db = await registry.transform(feUser, 'frontend', 'database') if (db.ok) { console.log(db.value) // includes passwordHash: "" console.log(db.meta.defaults) // [{ path: ["passwordHash"], message: "..." }] } // Multi-hop: database -> frontend -> ai const ai = await registry.transform(dbUser, 'database', 'ai') if (ai.ok) { console.log(ai.meta.path) // ["database", "frontend", "ai"] } ``` -------------------------------- ### Transform Data with Doba Registry Source: https://doba.karolbroda.com/docs/examples/with-arktype Perform direct and multi-hop data transformations using the Doba registry. This example shows transforming a database user to a frontend user directly, and a legacy user through multiple hops to an AI user. ```typescript const dbUser: typeof databaseUser.infer = { id: 'user-789', email: 'charlie@example.com', passwordHash: 'hashed_secret123', createdAt: '2024-05-10T08:00:00Z', settings: { theme: 'dark', notifications: { email: true, push: true }, internal: { lastLoginIp: '192.168.0.100', sessionCount: 42 }, }, } // Direct const frontend = await registry.transform(dbUser, 'database', 'frontend') // Multi-hop: legacy -> frontend -> ai const legacy: typeof legacyUser.infer = { name: 'Diana Prince', darkMode: true } const ai = await registry.transform(legacy, 'legacy', 'ai') if (ai.ok) { console.log(ai.value) console.log(ai.meta.path) // ["legacy", "frontend", "ai"] console.log(ai.meta.steps) // step-by-step breakdown } ``` -------------------------------- ### Define Reversible and One-Way Migrations Source: https://doba.karolbroda.com/docs/examples/reversible This snippet demonstrates how to define a reversible migration between 'database' and 'frontend' schemas, and a one-way migration from 'frontend' to 'ai'. It uses Zod for schema definition and Doba's `createRegistry` for migration setup. The reversible migration includes a `forward` transform for database to frontend and a `backward` transform for frontend to database, with a default value for `passwordHash`. The one-way migration transforms user roles to an `isAdmin` boolean. ```typescript import { z } from 'zod' import { createRegistry } from 'dobajs' const databaseUser = z.object({ id: z.string(), email: z.string().email(), passwordHash: z.string(), createdAt: z.string().datetime(), role: z.enum(['admin', 'user']), }) const frontendUser = z.object({ id: z.string(), email: z.string().email(), createdAt: z.string().datetime(), role: z.enum(['admin', 'user']), }) const aiUser = z.object({ id: z.string(), email: z.string(), isAdmin: z.boolean(), }) const registry = createRegistry({ schemas: { database: databaseUser, frontend: frontendUser, ai: aiUser, }, migrations: { // Reversible migration: both directions in one declaration 'database<->frontend': { forward: (user) => ({ id: user.id, email: user.email, createdAt: user.createdAt, role: user.role, }), backward: (user, ctx) => { ctx.defaulted(['passwordHash'], 'set to empty string') return { id: user.id, email: user.email, passwordHash: '', createdAt: user.createdAt, role: user.role, } }, label: 'db-frontend-sync', }, // One-way migration 'frontend->ai': (user) => ({ id: user.id, email: user.email, isAdmin: user.role === 'admin', }), }, }) ``` -------------------------------- ### Define ArkType Schemas and Doba Registry Source: https://doba.karolbroda.com/docs/examples/with-arktype Define various user schemas using ArkType and create a Doba registry with migrations between these schemas. This setup is essential for data transformation pipelines. ```typescript import { type } from 'arktype' import { createRegistry } from 'dobajs' const databaseUser = type({ id: 'string', email: 'string.email', passwordHash: 'string', createdAt: 'string', settings: { theme: "'light' | 'dark'", notifications: { email: 'boolean', push: 'boolean', }, internal: { lastLoginIp: 'string', sessionCount: 'number', }, }, }) const frontendUser = type({ id: 'string', email: 'string.email', createdAt: 'string', settings: { theme: "'light' | 'dark'", notifications: { email: 'boolean', push: 'boolean', }, }, }) const aiUser = type({ id: 'string', email: 'string', theme: 'string', notificationsEnabled: 'boolean', }) const legacyUser = type({ 'name?': 'string', 'darkMode?': 'boolean', }) const registry = createRegistry({ schemas: { database: databaseUser, frontend: frontendUser, ai: aiUser, legacy: legacyUser, }, migrations: { 'database->frontend': (user) => ({ id: user.id, email: user.email, createdAt: user.createdAt, settings: { theme: user.settings.theme, notifications: user.settings.notifications, }, }), 'database->ai': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, notificationsEnabled: user.settings.notifications.email || user.settings.notifications.push, }), 'frontend->ai': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, notificationsEnabled: user.settings.notifications.email || user.settings.notifications.push, }), 'legacy->frontend': (user, ctx) => { ctx.defaulted(['id'], 'generated new id') ctx.defaulted(['createdAt'], 'set to current timestamp') ctx.defaulted(['settings', 'notifications'], 'defaulted to all false') let email = 'unknown@example.com' if (typeof user.name === 'string' && user.name.length > 0) { email = `${user.name.toLowerCase().replace(/\s+/g, '.')}@legacy.example.com` ctx.warn(`converted name "${user.name}" to email`) } return { id: `legacy-${Date.now()}`, email, createdAt: new Date().toISOString(), settings: { theme: user.darkMode === true ? 'dark' : 'light', notifications: { email: false, push: false }, }, } }, }, }) ``` -------------------------------- ### from / to Source: https://doba.karolbroda.com/docs/api/context The schema keys for the current migration step. ```APIDOC ### from / to The schema keys for the current migration step: ```typescript 'a->b': (value, ctx) => { console.log(ctx.from) // "a" console.log(ctx.to) // "b" return { /* ... */ } } ``` ``` -------------------------------- ### warn() Source: https://doba.karolbroda.com/docs/api/context Emits a warning that gets collected in `result.meta.warnings`. Also triggers the `onWarning` hook if configured. ```APIDOC ### warn() Emits a warning that gets collected in `result.meta.warnings`. Also triggers the `onWarning` hook if configured. ```typescript 'v1->v2': (data, ctx) => { if (!data.email) { ctx.warn('User has no email, using empty string') } return { ...data, email: data.email || '' } } ``` ``` -------------------------------- ### Explain() Output for Non-existent Path Source: https://doba.karolbroda.com/docs/debugging When no migration path exists between schemas, `explain()` provides information on what is reachable from the source and what is missing to reach the target. ```javascript const info = registry.explain('v3', 'v1') console.log(info.summary) // No migration path from "v3" to "v1". // Reachable from "v3": flat. // No schema has a path to "v1". ``` -------------------------------- ### ok() Constructor Source: https://doba.karolbroda.com/docs/api/result Creates a successful ResultOk object. ```APIDOC ## Constructors ### ok() Creates a successful result: ```typescript function ok(value: T, meta?: M): ResultOk ``` ```typescript import { ok } from 'dobajs/result' const result = ok({ id: '1' }, { schema: 'v1' }) ``` ``` -------------------------------- ### Extract Value with unwrap() Source: https://doba.karolbroda.com/docs/api/result Use `unwrap()` to get the value from a successful Result. This function will throw an error if the Result is a failure. ```javascript import { unwrap } from 'dobajs/result' const data = unwrap(result) // throws Error if !result.ok ``` -------------------------------- ### explain() Source: https://doba.karolbroda.com/docs/api/registry Returns a diagnostic description of the migration path between two schemas without running any migrations. ```APIDOC ## explain() ### Description Returns a diagnostic description of the migration path between two schemas without running any migrations. Includes costs, labels, deprecation info, and a human-readable summary. ### Method ```typescript registry.explain( from: From, to: To, ): ExplainResult ``` ### Example ```typescript const info = registry.explain('v1', 'v3') console.log(info.summary) // Path: v1 -> v2 -> v3 (2 steps, total cost: 0) // 1. v1 -> v2 (cost: 0) [v1-to-v2-upgrade] // 2. v2 -> v3 (cost: 0) [v2-to-v3-upgrade] ``` When no path exists, the summary includes which schemas are reachable from the source and which schemas can reach the target. See Debugging for full details. ``` -------------------------------- ### Find Optimal Migration Path Source: https://doba.karolbroda.com/docs/examples/weighted Demonstrates how Doba's pathfinding algorithm selects the best migration route based on defined preferences and costs. Use this to understand the default behavior. ```javascript const user = { id: 'user-1', name: 'Alice Smith' } // v1 -> v3: prefers v1->v2->v3 (preferred, cost 0+0=0) // over v1->v3 (deprecated, cost 1000) const path1 = registry.findPath('v1', 'v3') // ["v1", "v2", "v3"] // v1 -> flat: v1->v2->flat (cost 0+1=1) // beats v1->v2->v3->flat (cost 0+0+10=10) const path2 = registry.findPath('v1', 'flat') // ["v1", "v2", "flat"] ``` -------------------------------- ### Create a Doba Registry Instance Source: https://doba.karolbroda.com/docs/api/registry Instantiate a registry with schemas and migration functions. The migration graph is pre-computed at construction time. ```typescript import { createRegistry } from 'dobajs' import { z } from 'zod' const registry = createRegistry({ schemas: { v1: z.object({ name: z.string() }), v2: z.object({ firstName: z.string(), lastName: z.string() }), }, migrations: { 'v1->v2': (v1) => ({ firstName: v1.name.split(' ')[0], lastName: v1.name.split(' ')[1] || '', }), }, }) ``` -------------------------------- ### explain() Source: https://doba.karolbroda.com/docs/api/registry Provides a detailed explanation of the transformation process between two schema versions. ```APIDOC ## explain() ### Description Explains the transformation process between two schema versions, including the path, total cost, and individual steps involved. ### Parameters #### `from` (string) - Required The identifier of the source schema version. #### `to` (string) - Required The identifier of the target schema version. #### `options` (TransformOptions) - Optional Options to control the explanation, such as specifying the path strategy. ### Returns - `ExplainResult` - An object detailing the transformation path, cost, steps, and a summary. ``` -------------------------------- ### Log Individual Migration Steps with onStep Source: https://doba.karolbroda.com/docs/debugging Implement the `onStep` hook to log details about each migration step, including its index, total steps, source and target schemas, optional label, and duration. This is useful for identifying slow or failing steps in a long migration chain. ```javascript hooks: { onStep: (info) => { const tag = info.label ? ` (${info.label})` : '' console.log(` step ${info.index + 1}/${info.total}: ${info.from} -> ${info.to}${tag} ${info.durationMs.toFixed(1)}ms`) }, } ``` -------------------------------- ### createRegistry() Source: https://doba.karolbroda.com/docs/api/registry Initializes a new schema registry with optional configuration and hooks. ```APIDOC ## createRegistry() ### Description Creates a new schema registry instance. You can provide configuration options and hooks to customize its behavior. ### Parameters #### `config` (RegistryConfig) - Optional Configuration object for the registry, including schema definitions and migration rules. #### `hooks` (RegistryHooks) - Optional An object containing functions to be called at various stages of registry operations. ### Returns - `Registry` - An instance of the schema registry. ``` -------------------------------- ### Create Doba Registry with Schemas and Migrations Source: https://doba.karolbroda.com/docs/examples/with-zod Set up the Doba registry by defining schemas and the migration functions that connect them. Migrations handle data transformation between different schema versions. ```typescript const registry = createRegistry({ schemas: { database: databaseUser, frontend: frontendUser, ai: aiUser, 'frontend:v1': frontendV1, 'frontend:v2': frontendV2, }, migrations: { 'database->frontend': (user) => ({ id: user.id, email: user.email, createdAt: user.createdAt, settings: { theme: user.settings.theme, notifications: user.settings.notifications, }, }), 'database->ai': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, notificationsEnabled: user.settings.notifications.email || user.settings.notifications.push || user.settings.notifications.sms, }), 'frontend->ai': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, notificationsEnabled: user.settings.notifications.email || user.settings.notifications.push || user.settings.notifications.sms, }), 'frontend:v1->frontend:v2': (user, ctx) => { ctx.defaulted(['settings', 'notifications'], 'defaulting all to true') ctx.warn('createdAt set to current timestamp') return { id: user.id, email: user.email, createdAt: new Date().toISOString(), settings: { theme: user.theme === 'light' || user.theme === 'dark' ? user.theme : 'light', notifications: { email: true, push: true, sms: false }, }, } }, 'frontend:v2->frontend:v1': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, }), 'frontend->frontend:v2': (user) => user, 'frontend:v2->frontend': (user) => user, }, }) ``` -------------------------------- ### Conditional Identify Methods in Registry Source: https://doba.karolbroda.com/docs/identify The `identify` and `identifyAndTransform` methods are only available on the registry type if `identify` is configured during creation. This example demonstrates type errors when `identify` is absent and successful calls when it is present. ```typescript // Without identify: methods don't exist const reg = createRegistry({ schemas, migrations }) // reg.identify -> type error: property does not exist // reg.identifyAndTransform -> type error: property does not exist // With identify: methods are present const reg = createRegistry({ schemas, migrations, identify: { ... } }) await reg.identify(data) // works await reg.identifyAndTransform(data, 'ai') // works ``` -------------------------------- ### Explain() Output for Same Schema Source: https://doba.karolbroda.com/docs/debugging Calling `explain()` with the same source and target schema versions returns a message indicating that the target schema is already met, with no steps required. ```javascript registry.explain('v2', 'v2') // { summary: '"v2" is already the target schema.', totalCost: 0, steps: [] } ``` -------------------------------- ### Run Identify Strategies Benchmarks Source: https://doba.karolbroda.com/docs/performance Execute benchmarks for identify strategies, match, and tryParse. This is useful for comparing the performance of different identification methods. ```bash bun tests/identify.bench.ts ``` -------------------------------- ### Configure Direct Path Strategy Source: https://doba.karolbroda.com/docs/path-finding Set the default path strategy to 'direct' to only allow direct migrations between schemas. An error will occur if no direct path exists. ```javascript createRegistry({ pathStrategy: 'direct', // ... }) ``` -------------------------------- ### Run Feature Overhead Benchmarks Source: https://doba.karolbroda.com/docs/performance Execute benchmarks for feature overhead, including explain and pipe functionalities. This helps in understanding the performance impact of specific features. ```bash bun tests/features.bench.ts ``` -------------------------------- ### createRegistry() Source: https://doba.karolbroda.com/docs/api/registry Creates a new registry instance. The migration graph is pre-computed at construction time. It can optionally accept an `identify` configuration for schema detection. ```APIDOC ## createRegistry() ### Description Creates a new registry instance. The migration graph is pre-computed at construction time. ### Method Signature ```typescript function createRegistry(config: RegistryConfig & { identify: IdentifyConfig> }): Registry function createRegistry(config: RegistryConfig): Registry ``` ### Parameters #### `config` (RegistryConfig) - `schemas` (SchemaMap) - Required - Map of schema names to schema objects. - `migrations` (MigrationsFor) - Required - Migration definitions. - `pathStrategy` (PathStrategy) - Optional - How to find migration paths ('shortest' or 'direct'). Defaults to 'shortest'. - `hooks` (RegistryHooks) - Optional - Lifecycle hooks for warnings and transformations. - `debug` (boolean) - Optional - Enables console logging for hook activity. Defaults to `false`. - `identify` (IdentifyConfig) - Optional - Guard map or function for schema detection. ### Example Usage ```javascript import { createRegistry } from 'dobajs' import { z } from 'zod' const registry = createRegistry({ schemas: { v1: z.object({ name: z.string() }), v2: z.object({ firstName: z.string(), lastName: z.string() }), }, migrations: { 'v1->v2': (v1) => ({ firstName: v1.name.split(' ')[0], lastName: v1.name.split(' ')[1] || '', }), }, }) ``` ``` -------------------------------- ### Accessing metadata Source: https://doba.karolbroda.com/docs/api/context Demonstrates how to access warnings and defaults from the transformation result. ```APIDOC ### Accessing metadata ```typescript const result = await registry.transform(data, 'legacy', 'current') if (result.ok) { for (const warning of result.meta.warnings) { console.log(`[${warning.from}->${warning.to}] ${warning.message}`) } for (const d of result.meta.defaults) { console.log(`Defaulted ${d.path.join('.')}: ${d.message}`) } } ``` ``` -------------------------------- ### Match Guard Source: https://doba.karolbroda.com/docs/api/identify The `match` entry point is used to build chainable identify guards. It returns a `Matcher` object that allows for fluent specification of conditions. ```APIDOC ## Match Guard Entry point for building chainable identify guards. Returns a `Matcher`. ```typescript const match: Matcher ``` ### Usage Examples ```typescript import { match } from 'dobajs' match.field('passwordHash') // field exists match.field('version', 2) // field === 2 match.fields('id', 'email') // both fields exist match.type('string') // typeof check match.test((v) => Array.isArray(v)) // custom predicate match.field('passwordHash').field('email') // AND: both must exist ``` ### Matcher Interface Each method returns a new `Matcher` that is both chainable (add more conditions) and callable as `(value: unknown) => boolean`. Chaining ANDs conditions together. ```typescript interface Matcher { (value: unknown): boolean field(name: string, expected?: unknown): Matcher fields(...names: string[]): Matcher type(type: string): Matcher test(fn: (value: unknown) => boolean): Matcher } ``` | Method | What it checks | |------------------|------------------------------------------------------------| | `.field(name)` | Field exists on the value (`name in value`). | | `.field(name, val)` | Field exists and `value[name] === val` (strict equality). | | `.fields(...names)`| All named fields exist on the value. | | `.type(t)` | `typeof value === t`. Note: `typeof null === 'object'` in JS. | | `.test(fn)` | Custom predicate returns `true`. | ``` -------------------------------- ### Create a Successful Result with ok() Source: https://doba.karolbroda.com/docs/api/result Use the `ok()` function to construct a `ResultOk` object. It takes the success value and optional metadata. ```javascript import { ok } from 'dobajs/result' const result = ok({ id: '1' }, { schema: 'v1' }) ``` -------------------------------- ### Log Migration Warnings with ctx.warn Source: https://doba.karolbroda.com/docs/context Use `ctx.warn()` within a migration function to log non-critical information. Warnings are collected in `result.meta.warnings`. ```javascript 'legacy->current': (value, ctx) => { ctx.warn('upgrading from legacy format, some fields may be approximate') return { /* ... */ } } ``` -------------------------------- ### Implement Registry-Level onWarning Hook Source: https://doba.karolbroda.com/docs/context Configure a global `onWarning` hook in the registry to react to all warnings (from `ctx.warn` and `ctx.defaulted`) across all migrations. The hook receives the warning message, source schema, and target schema. ```javascript const registry = createRegistry({ schemas: { /* ... */ }, migrations: { /* ... */ }, hooks: { onWarning: (message, from, to) => { console.log(`[${from}->${to}] ${message}`) }, }, }) ``` -------------------------------- ### Define Reversible Migrations Source: https://doba.karolbroda.com/docs/migrations Use the '<->' syntax to define migrations that can run in both forward and backward directions with a single declaration. All metadata options apply. ```javascript migrations: { 'database<->frontend': { forward: (user) => ({ id: user.id, email: user.email, role: user.role, }), backward: (user, ctx) => { ctx.defaulted(['passwordHash'], 'set to empty string') return { id: user.id, email: user.email, passwordHash: '', role: user.role, } }, label: 'db-frontend-sync', }, } ``` -------------------------------- ### findPath() Source: https://doba.karolbroda.com/docs/api/registry Returns the sequence of schemas needed to migrate from one to another, or null if no path exists. ```APIDOC ## findPath() ### Description Returns the sequence of schemas needed to migrate from one to another, or `null` if no path exists. ### Method ```typescript registry.findPath(from: From, to: To): readonly Keys[] | null ``` ### Example ```typescript const path = registry.findPath('v1', 'v3') // ['v1', 'v2', 'v3'] or null ``` Uses the registry's `pathStrategy` to determine the algorithm (BFS or Dijkstra). ``` -------------------------------- ### Access Migration Schema Info with ctx.from and ctx.to Source: https://doba.karolbroda.com/docs/context The context object provides `ctx.from` and `ctx.to` properties to access the source and target schema names for the current migration step. ```javascript 'a->b': (value, ctx) => { console.log(ctx.from) // "a" console.log(ctx.to) // "b" return { /* ... */ } } ``` -------------------------------- ### Valibot Schemas and Migrations with doba Source: https://doba.karolbroda.com/docs/examples/with-valibot Defines Valibot schemas for different user types (database, frontend, AI, legacy) and sets up migration functions between them using doba's createRegistry. The migration context allows tracking defaults and warnings. ```typescript import * as v from 'valibot' import { createRegistry } from 'dobajs' const databaseUser = v.object({ id: v.string(), email: v.pipe(v.string(), v.email()), passwordHash: v.string(), createdAt: v.pipe(v.string(), v.isoTimestamp()), settings: v.object({ theme: v.picklist(['light', 'dark']), notifications: v.object({ email: v.boolean(), push: v.boolean(), }), internal: v.object({ lastLoginIp: v.string(), failedAttempts: v.number(), }), }), }) const frontendUser = v.object({ id: v.string(), email: v.pipe(v.string(), v.email()), createdAt: v.pipe(v.string(), v.isoTimestamp()), settings: v.object({ theme: v.picklist(['light', 'dark']), notifications: v.object({ email: v.boolean(), push: v.boolean(), }), }), }) const aiUser = v.object({ id: v.string(), email: v.string(), theme: v.string(), hasNotifications: v.boolean(), }) const legacyUser = v.object({ name: v.optional(v.string()), darkMode: v.optional(v.boolean()), }) const registry = createRegistry({ schemas: { database: databaseUser, frontend: frontendUser, ai: aiUser, legacy: legacyUser, }, migrations: { 'database->frontend': (user) => ({ id: user.id, email: user.email, createdAt: user.createdAt, settings: { theme: user.settings.theme, notifications: user.settings.notifications, }, }), 'database->ai': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, hasNotifications: user.settings.notifications.email || user.settings.notifications.push, }), 'frontend->ai': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, hasNotifications: user.settings.notifications.email || user.settings.notifications.push, }), 'legacy->frontend': (user, ctx) => { ctx.defaulted(['id'], 'generated new id') ctx.defaulted(['createdAt'], 'set to current timestamp') ctx.defaulted(['settings', 'notifications'], 'defaulted to all false') let email = 'unknown@example.com' if (typeof user.name === 'string' && user.name.length > 0) { email = `${user.name.toLowerCase().replace(/\s+/g, '.')}@legacy.example.com` ctx.warn(`converted name "${user.name}" to email`) } return { id: `legacy-${Date.now()}`, email, createdAt: new Date().toISOString(), settings: { theme: user.darkMode === true ? 'dark' : 'light', notifications: { email: false, push: false }, }, } }, }, }) ``` -------------------------------- ### Migration Metadata Configuration Source: https://doba.karolbroda.com/docs/migrations Control migration behavior with metadata like label, preferred, deprecated, and cost. The 'migrate' function is required unless using 'pipe'. ```javascript migrations: { 'v1->v2': { migrate: (data, ctx) => { ctx.defaulted(['email'], 'generated from name') return { ...data, email: `${data.name}@example.com` } }, label: 'v1-to-v2-upgrade', // human-readable name for step info preferred: true, // cost 0, always chosen over alternatives }, 'v1->v3': { migrate: (data) => ({ /* ... */ }), deprecated: 'prefer v1 -> v2 -> v3 chain', // cost 1000, emits warning }, 'v3->flat': { migrate: (data) => ({ /* ... */ }), cost: 10, // explicit edge weight }, } ``` -------------------------------- ### Inspect Path Without Transforming Source: https://doba.karolbroda.com/docs/path-finding Use the `findPath` method to preview the migration route Doba would take without executing the transformation. ```javascript const path = registry.findPath('legacy', 'ai') // ["legacy", "database", "frontend", "ai"] or null ``` -------------------------------- ### Explain Migration Path Source: https://doba.karolbroda.com/docs/api/registry Provides a diagnostic description of the migration path between two schemas without executing any migrations. Includes details like costs, labels, deprecation information, and a human-readable summary. If no path exists, the summary indicates reachable and targetable schemas. ```typescript registry.explain( from: From, to: To, ): ExplainResult ``` ```typescript const info = registry.explain('v1', 'v3') console.log(info.summary) // Path: v1 -> v2 -> v3 (2 steps, total cost: 0) // 1. v1 -> v2 (cost: 0) [v1-to-v2-upgrade] // 2. v2 -> v3 (cost: 0) [v2-to-v3-upgrade] ``` -------------------------------- ### Define Schemas and Migrations with Weights Source: https://doba.karolbroda.com/docs/examples/weighted Sets up Zod schemas and defines migrations with custom costs, preferred flags, and deprecation settings. Use this to configure complex migration strategies. ```typescript import { z } from 'zod' import { createRegistry } from 'dobajs' const v1 = z.object({ id: z.string(), name: z.string() }) const v2 = z.object({ id: z.string(), name: z.string(), email: z.string() }) const v3 = z.object({ id: z.string(), displayName: z.string(), email: z.string(), verified: z.boolean(), }) const flat = z.object({ id: z.string(), label: z.string() }) const registry = createRegistry({ schemas: { v1, v2, v3, flat }, migrations: { // Preferred: always chosen over alternatives at equal distance 'v1->v2': { migrate: (user, ctx) => { ctx.defaulted(['email'], 'generated from name') return { id: user.id, name: user.name, email: `${user.name.toLowerCase().replace(/\s+/g, '.')}@example.com`, } }, preferred: true, label: 'v1-to-v2-upgrade', }, 'v2->v3': { migrate: (user, ctx) => { ctx.defaulted(['verified'], 'set to false for new migrations') return { id: user.id, displayName: user.name, email: user.email, verified: false, } }, preferred: true, label: 'v2-to-v3-upgrade', }, // Deprecated: high cost (1000), emits a warning when used 'v1->v3': { migrate: (user, ctx) => { ctx.warn('direct v1->v3 migration is outdated') return { id: user.id, displayName: user.name, email: `${user.name.toLowerCase().replace(/\s+/g, '.')}@example.com`, verified: false, } }, deprecated: 'prefer v1 -> v2 -> v3 chain for proper defaults', label: 'v1-to-v3-legacy', }, // Custom cost: makes this path more expensive 'v3->flat': { migrate: (user) => ({ id: user.id, label: `${user.displayName} <${user.email}>`, }), cost: 10, label: 'flatten-for-display', }, 'v2->flat': { migrate: (user) => ({ id: user.id, label: `${user.name} <${user.email}>`, }), label: 'flatten-v2', }, }, }) ``` -------------------------------- ### Run Core Operations Benchmarks Source: https://doba.karolbroda.com/docs/performance Execute the core operations benchmarks for doba. This command is useful for understanding the fundamental performance of the library. ```bash bun run bench ``` -------------------------------- ### Configure Shortest Path Strategy Source: https://doba.karolbroda.com/docs/path-finding Set the default path strategy to 'shortest' to ensure Doba always finds the optimal path using BFS or Dijkstra. ```javascript createRegistry({ pathStrategy: 'shortest', // ... }) ``` -------------------------------- ### Define Migrations and Transform Data Source: https://doba.karolbroda.com/docs/path-finding Define schemas and migrations, then use the registry to transform data from a source to a target schema, automatically chaining intermediate migrations. ```javascript const registry = createRegistry({ schemas: { legacy, database, frontend, ai }, migrations: { 'legacy->database': (data) => { /* upgrade */ }, 'database->frontend': (data) => { /* strip sensitive */ }, 'frontend->ai': (data) => { /* flatten */ }, }, }) ``` ```javascript const result = await registry.transform(legacyData, 'legacy', 'ai') if (result.ok) { result.meta.path // ["legacy", "database", "frontend", "ai"] } ```