@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.
- package/CHANGELOG.md +18 -0
- package/GUIDE.md +9 -9
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -2
- package/dist/openapi/generator.js +9 -4
- 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
package/dist/resource/schema.js
CHANGED
|
@@ -5,20 +5,12 @@
|
|
|
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
|
-
|
|
11
|
-
* Type guard to check if a schema is a TaggedResourceSchema (has _level)
|
|
12
|
-
*/
|
|
13
|
-
export function isTaggedResourceSchema(value) {
|
|
14
|
-
if (typeof value !== 'object' || value === null) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
const obj = value;
|
|
18
|
-
return (Array.isArray(obj.fields) &&
|
|
19
|
-
typeof obj._level === 'string' &&
|
|
20
|
-
(obj._level === 'public' || obj._level === 'authenticated' || obj._level === 'admin'));
|
|
21
|
-
}
|
|
13
|
+
import { defaultLevelToSet } from './levels.js';
|
|
22
14
|
// ============================================================================
|
|
23
15
|
// Schema Builder
|
|
24
16
|
// ============================================================================
|
|
@@ -53,66 +45,43 @@ export class ResourceSchemaBuilder {
|
|
|
53
45
|
}
|
|
54
46
|
/**
|
|
55
47
|
* Adds a public field (visible to everyone)
|
|
56
|
-
*
|
|
57
|
-
* @param name - Field name
|
|
58
|
-
* @param schema - Zod schema for the field
|
|
59
|
-
* @returns New builder with the field added
|
|
60
48
|
*/
|
|
61
49
|
public(name, schema) {
|
|
62
50
|
return new ResourceSchemaBuilder([
|
|
63
51
|
...this._fields,
|
|
64
|
-
{
|
|
52
|
+
{
|
|
53
|
+
name,
|
|
54
|
+
schema,
|
|
55
|
+
visibility: 'public',
|
|
56
|
+
visibleTo: new Set(['public', 'authenticated', 'admin']),
|
|
57
|
+
},
|
|
65
58
|
]);
|
|
66
59
|
}
|
|
67
60
|
/**
|
|
68
61
|
* Adds an authenticated field (visible to authenticated users and admins)
|
|
69
|
-
*
|
|
70
|
-
* @param name - Field name
|
|
71
|
-
* @param schema - Zod schema for the field
|
|
72
|
-
* @returns New builder with the field added
|
|
73
62
|
*/
|
|
74
63
|
authenticated(name, schema) {
|
|
75
64
|
return new ResourceSchemaBuilder([
|
|
76
65
|
...this._fields,
|
|
77
|
-
{
|
|
66
|
+
{
|
|
67
|
+
name,
|
|
68
|
+
schema,
|
|
69
|
+
visibility: 'authenticated',
|
|
70
|
+
visibleTo: new Set(['authenticated', 'admin']),
|
|
71
|
+
},
|
|
78
72
|
]);
|
|
79
73
|
}
|
|
80
74
|
/**
|
|
81
75
|
* Adds an admin field (visible only to admins)
|
|
82
|
-
*
|
|
83
|
-
* @param name - Field name
|
|
84
|
-
* @param schema - Zod schema for the field
|
|
85
|
-
* @returns New builder with the field added
|
|
86
76
|
*/
|
|
87
77
|
admin(name, schema) {
|
|
88
78
|
return new ResourceSchemaBuilder([
|
|
89
79
|
...this._fields,
|
|
90
|
-
{ name, schema, visibility: 'admin' },
|
|
80
|
+
{ name, schema, visibility: 'admin', visibleTo: new Set(['admin']) },
|
|
91
81
|
]);
|
|
92
82
|
}
|
|
93
83
|
/**
|
|
94
84
|
* Adds a has-one relation (nullable nested object)
|
|
95
|
-
*
|
|
96
|
-
* The relation's visibility controls WHETHER it appears in the output.
|
|
97
|
-
* The parent's projection level controls WHAT fields of the nested schema are shown.
|
|
98
|
-
*
|
|
99
|
-
* **Note:** The nested schema's generic field types are tracked at compile time
|
|
100
|
-
* for output type computation, but the runtime field stores an untyped
|
|
101
|
-
* `ResourceSchema` reference. Always pass the direct result of `.build()`
|
|
102
|
-
* to ensure the compile-time and runtime schemas stay in sync.
|
|
103
|
-
*
|
|
104
|
-
* @param name - Relation field name
|
|
105
|
-
* @param nestedSchema - The nested resource schema (result of `.build()`)
|
|
106
|
-
* @param visibility - Visibility level for this relation
|
|
107
|
-
* @returns New builder with the relation added
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```typescript
|
|
111
|
-
* const UserSchema = resourceSchema()
|
|
112
|
-
* .public('id', z.string())
|
|
113
|
-
* .hasOne('organization', OrgSchema, 'public')
|
|
114
|
-
* .build();
|
|
115
|
-
* ```
|
|
116
85
|
*/
|
|
117
86
|
hasOne(name, nestedSchema, visibility) {
|
|
118
87
|
return new ResourceSchemaBuilder([
|
|
@@ -120,6 +89,7 @@ export class ResourceSchemaBuilder {
|
|
|
120
89
|
{
|
|
121
90
|
name,
|
|
122
91
|
visibility,
|
|
92
|
+
visibleTo: defaultLevelToSet(visibility),
|
|
123
93
|
nestedSchema: nestedSchema,
|
|
124
94
|
cardinality: 'one',
|
|
125
95
|
},
|
|
@@ -127,27 +97,6 @@ export class ResourceSchemaBuilder {
|
|
|
127
97
|
}
|
|
128
98
|
/**
|
|
129
99
|
* Adds a has-many relation (array of nested objects)
|
|
130
|
-
*
|
|
131
|
-
* The relation's visibility controls WHETHER it appears in the output.
|
|
132
|
-
* The parent's projection level controls WHAT fields of the nested schema are shown.
|
|
133
|
-
*
|
|
134
|
-
* **Note:** The nested schema's generic field types are tracked at compile time
|
|
135
|
-
* for output type computation, but the runtime field stores an untyped
|
|
136
|
-
* `ResourceSchema` reference. Always pass the direct result of `.build()`
|
|
137
|
-
* to ensure the compile-time and runtime schemas stay in sync.
|
|
138
|
-
*
|
|
139
|
-
* @param name - Relation field name
|
|
140
|
-
* @param nestedSchema - The nested resource schema (result of `.build()`)
|
|
141
|
-
* @param visibility - Visibility level for this relation
|
|
142
|
-
* @returns New builder with the relation added
|
|
143
|
-
*
|
|
144
|
-
* @example
|
|
145
|
-
* ```typescript
|
|
146
|
-
* const UserSchema = resourceSchema()
|
|
147
|
-
* .public('id', z.string())
|
|
148
|
-
* .hasMany('posts', PostSchema, 'authenticated')
|
|
149
|
-
* .build();
|
|
150
|
-
* ```
|
|
151
100
|
*/
|
|
152
101
|
hasMany(name, nestedSchema, visibility) {
|
|
153
102
|
return new ResourceSchemaBuilder([
|
|
@@ -155,6 +104,7 @@ export class ResourceSchemaBuilder {
|
|
|
155
104
|
{
|
|
156
105
|
name,
|
|
157
106
|
visibility,
|
|
107
|
+
visibleTo: defaultLevelToSet(visibility),
|
|
158
108
|
nestedSchema: nestedSchema,
|
|
159
109
|
cardinality: 'many',
|
|
160
110
|
},
|
|
@@ -162,39 +112,15 @@ export class ResourceSchemaBuilder {
|
|
|
162
112
|
}
|
|
163
113
|
/**
|
|
164
114
|
* Adds a field with explicit visibility level
|
|
165
|
-
*
|
|
166
|
-
* @param name - Field name
|
|
167
|
-
* @param schema - Zod schema for the field
|
|
168
|
-
* @param visibility - Visibility level
|
|
169
|
-
* @returns New builder with the field added
|
|
170
115
|
*/
|
|
171
116
|
field(name, schema, visibility) {
|
|
172
117
|
return new ResourceSchemaBuilder([
|
|
173
118
|
...this._fields,
|
|
174
|
-
{ name, schema, visibility },
|
|
119
|
+
{ name, schema, visibility, visibleTo: defaultLevelToSet(visibility) },
|
|
175
120
|
]);
|
|
176
121
|
}
|
|
177
122
|
/**
|
|
178
123
|
* Builds the final resource schema with tagged views
|
|
179
|
-
*
|
|
180
|
-
* Returns a schema with `.public`, `.authenticated`, and `.admin`
|
|
181
|
-
* properties for declarative projection in procedures.
|
|
182
|
-
*
|
|
183
|
-
* @returns Completed resource schema with tagged views
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* ```typescript
|
|
187
|
-
* const UserSchema = resourceSchema()
|
|
188
|
-
* .public('id', z.string())
|
|
189
|
-
* .authenticated('email', z.string())
|
|
190
|
-
* .build();
|
|
191
|
-
*
|
|
192
|
-
* // Use tagged views in procedures
|
|
193
|
-
* procedure().resource(UserSchema.authenticated).query(handler);
|
|
194
|
-
*
|
|
195
|
-
* // Or in handlers
|
|
196
|
-
* resource(data, UserSchema.authenticated);
|
|
197
|
-
* ```
|
|
198
124
|
*/
|
|
199
125
|
build() {
|
|
200
126
|
const fields = [...this._fields];
|
|
@@ -206,44 +132,126 @@ export class ResourceSchemaBuilder {
|
|
|
206
132
|
});
|
|
207
133
|
}
|
|
208
134
|
}
|
|
209
|
-
// ============================================================================
|
|
210
|
-
// Factory Function
|
|
211
|
-
// ============================================================================
|
|
212
135
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* field-level visibility controls.
|
|
217
|
-
*
|
|
218
|
-
* @returns New empty schema builder
|
|
219
|
-
*
|
|
220
|
-
* @example
|
|
221
|
-
* ```typescript
|
|
222
|
-
* import { z } from 'zod';
|
|
223
|
-
* import { resourceSchema } from '@veloxts/router';
|
|
136
|
+
* Resolves a visibility reference (group name, level name, or array) to a Set.
|
|
137
|
+
* Group names are resolved via config, level names become single-element sets,
|
|
138
|
+
* and arrays are used directly.
|
|
224
139
|
*
|
|
225
|
-
*
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
function resolveVisibilityRef(visibility, config, groupNames, levelSet) {
|
|
143
|
+
if (typeof visibility !== 'string') {
|
|
144
|
+
return new Set(visibility);
|
|
145
|
+
}
|
|
146
|
+
if (groupNames.has(visibility)) {
|
|
147
|
+
return config.resolve(visibility);
|
|
148
|
+
}
|
|
149
|
+
if (levelSet.has(visibility)) {
|
|
150
|
+
return new Set([visibility]);
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Unknown group or level: "${visibility}"`);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Creates a Proxy-based custom schema builder
|
|
234
156
|
*
|
|
235
|
-
*
|
|
236
|
-
* // AnonymousOutput<typeof UserSchema> = { id: string; name: string; avatarUrl: string | null }
|
|
237
|
-
* // AuthenticatedOutput<typeof UserSchema> = { id: string; name: string; avatarUrl: string | null; email: string; createdAt: Date }
|
|
238
|
-
* // AdminOutput<typeof UserSchema> = { id: string; name: string; avatarUrl: string | null; email: string; createdAt: Date; internalNotes: string | null; lastLoginIp: string | null }
|
|
239
|
-
* ```
|
|
157
|
+
* @internal
|
|
240
158
|
*/
|
|
241
|
-
|
|
242
|
-
|
|
159
|
+
function createCustomSchemaBuilder(config, fields) {
|
|
160
|
+
const levelSet = config.allLevels();
|
|
161
|
+
const groupNames = new Set(Object.keys(config.groups));
|
|
162
|
+
const base = {
|
|
163
|
+
visibleTo(name, schema, levels) {
|
|
164
|
+
const set = new Set(levels);
|
|
165
|
+
return createCustomSchemaBuilder(config, [
|
|
166
|
+
...fields,
|
|
167
|
+
{ name, schema, visibleTo: set, visibility: levels[0] ?? '' },
|
|
168
|
+
]);
|
|
169
|
+
},
|
|
170
|
+
hasOne(name, nestedSchema, visibility) {
|
|
171
|
+
const set = resolveVisibilityRef(visibility, config, groupNames, levelSet);
|
|
172
|
+
return createCustomSchemaBuilder(config, [
|
|
173
|
+
...fields,
|
|
174
|
+
{
|
|
175
|
+
name,
|
|
176
|
+
nestedSchema,
|
|
177
|
+
visibleTo: set,
|
|
178
|
+
visibility: [...set][0] ?? '',
|
|
179
|
+
cardinality: 'one',
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
},
|
|
183
|
+
hasMany(name, nestedSchema, visibility) {
|
|
184
|
+
const set = resolveVisibilityRef(visibility, config, groupNames, levelSet);
|
|
185
|
+
return createCustomSchemaBuilder(config, [
|
|
186
|
+
...fields,
|
|
187
|
+
{
|
|
188
|
+
name,
|
|
189
|
+
nestedSchema,
|
|
190
|
+
visibleTo: set,
|
|
191
|
+
visibility: [...set][0] ?? '',
|
|
192
|
+
cardinality: 'many',
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
},
|
|
196
|
+
build() {
|
|
197
|
+
const frozenFields = [...fields];
|
|
198
|
+
const baseSchema = { fields: frozenFields };
|
|
199
|
+
const result = Object.assign(baseSchema, { _levelConfig: config });
|
|
200
|
+
for (const level of config.levels) {
|
|
201
|
+
result[level] = Object.assign({ fields: frozenFields }, { _level: level });
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
return new Proxy(base, {
|
|
207
|
+
get(target, prop, receiver) {
|
|
208
|
+
if (typeof prop === 'string') {
|
|
209
|
+
// Check if it's a group name
|
|
210
|
+
if (groupNames.has(prop)) {
|
|
211
|
+
return (name, schema) => {
|
|
212
|
+
const set = config.resolve(prop);
|
|
213
|
+
return createCustomSchemaBuilder(config, [
|
|
214
|
+
...fields,
|
|
215
|
+
{ name, schema, visibleTo: set, visibility: [...set][0] ?? '' },
|
|
216
|
+
]);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// Check if it's a level name (not a group)
|
|
220
|
+
if (levelSet.has(prop)) {
|
|
221
|
+
return (name, schema) => {
|
|
222
|
+
const set = new Set([prop]);
|
|
223
|
+
return createCustomSchemaBuilder(config, [
|
|
224
|
+
...fields,
|
|
225
|
+
{ name, schema, visibleTo: set, visibility: prop },
|
|
226
|
+
]);
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return Reflect.get(target, prop, receiver);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export function resourceSchema(config) {
|
|
235
|
+
if (!config) {
|
|
236
|
+
return ResourceSchemaBuilder.create();
|
|
237
|
+
}
|
|
238
|
+
return createCustomSchemaBuilder(config, []);
|
|
243
239
|
}
|
|
244
240
|
// ============================================================================
|
|
245
241
|
// Type Guards
|
|
246
242
|
// ============================================================================
|
|
243
|
+
/**
|
|
244
|
+
* Type guard to check if a schema is a TaggedResourceSchema (has _level)
|
|
245
|
+
*
|
|
246
|
+
* Accepts any string _level to support custom access levels.
|
|
247
|
+
*/
|
|
248
|
+
export function isTaggedResourceSchema(value) {
|
|
249
|
+
if (typeof value !== 'object' || value === null) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const obj = value;
|
|
253
|
+
return Array.isArray(obj.fields) && typeof obj._level === 'string';
|
|
254
|
+
}
|
|
247
255
|
/**
|
|
248
256
|
* Type guard to check if a value is a ResourceSchema
|
|
249
257
|
*/
|
package/dist/resource/tags.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* through the type system without any runtime overhead.
|
|
6
6
|
*
|
|
7
7
|
* Additionally provides runtime `__accessLevel` property for auto-projection
|
|
8
|
-
* when using the chained `.
|
|
8
|
+
* when using the chained `.expose()` method on procedures.
|
|
9
9
|
*
|
|
10
10
|
* @module resource/tags
|
|
11
11
|
*/
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
*
|
|
15
15
|
* These values are set by narrowing guards at runtime and used for
|
|
16
16
|
* automatic resource projection in the procedure builder.
|
|
17
|
+
*
|
|
18
|
+
* Widened to `string` to support custom access levels defined via
|
|
19
|
+
* `defineAccessLevels()`. The default 3-level system still uses
|
|
20
|
+
* `'public' | 'authenticated' | 'admin'` at the call site.
|
|
17
21
|
*/
|
|
18
|
-
export type AccessLevel =
|
|
22
|
+
export type AccessLevel = string;
|
|
19
23
|
/**
|
|
20
24
|
* Maps an AccessLevel string to its corresponding phantom ContextTag
|
|
21
25
|
*
|
|
@@ -26,15 +30,31 @@ export type AccessLevel = 'public' | 'authenticated' | 'admin';
|
|
|
26
30
|
* ```typescript
|
|
27
31
|
* type Tag = LevelToTag<'authenticated'>; // typeof AUTHENTICATED
|
|
28
32
|
* type Tag = LevelToTag<'admin'>; // typeof ADMIN
|
|
29
|
-
* type Tag = LevelToTag<'public'>; // typeof
|
|
33
|
+
* type Tag = LevelToTag<'public'>; // typeof PUBLIC
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export type LevelToTag<TLevel extends string> = TLevel extends 'admin' ? typeof ADMIN : TLevel extends 'authenticated' ? typeof AUTHENTICATED : typeof PUBLIC;
|
|
37
|
+
/**
|
|
38
|
+
* Maps a phantom ContextTag to its corresponding level string
|
|
39
|
+
*
|
|
40
|
+
* Inverse of `LevelToTag`. Used by `FilterFieldsByLevel` to bridge
|
|
41
|
+
* the tag-based system to the set-based visibility model.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* type L1 = TagToLevel<typeof ADMIN>; // 'admin'
|
|
46
|
+
* type L2 = TagToLevel<typeof AUTHENTICATED>; // 'authenticated'
|
|
47
|
+
* type L3 = TagToLevel<typeof PUBLIC>; // 'public'
|
|
30
48
|
* ```
|
|
31
49
|
*/
|
|
32
|
-
export type
|
|
50
|
+
export type TagToLevel<TTag extends ContextTag> = TTag extends typeof ADMIN ? 'admin' : TTag extends typeof AUTHENTICATED ? 'authenticated' : 'public';
|
|
33
51
|
/**
|
|
34
|
-
* Phantom symbol for
|
|
52
|
+
* Phantom symbol for public (unauthenticated) context
|
|
35
53
|
* @internal Compile-time only - never used at runtime
|
|
36
54
|
*/
|
|
37
|
-
export declare const
|
|
55
|
+
export declare const PUBLIC: unique symbol;
|
|
56
|
+
/** @deprecated Use PUBLIC */
|
|
57
|
+
export declare const ANONYMOUS: typeof PUBLIC;
|
|
38
58
|
/**
|
|
39
59
|
* Phantom symbol for authenticated user context
|
|
40
60
|
* @internal Compile-time only - never used at runtime
|
|
@@ -50,7 +70,7 @@ export declare const ADMIN: unique symbol;
|
|
|
50
70
|
*
|
|
51
71
|
* Used to constrain generic type parameters that represent access levels.
|
|
52
72
|
*/
|
|
53
|
-
export type ContextTag = typeof
|
|
73
|
+
export type ContextTag = typeof PUBLIC | typeof AUTHENTICATED | typeof ADMIN;
|
|
54
74
|
/**
|
|
55
75
|
* Interface for contexts tagged with an access level
|
|
56
76
|
*
|
|
@@ -59,10 +79,10 @@ export type ContextTag = typeof ANONYMOUS | typeof AUTHENTICATED | typeof ADMIN;
|
|
|
59
79
|
* without any memory overhead.
|
|
60
80
|
*
|
|
61
81
|
* The `__accessLevel` field is a runtime field set by narrowing guards.
|
|
62
|
-
* It enables automatic resource projection when using `.
|
|
82
|
+
* It enables automatic resource projection when using `.expose()` in
|
|
63
83
|
* the procedure builder chain.
|
|
64
84
|
*
|
|
65
|
-
* @template TTag - The context tag type (defaults to
|
|
85
|
+
* @template TTag - The context tag type (defaults to PUBLIC)
|
|
66
86
|
*
|
|
67
87
|
* @example
|
|
68
88
|
* ```typescript
|
|
@@ -77,7 +97,7 @@ export type ContextTag = typeof ANONYMOUS | typeof AUTHENTICATED | typeof ADMIN;
|
|
|
77
97
|
* }
|
|
78
98
|
* ```
|
|
79
99
|
*/
|
|
80
|
-
export interface TaggedContext<TTag extends ContextTag = typeof
|
|
100
|
+
export interface TaggedContext<TTag extends ContextTag = typeof PUBLIC> {
|
|
81
101
|
/**
|
|
82
102
|
* Phantom field for carrying the context tag
|
|
83
103
|
* @internal Never exists at runtime - purely for type inference
|
|
@@ -87,27 +107,27 @@ export interface TaggedContext<TTag extends ContextTag = typeof ANONYMOUS> {
|
|
|
87
107
|
* Runtime access level set by narrowing guards
|
|
88
108
|
*
|
|
89
109
|
* This field IS present at runtime (unlike __tag) and is used for
|
|
90
|
-
* automatic resource projection when using `.
|
|
110
|
+
* automatic resource projection when using `.expose()` in procedures.
|
|
91
111
|
*
|
|
92
112
|
* Set automatically by:
|
|
93
113
|
* - `authenticatedNarrow` → 'authenticated'
|
|
94
114
|
* - `adminNarrow` → 'admin'
|
|
95
115
|
* - No guard → 'public' (default)
|
|
96
116
|
*/
|
|
97
|
-
__accessLevel?:
|
|
117
|
+
__accessLevel?: string;
|
|
98
118
|
}
|
|
99
119
|
/**
|
|
100
120
|
* Extracts the tag from a tagged context type
|
|
101
121
|
*
|
|
102
|
-
* Returns
|
|
122
|
+
* Returns PUBLIC if the context is not tagged or has no tag.
|
|
103
123
|
*
|
|
104
124
|
* @example
|
|
105
125
|
* ```typescript
|
|
106
126
|
* type Tag1 = ExtractTag<TaggedContext<typeof ADMIN>>; // typeof ADMIN
|
|
107
|
-
* type Tag2 = ExtractTag<{ user: User }>; // typeof
|
|
127
|
+
* type Tag2 = ExtractTag<{ user: User }>; // typeof PUBLIC
|
|
108
128
|
* ```
|
|
109
129
|
*/
|
|
110
|
-
export type ExtractTag<TContext> = TContext extends TaggedContext<infer TTag> ? TTag : typeof
|
|
130
|
+
export type ExtractTag<TContext> = TContext extends TaggedContext<infer TTag> ? TTag : typeof PUBLIC;
|
|
111
131
|
/**
|
|
112
132
|
* Checks if a context has a specific tag
|
|
113
133
|
*
|
package/dist/resource/tags.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* through the type system without any runtime overhead.
|
|
6
6
|
*
|
|
7
7
|
* Additionally provides runtime `__accessLevel` property for auto-projection
|
|
8
|
-
* when using the chained `.
|
|
8
|
+
* when using the chained `.expose()` method on procedures.
|
|
9
9
|
*
|
|
10
10
|
* @module resource/tags
|
|
11
11
|
*/
|
package/dist/resource/types.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module resource/types
|
|
8
8
|
*/
|
|
9
|
-
import type { AdminOutput,
|
|
10
|
-
import type { ADMIN,
|
|
9
|
+
import type { AdminOutput, AuthenticatedOutput, OutputForTag, PublicOutput, ResourceSchema } from './schema.js';
|
|
10
|
+
import type { ADMIN, AUTHENTICATED, ContextTag, ExtractTag, PUBLIC, TaggedContext } from './tags.js';
|
|
11
11
|
/**
|
|
12
12
|
* Infers the full data shape from a resource schema
|
|
13
13
|
*
|
|
@@ -35,12 +35,14 @@ export type InferResourceData<TSchema extends ResourceSchema> = AdminOutput<TSch
|
|
|
35
35
|
*/
|
|
36
36
|
export type InferResourceOutput<TSchema extends ResourceSchema, TContext extends TaggedContext<ContextTag>> = OutputForTag<TSchema, ExtractTag<TContext>>;
|
|
37
37
|
/**
|
|
38
|
-
* Type that represents any
|
|
38
|
+
* Type that represents any public-level (unauthenticated) tagged context
|
|
39
39
|
*
|
|
40
40
|
* This is a minimal tagged context type. For auth-specific contexts with
|
|
41
41
|
* user and auth properties, use the types from @veloxts/auth.
|
|
42
42
|
*/
|
|
43
|
-
export type
|
|
43
|
+
export type PublicTaggedContext = TaggedContext<typeof PUBLIC>;
|
|
44
|
+
/** @deprecated Use PublicTaggedContext */
|
|
45
|
+
export type AnonymousTaggedContext = PublicTaggedContext;
|
|
44
46
|
/**
|
|
45
47
|
* Type that represents any authenticated-level tagged context
|
|
46
48
|
*
|
|
@@ -69,7 +71,7 @@ export type AdminTaggedContext = TaggedContext<typeof ADMIN>;
|
|
|
69
71
|
* }
|
|
70
72
|
* ```
|
|
71
73
|
*/
|
|
72
|
-
export type AnyResourceOutput<TSchema extends ResourceSchema> =
|
|
74
|
+
export type AnyResourceOutput<TSchema extends ResourceSchema> = PublicOutput<TSchema> | AuthenticatedOutput<TSchema> | AdminOutput<TSchema>;
|
|
73
75
|
/**
|
|
74
76
|
* Returns the output type if the context has at least authenticated access
|
|
75
77
|
*
|
|
@@ -80,7 +82,7 @@ export type AnyResourceOutput<TSchema extends ResourceSchema> = AnonymousOutput<
|
|
|
80
82
|
* // If MyContext is anonymous: never
|
|
81
83
|
* ```
|
|
82
84
|
*/
|
|
83
|
-
export type IfAuthenticated<TContext extends TaggedContext<ContextTag>, TSchema extends ResourceSchema> = ExtractTag<TContext> extends typeof
|
|
85
|
+
export type IfAuthenticated<TContext extends TaggedContext<ContextTag>, TSchema extends ResourceSchema> = ExtractTag<TContext> extends typeof PUBLIC ? never : OutputForTag<TSchema, ExtractTag<TContext>>;
|
|
84
86
|
/**
|
|
85
87
|
* Returns the output type if the context has admin access
|
|
86
88
|
*
|
|
@@ -92,5 +94,5 @@ export type IfAuthenticated<TContext extends TaggedContext<ContextTag>, TSchema
|
|
|
92
94
|
* ```
|
|
93
95
|
*/
|
|
94
96
|
export type IfAdmin<TContext extends TaggedContext<ContextTag>, TSchema extends ResourceSchema> = ExtractTag<TContext> extends typeof ADMIN ? AdminOutput<TSchema> : never;
|
|
95
|
-
export type { AdminOutput, AnonymousOutput, AuthenticatedOutput, OutputForTag, ResourceSchema, } from './schema.js';
|
|
96
|
-
export type { ADMIN, ANONYMOUS, AUTHENTICATED, ContextTag, ExtractTag, HasTag, TaggedContext, WithTag, } from './tags.js';
|
|
97
|
+
export type { AdminOutput, AnonymousOutput, AuthenticatedOutput, OutputForTag, PublicOutput, ResourceSchema, } from './schema.js';
|
|
98
|
+
export type { ADMIN, ANONYMOUS, AUTHENTICATED, ContextTag, ExtractTag, HasTag, PUBLIC, TaggedContext, WithTag, } from './tags.js';
|
|
@@ -22,12 +22,12 @@ export type VisibilityLevel = 'public' | 'authenticated' | 'admin';
|
|
|
22
22
|
* Implements the visibility hierarchy:
|
|
23
23
|
* - ADMIN sees: public, authenticated, admin
|
|
24
24
|
* - AUTHENTICATED sees: public, authenticated
|
|
25
|
-
* -
|
|
25
|
+
* - PUBLIC sees: public only
|
|
26
26
|
*
|
|
27
27
|
* @example
|
|
28
28
|
* ```typescript
|
|
29
|
-
* type T1 = IsVisibleToTag<'public', typeof
|
|
30
|
-
* type T2 = IsVisibleToTag<'authenticated', typeof
|
|
29
|
+
* type T1 = IsVisibleToTag<'public', typeof PUBLIC>; // true
|
|
30
|
+
* type T2 = IsVisibleToTag<'authenticated', typeof PUBLIC>; // false
|
|
31
31
|
* type T3 = IsVisibleToTag<'admin', typeof AUTHENTICATED>; // false
|
|
32
32
|
* type T4 = IsVisibleToTag<'admin', typeof ADMIN>; // true
|
|
33
33
|
* ```
|
|
@@ -70,3 +70,16 @@ export declare function getVisibilityForTag(tagId: 'anonymous' | 'authenticated'
|
|
|
70
70
|
* ```
|
|
71
71
|
*/
|
|
72
72
|
export declare function getAccessibleLevels(level: VisibilityLevel): VisibilityLevel[];
|
|
73
|
+
/**
|
|
74
|
+
* Runtime check: Is a field visible to a given level using set membership?
|
|
75
|
+
*
|
|
76
|
+
* All fields created by `ResourceSchemaBuilder` and `createCustomSchemaBuilder`
|
|
77
|
+
* include a `visibleTo` set, so this always uses set membership.
|
|
78
|
+
*
|
|
79
|
+
* @param field - The runtime field to check (must have `visibleTo` set)
|
|
80
|
+
* @param level - The access level to check visibility for
|
|
81
|
+
* @returns True if the field should be included in the output
|
|
82
|
+
*/
|
|
83
|
+
export declare function isFieldVisibleToLevel(field: {
|
|
84
|
+
visibleTo: ReadonlySet<string>;
|
|
85
|
+
}, level: string): boolean;
|
|
@@ -79,3 +79,19 @@ export function getAccessibleLevels(level) {
|
|
|
79
79
|
}
|
|
80
80
|
return levels;
|
|
81
81
|
}
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Set-Based Visibility (Custom Levels)
|
|
84
|
+
// ============================================================================
|
|
85
|
+
/**
|
|
86
|
+
* Runtime check: Is a field visible to a given level using set membership?
|
|
87
|
+
*
|
|
88
|
+
* All fields created by `ResourceSchemaBuilder` and `createCustomSchemaBuilder`
|
|
89
|
+
* include a `visibleTo` set, so this always uses set membership.
|
|
90
|
+
*
|
|
91
|
+
* @param field - The runtime field to check (must have `visibleTo` set)
|
|
92
|
+
* @param level - The access level to check visibility for
|
|
93
|
+
* @returns True if the field should be included in the output
|
|
94
|
+
*/
|
|
95
|
+
export function isFieldVisibleToLevel(field, level) {
|
|
96
|
+
return field.visibleTo.has(level);
|
|
97
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { BaseContext } from '@veloxts/core';
|
|
10
10
|
import type { HttpMethod } from '@veloxts/validation';
|
|
11
|
+
import type { ResourceSchema } from './resource/schema.js';
|
|
11
12
|
/**
|
|
12
13
|
* Procedure operation types
|
|
13
14
|
*
|
|
@@ -186,6 +187,21 @@ export interface MiddlewareArgs<TInput, TContext extends BaseContext = BaseConte
|
|
|
186
187
|
* @template TOutput - The output type
|
|
187
188
|
*/
|
|
188
189
|
export type MiddlewareFunction<TInput, TContext extends BaseContext = BaseContext, TNewContext extends BaseContext = TContext, TOutput = unknown> = (args: MiddlewareArgs<TInput, TContext, TNewContext, TOutput>) => Promise<MiddlewareResult<TOutput>>;
|
|
190
|
+
/**
|
|
191
|
+
* Simple middleware type for most use cases.
|
|
192
|
+
*
|
|
193
|
+
* Use this when defining standalone middleware functions. All generics
|
|
194
|
+
* default to sensible values so you only need to specify what you customize.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* const logging: Middleware = async ({ ctx, next }) => {
|
|
199
|
+
* console.log(`${ctx.request.method} ${ctx.request.url}`);
|
|
200
|
+
* return next();
|
|
201
|
+
* };
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export type Middleware<TContext extends BaseContext = BaseContext> = MiddlewareFunction<unknown, TContext, TContext>;
|
|
189
205
|
/**
|
|
190
206
|
* REST route override configuration
|
|
191
207
|
*
|
|
@@ -282,6 +298,13 @@ export interface CompiledProcedure<TInput = unknown, TOutput = unknown, TContext
|
|
|
282
298
|
readonly deprecated?: boolean;
|
|
283
299
|
/** Deprecation message explaining why and what to use instead */
|
|
284
300
|
readonly deprecationMessage?: string;
|
|
301
|
+
/**
|
|
302
|
+
* Whether this procedure is a webhook endpoint.
|
|
303
|
+
* Set by `.webhook()` builder method. Currently a metadata marker;
|
|
304
|
+
* will be consumed by REST adapter and OpenAPI generator in a future release.
|
|
305
|
+
* @internal
|
|
306
|
+
*/
|
|
307
|
+
readonly isWebhook?: boolean;
|
|
285
308
|
/**
|
|
286
309
|
* Parent resource configuration for nested routes (single level)
|
|
287
310
|
*
|
|
@@ -326,14 +349,14 @@ export interface CompiledProcedure<TInput = unknown, TOutput = unknown, TContext
|
|
|
326
349
|
/**
|
|
327
350
|
* Resource schema for auto-projection
|
|
328
351
|
*
|
|
329
|
-
* When set via `.
|
|
352
|
+
* When set via `.expose()`, the procedure executor will automatically
|
|
330
353
|
* project the handler's return value based on `ctx.__accessLevel`.
|
|
331
354
|
*
|
|
332
355
|
* This enables the elegant chained API:
|
|
333
356
|
* ```typescript
|
|
334
357
|
* procedure()
|
|
335
358
|
* .guardNarrow(authenticatedNarrow)
|
|
336
|
-
* .
|
|
359
|
+
* .expose(UserSchema)
|
|
337
360
|
* .query(async ({ ctx }) => {
|
|
338
361
|
* return ctx.db.user.findUnique(...);
|
|
339
362
|
* // Auto-projected based on __accessLevel
|
|
@@ -342,16 +365,16 @@ export interface CompiledProcedure<TInput = unknown, TOutput = unknown, TContext
|
|
|
342
365
|
*
|
|
343
366
|
* @internal
|
|
344
367
|
*/
|
|
345
|
-
readonly _resourceSchema?:
|
|
368
|
+
readonly _resourceSchema?: ResourceSchema;
|
|
346
369
|
/**
|
|
347
370
|
* Explicit resource projection level from tagged schema
|
|
348
371
|
*
|
|
349
|
-
* Set when using `.
|
|
372
|
+
* Set when using `.expose(UserSchema.authenticated)` etc.
|
|
350
373
|
* Takes precedence over guard-derived access level.
|
|
351
374
|
*
|
|
352
375
|
* @internal
|
|
353
376
|
*/
|
|
354
|
-
readonly _resourceLevel?:
|
|
377
|
+
readonly _resourceLevel?: string;
|
|
355
378
|
}
|
|
356
379
|
/**
|
|
357
380
|
* Record of named procedures
|