Music streaming on ATProto!
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(lexicons): getProfiles, getActorPlaylisst, getActorTracks

+341 -32
+37
packages/lexicons/defs/sh/comet/v0/actor/getProfiles.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.actor.getProfiles", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get the profile views of multiple actors.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actors"], 11 + "properties": { 12 + "actors": { 13 + "type": "array", 14 + "maxLength": 25, 15 + "items": { "type": "string", "format": "at-identifier" } 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["profiles"], 24 + "properties": { 25 + "profiles": { 26 + "type": "array", 27 + "items": { 28 + "type": "ref", 29 + "ref": "sh.comet.v0.actor.profile#view" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+7 -11
packages/lexicons/defs/sh/comet/v0/actor/profile.json
··· 58 58 "maxGraphemes": 64, 59 59 "maxLength": 640 60 60 }, 61 - "description": { 62 - "type": "string", 63 - "maxGraphemes": 256, 64 - "maxLength": 2560 65 - }, 66 - "descriptionFacets": { 67 - "type": "ref", 68 - "ref": "sh.comet.v0.richtext.facet" 69 - }, 70 61 "avatar": { "type": "string", "format": "uri" }, 71 62 "indexedAt": { "type": "string", "format": "datetime" }, 72 63 "createdAt": { "type": "string", "format": "datetime" }, 73 - "viewer": { "type": "unknown", "description": "TODO" } 64 + "viewer": { "type": "ref", "ref": "#viewerState" } 74 65 } 75 66 }, 76 67 "viewFull": { ··· 106 97 "maxLength": 5, 107 98 "items": { "type": "string", "format": "at-uri" } 108 99 }, 109 - "viewer": { "type": "unknown", "description": "TODO" } 100 + "viewer": { "type": "ref", "ref": "#viewerState" } 110 101 } 102 + }, 103 + "viewerState": { 104 + "type": "object", 105 + "description": "Metadata about the requesting account's relationship with the user. TODO: determine if we create our own graph or inherit bsky's.", 106 + "properties": {} 111 107 } 112 108 } 113 109 }
+2 -1
packages/lexicons/defs/sh/comet/v0/feed/defs.json
··· 30 30 "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", 31 31 "properties": { 32 32 "like": { "type": "string", "format": "at-uri" }, 33 - "repost": { "type": "string", "format": "at-uri" } 33 + "repost": { "type": "string", "format": "at-uri" }, 34 + "featured": { "type": "boolean" } 34 35 } 35 36 } 36 37 }
+44
packages/lexicons/defs/sh/comet/v0/feed/getActorPlaylists.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.getActorPlaylists", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a list of an actor's playlists.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "at-identifier" 15 + }, 16 + "limit": { 17 + "type": "integer", 18 + "minimum": 1, 19 + "maximum": 100, 20 + "default": 50 21 + }, 22 + "cursor": { "type": "string" } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "object", 29 + "required": ["playlists"], 30 + "properties": { 31 + "cursor": { "type": "string" }, 32 + "playlists": { 33 + "type": "array", 34 + "items": { 35 + "type": "ref", 36 + "ref": "sh.comet.v0.feed.playlist#view" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+44
packages/lexicons/defs/sh/comet/v0/feed/getActorTracks.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.getActorTracks", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a list of an actor's tracks.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "at-identifier" 15 + }, 16 + "limit": { 17 + "type": "integer", 18 + "minimum": 1, 19 + "maximum": 100, 20 + "default": 50 21 + }, 22 + "cursor": { "type": "string" } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "object", 29 + "required": ["tracks"], 30 + "properties": { 31 + "cursor": { "type": "string" }, 32 + "tracks": { 33 + "type": "array", 34 + "items": { 35 + "type": "ref", 36 + "ref": "sh.comet.v0.feed.track#view" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+32 -1
packages/lexicons/defs/sh/comet/v0/feed/playlist.json
··· 12 12 "properties": { 13 13 "tracks": { 14 14 "type": "array", 15 - "minLength": 1, 16 15 "items": { "type": "string", "format": "at-uri" } 17 16 }, 18 17 "image": { ··· 82 81 "podcast": { 83 82 "type": "token", 84 83 "description": "Indicates the playlist is a podcast or radio show split into several individual tracks." 84 + }, 85 + "view": { 86 + "type": "object", 87 + "required": ["uri", "cid", "author", "tracks", "record"], 88 + "properties": { 89 + "uri": { "type": "string", "format": "at-uri" }, 90 + "cid": { "type": "string", "format": "cid" }, 91 + "author": { 92 + "type": "ref", 93 + "ref": "sh.comet.v0.actor.profile#viewFull" 94 + }, 95 + "tracks": { 96 + "type": "array", 97 + "items": { "type": "ref", "ref": "sh.comet.v0.feed.track#view" }, 98 + "description": "TODO: include cursor for pagination if too many?" 99 + }, 100 + "image": { 101 + "type": "string", 102 + "format": "uri", 103 + "description": "URL pointing to where the image for the playlist can be fetched." 104 + }, 105 + "record": { 106 + "type": "ref", 107 + "ref": "#main" 108 + }, 109 + "repostCount": { "type": "integer" }, 110 + "likeCount": { "type": "integer" }, 111 + "commentCount": { "type": "integer" }, 112 + "trackCount": { "type": "integer" }, 113 + "indexedAt": { "type": "string", "format": "datetime" }, 114 + "viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" } 115 + } 85 116 } 86 117 } 87 118 }
+6 -2
packages/lexicons/defs/sh/comet/v0/feed/track.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "record", 7 - "description": "A Comet audio track.", 7 + "description": "A Comet audio track. TODO: should probably have some sort of pre-calculated waveform, or have a query to get one from a blob?", 8 8 "key": "tid", 9 9 "record": { 10 10 "type": "object", ··· 64 64 "properties": { 65 65 "uri": { "type": "string", "format": "at-uri" }, 66 66 "cid": { "type": "string", "format": "cid" }, 67 - "author": { "type": "unknown", "description": "TODO" }, 67 + "author": { 68 + "type": "ref", 69 + "ref": "sh.comet.v0.actor.profile#viewFull" 70 + }, 68 71 "audio": { 69 72 "type": "string", 70 73 "format": "uri", ··· 82 85 "repostCount": { "type": "integer" }, 83 86 "likeCount": { "type": "integer" }, 84 87 "playCount": { "type": "integer" }, 88 + "commentCount": { "type": "integer" }, 85 89 "indexedAt": { "type": "string", "format": "datetime" }, 86 90 "viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" } 87 91 }
+1 -1
packages/lexicons/package.json
··· 1 1 { 2 2 "name": "@comet/lexicons", 3 - "module": "index.ts", 3 + "main": "src/index.ts", 4 4 "devDependencies": { 5 5 "@atcute/lex-cli": "^2.0.2", 6 6 "@types/bun": "latest"
+3
packages/lexicons/src/index.ts
··· 1 1 export * as ShCometV0ActorGetProfile from "./types/sh/comet/v0/actor/getProfile.js"; 2 + export * as ShCometV0ActorGetProfiles from "./types/sh/comet/v0/actor/getProfiles.js"; 2 3 export * as ShCometV0ActorProfile from "./types/sh/comet/v0/actor/profile.js"; 3 4 export * as ShCometV0FeedComment from "./types/sh/comet/v0/feed/comment.js"; 4 5 export * as ShCometV0FeedDefs from "./types/sh/comet/v0/feed/defs.js"; 6 + export * as ShCometV0FeedGetActorPlaylists from "./types/sh/comet/v0/feed/getActorPlaylists.js"; 7 + export * as ShCometV0FeedGetActorTracks from "./types/sh/comet/v0/feed/getActorTracks.js"; 5 8 export * as ShCometV0FeedLike from "./types/sh/comet/v0/feed/like.js"; 6 9 export * as ShCometV0FeedPlay from "./types/sh/comet/v0/feed/play.js"; 7 10 export * as ShCometV0FeedPlaylist from "./types/sh/comet/v0/feed/playlist.js";
+33
packages/lexicons/src/types/sh/comet/v0/actor/getProfiles.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as ShCometV0ActorProfile from "./profile.js"; 5 + 6 + const _mainSchema = /*#__PURE__*/ v.query("sh.comet.v0.actor.getProfiles", { 7 + params: /*#__PURE__*/ v.object({ 8 + actors: /*#__PURE__*/ v.constrain( 9 + /*#__PURE__*/ v.array(/*#__PURE__*/ v.actorIdentifierString()), 10 + [/*#__PURE__*/ v.arrayLength(0, 25)], 11 + ), 12 + }), 13 + output: { 14 + type: "lex", 15 + schema: /*#__PURE__*/ v.object({ 16 + get profiles() { 17 + return /*#__PURE__*/ v.array(ShCometV0ActorProfile.viewSchema); 18 + }, 19 + }), 20 + }, 21 + }); 22 + 23 + type main$schematype = typeof _mainSchema; 24 + 25 + export interface mainSchema extends main$schematype {} 26 + 27 + export const mainSchema = _mainSchema as mainSchema; 28 + 29 + declare module "@atcute/lexicons/ambient" { 30 + interface XRPCQueries { 31 + "sh.comet.v0.actor.getProfiles": mainSchema; 32 + } 33 + }
+15 -11
packages/lexicons/src/types/sh/comet/v0/actor/profile.ts
··· 39 39 ), 40 40 avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 41 41 createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 42 - description: /*#__PURE__*/ v.optional( 43 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 44 - /*#__PURE__*/ v.stringLength(0, 2560), 45 - /*#__PURE__*/ v.stringGraphemes(0, 256), 46 - ]), 47 - ), 48 - get descriptionFacets() { 49 - return /*#__PURE__*/ v.optional(ShCometV0RichtextFacet.mainSchema); 50 - }, 51 42 did: /*#__PURE__*/ v.didString(), 52 43 displayName: /*#__PURE__*/ v.optional( 53 44 /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ ··· 57 48 ), 58 49 handle: /*#__PURE__*/ v.handleString(), 59 50 indexedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 60 - viewer: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), 51 + get viewer() { 52 + return /*#__PURE__*/ v.optional(viewerStateSchema); 53 + }, 61 54 }); 62 55 const _viewFullSchema = /*#__PURE__*/ v.object({ 63 56 $type: /*#__PURE__*/ v.optional( ··· 94 87 indexedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 95 88 playlistsCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 96 89 tracksCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 97 - viewer: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), 90 + get viewer() { 91 + return /*#__PURE__*/ v.optional(viewerStateSchema); 92 + }, 93 + }); 94 + const _viewerStateSchema = /*#__PURE__*/ v.object({ 95 + $type: /*#__PURE__*/ v.optional( 96 + /*#__PURE__*/ v.literal("sh.comet.v0.actor.profile#viewerState"), 97 + ), 98 98 }); 99 99 100 100 type main$schematype = typeof _mainSchema; 101 101 type view$schematype = typeof _viewSchema; 102 102 type viewFull$schematype = typeof _viewFullSchema; 103 + type viewerState$schematype = typeof _viewerStateSchema; 103 104 104 105 export interface mainSchema extends main$schematype {} 105 106 export interface viewSchema extends view$schematype {} 106 107 export interface viewFullSchema extends viewFull$schematype {} 108 + export interface viewerStateSchema extends viewerState$schematype {} 107 109 108 110 export const mainSchema = _mainSchema as mainSchema; 109 111 export const viewSchema = _viewSchema as viewSchema; 110 112 export const viewFullSchema = _viewFullSchema as viewFullSchema; 113 + export const viewerStateSchema = _viewerStateSchema as viewerStateSchema; 111 114 112 115 export interface Main extends v.InferInput<typeof mainSchema> {} 113 116 export interface View extends v.InferInput<typeof viewSchema> {} 114 117 export interface ViewFull extends v.InferInput<typeof viewFullSchema> {} 118 + export interface ViewerState extends v.InferInput<typeof viewerStateSchema> {} 115 119 116 120 declare module "@atcute/lexicons/ambient" { 117 121 interface Records {
+1
packages/lexicons/src/types/sh/comet/v0/feed/defs.ts
··· 20 20 $type: /*#__PURE__*/ v.optional( 21 21 /*#__PURE__*/ v.literal("sh.comet.v0.feed.defs#viewerState"), 22 22 ), 23 + featured: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 23 24 like: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 24 25 repost: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 25 26 });
+41
packages/lexicons/src/types/sh/comet/v0/feed/getActorPlaylists.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as ShCometV0FeedPlaylist from "./playlist.js"; 5 + 6 + const _mainSchema = /*#__PURE__*/ v.query( 7 + "sh.comet.v0.feed.getActorPlaylists", 8 + { 9 + params: /*#__PURE__*/ v.object({ 10 + actor: /*#__PURE__*/ v.actorIdentifierString(), 11 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 12 + limit: /*#__PURE__*/ v.optional( 13 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ 14 + /*#__PURE__*/ v.integerRange(1, 100), 15 + ]), 16 + 50, 17 + ), 18 + }), 19 + output: { 20 + type: "lex", 21 + schema: /*#__PURE__*/ v.object({ 22 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 23 + get playlists() { 24 + return /*#__PURE__*/ v.array(ShCometV0FeedPlaylist.viewSchema); 25 + }, 26 + }), 27 + }, 28 + }, 29 + ); 30 + 31 + type main$schematype = typeof _mainSchema; 32 + 33 + export interface mainSchema extends main$schematype {} 34 + 35 + export const mainSchema = _mainSchema as mainSchema; 36 + 37 + declare module "@atcute/lexicons/ambient" { 38 + interface XRPCQueries { 39 + "sh.comet.v0.feed.getActorPlaylists": mainSchema; 40 + } 41 + }
+38
packages/lexicons/src/types/sh/comet/v0/feed/getActorTracks.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as ShCometV0FeedTrack from "./track.js"; 5 + 6 + const _mainSchema = /*#__PURE__*/ v.query("sh.comet.v0.feed.getActorTracks", { 7 + params: /*#__PURE__*/ v.object({ 8 + actor: /*#__PURE__*/ v.actorIdentifierString(), 9 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 10 + limit: /*#__PURE__*/ v.optional( 11 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ 12 + /*#__PURE__*/ v.integerRange(1, 100), 13 + ]), 14 + 50, 15 + ), 16 + }), 17 + output: { 18 + type: "lex", 19 + schema: /*#__PURE__*/ v.object({ 20 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 21 + get tracks() { 22 + return /*#__PURE__*/ v.array(ShCometV0FeedTrack.viewSchema); 23 + }, 24 + }), 25 + }, 26 + }); 27 + 28 + type main$schematype = typeof _mainSchema; 29 + 30 + export interface mainSchema extends main$schematype {} 31 + 32 + export const mainSchema = _mainSchema as mainSchema; 33 + 34 + declare module "@atcute/lexicons/ambient" { 35 + interface XRPCQueries { 36 + "sh.comet.v0.feed.getActorTracks": mainSchema; 37 + } 38 + }
+32 -4
packages/lexicons/src/types/sh/comet/v0/feed/playlist.ts
··· 1 1 import type {} from "@atcute/lexicons"; 2 2 import * as v from "@atcute/lexicons/validations"; 3 3 import type {} from "@atcute/lexicons/ambient"; 4 + import * as ShCometV0ActorProfile from "../actor/profile.js"; 4 5 import * as ShCometV0FeedDefs from "./defs.js"; 6 + import * as ShCometV0FeedTrack from "./track.js"; 5 7 import * as ShCometV0RichtextFacet from "../richtext/facet.js"; 6 8 7 9 const _albumSchema = /*#__PURE__*/ v.literal("sh.comet.v0.feed.playlist#album"); ··· 41 43 /*#__PURE__*/ v.stringLength(1, 2560), 42 44 /*#__PURE__*/ v.stringGraphemes(0, 256), 43 45 ]), 44 - tracks: /*#__PURE__*/ v.constrain( 45 - /*#__PURE__*/ v.array(/*#__PURE__*/ v.resourceUriString()), 46 - [/*#__PURE__*/ v.arrayLength(1)], 47 - ), 46 + tracks: /*#__PURE__*/ v.array(/*#__PURE__*/ v.resourceUriString()), 48 47 type: /*#__PURE__*/ v.string< 49 48 | "sh.comet.v0.feed.playlist#album" 50 49 | "sh.comet.v0.feed.playlist#compilation" ··· 60 59 const _podcastSchema = /*#__PURE__*/ v.literal( 61 60 "sh.comet.v0.feed.playlist#podcast", 62 61 ); 62 + const _viewSchema = /*#__PURE__*/ v.object({ 63 + $type: /*#__PURE__*/ v.optional( 64 + /*#__PURE__*/ v.literal("sh.comet.v0.feed.playlist#view"), 65 + ), 66 + get author() { 67 + return ShCometV0ActorProfile.viewFullSchema; 68 + }, 69 + cid: /*#__PURE__*/ v.cidString(), 70 + commentCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 71 + image: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 72 + indexedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 73 + likeCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 74 + get record() { 75 + return mainSchema; 76 + }, 77 + repostCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 78 + trackCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 79 + get tracks() { 80 + return /*#__PURE__*/ v.array(ShCometV0FeedTrack.viewSchema); 81 + }, 82 + uri: /*#__PURE__*/ v.resourceUriString(), 83 + get viewer() { 84 + return /*#__PURE__*/ v.optional(ShCometV0FeedDefs.viewerStateSchema); 85 + }, 86 + }); 63 87 64 88 type album$schematype = typeof _albumSchema; 65 89 type compilation$schematype = typeof _compilationSchema; 66 90 type main$schematype = typeof _mainSchema; 67 91 type playlist$schematype = typeof _playlistSchema; 68 92 type podcast$schematype = typeof _podcastSchema; 93 + type view$schematype = typeof _viewSchema; 69 94 70 95 export interface albumSchema extends album$schematype {} 71 96 export interface compilationSchema extends compilation$schematype {} 72 97 export interface mainSchema extends main$schematype {} 73 98 export interface playlistSchema extends playlist$schematype {} 74 99 export interface podcastSchema extends podcast$schematype {} 100 + export interface viewSchema extends view$schematype {} 75 101 76 102 export const albumSchema = _albumSchema as albumSchema; 77 103 export const compilationSchema = _compilationSchema as compilationSchema; 78 104 export const mainSchema = _mainSchema as mainSchema; 79 105 export const playlistSchema = _playlistSchema as playlistSchema; 80 106 export const podcastSchema = _podcastSchema as podcastSchema; 107 + export const viewSchema = _viewSchema as viewSchema; 81 108 82 109 export type Album = v.InferInput<typeof albumSchema>; 83 110 export type Compilation = v.InferInput<typeof compilationSchema>; 84 111 export interface Main extends v.InferInput<typeof mainSchema> {} 85 112 export type Playlist = v.InferInput<typeof playlistSchema>; 86 113 export type Podcast = v.InferInput<typeof podcastSchema>; 114 + export interface View extends v.InferInput<typeof viewSchema> {} 87 115 88 116 declare module "@atcute/lexicons/ambient" { 89 117 interface Records {
+5 -1
packages/lexicons/src/types/sh/comet/v0/feed/track.ts
··· 1 1 import type {} from "@atcute/lexicons"; 2 2 import * as v from "@atcute/lexicons/validations"; 3 3 import type {} from "@atcute/lexicons/ambient"; 4 + import * as ShCometV0ActorProfile from "../actor/profile.js"; 4 5 import * as ShCometV0FeedDefs from "./defs.js"; 5 6 import * as ShCometV0RichtextFacet from "../richtext/facet.js"; 6 7 ··· 45 46 /*#__PURE__*/ v.literal("sh.comet.v0.feed.track#view"), 46 47 ), 47 48 audio: /*#__PURE__*/ v.genericUriString(), 48 - author: /*#__PURE__*/ v.unknown(), 49 + get author() { 50 + return ShCometV0ActorProfile.viewFullSchema; 51 + }, 49 52 cid: /*#__PURE__*/ v.cidString(), 53 + commentCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 50 54 image: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 51 55 indexedAt: /*#__PURE__*/ v.datetimeString(), 52 56 likeCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()),