@@ -4,6 +4,7 @@ import type {
44 ApiErrorResponse ,
55 PaginatedResponse ,
66 Stream ,
7+ StreamEventRecord ,
78 Tag ,
89 User ,
910} from "@xstreamroll/types"
@@ -85,6 +86,95 @@ export const authResponseSchema = z.object({
8586 refreshToken : z . string ( ) ,
8687} )
8788
89+ /** The closed set of stream lifecycle/data event types. */
90+ export const streamEventTypeSchema = z . enum ( [
91+ "stream:started" ,
92+ "stream:stopped" ,
93+ "stream:error" ,
94+ "viewer:joined" ,
95+ "viewer:left" ,
96+ "data" ,
97+ ] )
98+
99+ /**
100+ * A single persisted stream event, as returned by `GET /streams/:id/events`
101+ * (issue #396). The `id`/`streamId` are strings on the wire — the same
102+ * stringification that already bit `Stream` and `User` — and the schema
103+ * pins that choice so a regression fails the provider suite.
104+ */
105+ export const streamEventRecordSchema = typed < StreamEventRecord > ( ) (
106+ z . object ( {
107+ id : z . string ( ) ,
108+ streamId : z . string ( ) ,
109+ eventType : streamEventTypeSchema ,
110+ payload : z . record ( z . string ( ) , z . unknown ( ) ) ,
111+ occurredAt : z . string ( ) ,
112+ } ) ,
113+ )
114+
115+ export const paginatedStreamEventsSchema = z . object ( {
116+ data : z . array ( streamEventRecordSchema ) ,
117+ page : z . number ( ) ,
118+ limit : z . number ( ) ,
119+ total : z . number ( ) ,
120+ hasMore : z . boolean ( ) ,
121+ } )
122+
123+ /**
124+ * Aggregate analytics returned by `GET /streams/:id/analytics`.
125+ * Mirrors `StreamAnalyticsDto` in the API — no shared
126+ * `@xstreamroll/types` interface exists yet, so the schema is written
127+ * by hand and pinned by the consumer test's type assertion.
128+ */
129+ export const streamAnalyticsSchema = z . object ( {
130+ streamId : z . number ( ) ,
131+ totalEventsProcessed : z . object ( {
132+ last24h : z . number ( ) ,
133+ last7d : z . number ( ) ,
134+ last30d : z . number ( ) ,
135+ } ) ,
136+ errorRate : z . object ( {
137+ window : z . literal ( "30d" ) ,
138+ totalEvents : z . number ( ) ,
139+ errorEvents : z . number ( ) ,
140+ percentage : z . number ( ) ,
141+ } ) ,
142+ processingLatency : z . object ( {
143+ window : z . literal ( "30d" ) ,
144+ averageMs : z . number ( ) . nullable ( ) ,
145+ p99Ms : z . number ( ) . nullable ( ) ,
146+ } ) ,
147+ eventsPerMinute : z . array (
148+ z . object ( {
149+ minute : z . string ( ) ,
150+ count : z . number ( ) ,
151+ } ) ,
152+ ) ,
153+ generatedAt : z . string ( ) ,
154+ } )
155+
156+ /**
157+ * A single unread notification, as returned by `GET /notifications`.
158+ * Numeric ids and ISO-string timestamps on the wire.
159+ */
160+ export const notificationSchema = z . object ( {
161+ id : z . number ( ) ,
162+ userId : z . number ( ) ,
163+ type : z . string ( ) ,
164+ payload : z . record ( z . string ( ) , z . unknown ( ) ) ,
165+ readAt : z . string ( ) . nullable ( ) ,
166+ createdAt : z . string ( ) ,
167+ expiresAt : z . string ( ) ,
168+ } )
169+
170+ export const notificationsPageSchema = z . object ( {
171+ data : z . array ( notificationSchema ) ,
172+ page : z . number ( ) ,
173+ limit : z . number ( ) ,
174+ total : z . number ( ) ,
175+ unreadCount : z . number ( ) ,
176+ } )
177+
88178/**
89179 * Shape returned by `POST /streams/events` (issue #514) and, per row,
90180 * by `GET /streams/pending` — the `stream_data` wire shape the worker
@@ -98,14 +188,18 @@ export const pendingStreamEventSchema = z.object({
98188
99189/**
100190 * A webhook subscription as returned by `POST /webhooks` — the only
101- * response that includes the signing `secret`.
191+ * response that includes the signing `secret`. Field types mirror the
192+ * SDK's `WebhookSubscription` exactly (ids accept string or number on
193+ * the wire; `events` is pinned to the closed {@link streamEventTypeSchema}
194+ * union the SDK's `StreamEventType` declares) so a server-side type
195+ * change fails CI (issue #534).
102196 */
103197export const webhookSubscriptionSchema = z . object ( {
104198 id : z . union ( [ z . string ( ) , z . number ( ) ] ) ,
105199 userId : z . union ( [ z . string ( ) , z . number ( ) ] ) ,
106200 streamId : z . union ( [ z . string ( ) , z . number ( ) ] ) ,
107201 url : z . string ( ) ,
108- events : z . array ( z . string ( ) ) ,
202+ events : z . array ( streamEventTypeSchema ) ,
109203 secret : z . string ( ) ,
110204 active : z . boolean ( ) ,
111205 createdAt : z . string ( ) ,
@@ -127,11 +221,16 @@ export const paginatedWebhookSubscriptionsSchema = z.object({
127221 limit : z . number ( ) ,
128222} )
129223
130- /** A single webhook delivery, as returned by the deliveries endpoints. */
224+ /**
225+ * A single webhook delivery, as returned by the deliveries endpoints.
226+ * Field types mirror the SDK's `WebhookDelivery` exactly (issue #534),
227+ * including the `id`/`webhookSubscriptionId` string-vs-number choice and
228+ * the closed `event` union.
229+ */
131230export const webhookDeliverySchema = z . object ( {
132231 id : z . union ( [ z . string ( ) , z . number ( ) ] ) ,
133232 webhookSubscriptionId : z . union ( [ z . string ( ) , z . number ( ) ] ) ,
134- event : z . string ( ) ,
233+ event : streamEventTypeSchema ,
135234 payload : z . record ( z . string ( ) , z . unknown ( ) ) ,
136235 status : z . enum ( [ "pending" , "success" , "failed" ] ) ,
137236 attemptCount : z . number ( ) ,
@@ -143,6 +242,13 @@ export const webhookDeliverySchema = z.object({
143242 createdAt : z . string ( ) ,
144243} )
145244
245+ export const paginatedWebhookDeliveriesSchema = z . object ( {
246+ data : z . array ( webhookDeliverySchema ) ,
247+ total : z . number ( ) ,
248+ page : z . number ( ) ,
249+ limit : z . number ( ) ,
250+ } )
251+
146252export const apiErrorSchema = typed < ApiErrorResponse > ( ) (
147253 z . object ( {
148254 statusCode : z . number ( ) ,
0 commit comments