Savvi Studio

Resource Model

A resource is any entity in the graph: a user, team, document, predicate, template definition, or module-installed object. All resources share a single table and are distinguished by their kind.

Table Schema

CREATE TABLE graph.resource (
    id          BIGINT       PRIMARY KEY DEFAULT ids.gen_resource_id(),
    kind        LTREE        NOT NULL,
    external_id TEXT,
    data        JSONB        NOT NULL DEFAULT '{}',
    creator_id  BIGINT       REFERENCES graph.resource (id) ON DELETE SET NULL,
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ  NOT NULL DEFAULT now(),
    deleted_at  TIMESTAMPTZ,
    CONSTRAINT unique_resource UNIQUE (kind, external_id)
);

Columns

id — Snowflake ID

Every resource gets a BIGINT primary key generated by ids.gen_resource_id(), a PostgreSQL function that produces Snowflake-compatible IDs. These are time-ordered, globally unique, and safe for distributed ID generation. System bootstrap reserves low ID ranges for well-known resources (see Reserved IDs).

kind — Operational Classifier

kind is the single canonical classifier for a resource. It is an ltree (dot-separated hierarchical path) that associates the resource with the module that installed it and determines operational dispatch and storage behavior.

Examples:

  • graph.resource — a resource type definition
  • graph.predicate_policy — a permission policy
  • core.sdo.Person — a schema.org Person type
  • teams.member — a team membership predicate

The ltree grammar constrains segments to lowercase alphanumeric characters and underscores. Module namespaces map directly to kind prefixes — a module with namespace: teams installs resources with kind values under the teams.* subtree.

The legacy type_namespace column alias has been fully retired from the codebase. kind is the only classifier.

external_id — Human-Readable Identifier

An optional text identifier for looking up resources by name within a kind. The UNIQUE (kind, external_id) constraint guarantees that each (kind, external_id) pair is unique. This is the primary mechanism for idempotent resource creation: re-inserting a resource with the same kind and external_id can be handled via ON CONFLICT.

data — JSONB Payload

Flexible JSON data whose structure depends on the resource's kind and the application code. Defaults to '{}'::jsonb. JSONB indexing (@> containment, GIN indexes) enables efficient querying within the data payload.

creator_id — Audit Trail

References the resource that created this one (typically a user resource). Immutable after creation. Set to NULL on creator deletion (ON DELETE SET NULL).

Timestamps

  • created_at — Immutable creation timestamp
  • updated_at — Updated on modification
  • deleted_at — Soft-delete marker; NULL when active

Common Patterns

Idempotent Creation

INSERT INTO graph.resource (kind, external_id, data)
VALUES ('teams.team', 'engineering', '{"name": "Engineering"}')
ON CONFLICT (kind, external_id) DO UPDATE SET
    data = EXCLUDED.data,
    updated_at = now();

Query by Kind Subtree

-- All resources installed by the 'teams' module
SELECT * FROM graph.resource WHERE kind <@ 'teams';

-- All schema.org types
SELECT * FROM graph.resource WHERE kind <@ 'core.sdo';

Lookup by External ID

SELECT * FROM graph.resource
WHERE kind = 'auth.user' AND external_id = 'alice';

TypeScript Mapping

The graph-model package provides typed resource schemas:

import { resourceSchema } from '@savvi-studio/graph-model';

Key mapped fields: id (bigint), kind (string), externalId (string | null), data (any), creatorId (bigint | null).

See Also