diff --git a/.agents/skills/constructive-data-modeling.zip b/.agents/skills/constructive-data-modeling.zip index 354059c..655d710 100644 Binary files a/.agents/skills/constructive-data-modeling.zip and b/.agents/skills/constructive-data-modeling.zip differ diff --git a/.agents/skills/constructive-data-modeling/SKILL.md b/.agents/skills/constructive-data-modeling/SKILL.md index dd0ed00..23652e1 100644 --- a/.agents/skills/constructive-data-modeling/SKILL.md +++ b/.agents/skills/constructive-data-modeling/SKILL.md @@ -1,6 +1,6 @@ --- name: constructive-data-modeling -description: "Tables, fields, relations, constraints, indexes, enums, and database provisioning via the type-safe SDK. Use when asked to 'create a table', 'add a field', 'add a column', 'create a relation', 'add a constraint', 'add an index', 'create a foreign key', 'add a primary key', 'add a unique constraint', 'define field types', 'provision a database', 'create an enum', 'api_required', 'temporal table', 'application-time temporal', 'WITHOUT OVERLAPS', 'temporal foreign key', 'WITH PERIOD', 'period column', 'ON DELETE SET NULL (col)', 'ON DELETE SET DEFAULT (col)', 'FK column-list referential action', 'delete_set_field_ids', 'deferrable constraint', 'DEFERRABLE', 'INITIALLY DEFERRED', 'deferred constraint', 'isDeferrable', 'initiallyDeferred', 'exclusion constraint', 'EXCLUDE USING', 'EXCLUDE constraint', 'no overlap', 'no_overlap', 'gist exclusion', 'exclusionConstraint', 'identity column', 'GENERATED ALWAYS AS IDENTITY', 'GENERATED BY DEFAULT AS IDENTITY', 'auto-increment column', 'identityGeneration', 'sequence options', or when working with metaschema_public operations." +description: "Tables, fields, relations, constraints, indexes, enums, views, and database provisioning via the type-safe SDK. Use when asked to 'create a table', 'add a field', 'add a column', 'create a relation', 'add a constraint', 'add an index', 'create a foreign key', 'add a primary key', 'add a unique constraint', 'define field types', 'provision a database', 'create an enum', 'create a view', 'security_invoker', 'security_barrier', 'WITH CHECK OPTION', 'check_option', 'api_required', 'temporal table', 'application-time temporal', 'WITHOUT OVERLAPS', 'temporal foreign key', 'WITH PERIOD', 'period column', 'ON DELETE SET NULL (col)', 'ON DELETE SET DEFAULT (col)', 'FK column-list referential action', 'delete_set_field_ids', 'deferrable constraint', 'DEFERRABLE', 'INITIALLY DEFERRED', 'deferred constraint', 'isDeferrable', 'initiallyDeferred', 'exclusion constraint', 'EXCLUDE USING', 'EXCLUDE constraint', 'no overlap', 'no_overlap', 'gist exclusion', 'exclusionConstraint', 'identity column', 'GENERATED ALWAYS AS IDENTITY', 'GENERATED BY DEFAULT AS IDENTITY', 'auto-increment column', 'identityGeneration', 'sequence options', or when working with metaschema_public operations." metadata: author: constructive-io version: "1.0.0" @@ -16,6 +16,7 @@ Use this skill when: - Creating tables, fields, relations, constraints, or indexes via the SDK - Provisioning databases with module selection - Defining enum types +- Creating views and setting view options (`security_invoker`, `security_barrier`, `WITH [LOCAL|CASCADED] CHECK OPTION`) - Configuring field validation (regexp, min, max) - Adding identity columns (`GENERATED ALWAYS / BY DEFAULT AS IDENTITY`) with sequence options - Setting `api_required` on nullable FK columns @@ -336,6 +337,35 @@ Both default to `null`, so existing simple/advanced indexes are unchanged. See [indexes.md](./references/indexes.md) for the full matrix, the expression-AST escape hatch, and combining predicates with expressions. +## Views + +Create views via `db.view.create`; `viewType` selects the view body (`View*` node +type). Three optional options control PostgreSQL storage attributes and update +semantics: `securityInvoker` (default `true`), `securityBarrier` (default +`false`), and `checkOption` (`null | 'local' | 'cascaded'`). + +```typescript +await db.view.create({ + data: { + databaseId, + schemaId, + name: 'owners_view', + viewType: 'ViewTableProjection', + tableId: ownersTableId, + data: { source_schema: 'app_public', source_table: 'owners' }, + securityBarrier: true, + checkOption: 'cascaded', + isReadOnly: false, + }, + select: { id: true }, +}).execute(); +// → CREATE VIEW app_public.owners_view +// WITH (security_invoker = true, security_barrier = true) AS +// SELECT ... WITH CASCADED CHECK OPTION +``` + +See [views.md](./references/views.md) for all view options and generated SQL. + ## `api_required` (Required API Fields) For nullable FK columns that should be required at the GraphQL API level: @@ -357,6 +387,7 @@ await db.field.update({ | [field-types.md](./references/field-types.md) | Complete field type reference | | [identity-columns.md](./references/identity-columns.md) | Identity columns — `GENERATED ALWAYS / BY DEFAULT AS IDENTITY` + sequence options (`identityGeneration` / `identityOptions`) | | [provisioning.md](./references/provisioning.md) | Full database provisioning flow | +| [views.md](./references/views.md) | View creation + options (`securityInvoker`, `securityBarrier`, `WITH [LOCAL\|CASCADED] CHECK OPTION`) | ## Cross-References diff --git a/.agents/skills/constructive-data-modeling/references/views.md b/.agents/skills/constructive-data-modeling/references/views.md new file mode 100644 index 0000000..d7bbf02 --- /dev/null +++ b/.agents/skills/constructive-data-modeling/references/views.md @@ -0,0 +1,84 @@ +# Views + +Views are declared through the SDK ORM against the metaschema via `db.view.create`; +each row compiles to a PostgreSQL `CREATE VIEW`. The view body is chosen by +`viewType` (a `View*` node type — see +[`constructive-blueprints`](../../constructive-blueprints/references/node-type-registry.md) +for the catalog: `ViewTableProjection`, `ViewJoinedTables`, `ViewAggregated`, +`ViewFilteredTable`, `ViewComposite`). + +```typescript +await db.view.create({ + data: { + databaseId, + schemaId, + name: 'active_projects', + viewType: 'ViewTableProjection', + tableId: projectsTableId, + data: { source_schema: 'app_public', source_table: 'projects' }, + isReadOnly: true, + }, + select: { id: true }, +}).execute(); +``` + +## View options + +Three editable options control the view's PostgreSQL storage attributes and +update semantics. All are optional and default to the values below, so existing +views are unchanged. + +| Option | Type | Default | Generates | +|--------|------|---------|-----------| +| `securityInvoker` | Boolean | `true` | `WITH (security_invoker = true)` — RLS/permission checks run as the querying user (PG15+) | +| `securityBarrier` | Boolean | `false` | `WITH (security_barrier = true)` — prevents leaky operators/functions from seeing rows the view filters out | +| `checkOption` | String | `null` | `WITH [LOCAL\|CASCADED] CHECK OPTION` — rejects inserts/updates through the view that would produce rows the view can't see | + +`checkOption` accepts only `null`, `'local'`, or `'cascaded'`; any other value is +rejected. `'local'` checks the current view's predicate only; `'cascaded'` also +checks the predicates of every underlying view. + +When both flags and a check option are set, they combine into a single +statement: + +```typescript +await db.view.create({ + data: { + databaseId, + schemaId, + name: 'owners_view', + viewType: 'ViewTableProjection', + tableId: ownersTableId, + data: { source_schema: 'app_public', source_table: 'owners' }, + securityInvoker: true, + securityBarrier: true, + checkOption: 'cascaded', + isReadOnly: false, + }, + select: { id: true }, +}).execute(); +// → CREATE VIEW app_public.owners_view +// WITH (security_invoker = true, security_barrier = true) AS +// SELECT ... WITH CASCADED CHECK OPTION +``` + +`securityInvoker` / `securityBarrier` become `pg_class.reloptions` +(`security_invoker=true`, `security_barrier=true`); `checkOption` maps to +`reloptions` `check_option=local` / `check_option=cascaded`. + +## Updating options + +The options are editable on an existing view: + +```typescript +await db.view.update({ + where: { id: viewId }, + data: { securityBarrier: true, checkOption: 'local' }, + select: { id: true }, +}).execute(); +``` + +Set `checkOption: null` to drop the check option again. + +> Only `null | 'local' | 'cascaded'` are valid for `checkOption`. A check option +> is meaningful only on updatable views (`isReadOnly: false`).