Resource registry and codegen

defineResources builds the resource map, RecordOf reads a record type out of it, and chelone-supabase writes the map from your database.

The resource map is one object, keyed by resource name. Every record type in a Chelone app comes out of it, so it is the first thing to get right. This page covers how defineResources builds the map, what a resource can declare, how a record type is read back, and how the Supabase codegen writes the map for you. Registering your app covers the next step, telling the rest of the program about the map.

The registry is core's, so everything here is the same in @chelone/react and @chelone/vue. Only the way the map reaches <Admin> differs, and Handing the map to the app shows both.

Defining resources

defineResources is an identity function with a const type parameter. It returns the object you pass, and the const keeps what you wrote exact: each resource name stays a literal such as "posts", not string, and each schema's output type survives.

import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
} from "@chelone/core";
import { import zz } from "zod"; const
const postSchema: z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
postSchema
= import zz.
function object<{
    id: z.ZodNumber;
    title: z.ZodString;
}>(shape?: {
    id: z.ZodNumber;
    title: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() });
export const
const resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
resources
=
defineResources<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}>(resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
} & Checked<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}>): {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
({
posts: {
    readonly schema: z.ZodObject<{
        id: z.ZodNumber;
        title: z.ZodString;
    }, z.core.$strip>;
} & {
    references?: {} | undefined;
}
posts
: {
schema: z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
schema
:
const postSchema: z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
postSchema
},
comments: {
    references?: {} | undefined;
}
comments
: {},
}); type
type Names = "posts" | "comments"
Names
= keyof typeof
const resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
resources
;

The map's type is ResourceMap, { [name: string]: ResourceConfig }. defineResources accepts any ResourceMap and returns the narrower type of the object you wrote, which is what the rest of the types read.

ResourceConfig

A resource's config has two optional parts.

  • schema is any Standard Schema. The examples here use Zod, and the types are tested with Zod 3 and Zod 4. The schema's output type becomes the resource's record type. The same schema validates the forms of the resource, see Resource.
  • references maps a field name to the name of the resource it points at. A reference is declared here and never inferred from a name suffix, so a field called author_id points nowhere until you write references: { author_id: "authors" }.
import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
} from "@chelone/core";
import { import zz } from "zod"; export const
const resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "users";
        };
    };
    readonly users: {};
}
resources
=
defineResources<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "users";
        };
    };
    readonly users: {};
}>(resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "users";
        };
    };
    readonly users: {};
} & Checked<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "users";
        };
    };
    readonly users: {};
}>): {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "users";
        };
    };
    readonly users: {};
}
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
({
posts: {
    readonly schema: z.ZodObject<{
        id: z.ZodNumber;
        authorId: z.ZodNumber;
        title: z.ZodString;
    }, z.core.$strip>;
    readonly references: {
        readonly authorId: "users";
    };
} & {
    references?: {
        readonly authorId: "posts" | "users";
    } | undefined;
}
posts
: {
schema: z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
schema
: import zz.
function object<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
    title: z.ZodString;
}>(shape?: {
    id: z.ZodNumber;
    authorId: z.ZodNumber;
    title: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), authorId: z.ZodNumberauthorId: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }),
references: {
    readonly authorId: "users";
} & {
    readonly authorId: "posts" | "users";
}
references
: { authorId: "users"authorId: "users" },
},
users: {
    references?: {} | undefined;
}
users
: {},
});

Record types

RecordOf<TResources, TName> reads the record type of one resource out of a map. With a schema, it is the schema's output type.

