Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .agents/skills/constructive-data-modeling.zip
Binary file not shown.
33 changes: 32 additions & 1 deletion .agents/skills/constructive-data-modeling/SKILL.md
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down
84 changes: 84 additions & 0 deletions .agents/skills/constructive-data-modeling/references/views.md
Original file line number Diff line number Diff line change
@@ -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`).