@veloxts/router 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,24 +5,36 @@
5
5
  * field-level visibility controls. The builder tracks field types
6
6
  * at compile time to enable type-safe projections.
7
7
  *
8
+ * Supports both the default 3-level system (public/authenticated/admin)
9
+ * and custom access levels defined via `defineAccessLevels()`.
10
+ *
8
11
  * @module resource/schema
9
12
  */
10
13
  import type { ZodType } from 'zod';
11
- import type { AccessLevel, ADMIN, ANONYMOUS, AUTHENTICATED, ContextTag, LevelToTag } from './tags.js';
12
- import type { IsVisibleToTag, VisibilityLevel } from './visibility.js';
14
+ import type { AccessLevelConfig } from './levels.js';
15
+ import type { ContextTag, TagToLevel } from './tags.js';
16
+ import type { VisibilityLevel } from './visibility.js';
13
17
  /**
14
18
  * A single field definition in a resource schema
15
19
  *
20
+ * The `TLevel` parameter is a union of level strings representing which
21
+ * access levels can see this field. For the default system:
22
+ * - `.public()` → `'public' | 'authenticated' | 'admin'`
23
+ * - `.authenticated()` → `'authenticated' | 'admin'`
24
+ * - `.admin()` → `'admin'`
25
+ *
26
+ * For custom levels, `TLevel` is the union of levels passed to the builder method.
27
+ *
16
28
  * @template TName - The field name as a literal type
17
29
  * @template TSchema - The Zod schema type for this field
18
- * @template TLevel - The visibility level for this field
30
+ * @template TLevel - Union of access level strings that can see this field
19
31
  */
