Full registry example
A registry for a simple schema using two tables 'forums' and 'messages' might be look something like the following; please refer to the codec, resource (including executor) and relation documentation for details on the specifics.
Remember: you can auto-generate this.
import { sql } from "pg-sql2";
import { context, object } from "grafast";
import {
  PgExecutor,
  TYPES,
  pgResourceOptions,
  makeRegistry,
  makeRegistryBuilder,
  recordCodec,
} from "@dataplan/pg";
// The executor is responsible for talking to the database. If you have
// multiple databases, you will have multiple executors (one per database).
const executor = new PgExecutor({
  name: "default",
  context() {
    return object({ withPgClient: context().get("withPgClient") });
  },
});
// Represents the type of the 'forums' table:
const forumsCodec = recordCodec({
  name: "forums",
  identifier: sql`forums`,
  attributes: {
    id: {
      codec: TYPES.uuid,
      notNull: true,
      hasDefault: true,
    },
    name: {
      codec: TYPES.citext,
      notNull: true,
    },
  },
});
// Represents the 'forums' table, including knowledge of its primary key:
const forumsResourceOptions = pgResourceOptions({
  name: "forums",
  executor,
  codec: forumsCodec,
  from: sql`forums`,
  uniques: [{ attributes: ["id"], isPrimary: true }],
});
// Represents the type of the 'messages' table:
const messagesCodec = recordCodec({
  name: "messages",
  identifier: sql`messages`,
  attributes: {
    id: {
      codec: TYPES.int,
      notNull: true,
      hasDefault: true,
    },
    forum_id: {
      codec: TYPES.int,
      notNull: true,
    },
    message: {
      codec: TYPES.text,
      notNull: true,
    },
  },
});
// Represents the 'messages' table:
const messagesResourceOptions = pgResourceOptions({
  name: "messages",
  executor,
  codec: messagesCodec,
  from: sql`messages`,
  uniques: [{ isPrimary: true, attributes: ["id"] }],
});
// The builder tracks all the types so you end up with a strongly-typed registry
const builder = makeRegistryBuilder()
  // First add our codecs
  .addCodec(forumsCodec)
  .addCodec(messagesCodec)
  // Then add our resources
  .addResource(forumsResourceOptions)
  .addResource(messagesResourceOptions)
  // A message relates to a single forum:
  .addRelation(messagesCodec, "forum", forumsResourceOptions, {
    localAttributes: ["forum_id"],
    remoteAttributes: ["id"],
    isUnique: true,
  })
  // A forum can have many messages:
  .addRelation(forumsCodec, "messages", messagesResourceOptions, {
    localAttributes: ["id"],
    remoteAttributes: ["forum_id"],
    // The foreign key reference is defined on 'messages', so we're the one
    // that's referenced by a foreign key
    isReferencee: true,
  });
// Finally build the registry:
const registry = makeRegistry(builder.getRegistryConfig());
Example schema
Given the above registry, you might create a schema with plans something like this:
const { forums, messages } = registry.pgResources;
const typeDefs = /* GraphQL */ `
  type Query {
    forumById(id: Int!): Forum
  }
  type Forum {
    id: Int!
    name: String!
    messages: [Message!]!
  }
  type Message {
    id: Int!
    message: String!
    forum: Forum!
  }
`;
const objects = {
  Query: {
    plans: {
      forumById(_, { $id }) {
        return forums.get({ id: $id });
      },
    },
  },
  Forum: {
    plans: {
      messages($forum) {
        return messages.find({ forum_id: $forum.get("id") });
        // OR: return $forum.manyRelation("messages");
      },
    },
  },
  Message: {
    plans: {
      forum($message) {
        return forums.get({ id: $message.get("forum_id") });
        // OR: return $message.singleRelation("forum");
      },
    },
  },
};
import { makeGrafastSchema } from "grafast";
const schema = makeGrafastSchema({ typeDefs, objects });
note
Although this simple ORM-like appearance looks like it would trigger multiple
SQL statements, in most cases Grafast and @dataplan/pg working in concert
will result in the plan being analyzed and the requests being automatically
combined via joins and/or subqueries to produce a highly efficient SQL query
(TODO: test this works.)