Wurk.Flow User Manual
Wurk.Flow is a modern Data Engineering and End-to-End Analytics IDE. It combines a visual schema canvas, SQL query workbench, data lineage tracker, and STTM mapping tool into a single application for data architects, engineers, and analysts — native on Windows, macOS and Linux, or in your browser as the Web Studio at app.wurk.haus.
Built with Vue 3 · Electron · DuckDB WASM · Tailwind CSS
1 · Install Wurk.Flow
There are two ways to run Wurk.Flow. Both start on the Free edition, with no email address and no card.
| Option | Best for | Get it |
|---|---|---|
| Web Studio | Trying it right now, quick models, a locked-down machine you cannot install on | Open app.wurk.haus in Chrome, Edge or Firefox. Nothing to install. See Web Studio for what differs from the desktop app. |
| Desktop app | Live database connections, Git, Co-Op, working fully offline | Download for your OS from wurk.haus/download |
Desktop install by platform
- macOS — Open the
.dmgand drag Wurk.Flow into Applications. Builds are signed and notarized by Apple, so Gatekeeper opens them normally. Apple Silicon (M1 and later) only. If you still see an "app is damaged" dialog, see Troubleshooting. - Windows — Run the code-signed
.exeinstaller and follow the prompts. - Linux — Make the AppImage executable and run it. No package manager, no root:
chmod +x Wurk.Flow-26.10.0.AppImage ./Wurk.Flow-26.10.0.AppImage
First run
- The canvas opens with two sample tables (Users and Posts) and a short guided tour. Take the tour, or dismiss it and come back later.
- Press ? for the shortcut overlay and ⌘+K for the Command Palette. Everything in this manual is reachable from the palette. On Windows and Linux, read ⌘ as Ctrl throughout.
- Confirm the version: ⌘+K → About Wurk.Flow DataStudio. The desktop app never updates itself in the background; Check for Updates in that window is the only time it contacts the release feed, and installing is a second explicit click.
What Free includes. Up to 15 tables and views on the canvas, DDL in DuckDB, PostgreSQL and SQLite, one SQL Workspace tab on the built-in DuckDB sandbox, DBML import and export, DDL export, and PNG/PDF diagram export. Free exports start with a short "Generated by Wurk.Flow Community Edition" comment. Everything else is marked PRO or TEAM in this manual; Licensing has the full comparison.
Next: 2 · Your First Model.
2 · Your First Model
Build a two-table schema from scratch, watch the DDL write itself, and save the project.
- Start clean. ⌘+K → New Schema (or ⌘+N).
- Add a table. Click + Add Table in the toolbar, or run Add
Table from the palette. Click the new node to open the Table Properties Drawer on
the right and rename it
customers. - Add columns. In the drawer add
id(INTEGER, PK),email(VARCHAR, Unique, Not Null) andcreated_at(TIMESTAMP). Every constraint you tick shows up in the DDL immediately. - Add a second table called
orderswithid(PK),customer_id(INTEGER) andtotal(DECIMAL). - Draw the relationship. Drag from the handle beside
customers.idtoorders.customer_id. The edge becomes a foreign key; click it to set cardinality and theON DELETE/ON UPDATEactions in the Edge Properties Drawer. - Group it. Add Subject Area, drop both tables inside and name it
Sales. Subject areas drive the Schema Explorer grouping and every exported document. - Read the SQL. Open the DDL tab. Choose PostgreSQL, DuckDB or SQLite and
read the
CREATE TABLEstatements, including the FK constraint you just drew. You typed none of it. - Check for problems. The status bar shows an issue count; click it to open the Issues drawer (missing primary keys, dangling foreign keys, naming).
- Save. ⌘+S writes a plain-JSON
.dsmfile. In the Web Studio, Save downloads the file, and the browser also keeps an autosave copy between visits (see Web Studio).
Recipe: CSV → table → SQL
The fastest way to model real data is to start from a file instead of an empty table.
- Drop a CSV on the canvas. Wurk.Flow infers the column names and types and creates a table
node. JSON, SQLite and DuckDB files work the same way; a
.sqlfile is reverse-engineered from itsCREATE TABLEstatements instead. - Fix the mapping. Open the table's properties drawer and correct any inferred type (a zip code read as INTEGER, a date read as VARCHAR), mark the primary key, and rename columns to your standard. Enforce Naming Standards in the palette does the renaming for the whole canvas and previews what it will change first.
- Get the SQL. The DDL tab's Full Build mode gives you the
CREATE TABLE. Insert Data (Real) PRO turns the rows you dropped intoINSERT INTOstatements; Seed Data (Mock) (Free) makes synthetic rows instead. - Query it. Open the SQL Workspace. The Free tier's one tab runs against the built-in DuckDB sandbox, where every canvas table is scaffolded with seed data, so you can test joins and aggregations against the design before a database exists.
- Map it to a target PRO. In Data Mapping (STTM), add a source with your CSV's columns and a target with the destination shape, wire the columns, and drop transforms (CAST, CASE, COALESCE, JOIN, …) between them. The generated SQL appears in the output pane. See Data Mapping for the full transform list.
Next: 3 · Connect a Database, or skip to 5 · Export DDL & Reports.
3 · Connect a Database
Pull a live schema onto the canvas, then regenerate it as DDL for any dialect. Live connections are a desktop feature: the Web Studio has no server and never stores database credentials. In the browser, paste DDL with Import SQL DDL (free there) or Import DBML instead.
Recipe: introspect PostgreSQL → DDL
- ⌘+K → Manage Database Connections... (or File → Import Live Database).
- Choose PostgreSQL. Fill in host, port (5432 is pre-filled), database, schema, user and password. Toggle ALL next to Schema to introspect every non-system schema at once.
- Click Test Connection. A failure names the connection and a next step (firewall, certificate, wrong database) and keeps the driver's original message in brackets.
- Give the profile a name and click Save Profile. Profiles live only on this machine, AES-256
encrypted with a key derived from its hardware ID. They are never written into a
.dsmfile, so you can commit projects to Git without leaking a connection string. - Click Connect & Introspect. The Selective Table Import dialog groups tables by schema; tick what you want and click Import. Tables, columns, types, primary and foreign keys and views land on the canvas with their schema qualifier.
- Open the DDL tab and choose a dialect. Keep PostgreSQL, or switch to Snowflake, SQL Server,
BigQuery or any of the 13 to translate the schema. Identifiers are quoted per dialect
(
"sales"."orders",[sales].[orders],`sales`.`orders`). - Export DDL... writes the
.sqlfile. To ship only the difference against the live database, run Generate Migration Script...; to see what changed since you imported, Detect Schema Drift....
Where the traffic goes. Introspection and queries run from your machine straight to your database. Nothing is proxied through wurk.haus. Database Connections lists all 12 engines and their fields; Selective Import covers multi-schema catalogs.
Next: 4 · Buy & Activate.
4 · Buy & Activate
Plans are bought and managed at flow.wurk.haus, the customer portal. The desktop app is unlocked with a license key bound to one machine; the Web Studio is unlocked by signing in with the same account. Prices, what each plan unlocks, and what happens when a plan ends are in Licensing.
Buy
- Create an account at flow.wurk.haus and confirm your email.
- On the dashboard choose Pro (one seat) or Team (five seats), and monthly or annual. Annual plans carry perpetual fallback on the desktop app.
- Checkout runs on Stripe. When it completes, the dashboard lists your license seats. Each seat has its own key; click Copy Key on an unused seat.
Activate the desktop app
- Click the key icon at the bottom of the left sidebar (Manage License). Clicking any PRO feature opens the same window, with the feature that was denied named at the top.
- Paste the key and click Activate. You must be online for this one step: the license service binds the key to this machine's hardware ID and returns a signed token.
- The window now reads LICENSE ACTIVE with your edition and version year (for example PRO v26), and the status-bar badge changes from COMMUNITY to PRO or TEAM. Pro features are on, and you can go offline.
Recipe: activate Pro on a new machine
A key works on one machine at a time. Free the key in either of two ways, then activate on the new machine as above. Neither needs a support ticket.
- You still have the old machine: open the License window there and click DEACTIVATE / UNBIND MACHINE.
- The old machine is gone (reinstalled OS, dead laptop, replaced hardware): on the dashboard, find the seat and click Revoke Seat. The seat stays active and the same key is immediately claimable by the next machine.
Unlock the Web Studio
- At app.wurk.haus, click the key icon (Manage License) and sign in with your flow.wurk.haus email and password.
- The window reads SUBSCRIPTION ACTIVE. No key, no hardware binding, and it does not use up your desktop seat.
- Team members: the owner invites them by email from the dashboard; they create an account and sign in at app.wurk.haus. For the desktop app they take a seat key from the dashboard instead.
"I paid and features are still locked"
| Check | What to do |
|---|---|
| Key never entered | Paying creates the key; it does not activate anything by itself. Copy it from the dashboard and activate in the app. |
| "License is already bound to another machine" | The seat is in use elsewhere. Revoke the seat on the dashboard (or unbind on the old machine) and activate again. |
| "Invalid license key" | Check for a truncated paste. Keys are shown shortened on the dashboard; use Copy Key rather than retyping. |
| Window says RENEWAL REQUIRED | Your license covers an earlier version year than the build you are running (a v26 license on a v27 build). Renew, or install a release from your paid year, which keeps working under perpetual fallback. |
| Web: signed in, still Free | Sign in with the account that owns, or was invited to, the subscription. The check re-runs at sign-in and every 6 hours. |
| Pro vanished mid-session with "License no longer active" | The server answered revoked: a refund, chargeback or payment dispute is on record for the subscription. If that is wrong, get in touch. |
| Anything else | Use the contact form with the Hardware Signature shown at the bottom of the License window and your dashboard email. |
Next: 5 · Export DDL & Reports.
5 · Export DDL & Reports
Everything you model can leave Wurk.Flow as SQL, as DBML, as a diagram, or as a self-contained documentation site.
DDL
- Open the DDL tab and choose a mode: Full Build (Free), Seed Data (Mock) (Free), Migration Diff PRO or Insert Data (Real) PRO. DDL Generation explains each.
- Choose a dialect. Free: DuckDB, PostgreSQL, SQLite. Pro adds MySQL, MariaDB, SQL Server, Azure Synapse, Snowflake, BigQuery, Databricks, Redshift, ClickHouse and CockroachDB.
- ⌘+K → Export DDL... (or the export button on the DDL tab) writes a
.sqlfile. On Free the file starts with a short "Generated by Wurk.Flow Community Edition" comment; Pro exports are clean.
Reports and documentation
| Output | Tier | How |
|---|---|---|
| Documentation Portal (HTML) | PRO | ⌘+K → Export Documentation Portal (HTML). One self-contained file with interactive schema diagrams, a searchable data dictionary, lineage, requirements and health KPIs. Opens in any browser, no server. See HTML Report Generator. |
| Markdown data dictionary | PRO | ⌘+K → Export Documentation (Markdown). Drops straight into a wiki or a repository README. |
| Diagram image | Free | ⌘+K → Export Diagram as PNG or as PDF. |
| DBML file | Free | ⌘+K → Export DBML.... The file embeds your layout, so re-importing it rebuilds the diagram. Free exports carry a one-line header comment. Syntax and examples in DBML. |
| Query results (CSV) | PRO | From the SQL Workspace results grid. |
That is the whole loop: model, generate, export. From here the reference chapters cover each workspace in depth, and the search box at the top of the sidebar (press /) finds anything else.
Installation
Wurk.Flow is distributed as a native desktop application for Windows, macOS, and Linux, and as the Web Studio in your browser. For a step-by-step walkthrough see 1 · Install.
| Platform | Format | Notes |
|---|---|---|
| Windows | NSIS Installer (.exe) | Code-signed installer. Updates are click-initiated from the About window, never automatic. |
| macOS | DMG Disk Image (.dmg) | Signed and notarized by Apple. Apple Silicon (M1 and later). |
| Linux | AppImage | Portable — no installation required, just make executable |
| Web | app.wurk.haus | Nothing to install; Chrome, Edge or Firefox. Installable as an app in Chrome and Edge. See Web Studio. |
System Requirements
- Node.js 18+ — Required for development builds only
- Git — Required for Source Control features (optional)
- Minimum 4GB RAM recommended for large schemas (300+ tables)
Development Setup
# Clone and install git clone <repository-url> cd wurk.flow npm install # Run in development mode (Vite + Electron) npm run dev # Build for your platform npm run build:win # Windows npm run build:mac # macOS npm run build:linux # Linux
First Launch
When you first open Wurk.Flow, you'll see a default schema canvas with two sample tables (Users and Posts) connected by a relationship edge. An interactive guided tour will highlight each workspace tab and core feature.
- The left sidebar contains workspace tab icons (Schema Builder, Source Control, Data Glossary, SQL Workspace, DDL, Requirements, Lineage, Mapping)
- The Schema Explorer sidebar shows your tables grouped by Subject Area
- The status bar at the bottom shows your current Git branch, file path, dirty state, and error count
- Press ? at any time to see the keyboard shortcuts overlay
- Press ⌘+K to open the Command Palette for quick access to any action, including toggling the UI theme (Dark Brutalist ↔ Dark Soft)
- The same canvas opens in the browser at app.wurk.haus; see Web Studio for what is shared and what stays on the desktop
Web Studio
The Web Studio at app.wurk.haus is the same Wurk.Flow, built for the browser. It runs the same renderer, the same modeling engines and the same DDL generator as the desktop app; the differences come down to what a browser tab can and cannot reach. There is no application server: the canvas, the DDL and DBML engines and the DuckDB SQL sandbox all run inside your browser, and the site only serves static files.
Opening it
- Works in current Chrome, Edge and Firefox. In Chrome and Edge you can install it as an app from the address bar; it then opens in its own window like the desktop app.
- Free needs no account. Sign in (key icon at the bottom of the sidebar) only to unlock Pro or Team on your subscription.
- Windows narrower than 780 px, or touch-only devices, get a notice: the canvas is built for a pointer and a desktop-sized window.
- If the browser cannot start WebAssembly (some locked-down profiles), the SQL sandbox is unavailable and says so. Modeling, DDL and every export still work.
What is shared with the desktop app
Schema Builder, Schema Explorer, referential integrity, custom types, DDL generation in every mode and dialect, DBML import and export and the DBML Workspace, the SQL Workspace on the DuckDB sandbox and on MotherDuck, Data Glossary, Data Lineage, Data Mapping, Requirements, the Audit Log, version snapshots, all exports (DDL, DBML, HTML portal, Markdown, PNG, PDF), bring-your-own-key AI, the Command Palette and the keyboard shortcuts.
Tier rules are identical: the same 15-table Free cap and the same PRO and TEAM gates. One deliberate difference in your favour: Import SQL DDL is free in the Web Studio (it is Pro on the desktop).
What is desktop-only
| Feature | Why it stays on the desktop | In the browser, instead |
|---|---|---|
| Live database connections, introspection, migration scripts, schema drift | The database drivers run in the desktop app's main process. A browser cannot open a database socket, and the Web Studio deliberately holds no credentials. | Import SQL DDL (free on web) or Import DBML |
| API → Table import | Uses the desktop's CORS-free fetch | Save the JSON response and drop the file on the canvas |
| Git source control | Needs a local repository and the git binary | Download the .dsm and commit it yourself |
| Co-Op live sessions | The relay is a desktop feature; the web page is not allowed to open a WebSocket to it | — |
| Custom YAML macros | Macro files are read through the desktop file dialog | The built-in bulk actions (naming standards, timestamps, soft deletes, surrogate keys) work everywhere |
| AWS Bedrock and Custom (OpenAI-compatible) AI providers | Bedrock needs SigV4 request signing; custom hosts fall outside the fixed browser allow-list | Ollama, OpenAI, Anthropic, Groq, Mistral AI, Azure OpenAI |
| Crash-recovery snapshots, update checks, crash reports | Desktop process features | Browser autosave (below). The web always serves the latest deploy, so there is nothing to update. |
These controls are hidden in the browser rather than greyed out. A Git or live-database button appearing in the Web Studio is a bug worth reporting.
Files and autosave in the browser
- Save downloads. ⌘+S downloads a
.dsmfile, and that download is the durable copy. Open a project with Open File.... - Autosave lives in your browser. The current project is written continuously to the browser's IndexedDB, a private store on your machine that is not synced anywhere. On your next visit the studio offers to restore it. Declining keeps that copy as a one-time backup until you decline another one.
- One tab owns the autosave. Opening the studio in a second tab shows a warning; close one,
or download a
.dsmto be safe. - Clearing site data for app.wurk.haus deletes the autosave. Anything you downloaded is unaffected.
Licensing in the browser
The desktop app licenses a machine; the Web Studio licenses a user. Sign in with your flow.wurk.haus account and the studio asks the license service whether your organization holds an active subscription, at sign-in and every 6 hours while the tab is open. If that check cannot be reached, the last good answer is kept for 24 hours and then the studio drops to Free until a check succeeds. It never locks you out of your files. Full rules in Licensing.
What the Web Studio talks to
The page ships a Content-Security-Policy that names every host the browser may contact; your browser enforces it independently of us. This is the complete list.
| Host | When | What is sent |
|---|---|---|
| app.wurk.haus | Loading the page | The app's own files, including the DuckDB engine (about 35 MB on first load, cached after that) |
| Supabase (the license service) | Only while signed in, and when you submit the Feedback form | Sign-in and the subscription check; feedback text and any screenshot you attach. Signed out and not sending feedback, this host is never contacted. |
| Your AI provider (OpenAI, Anthropic, Groq, Mistral AI, Azure OpenAI) | Only when you invoke an AI feature | The prompt and your own API key, browser → provider directly. Nothing passes through wurk.haus. |
| localhost / 127.0.0.1 | Only if you choose Ollama | Model requests to your own machine |
| MotherDuck | Only if you connect a MotherDuck database PRO | Your MotherDuck token and queries, browser → MotherDuck directly |
Not on the list, by decision: no analytics, no crash reporting, no update checks, no collaboration relay. There is nothing else the page is permitted to reach. The desktop app's list is in Security Architecture.
Reporting a problem
Errors surface on screen; no report leaves the browser on its own. Use Submit Feedback... from the palette, which attaches an anonymous browser ID (there is no hardware ID on the web) so a reply can find you.
Schema Builder
The Schema Builder is the primary workspace — an infinite, pannable, zoomable canvas powered by Vue Flow where you visually design your database schema using interactive table nodes and relationship edges.
Canvas Controls
| Action | How |
|---|---|
| Pan the canvas | Click and drag on empty space (Pan mode) |
| Zoom | Scroll wheel |
| Select multiple nodes | Switch to Select mode, then click-drag a selection box |
| Fit all nodes to view | Click the fit-view button (⊡) in the toolbar |
| Search for a table | ⌘+F — focuses the search input in the toolbar |
| Drop files onto canvas | Drag CSV, JSON, SQLite, or DuckDB files directly onto the canvas to import |
Adding Tables
Use the toolbar buttons or the Command Palette to add new objects to the canvas:
- + Add Table — Creates a standard database table node
- 👁 View — Creates a view node (generates
CREATE VIEWDDL) - ⚡ Mat. View — Creates a materialized view node
- 🔳 Add Subject Area — Creates a resizable, glassmorphic bounding box to group related tables by domain
Table Nodes
Each table node on the canvas displays the table name and its columns. Click a table to select it and open the Table Properties Drawer on the right side, where you can edit:
- Table name — Also editable by double-clicking in the Schema Explorer sidebar
- Columns — Name, data type, constraints (PK, FK, Unique, Not Null, Check), default value, description
- Schema prefix — Prepended to the table name in generated DDL (e.g.,
analytics.users) - Domain / Subject Area — Governance metadata for classification
- Data classifications — Tags like PII, GDPR, PHI, HIPAA
- Table description & notes — Free-text documentation
- Certification tier — Draft, Silver, Gold, Deprecated
- Owner, refresh frequency — Data stewardship metadata
- Indexes — Define BTREE, HASH, GIN, GIST indexes with column lists
- Stored Procedures & Triggers — Define and document database-level logic
- View SQL — For view/materialized view nodes, the SQL body that defines the view
- CREATE behavior — Choose between
CREATE OR REPLACEorIF NOT EXISTS
Relationships (Edges)
To create a relationship, drag from a column handle on one table node to a column handle on another. Click an edge to open the Edge Properties Drawer where you can set:
- Cardinality — 1:1, 1:N, N:1, N:M, plus zero-or-many variants
- ON DELETE action — CASCADE, SET NULL, SET DEFAULT, RESTRICT, NO ACTION
- ON UPDATE action — Same options as ON DELETE
- Constraint name — Custom FK constraint name for DDL output
The canvas renders proper crow's-foot notation markers for each cardinality type.
Schema Modes
| Mode | Description | Tier |
|---|---|---|
| Physical | Full column-level detail with data types and constraints. Default mode. | Free |
| Conceptual | Entity-only view — hides columns, shows just table names and relationships. Includes an AI prompt bar to generate entities from a natural-language business workflow description. | PRO |
| Logical | Shows columns and relationships but omits physical implementation details like data types. | PRO |
Auto-Link Relationships PRO
The ✨ Auto-Link button scans all tables for columns that follow foreign key naming conventions
(e.g., user_id → users.id) and automatically creates relationship edges between them.
Auto-Organize Canvas PRO
The ⚡ Auto-Organize button applies an intelligent grid layout algorithm that arranges tables alphabetically within their respective Subject Area bounds, creating a clean, organized visual layout.
Copy & Paste
Select a table node and press ⌘+C to copy, then ⌘+V to paste a
duplicate. The copy is offset by 50px and given a _copy suffix. All columns are duplicated with new
IDs.
Undo & Redo
The Schema Builder maintains an undo/redo stack. Use ⌘+Z to undo and ⌘+Shift+Z (or ⌘+Y) to redo. The undo/redo buttons with their current stack depth are visible in the toolbar.
Schema Validation
Wurk.Flow continuously validates your schema in real time. The toolbar displays an error/warning count badge. Click it to open the Validation Issues Drawer which shows:
- Orphaned foreign keys referencing non-existent tables or columns
- Duplicate column names within a table
- Tables with no primary key defined
- Relationship edge mismatches
Many issues include a Target Table link to navigate directly to the offending table, and an Auto-Fix button to resolve the issue automatically.
Column Lineage Tracing
Click any column in a table node to activate Column Lineage Trace mode. All related upstream and downstream columns connected through foreign key relationships will be highlighted with a purple glow. A floating indicator bar appears at the bottom of the canvas — click another column to trace from there, or click empty space to dismiss.
Schema Explorer
The Schema Explorer is a 264px-wide sidebar panel docked to the left of the Schema Builder canvas. It provides a hierarchical, interactive tree view of every table in your schema, organized by Subject Area. It is the primary navigation and organization tool for managing large schemas.
Tree Hierarchy
The explorer organizes tables into a two-level tree:
📁 Subject Area: "User Management" [👁 Hide] [3] ├── 📄 users [👁] [8] ├── 📄 roles [👁] [4] └── 📄 permissions [👁] [6] 📁 Subject Area: "Payments" [👁 Hide] [2] ├── 📄 transactions [👁] [12] └── 📄 invoices [👁] [9] 📁 No Subject [👁 Hide] [1] └── 📄 audit_log [👁] [5]
- Subject Area groups — One collapsible section per Subject Area bounding box on the canvas, with an indigo-tinted header bar
- "No Subject" group — Tables not assigned to any Subject Area appear in a neutral gray group at the bottom
- Table count badge — Each group header displays a count badge (e.g.,
[3]) showing how many tables it contains - Column count badge — Each table entry displays a small badge (e.g.,
[8]) showing its column count
Subject Area Accordions
Click a Subject Area header to collapse or expand its table list. The chevron icon (▶) rotates 90° to indicate expanded state. Collapsed state is maintained during the session — it is not persisted to the project file.
This is essential for large schemas: collapse irrelevant domains so you can focus on the area you're actively working on.
Click to Focus & Pan
Click any table name in the explorer to:
- Select the table node on the canvas (highlighted with an indigo glow border)
- Pan the canvas to center the selected table in the viewport
- Open the Table Properties Drawer on the right side for editing
The currently selected table is highlighted in the explorer with an indigo background and a subtle
box-shadow glow, making it easy to see which table you're working on.
Drag-and-Drop Reassignment
Tables can be dragged between Subject Area groups to reassign them:
- Click and hold on a table entry to initiate a drag
- Drag the table over a different Subject Area group header
- Drop it — the table's
parentNodeis updated to the new Subject Area - The table's canvas position is reset to
(50, 50)relative to the new Subject Area bounding box
To unassign a table from a Subject Area, drag it to the "No Subject" group. This removes the
parentNode reference and the table becomes a free-floating canvas node.
Drop Zone Feedback
Empty Subject Area groups display a "Drop tables here" placeholder text to indicate they accept drag-and-drop operations. If there are no tables and no Subject Areas at all, the sidebar shows: "Start shaping your data. Add a table to begin modeling."
Inline Rename
Double-click any table name in the explorer to enter inline rename mode:
- The table name text is replaced with a focused text input pre-filled with the current name
- Press Enter or click outside the input to confirm the rename
- Press Escape to cancel without saving
- The rename is immediately reflected on the canvas table node, in the Data Glossary, and in all DDL output
Visibility Toggling
The Schema Explorer provides two levels of visibility control:
Table-Level Visibility
- Hover over any table entry to reveal the eye icon (👁) on the right side
- Click the eye to hide the table from the canvas — it becomes invisible on the canvas but remains in your project data
- Hidden tables appear dimmed (40% opacity) in the explorer with a strikethrough on their name
- The eye icon changes to a crossed-out eye (🚫👁) and stays permanently visible (amber color) when hidden
- Hiding a table also hides all relationship edges connected to it (both inbound and outbound)
- Click the crossed-out eye again to unhide the table and restore its edges
Subject Area-Level Visibility
- Click the eye icon on a Subject Area group header to hide/show the entire group
- This hides the Subject Area bounding box and all tables within it from the canvas in a single action
- The group header text is shown with strikethrough and reduced opacity when hidden
- Hidden groups still appear in the explorer tree — they're only hidden from the visual canvas
Visibility toggling is perfect for managing complex schemas — hide "Archived" or "Legacy" Subject Areas to reduce visual clutter while keeping them in your project for reference and DDL generation.
Test (Sandbox) Button
At the bottom of the Schema Explorer sidebar, a fixed "Test (Sandbox)" button provides a quick shortcut to switch to the SQL Query Workspace tab with the DuckDB sandbox pre-selected. This lets you immediately query the tables you're viewing in the explorer.
This button is available at every tier — it jumps to the SQL Workspace with the DuckDB sandbox selected. Free includes one query tab against the sandbox; Pro and Team add unlimited tabs and the live/cloud engines below.
Active Selection State
When a table is selected (either by clicking in the explorer or by clicking on the canvas), it is highlighted in the explorer with:
- An indigo background (
bg-primary/20) - Indigo text color for the table name and icon
- An indigo border with a purple glow shadow
This bidirectional sync means selecting a table on the canvas automatically highlights it in the explorer, and vice versa — you always know which table is active regardless of where you clicked.
Visual Relationships & Referential Integrity
Wurk.Flow supports the complete set of standard SQL Foreign Key constraints and referential actions. This guide explains how to model database relationships visually on the canvas, customize foreign key properties, and understand how they compile into target SQL DDL statements.
Visual Relationship Modeling
In Wurk.Flow, relationships are represented as directed canvas edges connecting columns between parent (Primary Key/Unique) and child (Foreign Key) tables.
1. Drawing Connections
- Manual Mapping: Drag from a handle next to any column in a source table and drop it onto the corresponding column in a target table.
- Auto-Link Relationships PRO: Trigger Auto-Link Relationships via the canvas toolbar or the Command Palette. The matching engine analyzes column names, data types, and primary key structures across all canvas tables to automatically draw foreign key connections.
2. Direction & Roles
Connections represent a Parent-to-Child mapping:
- Parent Column: The source of the constraint, typically a Primary Key (
PK) or a column marked with aUNIQUEconstraint. - Child Column: The destination column receiving the Foreign Key (
FK) constraint.
The Edge Properties Drawer
Clicking on any relationship edge on the canvas opens the Edge Properties Drawer in the right-hand panel. This drawer allows you to customize the physical database representation of that relationship:
- Constraint Name: Customize the physical SQL constraint identifier (e.g.,
fk_orders_customer_id). If left empty, Wurk.Flow auto-generates a standard name following thefk_<child_table>_<parent_table>naming convention. - ON DELETE Actions: Choose what the database should do when a row in the parent table is deleted.
- ON UPDATE Actions: Choose what the database should do when a primary key value in the parent table is updated.
Referential Integrity Actions
Wurk.Flow supports the five complete ANSI SQL referential actions:
| Action | SQL Keyword | Behavior |
|---|---|---|
| Cascade | CASCADE |
Automatically deletes or updates matching rows in the child table when the parent row is deleted or updated. |
| Restrict | RESTRICT |
Prevents the parent row from being deleted or updated if any matching rows exist in the child table. |
| Set Null | SET NULL |
Sets the foreign key column in the child table to NULL when the parent row is deleted or updated (requires the child column to be nullable). |
| Set Default | SET DEFAULT |
Sets the foreign key column in the child table to its defined column default value when the parent row is deleted or updated. |
| No Action | NO ACTION |
(Default) Enforces the relationship constraint at the end of the transaction. In generated DDL, this is either emitted explicitly as NO ACTION or omitted entirely to fall back to standard database defaults. |
Dialect Compilation Behavior
Different database engines enforce foreign keys using different SQL syntaxes. Wurk.Flow compiles your visual relationships into optimized, dialect-specific DDL based on your active project settings:
1. Inline FK Constraints
For dialects that favor single-file local schemas or embedded execution (e.g., SQLite and DuckDB), foreign key constraints and their referential actions are compiled directly inline inside the CREATE TABLE statement:
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER,
created_at TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers (id)
ON DELETE SET DEFAULT
ON UPDATE CASCADE
);
2. Separate ALTER TABLE Statements
For production enterprise databases (e.g., PostgreSQL, MySQL, SQL Server, and MariaDB), foreign keys are emitted as separate ALTER TABLE statements placed at the end of the SQL script. This prevents deployment failures caused by forward-referencing tables or circular dependencies:
-- PostgreSQL / MySQL / SQL Server Example
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers (id)
ON DELETE SET NULL
ON UPDATE NO ACTION;
Note: For PostgreSQL, the DDL engine automatically wraps these operations in idempotent DO $$ BEGIN ... EXCEPTION blocks to prevent duplicate constraint creation errors during migrations.
3. FK-Skipping Dialects
Modern analytical data warehouses (OLAP) often prioritize high-throughput query performance over structural referential checks. In these dialects, physical foreign key constraints are either not supported or severely impact write performance:
- Databricks
- ClickHouse
- Azure Synapse
When target DDL is compiled for these engines, Wurk.Flow skips physical FK generation in the final SQL scripts. However, the visual relationships are fully preserved in your canvas metadata (.dsm) and continue to drive Data Lineage, Schema YAML export, and Transformation SQL (STTM) generations.
Data Glossary PRO
The Data Glossary is a dedicated tab for exploring, filtering, and documenting every table and column in your schema. It provides a spreadsheet-like data dictionary view where each table is rendered as a glassmorphic card with full column metadata, governance badges, and inline editing — essentially a living, interactive data dictionary that stays synchronized with your canvas.
Layout
The glossary renders each canvas table as a standalone card containing:
- Table header — Table name (bold uppercase), Subject Area badge (indigo), and action buttons
- Governance metadata badges — Domain, Tier, Owner, Certification status, and Data Classification tags (displayed as color-coded pills in the card header)
- Business definition — An inline-editable text field below the header for the table's description / business definition
- Column grid — A full-width table with columns for: Column Name, Data Type, Classification, Constraints, and Definition
Column Grid Detail
| Grid Column | Content | Editable? |
|---|---|---|
| Column Name | Name with PK (🔑 yellow) or FK (🔗 blue) icon indicator | No (edit on canvas) |
| Data Type | SQL data type displayed in cyan monospace (e.g., VARCHAR, BIGINT,
TIMESTAMP)
|
No (edit on canvas) |
| Classification | Data sensitivity tag — purple badge (e.g., PII, GDPR, PHI, HIPAA, CONFIDENTIAL) | No (edit on canvas) |
| Constraints | Color-coded constraint badges: PRIMARY KEY (yellow), FOREIGN KEY (blue), NOT NULL (orange), UNIQUE (green) | No (edit on canvas) |
| Definition | Inline text input for the column's business definition / description | Yes — type directly |
Changes made in the Definition field are immediately reflected on the canvas table node and in all exports (DDL comments, Documentation Portal).
Governance Metadata Badges
Each table card displays governance metadata as compact, color-coded badges in the header row. These are set via the Table Properties Drawer on the canvas and are reflected read-only in the glossary (except Domain, which is editable inline):
| Badge | Color | Values | Inline Editable? |
|---|---|---|---|
| Domain | Blue | Free-text (e.g., "Finance", "HR", "Analytics") | Yes |
| Tier | Red / Yellow / Green | Tier 1 (Critical), Tier 2 (Important), Tier 3 (Informational) | No |
| Owner | Indigo | Free-text team or person name | No |
| Certification | Gold / Green / Red | Draft, Silver, Gold, Deprecated | No |
| Classifications | Red | PII, GDPR, PHI, HIPAA, CONFIDENTIAL, etc. | No |
Subject Area Filtering
The glossary toolbar includes a Subject Area dropdown filter that lets you scope the view to a specific domain group:
- All Subject Areas — Shows every table in the schema (default)
- No Subject Assigned — Shows only tables that haven't been placed into a Subject Area bounding box
- Specific Subject Area — Shows only tables belonging to that Subject Area group
This is essential for navigating large schemas with 300+ tables — filter down to "Payment Processing" to see only the 12 tables in that domain.
Column & Table Search
The search input in the glossary toolbar performs a dual-level fuzzy search across both table names and column names simultaneously:
- Typing
emailwill show any table that contains "email" in its name OR has a column named "email" - Search is case-insensitive and filters in real time as you type
- Combined with the Subject Area filter for precision drill-downs (e.g., filter to "User Management" Subject Area, then search for "phone")
AI Auto-Documentation (✨ Auto Button)
Each table card has an ✨ Auto button that triggers AI-powered documentation generation using your configured AI provider (Ollama, OpenAI, Anthropic, Azure OpenAI, Groq, Mistral, or AWS Bedrock):
- The AI receives the table name and all column names/types as context
- It generates a table-level business description (written into the table's description field)
- It generates per-column definitions (written into each column's description field)
- A loading spinner replaces the button while generation is in progress
- Results are immediately visible in the glossary and synchronized to the canvas
This is particularly powerful after importing a schema from a live database — you can click ✨ Auto on each table to generate documentation for an otherwise undocumented production database.
CSV Export
Click Export CSV to generate a complete data dictionary export. The CSV includes the following columns:
| CSV Column | Content |
|---|---|
| Subject Area | Parent Subject Area name (or "No Subject") |
| Table Name | Table label from the canvas |
| Domain | Domain governance tag |
| Classifications | Semicolon-separated classification tags (e.g., "PII;GDPR") |
| Table Definition | Table-level business description |
| Column Name | Column name |
| Data Type | SQL data type |
| Is Primary | "Yes" or "No" |
| Is Foreign | "Yes" or "No" |
| Column Definition | Column-level business description |
The export respects the current Subject Area filter — if you're filtered to a specific area, only those tables are included.
CSV Import (Round-Trip)
Click Import CSV to update table and column definitions from an external CSV file. This enables a powerful round-trip workflow:
- Export the glossary as CSV
- Distribute the CSV to subject matter experts, data stewards, or business analysts
- They fill in the
Table DefinitionandColumn Definitionfields in Excel or Google Sheets - Import the updated CSV back into Wurk.Flow
The import uses DuckDB WASM's read_csv_auto to parse the file, then matches rows
to canvas tables by table name and column name. Only description fields are updated — structural changes (types,
constraints, etc.) are ignored for safety. A toast notification summarizes the results (e.g., "Tables updated: 5
· Columns updated: 42").
Column Matching
The import uses fuzzy header matching — column headers like Table Name, TableName,
and table_name are all recognized. This ensures compatibility with spreadsheet tools that may
modify header formatting.
Data Profiling Stats
When sample data has been loaded into the DuckDB sandbox (via file drop, live introspection, or mock generation), the glossary can display column-level profiling statistics:
- ◆ Distinct count — Number of unique values
- ∅ Null % — Percentage of null values (color-coded: green <10%, amber 10-50%, red >50%)
- ↓ Min value — Minimum value in the column
- ↑ Max value — Maximum value in the column
These statistics appear as compact badges below each column's definition field in the exported Documentation
Portal HTML. They are computed during data import and stored in the column's profiling metadata
object.
SQL Query Workspace
A full-featured SQL IDE with syntax highlighting via CodeMirror, supporting multiple query tabs, variable binding, query history, and execution against multiple targets. The workspace is designed to feel like a native database client embedded directly inside your schema design tool.
The Free edition runs the built-in DuckDB sandbox (one query tab). The live and cloud engines below — MotherDuck and saved database connections — are Pro.
Execution Targets
| Target | Description | Connection |
|---|---|---|
| DuckDB Sandbox | Embedded DuckDB WASM engine running entirely in-browser. Your canvas tables are auto-scaffolded with synthetic seed data so you can immediately query your schema design. | Built-in — zero config |
| MotherDuck PRO | Cloud-hosted DuckDB via the WASM client. Provides persistent cloud storage for your DuckDB databases with collaborative access. | MotherDuck API token (set in Settings) |
| Saved Connections PRO | Execute queries against any saved database connection profile. Queries are dispatched via secure Electron IPC to the main process database drivers. | Any configured connection (see Database Connections) |
DuckDB Sandbox — How It Works
The sandbox is the default execution target. When you switch to the SQL Workspace, Wurk.Flow automatically:
- Drops existing sandbox tables — Cleans up previous schema state (tables with real imported data from CSV/JSON/API drops are preserved)
- Applies your canvas DDL — Executes
CREATE TABLE/CREATE VIEWstatements generated from your current schema canvas - Inserts synthetic seed data — Generates 5 realistic rows per table using the AI-powered seed data generator (with local fallback). Column names and types are used to generate contextually appropriate fake data (e.g., real-looking names, email addresses, and contextual values).
This means you can query your schema design as if it were a live database — immediately after modeling
a table, you can write SELECT * FROM users and see results.
Real vs. Synthetic Data
When you drag-and-drop a .csv, .json, or .sqlite file onto the canvas,
the actual data is loaded into DuckDB. These "real data" tables are protected — the sandbox
sync process will never drop or overwrite them. Synthetic data is only generated for tables that don't already
have imported data.
Multi-Tab Workspace
Free includes one query tab against the built-in DuckDB sandbox. Pro and Team add unlimited tabs and live/cloud connections. Each tab maintains its own independent state:
| Tab Property | Description |
|---|---|
| SQL Editor | CodeMirror editor with SQL syntax highlighting and schema-aware autocomplete |
| Results Grid | Tabular results with column headers and row data |
| Error Output | Detailed error messages from failed queries |
| Execution Time | Millisecond-precision timing for each query run |
| Variables | Per-tab variable bindings (see Variable Binding below) |
| EXPLAIN Plan | Cached explain output with toggle to show/hide |
| Connection Target | Per-tab connection selection — each tab can target a different database |
| Scratchpad Notes | Free-text notes area for documenting query intent or results |
- Add tab — Click the
+button. New tabs are auto-titledQuery 1,Query 2, etc. - Close tab — Click the
×on any tab. If you close the last tab, a fresh empty tab is created automatically. - Rename tab — Double-click the tab title to rename it for organization (e.g., "Revenue Report", "User Funnel").
- Persistence — All tab state (SQL, notes, title, variables) is serialized into your
.dsmproject file on save.
Multi-Statement Execution
The SQL Workspace supports multi-statement execution — write multiple SQL statements separated by semicolons and they will be executed sequentially in order. The results grid displays the output from the last statement that returned rows.
-- All three statements execute in sequence: CREATE TABLE temp_report AS SELECT * FROM orders WHERE status = 'complete'; UPDATE temp_report SET total = total * 1.1; SELECT * FROM temp_report ORDER BY total DESC; -- ← results shown
If any statement fails in a multi-statement batch, execution stops and the error message indicates which
statement number failed (e.g., "Statement 2 failed: ...").
Variable Binding
Use double-curly-brace syntax to parameterize your queries:
SELECT * FROM orders
WHERE created_at >= '{{ start_date }}'
AND status = '{{ order_status }}'
LIMIT {{ row_limit }}
Open the Variables panel to define key-value pairs. Variables are interpolated before the query is sent to the execution engine — this is string substitution, not parameterized binding, so use caution with untrusted input.
- Variables are per-tab — each query tab maintains its own variable set
- Whitespace inside the braces is ignored (
{{ var }}and{{var}}both work) - Undefined variables are left as-is in the SQL (no error thrown)
EXPLAIN Plans
Click the Explain button (or select "Explain" from the run menu) to execute
EXPLAIN <your query>. The execution plan is displayed in a monospaced text block below the
editor. This works across all targets — DuckDB, MotherDuck, and remote connections.
Query History
Every successful query execution is recorded in the session history panel:
- Capacity — Stores up to 200 entries (oldest entries are evicted when the limit is reached)
- Stored fields — SQL text (first 2,000 characters), timestamp, row count, execution time (ms), target connection
- Search — Filter history by SQL text with the search input
- Restore — Click any history entry to load its SQL back into the active tab's editor
- Clear — Wipe all history entries with the clear button
- Storage — History is stored in
sessionStorage(persists within a session but clears when the app is closed) - Relative timestamps — History entries show human-readable relative times ("5s ago", "2m ago", "1h ago")
Result Pagination
Query results are paginated at 100 rows per page to keep the UI responsive with large result sets:
- Navigate with Previous / Next buttons or jump to a specific page number
- The total row count and current page indicator are shown in the results toolbar
- Pagination resets to page 1 automatically when you run a new query or switch tabs
- CSV export always exports the full result set, not just the current page
Cancel / Abort Queries
Click the Cancel button (visible while a query is running) to abort execution. Cancellation
uses an AbortController and is checked between individual statements in multi-statement batches.
The results area shows "Query cancelled by user" and the tab returns to an idle state.
CSV Export
Click the Export CSV button in the results toolbar to download the full result set as a
.csv file. The export:
- Includes column headers as the first row
- Properly escapes commas, quotes, and newlines within cell values
- Auto-generates a timestamped filename (e.g.,
query_results_2026_05_06_14_30_00.csv) - Exports all rows (ignores pagination — you get the complete result set)
Database Explorer Tree
The left panel of the SQL Workspace displays an interactive database explorer tree showing the hierarchical structure of your current target:
📁 Catalog (database name)
📁 Schema (e.g., public, main)
📄 Table 1
📄 Table 2
📄 Table 3
- The tree auto-populates when you select an execution target
- For DuckDB/MotherDuck, it queries
information_schema.tablesdirectly - For remote connections, it uses the
db:introspectIPC channel to fetch schema metadata - Column-level enrichment — When you expand a table, its columns are fetched and used to
enrich the CodeMirror autocomplete. Typing a table name followed by
.will suggest its columns. - Click a table name to insert it at the cursor position in the editor
DDL Generation
The DDL tab generates SQL output in real time as you model on the canvas — every table you add, column you modify, or relationship you draw is instantly reflected in the generated DDL output. It supports 5 generation modes and 13 SQL dialects, with deep awareness of dialect-specific syntax, type mapping, and constraint handling.
Generation Modes
| Mode | Output | Tier |
|---|---|---|
| Full Build | Complete CREATE TABLE / CREATE VIEW / CREATE MATERIALIZED VIEW
DDL with all constraints, indexes, stored procedures, and triggers |
Free |
| Migration Diff | ALTER TABLE statements comparing your current canvas to the last saved baseline on disk
|
PRO |
| Seed Data (Mock) | AI-powered synthetic INSERT INTO statements with context-aware realistic fake data for testing (with local fallback) |
Free |
| Insert Data (Real) | INSERT INTO statements using actual imported data from CSV/JSON/API file drops |
PRO |
Full Build Mode — What Gets Generated
The Full Build mode produces a complete, deployment-ready DDL script with the following sections in order:
- Table metadata comments — Block comments with Domain, Description, and Classification tags for each table
- CREATE TABLE statements — Column definitions with data types, DEFAULT values, NOT NULL, UNIQUE, CHECK constraints, and inline PRIMARY KEY (for single-column PKs)
- Composite PRIMARY KEY — For tables with multi-column primary keys, a separate
PRIMARY KEY (col1, col2)clause is appended - Foreign Key constraints — Either inline
REFERENCES(DuckDB/SQLite) or separateALTER TABLE ADD CONSTRAINTstatements with ON DELETE / ON UPDATE actions - COMMENT ON statements — Table and column-level comments (PostgreSQL, Snowflake, Redshift, CockroachDB)
- CREATE INDEX statements — Including UNIQUE indexes and PostgreSQL-specific index types (GIN, HASH, etc.)
- CREATE VIEW / MATERIALIZED VIEW — With
CREATE OR REPLACEand the user-defined SQL definition (or aNULL AS colstub when no definition is provided) - Stored Procedures — Dialect-aware procedure generation (PostgreSQL functions, MySQL DELIMITER blocks, SQL Server CREATE OR ALTER PROCEDURE, Snowflake procedures)
- Triggers — Dialect-aware trigger generation (PostgreSQL trigger functions, MySQL DELIMITER, SQL Server AFTER triggers). Dialects that don't support triggers emit informational comments.
Topological Sorting
Tables are topologically sorted before DDL emission so that parent (referenced) tables are always created before their children. This ensures the DDL script can be executed top-to-bottom without foreign key reference errors. Circular dependencies are detected and handled gracefully (the cycle is broken safely).
CREATE Behavior Options
Each table has a configurable CREATE behavior that controls how the DDL handles existing tables:
| Option | PostgreSQL / DuckDB | SQL Server | MySQL |
|---|---|---|---|
| Default | CREATE TABLE IF NOT EXISTS or CREATE OR REPLACE TABLE (dialect-dependent) |
CREATE TABLE |
CREATE TABLE |
| If Not Exists | CREATE TABLE IF NOT EXISTS |
IF OBJECT_ID(N'...', N'U') IS NULL BEGIN ... END; |
CREATE TABLE IF NOT EXISTS |
| Replace | CREATE OR REPLACE TABLE |
CREATE TABLE (not supported) |
CREATE TABLE (not supported) |
PostgreSQL also supports UNLOGGED tables — when the "Unlogged" flag is set on a table, the DDL
emits CREATE UNLOGGED TABLE for write-heavy staging tables that don't need WAL durability.
Identifier Quoting
Identifiers (table names, column names) are quoted using the correct syntax for each dialect:
| Dialect Group | Quoting Style | Example |
|---|---|---|
| PostgreSQL, DuckDB, Snowflake, Redshift, CockroachDB | ANSI double-quotes | "users" |
| MySQL, MariaDB, BigQuery, Databricks, ClickHouse | Backticks | `users` |
| SQL Server, Synapse | Square brackets | [users] |
Fully-qualified names respect schema prefixes: "public"."users" or `dataset.users`
(BigQuery/Databricks use dotted backtick paths).
Data Type Mapping
Wurk.Flow includes a comprehensive type mapping engine that translates generic SQL types into dialect-specific equivalents. This means you can model your schema once and generate valid DDL for any target:
| Generic Type | PostgreSQL | SQL Server | BigQuery | ClickHouse | SQLite |
|---|---|---|---|---|---|
VARCHAR(255) |
VARCHAR(255) |
NVARCHAR(255) |
STRING |
String |
TEXT |
INTEGER |
INTEGER |
INT |
INT64 |
Int32 |
INTEGER |
BOOLEAN |
BOOLEAN |
BIT |
BOOL |
Bool |
INTEGER |
TIMESTAMP |
TIMESTAMP |
DATETIME2 |
TIMESTAMP |
DateTime |
TEXT |
UUID |
UUID |
UNIQUEIDENTIFIER |
STRING |
UUID |
TEXT |
JSON / JSONB |
JSON / JSONB |
NVARCHAR(MAX) |
JSON |
String |
TEXT |
SERIAL |
SERIAL |
INT IDENTITY(1,1) |
INT64 |
UInt64 |
INTEGER |
User-specified precision is preserved (e.g., VARCHAR(100) stays VARCHAR(100) in
PostgreSQL but maps to NVARCHAR(100) in SQL Server).
Foreign Key Constraints
Foreign keys are generated based on the relationship edges drawn on your canvas:
- Parent/Child detection — Wurk.Flow automatically determines which table is the parent (referenced) and which is the child (referencing) based on PK/FK column flags
- Inline REFERENCES — DuckDB and SQLite use inline
REFERENCES parent_table(pk_col)syntax - ALTER TABLE — All other dialects emit separate
ALTER TABLE ADD CONSTRAINT fk_...statements after all CREATE TABLEs - ON DELETE / ON UPDATE — Configurable referential actions (CASCADE, SET NULL, RESTRICT, NO ACTION) from the relationship edge properties
- Idempotent PostgreSQL — PostgreSQL FK constraints are wrapped in
DO $$ BEGIN ... EXCEPTION WHEN duplicate_object THEN NULL; END $$;blocks for safe re-execution - Informational comments — Databricks, ClickHouse, and Synapse don't enforce FK constraints,
so informational
-- FK (informational):comments are emitted instead
Column-Level Features in DDL
- DEFAULT values — Keyword defaults (
CURRENT_TIMESTAMP,NOW(),NEWID(),GEN_RANDOM_UUID()) are emitted unquoted; string defaults are single-quoted with escape handling - NOT NULL — Appended for non-primary-key columns with the NOT NULL flag (PKs are implicitly NOT NULL)
- UNIQUE — Inline UNIQUE constraint (skipped in sandbox mode to avoid DuckDB conflicts)
- CHECK constraints — Custom CHECK expressions from the column properties (auto-detects whether to prefix with the column name)
- Column descriptions — Appended as inline SQL comments (
-- description text) on each column definition line
ClickHouse-Specific Generation
ClickHouse requires an ENGINE and ORDER BY clause instead of a simple closing
parenthesis. The DDL generator automatically emits:
CREATE TABLE `events` (
`id` Int64,
`event_type` String,
`created_at` DateTime
)
ENGINE = MergeTree()
ORDER BY (`id`);
If no primary keys are defined, it falls back to ORDER BY tuple().
Migration Diff Mode PRO
Migration Diff compares your current canvas state against the last saved baseline (the .dsm file on disk) and generates only the delta as ALTER statements. It detects:
| Change Type | DDL Generated |
|---|---|
| New table added | Full CREATE TABLE statement |
| Table deleted | DROP TABLE with optional CASCADE/RESTRICT |
| Table renamed | ALTER TABLE ... RENAME TO (or sp_rename for SQL Server) |
| Column added | ALTER TABLE ... ADD COLUMN |
| Column dropped | ALTER TABLE ... DROP COLUMN |
| Column renamed | ALTER TABLE ... RENAME COLUMN (or sp_rename for SQL Server) |
| Column type changed | ALTER TABLE ... ALTER COLUMN ... TYPE (with USING cast for PostgreSQL) |
| No changes | Informational comment: "No structural layout changes detected" |
Seed Data (Mock) Mode
Generates INSERT INTO statements with contextually-aware synthetic data. The mock
generator inspects column names and types to produce realistic values:
| Column Pattern | Generated Data |
|---|---|
Column named email |
'[email protected]' |
Column named first_name |
'Charlie' |
Column named city / location |
'San Diego' |
Column named status / state |
'ACTIVE', 'PENDING', etc. |
Column named phone |
'555-847-3291' |
Column named ip |
'192.168.14.203' |
UUID type |
'a1b2c3d4-e5f6-4a7b-8c9d-...' |
INTEGER PK |
Sequential: 1, 2, 3, ... |
INTEGER FK |
Random reference: 1–N (within row count range) |
BOOLEAN |
Alternating TRUE/FALSE |
DATE |
Random date within the past year |
TIMESTAMP |
Random datetime within the past year |
FLOAT / DECIMAL |
Random value 0–1000 with 2 decimal places |
Default row count is 50 rows per table (5 rows when used for sandbox sync). Snowflake
timestamps use TO_TIMESTAMP() wrapper syntax.
Supported Dialects
| Dialect | Tier | Notable Dialect Quirks |
|---|---|---|
| DuckDB | Free | CREATE OR REPLACE, inline FK REFERENCES, JSON type native |
| PostgreSQL | Free | SERIAL/BIGSERIAL, COMMENT ON, UNLOGGED tables, idempotent FK blocks, trigger functions |
| SQLite | Free | No schema prefix, TEXT for timestamps/booleans/UUIDs, B-Tree only indexes, no triggers/procedures |
| MySQL / MariaDB | PRO | Backtick quoting, AUTO_INCREMENT, DELIMITER blocks for procedures/triggers |
| Snowflake | PRO | TIMESTAMP_NTZ, VARIANT for JSON, AUTOINCREMENT, LANGUAGE-based procedures, no triggers |
| SQL Server / Synapse | PRO | Bracket quoting, IDENTITY(1,1), IF OBJECT_ID wrapper, NVARCHAR, sp_rename, CREATE OR ALTER |
| BigQuery | PRO | Backtick-dotted paths, STRING/INT64/FLOAT64 types, no indexes, no FK enforcement |
| Databricks | PRO | GENERATED BY DEFAULT AS IDENTITY, STRING type, informational FK comments |
| Redshift | PRO | IDENTITY(1,1), VARCHAR(256) default, SUPER for JSON, COMMENT ON support |
| ClickHouse | PRO | MergeTree() ENGINE, ORDER BY required, no FK enforcement, no triggers/procedures |
| CockroachDB | PRO | INT8 default, STRING type, unique_rowid() for auto-IDs, COMMENT ON support |
Custom Types
Beyond plain tables, Wurk.Flow models first-class custom type objects on the Schema Builder canvas — enums, domains, sequences, and composite user-defined types. Each is its own node, participates in DDL generation with per-dialect output, and links visually to the columns that use it.
Enums
An enum defines a named, fixed set of allowed values (e.g.
status = active | inactive | pending). Add one from the toolbar + Enum button, the
Add Enum command, or via DBML import. Values are edited inline on the purple node itself (the
sticky-note pattern — no properties drawer).
- Usage — a column "uses" an enum by setting its type to the enum's name. Wurk.Flow draws a dashed link from the enum to every column whose type matches (display-only, never persisted).
- DDL — PostgreSQL emits
CREATE TYPE … AS ENUM (…); MySQL inlinesENUM(…)on the column; dialects without a native enum fall back to aCHECK (col IN (…))constraint.
Domains
A domain is a reusable base type plus a constraint — e.g. an email domain over
VARCHAR with a format CHECK — so many columns can share one validation rule.
- DDL — PostgreSQL emits
CREATE DOMAIN … AS <base> CHECK (…); dialects without domain support fall back to inlining theCHECKon each column that uses the domain.
Sequences
A sequence node models an auto-incrementing number generator. It generates
CREATE SEQUENCE DDL and links visually to any column whose DEFAULT references it (e.g.
DEFAULT nextval('order_id_seq')), so you can see which columns draw from it.
User-Defined Types (UDTs)
A composite UDT bundles several typed fields into one reusable structured type. UDT nodes
support per-field editing and emit cross-dialect DDL (e.g. PostgreSQL CREATE TYPE … AS (…)), with a
graceful fallback where a dialect has no composite-type support.
All custom types are saved with the project and emitted in DDL export in dependency order — the type definitions are written ahead of the tables that reference them. Enums also round-trip through DBML import.
Requirements Workbench
The Requirements tab provides a dedicated, two-pane workspace for capturing business requirements, data architecture context, and acceptance criteria alongside your schema design. It combines structured, card-based requirement tracking with a freeform markdown editor — giving you both formal traceability and unstructured documentation in a single view.
Layout
The workbench is split into two side-by-side panels:
- Left panel (420px) — Structured Requirement Cards: a scrollable list of formal requirement items with metadata, table links, and acceptance criteria
- Right panel (flexible) — Freeform Markdown Editor: a full-featured markdown editor with toolbar, live preview, and split-view mode for unstructured architecture context
Header Bar
The workbench header provides quick access to global actions and statistics:
- Stats badges — Live counters showing total items and coverage percentage (linked % of requirements → color-coded: ≥80% green, ≥50% amber, <50% red)
- 📋 Templates dropdown — Prebuilt requirement templates (see Templates section below)
- ⬇ CSV button — Export all structured requirements as a CSV file
- + Add Requirement — Create a new structured requirement card
Structured Requirement Cards
Each requirement is rendered as a glassmorphic card in the left panel. Clicking a card expands its edit form; clicking again collapses it. Each card contains:
Card Header (Always Visible)
- Requirement ID — Auto-generated unique ID (e.g.,
REQ-a7f3b) displayed in emerald monospace - Priority badge — MoSCoW priority (color-coded pill)
- Status badge — Workflow status (color-coded pill)
- Title — Truncated requirement title (or "Untitled requirement..." placeholder)
- Linked table badges — Up to 3 linked table names shown as indigo pills, with "+N more" overflow indicator
- Acceptance criteria progress bar — Green fill bar showing completion ratio (e.g., 3/5) with numeric label
Expanded Edit Form (On Click)
When a card is expanded, the full edit form is revealed with animated transition:
| Field | Type | Description |
|---|---|---|
| Title | Text input | Short descriptive name for the requirement |
| Description | Textarea | Detailed description — resizable, monospace font for technical content |
| Priority | Dropdown | MoSCoW priority classification (see table below) |
| Status | Dropdown | Workflow status (see table below) |
| Category | Dropdown | Requirement category classification (see table below) |
| Linked Tables | Multi-select | Canvas tables linked to this requirement for traceability |
| Acceptance Criteria | Checklist | Per-requirement checklist with done/not-done checkboxes |
MoSCoW Priority
Each requirement uses the MoSCoW prioritization framework:
| Priority | Badge Color | Icon | Meaning |
|---|---|---|---|
| Must Have | Red | 🔴 | Non-negotiable — project fails without this |
| Should Have | Amber | 🟡 | Important but not critical — workaround exists |
| Could Have | Blue | 🔵 | Nice-to-have — include if time permits |
| Won't Have | Gray | ⚫ | Explicitly out of scope for this iteration |
The left panel header includes a priority breakdown mini-bar — a thin horizontal bar with proportional colored segments showing the distribution of Must/Should/Could/Won't across all requirements at a glance.
Status Workflow
Requirements progress through a 4-stage workflow:
| Status | Badge Color | Meaning |
|---|---|---|
| Draft | Gray | Newly created, under review |
| Approved | Purple | Stakeholder-approved, ready for implementation |
| Implemented | Blue | Schema changes and logic have been built |
| Verified | Green | Acceptance criteria met, testing complete |
Categories
Requirements are classified into one of 5 categories:
| Category | Use For |
|---|---|
| Functional | Business logic, data flows, transformations |
| Non-Functional | SLAs, uptime, scalability, latency targets |
| Data Quality | Null checks, referential integrity, dedup rules |
| Security | Access controls, encryption, masking, compliance |
| Performance | Query speed, indexing, partitioning strategies |
Table Linking (Traceability)
Each requirement can be linked to one or more canvas tables via the "Linked Tables" dropdown:
- Expand a requirement card and scroll to the "Linked Tables" section
- Select a table from the dropdown — it is added as an indigo badge
- Click the × button on a badge to unlink a table
- Linked table names resolve in real-time from the canvas (if a table is renamed, the badge updates automatically)
The header badge shows coverage percentage: the ratio of requirements that have at least one linked table. This is a quick measure of how well your requirements are traced to your schema design.
Coverage Thresholds
- ≥ 80% — Green (excellent traceability)
- ≥ 50% — Amber (some gaps)
- < 50% — Red (significant traceability gaps)
Acceptance Criteria
Each requirement has its own checklist of acceptance criteria:
- Click "+ Add" to add a new criterion
- Type the criterion text in the inline input field
- Check the checkbox to mark it as done (text gets a strikethrough style)
- Click the × button to delete a criterion
The card header shows a compact progress bar with a numeric label (e.g., "3/5") that fills proportionally as criteria are marked done. This gives an immediate visual sense of completion state across all cards.
AI Proofreading (✨ Polish)
Each requirement card has a ✨ Polish button that sends the description text to your configured AI provider for grammar correction, tone refinement, and clarity improvements:
- The AI receives the raw description text and returns a polished version
- The corrected text replaces the original description in-place
- A "⏳ Polishing..." indicator is shown while the request is in flight
- The freeform markdown editor also has its own ✨ Polish button in the toolbar for proofreading the entire markdown document
Requires an AI provider to be configured in Settings (Ollama, OpenAI, Anthropic, Azure OpenAI, Groq, Mistral, or AWS Bedrock). If no AI is configured, a warning toast is shown.
Freeform Markdown Editor
The right panel is a full-featured markdown editor for unstructured documentation. It supports three view modes, toggled via the mode switcher in the toolbar:
| Mode | Description |
|---|---|
| Edit | Full-width monospace textarea for writing raw markdown |
| Split | Side-by-side editor + live preview (default) |
| Preview | Full-width rendered HTML preview with styled prose |
Markdown Toolbar
The toolbar provides quick-insert buttons for common markdown syntax:
- H1 / H2 / H3 — Insert heading prefix (
#,##,###) - B — Bold (
**text**) - I — Italic (
*text*) - </> — Inline code (
`text`) - • List — Bullet list (
- text) - ☐ Check — Task checklist (
- [ ] text) - ❝ Quote — Blockquote (
> text) - ― HR — Horizontal rule (
---) - ✨ Polish — AI proofreading for the entire document
All toolbar actions work with text selection — select text first and the formatting wraps the selection. If no text is selected, placeholder text is inserted.
Requirement Templates
The template picker in the header provides prebuilt markdown structures for common data engineering scenarios:
| Template | Icon | Sections Included |
|---|---|---|
| Data Warehouse | 🏢 | Business Context, Grain Definition, Slowly Changing Dimensions, Data Freshness, Historical Requirements |
| API Integration | 🔌 | Source Systems, Rate Limits & Throttling, Schema Contracts, Error Handling, Data Validation |
| ETL Pipeline | ⚙️ | Source-to-Target Mapping, Scheduling & Orchestration, Idempotency, Error Handling, Data Quality Checks |
| Data Migration | 📦 | Migration Phases (5-step), Rollback Strategy, Validation Checkpoints, Downtime Window |
| Compliance / GDPR | 🔒 | Data Classification, Retention Policies, Right to Erasure, Consent Management, Access Controls, Audit Trail |
| Blank | 📝 | Empty document — start from scratch |
Applying a template to a non-empty editor triggers a confirmation dialog: "Replace current content with template? Current content will be lost." — preventing accidental overwrites.
CSV Export
Click ⬇ CSV to export all structured requirements as a requirements.csv file. The
CSV includes:
| CSV Column | Content |
|---|---|
| ID | Auto-generated requirement ID |
| Title | Requirement title |
| Description | Full description text |
| Priority | must / should / could / wont |
| Status | draft / approved / implemented / verified |
| Category | functional / non-functional / data-quality /
security / performance
|
| Linked Tables | Semicolon-separated table names |
| Acceptance Criteria | Semicolon-separated checklist items with [x] / [ ] markers |
| Criteria Done | Count of completed acceptance criteria |
| Criteria Total | Total count of acceptance criteria |
| Created | Creation date (localized format) |
The CSV is exported with a UTF-8 BOM prefix for compatibility with Excel, SSMS, and Azure Data Studio.
Persistence
Both structured requirements (cards) and freeform markdown content are saved with your .dsm project file. They are also included in:
- Documentation Portal HTML export — Requirements appear as a dedicated section in the exported report
- Markdown export — The freeform markdown and structured items are both serialized into the markdown dictionary export
- Git version control — Changes to requirements are tracked alongside schema changes when using the Source Control panel
Data Lineage PRO
The Data Lineage tab provides a separate, dedicated canvas for mapping the end-to-end journey of data through your organization — from source systems through transformations to consumers. It uses the same Vue Flow engine as the Schema Builder but with specialized node types designed for pipeline architecture rather than table design.
Node Types
The lineage canvas supports 6 distinct node types, each with unique color coding, icons, and handles:
| Node Type | Color | Icon | Default Label | Purpose | Examples |
|---|---|---|---|---|---|
| System | Blue | 🗄️ Database cylinder | "New System" / DATABASE | Source or destination systems — databases, data lakes, SaaS platforms | Salesforce, SAP, PostgreSQL, S3 bucket, Snowflake |
| Job | Indigo | ⚡ Lightning bolt | "New ETL Job" / TRANSFORMATION | ETL/ELT processing steps, transformation logic, orchestration tasks | Airflow DAG, Glue job, Spark job, SSIS package |
| Consumer | Pink | 👁 Eye | "New Consumer" / BI TOOL | End consumers of data — visualization, analytics, ML | Tableau dashboard, Power BI, Looker, ML model, data product |
| API Call | Cyan | 🔗 Link | "New API" / REST EXTERNAL | External API integrations, webhooks, microservice calls | REST endpoint, GraphQL, webhook, event stream |
| Flat File | Amber | 📄 Document | "New Flat File" / S3 / BLOB | File-based data sources and sinks | CSV drops, Excel uploads, Parquet files, JSON feeds |
| Table Reference | Emerald/Gray | 📊 Grid table | (Canvas table name) / DESIGNED SCHEMA | Links to tables from your Schema Builder canvas — bridges schema design and data flow | Any canvas table (live-synced name and columns) |
Node Handles & Connections
Each node has two connection handles for creating directional data flow edges:
- Left handle (target) — Receives incoming data flow edges
- Right handle (source) — Emits outgoing data flow edges
Drag from any source handle to any target handle to create a connection. Edges are rendered as animated indigo lines with a 2px stroke, indicating the direction of data flow.
Table Reference nodes have additional per-column handles — each column in the expanded column
list has its own left/right handles, enabling column-level lineage tracking (e.g., mapping
source.customer_id directly to target.cust_id).
Table Reference Nodes
Table Reference nodes are unique — they are live links to your Schema Builder canvas tables:
- Adding — Use the "Link Table" dropdown in the toolbar to select a canvas table and click "Add"
- Deduplication — Each table appears once on the lineage canvas (matched by name). Adding a table that's already present shows a warning; if a database-populated reference of the same name already exists, it is linked to your designed table instead of being duplicated.
- Columns — The node displays a full column list (name + type) with per-column connection handles for column-level lineage
- Collapsible — Click the chevron (▲/▼) in the header to collapse/expand the column list. When collapsed, invisible handles are preserved so existing edge connections don't break.
- Read-only name — The label is synced from the canvas table name. Renaming is disabled in the lineage drawer; a note directs you to the Schema Builder.
- Styled badge — Shows "DESIGNED SCHEMA" in emerald text to distinguish it from external system nodes
Toolbar
The lineage toolbar provides all node creation and management actions:
| Button | Action |
|---|---|
| + Add System | Create a new System node at position (100, 100) |
| + Add Job | Create a new Job node at position (300, 100) |
| + Add Consumer | Create a new Consumer node at position (500, 100) |
| + Add API | Create a new API Call node at position (500, 300) |
| + Add File | Create a new Flat File node at position (500, 500) |
| ⚡ Auto-Organize | Run the BFS-based auto-layout algorithm (see below) |
| Link Table dropdown | Select a canvas table and click "Add" to create a Table Reference node |
| ⚡ Populate from DB | Open the database introspection popover (see below) |
| Derive from SQL | Parse modeled view SQL definitions into column-level lineage edges (see below) |
Properties Drawer
Click any lineage node to open the Properties Drawer (320px panel sliding in from the right). It provides:
| Field | Description | Editable? |
|---|---|---|
| Node Title | Name/label of the node | Yes (except Table Reference — synced from canvas) |
| Component Classification | Type sublabel (e.g., "DATA WAREHOUSE", "STAGING", "RAW", "MATERIALIZED VIEW") | Yes (except Table Reference) |
| Notes | Free-text textarea for implementation notes, SQL logic, or technical context | Yes (all node types) |
Click the × button or click on empty canvas space to close the drawer.
Populate from DB (⚡)
The "Populate from DB" button opens a popover that lets you auto-generate lineage nodes by introspecting a live database connection:
- Select a saved connection profile from the dropdown (uses connections saved via File → Import Live Database)
- Toggle which elements to include:
- Tables & Columns — Creates Table Reference nodes for every table in the database (with full column metadata)
- Foreign Key relationships — Creates animated edges between tables based on FK constraints
- Views & Materialized Views — Creates Job nodes with appropriate type labels ("VIEW" or "MATERIALIZED VIEW"), and auto-parses the view SQL to detect source table dependencies
- Click 🚀 Populate Lineage
What Gets Generated
The populate process creates the following elements automatically:
| Element | Node Type | Edge Style |
|---|---|---|
| Database system | System node (with engine icon, e.g., 🐘 PostgreSQL, ❄️ Snowflake) | — |
| Each table | Table Reference node (with columns, PKs, FK flags) | — |
| System → Table | — | Blue dashed line (1.5px, dasharray 6 3) |
| FK relationships | — | Indigo animated line (2px) with "col → col" label |
| Views | Job node (typeLabel: VIEW or MATERIALIZED VIEW) | — |
| Source table → View | — | Purple animated line (2px) with "reads from" label |
| View → Output table | — | Cyan animated line (2px) with "produces" label |
Tables are arranged in a 4-column grid (340px × 260px cells). Views are placed in a separate column block to the right. All nodes are deduplicated — running populate twice against the same connection will not create duplicates. A toast notification summarizes the results (e.g., "Populated 12 table(s), 3 view(s), 15 edge(s)").
Auto-Organize (⚡)
The auto-layout algorithm uses BFS-based layered graph layout to arrange all lineage nodes:
- Connected component detection — Identifies separate clusters of connected nodes
- Root detection — For each cluster, finds nodes with no incoming edges (data sources). Falls back to the node with the most outgoing connections if no pure root exists.
- BFS layering — Assigns each node to a horizontal layer based on its distance from roots
- Layer positioning — Nodes within each layer are sorted alphabetically and spaced vertically. Layers are spaced 420px apart horizontally.
- Isolated node grid — Unconnected nodes are arranged in a 4-column grid below the clustered layouts
This produces a clean left-to-right flow: Sources → Transformations → Destinations.
Edge Interactions
- Click an edge to select it (sets
selectedLineageEdgeId) - Click empty canvas to deselect all nodes and edges
- Edges support labels — auto-generated labels like "reads from" and "produces" describe the data flow direction
Column-Level Lineage
Beyond node-to-node flow, the lineage canvas tracks lineage at the individual column level.
Expand a Table Reference node and drag from a column's right (source) handle to another column's left (target)
handle to record a precise source.column → target.column relationship.
- Emerald edges — column-anchored edges render in emerald (
#34d399) with asource_col → target_collabel, visually distinguishing them from the indigo table-level edges. - Auto-populated — the Populate from DB and Derive from SQL flows create column-anchored edges automatically, anchoring foreign-key and view-column relationships to the exact columns.
- Survives collapse — column edges stay connected when a node's column list is collapsed.
Hover Path-Trace
Hovering — or selecting — any node or edge highlights its connected upstream + downstream path and dims everything else, so you can see at a glance what a given element touches end-to-end. Move the cursor away to restore full opacity. The trace is purely visual and never changes the graph.
Impact Analysis — Blast Radius
Impact analysis answers "what breaks if I drop this?" by walking the graph downstream from a focus point and highlighting everything affected in danger red.
- Node-level — select a node and click Analyze Impact in its properties drawer to see every downstream node it feeds (table → view → consumer).
- Column-level — click a column row inside a Table Reference node to trace the precise downstream columns that depend on it, following the column-anchored edges.
- Impact panel — a report panel summarizes the blast radius: the focus, the downstream-node and affected-column counts, and the full affected-column list. Close it with the × button.
- Staleness detection — if a Table Reference's snapshot columns no longer match its linked Schema Builder table (a column was renamed or dropped), the affected column is flagged stale with an amber badge, warning that the blast radius may be inaccurate.
Co-Op safe. Impact highlighting, the hover path-trace, and the impact panel are ephemeral — computed on the fly, never written into the saved project or broadcast over Co-Op — so they're safe to use mid-session and during live collaboration.
Derive Lineage from View SQL
The Derive from SQL toolbar button parses the SQL definition of any view modeled in your
Schema Builder (a node whose kind is VIEW / MATERIALIZED VIEW with a stored
definition) and wires column-level lineage to the source tables already on the canvas — no database round-trip
required.
- Table-level — one "reads from" edge per source table in the view's
FROM/JOINclauses (including old-style comma joins). - Column-level — emerald
source.col → view.coledges wherever both columns resolve by name (handles qualified columns,ASaliases — including quoted aliases containing spaces — and unqualified columns against a single source table). - Idempotent — re-running it never duplicates edges.
MiniMap
The lineage canvas includes a MiniMap in the corner — a dark glassmorphic overview panel showing the positions of all nodes as indigo dots. This helps with navigation on large lineage graphs with dozens of nodes.
Persistence & Export
- Saved with project — All lineage nodes and edges are saved as part of the
.dsmproject file (stored in thelineageElementsarray) - Documentation Portal — Lineage graphs are included in the HTML Documentation Portal export as interactive Mermaid diagrams
- Git version control — Lineage changes are tracked alongside schema changes when using the Source Control panel
Data Mapping (STTM) PRO
The Data Mapping tab implements a visual Source-to-Target Mapping (STTM) canvas for designing data transformation flows from source systems to target schemas. It provides column-level wiring, 18 built-in transform operations, interactive lineage highlighting, and auto-generated SQL output — all within a drag-and-drop flow canvas.
Layout
The mapping workspace is split into three areas:
- Left sidebar (264px) — Mapping Palette: buttons for adding sources, targets, and transform nodes
- Center canvas (flexible) — Vue Flow canvas for placing and connecting nodes
- Right drawer (resizable) — Transform Properties Drawer: appears when a transform node is selected
Node Types
| Node Type | Color | Badge | Purpose |
|---|---|---|---|
| Source Table | Blue (blue dot indicator + right border) | SOURCE |
Input table — the data you're reading from. Each column has a right-side output handle. |
| Target Table | Indigo (indigo dot indicator + left border) | TARGET |
Output table — the table you're writing to. Each column has a left-side input handle. |
| Transform | Emerald (default), Blue, Purple, Orange, or Red (configurable) | Operation name | Intermediate transformation step between source and target columns. |
Source & Target Table Nodes
Table nodes can be added in two ways:
Manual Creation
- Click "Add Manual Source" or "Add Manual Target" in the Mapping Palette
- A sample table is created with 3 default columns (
idINTEGER,nameVARCHAR,emailVARCHAR) - Sources are positioned at x=50 (left side), targets at x=900 (right side)
Live Database Import
- Click "Connect Live DB" under Sources or Targets in the palette
- The Import Live Database modal opens, scoped to import as either a source or target
- Select tables from the introspection results — each selected table becomes a node with its real columns and data types
- Nodes are stacked vertically with smart spacing based on column count
Engine-Aware Headers
Table node headers are color-coded by database engine:
| Engine | Header Color |
|---|---|
| PostgreSQL | Blue |
| Snowflake | Cyan |
| SQL Server | Red |
| MySQL | Orange |
| BigQuery | Emerald |
| Manual | Gray (default) |
Transform Operations
The Mapping Palette provides 18 built-in transform operations, each producing a compact node that can be wired between source and target columns:
| Operation | Icon | SQL Generated | Expression Field Usage |
|---|---|---|---|
| Direct Map | ⚡ | Pass-through — "table"."column" |
Not used |
| JOIN | ⚡ | INNER/LEFT/RIGHT/FULL/CROSS JOIN |
Join condition (e.g., "a"."id" = "b"."id"). Notes field controls join type. |
| FILTER | ⚡ | WHERE expression |
Filter predicate (e.g., status = 'active') |
| AGGREGATE | ⚡ | COUNT/SUM/AVG/MIN/MAX(...) + auto GROUP BY |
Aggregate function (e.g., SUM, COUNT(*)) |
| CASE | ⚡ | CASE ... END |
CASE body (e.g., WHEN status='A' THEN 'Active') |
| COALESCE | ⚡ | COALESCE(col, fallback) |
Fallback value |
| CAST | 📏 | CAST(col AS type) |
Target type (e.g., INTEGER, DATE) |
| Concat | 🔗 | CONCAT(col1, col2, ...) |
Separator or additional literal |
| Split | ➕ | SPLIT_PART(col, delim, idx) |
Delimiter. Notes field = index position. |
| TRIM | ⚡ | TRIM(col) |
Not used |
| UPPER | ⚡ | UPPER(col) |
Not used |
| LOWER | ⚡ | LOWER(col) |
Not used |
| REPLACE | ⚡ | REPLACE(col, 'old', 'new') |
Comma-separated: old_value, new_value |
| SUBSTRING | ⚡ | SUBSTRING(col, start, length) |
Start position and length (e.g., 1, 10) |
| Hardcode | ⚡ | Literal value (auto-quoted strings, unquoted numbers/keywords) | The literal value |
| GenUUID | ⚡ | GEN_RANDOM_UUID() |
Not used |
| DISTINCT | ⚡ | SELECT DISTINCT |
Not used |
| IFNULL | ⚡ | COALESCE(col, fallback) |
Fallback value |
Column-Level Connections
Connections are made at the individual column level, not at the table level:
- Each source column has a small blue output handle on its right edge
- Each target column has a small indigo input handle on its left edge
- Transform nodes have a left input handle and a right output handle
- Drag from a source column's output handle → through one or more transforms → to a target column's input handle
This creates a complete, traceable column-level lineage from source to target.
Interactive Lineage Highlighting
When you hover over any node or edge on the mapping canvas, the system traces the full data flow path in both directions:
- Upstream tracing — Follows edges backward to find all source columns feeding into the hovered element
- Downstream tracing — Follows edges forward to find all target columns receiving data from it
- Dimming — All nodes and edges not in the active lineage path are dimmed to 20% opacity and desaturated
- Color propagation — The active path adopts the color theme of any transform nodes in the chain (emerald, blue, purple, orange, or red). Column text and handles glow with the active color.
This makes it trivial to answer questions like "where does this target column get its data from?" at a glance.
Edge Rendering
Mapping edges use smooth step paths (rounded corners with 15px border radius) and are rendered with:
- Animated dashed lines (
strokeDasharray: 4 4) with a flowing animation - Color-matched to the connected transform node's theme color
- Selected edges get thicker stroke (2.5px vs 1.5px) and enhanced glow drop-shadow
- Edges not in the active lineage path are dimmed to 20% opacity
Transform Properties Drawer
Click any transform node to open the Transform Properties Drawer (resizable panel sliding in from the right). It provides:
| Field | Description |
|---|---|
| Transformation Type | Read-only display of the operation name (e.g., "CONCAT", "FILTER") |
| Color Theme | 5 color swatches (emerald, blue, purple, orange, red) — click to change the node and edge colors |
| Logic / Expression | Monospace textarea for the SQL expression, cast type, filter predicate, or literal value (context-dependent on operation) |
| Developer Notes | Free-text textarea for rationale, context, or implementation notes |
| Delete Node | Red button to remove the transform node and its edges |
The drawer border, input focus rings, and handle colors all dynamically adapt to the selected color theme.
Mapping Palette (Sidebar)
The left sidebar provides all node creation buttons:
| Section | Actions |
|---|---|
| Sources | Add Manual Source — create a sample source table Connect Live DB — import real schemas from a database connection |
| Targets | Add Manual Target — create a sample target table Connect Live DB — import real schemas from a database connection |
| Transforms | 2-column grid of 18 transform operation buttons (click to add to canvas) |
SQL Generation
The mapping canvas can auto-generate executable SQL by reverse-walking the graph from each target table, tracing through transform nodes to build complete queries. Two SQL modes are supported:
INSERT INTO Mode
Generates INSERT INTO target SELECT ... FROM source statements:
- Column aliases are applied when the transform expression differs from the target column name
- FROM clause uses the source table referenced most often (the "primary source")
- JOIN clauses are auto-generated from JOIN transform nodes — join type is determined from the Notes field (LEFT, RIGHT, FULL, CROSS, or INNER default)
- WHERE clauses are auto-generated from FILTER transform nodes
- GROUP BY clauses are auto-generated when AGGREGATE transforms are present — non-aggregated columns are added automatically
- DISTINCT is applied when any DISTINCT transform is in the path
- Unmapped target columns emit
NULL
CTAS Mode (Create Table As Select)
Generates CREATE TABLE target AS SELECT ... statements. The CREATE prefix is dialect-aware:
| Dialect | CREATE Prefix |
|---|---|
| PostgreSQL, Redshift | CREATE TABLE IF NOT EXISTS |
| Snowflake, DuckDB, Databricks | CREATE OR REPLACE TABLE |
| Others | CREATE TABLE |
Tabular STTM View
In addition to the visual canvas, the mapping workspace includes a Tabular STTM View. This spreadsheet-style interface allows you to view and edit your source-to-target mappings in a dense, data-rich grid.
- Toggle View — Switch between the visual canvas and tabular view using the toggle in the toolbar.
- Coverage Tracking — The header includes a coverage badge (e.g., "42/50 mapped (84%)") giving you a quick visual summary of your progress.
- Spreadsheet Editing — Edit mapping rules, transform logic, and developer notes directly within the grid cells. Changes sync bidirectionally with the canvas nodes.
- Bulk Filtering — A powerful dual-level fuzzy search allows you to filter rows by target table, column, source table, or transformation type.
- CSV Export — Click the CSV button to download a spreadsheet of all mappings, including current status ("Mapped" or "Unmapped").
Markdown Specification Export
The mapping can be exported as a markdown specification document with per-target-table sections. Each section contains a table with:
| Column | Content |
|---|---|
| Target Field | Column name in the target table |
| Data Type | Column data type |
| Transformation Logic | Auto-traced logic chain (e.g., UPPER → CONCAT, with expression and notes inline) |
| Source Field | Fully-qualified source reference (e.g., Source_Table.column_name) |
The trace logic recursively walks upstream through transform chains, so multi-step transformations (e.g., Source → TRIM → UPPER → CONCAT → Target) are fully documented with each step's operation and expression.
Persistence
- Saved with project — All mapping nodes and edges are saved in the
.dsmproject file (stored in themappingElementsarray) - Git version control — Mapping changes are tracked alongside schema changes when using the Source Control panel
Co-Op Mode — Real-Time Collaboration TEAM
Co-Op Mode enables real-time, multi-user collaboration in Wurk.Flow via a lightweight WebSocket relay server. All participants see live schema changes and each other's cursors in real-time.
| Aspect | Detail |
|---|---|
| Architecture | Stateless WebSocket relay (Node.js + ws). No data is stored on the relay — it is a pure
message-forwarding pipe. All project data stays local to each client. |
| Sync Strategy | Per-surface CRDT (Y.js). Schema, lineage, mapping, glossary, requirements and the audit log sync as conflict-free replicated documents, so concurrent edits from multiple peers merge automatically rather than clobbering one another. Freeform requirements text, query tabs and saved queries still use a debounced (500ms) full-state broadcast. All project data stays local to each client; the relay only forwards update messages. |
| Tier | TEAM |
How It Works
- Host creates a room — Opens the Co-Op panel and clicks Host Session. A 6-digit room code is generated.
- Peers join — Other users enter the room code and their display name. Joining adopts the session's project: the joiner's local canvas is replaced by the shared document. If the joiner has local work, Wurk.Flow asks for confirmation first (save or branch beforehand to keep it). The host's full state then syncs to the new peer automatically.
- Live editing — Any participant can edit the schema, add tables, modify columns, etc. Changes are serialized and broadcast to all peers within 500ms.
- Cursor tracking — Figma-style colored cursors with name tags show where each peer is working.
- Leave or disconnect — Peers can leave at any time. If the host disconnects, the relay auto-promotes the next peer to host.
Co-Op Panel UI
The Co-Op panel is a floating overlay accessible from the sidebar. It provides:
- Host Session — Start a room and display the generated room code
- Join Session — Enter a 6-digit code and display name to connect
- Peer List — Shows all connected peers with color indicators
- Leave Session — Disconnect from the room
- Error recovery — Connection failures display inline error banners with retry options
Wire Protocol
All messages are JSON over WebSocket.
Client → Relay
| Type | Payload | Purpose |
|---|---|---|
room:create |
{ displayName } |
Host creates a room |
room:join |
{ roomCode, displayName } |
Guest joins by 6-digit code |
room:leave |
— | Client leaves room |
state:update |
{ payload: string } |
Full project state broadcast |
state:full |
{ payload, targetPeerId } |
Host sends state to specific late-joiner |
cursor:update |
{ payload: { x, y } } |
Viewport-relative cursor position |
Relay → Client
| Type | Payload | Purpose |
|---|---|---|
room:created |
{ roomCode, peerId, peers } |
Room created confirmation |
room:joined |
{ roomCode, peerId, peers } |
Join confirmed |
room:peer-joined |
{ peer, peers } |
New peer notification |
room:peer-left |
{ peerId, peers } |
Peer disconnect |
room:host-changed |
{ newHostId, peers } |
Host promotion (auto) |
room:left |
— | Leave confirmed |
state:update |
{ payload, from, displayName } |
Forwarded state from another peer |
state:full |
{ payload, from } |
Full state for late-joiner |
state:request |
{ requestingPeerId } |
Relay asks host for state |
cursor:update |
{ payload, from, displayName } |
Forwarded cursor position |
error |
{ message } |
Error response |
Relay Server Deployment
The relay server lives in its own repository at
gitlab.com/WurkHaus/wurk.relay.
It is a ~300 LOC Node.js WebSocket server with a single dependency (ws, ~100KB). It can be
deployed as a Docker container (~50MB) or as a standalone Node.js process.
Docker (Recommended)
# Clone the relay repo git clone https://gitlab.com/WurkHaus/wurk.relay.git cd wurk.relay # Build the image docker build -t wurkhaus/wurk-relay . # Run on default port docker run -d -p 9500:9500 --name wurk-relay wurkhaus/wurk-relay # Custom port docker run -d -p 8080:8080 -e PORT=8080 wurkhaus/wurk-relay
Standalone Node.js
git clone https://gitlab.com/WurkHaus/wurk.relay.git cd wurk.relay npm install node server.js # [wurkflow-relay] Listening on port 9500 # Custom port PORT=8080 node server.js
The relay server is purely stateless — it forwards messages between peers and manages room lifecycle. No project data, credentials, or schema information is ever stored on the relay. Deploy it on your LAN for air-gapped collaboration with zero cloud dependency.
Cursor Tracking
- Viewport percentages — Coordinates are transmitted as values between 0.0–1.0 for cross-resolution compatibility
- Throttled to ~20fps — 50ms interval to limit WebSocket traffic
- Deterministic colors — Each peer gets a unique color derived from a hash of their peer ID
- Auto-expire — Stale cursors disappear after 5 seconds of no updates
- Non-interactive — Rendered as SVG arrows with name tags,
pointer-events: none
Echo-Loop Prevention
When a client receives remote state, it must suppress its own outbound broadcast to prevent an infinite loop:
receive remote state
→ update local state
→ trigger Vue watcher
→ broadcast back ← LOOP!
Implementation: A _suppressBroadcast flag is set to true before applying remote
state, then cleared via nextTick() after Vue reactivity flushes. This ensures local watchers see
the
mutation but don't re-broadcast it.
Security Notes
- The relay is a pure message pipe — it never inspects, logs, or stores payload content
- Room codes are 6-digit alphanumeric, generated server-side
- Heartbeat cleanup removes disconnected peers and empty rooms automatically
- Deploy behind a reverse proxy (nginx, Caddy) with TLS for production use
- For air-gapped environments, run the relay on your internal LAN with no external exposure
Audit Log
The Audit Log records project changes across every surface — schema, lineage, mapping, glossary, and requirements — into a single searchable timeline, so you can see what changed, where, and (in a Co-Op session) who made the change.
- All surfaces — captures table/column edits and changes on the other canvases, with a coarser capture for glossary and requirements.
- Attribution in Co-Op — the log syncs across peers through the same CRDT layer as the rest of the project, so each entry is attributed to the peer who made it; remote updates are recorded without double-counting the local mirror.
- Filter & search — narrow the timeline by surface or free text, and export the full log to CSV.
- Baseline resync — the log re-baselines on project load and after major operations (import, restore) so it reflects deltas rather than re-logging the whole project.
Database Connections
Wurk.Flow can connect to live databases for schema introspection, query execution, and data mapping.
Connections are managed via the Connection Modal (File → Import Live Database) and
stored locally using AES-256 encryption bound to your machine's hardware ID.
Connection Modal Layout
The modal is a glassmorphic dialog with the following sections from top to bottom:
- Load Saved Connection — Dropdown to select a previously saved profile (only appears when saved profiles exist)
- Database Engine — Select from 12 supported engines
- Engine-specific fields — Dynamic form fields that change based on the selected engine
- Test result banner — Green success or red error banner after testing
- Profile Name + Save — Name field and "Save Profile" button
- Sample Data Fetch — Row limit selector for data import
- Test Connection / Connect & Introspect — Action buttons
Supported Engines
Wurk.Flow supports 12 database engines, each with specific connection fields and sensible defaults:
| Engine | Required Fields | Default Port | Notes |
|---|---|---|---|
| PostgreSQL | Host, Port, Database, Schema, User, Password | 5432 | Standard libpq protocol |
| MySQL | Host, Port, Database, Schema, User, Password | 3306 | — |
| MariaDB | Host, Port, Database, Schema, User, Password | 3306 | Uses MySQL protocol |
| SQL Server | Host, Port, Database, Schema, User, Password | 1433 | TDS protocol; Schema defaults to "dbo" |
| Azure Synapse | Host, Port, Database, Schema, User, Password | 1433 | Uses SQL Server TDS protocol. Inline guidance with Azure Portal instructions. |
| Amazon Redshift | Host, Port, Database, Schema, User, Password | 5439 | Uses PostgreSQL wire protocol. Inline guidance with AWS Console instructions. |
| CockroachDB | Host, Port, Database, Schema, User, Password | 26257 | Uses PostgreSQL protocol |
| ClickHouse | Host/Endpoint, Port, Database, User, Password | 8443 | HTTPS for Cloud, HTTP 8123 for self-hosted. Inline guidance. Username defaults to "default". |
| Snowflake | Account, Warehouse, Role, Database, Schema, User, Password | — | Account format: xy12345.us-east-2.aws |
| BigQuery | Project ID, Dataset, Service Account JSON Path | — | Uses GCP service account key file authentication |
| Databricks | Server Hostname, HTTP Path, Access Token (PAT), Catalog, Schema | — | Connects to SQL Warehouse. Inline guidance with connection details location. |
| MotherDuck | Access Token, Database, Schema, Region | — | DuckDB Cloud via PostgreSQL wire protocol. Token from app.motherduck.com → Settings. |
Auto-Port Defaults
When you change the engine selection, the port field is automatically updated to the engine's default value:
| Engine | Default Port |
|---|---|
| PostgreSQL | 5432 |
| Redshift | 5439 |
| MySQL / MariaDB | 3306 |
| SQL Server / Synapse | 1433 |
| ClickHouse | 8443 |
| CockroachDB | 26257 |
Engine-Specific Guidance
Several engines display inline guidance callouts (colored banners with icons) to help with configuration:
- MotherDuck — Amber banner explaining DuckDB Cloud connection via PostgreSQL wire protocol, with token creation instructions
- Databricks — Red banner pointing users to SQL Warehouses → Connection Details for the Server Hostname and HTTP Path
- Redshift — Orange banner explaining PostgreSQL wire protocol usage and AWS Console navigation
- ClickHouse — Yellow banner distinguishing Cloud (HTTPS port 8443) from self-hosted (HTTP port 8123)
- Synapse — Blue banner explaining TDS protocol and Azure Portal navigation
MotherDuck Region Selection
MotherDuck connections include a region selector with 5 options:
- US East (N. Virginia) —
us-east-1 - US West (Oregon) —
us-west-2 - EU West (Ireland) —
eu-west-1 - EU Central (Frankfurt) —
eu-central-1 - Asia Pacific (Singapore) —
ap-southeast-1
Test Connection
Click "Test Connection" to verify connectivity before saving or importing:
- The connection is tested via Electron IPC using native database drivers
- Success — Green banner: "CONNECTION SUCCESSFUL!"
- Failure — Red banner: "CONNECTION FAILED" with the full error message
- A spinner appears while the test is in progress
Connect & Introspect
Click "Connect & Introspect" to connect and immediately run schema introspection. This:
- Opens a connection to the database
- Introspects all tables, columns, data types, primary keys, foreign keys, and view definitions
- Opens the Select Tables modal to choose which tables to import onto the canvas
- Creates canvas table nodes with full metadata (columns, types, constraints)
Sample Data Fetch (Row Limit)
The Sample Data Fetch dropdown controls how many rows of real data are fetched per table during introspection:
| Option | Rows per Table |
|---|---|
| OFF (Schema Only) | 0 — no data fetched, only schema metadata |
| 100 rows | 100 |
| 500 rows | 500 |
| 1,000 rows | 1,000 |
| 5,000 rows | 5,000 |
| 10,000 rows | 10,000 |
Fetched sample data is loaded into the DuckDB WASM sandbox so you can query it in the SQL Workspace.
Saved Connection Profiles
Connections can be saved as reusable profiles:
- Save — Enter a profile name (e.g., "PROD DB") and click "Save Profile". If no name is given, a label is auto-generated from the engine and host/account (e.g., "postgres: localhost").
- Load — Select a saved profile from the "Load Saved Connection" dropdown. All fields are populated from the saved profile.
- Update — Loading a profile, modifying fields, and clicking "Save Profile" updates the existing profile (matched by internal ID).
- Delete — Click the red trash icon next to the dropdown to permanently remove a saved profile.
- Start Fresh — Select "-- START FRESH --" to reset all fields for a new connection.
Credential Storage & Security
Connection profiles (including passwords and tokens) are stored in a dedicated electron-store file:
- File —
wurk_flow_connections.jsonin the Electron app data directory - Encryption — AES-256 encryption using the machine's hardware ID
(
node-machine-id) as the encryption key - Hardware-bound — Credentials cannot be decrypted on a different machine, even if the file is copied
- No cloud sync — All credentials remain local to your machine
Where Connections Are Used
Saved connection profiles appear throughout the application:
| Feature | Usage |
|---|---|
| SQL Workspace | Execution target dropdown — run queries against any saved connection |
| Data Mapping (STTM) | "Connect Live DB" buttons — import source or target schemas from a live connection |
| Data Lineage | "⚡ Populate from DB" — auto-generate lineage nodes from a connection's tables and relationships |
| Schema Builder | File → Import Live Database — introspect and import tables to the canvas |
| Schema Drift Detection | Compare canvas schema against a live database connection |
Multi-Schema Introspection & Selective Import
Wurk.Flow provides powerful multi-schema database mapping and selective table importing, designed to handle massive enterprise databases without causing canvas bloat or performance degradation.
By utilizing wildcard schema querying and an interactive grouping selector, you can import only the specific tables required for your data engineering and lineage tasks.
Multi-Schema Introspection (All Schemas Mode)
When configuring a database connection, you can toggle the ALL mode next to the Schema input field. This configures Wurk.Flow to introspect the entire database catalog rather than a single database schema.
Core Introspection Rules
- System Schemas Exclusion: To keep mapping metadata clean, Wurk.Flow automatically filters out system schemas depending on the driver:
- PostgreSQL: Excludes
information_schema,pg_catalog,pg_toast, andpg_internal. - MySQL: Excludes
information_schema,performance_schema,mysql, andsys. - SQL Server: Excludes
sys,INFORMATION_SCHEMA, andguest. - Snowflake: Excludes
INFORMATION_SCHEMA(performs database-levelSHOWoperations). - Databricks: Excludes
information_schema.
- PostgreSQL: Excludes
- Schema Qualification: When All Schemas is active, introspected tables are identified using their fully-qualified namespace:
schema.table(e.g.,sales.orders,finance.transactions).
Selective Table Selector (SelectTablesModal)
Rather than dumping hundreds of introspected database objects onto your canvas at once, Wurk.Flow opens the Selective Table Import interface upon a successful connection.
This interface groups tables into collapsible schema panels and provides granular controls:
Features & Controls
- Schema Grouping: Tables are automatically grouped under their parent schema header (e.g., all
sales.*tables appear under a single "sales" header). - Collapsible Views: You can expand or collapse individual schema groups by clicking the header dropdown arrow. This makes navigating large catalogs with dozens of schemas simple.
- Granular Selection:
- Select All Toggles: Click the global select toggle to select or deselect every table in the database.
- Per-Schema Toggles: Toggle the checkbox in a schema header to select or deselect all tables belonging to that schema.
- Table Checkboxes: Select individual tables by checking their respective boxes within expanded schema lists.
- Selection Indicators: Schema headers display a counter badge showing how many tables are selected within that schema (e.g.,
3 of 15 selected).
Canvas & Downstream Integration
Once you confirm your selection and click Import, the canvas and modeling engines adapt to the multi-schema layout:
1. Visual Node Qualification
Tables on the canvas are drawn with their schema qualifier visible on the node label. This allows you to visually identify and map relationships between tables residing in different database schemas on the same canvas layout.
2. Schema-Aware FK Resolution
The AST and relationship mapping engine (astMapper.ts) resolves Foreign Key mappings using qualified schema keys. An FK from sales.orders targeting customers.profiles is resolved uniquely, avoiding collision risks even if both schemas contain tables with identical names.
3. Dialect DDL Generation
When generating target DDL or migrations, the DDL engine outputs qualified identifiers, using the correct quoting characters for your target dialect:
- PostgreSQL / Snowflake:
"sales"."orders" - MySQL:
`sales`.`orders` - SQL Server:
[sales].[orders]
Importing Data
Import Methods
- Open .dsm file — ⌘+O or File → Open to load a saved Wurk.Flow project
- Import SQL DDL PRO (free in the Web Studio) — Parse
CREATE TABLEstatements from a.sqlfile and populate the canvas with tables, columns, and constraints - Import DBML — free for everyone. Paste DBML (the dbdiagram.io DSL),
open a
.dbmlfile, drop one on the canvas, or double-click a.dbmlfile in your OS (Wurk.Flow registers as a DBML editor). The dialog is a full code editor with syntax highlighting and live, line-numbered warnings from the same parser that runs the import — click a warning to jump to its line. Choose Append (add everything as new) or Merge & update (match against the canvas and patch in place, so re-importing your own export is a clean no-op — new / updated / unchanged shown before you commit). Reachable from the command palette, File → Import DBML (⌘+D), or a canvas drop. See DBML for the syntax reference, a complete example, and the Workspace. - Import from Live Database — Connect to a running database and introspect its schema. Tables, columns, data types, primary keys, foreign keys, and views are imported onto the canvas.
- File Drop (Canvas) — Drag and drop
.csv,.json,.sqlite,.duckdb,.dbml, or.sqlfiles directly onto the Schema Builder canvas. Data files create table nodes with inferred column types; a.dbmldrop opens the import dialog prefilled, and a.sqldrop reverse-engineers itsCREATE TABLEstatements. - API Import PRO — Fetch data from any REST API endpoint and auto-generate table schemas from the response JSON structure
Exporting Data
| Export | Description | Tier |
|---|---|---|
| Save (.dsm) | Save the full project to a .dsm JSON file |
Free |
| Export DDL (.sql) | Export generated DDL as a .sql file. Free exports start with a short "Community
Edition" header comment; Pro exports are clean. |
Free |
| Export DBML (.dbml) | Export the canvas as DBML with your diagram layout embedded in the file (positions,
sizes, colors, subject areas via versioned // wurk:1: comments) — a Wurk.Flow
.dbml rebuilds the whole diagram, layout included. Verified against the official DBML
parser. Never paywalled: your model is always portable. Free exports carry a one-line header
comment. See DBML. |
Free |
| Copy as DBML | Copy the canvas to the clipboard as DBML (DDL panel or File menu) | Free |
| Export Docs (HTML) | Generate a self-contained Documentation Portal with interactive schema diagrams, data dictionary, lineage graphs, and KPI dashboards | PRO |
| Export Markdown | Export a markdown-formatted data dictionary | PRO |
| Export CSV (Query) | Export SQL Workspace query results as CSV | PRO |
| Export CSV (Glossary) | Export the full schema topology as a CSV | PRO |
DBML
DBML (Database Markup Language) is the plain-text schema notation popularised by dbdiagram.io. Wurk.Flow reads and writes it natively. Import, export, and the Workspace are free on every surface, because a model you cannot take with you is not yours; the DBML Workspace goes one step further and lets you edit the live canvas as text.
Wurk.Flow ships its own parser rather than wrapping a library, and it is deliberately forgiving: a malformed
line is skipped and reported with its line number, and everything else still imports. Every file Wurk.Flow
exports is checked in the test suite against the official @dbml/core reference parser, so a
Wurk.Flow .dbml opens cleanly in dbdiagram.io and any other DBML tool.
Syntax reference
What each construct becomes on the canvas. Anything not listed is reported in the import dialog and skipped, never silently dropped. Declaration order does not matter: refs, groups and enum types resolve after the whole document is read.
| Construct | Syntax | On the canvas |
|---|---|---|
| Table | Table users { ... }Table sales.orders { ... }Table "order items" as OI { ... }Table users [headercolor: #3498DB, note: 'People'] { ... } |
A table node. schema. becomes the schema prefix that generated DDL uses (unqualified
tables live in public). Quote names that contain spaces or keywords. An alias can stand in
for the table in refs. headercolor colours the node header; a header note
becomes the description. Only one level of qualification (schema.table) is supported. |
| Column | id integer [pk, increment]email varchar(255) [not null, unique]tags text[]status account_status [note: 'Lifecycle'] |
Types pass through exactly as written, so use your target dialect's spelling; array types keep their
brackets; an Enum name is a valid type. Settings: pk or primary key,
not null, null, unique, increment,
default:, note:, ref:. A check: setting is reported
and not imported yet. |
| Defaults | [default: 0][default: 'active'][default: `now()`] |
Three forms, kept distinct: a bare literal, a quoted string, and a backtick expression. The distinction carries into DDL (strings are quoted, expressions are not) and back out on export. |
| Inline ref | author_id integer [ref: > users.id]author_id integer [ref: > U.id] |
Flags the column as a foreign key and draws the relationship edge. Same operators as a standalone Ref; aliases resolve. Referential actions need the standalone form. |
| Ref | Ref: posts.author_id > users.idRef fk_name: a.x > b.y [delete: cascade, update: no action]Ref fk_name { a.x > b.y }Ref: a.(x, y) > b.(p, q) |
A relationship edge with cardinality and referential actions. Operators: >
many-to-one (the FK is on the left), < one-to-many (FK on the right),
- one-to-one (FK on the second endpoint, per the DBML spec), <>
many-to-many (no FK column is flagged). Actions: cascade, restrict,
set null, set default, no action. A composite ref draws one edge
per column pair. Omit the column and the referenced table's primary key is assumed. dbdiagram's
color and inactive settings are display-only there and ignored here. |
| Indexes | indexes { (order_id, line_no) [pk] email [unique, name: 'idx_email', type: btree] (customer_id, placed_at)} |
A composite entry marked [pk] is the composite primary key, and this is
the only way to declare one. Other entries become indexes with optional unique,
name: and type: (btree, hash, …); an unnamed index gets a generated name. An
index on a column the table does not declare is reported. |
| Table note | Note: 'One row per billing account'Note: ''' several lines''' |
The table description, shown in the properties drawer and in exported documentation. Written inside the table body. |
| Enum | Enum sales.account_status { active frozen "on hold"} |
A purple enum node. Columns typed with the enum name keep it, and the DDL engine writes the type
definition ahead of the tables that use it. Per-value [note:] settings are ignored. |
| TableGroup | TableGroup Sales { sales.customers sales.orders} |
Sets each member's domain, the grouping label the Schema Explorer and the exports use.
A group Note: or [color:] is reported and ignored; a member that matches no
table is reported. |
| Note (standalone) | Note release_plan { ''' Ship the tags table after the backfill finishes. '''} |
A sticky note on the canvas. The block name is optional and is not kept: canvas notes are text-only. |
| Comments & strings | // line comment/* block comment */'single' "double" '''multi-line''' |
Comments are stripped. Triple-quoted strings keep their line breaks, minus common indentation. Windows line endings are fine. |
| Layout sidecar | // wurk:1:{"id":"t_orders","x":400,"y":50} |
Wurk.Flow's own comment channel for canvas positions, sizes, colours and subject areas. Written on export, reattached on import, invisible to every other DBML tool. See Exporting. |
| Recognised, not imported | Project, TablePartial and ~partial injections,
Records, DiagramView, multifile imports, check constraints |
Each is reported with its line number. A table that injects a partial will be missing those columns; paste them into the table instead. |
A complete example
Paste this into Import DBML (⌘+D). It builds a three-table Sales model with an enum, a composite primary key, two indexes, a cascading foreign key, a table group and a sticky note, and it imports with no warnings.
// Sales — a small model that uses every construct Wurk.Flow imports.
Enum sales.account_status {
active
frozen
closed
}
Table sales.customers [headercolor: #3498DB] {
id integer [pk, increment]
email varchar(255) [not null, unique, note: 'Login identity']
status account_status [not null, default: 'active']
created_at timestamp [not null, default: `now()`]
indexes {
email [unique, name: 'idx_customers_email', type: btree]
}
Note: 'One row per billing account'
}
Table sales.orders {
id integer [pk]
customer_id integer [not null, ref: > sales.customers.id]
total decimal(12,2) [default: 0]
placed_at timestamp
indexes {
(customer_id, placed_at) [name: 'idx_orders_customer_placed']
}
}
Table sales."order items" as OI {
order_id integer
line_no integer
sku varchar(64) [not null]
qty integer [default: 1]
indexes {
(order_id, line_no) [pk]
}
}
Ref fk_items_order: OI.order_id > sales.orders.id [delete: cascade, update: no action]
TableGroup Sales {
sales.customers
sales.orders
sales."order items"
}
Note {
'Reindex nightly after the ETL run'
}
The dialog reports 3 tables · 2 relationships · 1 enum · 1 note. On the canvas: order
items shows order_id and line_no both marked PK, orders.customer_id
and order items.order_id carry the FK marker, the three tables sit under the Sales domain
in the Schema Explorer, and the orders → order items edge shows ON DELETE CASCADE in
its drawer. Switch to the DDL tab and PostgreSQL output starts with CREATE SCHEMA IF NOT EXISTS
"sales" and CREATE TYPE "sales"."account_status" AS ENUM (…) ahead of the tables.
The same model, as Wurk.Flow exports it
Move the nodes around, run Export DBML..., and this is the file. Sections come out in a fixed
order (enums, tables, refs, notes, groups, subject areas), alphabetically within each, so two exports of the same
model diff cleanly in Git. The // wurk:1: lines are the layout sidecar; the inline ref has become a
standalone Ref: written from the referenced side with the < operator, which means
the same thing.
// wurk:1:{"id":"enum_1","x":1100,"y":50}
Enum sales.account_status {
active
frozen
closed
}
// wurk:1:{"id":"t_customers","x":50,"y":50}
Table sales.customers [headercolor: #3498DB] {
id integer [pk, increment]
email varchar(255) [not null, unique, note: 'Login identity']
status account_status [not null, default: 'active']
created_at timestamp [not null, default: `now()`]
indexes {
email [unique, name: 'idx_customers_email', type: btree]
}
Note: 'One row per billing account'
}
// wurk:1:{"id":"t_order_items","x":750,"y":50}
Table sales."order items" as OI {
order_id integer
line_no integer
sku varchar(64) [not null]
qty integer [default: 1]
indexes {
(order_id, line_no) [pk]
}
}
// wurk:1:{"id":"t_orders","x":400,"y":50}
Table sales.orders {
id integer [pk]
customer_id integer [not null]
total decimal(12,2) [default: 0]
placed_at timestamp
indexes {
(customer_id, placed_at) [name: 'idx_orders_customer_placed']
}
}
Ref: sales.customers.id < sales.orders.customer_id
Ref: sales.orders.id < sales."order items".order_id [delete: cascade, update: no action]
// wurk:1:{"id":"note_1","x":50,"y":294,"w":200,"h":150,"color":"yellow"}
Note note_16z218a {
'Reindex nightly after the ETL run'
}
TableGroup Sales {
sales.customers
sales."order items"
sales.orders
}
Importing
Free on every tier. Five ways in, all landing in the same dialog:
- ⌘+K → Import DBML..., or File → Import DBML (⌘+D), then paste or type.
- Load .dbml file in the dialog, or Load example for a two-table starter.
- Drop a
.dbmlfile on the Schema Builder canvas: the dialog opens prefilled. - Double-click a
.dbmlfile in your OS: the desktop app registers as a DBML editor (files up to 10 MB).
The editor highlights DBML syntax and lints it live with the same parser that runs the import. Problems are underlined in place, with the severity in the gutter; click one to jump to its line. Above the buttons, a summary counts what will be imported: tables, relationships, enums, notes.
Append or Merge & update
| Mode | Use it when | What happens |
|---|---|---|
| Append | The text is new to this canvas: a dbdiagram export, a colleague's file, a snippet. Selected automatically for text without Wurk.Flow sidecar ids. | Everything is added as new nodes. If some of it is already on the canvas, a Skip N elements already on the canvas checkbox (ticked by default) leaves those out and re-points their relationships at the existing nodes, so importing a file over its own tables never stacks duplicates. |
| Merge & update | The text describes tables you already have: your own export edited in another tool, or a re-import after changes. Selected automatically when the text carries Wurk.Flow sidecar ids. | Each incoming element is matched to the canvas and listed as new, updated or unchanged, with a checkbox to leave any of them out. Matched tables are patched in place. Elements on the canvas that the text no longer mentions are listed as missing and kept, unless you tick Remove missing. |
How matching works (first hit wins): by sidecar id, then by name (schema plus table or enum name, case-insensitive; sticky notes by exact text; subject areas by title). Text that has no sidecar and renames a table therefore reads as one table removed and one added; your own exports rename cleanly because the sidecar id follows the node.
What a merge preserves. The canvas element keeps its identity: position, size, subject-area membership, and every field DBML cannot express (governance, profiling, lineage links, procedures, triggers, view definitions). The text wins for everything DBML owns: name, columns, indexes, description, header colour, alias, schema and group. Columns are matched by name so they keep their ids and the edges bound to them; a column absent from the text is removed from that table, and any relationship it anchored is dropped and counted in the summary. An ungrouped table in the text does not clear a domain you set on the canvas.
Layout. Text with sidecar lines lands exactly where it was. Text without them is laid out on a four-column grid whose rows are as tall as their tallest table, so a wide table never overlaps the row below. Nothing is deleted implicitly, and every import is a single undo step.
Free tier. An import that would exceed the 15-table cap says so up front and still imports in full; adding new tables afterwards is what needs Pro.
Exporting
Free on every tier. ⌘+K → Export DBML... writes
schema.dbml (a save dialog on the desktop, a download in the browser). The DDL tab has
Export .dbml and Copy as DBML buttons for the same output on the clipboard. On
the Free edition the file begins with one comment line naming Wurk.Flow Community Edition; Pro exports have no
header. The toast tells you if anything could not be represented.
The layout sidecar
DBML has no place for canvas positions, so Wurk.Flow writes them as versioned comment lines that its own parser recognises and every other tool ignores. A sidecar line describes the block that follows it; subject areas, which are visual-only, are written as standalone lines at the end of the file.
| Field | Meaning |
|---|---|
id |
The element's stable id: the key Merge & update uses to recognise it again |
x, y |
Canvas position in pixels |
w, h |
Size, for notes and subject areas |
color |
Note or subject-area colour |
parent |
The subject area a table sits inside |
el, title |
Standalone subject-area lines: "el":"subject_area" plus the area's title |
Unknown fields are ignored. A file from a newer Wurk.Flow (wurk:2:) imports with its layout
hints skipped and an info note, never an error. Delete the sidecar lines and the file is plain DBML.
What does not fit in DBML
Views and materialized views (their SQL bodies), domains, sequences, composite types, procedures, triggers,
governance fields, a default expression containing a backtick, and any relationship whose endpoint is not a
table column. All of it stays in the .dsm project; the export lists it in a trailing
// Not representable in DBML comment so nothing vanishes without a trace.
Diagnostics you may see
A deliberately imperfect paste and what the dialog reports for it:
Project shop { database_type: 'PostgreSQL' }
Table posts {
id integer [pk]
title varchar [check: `length(title) > 0`]
~timestamps
}
TablePartial timestamps {
created_at timestamp
}
Records posts(id, title) { 1, 'Hello' }
Line 1 info Project block ignored — project metadata isn't imported Line 5 warning Check constraint on posts.title isn't imported yet Line 6 warning TablePartial injection "~timestamps" references an unknown partial — and partials aren't supported Line 9 warning TablePartial "timestamps" isn't supported — tables injecting it will be missing its columns Line 13 warning Records (sample data) blocks aren't imported
posts still imports with its two columns. Other messages worth knowing:
| Message | What it means |
|---|---|
| Unknown top-level keyword "…" — line skipped | A typo or a construct DBML does not have. The rest of the file imports. |
| Relationship skipped — unknown table / unknown column | A ref names something the text never declares. Check spelling and schema qualification. |
| "auth.users" doesn't match any table in schema "auth" — using "users" | The ref is schema-qualified but the table is not (or the reverse). The unique bare match is used; qualify consistently to silence it. |
| Conflicting duplicate ref (1:N vs N:M) — keeping the first | The same column pair is declared twice with different operators. Delete one. |
| Duplicate ref ignored (already declared) | Info only: an inline ref: and a standalone Ref: describe the same edge.
Harmless; one edge is drawn. |
| Unterminated "Table" block | A missing closing brace. Whatever was parsed before the end still imports. |
| Index on "…" references unknown column "…" | The indexes block names a column the table does not declare. |
| Sidecar version wurk:2 is newer than this build — layout hint ignored | The file came from a newer Wurk.Flow. Content imports; positions fall back to the grid. |
DBML Workspace
The DBML Workspace is a two-way editing surface: a right-side pane on the Schema tab that renders your entire canvas as live DBML. Write your schema as code and apply it, or edit on the canvas and watch the text follow. Open it from the DBML pill in the status bar, the View menu, or ⌘+Shift+D.
- Explicit Apply, no surprises. As you type, nothing syncs to the canvas. The strip under
the editor previews exactly what Apply to canvas will do (N new · N updated · N
unchanged, plus a count of canvas elements the text no longer mentions) before you commit. A typed
Ref:line becomes a real relationship; applies are undoable; and the Workspace never deletes. Elements missing from your text are flagged, not removed; explicit removal lives only in the import dialog. - You own your text. While the editor is idle, the text follows the canvas live (including a Co-Op teammate's edits). The moment you focus or type, the text is yours: canvas changes raise a Canvas changed since this text was generated banner offering Reload from canvas (or Discard edits & reload), and never overwrite what you are writing.
- Merge, in place. Apply matches your text against the canvas the same way Merge & update does: positions, relationships and non-DBML metadata survive, so you edit rather than replace. After an apply the text regenerates from the canvas, which canonicalises your formatting and swaps in real element ids.
- Same lint. The editor underlines problems live with the diagnostics above; Apply is disabled while the text has nothing importable.
Tips and gotchas
- Direction.
orders.customer_id > customers.idputs the foreign key onorders. If the arrow looks reversed on the canvas, the operator is. - Composite keys only exist in an
indexesblock with[pk]; marking two columns[pk]individually is not valid DBML. - Schemas. An unqualified table is
public, andpublic.usersin a ref matches an unqualifiedusers. Mixing qualified and unqualified spellings of the same table works but warns; pick one. - Quoting. Any name that is not
letters_digits_underscoresneeds double quotes, including on export:sales."order items". - Types are yours. Nothing is normalised, so
varchar(255),textandnvarchar(max)all pass through. Pick the dialect you generate for. - Referential actions live on the standalone
Ref:form. An inline[ref: > …]draws the edge; add actions in the edge drawer or in aRef:line. - Renames. To rename a table by text without it reading as delete-plus-add, use the Workspace or edit an export that still has its sidecar ids.
- Partials.
TablePartialis the one common dbdiagram feature not supported yet. Expand the partial's columns into each table before importing.
API Import PRO
The API Import modal lets you fetch data from any REST API endpoint, preview the JSON response, and auto-generate canvas table schemas with full type inference. Nested JSON structures can be automatically normalized into related tables with foreign key relationships.
Opening the Modal
Open via Command Palette → API Import or the menu. The URL input is auto-focused and
pre-filled with a sample endpoint (https://jsonplaceholder.typicode.com/users).
Request Configuration
The modal provides a complete HTTP request builder:
| Field | Description |
|---|---|
| Method | Dropdown: GET, POST, PUT, DELETE |
| URL | Full endpoint URL. Press Enter to send immediately. |
| Bearer Token | Optional password field — auto-prepended as Authorization: Bearer {token} header |
| Headers | Key-value pairs. Starts with Content-Type: application/json. Click "+ Add Header" for
more.
Each header has a remove button. |
| Request Body | JSON textarea — only shown for POST and PUT methods |
CORS Bypass
In desktop mode, requests are dispatched via Electron IPC (api:fetch), which
sends the HTTP request from the Node.js main process. This bypasses all CORS restrictions,
allowing you to hit any API endpoint regardless of its CORS policy. In browser dev mode, requests use the
browser's native fetch() API and may be blocked by CORS.
Response Preview
After sending a request, the response is displayed inline:
- Status badge — Color-coded HTTP status code (green for 2xx/3xx, red for 4xx/5xx)
- Execution time — Millisecond-precision timing
- Record count — Number of top-level records (array length, or 1 for object responses)
- JSON preview — Pretty-printed JSON in a scrollable monospace block (truncated at 5,000 characters with "(truncated)" indicator)
- Error display — Red bordered panel with the error message. API error bodies are still shown if available.
Import Controls
The import panel appears when a valid JSON response is received:
Normalize Toggle
A toggle switch labeled "Normalize nested objects" controls the import mode:
- ON (default) — Nested JSON structures are split into separate related tables with auto-generated foreign keys
- OFF — The entire response is imported as a single flat table
Import as Canvas Table(s)
Click this button to generate canvas table nodes from the response. The table name is auto-derived from the URL path (last path segment, lowercased, special characters replaced with underscores).
Flat Import Mode (Normalize OFF)
When normalization is disabled, or the response has no nested objects:
- A single table node is created with one column per JSON key
- Column types are inferred from the first record's values:
boolean→ BOOLEAN, integer → INTEGER, float → FLOAT, string → VARCHAR - If the first column is named
idor ends with_id, it's automatically marked as a primary key - Response data is loaded into the DuckDB sandbox for querying
Normalized Import Mode (Normalize ON)
When normalization is enabled and the response contains nested structures, the JSON Normalizer engine recursively walks the JSON and produces a multi-table schema:
Type Inference
The normalizer samples up to 50 rows per level and infers SQL types:
| JSON Value | Inferred SQL Type |
|---|---|
| null / undefined | VARCHAR |
| boolean | BOOLEAN |
| integer number | INTEGER |
| decimal number | FLOAT |
ISO date string (YYYY-MM-DD) |
DATE |
ISO timestamp string (YYYY-MM-DDT...) |
TIMESTAMP |
| UUID string | UUID |
| Other string | VARCHAR |
When the same column has conflicting types across rows, the broader type wins (e.g., INTEGER + FLOAT = FLOAT, anything + VARCHAR = VARCHAR).
Normalization Rules
Each JSON key is classified and processed according to these rules:
| JSON Structure | Result | Relationship |
|---|---|---|
| Primitive value (string, number, boolean) | Column on the current table | — |
Nested object ({ "address": { "city": "..." } }) |
Separate child table (parent_address) with FK back to parent |
1:1 |
Array of objects ({ "posts": [{ ... }, ...] }) |
Separate child table (parent_posts) with FK back to parent |
1:N |
Array of scalars ({ "tags": ["a", "b"] }) |
Junction table (parent_tags) with FK + value column |
1:N (junction) |
Generated Table Structure
Every generated table includes:
- Surrogate PK — Auto-generated
idcolumn (INTEGER, primary key) - Foreign key —
parent_table_idcolumn linking back to the parent (for child tables) - Scalar columns — All primitive keys, sorted alphabetically, with camelCase converted to snake_case
FK Edge Generation
For each parent-child relationship, a foreign key edge is created on the canvas linking the parent's
id column to the child's FK column. Child columns are automatically marked with
isForeign: true.
DuckDB Data Loading
After table generation, the actual row data for every table is loaded into the DuckDB WASM sandbox via
loadJsonIntoDuckDB(). This means you can immediately query the imported data in the SQL
Workspace.
Canvas Layout
Generated tables are arranged in a grid layout:
- Starting position: (200, 200)
- Horizontal spacing: 380px between tables
- Wraps to next row after 1200px (approximately 3-4 tables per row)
- Vertical spacing: 300px between rows
Custom YAML Macros
Custom YAML Macros allow you to define declarative governance, schema mutations, and naming policies that execute in bulk against your canvas elements. By utilizing YAML instead of arbitrary javascript execution, Wurk.Flow ensures that macros remain fast, highly reproducible, and fully integrated with the canvas state and undo/redo systems.
Execution Flow
Macros are executed purely on the renderer state, modifying canvas nodes in-place. This operation occurs entirely inside the application sandbox, using the following sequence:
- Invocation: Press Cmd+K or Ctrl+K to open the Command Palette, and select Run Custom Macro (YAML)....
- File Selection: A native Electron file dialog filters for
.yamland.ymlfiles. - Validation: The engine parses the definition and validates the required
actionspayload. - State Snapshotting: The application pushes an undo snapshot (
pushUndoSnapshot) before applying mutations to guarantee reliable Ctrl+Z recovery. - Execution: The schema nodes are filtered and mutated sequentially. A success toast displays the macro name and total affected tables.
Schema Specification
A macro is structured as a declarative YAML document with the following root-level schema:
| Property | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | The title of the macro (displayed in UI toasts). |
description |
String | No | A description of the macro's purpose. |
scope |
String | No | Limits the tables evaluated. Options: tables, views, all (default). |
filter |
Object | No | A set of filters to restrict execution to specific tables or views. |
actions |
Array | Yes | A sequential array of governance mutations to perform. |
The Filter Object
The filter configuration defines criteria that a schema node must meet to undergo mutations. If multiple criteria are defined, they must all match (logical AND).
hasTag(String): Matches tables/views containing this exact string in their classifications/tags list (e.g.phi,pii).hasPattern(String): A standard regular expression matched against the table/view name/label (e.g.,^Customer,.*_staging$).
Action Reference
The actions list contains one or more objects defining the schema changes. Below are the supported action specifications.
1. add-column
Inserts a column definition if a column with the same name (case-insensitive) does not already exist.
type: add-column column: name: string # Required. Name of the column. type: string # Required. Canonical data type (e.g., VARCHAR, TIMESTAMP). isNotNull: boolean # Optional. Default is false. isPrimary: boolean # Optional. Default is false.
2. enforce-naming
Rewrites table labels and column names to adhere to standard naming styles.
type: enforce-naming style: snake_case # Required. Currently 'snake_case' is the active implementation.
Note: This style uses standard regular expressions to replace uppercase boundaries, spaces, and hyphens with underscores, converting all characters to lowercase.
3. add-index
Injects a database index definition onto the table.
type: add-index columns: [string] # Required. Array of columns included in the index. isUnique: boolean # Optional. Default is false. name: string # Optional. Custom index name. Defaults to idx_<table_label>_<index_count>.
4. set-property
Updates metadata properties on matching tables and views.
type: set-property description: string # Optional. Sets a new description comment. tier: string # Optional. Sets the classification tier (e.g., "Tier 1"). prefix: string # Optional. Prepends this prefix to the table name if not already present.
5. rename-column
Renames columns based on a regular expression pattern and replacement template.
type: rename-column pattern: string # Required. Regex string matching the target columns (e.g., ".*_id$"). replacement: string # Required. The string to replace matches (supports standard capture groups/backreferences).
6. add-constraint
Appends a check, unique, or not-null constraint to the table.
type: add-constraint constraintType: string # Required. Must be 'CHECK', 'UNIQUE', or 'NOT NULL'. column: string # Optional. The column name to which the constraint is attached. expression: string # Optional. The SQL check statement or constraint expression.
7. remove-column
Drops columns whose name matches a regular expression pattern.
type: remove-column pattern: string # Required. Regex string matching the columns to drop (e.g., "^temp_").
8. set-tag
Appends a classification tag to the table.
type: set-tag tag: string # Required. The tag name to apply (e.g., "pii").
Practical Examples
Example 1: Standard Audit Logging Injection
This macro targets all base tables (excluding views) to inject auditing fields (created_at, updated_at), enforce standard database snake_case naming conventions, and tag them as standard data warehouse assets.
name: "Enforce DW Standards"
description: "Injects standard audit columns and enforces snake_case across all tables"
scope: "tables"
actions:
- type: enforce-naming
style: snake_case
- type: add-column
column:
name: "created_at"
type: "TIMESTAMP"
isNotNull: true
- type: add-column
column:
name: "updated_at"
type: "TIMESTAMP"
isNotNull: false
- type: set-tag
tag: "dw-managed"
Example 2: Sensitive Data (PHI/PII) Compliance
This macro locates tables with the classification tag phi or pii and enforces an indexing policy. It adds composite indexes and check constraints to verify primary ID formats, while setting their description and operational tier.
name: "HIPAA Data Governance"
description: "Applies indexes and CHECK constraints to tables containing PHI or PII"
filter:
hasTag: "phi"
actions:
- type: set-property
tier: "Tier 1 - Restricted"
description: "Contains protected health information. Monitored for regulatory compliance."
- type: add-index
columns: ["customer_id", "record_date"]
isUnique: true
name: "idx_phi_compliance_lookup"
- type: add-constraint
constraintType: "CHECK"
column: "customer_id"
expression: "customer_id IS NOT NULL AND length(customer_id) >= 8"
Example 3: Bulk Naming Cleanup & Dropping
This macro targets tables beginning with Staging_ to run key cleanup tasks: dropping staging-specific temporary fields, stripping the trailing _id suffix from primary/foreign identifiers to use id as the primary key name, and prepending a standard namespace prefix.
name: "Staging Pipeline Cleanup"
description: "Performs bulk drops and regex renames on incoming staging schemas"
scope: "tables"
filter:
hasPattern: "^Staging_"
actions:
- type: remove-column
pattern: "^temp_.*$"
- type: rename-column
pattern: ".*_id$"
replacement: "id"
- type: set-property
prefix: "stg_"
Source Control (Git)
The Source Control panel provides built-in Git integration for versioning your .dsm project
files.
It supports repository initialization, branching, committing, pushing, pulling, remote origin configuration,
and
commit-level revert — all within a side panel without leaving the application.
Panel Layout
The Source Control panel slides in from the right side of the screen with the following sections:
- Active Branch (Checkout) — Branch selector with dropdown
- Remote Origin (Sync) — Remote URL configuration with credentials
- Commit Message — Textarea with working state indicator
- Action Buttons — Pull, Commit (Local), Commit & Push
- Recent Commits — Scrollable commit history with revert capability
Initializing a Repository
If the project file is not tracked by a Git repository, the panel shows:
- An informational message: "This file is not currently tracked inside a valid Git repository."
- An "Initialize Repository" button that runs
git initin the directory containing the.dsmfile - After initialization, the panel refreshes and shows the full set of controls
Note: The project file must be saved to disk before a repository can be initialized.
Branch Management
Active Branch Display
The current branch name is displayed as a clickable button at the top of the panel. Clicking it opens the branch selector dropdown.
Branch Selector Dropdown
The dropdown shows all local branches with:
- A green dot indicator next to the currently active branch
- Click any branch to check it out — the canvas automatically reloads with the content from that branch
- A "Create new branch" text input at the top — type a name and press Enter or click "GO" to create and switch to a new branch
Branch checkout reloads the project file from the checked-out branch and updates the canvas immediately.
Remote Origin Configuration
The Remote Origin section shows the configured remote URL (with credentials redacted for display). Click "Edit Origin" to configure:
| Field | Description |
|---|---|
| Remote URL | Full Git URL (e.g., https://github.com/user/repo.git) |
| Git Username | Optional — embedded into the URL for authentication |
| Access Token (PAT) | Optional — Personal Access Token, embedded as password in the URL |
Click "Save Config" to apply. Credentials are embedded into the URL in the format
https://user:[email protected]/... for push/pull authentication. The displayed URL always redacts
credentials for security.
Working State Indicators
A status badge next to the "Commit Message" label shows the current working state:
| Badge | Color | Meaning |
|---|---|---|
UNSAVED |
Yellow (pulsing dot) | Canvas has been modified but not saved to disk yet |
CHANGES |
Orange (pulsing dot) | File has been saved but there are uncommitted Git changes |
CLEAN |
Green (steady dot) | All changes are committed — working tree is clean |
Commit Workflow
A required commit message textarea must be filled before committing. Three action buttons are available:
| Button | Color | Action | Git Operations |
|---|---|---|---|
| Pull Remote Changes | Emerald | Fetch and merge remote changes into the current branch | git pull — if the file content changes, the canvas auto-reloads |
| Commit (Local) | Gray | Stage and commit changes locally without pushing | git add + git commit -m "..." |
| Commit & Push | Indigo | Stage, commit, and push to the remote origin in one step | git add + git commit -m "..." + git push |
Both commit buttons are disabled when there are no changes and the commit message is empty.
Recent Commits
A scrollable list of commit history cards, each showing:
| Field | Display |
|---|---|
| Commit hash | Short hash in an indigo monospace badge (e.g., a1b2c3d) |
| Commit message | Truncated message text with tooltip for full message |
| Author | "BY author_name" in uppercase tracking |
| Timestamp | Relative or absolute time |
Revert to Commit
Hovering over any commit card reveals a red "Revert" button. Clicking it:
- Shows a danger confirmation modal: "Revert this document to commit [hash]?" with a warning that uncommitted changes will be irreversibly lost
- If confirmed, checks out the project file at that specific commit hash
- Reloads the canvas with the reverted content
- Refreshes the commit history
Underlying Git Operations
All Git operations are executed via Electron IPC handlers using the system's git CLI:
| IPC Channel | Operation |
|---|---|
git:status |
Check working tree status and current branch |
git:init |
Initialize a new repository |
git:commit |
Stage, commit, and push |
git:commit-local |
Stage and commit locally |
git:pull |
Pull from remote origin |
git:branches |
List all local branches |
git:checkout |
Switch to a branch (creates if new) |
git:checkout-commit |
Revert file to a specific commit hash |
git:history |
Retrieve commit log (hash|msg|author|time format) |
git:get-remote |
Read the current remote origin URL |
git:set-remote |
Set or update the remote origin URL |
Prerequisite: Git must be installed and available on your system PATH. If Git
is
not available, the panel gracefully degrades — no errors are thrown, and the branch indicator is simply
hidden.
Version History
Independent of Git, Wurk.Flow maintains in-project snapshots — lightweight, named save points that capture the complete canvas state. Snapshots enable schema evolution tracking, diff comparison, and one-click rollback without any external version control system.
Opening the Panel
Open via Command Palette → Save Snapshot or View Snapshots. The Version History panel slides in from the right side of the screen (420px wide) with a smooth transition animation. The header displays the total snapshot count.
Creating a Snapshot
The top of the panel contains a snapshot creation form:
| Field | Description | Required |
|---|---|---|
| Snapshot Name | A label for this save point (e.g., "Before normalization") | Yes |
| Description | Optional notes about what this snapshot captures | No |
Click "📸 Save Snapshot" to capture the current state. The button is disabled until a name is entered.
What a Snapshot Captures
Each snapshot stores:
| Field | Content |
|---|---|
id |
Unique identifier (snap_{timestamp}) |
name |
User-provided label |
timestamp |
ISO 8601 creation time |
description |
Optional notes |
tableCount |
Number of table nodes at snapshot time |
columnCount |
Total columns across all tables |
elements |
Deep clone of the entire canvas (tables, edges, subject areas) — runtime metadata like
sourceNode, targetNode, and class are stripped
|
Snapshot List
Saved snapshots are displayed in reverse chronological order (newest first). Each snapshot card shows:
- Name — Bold text, truncated with hover tooltip
- Timestamp — Locale-formatted date/time (e.g., "5/6/2026, 6:30:00 PM")
- Description — Shown below the name if provided
- Statistics — Table count and column count (e.g., "12 tables · 87 columns")
When no snapshots exist, an empty state is shown with a clock icon and the message "Save a snapshot to track schema evolution."
Snapshot Actions
Each snapshot card has three action buttons:
| Button | Color | Action |
|---|---|---|
| Compare | Blue (toggleable) | Generate a diff between this snapshot and the current canvas state. Click again to hide the diff. |
| Restore | Emerald | Replace the current canvas with this snapshot's saved state |
| Delete | Red | Permanently remove this snapshot |
Restore
Restoring a snapshot triggers a danger confirmation modal:
- Title: "Restore Snapshot"
- Message: "Restore snapshot '{name}'?"
- Warning: "This will replace your current canvas with the snapshot state. This action cannot be undone."
If confirmed, the entire elements array is replaced with the deep-cloned snapshot data.
Schema Diff Engine
The Compare action runs a structural diff between the snapshot and the current canvas, detecting:
| Change Type | Detection Logic |
|---|---|
| Added Tables | Tables in current canvas not found in snapshot (by ID) |
| Removed Tables | Tables in snapshot not found in current canvas (by ID) |
| Renamed Tables | Same table ID but different data.label |
| Added Columns | Columns in current table not found in snapshot table (by column ID) |
| Removed Columns | Columns in snapshot table not found in current table (by column ID) |
| Type Changes | Same column ID but different type value |
Migration DDL Generation
From any snapshot diff, Wurk.Flow can generate migration DDL — the SQL statements needed to transform the database from the snapshot state to the current state:
| Change | Generated SQL |
|---|---|
| Removed table | DROP TABLE IF EXISTS "table_name"; |
| Added table | CREATE TABLE "table_name" (...columns...); with types, PRIMARY KEY, and NOT NULL
constraints |
| Added column | ALTER TABLE "table" ADD COLUMN "col" TYPE; |
| Removed column | ALTER TABLE "table" DROP COLUMN "col"; |
| Type change | ALTER TABLE "table" ALTER COLUMN "col" TYPE new_type; |
| Renamed table | ALTER TABLE "old_name" RENAME TO "new_name"; |
The generated DDL includes a header comment with the dialect, generation timestamp, and a note if no differences are detected.
Persistence & Lifecycle
- In-memory singleton — Snapshots are stored in a singleton reactive
refso they persist across component mount/unmount cycles during a session - Project file — Snapshots are serialized into the
snapshotsarray inside the.dsmproject file when saving - Independent of Git — Snapshots are purely in-project and do not interact with or depend on Git version control
Auto-Save & Dirty Indicator
- Auto-save — The project auto-saves every 5 seconds when changes are detected (debounced via content hash comparison)
- Dirty indicator — The status bar shows an unsaved changes indicator (●) when the canvas has been modified since the last save
- Snapshots are not auto-created — they are always explicit, user-initiated save points
File Format (.dsm)
Wurk.Flow projects are saved as .dsm files (Data Schema Model) — plain JSON with the following
top-level structure:
{
"schema": [], // Canvas elements (tables, edges, subject areas)
"lineage": [], // Lineage canvas elements
"mapping": [], // Mapping canvas elements
"requirements": "", // Freeform markdown text
"requirementItems": [], // Structured requirement objects
"workspaceTabs": [], // Query workspace tabs (SQL, notes)
"snapshots": [], // Version history snapshots
}
Files can be version-controlled with Git, diff'd as plain text, and merged manually when needed.
AI Configuration
Wurk.Flow supports AI-powered features including auto-documentation, conceptual schema generation, schema
normalization, column inference, and intelligent suggestions. Configure your AI provider via the AI
Automation Settings modal (Command Palette → AI Configuration).
Settings Modal
The modal contains the following fields:
| Field | Description |
|---|---|
| AI Execution Provider | Radio button grid — select Ollama (Local), OpenAI, Anthropic, Azure OpenAI, Groq, Mistral AI, AWS Bedrock, or Custom (OpenAI-compatible). Bedrock and Custom are desktop-only (see Web Studio). |
| API Chat Target URL | The base endpoint URL. Auto-populated with defaults when switching providers. |
| Inference Model Tag | The model identifier to use (e.g., llama3.2, gpt-4o,
claude-sonnet-4-20250514)
|
| Bearer Auth Strategy (Key) | API key or token. Password field. Hidden for Ollama unless a key has been set. |
Supported Providers
When you switch providers, the URL and model fields auto-populate with sensible defaults:
| Provider | Default URL | Default Model | Auth Required |
|---|---|---|---|
| Ollama (Local) | http://localhost:11434/v1 |
llama3.2 |
No (URL only) |
| OpenAI | https://api.openai.com/v1 |
gpt-4o |
Yes (API key) |
| Anthropic | https://api.anthropic.com |
claude-sonnet-4-20250514 |
Yes (API key via x-api-key header) |
| Azure OpenAI | https://<your-resource>.openai.azure.com/openai/deployments/<deployment> |
gpt-4o |
Yes (API key via api-key header) |
| Groq | https://api.groq.com/openai |
llama-3.3-70b-versatile |
Yes (API key) |
| Mistral AI | https://api.mistral.ai |
mistral-large-latest |
Yes (API key) |
| AWS Bedrock | us-east-1 (region, not URL) |
anthropic.claude-sonnet-4-20250514-v1:0 |
Yes (AWS Access Key ID + Secret + optional Session Token) |
| Custom (OpenAI-compatible) — desktop only, new in 26.9.0 | Any /v1 base URL: OpenRouter, Together, Fireworks, DeepSeek, LM Studio, vLLM, or your
own gateway |
Whatever the endpoint serves | Optional (local endpoints need none) |
All providers except Anthropic and Bedrock share the OpenAI-compatible /chat/completions shape. The
Custom provider (desktop) points that shape at any endpoint you name, with the same
bring-your-own-key posture as every other provider: requests go from your machine straight to the endpoint.
Temperature-strict models. Some models refuse the temperature parameter (Anthropic
requests with extended thinking, OpenAI reasoning models). Since 26.9.0 Wurk.Flow strips the parameter and
retries once when a provider rejects it, for every provider, so there is no per-model compatibility list to
go stale.
AWS Bedrock is the recommended path for enterprise AWS accounts with strict egress policies. Calls go through the AWS SDK with SigV4 signing rather than direct HTTPS, so corporate firewalls that block api.openai.com / api.anthropic.com still allow AI features to work. Supports Claude (anthropic.*), Llama (meta.*), Titan (amazon.titan-*), and Mistral (mistral.*) model families through one connection.
Provider-Specific API Routing
The AI service automatically normalizes the endpoint URL and request format per provider:
- Ollama — URL is cleaned of redundant path segments and routed to
/v1/chat/completions. Noresponse_formatis sent (Ollama doesn't support it). - OpenAI — Appends
/v1/chat/completionsif not already present. UsesAuthorization: Bearer {key}header andresponse_format: { type: "json_object" }for JSON requests. - Anthropic — Routes to
/v1/messages. Usesx-api-keyandanthropic-version: 2023-06-01headers. System prompts are sent as a top-levelsystemstring and user messages as themessagesarray. - Azure OpenAI — Appends
/chat/completions?api-version=2024-02-15-previewto the deployment URL. Usesapi-keyheader instead ofAuthorization. - Groq / Mistral — Same shape as OpenAI; just different base URLs and keys.
- AWS Bedrock — Routes through
@aws-sdk/client-bedrock-runtimewith SigV4 signing — not plain HTTP. The "URL" field holds the AWS region (e.g.us-east-1); the SDK constructs the endpoint internally. Each Bedrock model family has its own request/response shape, handled automatically: Claude uses Anthropic Messages withanthropic_version: "bedrock-2023-05-31"; Llama uses the chat-template prompt format; Titan usestextGenerationConfig; Mistral uses the[INST]template. The SDK is lazy-loaded so non-Bedrock users don't pay the bundle cost.
Configuration Storage
AI settings are saved using a dual-layer approach:
- Primary: electron-store — Settings are saved via
settings:setIPC to the encrypted Electron store (AES-256, hardware-bound). Loaded on mount viasettings:get. - Fallback: localStorage — Also written to
localStoragekeys (ai-provider,ai-url,ai-model,ai-key) for synchronous reads and browser dev mode.
API keys are stored using hardware-bound encryption — they never leave your machine unencrypted.
AI-Powered Features
Once configured, the following AI features are available throughout the application:
| Feature | Trigger | What It Does | Temperature |
|---|---|---|---|
| Generate Table Glossary | Table Properties → "🤖 Generate" button | Creates a business description for the table and concise descriptions for each column based on schema structure | 0.1 |
| Infer Column Types & Tags | Table Properties → "Infer Types" button | Infers SQL data types (INTEGER, VARCHAR, BOOLEAN, DATE, FLOAT) and security classification tags (PII, PCI, PHI, HIPAA, GDPR, Internal) for each column. If the table has no columns, it generates 4-8 probable columns for the entity. | 0.2 |
| Generate Conceptual Schema | Canvas toolbar → "AI Generate" prompt | Takes a business workflow prompt (e.g., "e-commerce platform") and generates a complete entity-relationship schema with 4-6 columns per table and relationship edges | 0.3 |
| Normalize to 3NF | Table right-click → "Normalize (AI)" | Takes a large, flat "BigAssTable" and decomposes it into normalized 3NF entities (dimensions/facts) with proper PKs, FKs, and relationship edges | 0.2 |
| Generate Policy Boilerplate | Data Glossary → Policy section | Generates a standard operating protocol (SOP) for a data policy based on its name and classification tags. Returns markdown text under 150 words. | 0.3 |
| Proofread Requirements | Requirements Workbench → "Proofread" button | Fixes spelling and grammar in business requirements text while preserving meaning, structure, and markdown formatting | 0.2 |
Rate Limiting
A client-side rate limiter prevents rapid-fire API calls:
- Minimum interval: 2 seconds between requests
- If triggered, the error message is: "Rate limited — please wait a moment before sending another AI request."
Retry Logic
Transient failures are automatically retried:
- Max retries: 1 (2 total attempts)
- Backoff: 1.5 seconds × 2attempt (exponential)
- 4xx errors (client/auth errors) are not retried — they are thrown immediately
- 5xx errors and network failures are retried after the backoff delay
JSON Response Parsing
AI responses expected as JSON pass through a multi-stage extraction pipeline:
- Detect the outermost
{...}or[...]boundary - Strip trailing commas before closing brackets
- Attempt
JSON.parse() - Fallback: strip markdown code block fences (
```json...```) and parse the cleaned string
If parsing fails entirely, a descriptive error is thrown with the first 150 characters of the raw response for debugging.
MCP Server PRO
The MCP server lets an AI coding agent — Claude Code, Cursor, Claude Desktop, or any client that speaks the Model Context Protocol — read the model that is open in Wurk.Flow right now and change it. It is a desktop-app feature, it is off until you turn it on, and it listens on this machine only. Nothing leaves the computer: the agent talks to the app over the loopback address, and every request has to carry a token that only exists on your machine.
Turning it on
- Open the Command Palette (Ctrl/Cmd+K) and run Settings: MCP Server (AI agents)...
- Flip Enable MCP server. The status line shows the endpoint, normally
http://127.0.0.1:47831/mcp. Change the port there if something else already uses it. - Pick your client under Connect a client and press Copy. Claude Code gets a one-line
claude mcp addcommand; Cursor gets amcp.jsonblock; Claude Desktop gets aclaude_desktop_config.jsonblock that launches the bundled stdio shim through the Wurk.Flow binary.
The setting is remembered: the server starts with the app on the next launch. Wurk.Flow has to be running for any client to connect, including the Claude Desktop shim.
What an agent can do
| Tool | What it does |
|---|---|
get_project | File, unsaved-changes flag, tier, dialect, and element counts. |
list_tables / get_table | Tables, views, and materialized views; one table in full with columns, constraints, indexes, and relationships. |
find_columns | Find a column by name across every table. |
get_dbml / generate_ddl | The canvas as DBML, or as DDL in any of the 13 dialects. |
get_glossary | Business glossary terms with status, owner, and linked references. |
get_lineage / lineage_impact | The lineage graph, and the downstream columns a change would touch. |
plan_dbml | Dry run: how a DBML document would reconcile against the canvas (new, updated, unchanged, missing). |
apply_dbml | Merge a DBML document into the canvas. Adds and updates; never removes unless the agent explicitly asks. |
Writes go through DBML on purpose. Every model already speaks it, and the merge is the same reconcile the DBML Workspace uses for Apply, so an agent's change lands as one undo step: press Ctrl/Cmd+Z and it is gone. A toast announces each applied change.
Security
- Loopback only. The server binds to
127.0.0.1; it is not reachable from the network. - Token on every request. Generated once per install, stored with the same OS-keychain wrapping as your AI keys. Regenerate invalidates the old one immediately; update your client configs afterwards.
- Browser pages are refused. A request carrying an
Originheader, or a non-loopbackHost, is rejected before the token is even checked, which closes the DNS-rebinding route. - Pro is checked on every call. If the license lapses while the server is up, tools fail closed.
Settings
Settings are persisted using a dual-layer approach:
- Electron Store (desktop) — Encrypted at rest using a hardware-derived key from
node-machine-id - localStorage (fallback) — Used when Electron APIs are unavailable (e.g., during development in a browser)
Settings include: AI provider configuration, MotherDuck token, and database connection profiles.
Licensing
Wurk.Flow has three editions. The names below are the public ones. The in-app status-bar badge and a few messages still call the Free edition COMMUNITY, and Free exports are stamped "Community Edition"; they mean the same thing.
| Edition | Price | Seats | Unlocks |
|---|---|---|---|
| Free | $0, no account needed | — | Modeling up to 15 tables and views, DDL in DuckDB / PostgreSQL / SQLite (Full Build and Seed Data modes), one SQL Workspace tab on the DuckDB sandbox, DBML import and export, DDL export (with a header comment), PNG/PDF diagrams, custom types, the Audit Log |
| Pro | $9.99 / month or $99 / year | 1 machine on the desktop, plus your account in the Web Studio | Everything marked PRO: unlimited tables, all 13 dialects, Migration Diff and Insert Data DDL, live database connections and introspection, unlimited SQL tabs with live and cloud engines, Data Glossary, Data Lineage, Data Mapping, Git source control, version snapshots, Conceptual/Logical views, Auto-Link and Auto-Organize, documentation exports, API import, custom macros, clean exports |
| Team | $39.99 / month or $399 / year | 5 seats | Everything in Pro plus TEAM features: Co-Op live sessions, the Requirements Workbench, and the option to self-host the Co-Op relay |
Plans are bought and managed at flow.wurk.haus. Billing is monthly or annual through Stripe. 4 · Buy & Activate walks through the purchase, the first activation, and moving a key to a new machine.
Desktop: a key bound to one machine
- You paste a license key into the License window (key icon at the bottom of the sidebar). This one step needs an internet connection.
- The license service binds the key to this machine's hardware ID and returns a signed token (ES256). The app stores the token encrypted at rest and verifies its signature locally, in the main process, against a public key compiled into the app. A token whose signature does not verify is refused.
- On every launch, and about once an hour while running, the app sends its hardware ID to the license service. This heartbeat answers with a fresh token reflecting your subscription right now, or with a reason it will not issue one.
- The token is what gates Pro features. A subscription token is valid for 30 days from the moment it was issued, and the heartbeat replaces it long before that.
Offline. After activation the app works fully offline. When the heartbeat cannot reach the service (no network, or the service is down), the app keeps the token it already holds, so you stay Pro until that token's own expiry: 30 days after the last successful check. Free features never need a license or a network connection.
Moving machines. One key, one machine at a time. Click DEACTIVATE / UNBIND MACHINE in the License window, or Revoke Seat on the dashboard if the old machine is gone. The same key then activates on the next machine.
Version years
Releases are numbered by year: every 26.x release belongs to version year 26 (2026). A license carries the version year it was bought in and unlocks every release of that year. Running a release from a newer year (a v27 build on a v26 license) shows RENEWAL REQUIRED: Pro features are locked in that build until you renew, while every v26 release keeps working. There is no calendar grace period at year end. The year gate is about which build you run, not about a date.
What happens when…
| Situation | Desktop entitlement |
|---|---|
| Subscription active (monthly or annual) | Full Pro or Team. Each heartbeat issues a fresh 30-day token. |
| A renewal payment fails | Nothing changes while Stripe retries the card. Full access continues through the retry window; the subscription only ends if Stripe finally gives up. |
| Annual plan cancelled, or simply not renewed | Perpetual fallback. At the first heartbeat after the paid period ends, the service issues a fallback token pinned to your paid version year, and keeps re-issuing it on every heartbeat, at no charge, indefinitely. Every release of that year keeps working. Releases of a later year show RENEWAL REQUIRED until you renew. |
| Monthly plan cancelled or ended | No fallback. Pro features stop when the last issued token expires, within 30 days of the end of the final paid period. Your files and every Free feature are untouched. |
| Refund, chargeback, or payment dispute | The subscription is marked revoked. The next heartbeat wipes the stored token, the app drops to Free with a "License no longer active" notice, and perpetual fallback no longer applies. A dispute decided in Wurk.Haus's favour restores standing. |
| Seat revoked from the dashboard | That machine drops to Free at its next heartbeat; the key is free to activate elsewhere. |
Two things to know about perpetual fallback.
- It needs one online heartbeat after your plan ends to arrive. A machine that has been offline since before the plan ended still holds an ordinary subscription token, which lapses 30 days after its last check; reconnect once and the fallback token replaces it.
- Fallback tokens last about 13 months and renew on every heartbeat, so a machine that is online even occasionally never notices. A machine kept offline longer than that drops to Free until it reconnects once. For a truly air-gapped installation, ask for an offline perpetual key through the contact form.
The "expiring soon" notice in the License window keys off the token currently held. For an active subscription that is always a rolling 30-day token, so the notice is not a sign that your subscription is ending. The dashboard shows your real renewal date.
Team seats
A Team subscription is five seats. Each seat is its own key, listed on the dashboard, and counts as "in use" once a machine has activated it. The owner can revoke any seat at any time. Team features (Co-Op, Requirements) follow the seat. In the Web Studio the owner invites members by email and they sign in with their own account; no key is involved.
Web Studio: an account, not a machine
At app.wurk.haus there is no key and no hardware ID. Sign in with your flow.wurk.haus account; the studio asks the license service whether your organization has an active subscription, at sign-in and every 6 hours. The service applies the same standing rules as the desktop heartbeat, including good-standing fallback for a lapsed annual plan. An unreachable check keeps the last good answer for 24 hours, after which the studio drops to Free until a check succeeds. Web access never consumes a desktop seat. The Web Studio always runs the current release, so version-year pinning has no meaning there.
Command Palette
Press ⌘+K to open the Command Palette — a searchable, keyboard-navigable action launcher for accessing any feature in the application. The palette is the fastest way to perform actions without navigating menus.
Interface
The palette opens as a centered glassmorphic dialog (positioned at 15% from the top of the viewport) with a slide-in animation:
- Search input — Auto-focused with placeholder "TYPE A COMMAND OR SEARCH...". The input is aggressively focused on open (retried at 0ms, 50ms, and 150ms to overcome focus steal).
- ESC badge — Visual reminder that pressing Escape closes the palette
- Action list — Scrollable list (max 350px height) of all matching actions, grouped by category
- "Suggestions" header — Shown when no search query is entered
Keyboard Navigation
| Key | Action |
|---|---|
| ↓ Arrow Down | Move selection to next action |
| ↑ Arrow Up | Move selection to previous action |
| Enter | Execute the selected action |
| Escape | Close the palette |
The selected action auto-scrolls into view. Mouse hover also moves the selection. All keyboard events use capture phase to intercept before Vue Flow's canvas handlers.
Search & Filtering
Type in the search input to filter actions. The filter uses case-insensitive substring matching against the action label. The selection index resets to 0 whenever the search query changes.
Pro Feature Gating
Actions marked with a lock icon (🔒) require a Pro license. Selecting a Pro action without a valid license opens the License Activation modal instead of executing the action. Pro validation uses offline-first license checking.
Registered Actions
The command palette contains 37 actions organized into 9 categories:
Navigation
| Action | Tier | Description |
|---|---|---|
| Go to Schema Builder | Free | Switch to the Schema Builder canvas tab |
| Go to Data Glossary | PRO | Switch to the Data Glossary panel |
| Go to SQL Workspace | Free | Switch to the SQL Workspace tab |
| Go to DDL Engine | Free | Switch to the DDL Generation panel |
| Go to Business Requirements | TEAM | Switch to the Requirements Workbench |
| Go to Data Lineage | PRO | Switch to the Data Lineage canvas |
| Go to Data Mapping (STTM) | PRO | Switch to the Data Mapping canvas |
| Go to Source Control (Git) | PRO | Open the Source Control panel |
| Fit View (Re-center Canvas) | Free | Re-center and zoom the canvas to fit all nodes |
Database
| Action | Tier | Description |
|---|---|---|
| Add Table | Free | Create a new table node on the canvas |
| Add View | Free | Create a new view node on the canvas |
| Add Materialized View | Free | Create a new materialized view node |
| Import Live Database... | PRO | Open the Connection Modal for database introspection |
| Manage Database Connections... | PRO | Open the Connection Modal to manage saved profiles |
| Generate Migration Script... | PRO | Open the Migration DDL generator |
| Detect Schema Drift... | PRO | Compare canvas schema against a live database |
Macro
| Action | Tier | Description |
|---|---|---|
| Inject Soft Deletes (All Tables) | Free | Add is_deleted / deleted_at columns to all tables |
| Generate Missing Primary Keys | Free | Add surrogate id column to tables without a PK |
| Inject Timestamps (All Tables) | Free | Add created_at / updated_at columns to all tables |
| Enforce Naming Standards (Snake Case) | Free | Convert all table and column names to snake_case |
| Auto-Link Relationships | PRO | Auto-detect and create FK edges based on column naming patterns |
| Auto-Organize Canvas Layout | PRO | Automatically arrange table nodes in an optimized grid layout |
| Clean Up Canvas (Dedupe Tables) | Free | Collapse duplicate same-name tables down to one and drop orphaned edges (undoable) — useful after re-importing the same database |
File
| Action | Tier | Description |
|---|---|---|
| New Schema | Free | Create a new blank project |
| Open File... | Free | Open a .dsm project file |
| Save | Free | Save the current project |
| Save As... | Free | Save to a new file path |
| Import SQL DDL... | PRO | Parse .sql files and create tables from DDL |
Export
| Action | Tier | Description |
|---|---|---|
| Export DDL... | Free | Export generated DDL as a .sql file |
| Export Documentation Portal (HTML) | PRO | Generate a self-contained HTML documentation site |
| Export Documentation (Markdown) | PRO | Export a markdown-formatted data dictionary |
DataOps
| Action | Tier | Description |
|---|---|---|
| Fetch API → Table | PRO | Open the API Import modal |
Version Control
| Action | Tier | Description |
|---|---|---|
| Save Schema Snapshot... | PRO | Open the Version History panel to save a snapshot |
| View Version History... | PRO | Open the Version History panel to browse snapshots |
| Git: Checkout Branch... | PRO | Open a prompt to switch Git branches |
Edit
| Action | Tier | Description |
|---|---|---|
| Undo | Free | Undo the last canvas action |
| Redo | Free | Redo the last undone action |
System / Configuration
| Action | Tier | Description |
|---|---|---|
| Configure AI Assistant... | Free | Open the AI Automation Settings modal |
| About Wurk.Flow DataStudio | Free | Show the About modal with version info |
| Submit Feedback... | Free | Open the Feedback modal |
| Keyboard Shortcuts | Free | Show the keyboard shortcuts overlay |
| Quit Application | Free | Quit the application (with unsaved changes confirmation) |
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| ⌘+K | Open Command Palette |
| ⌘+S | Save project |
| ⌘+Shift+S | Save As (new file) |
| ⌘+F | Focus canvas search (Schema tab) |
| ⌘+Z | Undo |
| ⌘+Shift+Z | Redo |
| ⌘+Y | Redo (alternate) |
| ⌘+C | Copy selected node |
| ⌘+V | Paste copied node |
| Delete / Backspace | Delete selected node or edge |
| ? | Show keyboard shortcuts overlay |
Note: On Windows/Linux, replace ⌘ with Ctrl.
Security Architecture
Process Isolation
Wurk.Flow uses Electron's security best practices:
- Context Isolation — The renderer process runs in a sandboxed environment with no direct access to Node.js APIs
- IPC Whitelist — Only explicitly whitelisted IPC channels are exposed via the preload bridge. Any non-whitelisted channel invocation is silently blocked.
- No Remote Module — The deprecated Electron remote module is not used
Data at Rest
- Hardware-Bound Encryption — Sensitive data (API keys, JWT tokens, database passwords) is
encrypted using a key derived from
node-machine-id(hardware-specific identifier) - Electron Store — Settings are stored in an encrypted JSON file in the app data directory
- No Cloud Sync — All project data stays on your local machine unless you explicitly push to a Git remote
Network Access
Everything the desktop app can contact, and when. The browser build's shorter list is in Web Studio.
- License service (Supabase) — Activation when you enter a key; the heartbeat at launch and about hourly while running (hardware ID out, signed token back); and the Feedback form when you submit it. Free users who never activate and never send feedback do not contact it.
- Release feed — Only when you click Check for Updates in the About window. The app never checks in the background, and nothing downloads without a second explicit click.
- Crash reports (Sentry) — Packaged desktop builds only, and only when an error actually occurs. License keys, machine IDs and known AI-key formats are scrubbed before sending; AI keys never leave the encrypted local store.
- Your databases — Direct connections to databases you configure, from your machine to the database. Never proxied.
- AI providers — Optional, off by default: Ollama (local), OpenAI, Anthropic, Azure OpenAI, Groq, Mistral AI, AWS Bedrock (via the AWS SDK), or a Custom OpenAI-compatible endpoint, called directly with your own key only when you invoke an AI feature.
- Co-Op relay TEAM — Only during a live session: the default
wss://relay.wurk.haus, or a relay you self-host on your own network. - MotherDuck — Only if you connect a MotherDuck database.
- CDN — Tailwind CSS and Google Fonts loaded from CDN in exported HTML documents only
Not on the list: analytics of any kind. Wurk.Flow ships no usage tracking, on the desktop or on the web.
HTML Report Generator
The HTML Report Generator exports your entire project — schema architecture, data dictionary, lineage graph, business requirements, DDL preview, and model health — into a single, self-contained HTML file that can be opened in any browser without a server. The output is a fully interactive documentation portal designed for sharing with stakeholders, auditors, and downstream teams.
Invocation
There are three ways to trigger an HTML report export:
| Method | Path |
|---|---|
| Command Palette | ⌘+K → "Export Documentation Portal (HTML)" |
| Menu Bar | Export → Documentation Portal (HTML) |
| Toolbar | Export dropdown → HTML Documentation Portal |
After invocation, a Report Title modal prompts for the project name. The default is derived
from the current .dsm filename. Pressing Cancel aborts the export; submitting proceeds to the
native Save dialog.
Portal Tabs
The generated HTML portal contains 7 navigable tabs, each rendered as a discrete content section with CSS-based show/hide toggling:
| Tab | Content | Key Features |
|---|---|---|
| Summary | Executive overview with KPI cards and health status | 5 KPI cards, dual coverage progress bars (column + table documentation), Subject Area breakdown grid, health status badge |
| Requirements | Business requirements and architectural context | Structured requirements table with priority/status/category badges, linked table references,
acceptance
criteria progress, freeform Markdown rendering via marked.js |
| Schema Architecture | Interactive ER diagrams with conceptual/physical sub-views | HTML5 Canvas renderer with pan/zoom, dot-grid background, color-coded node kinds (table/view/mview), Bézier edge curves, relationship reference table with cardinality badges |
| Data Dictionary | Column-level documentation grouped by Subject Area | Table Index sidebar (TOC), search filter, governance badges (domain, certification, tier, owner, refresh frequency, classifications), profiling statistics, constraint badges (PK/FK/UQ/CHECK), index and trigger listings, stored procedure bodies |
| Data Lineage | End-to-end data flow DAG | Mermaid graph LR diagram, color-coded component specification cards (system, job,
consumer,
apiCall, flatFile), classification and notes display |
| DDL Preview | Auto-generated SQL DDL | Syntax-highlighted DDL output with dialect badge, select-all for easy copy |
| Health | Model validation results | Error/warning severity badges, entity reference, validation failure descriptions, "All Checks Passed" zero-state |
KPI Computation
The Summary tab computes 5 key performance indicators at generation time:
| KPI | Computation | Color Logic |
|---|---|---|
| Tables | elements.filter(type === 'table').length |
Indigo (static) |
| Columns | Sum of all table.data.columns.length |
Cyan (static) |
| Relationships | elements.filter(type === 'relationship').length |
Purple (static) |
| Doc Coverage | Math.round((documentedCols / totalColumns) * 100) |
≥80% = emerald · ≥50% = amber · <50% = red |
| Issues | errorCount + warningCount |
errors > 0 = red · warnings only = amber · 0 = emerald |
Two additional progress bars show Column Documentation Coverage
(documentedCols / totalColumns) and Table Documentation Coverage
(documentedTables / totalTables) with the same color thresholds.
Interactive Canvas Renderer
The Schema Architecture tab uses a custom HTML5 Canvas renderer (not Mermaid) to display the full ER diagram with interactive controls. This is embedded directly in the generated HTML and requires no external dependencies.
| Feature | Implementation |
|---|---|
| Pan | Mouse drag on canvas (mousedown/mousemove/mouseup events) |
| Zoom | Mouse wheel with focal-point zoom (wheel event, non-passive) |
| Fit All | Toolbar button — computes bounding box of all nodes, sets zoom to fit with 60px padding |
| Zoom In/Out | Toolbar buttons with 1.2× / 0.8× scale factor centered on viewport |
| DPR Scaling | window.devicePixelRatio-aware rendering for Retina/HiDPI displays |
| Dot Grid | 24px grid of 0.8px radius dots at 3% white opacity |
| Resize Handling | window.resize event listener recalculates canvas dimensions |
Canvas Node Rendering
| Node Kind | Border Color | Header Color | Badge |
|---|---|---|---|
table |
rgba(99,102,241,0.4) (indigo) |
rgba(99,102,241,0.15) |
— |
view |
rgba(34,211,238,0.4) (cyan) |
rgba(34,211,238,0.15) |
⊡ |
materialized_view |
rgba(251,191,36,0.4) (amber) |
rgba(251,191,36,0.15) |
◈ |
In Conceptual mode, nodes render as 160×40px labels. In Physical mode, nodes expand to 200px width with column listings showing name, type (right-aligned in cyan), and PK indicators (yellow highlight strip with bold "PK" badge).
Canvas Edge Rendering
Relationships are drawn as Bézier curves (bezierCurveTo) with indigo stroke
(rgba(99,102,241,0.35)) and directional arrow heads. Control point offset is calculated as 50% of
the horizontal distance between source and target nodes.
Mermaid Diagram Generation
The generator produces three Mermaid diagram strings:
| Diagram | Type | Used In | Content |
|---|---|---|---|
| Conceptual ER | erDiagram |
Schema tab (fallback) | Entity names only, relationships with cardinality connectors and ON DELETE labels |
| Physical ER | erDiagram |
Schema tab (fallback) | Entity names + column definitions (type, name, PK/FK), relationships with connectors |
| Lineage DAG | graph LR |
Lineage tab | Left-to-right flow graph with tableRef nodes as cylindrical shapes, other nodes as
rectangles |
Cardinality Mapping
| Cardinality | Mermaid Connector | Meaning |
|---|---|---|
1:1 |
||--|| |
One-to-one |
1:N |
||--o{ |
One-to-many (default) |
N:1 |
}o--|| |
Many-to-one |
N:M |
}o--o{ |
Many-to-many |
Data Dictionary: Governance Metadata
Each table card in the Data Dictionary tab renders governance badges from the schema model:
| Badge | Source Field | Color |
|---|---|---|
| Domain | table.data.domain |
Blue |
| Certification | table.data.certification |
Gold = yellow · Deprecated = red · Other = green |
| Tier | table.data.tier |
Tier 1 = red · Tier 2 = yellow · Tier 3 = green |
| Owner | table.data.owner |
Indigo |
| Refresh Frequency | table.data.refreshFrequency |
Cyan |
| Classifications (tags) | table.data.classifications[] |
Red (per tag) |
Column-Level Detail
Each column row includes:
- Constraint Badges — PK (yellow), FK (blue), UQ (emerald), CHECK (pink)
- Classification Tag — Data sensitivity classification (purple badge)
- Description — Column definition text with inline CHECK expression display
- Profiling Statistics — When available: distinct count (◆), null percentage (∅), min (↓), max (↑). Null percentages >50% render in red, >10% in amber.
Additional Table Metadata
If present in the schema model, each table card also renders:
- Indexes — Name, columns, type (BTREE default), unique indicator
- Stored Procedures — Name, language badge, parameter list (direction, name, type), procedure body in a scrollable code block
- Triggers — Name, timing (BEFORE/AFTER), event (INSERT/UPDATE/DELETE), FOR EACH ROW/STATEMENT, optional condition, trigger body
- Implementation Notes — Collapsible
<details>element withtable.data.notes
CDN Dependencies
The generated HTML loads the following external resources at runtime:
| Resource | CDN Source | Purpose |
|---|---|---|
| Tailwind CSS | cdn.tailwindcss.com (with typography plugin) |
Utility-first styling engine for the portal layout |
| Mermaid | cdn.jsdelivr.net/npm/mermaid@10 (ESM) |
Lineage DAG rendering — initialized with dark theme, transparent background |
| marked.js | cdn.jsdelivr.net/npm/marked |
Markdown → HTML rendering for freeform business requirements |
| Google Fonts | fonts.googleapis.com |
Inter (UI text) and JetBrains Mono (code/monospace) |
Note: An internet connection is required to view the generated HTML portal. The CDN resources are not embedded inline. Offline viewing is not supported.
Print Mode
The generated portal includes a @media print stylesheet that:
- Hides header, navigation, footer, and all elements with
.no-print - Makes all tab content visible simultaneously (removes tab toggling)
- Switches background to white and text to dark for print readability
- Removes all shadows, blur effects, and backdrop filters
- Applies
page-break-inside: avoidto content sections
Troubleshooting
macOS: "App is damaged" or Gatekeeper Warning
macOS Gatekeeper may block unsigned or notarization-pending builds. To resolve:
- Open Terminal
- Run:
xattr -cr /Applications/Wurk.Flow.app - Re-launch the application
Alternatively, right-click the app in Finder and select "Open" to bypass the Gatekeeper dialog once.
Database Connection Fails
If connection testing fails in the Connection Modal:
| Symptom | Solution |
|---|---|
| Timeout / no response | Check firewall rules — Wurk.Flow connects directly from your machine via the Electron main process |
| Authentication error | Verify credentials with an external tool (psql, mysql, sqlcmd,
etc.) before retrying |
| SSL/TLS error | Ensure certificates are valid and accessible. For self-signed certs, check the engine's SSL mode settings. |
| Databricks: "thrift" error | Known vulnerability in the @databricks/sql thrift dependency — isolated to the sandboxed
desktop environment and does not affect security |
| MotherDuck: token rejected | MotherDuck tokens can expire. Generate a fresh token from the MotherDuck dashboard. |
| Wrong port | Auto-port defaults are applied per engine (e.g., 5432 for PostgreSQL, 3306 for MySQL). Override in the Port field if your server uses a custom port. |
DuckDB Sandbox Not Loading Data
The DuckDB WASM sandbox auto-scaffolds tables from your canvas schema. If data isn't appearing:
| Issue | Solution |
|---|---|
| Tables not visible in SQL Workspace | Ensure your tables have at least one column defined — empty tables are skipped during scaffold |
| WASM loading error in console | Check the browser DevTools console for DuckDB Init Error messages. May indicate a browser
incompatibility or CDN issue. |
| "Sandbox cleanup issue" warning | Some tables couldn't be dropped during re-sync. Try switching SQL targets and switching back to force a full re-scaffold. |
| Seed data not inserting | Batch inserts may silently skip if data types don't match. Check console for
Seed insert skipped warnings.
|
AI Features Not Working
| Error Message | Cause & Solution |
|---|---|
| "AI Provider is not fully configured" | Open Command Palette → Configure AI Assistant and set your provider, URL, and API key
|
| "AI is not configured — Please set an API key in Settings first" | Cloud providers (OpenAI, Anthropic, Azure OpenAI, Groq, Mistral) require an API key. Ollama only requires the base URL. |
| "Rate limited — please wait a moment" | A 2-second cooldown exists between AI requests. Wait briefly and retry. |
AI API Error (401) or (403) |
Invalid or expired API key. These 4xx errors are not retried. Check your key is correct and has sufficient quota. |
AI API Server Error (5xx) |
The AI provider is experiencing issues. The request will auto-retry once (with 1.5s backoff). If it persists, try again later. |
| "Failed to parse AI response into strict JSON" | The AI model returned malformed JSON. Try a different model (e.g., gpt-4o tends to
produce
cleaner JSON than smaller models). |
License Activation Issues
The checklist in 4 · Buy & Activate covers the common "I paid and it is still locked" cases; the rows below add the rest.
| Problem | Solution |
|---|---|
| Pro features locked after purchase | Paying creates a key on the dashboard; it does not activate anything by itself. Copy the key and activate it in the License window (key icon, bottom of the sidebar). Activation needs a one-time internet connection. |
| "License is already bound to another machine" | The seat is in use elsewhere. Click Revoke Seat on the dashboard, or DEACTIVATE / UNBIND MACHINE on the old machine, then activate again. |
| RENEWAL REQUIRED / "Your license covers v26" | The build you are running belongs to a newer version year than your license. Renew at flow.wurk.haus, or run a release from your paid year, which keeps working (perpetual fallback on annual plans). |
| License lost after an OS reinstall or new hardware | The hardware ID changed, so the seat still points at the old machine. Revoke the seat on the dashboard and activate the same key again. No support transfer is needed. |
| "License verification failed" at launch | The stored token failed its signature check or is corrupted. Re-activate with your key while online; the service issues a fresh, correctly signed token. |
| Pro dropped to Free while offline | The held token reached its 30-day expiry without a successful heartbeat. Connect once; the next heartbeat issues a new token (a subscription token, or the perpetual fallback token if an annual plan has ended). |
| "License no longer active" mid-session | The service revoked the license: a refund, chargeback or dispute is on record, or the seat was revoked from the dashboard. If that is wrong, use the contact form with your Hardware Signature. |
| "License expiring soon" notice | Refers to the current token, which for an active subscription is a rolling 30-day token renewed hourly. It is not your renewal date; the dashboard is. |
| Web Studio: signed in but Free | Sign in with the account that owns or was invited to the subscription. If the check is unreachable, the last good answer holds for 24 hours, then Free until it succeeds. |
Git / Source Control Failures
| Error | Solution |
|---|---|
| Branch indicator missing | Git may not be installed or the project directory isn't a Git repository. Install Git and initialize via the Source Control panel. |
| "Git checkout failed" | You may have uncommitted changes that would be overwritten. Commit or stash changes first. |
| "Push failed" | Check that a remote origin is configured and your credentials (username/PAT) are valid. Tokens expire — regenerate if needed. |
| "Git pull failed" | May indicate merge conflicts. Resolve conflicts manually using an external Git client, then retry. |
| "Parse Error: historical commit file is invalid JSON" | The .dsm file at the target commit is corrupted. Try reverting to a different commit.
|
API Import: CORS Errors
In desktop mode, API requests are proxied through Electron's Node.js process, bypassing CORS
entirely. CORS errors should only occur in browser dev mode (running via
npm run dev without Electron).
- For development, use the packaged Electron app for API testing
- Alternatively, target APIs that include permissive CORS headers
(
Access-Control-Allow-Origin: *)
Large Schema Performance
For schemas with 300+ tables, canvas performance may degrade. Recommended optimizations:
| Strategy | Effect |
|---|---|
| Use Subject Areas | Visually partition your canvas into logical sections |
| Hide inactive Subject Areas | Toggle visibility via the eye icon to reduce rendering load |
| Switch to Conceptual mode | Reduces rendering complexity by hiding column-level detail |
| Close unused workspace tabs | Reduces memory usage from idle SQL editors |
File Import Errors
| Format | Common Issue | Solution |
|---|---|---|
| SQL DDL | "No valid CREATE TABLE statements detected" | Ensure the file contains standard CREATE TABLE DDL. Proprietary extensions or stored
procedures are not parsed. |
| SQL DDL | "Failed to parse SQL" | The DDL parser expects standard ANSI SQL. Remove vendor-specific syntax before importing. |
| CSV | "CSV Inference Failed" | The file may have inconsistent column counts or encoding issues. Ensure UTF-8 encoding and consistent delimiters. |
| JSON | "JSON file must contain an object or array of objects" | Top-level scalars or arrays of primitives are not supported. Wrap data in an object. |
| JSON | "JSON file contains an empty array" | At least one record is needed for schema inference. |
| .dsm | "Invalid Schema File" | The .dsm file is corrupted or not valid JSON. Try opening a backup or previous Git
commit.
|
MotherDuck Connection Issues
- "MotherDuck introspection failed" — The WASM client couldn't connect to MotherDuck. Verify your token is valid and that you have internet access.
- DB Map not loading — MotherDuck metadata fetch is non-blocking. Check the console for
MotherDuck DB Map fetch failedwarnings. - Query timeout — MotherDuck WASM connections have a separate lifecycle from DuckDB local. If queries hang, try disconnecting and reconnecting.
Wurk.Flow v26.10.0 · Built by Wurk.Haus · wurk.haus