20
- export interface ResourceField<TName extends string = string, TSchema extends ZodType = ZodType, TLevel extends VisibilityLevel = VisibilityLevel> {
32
+ export interface ResourceField<TName extends string = string, TSchema extends ZodType = ZodType, TLevel extends string = string> {
21
33
  /** Field name */
22
34
  readonly name: TName;
23
35
  /** Zod schema for validation */
24
36
  readonly schema: TSchema;
25
- /** Visibility level */
37
+ /** Visibility level (union of levels that can see this field) */
26
38
  readonly visibility: TLevel;
27
39
  }
28
40
  /**
@@ -30,10 +42,10 @@ export interface ResourceField<TName extends string = string, TSchema extends Zo
30
42
  *
31
43
  * @template TName - The field name as a literal type
32
44
  * @template TNestedFields - The nested schema's field definitions
33
- * @template TLevel - The visibility level controlling WHETHER the relation is included
45
+ * @template TLevel - Union of access level strings controlling WHETHER the relation is included
34
46
  * @template TCardinality - 'one' for nullable object, 'many' for array
35
47
  */
36
- export interface RelationField<TName extends string = string, TNestedFields extends readonly BuilderField[] = readonly BuilderField[], TLevel extends VisibilityLevel = VisibilityLevel, TCardinality extends 'one' | 'many' = 'one' | 'many'> {
48
+ export interface RelationField<TName extends string = string, TNestedFields extends readonly BuilderField[] = readonly BuilderField[], TLevel extends string = string, TCardinality extends 'one' | 'many' = 'one' | 'many'> {
37
49
  readonly name: TName;
38
50
  readonly nestedSchema: ResourceSchema<TNestedFields>;
39
51
  readonly visibility: TLevel;
@@ -51,7 +63,10 @@ export type BuilderField = ResourceField | RelationField;
51
63
  export interface RuntimeField {
52
64
  readonly name: string;
53
65
  readonly schema?: ZodType;
54
- readonly visibility: VisibilityLevel;
66
+ /** Set of levels that can see this field (source of truth) */
67
+ readonly visibleTo: ReadonlySet<string>;
68
+ /** Single visibility level string (backward compat, first level in set) */
69
+ readonly visibility: string;
55
70
  readonly nestedSchema?: ResourceSchema;
56
71
  readonly cardinality?: 'one' | 'many';
57
72
  }
@@ -72,27 +87,13 @@ export interface ResourceSchema<TFields extends readonly BuilderField[] = readon
72
87
  /**
73
88
  * A resource schema tagged with an explicit access level
74
89
  *
75
- * Created by accessing `.public`, `.authenticated`, or `.admin` on a built schema.
76
- * Used in both procedure definitions (auto-projection) and handler-level
77
- * projection via `resource(data, Schema.authenticated)`.
90
+ * Created by accessing `.public`, `.authenticated`, or `.admin` on a built schema,
91
+ * or by accessing any custom level property on a custom-level schema.
78
92
  *
79
93
  * @template TFields - The field definitions from the base schema
80
- * @template TLevel - The access level for projection
81
- *
82
- * @example
83
- * ```typescript
84
- * const UserSchema = resourceSchema()
85
- * .public('id', z.string())
86
- * .authenticated('email', z.string())
87
- * .build();
88
- *
89
- * // Tagged views
90
- * UserSchema.public // TaggedResourceSchema<..., 'public'>
91
- * UserSchema.authenticated // TaggedResourceSchema<..., 'authenticated'>
92
- * UserSchema.admin // TaggedResourceSchema<..., 'admin'>
93
- * ```
94
+ * @template TLevel - The access level for projection (any string for custom levels)
94
95
  */
95
- export interface TaggedResourceSchema<TFields extends readonly BuilderField[] = readonly BuilderField[], TLevel extends AccessLevel = AccessLevel> extends ResourceSchema<TFields> {
96
+ export interface TaggedResourceSchema<TFields extends readonly BuilderField[] = readonly BuilderField[], TLevel extends string = string> extends ResourceSchema<TFields> {
96
97
  readonly _level: TLevel;
97
98
  }
98
99
  /**
@@ -110,36 +111,25 @@ export interface ResourceSchemaWithViews<TFields extends readonly BuilderField[]
110
111
  readonly admin: TaggedResourceSchema<TFields, 'admin'>;
111
112
  }
112
113
  /**
113
- * Computes the output type for a tagged resource schema
114
+ * A completed custom-level resource schema with tagged views for each level
114
115
  *
115
- * Maps the access level to the corresponding phantom tag and
116
- * computes the projected output type.
116
+ * Returned by `resourceSchema(config).build()`. Has one property per
117
+ * defined level, each a TaggedResourceSchema for that level.
117
118
  *
118
- * @template TSchema - A tagged resource schema
119
- */
120
- export type OutputForLevel<TSchema extends TaggedResourceSchema> = TSchema extends TaggedResourceSchema<infer TFields, infer TLevel> ? OutputForTag<ResourceSchema<TFields>, LevelToTag<TLevel>> : never;
121
- /**
122
- * Type guard to check if a schema is a TaggedResourceSchema (has _level)
119
+ * @template TFields - The field definitions
120
+ * @template TLevels - The tuple of level names
123
121
  */
124
- export declare function isTaggedResourceSchema(value: unknown): value is TaggedResourceSchema;
122
+ export type CustomResourceSchemaWithViews<TFields extends readonly BuilderField[], TLevels extends readonly string[]> = ResourceSchema<TFields> & {
123
+ [K in TLevels[number]]: TaggedResourceSchema<TFields, K>;
124
+ } & {
125
+ readonly _levelConfig: AccessLevelConfig;
126
+ };
125
127
  /**
126
128
  * Helper to infer the output type of a Zod schema
127
129
  */
128
130
  type InferZodOutput<T> = T extends {
129
131
  parse: (data: unknown) => infer O;
130
132
  } ? O : never;
131
- /**
132
- * Filters fields by visibility and extracts their types
133
- *
134
- * This type iterates over the fields tuple and includes only those
135
- * that are visible to the given context tag. RelationField entries
136
- * are recursively projected using the same tag.
137
- */
138
- type FilterFieldsByTag<TFields extends readonly BuilderField[], TTag extends ContextTag> = TFields extends readonly [infer First, ...infer Rest] ? Rest extends readonly BuilderField[] ? First extends RelationField<infer Name, infer NestedFields, infer Level, infer Card> ? IsVisibleToTag<Level, TTag> extends true ? {
139
- [K in Name]: Card extends 'one' ? Simplify<FilterFieldsByTag<NestedFields, TTag>> | null : Array<Simplify<FilterFieldsByTag<NestedFields, TTag>>>;
140
- } & FilterFieldsByTag<Rest, TTag> : FilterFieldsByTag<Rest, TTag> : First extends ResourceField<infer Name, infer Schema, infer Level> ? IsVisibleToTag<Level, TTag> extends true ? {
141
- [K in Name]: InferZodOutput<Schema>;
142
- } & FilterFieldsByTag<Rest, TTag> : FilterFieldsByTag<Rest, TTag> : FilterFieldsByTag<Rest, TTag> : unknown : unknown;
143
133
  /**
144
134
  * Simplifies an intersection type to a cleaner object type
145
135
  *
@@ -148,28 +138,59 @@ type FilterFieldsByTag<TFields extends readonly BuilderField[], TTag extends Con
148
138
  type Simplify<T> = T extends object ? {
149
139
  [K in keyof T]: T[K];
150
140
  } : T;
141
+ /**
142
+ * Converts a default VisibilityLevel to its union of visible levels
143
+ *
144
+ * Used by the default builder to track which levels can see a field:
145
+ * - `'public'` → `'public' | 'authenticated' | 'admin'`
146
+ * - `'authenticated'` → `'authenticated' | 'admin'`
147
+ * - `'admin'` → `'admin'`
148
+ */
149
+ export type LevelToVisibleUnion<TLevel extends VisibilityLevel> = TLevel extends 'public' ? 'public' | 'authenticated' | 'admin' : TLevel extends 'authenticated' ? 'authenticated' | 'admin' : 'admin';
150
+ /**
151
+ * Filters fields by level using set membership (union extends check)
152
+ *
153
+ * Includes a field if `TTarget extends TFieldLevels` — i.e., the target
154
+ * level string is a member of the field's visibility union.
155
+ *
156
+ * Works for both the default 3-level system and custom levels.
157
+ */
158
+ export type FilterFieldsByLevel<TFields extends readonly BuilderField[], TTarget extends string> = TFields extends readonly [infer First, ...infer Rest] ? Rest extends readonly BuilderField[] ? First extends RelationField<infer Name, infer NestedFields, infer Levels, infer Card> ? TTarget extends Levels ? {
159
+ [K in Name]: Card extends 'one' ? Simplify<FilterFieldsByLevel<NestedFields, TTarget>> | null : Array<Simplify<FilterFieldsByLevel<NestedFields, TTarget>>>;
160
+ } & FilterFieldsByLevel<Rest, TTarget> : FilterFieldsByLevel<Rest, TTarget> : First extends ResourceField<infer Name, infer Schema, infer Levels> ? TTarget extends Levels ? {
161
+ [K in Name]: InferZodOutput<Schema>;
162
+ } & FilterFieldsByLevel<Rest, TTarget> : FilterFieldsByLevel<Rest, TTarget> : FilterFieldsByLevel<Rest, TTarget> : unknown : unknown;
163
+ /**
164
+ * Filters fields by visibility using tag-based access
165
+ *
166
+ * Delegates to `FilterFieldsByLevel` using `TagToLevel` to convert the
167
+ * phantom tag to a level string. Works with the default 3-level system.
168
+ */
169
+ type FilterFieldsByTag<TFields extends readonly BuilderField[], TTag extends ContextTag> = FilterFieldsByLevel<TFields, TagToLevel<TTag>>;
151
170
  /**
152
171
  * Computes the output type for a schema at a given context tag
153
172
  *
154
173
  * @template TSchema - The resource schema type
155
174
  * @template TTag - The context tag to compute output for
175
+ */
176
+ export type OutputForTag<TSchema extends ResourceSchema, TTag extends ContextTag> = TSchema extends ResourceSchema<infer TFields> ? Simplify<FilterFieldsByTag<TFields, TTag>> : never;
177
+ /**
178
+ * Computes the output type for a tagged resource schema
156
179
  *
157
- * @example
158
- * ```typescript
159
- * type PublicOutput = OutputForTag<UserSchema, typeof ANONYMOUS>;
160
- * // Result: { id: string; name: string }
180
+ * Uses `FilterFieldsByLevel` directly, which works for both default
181
+ * and custom access levels.
161
182
  *
162
- * type AuthOutput = OutputForTag<UserSchema, typeof AUTHENTICATED>;
163
- * // Result: { id: string; name: string; email: string }
164
- * ```
183
+ * @template TSchema - A tagged resource schema
165
184
  */
166
- export type OutputForTag<TSchema extends ResourceSchema, TTag extends ContextTag> = TSchema extends ResourceSchema<infer TFields> ? Simplify<FilterFieldsByTag<TFields, TTag>> : never;
185
+ export type OutputForLevel<TSchema extends TaggedResourceSchema> = TSchema extends TaggedResourceSchema<infer TFields, infer TLevel> ? Simplify<FilterFieldsByLevel<TFields, TLevel>> : never;
167
186
  /**
168
187
  * Convenience type aliases for common tag outputs
169
188
  */
170
- export type AnonymousOutput<TSchema extends ResourceSchema> = OutputForTag<TSchema, typeof ANONYMOUS>;
171
- export type AuthenticatedOutput<TSchema extends ResourceSchema> = OutputForTag<TSchema, typeof AUTHENTICATED>;
172
- export type AdminOutput<TSchema extends ResourceSchema> = OutputForTag<TSchema, typeof ADMIN>;
189
+ export type PublicOutput<TSchema extends ResourceSchema> = OutputForTag<TSchema, typeof import('./tags.js').PUBLIC>;
190
+ /** @deprecated Use PublicOutput */
191
+ export type AnonymousOutput<TSchema extends ResourceSchema> = PublicOutput<TSchema>;
192
+ export type AuthenticatedOutput<TSchema extends ResourceSchema> = OutputForTag<TSchema, typeof import('./tags.js').AUTHENTICATED>;
193
+ export type AdminOutput<TSchema extends ResourceSchema> = OutputForTag<TSchema, typeof import('./tags.js').ADMIN>;
173
194
  /**
174
195
  * Fluent builder for constructing resource schemas
175
196
  *
@@ -197,141 +218,112 @@ export declare class ResourceSchemaBuilder<TFields extends readonly BuilderField
197
218
  static create(): ResourceSchemaBuilder<readonly []>;
198
219
  /**
199
220
  * Adds a public field (visible to everyone)
200
- *
201
- * @param name - Field name
202
- * @param schema - Zod schema for the field
203
- * @returns New builder with the field added
204
221
  */
205
- public<TName extends string, TSchema extends ZodType>(name: TName, schema: TSchema): ResourceSchemaBuilder<readonly [...TFields, ResourceField<TName, TSchema, 'public'>]>;
222
+ public<TName extends string, TSchema extends ZodType>(name: TName, schema: TSchema): ResourceSchemaBuilder<readonly [...TFields, ResourceField<TName, TSchema, 'public' | 'authenticated' | 'admin'>]>;
206
223
  /**
207
224
  * Adds an authenticated field (visible to authenticated users and admins)
208
- *
209
- * @param name - Field name
210
- * @param schema - Zod schema for the field
211
- * @returns New builder with the field added
212
225
  */
213
- authenticated<TName extends string, TSchema extends ZodType>(name: TName, schema: TSchema): ResourceSchemaBuilder<readonly [...TFields, ResourceField<TName, TSchema, 'authenticated'>]>;
226
+ authenticated<TName extends string, TSchema extends ZodType>(name: TName, schema: TSchema): ResourceSchemaBuilder<readonly [...TFields, ResourceField<TName, TSchema, 'authenticated' | 'admin'>]>;
214
227
  /**
215
228
  * Adds an admin field (visible only to admins)
216
- *
217
- * @param name - Field name
218
- * @param schema - Zod schema for the field
219
- * @returns New builder with the field added
220
229
  */
221
230
  admin<TName extends string, TSchema extends ZodType>(name: TName, schema: TSchema): ResourceSchemaBuilder<readonly [...TFields, ResourceField<TName, TSchema, 'admin'>]>;
222
231
  /**
223
232
  * Adds a has-one relation (nullable nested object)
224
- *
225
- * The relation's visibility controls WHETHER it appears in the output.
226
- * The parent's projection level controls WHAT fields of the nested schema are shown.
227
- *
228
- * **Note:** The nested schema's generic field types are tracked at compile time
229
- * for output type computation, but the runtime field stores an untyped
230
- * `ResourceSchema` reference. Always pass the direct result of `.build()`
231
- * to ensure the compile-time and runtime schemas stay in sync.
232
- *
233
- * @param name - Relation field name
234
- * @param nestedSchema - The nested resource schema (result of `.build()`)
235
- * @param visibility - Visibility level for this relation
236
- * @returns New builder with the relation added
237
- *
238
- * @example
239
- * ```typescript
240
- * const UserSchema = resourceSchema()
241
- * .public('id', z.string())
242
- * .hasOne('organization', OrgSchema, 'public')
243
- * .build();
244
- * ```
245
233
  */
246
- hasOne<TName extends string, TNestedFields extends readonly BuilderField[], TLevel extends VisibilityLevel>(name: TName, nestedSchema: ResourceSchema<TNestedFields>, visibility: TLevel): ResourceSchemaBuilder<readonly [...TFields, RelationField<TName, TNestedFields, TLevel, 'one'>]>;
234
+ hasOne<TName extends string, TNestedFields extends readonly BuilderField[], TLevel extends VisibilityLevel>(name: TName, nestedSchema: ResourceSchema<TNestedFields>, visibility: TLevel): ResourceSchemaBuilder<readonly [...TFields, RelationField<TName, TNestedFields, LevelToVisibleUnion<TLevel>, 'one'>]>;
247
235
  /**
248
236
  * Adds a has-many relation (array of nested objects)
249
- *
250
- * The relation's visibility controls WHETHER it appears in the output.
251
- * The parent's projection level controls WHAT fields of the nested schema are shown.
252
- *
253
- * **Note:** The nested schema's generic field types are tracked at compile time
254
- * for output type computation, but the runtime field stores an untyped
255
- * `ResourceSchema` reference. Always pass the direct result of `.build()`
256
- * to ensure the compile-time and runtime schemas stay in sync.
257
- *
258
- * @param name - Relation field name
259
- * @param nestedSchema - The nested resource schema (result of `.build()`)
260
- * @param visibility - Visibility level for this relation
261
- * @returns New builder with the relation added
262
- *
263
- * @example
264
- * ```typescript
265
- * const UserSchema = resourceSchema()
266
- * .public('id', z.string())
267
- * .hasMany('posts', PostSchema, 'authenticated')
268
- * .build();
269
- * ```
270
237
  */
271
- hasMany<TName extends string, TNestedFields extends readonly BuilderField[], TLevel extends VisibilityLevel>(name: TName, nestedSchema: ResourceSchema<TNestedFields>, visibility: TLevel): ResourceSchemaBuilder<readonly [...TFields, RelationField<TName, TNestedFields, TLevel, 'many'>]>;
238
+ hasMany<TName extends string, TNestedFields extends readonly BuilderField[], TLevel extends VisibilityLevel>(name: TName, nestedSchema: ResourceSchema<TNestedFields>, visibility: TLevel): ResourceSchemaBuilder<readonly [...TFields, RelationField<TName, TNestedFields, LevelToVisibleUnion<TLevel>, 'many'>]>;
272
239
  /**
273
240
  * Adds a field with explicit visibility level
274
- *
275
- * @param name - Field name
276
- * @param schema - Zod schema for the field
277
- * @param visibility - Visibility level
278
- * @returns New builder with the field added
279
241
  */
280
- field<TName extends string, TSchema extends ZodType, TLevel extends VisibilityLevel>(name: TName, schema: TSchema, visibility: TLevel): ResourceSchemaBuilder<readonly [...TFields, ResourceField<TName, TSchema, TLevel>]>;
242
+ field<TName extends string, TSchema extends ZodType, TLevel extends VisibilityLevel>(name: TName, schema: TSchema, visibility: TLevel): ResourceSchemaBuilder<readonly [...TFields, ResourceField<TName, TSchema, LevelToVisibleUnion<TLevel>>]>;
281
243
  /**
282
244
  * Builds the final resource schema with tagged views
283
- *
284
- * Returns a schema with `.public`, `.authenticated`, and `.admin`
285
- * properties for declarative projection in procedures.
286
- *
287
- * @returns Completed resource schema with tagged views
288
- *
289
- * @example
290
- * ```typescript
291
- * const UserSchema = resourceSchema()
292
- * .public('id', z.string())
293
- * .authenticated('email', z.string())
294
- * .build();
295
- *
296
- * // Use tagged views in procedures
297
- * procedure().resource(UserSchema.authenticated).query(handler);
298
- *
299
- * // Or in handlers
300
- * resource(data, UserSchema.authenticated);
301
- * ```
302
245
  */
303
246
  build(): ResourceSchemaWithViews<TFields>;
304
247
  }
248
+ /**
249
+ * Resolves a group reference to its union of level strings
250
+ */
251
+ type ResolveGroupToUnion<TLevels extends readonly string[], TGroups extends Record<string, '*' | readonly string[]>, K extends keyof TGroups> = TGroups[K] extends '*' ? TLevels[number] : TGroups[K] extends readonly (infer U extends string)[] ? U : never;
252
+ /**
253
+ * Resolves a group name or levels array to a union of level strings
254
+ */
255
+ type ResolveRefToUnion<TLevels extends readonly string[], TGroups extends Record<string, '*' | readonly string[]>, TRef> = TRef extends keyof TGroups ? ResolveGroupToUnion<TLevels, TGroups, TRef> : TRef extends readonly (infer U extends string)[] ? U : never;
256
+ /**
257
+ * Methods available on the custom schema builder (non-dynamic)
258
+ */
259
+ interface CustomSchemaBuilderMethods<TLevels extends readonly string[], TGroups extends Record<string, '*' | readonly string[]>, TFields extends readonly BuilderField[]> {
260
+ /** Adds a field visible to an explicit set of levels */
261
+ visibleTo<TName extends string, TSchema extends ZodType, const TSet extends readonly TLevels[number][]>(name: TName, schema: TSchema, levels: TSet): CustomSchemaBuilder<TLevels, TGroups, readonly [...TFields, ResourceField<TName, TSchema, TSet[number]>]>;
262
+ /** Adds a has-one relation */
263
+ hasOne<TName extends string, TNestedFields extends readonly BuilderField[], TRef extends (keyof TGroups & string) | readonly TLevels[number][]>(name: TName, nestedSchema: ResourceSchema<TNestedFields>, visibility: TRef): CustomSchemaBuilder<TLevels, TGroups, readonly [
264
+ ...TFields,
265
+ RelationField<TName, TNestedFields, ResolveRefToUnion<TLevels, TGroups, TRef>, 'one'>
266
+ ]>;
267
+ /** Adds a has-many relation */
268
+ hasMany<TName extends string, TNestedFields extends readonly BuilderField[], TRef extends (keyof TGroups & string) | readonly TLevels[number][]>(name: TName, nestedSchema: ResourceSchema<TNestedFields>, visibility: TRef): CustomSchemaBuilder<TLevels, TGroups, readonly [
269
+ ...TFields,
270
+ RelationField<TName, TNestedFields, ResolveRefToUnion<TLevels, TGroups, TRef>, 'many'>
271
+ ]>;
272
+ /** Builds the final schema with one tagged view per level */
273
+ build(): CustomResourceSchemaWithViews<TFields, TLevels>;
274
+ }
275
+ /**
276
+ * Custom schema builder type with dynamic methods from groups and levels
277
+ *
278
+ * Group names become fluent methods that resolve to the group's level set.
279
+ * Level names (that don't collide with groups) become methods for single-level visibility.
280
+ */
281
+ export type CustomSchemaBuilder<TLevels extends readonly string[], TGroups extends Record<string, '*' | readonly string[]>, TFields extends readonly BuilderField[] = readonly []> = {
282
+ [K in keyof TGroups & string]: <TName extends string, TSchema extends ZodType>(name: TName, schema: TSchema) => CustomSchemaBuilder<TLevels, TGroups, readonly [...TFields, ResourceField<TName, TSchema, ResolveGroupToUnion<TLevels, TGroups, K>>]>;
283
+ } & {
284
+ [K in TLevels[number] as K extends keyof TGroups ? never : K]: <TName extends string, TSchema extends ZodType>(name: TName, schema: TSchema) => CustomSchemaBuilder<TLevels, TGroups, readonly [...TFields, ResourceField<TName, TSchema, K>]>;
285
+ } & CustomSchemaBuilderMethods<TLevels, TGroups, TFields>;
305
286
  /**
306
287
  * Creates a new resource schema builder
307
288
  *
308
- * This is the primary entry point for defining resource schemas with
309
- * field-level visibility controls.
289
+ * Without arguments, returns the default 3-level builder with
290
+ * `.public()`, `.authenticated()`, `.admin()` methods.
310
291
  *
311
- * @returns New empty schema builder
292
+ * With an `AccessLevelConfig` argument (from `defineAccessLevels()`),
293
+ * returns a custom builder whose methods correspond to the defined
294
+ * levels and groups.
312
295
  *
313
- * @example
296
+ * @example Default (3-level)
314
297
  * ```typescript
315
- * import { z } from 'zod';
316
- * import { resourceSchema } from '@veloxts/router';
317
- *
318
298
  * const UserSchema = resourceSchema()
319
- * .public('id', z.string().uuid())
320
- * .public('name', z.string())
321
- * .public('avatarUrl', z.string().url().nullable())
322
- * .authenticated('email', z.string().email())
323
- * .authenticated('createdAt', z.date())
324
- * .admin('internalNotes', z.string().nullable())
325
- * .admin('lastLoginIp', z.string().nullable())
299
+ * .public('id', z.string())
300
+ * .authenticated('email', z.string())
301
+ * .admin('internalNotes', z.string())
326
302
  * .build();
303
+ * ```
327
304
  *
328
- * // Type inference:
329
- * // AnonymousOutput<typeof UserSchema> = { id: string; name: string; avatarUrl: string | null }
330
- * // AuthenticatedOutput<typeof UserSchema> = { id: string; name: string; avatarUrl: string | null; email: string; createdAt: Date }
331
- * // AdminOutput<typeof UserSchema> = { id: string; name: string; avatarUrl: string | null; email: string; createdAt: Date; internalNotes: string | null; lastLoginIp: string | null }
305
+ * @example Custom levels
306
+ * ```typescript
307
+ * const access = defineAccessLevels(
308
+ * ['public', 'reviewer', 'authenticated', 'moderator', 'admin'],
309
+ * { everyone: '*', staff: ['moderator', 'admin'] }
310
+ * );
311
+ *
312
+ * const ArticleSchema = resourceSchema(access)
313
+ * .everyone('id', z.string())
314
+ * .staff('moderationLog', z.string())
315
+ * .visibleTo('authorEmail', z.string(), ['authenticated', 'admin'])
316
+ * .build();
332
317
  * ```
333
318
  */
334
319
  export declare function resourceSchema(): ResourceSchemaBuilder<readonly []>;
320
+ export declare function resourceSchema<const TLevels extends readonly [string, ...string[]], const TGroups extends Record<string, '*' | readonly string[]>>(config: AccessLevelConfig<TLevels, TGroups>): CustomSchemaBuilder<TLevels, TGroups, readonly []>;
321
+ /**
322
+ * Type guard to check if a schema is a TaggedResourceSchema (has _level)
323
+ *
324
+ * Accepts any string _level to support custom access levels.
325
+ */
326
+ export declare function isTaggedResourceSchema(value: unknown): value is TaggedResourceSchema;
335
327
  /**
336
328
  * Type guard to check if a value is a ResourceSchema
337
329
  */