Compare commits

..

6 Commits

Author SHA1 Message Date
Dean Wenstrand
8441bb12e9 Select stream.id in upload-status operations so the cache stays normalized
All checks were successful
Tests / Tests (pull_request) Successful in 11s
UploadStreamWithDetails and the editUploadStream response selected
stream fields without id. Apollo cannot normalize an object with no id
selected, so every upload-service poll rewrote VideoGQL.stream as an
anonymous inline object — clobbering the id that feed/detail queries
select. Their cache reads went partial and useQuery silently fell back
to the last network result, rendered as the home feed collapsing to
the first page (and bouncing the viewport) whenever an upload was
polling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 19:27:25 -07:00
6b8fdd4262 Add per-session Coach GraphQL contract
All checks were successful
Tests / Tests (pull_request) Successful in 10s
2026-07-16 16:45:55 -07:00
bfaed33c89 Merge pull request 'feed: select uploadCompletionCursor on VideoCardFields stream' (#296) from dean/feed-upload-completion-cursor into master
Reviewed-on: #296
2026-07-14 18:24:54 +00:00
Dean Wenstrand
1ce3aaed1b feed: select uploadCompletionCursor on VideoCardFields stream
All checks were successful
Tests / Tests (pull_request) Successful in 24s
Lets the client distinguish a finalized-but-not-fully-uploaded stream
(is_completed set, but received cursor short of last_intended_segment_bound)
from one that is genuinely processing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 11:15:41 -07:00
1bd03f2bbe Merge pull request 'dean/unify-shotlab-cohorts' (#295) from dean/unify-shotlab-cohorts into master
Reviewed-on: #295
2026-07-13 01:08:48 +00:00
Dean Wenstrand
4a1c41aae1 Regenerate schema and hooks from backend master
All checks were successful
Tests / Tests (pull_request) Successful in 12s
Union of the cohort funnel, Shot Lab picker queries, and transient
homography IDs — converges the three divergent submodule pointers
(gql master, railbird master, railbird-mobile master).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:03:33 -07:00
5 changed files with 674 additions and 76 deletions

View File

@@ -3088,6 +3088,7 @@ export type Query = {
getQuotaStatus: QuotaStatusGql; getQuotaStatus: QuotaStatusGql;
getResolvedTier: ResolvedTierGql; getResolvedTier: ResolvedTierGql;
getRuns: GetRunsResult; getRuns: GetRunsResult;
getSessionCoach: SessionCoachGql;
getShotAnnotationTypes: Array<ShotAnnotationTypeGql>; getShotAnnotationTypes: Array<ShotAnnotationTypeGql>;
getShotTableState: ShotTableStateGql; getShotTableState: ShotTableStateGql;
getShots: Array<ShotGql>; getShots: Array<ShotGql>;
@@ -3231,6 +3232,10 @@ export type QueryGetRunsArgs = {
runsOrdering?: InputMaybe<GetRunsOrdering>; runsOrdering?: InputMaybe<GetRunsOrdering>;
}; };
export type QueryGetSessionCoachArgs = {
videoId: Scalars["Int"]["input"];
};
export type QueryGetShotAnnotationTypesArgs = { export type QueryGetShotAnnotationTypesArgs = {
errorTypes?: InputMaybe<Scalars["Boolean"]["input"]>; errorTypes?: InputMaybe<Scalars["Boolean"]["input"]>;
}; };
@@ -3546,6 +3551,84 @@ export type SerializedShotPathsGql = {
b64EncodedBuffer?: Maybe<Scalars["String"]["output"]>; b64EncodedBuffer?: Maybe<Scalars["String"]["output"]>;
}; };
export enum SessionCoachCandidateFamilyEnum {
Aiming = "AIMING",
BackCut = "BACK_CUT",
CueToObjectDistance = "CUE_TO_OBJECT_DISTANCE",
CutAngle = "CUT_ANGLE",
ObjectToPocketDistance = "OBJECT_TO_POCKET_DISTANCE",
Spin = "SPIN",
}
export type SessionCoachCandidateGql = {
__typename?: "SessionCoachCandidateGQL";
action: Scalars["String"]["output"];
body: Scalars["String"]["output"];
drillId?: Maybe<Scalars["String"]["output"]>;
evidence: SessionCoachEvidenceGql;
family: SessionCoachCandidateFamilyEnum;
id: Scalars["ID"]["output"];
kind: SessionCoachCandidateKindEnum;
limitations: Array<Scalars["String"]["output"]>;
rank: Scalars["Int"]["output"];
supportingShotIds: Array<Scalars["Int"]["output"]>;
title: Scalars["String"]["output"];
};
export enum SessionCoachCandidateKindEnum {
DirectionalMiss = "DIRECTIONAL_MISS",
OutcomePattern = "OUTCOME_PATTERN",
UsagePattern = "USAGE_PATTERN",
}
export type SessionCoachEvidenceGql = {
__typename?: "SessionCoachEvidenceGQL";
attemptCount: Scalars["Int"]["output"];
bucketKey: Scalars["String"]["output"];
bucketLabel: Scalars["String"]["output"];
comparisonAttemptCount: Scalars["Int"]["output"];
comparisonMadeCount: Scalars["Int"]["output"];
comparisonMakePercentage?: Maybe<Scalars["Float"]["output"]>;
madeCount: Scalars["Int"]["output"];
makePercentage: Scalars["Float"]["output"];
missedCount: Scalars["Int"]["output"];
patternShotCount: Scalars["Int"]["output"];
rankReason: Scalars["String"]["output"];
score: Scalars["Float"]["output"];
};
export type SessionCoachGql = {
__typename?: "SessionCoachGQL";
analysisVersion: Scalars["String"]["output"];
analyzedShotCount: Scalars["Int"]["output"];
candidates: Array<SessionCoachCandidateGql>;
generatedAt: Scalars["DateTime"]["output"];
ineligibilityReason?: Maybe<SessionCoachIneligibilityReasonEnum>;
madeCount: Scalars["Int"]["output"];
makePercentage?: Maybe<Scalars["Float"]["output"]>;
missedCount: Scalars["Int"]["output"];
primaryCandidate?: Maybe<SessionCoachCandidateGql>;
processingId?: Maybe<Scalars["Int"]["output"]>;
processingStatus?: Maybe<Scalars["String"]["output"]>;
shotCount: Scalars["Int"]["output"];
state: SessionCoachStateEnum;
summary?: Maybe<Scalars["String"]["output"]>;
unknownOutcomeCount: Scalars["Int"]["output"];
videoId: Scalars["Int"]["output"];
};
export enum SessionCoachIneligibilityReasonEnum {
Multiplayer = "MULTIPLAYER",
}
export enum SessionCoachStateEnum {
Failed = "FAILED",
Ineligible = "INELIGIBLE",
NoTrackedShots = "NO_TRACKED_SHOTS",
Processing = "PROCESSING",
Ready = "READY",
}
export type ShotAnnotationGql = { export type ShotAnnotationGql = {
__typename?: "ShotAnnotationGQL"; __typename?: "ShotAnnotationGQL";
createdAt?: Maybe<Scalars["DateTime"]["output"]>; createdAt?: Maybe<Scalars["DateTime"]["output"]>;
@@ -4784,6 +4867,7 @@ export type GetFeedQuery = {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string; id: string;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number;
streamSegmentType: StreamSegmentTypeEnum; streamSegmentType: StreamSegmentTypeEnum;
isCompleted: boolean; isCompleted: boolean;
lastSegmentUploadedAt?: any | null; lastSegmentUploadedAt?: any | null;
@@ -4898,6 +4982,7 @@ export type VideoCardFieldsFragment = {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string; id: string;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number;
streamSegmentType: StreamSegmentTypeEnum; streamSegmentType: StreamSegmentTypeEnum;
isCompleted: boolean; isCompleted: boolean;
lastSegmentUploadedAt?: any | null; lastSegmentUploadedAt?: any | null;
@@ -5045,6 +5130,7 @@ export type GetVideoFeedQuery = {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string; id: string;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number;
streamSegmentType: StreamSegmentTypeEnum; streamSegmentType: StreamSegmentTypeEnum;
isCompleted: boolean; isCompleted: boolean;
lastSegmentUploadedAt?: any | null; lastSegmentUploadedAt?: any | null;
@@ -6763,6 +6849,116 @@ export type GetRunsWithTimestampsQuery = {
}; };
}; };
export type GetSessionCoachQueryVariables = Exact<{
videoId: Scalars["Int"]["input"];
}>;
export type GetSessionCoachQuery = {
__typename?: "Query";
getSessionCoach: {
__typename?: "SessionCoachGQL";
videoId: number;
processingId?: number | null;
analysisVersion: string;
state: SessionCoachStateEnum;
generatedAt: any;
processingStatus?: string | null;
ineligibilityReason?: SessionCoachIneligibilityReasonEnum | null;
summary?: string | null;
shotCount: number;
madeCount: number;
missedCount: number;
unknownOutcomeCount: number;
analyzedShotCount: number;
makePercentage?: number | null;
primaryCandidate?: {
__typename?: "SessionCoachCandidateGQL";
id: string;
rank: number;
family: SessionCoachCandidateFamilyEnum;
kind: SessionCoachCandidateKindEnum;
title: string;
body: string;
action: string;
supportingShotIds: Array<number>;
drillId?: string | null;
limitations: Array<string>;
evidence: {
__typename?: "SessionCoachEvidenceGQL";
bucketKey: string;
bucketLabel: string;
attemptCount: number;
madeCount: number;
missedCount: number;
makePercentage: number;
comparisonAttemptCount: number;
comparisonMadeCount: number;
comparisonMakePercentage?: number | null;
patternShotCount: number;
score: number;
rankReason: string;
};
} | null;
candidates: Array<{
__typename?: "SessionCoachCandidateGQL";
id: string;
rank: number;
family: SessionCoachCandidateFamilyEnum;
kind: SessionCoachCandidateKindEnum;
title: string;
body: string;
action: string;
supportingShotIds: Array<number>;
drillId?: string | null;
limitations: Array<string>;
evidence: {
__typename?: "SessionCoachEvidenceGQL";
bucketKey: string;
bucketLabel: string;
attemptCount: number;
madeCount: number;
missedCount: number;
makePercentage: number;
comparisonAttemptCount: number;
comparisonMadeCount: number;
comparisonMakePercentage?: number | null;
patternShotCount: number;
score: number;
rankReason: string;
};
}>;
};
};
export type SessionCoachCandidateFieldsFragment = {
__typename?: "SessionCoachCandidateGQL";
id: string;
rank: number;
family: SessionCoachCandidateFamilyEnum;
kind: SessionCoachCandidateKindEnum;
title: string;
body: string;
action: string;
supportingShotIds: Array<number>;
drillId?: string | null;
limitations: Array<string>;
evidence: {
__typename?: "SessionCoachEvidenceGQL";
bucketKey: string;
bucketLabel: string;
attemptCount: number;
madeCount: number;
missedCount: number;
makePercentage: number;
comparisonAttemptCount: number;
comparisonMadeCount: number;
comparisonMakePercentage?: number | null;
patternShotCount: number;
score: number;
rankReason: string;
};
};
export type PlayerSummaryFieldsFragment = { export type PlayerSummaryFieldsFragment = {
__typename?: "PlayerSummaryGQL"; __typename?: "PlayerSummaryGQL";
clusterId: number; clusterId: number;
@@ -6920,7 +7116,6 @@ export type GetLiveTableStateQuery = {
__typename?: "RGBColorGQL"; __typename?: "RGBColorGQL";
hex: string; hex: string;
} | null>; } | null>;
<<<<<<< HEAD
}; };
}; };
}; };
@@ -6943,36 +7138,6 @@ export type GetShotTableStateQuery = {
__typename?: "RGBColorGQL"; __typename?: "RGBColorGQL";
hex: string; hex: string;
} | null>; } | null>;
=======
homography?: {
__typename?: "HomographyInfoGQL";
id?: number | null;
frameIndex: number;
crop: {
__typename?: "BoundingBoxGQL";
left: number;
top: number;
width: number;
height: number;
};
pockets: Array<{
__typename?: "BoundingBoxGQL";
left: number;
top: number;
width: number;
height: number;
}>;
sourcePoints: {
__typename?: "PocketPointsGQL";
topLeft: { __typename?: "IntPoint2D"; x: number; y: number };
topSide: { __typename?: "IntPoint2D"; x: number; y: number };
topRight: { __typename?: "IntPoint2D"; x: number; y: number };
bottomLeft: { __typename?: "IntPoint2D"; x: number; y: number };
bottomSide: { __typename?: "IntPoint2D"; x: number; y: number };
bottomRight: { __typename?: "IntPoint2D"; x: number; y: number };
};
} | null;
>>>>>>> 6d13dc3
}; };
}; };
}; };
@@ -8224,6 +8389,7 @@ export type GetVideoCardQuery = {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string; id: string;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number;
streamSegmentType: StreamSegmentTypeEnum; streamSegmentType: StreamSegmentTypeEnum;
isCompleted: boolean; isCompleted: boolean;
lastSegmentUploadedAt?: any | null; lastSegmentUploadedAt?: any | null;
@@ -8865,6 +9031,7 @@ export type GetUploadLinkMutation = {
}; };
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string;
uploadCompletionCursor: number; uploadCompletionCursor: number;
} | null; } | null;
}; };
@@ -8963,6 +9130,7 @@ export type UploadStreamWithDetailsFragment = {
startTime?: any | null; startTime?: any | null;
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean; isCompleted: boolean;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number; uploadCompletionCursor: number;
@@ -8987,6 +9155,7 @@ export type GetUploadStreamsWithDetailsQuery = {
startTime?: any | null; startTime?: any | null;
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean; isCompleted: boolean;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number; uploadCompletionCursor: number;
@@ -9051,6 +9220,7 @@ export const VideoCardFieldsFragmentDoc = gql`
stream { stream {
id id
lastIntendedSegmentBound lastIntendedSegmentBound
uploadCompletionCursor
streamSegmentType streamSegmentType
isCompleted isCompleted
lastSegmentUploadedAt lastSegmentUploadedAt
@@ -9238,6 +9408,34 @@ export const PocketingIntentionFragmentFragmentDoc = gql`
difficulty difficulty
} }
`; `;
export const SessionCoachCandidateFieldsFragmentDoc = gql`
fragment SessionCoachCandidateFields on SessionCoachCandidateGQL {
id
rank
family
kind
title
body
action
supportingShotIds
drillId
limitations
evidence {
bucketKey
bucketLabel
attemptCount
madeCount
missedCount
makePercentage
comparisonAttemptCount
comparisonMadeCount
comparisonMakePercentage
patternShotCount
score
rankReason
}
}
`;
export const PlayerClusterShotFieldsFragmentDoc = gql` export const PlayerClusterShotFieldsFragmentDoc = gql`
fragment PlayerClusterShotFields on PlayerClusterShotGQL { fragment PlayerClusterShotFields on PlayerClusterShotGQL {
shotId shotId
@@ -9497,6 +9695,7 @@ export const UploadStreamWithDetailsFragmentDoc = gql`
name name
startTime startTime
stream { stream {
id
isCompleted isCompleted
lastIntendedSegmentBound lastIntendedSegmentBound
uploadCompletionCursor uploadCompletionCursor
@@ -14726,6 +14925,99 @@ export type GetRunsWithTimestampsQueryResult = Apollo.QueryResult<
GetRunsWithTimestampsQuery, GetRunsWithTimestampsQuery,
GetRunsWithTimestampsQueryVariables GetRunsWithTimestampsQueryVariables
>; >;
export const GetSessionCoachDocument = gql`
query GetSessionCoach($videoId: Int!) {
getSessionCoach(videoId: $videoId) {
videoId
processingId
analysisVersion
state
generatedAt
processingStatus
ineligibilityReason
summary
shotCount
madeCount
missedCount
unknownOutcomeCount
analyzedShotCount
makePercentage
primaryCandidate {
...SessionCoachCandidateFields
}
candidates {
...SessionCoachCandidateFields
}
}
}
${SessionCoachCandidateFieldsFragmentDoc}
`;
/**
* __useGetSessionCoachQuery__
*
* To run a query within a React component, call `useGetSessionCoachQuery` and pass it any options that fit your needs.
* When your component renders, `useGetSessionCoachQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetSessionCoachQuery({
* variables: {
* videoId: // value for 'videoId'
* },
* });
*/
export function useGetSessionCoachQuery(
baseOptions: Apollo.QueryHookOptions<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<GetSessionCoachQuery, GetSessionCoachQueryVariables>(
GetSessionCoachDocument,
options,
);
}
export function useGetSessionCoachLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>(GetSessionCoachDocument, options);
}
export function useGetSessionCoachSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>(GetSessionCoachDocument, options);
}
export type GetSessionCoachQueryHookResult = ReturnType<
typeof useGetSessionCoachQuery
>;
export type GetSessionCoachLazyQueryHookResult = ReturnType<
typeof useGetSessionCoachLazyQuery
>;
export type GetSessionCoachSuspenseQueryHookResult = ReturnType<
typeof useGetSessionCoachSuspenseQuery
>;
export type GetSessionCoachQueryResult = Apollo.QueryResult<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>;
export const VideoPlayerClustersDocument = gql` export const VideoPlayerClustersDocument = gql`
query VideoPlayerClusters($videoId: Int!) { query VideoPlayerClusters($videoId: Int!) {
videoPlayerClusters(videoId: $videoId) { videoPlayerClusters(videoId: $videoId) {
@@ -19187,6 +19479,7 @@ export const GetUploadLinkDocument = gql`
} }
} }
stream { stream {
id
uploadCompletionCursor uploadCompletionCursor
} }
} }

View File

@@ -41,6 +41,7 @@ fragment VideoCardFields on VideoGQL {
stream { stream {
id id
lastIntendedSegmentBound lastIntendedSegmentBound
uploadCompletionCursor
streamSegmentType streamSegmentType
isCompleted isCompleted
lastSegmentUploadedAt lastSegmentUploadedAt

View File

@@ -0,0 +1,51 @@
query GetSessionCoach($videoId: Int!) {
getSessionCoach(videoId: $videoId) {
videoId
processingId
analysisVersion
state
generatedAt
processingStatus
ineligibilityReason
summary
shotCount
madeCount
missedCount
unknownOutcomeCount
analyzedShotCount
makePercentage
primaryCandidate {
...SessionCoachCandidateFields
}
candidates {
...SessionCoachCandidateFields
}
}
}
fragment SessionCoachCandidateFields on SessionCoachCandidateGQL {
id
rank
family
kind
title
body
action
supportingShotIds
drillId
limitations
evidence {
bucketKey
bucketLabel
attemptCount
madeCount
missedCount
makePercentage
comparisonAttemptCount
comparisonMadeCount
comparisonMakePercentage
patternShotCount
score
rankReason
}
}

View File

@@ -48,6 +48,7 @@ mutation GetUploadLink($videoId: Int!, $segmentIndex: Int!) {
} }
} }
stream { stream {
id
uploadCompletionCursor uploadCompletionCursor
} }
} }
@@ -127,6 +128,7 @@ fragment UploadStreamWithDetails on VideoGQL {
name name
startTime startTime
stream { stream {
id
isCompleted isCompleted
lastIntendedSegmentBound lastIntendedSegmentBound
uploadCompletionCursor uploadCompletionCursor

View File

@@ -1,5 +1,7 @@
type Query { type Query {
getAggregatedShotMetrics(aggregateInput: AggregateInputGQL!): [AggregateResultGQL!]! getAggregatedShotMetrics(
aggregateInput: AggregateInputGQL!
): [AggregateResultGQL!]!
getBucketSet(keyName: String!): BucketSetGQL getBucketSet(keyName: String!): BucketSetGQL
challenges(includeDismissed: Boolean! = false): [Challenge!]! challenges(includeDismissed: Boolean! = false): [Challenge!]!
myDismissedChallenges: [Challenge!]! myDismissedChallenges: [Challenge!]!
@@ -11,14 +13,44 @@ type Query {
myChallengeEntries: [ChallengeEntry!]! myChallengeEntries: [ChallengeEntry!]!
getDeployedConfig: DeployedConfigGQL! getDeployedConfig: DeployedConfigGQL!
waitFor(duration: Float!): Float! waitFor(duration: Float!): Float!
getFeedVideos(limit: Int! = 5, after: String = null, includePrivate: IncludePrivateEnum! = MINE, includeCallersVideos: Boolean = true, filters: VideoFilterInput = null, feedInput: VideoFeedInputGQL = null): VideoHistoryGQL! getFeedVideos(
getVideoMakePercentageIntervals(videoId: ID!, intervalDuration: Int! = 300): [MakePercentageIntervalGQL!]! limit: Int! = 5
getLongestRunsLeaderboard(interval: TimeInterval = null, when: DateTime = null, limit: Int! = 50, requiredTags: [String!] = null): RunLeaderboardGQL! after: String = null
getDrillRunLeaderboard(drillTag: String!, interval: TimeInterval = null, limit: Int! = 50, tableSizeMin: Float = null, tableSizeMax: Float = null, pocketSizeMin: Float = null, pocketSizeMax: Float = null): DrillRunLeaderboardGQL! includePrivate: IncludePrivateEnum! = MINE
includeCallersVideos: Boolean = true
filters: VideoFilterInput = null
feedInput: VideoFeedInputGQL = null
): VideoHistoryGQL!
getVideoMakePercentageIntervals(
videoId: ID!
intervalDuration: Int! = 300
): [MakePercentageIntervalGQL!]!
getLongestRunsLeaderboard(
interval: TimeInterval = null
when: DateTime = null
limit: Int! = 50
requiredTags: [String!] = null
): RunLeaderboardGQL!
getDrillRunLeaderboard(
drillTag: String!
interval: TimeInterval = null
limit: Int! = 50
tableSizeMin: Float = null
tableSizeMax: Float = null
pocketSizeMin: Float = null
pocketSizeMax: Float = null
): DrillRunLeaderboardGQL!
getMyDrillRuns(drillTag: String!, limit: Int! = 50): [RunGQL!]! getMyDrillRuns(drillTag: String!, limit: Int! = 50): [RunGQL!]!
getMakesLeaderboard(interval: TimeInterval = null, when: DateTime = null): CountLeaderboardGQL! getMakesLeaderboard(
interval: TimeInterval = null
when: DateTime = null
): CountLeaderboardGQL!
getMedals(scope: MedalScope!, userId: Int = null): RequestedMedalsGQL! getMedals(scope: MedalScope!, userId: Int = null): RequestedMedalsGQL!
notifications(limit: Int! = 20, offset: Int! = 0, filters: NotificationFilters = null): NotificationConnection! notifications(
limit: Int! = 20
offset: Int! = 0
filters: NotificationFilters = null
): NotificationConnection!
unreadNotificationCount: Int! unreadNotificationCount: Int!
poolHalls: [PoolHall!]! poolHalls: [PoolHall!]!
claimablePoolHalls: [PoolHall!]! claimablePoolHalls: [PoolHall!]!
@@ -26,25 +58,70 @@ type Query {
claimableCameras(poolHallId: ID!): [PoolHallCamera!]! claimableCameras(poolHallId: ID!): [PoolHallCamera!]!
cameraClaimSession(id: ID!): CameraClaimSession cameraClaimSession(id: ID!): CameraClaimSession
activeCameraLease: CameraLease activeCameraLease: CameraLease
getRuns(filterInput: RunFilterInput!, runIds: [Int!] = null, runsOrdering: GetRunsOrdering = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetRunsResult! getRuns(
filterInput: RunFilterInput!
runIds: [Int!] = null
runsOrdering: GetRunsOrdering = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetRunsResult!
getSessionCoach(videoId: Int!): SessionCoachGQL!
videoPlayerClusters(videoId: Int!): [PlayerClusterGQL!]! videoPlayerClusters(videoId: Int!): [PlayerClusterGQL!]!
getShotAnnotationTypes(errorTypes: Boolean = false): [ShotAnnotationTypeGQL!]! getShotAnnotationTypes(errorTypes: Boolean = false): [ShotAnnotationTypeGQL!]!
getTableState(b64Image: String!, tableSize: Float = 100, useHomography: HomographyInputGQL = null): TableStateGQL! getTableState(
b64Image: String!
tableSize: Float = 100
useHomography: HomographyInputGQL = null
): TableStateGQL!
getLiveTableState(videoId: Int!): LiveTableStateGQL! getLiveTableState(videoId: Int!): LiveTableStateGQL!
getShotTableState(shotId: Int!): ShotTableStateGQL! getShotTableState(shotId: Int!): ShotTableStateGQL!
simulateShot(simulationInput: SimulateShotInputGQL!): ShotProjectionGQL! simulateShot(simulationInput: SimulateShotInputGQL!): ShotProjectionGQL!
computePotAim(simulationInput: SimulateShotInputGQL!, targetBallId: Int!, pocket: PocketIdentifier!): PotAimGQL! computePotAim(
getOrderedShots(filterInput: FilterInput!, ids: [Int!] = null, shotsOrdering: GetShotsOrdering = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetShotsResult! simulationInput: SimulateShotInputGQL!
getShotsWithMetadata(filterInput: FilterInput!, ids: [Int!] = null, shotsPagination: GetShotsPagination = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetShotsResult! targetBallId: Int!
getShots(filterInput: FilterInput!, shotsPagination: GetShotsPagination = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): [ShotGQL!]! pocket: PocketIdentifier!
): PotAimGQL!
getOrderedShots(
filterInput: FilterInput!
ids: [Int!] = null
shotsOrdering: GetShotsOrdering = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetShotsResult!
getShotsWithMetadata(
filterInput: FilterInput!
ids: [Int!] = null
shotsPagination: GetShotsPagination = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetShotsResult!
getShots(
filterInput: FilterInput!
shotsPagination: GetShotsPagination = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): [ShotGQL!]!
getShotsByIds(ids: [Int!]!): [ShotGQL!]! getShotsByIds(ids: [Int!]!): [ShotGQL!]!
getUsageStats(days: Int! = 30): UsageStatsGQL! getUsageStats(days: Int! = 30): UsageStatsGQL!
getUser(userId: Int!): UserGQL getUser(userId: Int!): UserGQL
doesUsernameExist(candidateUsername: String!): Boolean! doesUsernameExist(candidateUsername: String!): Boolean!
getLoggedInUser: UserGQL getLoggedInUser: UserGQL
getUsernames(matchString: String = null, limit: Int = null, after: String = null): [String!]! getUsernames(
getUsersMatching(matchString: String = null, limit: Int = null, after: String = null): [UserGQL!]! matchString: String = null
getUserRelationshipsMatching(userId: Int!, matchString: String = null, limit: Int = 100, after: String = null): UserRelationshipsResult! limit: Int = null
after: String = null
): [String!]!
getUsersMatching(
matchString: String = null
limit: Int = null
after: String = null
): [UserGQL!]!
getUserRelationshipsMatching(
userId: Int!
matchString: String = null
limit: Int = 100
after: String = null
): UserRelationshipsResult!
getAvailableSubscriptionOptions: StripeSubscriptionOptionsGQL! getAvailableSubscriptionOptions: StripeSubscriptionOptionsGQL!
getUserSubscriptionStatus: UserSubscriptionStatusGQL! getUserSubscriptionStatus: UserSubscriptionStatusGQL!
getResolvedTier: ResolvedTierGQL! getResolvedTier: ResolvedTierGQL!
@@ -52,7 +129,12 @@ type Query {
getQuotaStatus: QuotaStatusGQL! getQuotaStatus: QuotaStatusGQL!
getStorageStatus: StorageStatusGQL getStorageStatus: StorageStatusGQL
getPlayTime(userId: Int!, filters: VideoFilterInput = null): UserPlayTimeGQL! getPlayTime(userId: Int!, filters: VideoFilterInput = null): UserPlayTimeGQL!
getUserVideos(userId: Int = null, limit: Int! = 5, after: String = null, filters: VideoFilterInput = null): VideoHistoryGQL! getUserVideos(
userId: Int = null
limit: Int! = 5
after: String = null
filters: VideoFilterInput = null
): VideoHistoryGQL!
getUserTags(includeRetiredTags: Boolean = false): [TagGQL!]! getUserTags(includeRetiredTags: Boolean = false): [TagGQL!]!
getGameTypeTagMetrics(input: GameTypeTagMetricsInput!): [GameTypeTagMetric!]! getGameTypeTagMetrics(input: GameTypeTagMetricsInput!): [GameTypeTagMetric!]!
videoExportJob(jobId: Int!): VideoExportJobGQL videoExportJob(jobId: Int!): VideoExportJobGQL
@@ -124,7 +206,9 @@ input DatetimeRangeAggregationInput {
feature: String! = "created_at" feature: String! = "created_at"
} }
"""Date with time (isoformat)""" """
Date with time (isoformat)
"""
scalar DateTime scalar DateTime
input TimeInterval @oneOf { input TimeInterval @oneOf {
@@ -240,7 +324,9 @@ input DateRangeFilter {
greaterThanInclusive: Boolean! = true greaterThanInclusive: Boolean! = true
} }
"""Date (isoformat)""" """
Date (isoformat)
"""
scalar Date scalar Date
type BucketSetGQL { type BucketSetGQL {
@@ -766,7 +852,8 @@ type MedalGQL {
input MedalScope @oneOf { input MedalScope @oneOf {
videoId: Int videoId: Int
interval: TimeInterval @deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE") interval: TimeInterval
@deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE")
datetimeRange: DatetimeRangeAggregationInput datetimeRange: DatetimeRangeAggregationInput
} }
@@ -909,6 +996,81 @@ input DatetimeOrdering {
startingAt: DateTime = null startingAt: DateTime = null
} }
type SessionCoachGQL {
videoId: Int!
processingId: Int
analysisVersion: String!
state: SessionCoachStateEnum!
generatedAt: DateTime!
processingStatus: String
ineligibilityReason: SessionCoachIneligibilityReasonEnum
summary: String
shotCount: Int!
madeCount: Int!
missedCount: Int!
unknownOutcomeCount: Int!
analyzedShotCount: Int!
makePercentage: Float
primaryCandidate: SessionCoachCandidateGQL
candidates: [SessionCoachCandidateGQL!]!
}
enum SessionCoachStateEnum {
PROCESSING
READY
NO_TRACKED_SHOTS
INELIGIBLE
FAILED
}
enum SessionCoachIneligibilityReasonEnum {
MULTIPLAYER
}
type SessionCoachCandidateGQL {
id: ID!
rank: Int!
family: SessionCoachCandidateFamilyEnum!
kind: SessionCoachCandidateKindEnum!
title: String!
body: String!
action: String!
supportingShotIds: [Int!]!
drillId: String
evidence: SessionCoachEvidenceGQL!
limitations: [String!]!
}
enum SessionCoachCandidateFamilyEnum {
AIMING
CUT_ANGLE
SPIN
OBJECT_TO_POCKET_DISTANCE
CUE_TO_OBJECT_DISTANCE
BACK_CUT
}
enum SessionCoachCandidateKindEnum {
DIRECTIONAL_MISS
OUTCOME_PATTERN
USAGE_PATTERN
}
type SessionCoachEvidenceGQL {
bucketKey: String!
bucketLabel: String!
attemptCount: Int!
madeCount: Int!
missedCount: Int!
makePercentage: Float!
comparisonAttemptCount: Int!
comparisonMadeCount: Int!
comparisonMakePercentage: Float
patternShotCount: Int!
score: Float!
rankReason: String!
}
type PlayerClusterGQL { type PlayerClusterGQL {
videoId: Int! videoId: Int!
clusterId: Int! clusterId: Int!
@@ -1285,7 +1447,9 @@ type StorageStatusGQL {
lastCalculatedAt: DateTime lastCalculatedAt: DateTime
} }
"""Integer value that can exceed GraphQL Int's 32-bit range.""" """
Integer value that can exceed GraphQL Int's 32-bit range.
"""
scalar BigInt scalar BigInt
type UserPlayTimeGQL { type UserPlayTimeGQL {
@@ -1356,14 +1520,34 @@ enum VideoExportStatusEnum {
""" """
The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
""" """
scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf") scalar JSON
@specifiedBy(
url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf"
)
type Mutation { type Mutation {
createBucketSet(params: CreateBucketSetInput!): BucketSetGQL! createBucketSet(params: CreateBucketSetInput!): BucketSetGQL!
createRuleSet(name: String!, description: String = null): RuleSet! createRuleSet(name: String!, description: String = null): RuleSet!
createChallenge(name: String!, ruleSetId: ID!, minimumShots: Int!, startDate: DateTime!, endDate: DateTime!, description: String = null, requiredTableSize: Float = null, requiredPocketSize: Float = null, isPublic: Boolean! = false, maxAttempts: Int = null): Challenge! createChallenge(
inviteUsersToChallenge(challengeId: ID!, userIds: [ID!]!): [ChallengeInvitation!]! name: String!
respondToChallengeInvitation(invitationId: ID!, accept: Boolean!): ChallengeInvitation! ruleSetId: ID!
minimumShots: Int!
startDate: DateTime!
endDate: DateTime!
description: String = null
requiredTableSize: Float = null
requiredPocketSize: Float = null
isPublic: Boolean! = false
maxAttempts: Int = null
): Challenge!
inviteUsersToChallenge(
challengeId: ID!
userIds: [ID!]!
): [ChallengeInvitation!]!
respondToChallengeInvitation(
invitationId: ID!
accept: Boolean!
): ChallengeInvitation!
startChallenge(challengeId: ID!): ChallengeEntry! startChallenge(challengeId: ID!): ChallengeEntry!
recalculateChallengeEntry(entryId: ID!): ChallengeEntry! recalculateChallengeEntry(entryId: ID!): ChallengeEntry!
submitChallengeEntry(entryId: ID!, videoId: ID!): ChallengeEntry! submitChallengeEntry(entryId: ID!, videoId: ID!): ChallengeEntry!
@@ -1371,30 +1555,61 @@ type Mutation {
undismissChallenge(challengeId: ID!): Boolean! undismissChallenge(challengeId: ID!): Boolean!
setLoggerLevel(path: String!, level: String!): Boolean! setLoggerLevel(path: String!, level: String!): Boolean!
reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean! reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean!
commentOnVideo(videoId: Int!, message: String!, parentCommentId: Int): Boolean! commentOnVideo(
videoId: Int!
message: String!
parentCommentId: Int
): Boolean!
editComment(videoId: Int!, commentId: Int!, newMessage: String!): Boolean! editComment(videoId: Int!, commentId: Int!, newMessage: String!): Boolean!
deleteComment(videoId: Int!, commentId: Int!): Boolean! deleteComment(videoId: Int!, commentId: Int!): Boolean!
blockContent(videoId: Int!): Boolean! blockContent(videoId: Int!): Boolean!
blockUser(userId: Int!): Boolean! blockUser(userId: Int!): Boolean!
reportContent(videoId: Int!, reason: ReportReasonEnum!, customReason: String = null): Boolean! reportContent(
videoId: Int!
reason: ReportReasonEnum!
customReason: String = null
): Boolean!
markNotificationAsRead(notificationId: Int!): Boolean! markNotificationAsRead(notificationId: Int!): Boolean!
markAllNotificationsAsRead: Boolean! markAllNotificationsAsRead: Boolean!
markNotificationsAsRead(notificationIds: [Int!]!): Boolean! markNotificationsAsRead(notificationIds: [Int!]!): Boolean!
deleteNotification(notificationId: Int!): Boolean! deleteNotification(notificationId: Int!): Boolean!
createPoolHall(input: CreatePoolHallInput!): PoolHall! createPoolHall(input: CreatePoolHallInput!): PoolHall!
updatePoolHall(input: UpdatePoolHallInput!): PoolHall! updatePoolHall(input: UpdatePoolHallInput!): PoolHall!
createPoolHallCamera(input: CreatePoolHallCameraInput!): PoolHallCameraStreamCredentials! createPoolHallCamera(
input: CreatePoolHallCameraInput!
): PoolHallCameraStreamCredentials!
updatePoolHallCamera(input: UpdatePoolHallCameraInput!): PoolHallCamera! updatePoolHallCamera(input: UpdatePoolHallCameraInput!): PoolHallCamera!
rotatePoolHallCameraStreamKey(cameraId: ID!): PoolHallCameraStreamCredentials! rotatePoolHallCameraStreamKey(cameraId: ID!): PoolHallCameraStreamCredentials!
createCameraClaimSession(cameraId: ID!, durationMinutes: Int = null, videoName: String = null, videoPrivate: Boolean = null, agreedTermsVersion: Int = null, tags: [VideoTagInput!] = null): CameraClaimSession! createCameraClaimSession(
cameraId: ID!
durationMinutes: Int = null
videoName: String = null
videoPrivate: Boolean = null
agreedTermsVersion: Int = null
tags: [VideoTagInput!] = null
): CameraClaimSession!
cancelCameraClaimSession(claimSessionId: ID!): CameraClaimSession! cancelCameraClaimSession(claimSessionId: ID!): CameraClaimSession!
endCameraLease(leaseId: ID!): CameraLease! endCameraLease(leaseId: ID!): CameraLease!
extendCameraLease(leaseId: ID!, additionalMinutes: Int! = 60): CameraLease! extendCameraLease(leaseId: ID!, additionalMinutes: Int! = 60): CameraLease!
finalizePlayerAssignments(input: FinalizePlayerAssignmentsInput!): [PlayerClusterGQL!]! finalizePlayerAssignments(
addAnnotationToShot(shotId: Int!, annotationName: String!, notes: String = null): AddShotAnnotationReturn! input: FinalizePlayerAssignmentsInput!
updateShotAnnotations(shotId: Int!, annotations: [UpdateAnnotationInputGQL!]!): UpdateShotAnnotationReturn! ): [PlayerClusterGQL!]!
editShot(shotId: Int!, fieldsToEdit: EditableShotFieldInputGQL!): EditShotReturn! addAnnotationToShot(
getProfileImageUploadLink(fileExt: String = ".png"): GetProfileUploadLinkReturn! shotId: Int!
annotationName: String!
notes: String = null
): AddShotAnnotationReturn!
updateShotAnnotations(
shotId: Int!
annotations: [UpdateAnnotationInputGQL!]!
): UpdateShotAnnotationReturn!
editShot(
shotId: Int!
fieldsToEdit: EditableShotFieldInputGQL!
): EditShotReturn!
getProfileImageUploadLink(
fileExt: String = ".png"
): GetProfileUploadLinkReturn!
editProfileImageUri(profileImageUri: String!): UserGQL! editProfileImageUri(profileImageUri: String!): UserGQL!
editUser(input: EditUserInputGQL!): UserGQL! editUser(input: EditUserInputGQL!): UserGQL!
followUser(followedUserId: Int!): UserGQL! followUser(followedUserId: Int!): UserGQL!
@@ -1402,20 +1617,39 @@ type Mutation {
retireTags(tagIds: [Int!]!): Boolean! retireTags(tagIds: [Int!]!): Boolean!
ensureStripeCustomerExists: UserGQL! ensureStripeCustomerExists: UserGQL!
deleteUser: Boolean! deleteUser: Boolean!
syncAppleSubscription(input: SyncAppleSubscriptionInputGQL!): SyncAppleSubscriptionResultGQL! syncAppleSubscription(
input: SyncAppleSubscriptionInputGQL!
): SyncAppleSubscriptionResultGQL!
createSubscription(priceId: String!): CreateSubscriptionResultGQL! createSubscription(priceId: String!): CreateSubscriptionResultGQL!
createCustomerPortalSession: CreateCustomerPortalSessionResultGQL! createCustomerPortalSession: CreateCustomerPortalSessionResultGQL!
cancelSubscription: UserSubscriptionStatusGQL! cancelSubscription: UserSubscriptionStatusGQL!
grantManualEntitlement(userId: Int!, tierName: String! = "pro", startsAt: DateTime = null, endsAt: DateTime = null, reason: String = null): UserSubscriptionStatusGQL! grantManualEntitlement(
userId: Int!
tierName: String! = "pro"
startsAt: DateTime = null
endsAt: DateTime = null
reason: String = null
): UserSubscriptionStatusGQL!
revokeManualEntitlement(userId: Int!): UserSubscriptionStatusGQL! revokeManualEntitlement(userId: Int!): UserSubscriptionStatusGQL!
submitCancellationFeedback(reasons: [CancellationReasonEnum!] = null, feedback: String = null, metadata: CancellationFeedbackMetadataInput = null): Boolean! submitCancellationFeedback(
reasons: [CancellationReasonEnum!] = null
feedback: String = null
metadata: CancellationFeedbackMetadataInput = null
): Boolean!
requestVideoExport(input: RequestVideoExportInput!): VideoExportJobGQL! requestVideoExport(input: RequestVideoExportInput!): VideoExportJobGQL!
dismissVideoExport(jobId: Int!): Boolean! dismissVideoExport(jobId: Int!): Boolean!
findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL
createUploadStream(videoMetadata: VideoMetadataInput!, expectedDurationSeconds: Float = null): CreateUploadStreamReturn! createUploadStream(
videoMetadata: VideoMetadataInput!
expectedDurationSeconds: Float = null
): CreateUploadStreamReturn!
getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn! getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn!
getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn! getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn!
setSegmentDuration(videoId: Int!, segmentIndex: Int!, duration: Float!): Boolean! setSegmentDuration(
videoId: Int!
segmentIndex: Int!
duration: Float!
): Boolean!
editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean! editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean!
deleteVideo(videoId: Int!): Boolean! deleteVideo(videoId: Int!): Boolean!
deleteTags(videoId: Int!, tagsToDelete: [VideoTagInput!]!): Boolean! deleteTags(videoId: Int!, tagsToDelete: [VideoTagInput!]!): Boolean!
@@ -1507,7 +1741,9 @@ type AddShotAnnotationReturn {
value: SuccessfulAddAddShotAnnotationErrors! value: SuccessfulAddAddShotAnnotationErrors!
} }
union SuccessfulAddAddShotAnnotationErrors = SuccessfulAdd | AddShotAnnotationErrors union SuccessfulAddAddShotAnnotationErrors =
SuccessfulAdd
| AddShotAnnotationErrors
type SuccessfulAdd { type SuccessfulAdd {
value: Boolean! value: Boolean!
@@ -1517,7 +1753,9 @@ type AddShotAnnotationErrors {
error: DoesNotOwnShotErrOtherErrorNeedsNote! error: DoesNotOwnShotErrOtherErrorNeedsNote!
} }
union DoesNotOwnShotErrOtherErrorNeedsNote = DoesNotOwnShotErr | OtherErrorNeedsNote union DoesNotOwnShotErrOtherErrorNeedsNote =
DoesNotOwnShotErr
| OtherErrorNeedsNote
type DoesNotOwnShotErr { type DoesNotOwnShotErr {
shotId: Int! shotId: Int!
@@ -1558,7 +1796,9 @@ type GetProfileUploadLinkReturn {
value: UploadLinkGetProfileUploadLinkErrors! value: UploadLinkGetProfileUploadLinkErrors!
} }
union UploadLinkGetProfileUploadLinkErrors = UploadLink | GetProfileUploadLinkErrors union UploadLinkGetProfileUploadLinkErrors =
UploadLink
| GetProfileUploadLinkErrors
type UploadLink { type UploadLink {
uploadUrl: String! uploadUrl: String!
@@ -1645,9 +1885,12 @@ input VideoMetadataInput {
videoName: String = null videoName: String = null
startTime: DateTime = null startTime: DateTime = null
endTime: DateTime = null endTime: DateTime = null
gameType: String = null @deprecated(reason: "`game_type` is deprecated. Use `tags` instead.") gameType: String = null
@deprecated(reason: "`game_type` is deprecated. Use `tags` instead.")
"""A list of tags associated with the video. Replace `game_type`""" """
A list of tags associated with the video. Replace `game_type`
"""
tags: [VideoTagInput!] = null tags: [VideoTagInput!] = null
tableSize: Float = null tableSize: Float = null
pocketSize: Float = null pocketSize: Float = null
@@ -1676,7 +1919,15 @@ type GetUploadLinkErrors {
error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr! error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr!
} }
union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr = MustHaveSetForUploadLinkErr | SegmentAlreadyUploadedErr | ProcessingFailedErr | NoInitForChunkedUploadErr | TooManyProfileImageUploadsErr | InitUploadAlreadyCompletedErr | TooManyInitUploadsErr | StorageLimitExceededErr union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr =
MustHaveSetForUploadLinkErr
| SegmentAlreadyUploadedErr
| ProcessingFailedErr
| NoInitForChunkedUploadErr
| TooManyProfileImageUploadsErr
| InitUploadAlreadyCompletedErr
| TooManyInitUploadsErr
| StorageLimitExceededErr
type MustHaveSetForUploadLinkErr { type MustHaveSetForUploadLinkErr {
resolution: Boolean resolution: Boolean