@@ -143,7 +143,7 @@ export const MimeTypeSchema = z
143143export const NsidSchema = z
144144 . string ( )
145145 . regex (
146- / ^ [ a - z ] [ a - z 0 - 9 - ] * ( \. [ a - z ] [ a - z 0 - 9 - ] * ) + $ / ,
146+ / ^ [ a - z A - Z ] [ a - z A - Z 0 - 9 - ] * ( \. [ a - z A - Z ] [ a - z A - Z 0 - 9 - ] * ) + $ / ,
147147 'Invalid NSID format. Expected reverse-DNS format (e.g., "app.bsky.feed.post")' ,
148148 ) ;
149149
@@ -231,3 +231,187 @@ export const RepoPermissionSchema = z
231231 * Input type for repository permission (before transform).
232232 */
233233export type RepoPermissionInput = z . input < typeof RepoPermissionSchema > ;
234+
235+ /**
236+ * Zod schema for blob permission.
237+ *
238+ * Blob permissions control media file uploads constrained by MIME type patterns.
239+ *
240+ * @example Single MIME type
241+ * ```typescript
242+ * const input = { type: 'blob', mimeTypes: ['image/*'] };
243+ * BlobPermissionSchema.parse(input); // Returns: "blob:image/*"
244+ * ```
245+ *
246+ * @example Multiple MIME types
247+ * ```typescript
248+ * const input = { type: 'blob', mimeTypes: ['image/*', 'video/*'] };
249+ * BlobPermissionSchema.parse(input); // Returns: "blob?accept=image/*&accept=video/*"
250+ * ```
251+ */
252+ export const BlobPermissionSchema = z
253+ . object ( {
254+ type : z . literal ( "blob" ) ,
255+ mimeTypes : z . array ( MimeTypeSchema ) . min ( 1 , "At least one MIME type required" ) ,
256+ } )
257+ . transform ( ( { mimeTypes } ) => {
258+ if ( mimeTypes . length === 1 ) {
259+ return `blob:${ mimeTypes [ 0 ] } ` ;
260+ }
261+ const accepts = mimeTypes . map ( ( t ) => `accept=${ encodeURIComponent ( t ) } ` ) . join ( "&" ) ;
262+ return `blob?${ accepts } ` ;
263+ } ) ;
264+
265+ /**
266+ * Input type for blob permission (before transform).
267+ */
268+ export type BlobPermissionInput = z . input < typeof BlobPermissionSchema > ;
269+
270+ /**
271+ * Zod schema for RPC permission.
272+ *
273+ * RPC permissions control authenticated API calls to remote services.
274+ * At least one of lexicon or aud must be restricted (both cannot be wildcards).
275+ *
276+ * @example Specific lexicon with wildcard audience
277+ * ```typescript
278+ * const input = {
279+ * type: 'rpc',
280+ * lexicon: 'com.atproto.repo.createRecord',
281+ * aud: '*'
282+ * };
283+ * RpcPermissionSchema.parse(input);
284+ * // Returns: "rpc:com.atproto.repo.createRecord?aud=*"
285+ * ```
286+ *
287+ * @example With specific audience
288+ * ```typescript
289+ * const input = {
290+ * type: 'rpc',
291+ * lexicon: 'com.atproto.repo.createRecord',
292+ * aud: 'did:web:api.example.com',
293+ * inheritAud: true
294+ * };
295+ * RpcPermissionSchema.parse(input);
296+ * // Returns: "rpc:com.atproto.repo.createRecord?aud=did%3Aweb%3Aapi.example.com&inheritAud=true"
297+ * ```
298+ */
299+ export const RpcPermissionSchema = z
300+ . object ( {
301+ type : z . literal ( "rpc" ) ,
302+ lexicon : NsidSchema . or ( z . literal ( "*" ) ) ,
303+ aud : z . string ( ) . min ( 1 , "Audience is required" ) ,
304+ inheritAud : z . boolean ( ) . optional ( ) ,
305+ } )
306+ . refine (
307+ ( { lexicon, aud } ) => lexicon !== "*" || aud !== "*" ,
308+ "At least one of lexicon or aud must be restricted (wildcards cannot both be used)" ,
309+ )
310+ . transform ( ( { lexicon, aud, inheritAud } ) => {
311+ let perm = `rpc:${ lexicon } ?aud=${ encodeURIComponent ( aud ) } ` ;
312+ if ( inheritAud ) {
313+ perm += "&inheritAud=true" ;
314+ }
315+ return perm ;
316+ } ) ;
317+
318+ /**
319+ * Input type for RPC permission (before transform).
320+ */
321+ export type RpcPermissionInput = z . input < typeof RpcPermissionSchema > ;
322+
323+ /**
324+ * Zod schema for identity permission.
325+ *
326+ * Identity permissions control access to DID documents and handles.
327+ *
328+ * @example Handle management
329+ * ```typescript
330+ * const input = { type: 'identity', attr: 'handle' };
331+ * IdentityPermissionSchema.parse(input); // Returns: "identity:handle"
332+ * ```
333+ *
334+ * @example All identity attributes
335+ * ```typescript
336+ * const input = { type: 'identity', attr: '*' };
337+ * IdentityPermissionSchema.parse(input); // Returns: "identity:*"
338+ * ```
339+ */
340+ export const IdentityPermissionSchema = z
341+ . object ( {
342+ type : z . literal ( "identity" ) ,
343+ attr : IdentityAttrSchema ,
344+ } )
345+ . transform ( ( { attr } ) => `identity:${ attr } ` ) ;
346+
347+ /**
348+ * Input type for identity permission (before transform).
349+ */
350+ export type IdentityPermissionInput = z . input < typeof IdentityPermissionSchema > ;
351+
352+ /**
353+ * Zod schema for permission set inclusion.
354+ *
355+ * Include permissions reference permission sets bundled under a single NSID.
356+ *
357+ * @example Without audience
358+ * ```typescript
359+ * const input = { type: 'include', nsid: 'com.example.authBasicFeatures' };
360+ * IncludePermissionSchema.parse(input);
361+ * // Returns: "include:com.example.authBasicFeatures"
362+ * ```
363+ *
364+ * @example With audience
365+ * ```typescript
366+ * const input = {
367+ * type: 'include',
368+ * nsid: 'com.example.authBasicFeatures',
369+ * aud: 'did:web:api.example.com'
370+ * };
371+ * IncludePermissionSchema.parse(input);
372+ * // Returns: "include:com.example.authBasicFeatures?aud=did%3Aweb%3Aapi.example.com"
373+ * ```
374+ */
375+ export const IncludePermissionSchema = z
376+ . object ( {
377+ type : z . literal ( "include" ) ,
378+ nsid : NsidSchema ,
379+ aud : z . string ( ) . optional ( ) ,
380+ } )
381+ . transform ( ( { nsid, aud } ) => {
382+ let perm = `include:${ nsid } ` ;
383+ if ( aud ) {
384+ perm += `?aud=${ encodeURIComponent ( aud ) } ` ;
385+ }
386+ return perm ;
387+ } ) ;
388+
389+ /**
390+ * Input type for include permission (before transform).
391+ */
392+ export type IncludePermissionInput = z . input < typeof IncludePermissionSchema > ;
393+
394+ /**
395+ * Union schema for all permission types.
396+ *
397+ * This schema accepts any of the supported permission types and validates
398+ * them according to their specific rules.
399+ */
400+ export const PermissionSchema = z . union ( [
401+ AccountPermissionSchema ,
402+ RepoPermissionSchema ,
403+ BlobPermissionSchema ,
404+ RpcPermissionSchema ,
405+ IdentityPermissionSchema ,
406+ IncludePermissionSchema ,
407+ ] ) ;
408+
409+ /**
410+ * Input type for any permission (before transform).
411+ */
412+ export type PermissionInput = z . input < typeof PermissionSchema > ;
413+
414+ /**
415+ * Output type for any permission (after transform).
416+ */
417+ export type Permission = z . output < typeof PermissionSchema > ;
0 commit comments