import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
, type
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
} from "@chelone/core";
import { import zz } from "zod"; const
const resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
resources
=
defineResources<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}>(resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
} & Checked<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}>): {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
({
posts: {
    readonly schema: z.ZodObject<{
        id: z.ZodNumber;
        title: z.ZodString;
    }, z.core.$strip>;
} & {
    references?: {} | undefined;
}
posts
: {
schema: z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
schema
: import zz.
function object<{
    id: z.ZodNumber;
    title: z.ZodString;
}>(shape?: {
    id: z.ZodNumber;
    title: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }) },
comments: {
    references?: {} | undefined;
}
comments
: {},
}); type
type Post = {
    id: number;
    title: string;
}
Post
=
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
<typeof
const resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
resources
, "posts">;

A resource with no schema has no output type to read, so its record is LooseRecord: a record with an id and any other string key holding unknown. The hover below shows LooseRecord expanded.

import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
, type
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
} from "@chelone/core";
import { import zz } from "zod"; const
const resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
resources
=
defineResources<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}>(resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
} & Checked<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}>): {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
({
posts: {
    readonly schema: z.ZodObject<{
        id: z.ZodNumber;
        title: z.ZodString;
    }, z.core.$strip>;
} & {
    references?: {} | undefined;
}
posts
: {
schema: z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
schema
: import zz.
function object<{
    id: z.ZodNumber;
    title: z.ZodString;
}>(shape?: {
    id: z.ZodNumber;
    title: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    title: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }) },
comments: {
    references?: {} | undefined;
}
comments
: {},
}); type
type Comment = ResourceRecord<Identifier> & {
    [key: string]: unknown;
}
Comment
=
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
<typeof
const resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            title: z.ZodString;
        }, z.core.$strip>;
    };
    readonly comments: {};
}
resources
, "comments">;

Once the map is registered, RegisteredRecord<"posts"> is the same lookup against the registered map, with no map to pass. It is covered on Registering your app.

Checked references

defineResources checks each references entry against the map it is part of.

  • A key must be a top-level field of the resource's record. A misspelled key is an error on the key.
  • A value must be the name of a resource in the same map.
import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
} from "@chelone/core";
import { import zz } from "zod"; const
const postSchema: z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
postSchema
= import zz.
function object<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}>(shape?: {
    id: z.ZodNumber;
    authorId: z.ZodNumber;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), authorId: z.ZodNumberauthorId: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() });
defineResources<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorid: "users";
        };
    };
    readonly users: {};
}>(resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorid: "users";
        };
    };
    readonly users: {};
} & Checked<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorid: "users";
        };
    };
    readonly users: {};
}>): {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorid: "users";
        };
    };
    readonly users: {};
}
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
({
posts: {
    readonly schema: z.ZodObject<{
        id: z.ZodNumber;
        authorId: z.ZodNumber;
    }, z.core.$strip>;
    readonly references: {
        readonly authorid: "users";
    };
} & {
    references?: {
        readonly authorid: never;
    } | undefined;
}
posts
: {
schema: z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
schema
:
const postSchema: z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
postSchema
,
references: {
    readonly authorid: "users";
} & {
    readonly authorid: never;
}
references
: { authorid: "users" },
Type 'string' is not assignable to type 'never'.
},
users: {
    references?: {} | undefined;
}
users
: {},
});
import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
} from "@chelone/core";
import { import zz } from "zod"; const
const postSchema: z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
postSchema
= import zz.
function object<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}>(shape?: {
    id: z.ZodNumber;
    authorId: z.ZodNumber;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), authorId: z.ZodNumberauthorId: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() });
defineResources<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "writers";
        };
    };
    readonly users: {};
}>(resources: {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "writers";
        };
    };
    readonly users: {};
} & Checked<{
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "writers";
        };
    };
    readonly users: {};
}>): {
    readonly posts: {
        readonly schema: z.ZodObject<{
            id: z.ZodNumber;
            authorId: z.ZodNumber;
        }, z.core.$strip>;
        readonly references: {
            readonly authorId: "writers";
        };
    };
    readonly users: {};
}
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
({
posts: {
    readonly schema: z.ZodObject<{
        id: z.ZodNumber;
        authorId: z.ZodNumber;
    }, z.core.$strip>;
    readonly references: {
        readonly authorId: "writers";
    };
} & {
    references?: {
        readonly authorId: "posts" | "users";
    } | undefined;
}
posts
: {
schema: z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
schema
:
const postSchema: z.ZodObject<{
    id: z.ZodNumber;
    authorId: z.ZodNumber;
}, z.core.$strip>
postSchema
,
references: neverreferences: { authorId: "writers" },
Type 'string' is not assignable to type 'never'.
},
users: {
    references?: {} | undefined;
}
users
: {},
});

