@croutonian/with-openapi 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.d.ts +46 -10
  2. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -660,15 +660,47 @@ type MatchedOperation<Document, Route extends string, Method extends string> = {
660
660
  */
661
661
  type MatchedFor<Document> = { [Route in RoutesOf<Document>]: { [Method in MethodsOf<Document, Route>]: MatchedOperation<Document, Route, Method> }[MethodsOf<Document, Route>] }[RoutesOf<Document>];
662
662
  /**
663
- * What lands at `ctx.openapi` for a given document.
663
+ * Whether `Key` is set to `'pass'` or might be.
664
664
  *
665
- * Falls back to the unspecialized {@link OpenApiContribution} when the
666
- * document arrived without its literal type annotated `: OpenAPIObject`,
667
- * say. `RoutesOf` is `never` there, which would otherwise leave a union with
668
- * no `matched: true` branch at all and break every existing consumer.
669
- * Degrading to today's shape is what keeps this change additive.
665
+ * Everything here fails *safe*: the unmatched branch is only dropped when the
666
+ * config provably cannot produce one. A widened config satisfies
667
+ * `'pass' extends string`, so it keeps the branch rather than promising a
668
+ * `matched: true` the runtime may not deliver.
670
669
  */
671
- type ContributionFor<Document> = [RoutesOf<Document>] extends [never] ? OpenApiContribution : MatchedFor<Document> | OpenApiUnmatched;
670
+ type MayPass<Config, Key extends string> = Key extends keyof Config ? 'pass' extends Config[Key] ? true : false : false;
671
+ /**
672
+ * Whether a `skip` predicate might fire.
673
+ *
674
+ * Asks whether `skip` could *possibly* be a function, not whether it
675
+ * definitely is. `skip: condition ? fn : undefined` has type
676
+ * `Fn | undefined`, and a check of the second kind reads that as "no skip"
677
+ * and drops the branch — while at runtime the predicate fires whenever the
678
+ * condition holds, handing a handler an unmatched contribution the compiler
679
+ * said could not exist.
680
+ */
681
+ type HasSkip<Config> = 'skip' extends keyof Config ? [Config['skip']] extends [undefined] ? false : true : false;
682
+ /**
683
+ * Whether this config can produce an unmatched contribution at all.
684
+ *
685
+ * Mirrors the three runtime branches that call `unmatched()`: a `skip` that
686
+ * fired, `onUnknownRoute: 'pass'` with no route match, `onUnknownMethod:
687
+ * 'pass'` with no operation. On the defaults none of them can, because both
688
+ * options default to `'reject'` and answer the request themselves.
689
+ */
690
+ type CanBeUnmatched<Config> = MayPass<Config, 'onUnknownRoute'> extends true ? true : MayPass<Config, 'onUnknownMethod'> extends true ? true : HasSkip<Config> extends true ? true : false;
691
+ /**
692
+ * What lands at `ctx.openapi` for a given document and config.
693
+ *
694
+ * Two reductions, in order. Handed a document that arrived without its
695
+ * literal type — annotated `: OpenAPIObject`, say — `RoutesOf` is `never`,
696
+ * and this degrades to the unspecialized {@link OpenApiContribution}; without
697
+ * that, the union would have no `matched: true` branch at all and every
698
+ * existing consumer would break. Otherwise the unmatched branch is dropped
699
+ * unless the config can actually produce one, so a downstream middleware or
700
+ * handler can read `route`, `operationId` and `params` without a guard for a
701
+ * case that cannot happen.
702
+ */
703
+ type ContributionFor<Document, Config = WithOpenApiConfig> = [RoutesOf<Document>] extends [never] ? OpenApiContribution : CanBeUnmatched<Config> extends true ? MatchedFor<Document> | OpenApiUnmatched : MatchedFor<Document>;
672
704
  //#endregion
673
705
  //#region src/with-openapi.d.ts
674
706
  /**
@@ -681,15 +713,19 @@ type ContributionFor<Document> = [RoutesOf<Document>] extends [never] ? OpenApiC
681
713
  * handler-taking forms fall through to the general signatures below it, where
682
714
  * `ctx.openapi` keeps its unspecialized shape.
683
715
  *
716
+ * The whole config is captured, not just the document: `onUnknownRoute`,
717
+ * `onUnknownMethod` and `skip` are what decide whether an unmatched
718
+ * contribution is reachable, and on the defaults it is not — so a downstream
719
+ * middleware or handler gets `matched: true` already narrowed, with no guard
720
+ * for a case the config rules out.
721
+ *
684
722
  * The runtime is untouched: this re-describes what `defineMiddleware` already
685
723
  * returns. That makes the description an assertion we own — if `ParamsFor`
686
724
  * ever disagrees with what the middleware actually deserializes, the types
687
725
  * are what lie, and nothing here would catch it.
688
726
  */
689
727
  interface TypedByDocument {
690
- <const Document extends OpenAPIObject>(config: Omit<WithOpenApiConfig, 'document'> & {
691
- document: Document;
692
- }): SingleKeyEntry<'openapi', Record<never, never>, ContributionFor<Document>>;
728
+ <const Config extends WithOpenApiConfig>(config: Config): SingleKeyEntry<'openapi', Record<never, never>, ContributionFor<Config['document'], Config>>;
693
729
  }
694
730
  /**
695
731
  * Middleware that holds an API to its own description.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@croutonian/with-openapi",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "OpenAPI middleware for @supabase/middleware. Matches each request against an OpenAPI 3.1 document, optionally rejects the ones it does not describe, contributes the matched operation and validated params to ctx, and optionally serves a Scalar API reference.",
5
5
  "keywords": [
6
6
  "openapi",