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>): TIdentity 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.
schemais 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.referencesmaps 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 calledauthor_idpoints nowhere until you writereferences: { author_id: "authors" }.
import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): TIdentity 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>): TIdentity 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>): TIdentity 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>): TIdentity 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" }, },
users: {
references?: {} | undefined;
}
users: {},
});import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): TIdentity 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" }, },
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 = PostBack = 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.
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>
);<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.tsholds one Zod schema per table and view, written bysupazod.resources.tsholds adefineResourcesmap of those schemas, with each foreign key as areferencesentry, and theRegisteraugmentation.
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.tsalready carries theresourcesentry onRegister, so an app adds none. - The bin overwrites both files each run. Put
permissionsandmessagesonRegisterin a file of your own, since an edit toresources.tsis lost on the next run. - A second
resourcesentry onRegisterwith a different type fails. Interface members must agree, so a hand-writtenresourcesbeside the generated one is a type error.
import type { messages } from "./messages";
declare module "@chelone/core" {
interface Register {
messages: typeof messages;
permissions: string[];
}
}