|
1 | 1 | import { ValidationError } from "./error"; |
2 | | -import { asType, type InferInput, isVar, validate } from "./schema"; |
| 2 | +import { |
| 3 | + asType, |
| 4 | + type InferArgs, |
| 5 | + type InferInput, |
| 6 | + isNoInput, |
| 7 | + isNoOutput, |
| 8 | + isVar, |
| 9 | + projectValue, |
| 10 | + rejectFields, |
| 11 | + type SchemaInputOf, |
| 12 | + type SchemaOutputOf, |
| 13 | + toInputSchema, |
| 14 | + validate, |
| 15 | +} from "./schema"; |
3 | 16 | import type { |
4 | 17 | LiteralString, |
5 | 18 | Members, |
6 | 19 | Prettify, |
7 | 20 | UnionToIntersection, |
8 | 21 | } from "./types"; |
9 | 22 |
|
10 | | -/** Payload for each kind in an event-type map. */ |
| 23 | +/** Handler-visible payload (full schema, including noInput / noOutput). */ |
11 | 24 | export type EventPayloads<T> = { |
12 | 25 | [K in keyof T]: InferInput<T[K]>; |
13 | 26 | }; |
14 | 27 |
|
| 28 | +/** What {@link EventDefination.publish} accepts - the `.input` view. */ |
| 29 | +export type EventPublishArgs<T> = { |
| 30 | + [K in keyof T]: InferArgs<SchemaInputOf<T[K]>>; |
| 31 | +}; |
| 32 | + |
| 33 | +/** Validated publish-time payload - parsed `.input` view. */ |
| 34 | +export type EventPublishResult<T> = { |
| 35 | + [K in keyof T]: InferInput<SchemaInputOf<T[K]>>; |
| 36 | +}; |
| 37 | + |
| 38 | +/** What `complete()` resolves to - the `.output` view. */ |
| 39 | +export type EventCompleteResult<T> = { |
| 40 | + [K in keyof T]: InferInput<SchemaOutputOf<T[K]>>; |
| 41 | +}; |
| 42 | + |
15 | 43 | /** Discriminated message handed to subscribers. */ |
16 | 44 | export type EventMessage<T> = Prettify< |
17 | 45 | { |
@@ -47,18 +75,21 @@ export interface EventDefination< |
47 | 75 | */ |
48 | 76 | subscribe: (handler: EventHandler<T>) => () => void; |
49 | 77 | /** |
50 | | - * Validate `data` against the kind's schema, run subscribers (direct + |
51 | | - * any mounted via `v.on` / modules), merge `next` mutations, and return: |
52 | | - * - `result`: the validated payload at publish-time |
53 | | - * - `complete`: a promise function resolving to the final payload after |
54 | | - * the full subscriber chain finishes |
| 78 | + * Validate `data` against the kind's `.input` view, run subscribers |
| 79 | + * (direct + any mounted via `v.on` / modules), merge `next` mutations, |
| 80 | + * and return: |
| 81 | + * - `result`: the validated `.input` payload at publish-time |
| 82 | + * - `complete`: a promise function resolving to the `.output` payload |
| 83 | + * after the full subscriber chain finishes |
55 | 84 | */ |
56 | 85 | publish: <K extends keyof T & string>( |
57 | 86 | type: K, |
58 | | - data: EventPayloads<T>[K], |
| 87 | + data: EventPublishArgs<T>[K], |
59 | 88 | ) => |
60 | | - | [EventPayloads<T>[K], () => Promise<EventPayloads<T>[K]>] |
61 | | - | Promise<[EventPayloads<T>[K], () => Promise<EventPayloads<T>[K]>]>; |
| 89 | + | [EventPublishResult<T>[K], () => Promise<EventCompleteResult<T>[K]>] |
| 90 | + | Promise< |
| 91 | + [EventPublishResult<T>[K], () => Promise<EventCompleteResult<T>[K]>] |
| 92 | + >; |
62 | 93 | /** |
63 | 94 | * Mint a NEW event def under the same name with more kinds - the |
64 | 95 | * re-export pattern (`customize` for vars). Shared bus; widened types. |
@@ -307,12 +338,30 @@ const publishOn = ( |
307 | 338 | } |
308 | 339 | const effective = applyVarExtsToSchema(schema, varExts); |
309 | 340 | const handlers = [...bus.mounted, ...bus.direct]; |
310 | | - return thenMaybe(validate(asType(effective), data, path), (parsed) => { |
| 341 | + // Publish door matches v.fn input: reject smuggled noInput keys, then |
| 342 | + // validate the `.input` view. Handlers still patch against the full |
| 343 | + // schema so they can fill noInput fields via `next`. |
| 344 | + const parseInput = () => |
| 345 | + thenMaybe( |
| 346 | + rejectFields( |
| 347 | + effective, |
| 348 | + data, |
| 349 | + isNoInput, |
| 350 | + path, |
| 351 | + "noInput field is not allowed", |
| 352 | + ), |
| 353 | + () => validate(asType(toInputSchema(effective)), data, path), |
| 354 | + ); |
| 355 | + return thenMaybe(parseInput(), (parsed) => { |
311 | 356 | const done = runHandlers(handlers, type, parsed, effective, path); |
312 | 357 | return [ |
313 | 358 | parsed, |
314 | 359 | () => |
315 | | - isThenable(done) ? (done as Promise<unknown>) : Promise.resolve(done), |
| 360 | + Promise.resolve( |
| 361 | + thenMaybe(done, (final) => |
| 362 | + projectValue(effective, final, isNoOutput), |
| 363 | + ), |
| 364 | + ), |
316 | 365 | ] as [unknown, () => Promise<unknown>]; |
317 | 366 | }); |
318 | 367 | }; |
|
0 commit comments