When Types Widen
Every fallback that keeps an app compiling before and after registering, what each one gives up, and what the checks cost an editor.
Registering your app is what turns a
source from a plain string into a checked path. This page is the other half:
the places where a checked type falls back to a loose one, what each fallback
gives up, and what the whole scheme costs an editor.
Every example here imports from @chelone/core. Both adapters put these same
types on their props, and Typed components
covers how a component gets them.
Nothing registered
An app that never writes a Register augmentation still compiles and runs. Each
registered type takes its fallback.
| Type | With no augmentation |
|---|---|
AnySourcePath | string |
RegisteredResourceName | string |
RegisteredMessages | MessageCatalog |
RegisteredPermissions | unknown |
import {
type type AnySourcePath = string
{@link
AnySourcePathOf
}
over the app's registered resources.AnySourcePath,
type type RegisteredMessages = {
[x: string]: MessageValue;
}
The registered message catalog, or
{@link
MessageCatalog
}
when nothing is registered.RegisteredMessages,
type type RegisteredPermissions = unknownThe registered permissions type, or `unknown` when nothing is registered.
An app registers it with `interface Register { permissions: string[] }`.RegisteredPermissions,
type type RegisteredResourceName = stringThe registered resource names, or `string` when nothing is registered.RegisteredResourceName,
type interface SortPayload<TRecord = LooseRecord>The sort a list applies: one field of the record and a direction.
`field` is a
{@link
SourcePath
}
of the record, so sorting on a column the
record does not have stops compiling. A loose record (the default, and every
app that registers nothing) takes
{@link
AnySourcePath
}
instead, the same
loose arm a `source` prop has, so a `DataTable` column can write its own
`SourceProp` into `setSort`.
A data provider verb takes
{@link
LooseSortPayload
}
: a controller generic over
`TResources` cannot prove its `SortPayload<RecordOf<TResources, TName>>`
assigns to the registered union.SortPayload,
} from "@chelone/core";
type type Path = stringPath = type AnySourcePath = string
{@link
AnySourcePathOf
}
over the app's registered resources.AnySourcePath;
type type Name = stringName = type RegisteredResourceName = stringThe registered resource names, or `string` when nothing is registered.RegisteredResourceName;
type type SortField = stringSortField = interface SortPayload<TRecord = LooseRecord>The sort a list applies: one field of the record and a direction.
`field` is a
{@link
SourcePath
}
of the record, so sorting on a column the
record does not have stops compiling. A loose record (the default, and every
app that registers nothing) takes
{@link
AnySourcePath
}
instead, the same
loose arm a `source` prop has, so a `DataTable` column can write its own
`SourceProp` into `setSort`.
A data provider verb takes
{@link
LooseSortPayload
}
: a controller generic over
`TResources` cannot prove its `SortPayload<RecordOf<TResources, TName>>`
assigns to the registered union.SortPayload["field"];
// `MessageCatalog`, which is what the hover expands to.
type type Messages = {
[x: string]: MessageValue;
}
Messages = type RegisteredMessages = {
[x: string]: MessageValue;
}
The registered message catalog, or
{@link
MessageCatalog
}
when nothing is registered.RegisteredMessages;
type type Permissions = unknownPermissions = type RegisteredPermissions = unknownThe registered permissions type, or `unknown` when nothing is registered.
An app registers it with `interface Register { permissions: string[] }`.RegisteredPermissions;
const const sort: SortPayload<LooseRecord>sort: interface SortPayload<TRecord = LooseRecord>The sort a list applies: one field of the record and a direction.
`field` is a
{@link
SourcePath
}
of the record, so sorting on a column the
record does not have stops compiling. A loose record (the default, and every
app that registers nothing) takes
{@link
AnySourcePath
}
instead, the same
loose arm a `source` prop has, so a `DataTable` column can write its own
`SourceProp` into `setSort`.
A data provider verb takes
{@link
LooseSortPayload
}
: a controller generic over
`TResources` cannot prove its `SortPayload<RecordOf<TResources, TName>>`
assigns to the registered union.SortPayload = { SortPayload<LooseRecord>.field: stringfield: "made_up_column", SortPayload<LooseRecord>.order: "ASC" | "DESC"order: "ASC" };Nothing breaks, and nothing is checked. Resource takes a
RegisteredResourceName, so name="posts" and name="pots" are the same call.
A source is a string, so a misspelt column compiles and renders an empty
cell. SortPayload["field"] and FilterPayload's keys are string too, so a
sort on a column that does not exist reaches the data provider as written.
useTranslate takes any key, and usePermissions hands back unknown.
This is the state an app is in before it has written a single schema, and it is
the reason @chelone/core has no compile-time requirement on registration.
One resource without a schema
defineResources accepts a resource with no schema. RecordOf then hands back
LooseRecord for it, and SourcePath<LooseRecord> is string.
That reaches the whole map, not just the one resource. AnySourcePathOf unions
every resource's paths, row paths and filter keys, and a union that holds
string is string. One schemaless resource keeps the loose arm string for
every prop that takes it. SortPayload and FilterPayload read that same arm
for a loose record, so they widen with it.
import {
type type AnySourcePath = string
{@link
AnySourcePathOf
}
over the app's registered resources.AnySourcePath,
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,
type interface SortPayload<TRecord = LooseRecord>The sort a list applies: one field of the record and a direction.
`field` is a
{@link
SourcePath
}
of the record, so sorting on a column the
record does not have stops compiling. A loose record (the default, and every
app that registers nothing) takes
{@link
AnySourcePath
}
instead, the same
loose arm a `source` prop has, so a `DataTable` column can write its own
`SourceProp` into `setSort`.
A data provider verb takes
{@link
LooseSortPayload
}
: a controller generic over
`TResources` cannot prove its `SortPayload<RecordOf<TResources, TName>>`
assigns to the registered union.SortPayload,
} 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: {},
});
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: {};
}
resources: typeof const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
}, z.core.$strip>;
};
readonly comments: {};
}
resources;
}
}
// `comments` has no schema, so the loose arm is `string` for the whole map.
type type AnyPath = stringAnyPath = type AnySourcePath = string
{@link
AnySourcePathOf
}
over the app's registered resources.AnySourcePath;
type type CommentRecord = ResourceRecord<Identifier> & {
[key: string]: unknown;
}
CommentRecord = 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">;
type type PostRecord = {
id: number;
title: string;
}
PostRecord = 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">;
const const sort: SortPayload<{
id: number;
title: string;
}>
sort: interface SortPayload<TRecord = LooseRecord>The sort a list applies: one field of the record and a direction.
`field` is a
{@link
SourcePath
}
of the record, so sorting on a column the
record does not have stops compiling. A loose record (the default, and every
app that registers nothing) takes
{@link
AnySourcePath
}
instead, the same
loose arm a `source` prop has, so a `DataTable` column can write its own
`SourceProp` into `setSort`.
A data provider verb takes
{@link
LooseSortPayload
}
: a controller generic over
`TResources` cannot prove its `SortPayload<RecordOf<TResources, TName>>`
assigns to the registered union.SortPayload<type PostRecord = {
id: number;
title: string;
}
PostRecord> = { field: "titel", SortPayload<{ id: number; title: string; }>.order: "ASC" | "DESC"order: "ASC" };posts is registered and checked on its own: SortPayload<PostRecord> rejects
"titel" whatever comments holds. The loose arm is what stays open. Give
comments a schema and it closes.
Bare components
There are two ways to get a TextField, and they are typed differently.
createResourceComponents("posts") retypes every member's source against
posts' record alone. Importing the component from the package gives you the
generic one, whose source is SourceProp with its default LooseRecord, which
is AnySourcePath: the union over every registered resource.
import {
type type AnySourcePath = "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}`
{@link
AnySourcePathOf
}
over the app's registered resources.AnySourcePath,
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,
type type SourceProp<TRecord = LooseRecord> = LooseRecord extends TRecord ? "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}` : _RecordSourcePath<TRecord>The type a `source` prop takes.
A loose record (the default, and every app that registers nothing) gets
{@link
AnySourcePath
}
, which is `string` until `Register` is augmented; a
typed record gets its own paths.SourceProp,
} 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;
body: z.ZodString;
}, 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;
body: z.ZodString;
}, 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;
body: z.ZodString;
}, 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;
body: z.ZodString;
}, 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;
body: 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() }) },
comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
body: z.ZodString;
}, z.core.$strip>;
} & {
references?: {} | undefined;
}
comments: { schema: z.ZodObject<{
id: z.ZodNumber;
body: z.ZodString;
}, z.core.$strip>
schema: import zz.function object<{
id: z.ZodNumber;
body: z.ZodString;
}>(shape?: {
id: z.ZodNumber;
body: 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;
body: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), body: z.ZodStringbody: 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>;
};
readonly comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
body: z.ZodString;
}, 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;
body: z.ZodString;
}, z.core.$strip>;
};
}
resources;
}
}
// Every registered resource's paths, row paths and filter keys in one union.
type type AnyPath = "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}`AnyPath = type AnySourcePath = "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}`
{@link
AnySourcePathOf
}
over the app's registered resources.AnySourcePath;
type type PostRecord = {
id: number;
title: string;
}
PostRecord = 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: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
body: z.ZodString;
}, z.core.$strip>;
};
}
resources, "posts">;
// A bare component takes the union, so a `comments` path compiles on a post.
const const bare: "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}`bare: type SourceProp<TRecord = LooseRecord> = LooseRecord extends TRecord ? "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}` : _RecordSourcePath<TRecord>The type a `source` prop takes.
A loose record (the default, and every app that registers nothing) gets
{@link
AnySourcePath
}
, which is `string` until `Register` is augmented; a
typed record gets its own paths.SourceProp = "body";
// The same typo is rejected on the union.
const typo: type SourceProp<TRecord = LooseRecord> = LooseRecord extends TRecord ? "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}` : _RecordSourcePath<TRecord>The type a `source` prop takes.
A loose record (the default, and every app that registers nothing) gets
{@link
AnySourcePath
}
, which is `string` until `Register` is augmented; a
typed record gets its own paths.SourceProp = "titel";
// A typed component takes `posts` paths alone, whatever the other resources hold.
const typed: type SourceProp<TRecord = LooseRecord> = LooseRecord extends TRecord ? "id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}` : _RecordSourcePath<TRecord>The type a `source` prop takes.
A loose record (the default, and every app that registers nothing) gets
{@link
AnySourcePath
}
, which is `string` until `Register` is augmented; a
typed record gets its own paths.SourceProp<type PostRecord = {
id: number;
title: string;
}
PostRecord> = "body";A bare component is checked against the app, not against the page you are looking at. A component from the factory is checked against one record. Typed components covers what the factory hands back and how its members are retyped.
Records that name no keys
LooseRecord is ResourceRecord & { [key: string]: unknown }. A field of a
loose record reads as unknown, never any, so reading one is a check you write
yourself.
import { type type LooseRecord = ResourceRecord<Identifier> & {
[key: string]: unknown;
}
Fallback record type when a resource has no registered schema. Extra fields are `unknown`, never `any`.LooseRecord } from "@chelone/core";
type type Row = ResourceRecord<Identifier> & {
[key: string]: unknown;
}
Row = type LooseRecord = ResourceRecord<Identifier> & {
[key: string]: unknown;
}
Fallback record type when a resource has no registered schema. Extra fields are `unknown`, never `any`.LooseRecord;
declare const const row: LooseRecordrow: type LooseRecord = ResourceRecord<Identifier> & {
[key: string]: unknown;
}
Fallback record type when a resource has no registered schema. Extra fields are `unknown`, never `any`.LooseRecord;
const const title: anytitle = row.title.toUpperCase();any would let that line compile and fail at runtime. unknown stops it at the
line.
A schema can also name no keys for one field, and each shape gives up something different.
- A record type that is an index signature,
LooseRecordor a whole record written asz.record(z.string(), z.unknown()), haskeyofofstring, soRecordFilterKeyis plainstringfor it and every filter key compiles. - A field with an index signature accepts any path under it,
extra.anythingandextra.anything_gte, plus every filter key built from those paths. - A jsonb column typed
Json, the union Supabase's generated types write for one, has an object arm with an index signature, so it reads the same way. - An array of open rows adds no row paths.
RowSourcePathdrops a row whose paths arestring, because onestringthere would swallow every other array's row paths and let every typo through again. - A
z.any()field accepts any path under it, dotted or bracketed, and the rest of the record stays checked.
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, type type SourceProp<TRecord = LooseRecord> = LooseRecord extends TRecord ? string : _RecordSourcePath<TRecord>The type a `source` prop takes.
A loose record (the default, and every app that registers nothing) gets
{@link
AnySourcePath
}
, which is `string` until `Register` is augmented; a
typed record gets its own paths.SourceProp } from "@chelone/core";
import { import zz } from "zod";
const const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>;
};
}
resources = defineResources<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>;
};
}>(resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>;
};
} & Checked<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>;
};
}>): {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, 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;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>;
} & {
references?: {} | undefined;
}
posts: {
schema: z.ZodObject<{
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>
schema: import zz.function object<{
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}>(shape?: {
id: z.ZodNumber;
title: z.ZodString;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<...>
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(),
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>extra: import zz.function record<z.ZodString, z.ZodUnknown>(keyType: z.ZodString, valueType: z.ZodUnknown, params?: string | z.core.$ZodRecordParams): z.ZodRecord<z.ZodString, z.ZodUnknown>record(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), import zz.function unknown(): z.ZodUnknownunknown()),
any: z.ZodAnyany: import zz.function any(): z.ZodAnyany(),
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>rows: import zz.function array<z.ZodRecord<z.ZodString, z.ZodUnknown>>(element: z.ZodRecord<z.ZodString, z.ZodUnknown>, params?: string | z.core.$ZodArrayParams): z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>array(import zz.function record<z.ZodString, z.ZodUnknown>(keyType: z.ZodString, valueType: z.ZodUnknown, params?: string | z.core.$ZodRecordParams): z.ZodRecord<z.ZodString, z.ZodUnknown>record(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), import zz.function unknown(): z.ZodUnknownunknown())),
}),
},
});
type type Open = {
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}
Open = 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;
extra: z.ZodRecord<z.ZodString, z.ZodUnknown>;
any: z.ZodAny;
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>;
};
}
resources, "posts">;
// The record's own paths, plus a wildcard under each open member.
type type OpenSource = "id" | "title" | "extra" | "any" | "rows" | `extra.${string}` | `any.${string}` | `any[${number}]${string}` | `rows[${number}]` | `rows[${number}].${string}` | FilterSourceKey<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
OpenSource = type SourceProp<TRecord = LooseRecord> = LooseRecord extends TRecord ? string : _RecordSourcePath<TRecord>The type a `source` prop takes.
A loose record (the default, and every app that registers nothing) gets
{@link
AnySourcePath
}
, which is `string` until `Register` is augmented; a
typed record gets its own paths.SourceProp<type Open = {
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}
Open>;
const const extra: _RecordSourcePath<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
extra: type OpenSource = "id" | "title" | "extra" | "any" | "rows" | `extra.${string}` | `any.${string}` | `any[${number}]${string}` | `rows[${number}]` | `rows[${number}].${string}` | FilterSourceKey<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
OpenSource = "extra.anything";
const const underAny: _RecordSourcePath<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
underAny: type OpenSource = "id" | "title" | "extra" | "any" | "rows" | `extra.${string}` | `any.${string}` | `any[${number}]${string}` | `rows[${number}]` | `rows[${number}].${string}` | FilterSourceKey<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
OpenSource = "any.anything";
const const underRow: _RecordSourcePath<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
underRow: type OpenSource = "id" | "title" | "extra" | "any" | "rows" | `extra.${string}` | `any.${string}` | `any[${number}]${string}` | `rows[${number}]` | `rows[${number}].${string}` | FilterSourceKey<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
OpenSource = "rows[0].anything";
const typo: type OpenSource = "id" | "title" | "extra" | "any" | "rows" | `extra.${string}` | `any.${string}` | `any[${number}]${string}` | `rows[${number}]` | `rows[${number}].${string}` | FilterSourceKey<{
id: number;
title: string;
extra: Record<string, unknown>;
any: any;
rows: Record<string, unknown>[];
}>
OpenSource = "titel";A record that wants its jsonb rows checked gives them a schema. That is the whole fix, and Sources, filters and sort covers what a path may look like once a member is closed.
Limits
Two bounds keep the path union finite, and both are in the type rather than a warning at runtime.
SourcePathwalks 8 object levels, so a path stops at 8 segments.RowSourcePathwalks the same 8 levels looking for arrays and follows array nesting 3 deep.
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, type type SourcePath<TRecord> = unknown extends TRecord ? string : TRecord extends _Leaf ? never : 0 extends 1 & TRecord ? string : TRecord extends readonly unknown[] ? TRecord extends readonly [] ? never : _BracketEntry<`[${number}]`, NonNullable<TRecord[number]> extends _Leaf ? never : 0 extends 1 & TRecord[number] ? `[${number}]${string}` | `.${string}` : NonNullable<TRecord[number]> extends readonly unknown[] ? readonly unknown[] & TRecord[number] extends readonly [] ? never : _BracketEntry<`[${number}]`, NonNullable<(readonly unknown[] & TRecord[number])[number]> extends _Leaf ? never : 0 extends 1 & (readonly unknown[] & TRecord[number])[number] ? `[${number}]${string}` | `.${string}` : NonNullable<...> extends readonly unknown[] ? readonly unknown[] & (readonly unknown[] & TRecord[number])[number] extends readonly [] ? never : _BracketEntry<...> : { [K in keyof NonNullable<...> & (string | number)]: _BracketEntry<...>; }[keyof NonNullable<...> & (string | number)]> : { [K in keyof NonNullable<...> & (string | number)]: _BracketEntry<...>; }[keyof NonNullable<...> & (string | number)]> : { [K in keyof TRecord & (string | number)]: _BracketEntry<...>; }[keyof TRecord & (string | number)]Every legal `source` path into `TRecord`: dotted object paths and bracket
array indices (`items[0].sku`).
The `unknown` guard is load-bearing: `Paths<unknown>` is `never`, which would
reject every source of a resource with no schema, while
`Paths<LooseRecord, { arrays: "bracket" }>` is `string`.SourcePath } from "@chelone/core";
import { import zz } from "zod";
const const resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
};
}
resources = defineResources<{
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
};
}>(resources: {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
};
} & Checked<...>): {
readonly posts: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, 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;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
} & {
references?: {} | undefined;
}
posts: {
schema: z.ZodObject<{
id: z.ZodNumber;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
schema: import zz.function object<{
id: z.ZodNumber;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}>(shape?: {
id: z.ZodNumber;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
} | undefined, params?: string | ... 1 more ... | undefined): z.ZodObject<...>
object({
id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(),
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
a: import zz.function object<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}>(shape?: {
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
} | undefined, params?: string | ... 1 more ... | undefined): z.ZodObject<...>
object({
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
b: import zz.function object<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}>(shape?: {
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
} | undefined, params?: string | ... 1 more ... | undefined): z.ZodObject<...>
object({
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
c: import zz.function object<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}>(shape?: {
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<...>
object({
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
d: import zz.function object<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}>(shape?: {
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<...>
object({
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
e: import zz.function object<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}>(shape?: {
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
object({
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
f: import zz.function object<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}>(shape?: {
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>
object({
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>
g: import zz.function object<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}>(shape?: {
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>
object({ h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>
h: import zz.function object<{
i: z.ZodString;
}>(shape?: {
i: 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<{
i: z.ZodString;
}, z.core.$strip>
object({ i: z.ZodStringi: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }) }),
}),
}),
}),
}),
}),
}),
}),
},
});
type type Deep = {
id: number;
a: {
b: {
c: {
d: {
e: {
f: {
g: {
h: {
i: string;
};
};
};
};
};
};
};
};
}
Deep = 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;
a: z.ZodObject<{
b: z.ZodObject<{
c: z.ZodObject<{
d: z.ZodObject<{
e: z.ZodObject<{
f: z.ZodObject<{
g: z.ZodObject<{
h: z.ZodObject<{
i: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
}, z.core.$strip>;
};
}
resources, "posts">;
// `a` is level 1, and `a.b.c.d.e.f.g.h` is level 8.
const const eight: "id" | "a" | "a.b" | "a.b.c" | "a.b.c.d" | "a.b.c.d.e" | "a.b.c.d.e.f" | "a.b.c.d.e.f.g" | "a.b.c.d.e.f.g.h"eight: type SourcePath<TRecord> = unknown extends TRecord ? string : TRecord extends _Leaf ? never : 0 extends 1 & TRecord ? string : TRecord extends readonly unknown[] ? TRecord extends readonly [] ? never : _BracketEntry<`[${number}]`, NonNullable<TRecord[number]> extends _Leaf ? never : 0 extends 1 & TRecord[number] ? `[${number}]${string}` | `.${string}` : NonNullable<TRecord[number]> extends readonly unknown[] ? readonly unknown[] & TRecord[number] extends readonly [] ? never : _BracketEntry<`[${number}]`, NonNullable<(readonly unknown[] & TRecord[number])[number]> extends _Leaf ? never : 0 extends 1 & (readonly unknown[] & TRecord[number])[number] ? `[${number}]${string}` | `.${string}` : NonNullable<...> extends readonly unknown[] ? readonly unknown[] & (readonly unknown[] & TRecord[number])[number] extends readonly [] ? never : _BracketEntry<...> : { [K in keyof NonNullable<...> & (string | number)]: _BracketEntry<...>; }[keyof NonNullable<...> & (string | number)]> : { [K in keyof NonNullable<...> & (string | number)]: _BracketEntry<...>; }[keyof NonNullable<...> & (string | number)]> : { [K in keyof TRecord & (string | number)]: _BracketEntry<...>; }[keyof TRecord & (string | number)]Every legal `source` path into `TRecord`: dotted object paths and bracket
array indices (`items[0].sku`).
The `unknown` guard is load-bearing: `Paths<unknown>` is `never`, which would
reject every source of a resource with no schema, while
`Paths<LooseRecord, { arrays: "bracket" }>` is `string`.SourcePath<type Deep = {
id: number;
a: {
b: {
c: {
d: {
e: {
f: {
g: {
h: {
i: string;
};
};
};
};
};
};
};
};
}
Deep> = "a.b.c.d.e.f.g.h";
// Level 9 is never produced, and the suggestion names the deepest legal path.
const past: type SourcePath<TRecord> = unknown extends TRecord ? string : TRecord extends _Leaf ? never : 0 extends 1 & TRecord ? string : TRecord extends readonly unknown[] ? TRecord extends readonly [] ? never : _BracketEntry<`[${number}]`, NonNullable<TRecord[number]> extends _Leaf ? never : 0 extends 1 & TRecord[number] ? `[${number}]${string}` | `.${string}` : NonNullable<TRecord[number]> extends readonly unknown[] ? readonly unknown[] & TRecord[number] extends readonly [] ? never : _BracketEntry<`[${number}]`, NonNullable<(readonly unknown[] & TRecord[number])[number]> extends _Leaf ? never : 0 extends 1 & (readonly unknown[] & TRecord[number])[number] ? `[${number}]${string}` | `.${string}` : NonNullable<...> extends readonly unknown[] ? readonly unknown[] & (readonly unknown[] & TRecord[number])[number] extends readonly [] ? never : _BracketEntry<...> : { [K in keyof NonNullable<...> & (string | number)]: _BracketEntry<...>; }[keyof NonNullable<...> & (string | number)]> : { [K in keyof NonNullable<...> & (string | number)]: _BracketEntry<...>; }[keyof NonNullable<...> & (string | number)]> : { [K in keyof TRecord & (string | number)]: _BracketEntry<...>; }[keyof TRecord & (string | number)]Every legal `source` path into `TRecord`: dotted object paths and bracket
array indices (`items[0].sku`).
The `unknown` guard is load-bearing: `Paths<unknown>` is `never`, which would
reject every source of a resource with no schema, while
`Paths<LooseRecord, { arrays: "bracket" }>` is `string`.SourcePath<type Deep = {
id: number;
a: {
b: {
c: {
d: {
e: {
f: {
g: {
h: {
i: string;
};
};
};
};
};
};
};
};
}
Deep> = "a.b.c.d.e.f.g.h.i";A record nested past the bound has paths its props cannot name. The values are
still there at runtime, and getAtPath reads any string you hand it. What stops
is the autocomplete and the typo check, not the data.
The *Of variants
Register is global to a TypeScript program. A published library cannot augment
it on your behalf, and a type test that wants two different maps in one file
cannot either. Each registry type has a variant that takes the map or the catalog
as an argument:
AnySourcePathOf<TResources>andAnyFilterSourceKeyOf<TResources>RecordOf<TResources, TName>createResourceComponentsOf<TResources, TName>in@chelone/reactand in@chelone/vueTranslateFor<TCatalog, TRegister>
import {
type type AnyFilterSourceKeyOf<TResources extends ResourceMap> = string extends keyof TResources ? string : { [TName in keyof TResources & string]: RecordFilterKey<RecordOf<TResources, TName>>; }[keyof TResources & string]The union of every registered resource's filter keys.
`string` when the name set is `string` (nothing registered) and `string` when
any resource has no schema, as
{@link
AnySourcePathOf
}
does: an app that
registers some resources but filters one without a schema stays string-keyed.AnyFilterSourceKeyOf,
type type AnySourcePathOf<TResources extends ResourceMap> = string extends keyof TResources ? string : { [TName in keyof TResources & string]: _RecordSourcePath<RecordOf<TResources, TName>>; }[keyof TResources & string]The union of every registered resource's source paths.
`string` when the name set is `string` — the unregistered case, and the one
that keeps `AnySourcePath` from recursing on itself through
{@link
SourceProp
}
— and `string` when any resource has no schema.AnySourcePathOf,
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 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;
body: z.ZodString;
}, 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;
body: z.ZodString;
}, 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;
body: z.ZodString;
}, 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;
body: z.ZodString;
}, 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;
body: 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() }) },
comments: {
readonly schema: z.ZodObject<{
id: z.ZodNumber;
body: z.ZodString;
}, z.core.$strip>;
} & {
references?: {} | undefined;
}
comments: { schema: z.ZodObject<{
id: z.ZodNumber;
body: z.ZodString;
}, z.core.$strip>
schema: import zz.function object<{
id: z.ZodNumber;
body: z.ZodString;
}>(shape?: {
id: z.ZodNumber;
body: 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;
body: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), body: z.ZodStringbody: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }) },
});
// No augmentation in this file: the map is an argument.
type type Paths = "q" | "id" | "title" | "body" | `id_${string}` | `title_${string}` | `body_${string}`Paths = type AnySourcePathOf<TResources extends ResourceMap> = string extends keyof TResources ? string : { [TName in keyof TResources & string]: _RecordSourcePath<RecordOf<TResources, TName>>; }[keyof TResources & string]The union of every registered resource's source paths.
`string` when the name set is `string` — the unregistered case, and the one
that keeps `AnySourcePath` from recursing on itself through
{@link
SourceProp
}
— and `string` when any resource has no schema.AnySourcePathOf<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;
body: z.ZodString;
}, z.core.$strip>;
};
}
resources>;
type type FilterKeys = "q" | "id" | "title" | "body" | `id_${string}` | `title_${string}` | `body_${string}`FilterKeys = type AnyFilterSourceKeyOf<TResources extends ResourceMap> = string extends keyof TResources ? string : { [TName in keyof TResources & string]: RecordFilterKey<RecordOf<TResources, TName>>; }[keyof TResources & string]The union of every registered resource's filter keys.
`string` when the name set is `string` (nothing registered) and `string` when
any resource has no schema, as
{@link
AnySourcePathOf
}
does: an app that
registers some resources but filters one without a schema stays string-keyed.AnyFilterSourceKeyOf<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;
body: z.ZodString;
}, z.core.$strip>;
};
}
resources>;
The registered types are these with RegisteredResources filled in, so a library
can take a map from its caller and stay checked without touching Register.
createResourceComponentsOf is the same factory with the map written out, which
is how a test checks a resource it never registers.
Editor performance and type cost
Registration replaces a string prop with a union of paths. The loose arm unions
every resource's paths, row paths and filter keys, so what grows is the number of
types an editor instantiates when it checks one of those props. It follows the
number of resources in the map and the number of fields each schema names.
Nesting is bounded by the limits above, so a deep record cannot push the union
past them.
Measure instead of guessing. tsc --noEmit --extendedDiagnostics prints two
counters worth watching:
tsc --noEmit --extendedDiagnosticsInstantiations is how many times the compiler opened a generic, and Types is
how many types it built. tsc --generateTrace <dir> then shows which files and
which types they came from:
tsc --generateTrace .type-traceChelone holds its own packages to those two counters in type-cost-budget.json
through pnpm check:type-cost, which fails a package whose numbers move past its
budget. That script is for contributors, and it is what keeps Chelone's own
packages inside the bounds rather than leaving them to a reviewer's judgement.
Three things follow from how the types are written:
- Type a component per resource.
createResourceComponents(name)types each member against one record, so a page built from it checks that record's paths instead of the whole map's union. - Give a jsonb column a schema only where you want its rows checked. An open
Jsonmember adds almost nothing to the union, and a closed one adds a path per field you named. - Leave
literalMessagesoff unless a type needs the exact text of a key. It turns each key's return type into that text, which is a second union over your catalog.
No threshold is given here on purpose. The numbers depend on how many resources and fields your app has, and a figure measured on one machine goes stale on the next TypeScript release.
Vue
The Vue package is checked by vue-tsc, which reports the same two counters:
vue-tsc --noEmit --extendedDiagnosticsThat is the compiler pnpm check:type-cost runs for @chelone/vue, so a Vue app
measures its own type cost the same way the repo measures its own.
Where to read next
- Registering your app for
defineResources, theRegisteraugmentation and handing the map toAdmin. - Resource registry and codegen for the registry as a runtime value and the codegen that writes the map.
- Typed components for how a component's props read the map.
- Sources, filters and sort for the path grammar and the operator-suffixed filter keys.
- Translations and permissions
for
RegisteredMessages,literalMessagesandRegisteredPermissions.