Registering your app
One declare module block tells Chelone your resources, permissions and message catalog. Every typed source, hook and component reads it.
Chelone cannot know your app's resources, permissions or messages on its own.
Register, in packages/core/src/resource/register.ts, is the one place you
say. It is an empty interface that your app augments, and every Registered*
type reads what you put on it.
Registering is optional. Without it each of those types falls back to a loose
shape and nothing breaks. With it, a misspelt resource name or source stops
compiling.
The registration
One file defines the resources and augments Register:
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, const englishMessages: {
readonly "chelone.page.list": "{name}";
readonly "chelone.page.create": "Create {name}";
readonly "chelone.page.edit": "{name} {recordRepresentation}";
readonly "chelone.page.show": "{name} {recordRepresentation}";
readonly "chelone.page.dashboard": "Dashboard";
readonly "chelone.page.error": "Something went wrong";
readonly "chelone.page.not_found": "Not Found";
readonly "chelone.page.loading": "Loading";
readonly "chelone.page.empty": "No {name} yet.";
readonly "chelone.page.access_denied": "Access denied";
readonly "chelone.page.authentication_error": "Authentication error";
... 360 more ...;
readonly "chelone.validation.required": "Required";
}
The English catalog an `<Admin>` with no `i18n` prop reads from, and the base
an app extends: spread it, then add or override entries.
Every key is `chelone.<group>.<leaf>`: the ported shadcn components used to
translate the react-admin `ra.*` keys they were ported from, and those keys
were moved under `chelone.*` with their group names kept (`page`, `action`,
`navigation`). Chelone's i18n never throws on a missing key (it returns the
key or a `_` fallback), so a partial catalog shows raw
`chelone.page.list`-style keys.
Interpolation is single-brace `{name}` (not react-admin's `%{name}`). Plural
strings are `{ one, other }` objects keyed by Intl.PluralRules categories
(react-admin's `a |||| b` form).
`defaultMessages` (the `chelone.*` keys the core controllers emit) and
`mapMessages` (the maps add-on's labels) sit underneath, and no entry here
repeats one of them. Where a moved key landed on a controller key that says
something different, the component's own key keeps the group and names the
role: `access_denied_detail`, `authentication_error_detail`,
`loading_detail` and `http_error_label` sit beside the controller's
`access_denied`, `authentication_error`, `loading` and `http_error`.englishMessages } from "@chelone/core";
import { import zz } from "zod";
export const const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources = defineResources<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}>(resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
} & Checked<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}>): {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
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: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
} & {
references?: {} | undefined;
}
comments: { schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>
schema: import zz.function object<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}>(shape?: {
id: z.ZodNumber;
post_id: 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;
post_id: z.ZodNumber;
}, z.core.$strip>
object({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), post_id: z.ZodNumberpost_id: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() }) },
});
declare module "@chelone/core" {
interface Register {
Register.resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources: typeof const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources;
Register.permissions: string[]permissions: string[];
Register.messages: {
readonly "chelone.page.list": "{name}";
readonly "chelone.page.create": "Create {name}";
readonly "chelone.page.edit": "{name} {recordRepresentation}";
readonly "chelone.page.show": "{name} {recordRepresentation}";
readonly "chelone.page.dashboard": "Dashboard";
readonly "chelone.page.error": "Something went wrong";
readonly "chelone.page.not_found": "Not Found";
readonly "chelone.page.loading": "Loading";
readonly "chelone.page.empty": "No {name} yet.";
readonly "chelone.page.access_denied": "Access denied";
readonly "chelone.page.authentication_error": "Authentication error";
... 360 more ...;
readonly "chelone.validation.required": "Required";
}
messages: typeof const englishMessages: {
readonly "chelone.page.list": "{name}";
readonly "chelone.page.create": "Create {name}";
readonly "chelone.page.edit": "{name} {recordRepresentation}";
readonly "chelone.page.show": "{name} {recordRepresentation}";
readonly "chelone.page.dashboard": "Dashboard";
readonly "chelone.page.error": "Something went wrong";
readonly "chelone.page.not_found": "Not Found";
readonly "chelone.page.loading": "Loading";
readonly "chelone.page.empty": "No {name} yet.";
readonly "chelone.page.access_denied": "Access denied";
readonly "chelone.page.authentication_error": "Authentication error";
... 360 more ...;
readonly "chelone.validation.required": "Required";
}
The English catalog an `<Admin>` with no `i18n` prop reads from, and the base
an app extends: spread it, then add or override entries.
Every key is `chelone.<group>.<leaf>`: the ported shadcn components used to
translate the react-admin `ra.*` keys they were ported from, and those keys
were moved under `chelone.*` with their group names kept (`page`, `action`,
`navigation`). Chelone's i18n never throws on a missing key (it returns the
key or a `_` fallback), so a partial catalog shows raw
`chelone.page.list`-style keys.
Interpolation is single-brace `{name}` (not react-admin's `%{name}`). Plural
strings are `{ one, other }` objects keyed by Intl.PluralRules categories
(react-admin's `a |||| b` form).
`defaultMessages` (the `chelone.*` keys the core controllers emit) and
`mapMessages` (the maps add-on's labels) sit underneath, and no entry here
repeats one of them. Where a moved key landed on a controller key that says
something different, the component's own key keeps the group and names the
role: `access_denied_detail`, `authentication_error_detail`,
`loading_detail` and `http_error_label` sit beside the controller's
`access_denied`, `authentication_error`, `loading` and `http_error`.englishMessages;
}
}Hand resources to <Admin resources={resources}> in React and to
<Admin :registry="resources"> in Vue. The augmentation is what the compiler
reads, and the map is what the app runs.
Once registered, the names are a union:
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 RegisteredResourceName = "posts" | "comments"The registered resource names, or `string` when nothing is registered.RegisteredResourceName } 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: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources = defineResources<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}>(resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
} & Checked<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}>): {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
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: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
} & {
references?: {} | undefined;
}
comments: { schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>
schema: import zz.function object<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}>(shape?: {
id: z.ZodNumber;
post_id: 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;
post_id: z.ZodNumber;
}, z.core.$strip>
object({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), post_id: z.ZodNumberpost_id: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() }) },
});
declare module "@chelone/core" {
interface Register {
Register.resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources: typeof const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources;
}
}
type type Name = "posts" | "comments"Name = type RegisteredResourceName = "posts" | "comments"The registered resource names, or `string` when nothing is registered.RegisteredResourceName;
A record type is inferred from the resource's schema:
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 RegisteredRecord<TName extends RegisteredResourceName> = {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
}[TName] extends {
schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type of a registered resource, or
{@link
LooseRecord
}
when it has no schema.RegisteredRecord } 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>;
};
}
resources = defineResources<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
}>(resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
} & Checked<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
}>): {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
}
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() }) },
});
declare module "@chelone/core" {
interface Register {
Register.resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
}
resources: typeof const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
}
resources;
}
}
type type Post = {
id: number;
title: string;
}
Post = type RegisteredRecord<TName extends RegisteredResourceName> = {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
}[TName] extends {
schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type of a registered resource, or
{@link
LooseRecord
}
when it has no schema.RegisteredRecord<"posts">;
A name the registry lacks is an error:
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 RegisteredRecord<TName extends RegisteredResourceName> = {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}[TName] extends {
schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type of a registered resource, or
{@link
LooseRecord
}
when it has no schema.RegisteredRecord } 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: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources = defineResources<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}>(resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
} & Checked<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}>): {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
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: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
} & {
references?: {} | undefined;
}
comments: { schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>
schema: import zz.function object<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}>(shape?: {
id: z.ZodNumber;
post_id: 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;
post_id: z.ZodNumber;
}, z.core.$strip>
object({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), post_id: z.ZodNumberpost_id: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() }) },
});
declare module "@chelone/core" {
interface Register {
Register.resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources: typeof const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}
resources;
}
}
type type Missing = ResourceRecord<Identifier> & {
[key: string]: unknown;
}
Missing = type RegisteredRecord<TName extends RegisteredResourceName> = {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
post_id: z.ZodNumber;
}, z.core.$strip>;
};
}[TName] extends {
schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type of a registered resource, or
{@link
LooseRecord
}
when it has no schema.RegisteredRecord<"commnets">;What each key types
| Key | Read by | Types |
|---|---|---|
resources | RegisteredResources, RegisteredResourceName, RegisteredRecord | <Resource name>, every source, and createResourceComponents |
permissions | RegisteredPermissions | AuthProvider, usePermissions and the <Admin> function child in React; usePermissions in Vue |
messages | RegisteredMessages | useTranslate: its keys and their {placeholders} |
literalMessages | RegisteredLiteralMessages and HasLiteralMessages | translate, which returns a key's text instead of string when the value is true |
literalMessages: true sits beside messages on Register. It is off by
default, because a union of literal text is not a string you can reassign.
Where it goes
Put the block in one file the TypeScript program includes. The file needs a
top-level import or export: without one it is a script, and declare module
replaces the module instead of augmenting it.
The augmentation is global to the program. Every file reads it, and a second
interface Register in another file merges with the first, so register each key
once.
Without it
A key you leave off keeps its fallback:
| Key | Falls back to |
|---|---|
resources | ResourceMap, so a resource name is a string and a record is LooseRecord |
permissions | unknown |
messages | MessageCatalog, so any key is accepted |
literalMessages | off, so translate returns string |
See When types widen for the cases where a registered app still falls back, such as a resource with no schema.
Slots an adapter fills
ResourceSlots, in packages/core/src/resource/registry-store.ts, is a second
augmentation point. Core does not know what a menu icon or a view is, because
that depends on the framework. Each adapter augments ResourceSlots with an
icon, a representation and a component, so an app writes none of them.
They resolve ResourceIcon, ResourceRepresentation and ResourceComponent
per adapter.
React declares:
declare module "@chelone/core" {
interface ResourceSlots {
icon: ComponentType;
representation: RecordRepresentation;
component: ResourceViewChild;
}
}Vue declares:
declare module "@chelone/core" {
interface ResourceSlots {
icon: Component;
representation: RecordRepresentation;
component: Component;
}
}Import an adapter and the slot resolves to its type:
import "@chelone/react";
import type { type ResourceIcon = ComponentClass<{}, any> | FunctionComponent<{}>A resource's menu icon: the adapter's icon type, or `unknown` when none is declared.ResourceIcon } from "@chelone/core";
type type Icon = ComponentClass<{}, any> | FunctionComponent<{}>Icon = type ResourceIcon = ComponentClass<{}, any> | FunctionComponent<{}>A resource's menu icon: the adapter's icon type, or `unknown` when none is declared.ResourceIcon;
A program that imports both adapters sees both augmentations. They declare the
same properties with different types, so TypeScript keeps one declaration per
property. Keep one adapter in a program. With neither, each of the three types
is unknown.
Pages that use the registration
- Resource registry and codegen
for the registry as a runtime value and
chelone-supabase gen. - Typed components for
createResourceComponents. - Typed hooks for the hooks that read the registry.
- Translations and permissions
for
messages,literalMessagesandpermissionsin use. - Translation and Security for the runtime side of the same keys.
- Supabase for the file the bin writes.