@veloxts/router 0.7.5 → 0.7.7
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.
- package/CHANGELOG.md +18 -0
- package/GUIDE.md +11 -36
- package/dist/index.d.ts +7 -7
- package/dist/index.js +8 -6
- package/dist/openapi/generator.js +9 -4
- package/dist/openapi/index.d.ts +3 -4
- package/dist/openapi/index.js +3 -4
- package/dist/openapi/plugin.d.ts +3 -47
- package/dist/openapi/plugin.js +3 -51
- package/dist/openapi/schema-converter.d.ts +13 -0
- package/dist/openapi/schema-converter.js +51 -0
- package/dist/procedure/builder.js +33 -21
- package/dist/procedure/types.d.ts +67 -28
- package/dist/resource/index.d.ts +7 -5
- package/dist/resource/index.js +3 -2
- package/dist/resource/instance.d.ts +38 -20
- package/dist/resource/instance.js +51 -44
- package/dist/resource/levels.d.ts +71 -0
- package/dist/resource/levels.js +109 -0
- package/dist/resource/schema.d.ts +151 -159
- package/dist/resource/schema.js +132 -124
- package/dist/resource/tags.d.ts +35 -15
- package/dist/resource/tags.js +1 -1
- package/dist/resource/types.d.ts +10 -8
- package/dist/resource/visibility.d.ts +16 -3
- package/dist/resource/visibility.js +16 -0
- package/dist/types.d.ts +28 -5
- package/package.json +3 -3
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { BaseContext } from '@veloxts/core';
|
|
13
13
|
import type { ZodType } from 'zod';
|
|
14
|
-
import type { OutputForTag, ResourceSchema, TaggedResourceSchema } from '../resource/index.js';
|
|
14
|
+
import type { FilterFieldsByLevel, OutputForTag, ResourceSchema, TaggedResourceSchema } from '../resource/index.js';
|
|
15
15
|
import type { ContextTag, ExtractTag, LevelToTag, TaggedContext } from '../resource/tags.js';
|
|
16
16
|
import type { CompiledProcedure, GuardLike, MiddlewareFunction, ParentResourceConfig, ProcedureHandler, RestRouteOverride } from '../types.js';
|
|
17
17
|
/**
|
|
@@ -94,10 +94,13 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
94
94
|
*/
|
|
95
95
|
input<TSchema extends ValidSchema>(schema: TSchema): ProcedureBuilder<InferSchemaOutput<TSchema>, TOutput, TContext>;
|
|
96
96
|
/**
|
|
97
|
-
* Defines the output validation schema
|
|
97
|
+
* Defines the output validation schema (Zod)
|
|
98
98
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
* Sets a Zod schema that validates the handler's return value.
|
|
100
|
+
* All callers receive the same fields.
|
|
101
|
+
*
|
|
102
|
+
* For field-level visibility (different fields per access level),
|
|
103
|
+
* use `.expose()` with a resource schema instead.
|
|
101
104
|
*
|
|
102
105
|
* @template TSchema - The Zod schema type
|
|
103
106
|
* @param schema - Zod schema for output validation
|
|
@@ -106,14 +109,39 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
106
109
|
* @example
|
|
107
110
|
* ```typescript
|
|
108
111
|
* procedure()
|
|
109
|
-
* .output(z.object({
|
|
110
|
-
*
|
|
111
|
-
* name: z.string(),
|
|
112
|
-
* }))
|
|
113
|
-
* // handler must return { id: string; name: string }
|
|
112
|
+
* .output(z.object({ id: z.string(), name: z.string() }))
|
|
113
|
+
* .query(handler) // handler must return { id: string; name: string }
|
|
114
114
|
* ```
|
|
115
115
|
*/
|
|
116
116
|
output<TSchema extends ValidSchema>(schema: TSchema): ProcedureBuilder<TInput, InferSchemaOutput<TSchema>, TContext>;
|
|
117
|
+
/**
|
|
118
|
+
* Sets field-level visibility via a resource schema
|
|
119
|
+
*
|
|
120
|
+
* Accepts two resource schema variants:
|
|
121
|
+
* 1. **Tagged resource schema** (e.g., `UserSchema.authenticated`) — explicit field projection by access level
|
|
122
|
+
* 2. **Plain resource schema** (e.g., `UserSchema`) — context-derived field projection from `guardNarrow`
|
|
123
|
+
*
|
|
124
|
+
* @template TSchema - The resource schema type
|
|
125
|
+
* @param schema - Resource schema for field projection
|
|
126
|
+
* @returns New builder with updated output type
|
|
127
|
+
*
|
|
128
|
+
* @example Tagged resource schema — explicit projection level
|
|
129
|
+
* ```typescript
|
|
130
|
+
* procedure()
|
|
131
|
+
* .guard(authenticated)
|
|
132
|
+
* .expose(UserSchema.authenticated) // returns { id, name, email }
|
|
133
|
+
* .query(handler)
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @example Plain resource schema — derives level from guardNarrow
|
|
137
|
+
* ```typescript
|
|
138
|
+
* procedure()
|
|
139
|
+
* .guardNarrow(authenticatedNarrow)
|
|
140
|
+
* .expose(UserSchema) // auto-projects based on guard's accessLevel
|
|
141
|
+
* .query(handler)
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
expose<TSchema extends ResourceSchema>(schema: TSchema): ProcedureBuilder<TInput, TSchema extends TaggedResourceSchema<infer TFields, infer TLevel> ? TLevel extends 'admin' | 'authenticated' | 'public' ? OutputForTag<ResourceSchema<TFields>, LevelToTag<TLevel>> : FilterFieldsByLevel<TFields, TLevel> : TContext extends TaggedContext<infer TTag> ? TTag extends ContextTag ? OutputForTag<TSchema, TTag> : OutputForTag<TSchema, ExtractTag<TContext>> : OutputForTag<TSchema, ExtractTag<TContext>>, TContext>;
|
|
117
145
|
/**
|
|
118
146
|
* Adds middleware to the procedure chain
|
|
119
147
|
*
|
|
@@ -255,6 +283,26 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
255
283
|
* ```
|
|
256
284
|
*/
|
|
257
285
|
rest(config: RestRouteOverride): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
286
|
+
/**
|
|
287
|
+
* Configures the procedure as a webhook endpoint
|
|
288
|
+
*
|
|
289
|
+
* Sugar for `.rest({ method: 'POST', path })` with a webhook flag.
|
|
290
|
+
* Webhook procedures expect raw body access for signature verification.
|
|
291
|
+
*
|
|
292
|
+
* @param path - The webhook endpoint path (e.g., '/webhooks/stripe')
|
|
293
|
+
* @returns New builder with webhook configuration
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* procedure()
|
|
298
|
+
* .webhook('/webhooks/stripe')
|
|
299
|
+
* .mutation(async ({ input, ctx }) => {
|
|
300
|
+
* const isValid = verifySignature(ctx.request.rawBody, ctx.request.headers['stripe-signature']);
|
|
301
|
+
* // ...
|
|
302
|
+
* })
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
webhook(path: string): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
258
306
|
/**
|
|
259
307
|
* Marks the procedure as deprecated
|
|
260
308
|
*
|
|
@@ -377,28 +425,17 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
377
425
|
*/
|
|
378
426
|
mutation(handler: ProcedureHandler<TInput, TOutput, TContext>): CompiledProcedure<TInput, TOutput, TContext, 'mutation'>;
|
|
379
427
|
/**
|
|
380
|
-
*
|
|
428
|
+
* @deprecated Use `.expose()` instead. `.resource()` will be removed in v1.0.
|
|
381
429
|
*
|
|
382
|
-
*
|
|
383
|
-
* explicit auto-projection, or a plain schema for backward compatibility.
|
|
430
|
+
* Sets field-level visibility via a resource schema.
|
|
384
431
|
*
|
|
385
|
-
*
|
|
386
|
-
* tag's access level. When a plain schema is used, the output type is
|
|
387
|
-
* derived from the context's phantom tag (set by `guardNarrow`).
|
|
388
|
-
*
|
|
389
|
-
* @example
|
|
432
|
+
* @example Migration
|
|
390
433
|
* ```typescript
|
|
391
|
-
* //
|
|
392
|
-
* procedure()
|
|
393
|
-
* .guard(authenticated)
|
|
394
|
-
* .resource(UserSchema.authenticated)
|
|
395
|
-
* .query(async ({ ctx }) => ctx.db.user.findUnique(...));
|
|
434
|
+
* // Before
|
|
435
|
+
* procedure().resource(UserSchema.authenticated).query(handler)
|
|
396
436
|
*
|
|
397
|
-
* //
|
|
398
|
-
* procedure()
|
|
399
|
-
* .guardNarrow(authenticatedNarrow)
|
|
400
|
-
* .resource(UserSchema)
|
|
401
|
-
* .query(async ({ ctx }) => ctx.db.user.findUnique(...));
|
|
437
|
+
* // After
|
|
438
|
+
* procedure().expose(UserSchema.authenticated).query(handler)
|
|
402
439
|
* ```
|
|
403
440
|
*/
|
|
404
441
|
resource<TSchema extends ResourceSchema>(schema: TSchema): ProcedureBuilder<TInput, TSchema extends TaggedResourceSchema<infer TFields, infer TLevel> ? OutputForTag<ResourceSchema<TFields>, LevelToTag<TLevel>> : TContext extends TaggedContext<infer TTag> ? TTag extends ContextTag ? OutputForTag<TSchema, TTag> : OutputForTag<TSchema, ExtractTag<TContext>> : OutputForTag<TSchema, ExtractTag<TContext>>, TContext>;
|
|
@@ -417,7 +454,7 @@ export interface BuilderRuntimeState {
|
|
|
417
454
|
/** Resource schema for context-dependent output */
|
|
418
455
|
resourceSchema?: ResourceSchema;
|
|
419
456
|
/** Explicit resource level from tagged schema (e.g., UserSchema.authenticated) */
|
|
420
|
-
resourceLevel?:
|
|
457
|
+
resourceLevel?: string;
|
|
421
458
|
/** Middleware chain */
|
|
422
459
|
middlewares: MiddlewareFunction<unknown, BaseContext, BaseContext, unknown>[];
|
|
423
460
|
/** Guards to execute before handler */
|
|
@@ -432,6 +469,8 @@ export interface BuilderRuntimeState {
|
|
|
432
469
|
deprecated?: boolean;
|
|
433
470
|
/** Deprecation message */
|
|
434
471
|
deprecationMessage?: string;
|
|
472
|
+
/** Whether this procedure is a webhook endpoint (metadata marker) */
|
|
473
|
+
isWebhook?: boolean;
|
|
435
474
|
}
|
|
436
475
|
/**
|
|
437
476
|
* Type for the procedures object passed to defineProcedures
|
package/dist/resource/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* .input(z.object({ id: z.string() }))
|
|
35
35
|
* .query(async ({ input, ctx }) => {
|
|
36
36
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
37
|
-
* return resource(user, UserSchema).
|
|
37
|
+
* return resource(user, UserSchema).forPublic();
|
|
38
38
|
* }),
|
|
39
39
|
*
|
|
40
40
|
* // Authenticated endpoint → returns { id, name, email }
|
|
@@ -60,9 +60,11 @@
|
|
|
60
60
|
* @module resource
|
|
61
61
|
*/
|
|
62
62
|
export { Resource, ResourceCollection, resource, resourceCollection } from './instance.js';
|
|
63
|
-
export type {
|
|
63
|
+
export type { AccessLevelConfig } from './levels.js';
|
|
64
|
+
export { defineAccessLevels } from './levels.js';
|
|
65
|
+
export type { AdminOutput, AnonymousOutput, AuthenticatedOutput, BuilderField, CustomResourceSchemaWithViews, CustomSchemaBuilder, FilterFieldsByLevel, OutputForLevel, OutputForTag, PublicOutput, RelationField, ResourceField, ResourceSchema, ResourceSchemaWithViews, RuntimeField, TaggedResourceSchema, } from './schema.js';
|
|
64
66
|
export { isResourceSchema, isTaggedResourceSchema, ResourceSchemaBuilder, resourceSchema, } from './schema.js';
|
|
65
|
-
export type { AccessLevel, ADMIN, ANONYMOUS, AUTHENTICATED, ContextTag, ExtractTag, HasTag, LevelToTag, TaggedContext, WithTag, } from './tags.js';
|
|
66
|
-
export type { AdminTaggedContext, AnonymousTaggedContext, AnyResourceOutput, AuthenticatedTaggedContext, IfAdmin, IfAuthenticated, InferResourceData, InferResourceOutput, } from './types.js';
|
|
67
|
+
export type { AccessLevel, ADMIN, ANONYMOUS, AUTHENTICATED, ContextTag, ExtractTag, HasTag, LevelToTag, PUBLIC, TaggedContext, TagToLevel, WithTag, } from './tags.js';
|
|
68
|
+
export type { AdminTaggedContext, AnonymousTaggedContext, AnyResourceOutput, AuthenticatedTaggedContext, IfAdmin, IfAuthenticated, InferResourceData, InferResourceOutput, PublicTaggedContext, } from './types.js';
|
|
67
69
|
export type { IsVisibleToTag, VisibilityLevel } from './visibility.js';
|
|
68
|
-
export { getAccessibleLevels, getVisibilityForTag, isVisibleAtLevel } from './visibility.js';
|
|
70
|
+
export { getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isVisibleAtLevel, } from './visibility.js';
|
package/dist/resource/index.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* .input(z.object({ id: z.string() }))
|
|
35
35
|
* .query(async ({ input, ctx }) => {
|
|
36
36
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
37
|
-
* return resource(user, UserSchema).
|
|
37
|
+
* return resource(user, UserSchema).forPublic();
|
|
38
38
|
* }),
|
|
39
39
|
*
|
|
40
40
|
* // Authenticated endpoint → returns { id, name, email }
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
// ============================================================================
|
|
65
65
|
// Resource instances
|
|
66
66
|
export { Resource, ResourceCollection, resource, resourceCollection } from './instance.js';
|
|
67
|
+
export { defineAccessLevels } from './levels.js';
|
|
67
68
|
// Schema builder
|
|
68
69
|
export { isResourceSchema, isTaggedResourceSchema, ResourceSchemaBuilder, resourceSchema, } from './schema.js';
|
|
69
70
|
// Visibility
|
|
70
|
-
export { getAccessibleLevels, getVisibilityForTag, isVisibleAtLevel } from './visibility.js';
|
|
71
|
+
export { getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isVisibleAtLevel, } from './visibility.js';
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module resource/instance
|
|
8
8
|
*/
|
|
9
|
-
import type { OutputForLevel, OutputForTag, ResourceSchema, TaggedResourceSchema } from './schema.js';
|
|
10
|
-
import type { ADMIN,
|
|
9
|
+
import type { FilterFieldsByLevel, OutputForLevel, OutputForTag, ResourceSchema, TaggedResourceSchema } from './schema.js';
|
|
10
|
+
import type { ADMIN, AUTHENTICATED, ContextTag, ExtractTag, PUBLIC, TaggedContext } from './tags.js';
|
|
11
11
|
/**
|
|
12
12
|
* A resource instance that can project data based on access level
|
|
13
13
|
*
|
|
@@ -23,7 +23,7 @@ import type { ADMIN, ANONYMOUS, AUTHENTICATED, ContextTag, ExtractTag, TaggedCon
|
|
|
23
23
|
* const resource = new Resource(user, UserSchema);
|
|
24
24
|
*
|
|
25
25
|
* // Returns only public fields: { id, name }
|
|
26
|
-
* const publicData = resource.
|
|
26
|
+
* const publicData = resource.forPublic();
|
|
27
27
|
*
|
|
28
28
|
* // Returns public + authenticated fields: { id, name, email }
|
|
29
29
|
* const authData = resource.forAuthenticated();
|
|
@@ -37,11 +37,13 @@ export declare class Resource<TSchema extends ResourceSchema> {
|
|
|
37
37
|
private readonly _schema;
|
|
38
38
|
constructor(data: Record<string, unknown>, schema: TSchema);
|
|
39
39
|
/**
|
|
40
|
-
* Projects data for
|
|
40
|
+
* Projects data for public (unauthenticated) access
|
|
41
41
|
*
|
|
42
42
|
* Returns only fields marked as 'public'.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
forPublic(): OutputForTag<TSchema, typeof PUBLIC>;
|
|
45
|
+
/** @deprecated Use forPublic() */
|
|
46
|
+
forAnonymous(): OutputForTag<TSchema, typeof PUBLIC>;
|
|
45
47
|
/**
|
|
46
48
|
* Projects data for authenticated user access
|
|
47
49
|
*
|
|
@@ -54,6 +56,22 @@ export declare class Resource<TSchema extends ResourceSchema> {
|
|
|
54
56
|
* Returns all fields (public, authenticated, and admin).
|
|
55
57
|
*/
|
|
56
58
|
forAdmin(): OutputForTag<TSchema, typeof ADMIN>;
|
|
59
|
+
/**
|
|
60
|
+
* Projects data for an explicit access level string
|
|
61
|
+
*
|
|
62
|
+
* Works with both default levels ('public', 'authenticated', 'admin')
|
|
63
|
+
* and custom levels defined via `defineAccessLevels()`.
|
|
64
|
+
*
|
|
65
|
+
* @param level - The access level to project for
|
|
66
|
+
* @returns Object with only the fields visible to the given level
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* // Custom level projection
|
|
71
|
+
* const data = resource.forLevel('reviewer');
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
forLevel<TLevel extends string>(level: TLevel): TSchema extends ResourceSchema<infer TFields> ? FilterFieldsByLevel<TFields, TLevel> : Record<string, unknown>;
|
|
57
75
|
/**
|
|
58
76
|
* Projects data based on a tagged context
|
|
59
77
|
*
|
|
@@ -70,16 +88,6 @@ export declare class Resource<TSchema extends ResourceSchema> {
|
|
|
70
88
|
* ```
|
|
71
89
|
*/
|
|
72
90
|
for<TContext extends TaggedContext<ContextTag>>(ctx: TContext): OutputForTag<TSchema, ExtractTag<TContext>>;
|
|
73
|
-
/**
|
|
74
|
-
* Projects data for an explicit visibility level
|
|
75
|
-
*
|
|
76
|
-
* Delegates to the standalone `projectData()` function which supports
|
|
77
|
-
* recursive projection of nested relations.
|
|
78
|
-
*
|
|
79
|
-
* @param level - The visibility level to project for
|
|
80
|
-
* @returns Object with only the visible fields (null prototype)
|
|
81
|
-
*/
|
|
82
|
-
private _project;
|
|
83
91
|
/**
|
|
84
92
|
* Infers the visibility level from a context object
|
|
85
93
|
*
|
|
@@ -104,7 +112,7 @@ export declare class Resource<TSchema extends ResourceSchema> {
|
|
|
104
112
|
* const collection = new ResourceCollection(users, UserSchema);
|
|
105
113
|
*
|
|
106
114
|
* // Returns array of public views
|
|
107
|
-
* const publicList = collection.
|
|
115
|
+
* const publicList = collection.forPublic();
|
|
108
116
|
*
|
|
109
117
|
* // Returns array with authenticated fields
|
|
110
118
|
* const authList = collection.forAuthenticated();
|
|
@@ -115,9 +123,11 @@ export declare class ResourceCollection<TSchema extends ResourceSchema> {
|
|
|
115
123
|
private readonly _schema;
|
|
116
124
|
constructor(items: Array<Record<string, unknown>>, schema: TSchema);
|
|
117
125
|
/**
|
|
118
|
-
* Projects all items for
|
|
126
|
+
* Projects all items for public (unauthenticated) access
|
|
119
127
|
*/
|
|
120
|
-
|
|
128
|
+
forPublic(): Array<OutputForTag<TSchema, typeof PUBLIC>>;
|
|
129
|
+
/** @deprecated Use forPublic() */
|
|
130
|
+
forAnonymous(): Array<OutputForTag<TSchema, typeof PUBLIC>>;
|
|
121
131
|
/**
|
|
122
132
|
* Projects all items for authenticated user access
|
|
123
133
|
*/
|
|
@@ -126,6 +136,14 @@ export declare class ResourceCollection<TSchema extends ResourceSchema> {
|
|
|
126
136
|
* Projects all items for admin access
|
|
127
137
|
*/
|
|
128
138
|
forAdmin(): Array<OutputForTag<TSchema, typeof ADMIN>>;
|
|
139
|
+
/**
|
|
140
|
+
* Projects all items for an explicit access level string
|
|
141
|
+
*
|
|
142
|
+
* Works with both default and custom access levels.
|
|
143
|
+
*
|
|
144
|
+
* @param level - The access level to project for
|
|
145
|
+
*/
|
|
146
|
+
forLevel<TLevel extends string>(level: TLevel): TSchema extends ResourceSchema<infer TFields> ? Array<FilterFieldsByLevel<TFields, TLevel>> : Array<Record<string, unknown>>;
|
|
129
147
|
/**
|
|
130
148
|
* Projects all items based on a tagged context
|
|
131
149
|
*/
|
|
@@ -144,8 +162,8 @@ export declare class ResourceCollection<TSchema extends ResourceSchema> {
|
|
|
144
162
|
*
|
|
145
163
|
* When called with a tagged schema (e.g., `UserSchema.authenticated`),
|
|
146
164
|
* returns the projected data directly. When called with an untagged schema,
|
|
147
|
-
* returns a Resource instance with `.
|
|
148
|
-
* `.forAdmin()` methods.
|
|
165
|
+
* returns a Resource instance with `.forPublic()`, `.forAuthenticated()`,
|
|
166
|
+
* `.forAdmin()`, `.forLevel()` methods.
|
|
149
167
|
*
|
|
150
168
|
* @param data - The raw data object
|
|
151
169
|
* @param schema - Resource schema (tagged for direct projection, untagged for Resource instance)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module resource/instance
|
|
8
8
|
*/
|
|
9
9
|
import { isTaggedResourceSchema } from './schema.js';
|
|
10
|
-
import {
|
|
10
|
+
import { isFieldVisibleToLevel } from './visibility.js';
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// Security Constants
|
|
13
13
|
// ============================================================================
|
|
@@ -38,7 +38,7 @@ const MAX_PROJECTION_DEPTH = 10;
|
|
|
38
38
|
* @internal
|
|
39
39
|
* @param data - The raw data object
|
|
40
40
|
* @param schema - The resource schema to project against
|
|
41
|
-
* @param level - The
|
|
41
|
+
* @param level - The access level to project for (any string)
|
|
42
42
|
* @param depth - Current recursion depth (defaults to 0)
|
|
43
43
|
* @param visited - Set of already-visited data objects for cycle detection
|
|
44
44
|
* @returns Projected object with null prototype
|
|
@@ -54,7 +54,7 @@ function projectData(data, schema, level, depth = 0, visited) {
|
|
|
54
54
|
seen.add(data);
|
|
55
55
|
const result = Object.create(null);
|
|
56
56
|
for (const field of schema.fields) {
|
|
57
|
-
if (!
|
|
57
|
+
if (!isFieldVisibleToLevel(field, level))
|
|
58
58
|
continue;
|
|
59
59
|
if (DANGEROUS_PROPERTIES.has(field.name))
|
|
60
60
|
continue;
|
|
@@ -107,7 +107,7 @@ function projectData(data, schema, level, depth = 0, visited) {
|
|
|
107
107
|
* const resource = new Resource(user, UserSchema);
|
|
108
108
|
*
|
|
109
109
|
* // Returns only public fields: { id, name }
|
|
110
|
-
* const publicData = resource.
|
|
110
|
+
* const publicData = resource.forPublic();
|
|
111
111
|
*
|
|
112
112
|
* // Returns public + authenticated fields: { id, name, email }
|
|
113
113
|
* const authData = resource.forAuthenticated();
|
|
@@ -124,12 +124,16 @@ export class Resource {
|
|
|
124
124
|
this._schema = schema;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
* Projects data for
|
|
127
|
+
* Projects data for public (unauthenticated) access
|
|
128
128
|
*
|
|
129
129
|
* Returns only fields marked as 'public'.
|
|
130
130
|
*/
|
|
131
|
+
forPublic() {
|
|
132
|
+
return this.forLevel('public');
|
|
133
|
+
}
|
|
134
|
+
/** @deprecated Use forPublic() */
|
|
131
135
|
forAnonymous() {
|
|
132
|
-
return this.
|
|
136
|
+
return this.forPublic();
|
|
133
137
|
}
|
|
134
138
|
/**
|
|
135
139
|
* Projects data for authenticated user access
|
|
@@ -137,7 +141,7 @@ export class Resource {
|
|
|
137
141
|
* Returns fields marked as 'public' or 'authenticated'.
|
|
138
142
|
*/
|
|
139
143
|
forAuthenticated() {
|
|
140
|
-
return this.
|
|
144
|
+
return this.forLevel('authenticated');
|
|
141
145
|
}
|
|
142
146
|
/**
|
|
143
147
|
* Projects data for admin access
|
|
@@ -145,7 +149,25 @@ export class Resource {
|
|
|
145
149
|
* Returns all fields (public, authenticated, and admin).
|
|
146
150
|
*/
|
|
147
151
|
forAdmin() {
|
|
148
|
-
return this.
|
|
152
|
+
return this.forLevel('admin');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Projects data for an explicit access level string
|
|
156
|
+
*
|
|
157
|
+
* Works with both default levels ('public', 'authenticated', 'admin')
|
|
158
|
+
* and custom levels defined via `defineAccessLevels()`.
|
|
159
|
+
*
|
|
160
|
+
* @param level - The access level to project for
|
|
161
|
+
* @returns Object with only the fields visible to the given level
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* // Custom level projection
|
|
166
|
+
* const data = resource.forLevel('reviewer');
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
forLevel(level) {
|
|
170
|
+
return projectData(this._data, this._schema, level);
|
|
149
171
|
}
|
|
150
172
|
/**
|
|
151
173
|
* Projects data based on a tagged context
|
|
@@ -166,19 +188,7 @@ export class Resource {
|
|
|
166
188
|
// At runtime, we need to determine the level from context properties
|
|
167
189
|
// Since the tag is phantom (doesn't exist at runtime), we use heuristics
|
|
168
190
|
const level = this._inferLevelFromContext(ctx);
|
|
169
|
-
return this.
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Projects data for an explicit visibility level
|
|
173
|
-
*
|
|
174
|
-
* Delegates to the standalone `projectData()` function which supports
|
|
175
|
-
* recursive projection of nested relations.
|
|
176
|
-
*
|
|
177
|
-
* @param level - The visibility level to project for
|
|
178
|
-
* @returns Object with only the visible fields (null prototype)
|
|
179
|
-
*/
|
|
180
|
-
_project(level) {
|
|
181
|
-
return projectData(this._data, this._schema, level);
|
|
191
|
+
return this.forLevel(level);
|
|
182
192
|
}
|
|
183
193
|
/**
|
|
184
194
|
* Infers the visibility level from a context object
|
|
@@ -232,7 +242,7 @@ export class Resource {
|
|
|
232
242
|
* const collection = new ResourceCollection(users, UserSchema);
|
|
233
243
|
*
|
|
234
244
|
* // Returns array of public views
|
|
235
|
-
* const publicList = collection.
|
|
245
|
+
* const publicList = collection.forPublic();
|
|
236
246
|
*
|
|
237
247
|
* // Returns array with authenticated fields
|
|
238
248
|
* const authList = collection.forAuthenticated();
|
|
@@ -246,10 +256,14 @@ export class ResourceCollection {
|
|
|
246
256
|
this._schema = schema;
|
|
247
257
|
}
|
|
248
258
|
/**
|
|
249
|
-
* Projects all items for
|
|
259
|
+
* Projects all items for public (unauthenticated) access
|
|
250
260
|
*/
|
|
261
|
+
forPublic() {
|
|
262
|
+
return this._items.map((item) => new Resource(item, this._schema).forPublic());
|
|
263
|
+
}
|
|
264
|
+
/** @deprecated Use forPublic() */
|
|
251
265
|
forAnonymous() {
|
|
252
|
-
return this.
|
|
266
|
+
return this.forPublic();
|
|
253
267
|
}
|
|
254
268
|
/**
|
|
255
269
|
* Projects all items for authenticated user access
|
|
@@ -263,6 +277,16 @@ export class ResourceCollection {
|
|
|
263
277
|
forAdmin() {
|
|
264
278
|
return this._items.map((item) => new Resource(item, this._schema).forAdmin());
|
|
265
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Projects all items for an explicit access level string
|
|
282
|
+
*
|
|
283
|
+
* Works with both default and custom access levels.
|
|
284
|
+
*
|
|
285
|
+
* @param level - The access level to project for
|
|
286
|
+
*/
|
|
287
|
+
forLevel(level) {
|
|
288
|
+
return this._items.map((item) => new Resource(item, this._schema).forLevel(level));
|
|
289
|
+
}
|
|
266
290
|
/**
|
|
267
291
|
* Projects all items based on a tagged context
|
|
268
292
|
*/
|
|
@@ -284,31 +308,14 @@ export class ResourceCollection {
|
|
|
284
308
|
}
|
|
285
309
|
export function resource(data, schema) {
|
|
286
310
|
if (isTaggedResourceSchema(schema)) {
|
|
287
|
-
|
|
288
|
-
switch (schema._level) {
|
|
289
|
-
case 'admin':
|
|
290
|
-
return r.forAdmin();
|
|
291
|
-
case 'authenticated':
|
|
292
|
-
return r.forAuthenticated();
|
|
293
|
-
default:
|
|
294
|
-
return r.forAnonymous();
|
|
295
|
-
}
|
|
311
|
+
return new Resource(data, schema).forLevel(schema._level);
|
|
296
312
|
}
|
|
297
313
|
return new Resource(data, schema);
|
|
298
314
|
}
|
|
299
315
|
export function resourceCollection(items, schema) {
|
|
300
316
|
if (isTaggedResourceSchema(schema)) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
switch (schema._level) {
|
|
304
|
-
case 'admin':
|
|
305
|
-
return r.forAdmin();
|
|
306
|
-
case 'authenticated':
|
|
307
|
-
return r.forAuthenticated();
|
|
308
|
-
default:
|
|
309
|
-
return r.forAnonymous();
|
|
310
|
-
}
|
|
311
|
-
});
|
|
317
|
+
const level = schema._level;
|
|
318
|
+
return items.map((item) => new Resource(item, schema).forLevel(level));
|
|
312
319
|
}
|
|
313
320
|
return new ResourceCollection(items, schema);
|
|
314
321
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access level configuration for custom visibility systems
|
|
3
|
+
*
|
|
4
|
+
* Provides `defineAccessLevels()` for creating custom access level
|
|
5
|
+
* hierarchies with named groups. The default 3-level system
|
|
6
|
+
* (public/authenticated/admin) is a special case of this.
|
|
7
|
+
*
|
|
8
|
+
* @module resource/levels
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The built-in 3-level hierarchy used when `resourceSchema()` is called
|
|
12
|
+
* without arguments.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_LEVELS: readonly ["public", "authenticated", "admin"];
|
|
15
|
+
export type DefaultLevels = typeof DEFAULT_LEVELS;
|
|
16
|
+
/**
|
|
17
|
+
* Configuration object returned by `defineAccessLevels()`.
|
|
18
|
+
*
|
|
19
|
+
* Holds the defined levels, named groups, and a `resolve()` method
|
|
20
|
+
* that expands a group name to the concrete set of levels it covers.
|
|
21
|
+
*
|
|
22
|
+
* @template TLevels - Tuple of level name literals
|
|
23
|
+
* @template TGroups - Record mapping group names to `'*'` or level arrays
|
|
24
|
+
*/
|
|
25
|
+
export interface AccessLevelConfig<TLevels extends readonly string[] = readonly string[], TGroups extends Record<string, '*' | readonly string[]> = Record<string, never>> {
|
|
26
|
+
readonly levels: TLevels;
|
|
27
|
+
readonly groups: TGroups;
|
|
28
|
+
/** Resolves a group name to the concrete set of levels */
|
|
29
|
+
resolve(ref: keyof TGroups & string): ReadonlySet<string>;
|
|
30
|
+
/** Returns the set containing all defined levels */
|
|
31
|
+
allLevels(): ReadonlySet<string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Defines custom access levels and optional named groups.
|
|
35
|
+
*
|
|
36
|
+
* Groups become fluent builder methods on `resourceSchema(config)`.
|
|
37
|
+
* The `'*'` wildcard resolves to all defined levels.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const access = defineAccessLevels(
|
|
42
|
+
* ['public', 'reviewer', 'authenticated', 'moderator', 'admin'],
|
|
43
|
+
* {
|
|
44
|
+
* everyone: '*',
|
|
45
|
+
* internal: ['reviewer', 'moderator', 'admin'],
|
|
46
|
+
* staff: ['moderator', 'admin'],
|
|
47
|
+
* }
|
|
48
|
+
* );
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function defineAccessLevels<const TLevels extends readonly [string, ...string[]]>(levels: TLevels): AccessLevelConfig<TLevels, Record<string, never>>;
|
|
52
|
+
export declare function defineAccessLevels<const TLevels extends readonly [string, ...string[]], const TGroups extends Record<string, '*' | readonly NoInfer<TLevels[number]>[]>>(levels: TLevels, groups: TGroups): AccessLevelConfig<TLevels, TGroups>;
|
|
53
|
+
/**
|
|
54
|
+
* Pre-built config for the default 3-level system.
|
|
55
|
+
*
|
|
56
|
+
* Used internally by the default `resourceSchema()` builder.
|
|
57
|
+
* The default levels use a hierarchical model where higher levels
|
|
58
|
+
* include all fields visible to lower levels.
|
|
59
|
+
*/
|
|
60
|
+
export declare const DEFAULT_ACCESS_LEVELS: AccessLevelConfig<DefaultLevels, Record<string, never>>;
|
|
61
|
+
/**
|
|
62
|
+
* Converts a default hierarchical level to the set of levels that can see
|
|
63
|
+
* a field at that visibility.
|
|
64
|
+
*
|
|
65
|
+
* - `'public'` → `Set(['public', 'authenticated', 'admin'])`
|
|
66
|
+
* - `'authenticated'` → `Set(['authenticated', 'admin'])`
|
|
67
|
+
* - `'admin'` → `Set(['admin'])`
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export declare function defaultLevelToSet(level: string): ReadonlySet<string>;
|