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.

TypeWith no augmentation
AnySourcePathstring
RegisteredResourceNamestring
RegisteredMessagesMessageCatalog
RegisteredPermissionsunknown
src/loose.ts
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 = unknown
The registered permissions type, or `unknown` when nothing is registered. An app registers it with `interface Register { permissions: string[] }`.
RegisteredPermissions
,
type type RegisteredResourceName = string
The 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 = string
Path
= type AnySourcePath = string
{@link AnySourcePathOf } over the app's registered resources.
AnySourcePath
;
type
type Name = string
Name
= type RegisteredResourceName = string
The registered resource names, or `string` when nothing is registered.
RegisteredResourceName
;
type
type SortField = string
SortField
= 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 = unknown
Permissions
= type RegisteredPermissions = unknown
The 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.

src/half-registered.ts
import {
  type type AnySourcePath = string
{@link AnySourcePathOf } over the app's registered resources.
AnySourcePath
,
function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
,
type
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
,
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 = string
AnyPath
= 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" };
Type '"titel"' is not assignable to type '"id" | "title"'. Did you mean '"title"'?

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.

src/bare-and-typed.ts
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>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
,
type
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
,
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";
Type '"titel"' is not assignable to type '"id" | "title" | "body" | "q" | `id_${string}` | `title_${string}` | `body_${string}`'. Did you mean '"title"'?
// 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";
Type '"body"' is not assignable to type '_RecordSourcePath<{ id: number; title: string; }>'.

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.

src/loose-record.ts
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();
'row.title' is of type 'unknown'.

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, LooseRecord or a whole record written as z.record(z.string(), z.unknown()), has keyof of string, so RecordFilterKey is plain string for it and every filter key compiles.
  • A field with an index signature accepts any path under it, extra.anything and extra.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. RowSourcePath drops a row whose paths are string, because one string there 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.
src/open-record.ts
import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
, type
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
, 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";
Type '"titel"' is not assignable to type '_RecordSourcePath<{ id: number; title: string; extra: Record<string, unknown>; any: any; rows: Record<string, unknown>[]; }>'. Did you mean '"title"'?

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.

  • SourcePath walks 8 object levels, so a path stops at 8 segments.
  • RowSourcePath walks the same 8 levels looking for arrays and follows array nesting 3 deep.
src/deep.ts
import { function defineResources<const T extends ResourceMap>(resources: T & Checked<T>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
, type
type RecordOf<TResources extends ResourceMap, TName extends keyof TResources> = TResources[TName] extends {
    schema: infer S;
} ? S extends StandardSchemaV1<unknown, infer Output> ? Output : LooseRecord : LooseRecord
The record type for a given resource name in a registry: the schema's Standard Schema output type when a schema is present, else `LooseRecord`.
RecordOf
, 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";
Type '"a.b.c.d.e.f.g.h.i"' is not assignable to type '"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"'. Did you mean '"a.b.c.d.e.f.g.h"'?

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> and AnyFilterSourceKeyOf<TResources>
  • RecordOf<TResources, TName>
  • createResourceComponentsOf<TResources, TName> in @chelone/react and in @chelone/vue
  • TranslateFor<TCatalog, TRegister>
src/type-test.ts
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>): T
Identity helper that pins literal resource names + schema types via `const`. `defineResources({ posts: { schema: postSchema } })` keeps `"posts"` literal and `typeof postSchema` exact, enabling end-to-end inference.
defineResources
,
} from "@chelone/core"; import { import zz } from "zod"; const
const 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 --extendedDiagnostics

Instantiations 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-trace

Chelone 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 Json member adds almost nothing to the union, and a closed one adds a path per field you named.
  • Leave literalMessages off 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 --extendedDiagnostics

That 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.