Computed columns and server validation
BifrostQL can expose table-level virtual fields without changing the database schema. The same module surface also supports server-side mutation validation.
SQL computed columns
Section titled “SQL computed columns”Use computed-sql table metadata for simple cross-platform expressions. Each entry is:
fieldName:GraphQlType:expressionReference physical columns with {column} placeholders. BifrostQL resolves placeholders through the table schema and quotes them with the active SQL dialect.
dbo.orders { computed-sql: totalWithTax:Float:({subtotal} + {tax})}totalWithTax is emitted as a selectable GraphQL field and projected in SQL as an expression. SQL computed fields are read-only; they are not added to mutation inputs.
Provider computed columns
Section titled “Provider computed columns”Use computed-plugin table metadata for .NET/provider-backed fields, including enrichment from remote APIs.
dbo.orders { computed-plugin: shippingEstimate:String:shipping-api:depends=Id,destination_zip}Register an IComputedColumnProvider whose Name matches the metadata provider name:
public sealed class ShippingEstimateProvider : IComputedColumnProvider{ public string Name => "shipping-api";
public async ValueTask<object?> ComputeAsync( ComputedColumnContext context, CancellationToken cancellationToken = default) { var id = context.Row["Id"]; var zip = context.Row["destination_zip"]; // Call a remote service or local dependency here. return "2 business days"; }}Provider fields are computed after the database query returns. If no depends= list is supplied, BifrostQL projects the table primary key columns so the provider has row identity.
File folder columns
Section titled “File folder columns”Use file-folder table metadata to expose a storage folder as a read-only JSON column. This is useful for CMS, DAM, and other blob-backed content models where the database row owns a folder of files.
dbo.pages { storage: bucket:/srv/cms;provider:local file-folder: assets:JSON:local:folder=assets/{Id},depends=Id,recursive=false}The emitted assets field returns file/folder entries with name, key, isFolder, size, lastModified, contentType, and url fields. The folder template can reference projected row values with {ColumnName} placeholders.
Built-in providers:
local/file-folder-local— lists folders from the local filesystem storage bucket.s3/file-folder-s3— lists objects and common prefixes from S3 or S3-compatible storage.
You can configure the folder column inline:
dbo.assets { file-folder: files:JSON:s3:folder=tenant/{tenant_id}/assets,depends=tenant_id,bucket=my-bucket,region=us-east-1,prefix=prod}Or use table/database storage metadata as the default bucket config and keep the folder column focused on the row-specific path.
Server validation
Section titled “Server validation”Server-side validation runs by default on every insert and update mutation: any validation metadata you declare is enforced, with no enable flag required.
dbo.contacts.name { required: true }dbo.contacts.age { min: 18 }dbo.contacts.email { pattern: ^[^@]+@[^@]+\.[^@]+$ pattern-message: Email must be valid.}Supported built-in rules are required, min, max, minlength, maxlength,
step, pattern, pattern-message, and input-type (email/url). Patterns
are anchored as a full-string match (like the HTML5 pattern attribute), schema
VARCHAR(n) lengths are enforced as maxlength, and a pathological pattern is
bounded so it cannot hang a mutation.
To turn validation off for a table or column, set server-validation to an
off value (off, false, disabled, none, no, 0):
dbo.imports { server-validation: off } # whole tabledbo.contacts.legacy_blob { server-validation: off } # single columnFor custom validation, use validation-plugin with registered IServerValidationProvider implementations:
dbo.contacts { validation-plugin: custom-contact-rules }public sealed class ContactRules : IServerValidationProvider{ public string Name => "custom-contact-rules";
public async ValueTask<IReadOnlyList<string>> ValidateAsync( ServerValidationContext context, CancellationToken cancellationToken = default) { // Return zero or more error messages. Any error aborts the mutation. // Async lets you call a database or external policy service here. return Array.Empty<string>(); }}Validation runs inside the mutation pipeline, so it applies to top-level and nested (tree-sync) writes alike. The same declarative rules (required, min, pattern, …) are derived once and exposed to generated client forms, keeping browser and server validation in lockstep. For the full hook surface — before-commit veto hooks, custom transformers, and DI registration — see Extending BifrostQL.