A resource with no schema has no fields to check against, so it accepts any key. Its values are still checked.

Build the map as one literal object

The checks read the object literal you pass. Build the map some other way and they have nothing to read. An array of entries passed through Object.fromEntries has a type with string keys, so every record type collapses to LooseRecord and no reference is checked. Nothing reports it, since the result is still a valid ResourceMap.

// Every record is LooseRecord, and "writers" passes.
const resources = defineResources(
  Object.fromEntries([
    ["posts", { schema: postSchema, references: { authorId: "writers" } }],
    ["users", {}],
  ]),
);

Write the keys out, as above. A map you need to assemble from parts can spread other literal objects, since a spread of a literal keeps its keys.

RecordResources

A view that is generic over its record type has no map to hand a hook that is generic over a map. RecordResources<TRecord> is a map in which every resource has TRecord as its record, so the view passes it in and the hook sees TRecord.

import type { 
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
,
type RecordResources<TRecord> = {
    [name: string]: {
        schema: StandardSchemaV1<unknown, TRecord>;
    };
}
A resource map whose every resource has `TRecord` as its record. A view generic over its record passes it to a hook that is generic over a map, so `RecordOf<RecordResources<TRecord>, string>` is `TRecord`.
RecordResources
} from "@chelone/core";
interface Post { Post.id: numberid: number; Post.title: stringtitle: string; } type
type Back = Post
Back
=
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
<
type RecordResources<TRecord> = {
    [name: string]: {
        schema: StandardSchemaV1<unknown, TRecord>;
    };
}
A resource map whose every resource has `TRecord` as its record. A view generic over its record passes it to a hook that is generic over a map, so `RecordOf<RecordResources<TRecord>, string>` is `TRecord`.
RecordResources
<Post>, string>;

ApiKeyInput in @chelone/react uses it this way. It is generic over TRecord, so it calls useUpdate<RecordResources<TRecord>, string>(resource) and the mutation's data and previousData are typed as TRecord.

Handing the map to the app

Pass the map to <Admin>. React takes it as resources, and Vue as registry. The prop gives the app the schemas at run time, which is how forms validate and the guessers read fields. It is separate from the types, which come from the Register augmentation on Registering your app.

src/App.tsx
import { Admin, Resource } from "@chelone/react";
import { dataProvider } from "./data-provider";
import { resources } from "./resources";
 
export const App = () => (
  <Admin dataProvider={dataProvider} resources={resources}>
    <Resource name="posts" />
  </Admin>
);
src/App.vue
<script setup lang="ts">
import { Admin, Resource } from "@chelone/vue";
import { dataProvider } from "./data-provider";
import { resources } from "./resources";
</script>
 
<template>
  <Admin :data-provider="dataProvider" :registry="resources">
    <Resource name="posts" />
  </Admin>
</template>

A <Resource> takes its name from the map. See Resource for what the element declares.

Generating the registry

A Supabase project already describes its tables, so the chelone-supabase bin writes the map from the types file the Supabase CLI makes. It writes two files to the directory you choose:

  • schemas.ts holds one Zod schema per table and view, written by supazod.
  • resources.ts holds a defineResources map of those schemas, with each foreign key as a references entry, and the Register augmentation.
src/resources/resources.ts
export const resources = defineResources({
  authors: { schema: publicAuthorsRowSchema },
  posts: {
    schema: publicPostsRowSchema,
    references: { author_id: "authors" },
  },
});
 
declare module "@chelone/core" {
  interface Register {
    resources: typeof resources;
  }
}

The command, its flags and what it reads are on Supabase. Three things about the result matter for types:

  • The generated resources.ts already carries the resources entry on Register, so an app adds none.
  • The bin overwrites both files each run. Put permissions and messages on Register in a file of your own, since an edit to resources.ts is lost on the next run.
  • A second resources entry on Register with a different type fails. Interface members must agree, so a hand-written resources beside the generated one is a type error.
src/register.ts
import type { messages } from "./messages";
 
declare module "@chelone/core" {
  interface Register {
    messages: typeof messages;
    permissions: string[];
  }
}