@opsydyn/elysia-spectral 0.5.1 → 0.5.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.2](https://github.com/opsydyn/elysia-spectral/compare/v0.5.1...v0.5.2) (2026-04-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * replace export * with named exports in core/index.ts for stable public API ([d0ef1c9](https://github.com/opsydyn/elysia-spectral/commit/d0ef1c95618ecd5a83e8224f2ba831b226a7ab38))
9
+
3
10
  ## [0.5.1](https://github.com/opsydyn/elysia-spectral/compare/v0.5.0...v0.5.1) (2026-04-15)
4
11
 
5
12
 
package/README.md CHANGED
@@ -565,17 +565,19 @@ git commit -m "chore: add openapi snapshot"
565
565
 
566
566
  If the snapshot has changed, the CI step fails and the diff is visible in the logs. Deliberate API changes are acknowledged by updating the committed snapshot — accidental ones are caught before they ship.
567
567
 
568
- ### Generate a typed client with openapi-ts
568
+ ### Generate a typed client
569
569
 
570
- Use the committed OpenAPI snapshot as input to [`openapi-ts`](https://openapi-ts.dev) to keep a generated TypeScript client in sync with the API surface.
570
+ **If your consumer is a TypeScript project that can import from your Elysia app, use [Eden Treaty](https://elysiajs.com/eden/treaty/overview) instead.** It derives types directly from the app instance with no codegen, no snapshot, and no drift.
571
571
 
572
- 1. Install `openapi-ts`:
572
+ ```ts
573
+ import { treaty } from '@elysiajs/eden'
574
+ import type { App } from '../server'
573
575
 
574
- ```bash
575
- bun add -d @hey-api/openapi-ts
576
+ const client = treaty<App>('localhost:3000')
577
+ // fully typed — zero codegen
576
578
  ```
577
579
 
578
- 2. Add a codegen script to `package.json`:
580
+ For vendor-agnostic consumers — cross-repo TypeScript, non-TypeScript clients, or a published SDK — use the committed OpenAPI snapshot as input to [`openapi-ts`](https://openapi-ts.dev):
579
581
 
580
582
  ```json
581
583
  {
@@ -585,7 +587,7 @@ bun add -d @hey-api/openapi-ts
585
587
  }
586
588
  ```
587
589
 
588
- 3. Chain it after the lint step in CI:
590
+ Chain it after the lint step in CI and guard against drift:
589
591
 
590
592
  ```yaml
591
593
  - name: Lint OpenAPI spec
@@ -598,7 +600,7 @@ bun add -d @hey-api/openapi-ts
598
600
  run: git diff --exit-code src/generated/client/
599
601
  ```
600
602
 
601
- The lint gate runs first — if the spec is invalid the codegen step never runs. The drift check ensures the committed client always matches the current spec.
603
+ The lint gate runs first — if the spec is invalid the codegen step never runs.
602
604
 
603
605
  ### Work on this repository locally
604
606
 
@@ -630,66 +632,38 @@ That example uses `startup.mode: 'report'`, so the app still boots while the pac
630
632
  ### Package API
631
633
 
632
634
  ```ts
633
- type PresetName = 'recommended' | 'server' | 'strict'
635
+ // ── Vocabulary types ──────────────────────────────────────────────────────────
634
636
 
637
+ type PresetName = 'recommended' | 'server' | 'strict'
638
+ type LintSeverity = 'error' | 'warn' | 'info' | 'hint'
635
639
  type SeverityThreshold = 'error' | 'warn' | 'info' | 'hint' | 'never'
636
-
637
640
  type StartupLintMode = 'enforce' | 'report' | 'off'
638
-
641
+ type LintRunSource = 'startup' | 'healthcheck' | 'manual'
639
642
  type ArtifactWriteFailureMode = 'warn' | 'error'
643
+ type OpenApiLintRuntimeStatus = 'idle' | 'running' | 'passed' | 'failed'
640
644
 
641
- type OpenApiLintArtifacts = {
642
- jsonReportPath?: string
643
- junitReportPath?: string
644
- sarifReportPath?: string
645
- specSnapshotPath?: string
646
- brunoCollectionPath?: string
647
- }
648
-
649
- type OpenApiLintSink = {
650
- name: string
651
- write: (
652
- result: LintRunResult,
653
- context: {
654
- spec: Record<string, unknown>
655
- logger: SpectralLogger
656
- }
657
- ) => undefined | Partial<OpenApiLintArtifacts> | Promise<undefined | Partial<OpenApiLintArtifacts>>
658
- }
659
-
660
- type RulesetResolver = (
661
- input: string | RulesetDefinition | Record<string, unknown> | undefined,
662
- context: {
663
- baseDir: string
664
- defaultRuleset: RulesetDefinition
665
- mergeAutodiscoveredWithDefault: boolean
666
- }
667
- ) => Promise<LoadedRuleset | undefined>
668
-
669
- type LoadResolvedRulesetOptions = {
670
- baseDir?: string
671
- resolvers?: RulesetResolver[]
672
- mergeAutodiscoveredWithDefault?: boolean
673
- /** Override the base ruleset used for autodiscovery merging and the fallback when no ruleset is configured. */
674
- defaultRuleset?: RulesetDefinition
675
- }
645
+ // ── Plugin options ────────────────────────────────────────────────────────────
676
646
 
677
647
  type SpectralPluginOptions = {
678
648
  /** First-party governance preset. Sets the base ruleset and autodiscovery merge target. */
679
649
  preset?: PresetName
680
650
  /** Custom ruleset path, object, or inline definition. Merged on top of preset when both are set. */
681
651
  ruleset?: string | RulesetDefinition | Record<string, unknown>
652
+ /** Severity level at which the lint run is considered failed. Defaults to 'error'. */
682
653
  failOn?: SeverityThreshold
683
654
  healthcheck?: false | { path?: string }
684
655
  output?: {
656
+ /** Print findings to the console. Default: true. */
685
657
  console?: boolean
686
658
  jsonReportPath?: string
687
659
  junitReportPath?: string
688
660
  sarifReportPath?: string
661
+ /** true derives the path from the consuming app's package name. */
689
662
  specSnapshotPath?: string | true
690
663
  /** .yml/.yaml → OpenCollection YAML (Bruno v3+), .json → Bruno collection JSON */
691
664
  brunoCollectionPath?: string
692
665
  pretty?: boolean
666
+ /** Whether artifact write failures throw or warn. Default: 'warn'. */
693
667
  artifactWriteFailures?: ArtifactWriteFailureMode
694
668
  sinks?: OpenApiLintSink[]
695
669
  }
@@ -697,16 +671,160 @@ type SpectralPluginOptions = {
697
671
  specPath?: string
698
672
  baseUrl?: string
699
673
  }
700
- enabled?: boolean | ((env: Record<string, string | undefined>) => boolean)
674
+ /**
675
+ * Controls startup lint behaviour.
676
+ * startup.mode takes precedence over the legacy enabled option.
677
+ * 'enforce' — lint runs at startup and throws on threshold failure (default)
678
+ * 'report' — lint runs at startup, prints findings, but never blocks boot
679
+ * 'off' — startup lint is skipped entirely
680
+ */
701
681
  startup?: {
702
682
  mode?: StartupLintMode
703
683
  }
704
- logger?: {
705
- info: (message: string) => void
706
- warn: (message: string) => void
707
- error: (message: string) => void
684
+ /**
685
+ * Legacy enable flag. Prefer startup.mode for new code.
686
+ * false or () => false is equivalent to startup.mode: 'off'.
687
+ * The function form receives process.env for environment-based toggling.
688
+ */
689
+ enabled?: boolean | ((env: Record<string, string | undefined>) => boolean)
690
+ logger?: SpectralLogger
691
+ }
692
+
693
+ // ── Result types ──────────────────────────────────────────────────────────────
694
+
695
+ type LintFinding = {
696
+ code: string
697
+ message: string
698
+ severity: LintSeverity
699
+ path: Array<string | number>
700
+ documentPointer?: string
701
+ recommendation?: string
702
+ source?: string
703
+ range?: {
704
+ start?: { line: number; character: number }
705
+ end?: { line: number; character: number }
706
+ }
707
+ operation?: {
708
+ method?: string
709
+ path?: string
710
+ operationId?: string
711
+ }
712
+ }
713
+
714
+ type LintRunResult = {
715
+ /** True when no findings meet or exceed the configured failOn threshold. */
716
+ ok: boolean
717
+ generatedAt: string
718
+ /** Where the lint run was triggered from. */
719
+ source: LintRunSource
720
+ summary: {
721
+ error: number
722
+ warn: number
723
+ info: number
724
+ hint: number
725
+ total: number
726
+ }
727
+ artifacts?: OpenApiLintArtifacts
728
+ findings: LintFinding[]
729
+ }
730
+
731
+ type OpenApiLintArtifacts = {
732
+ jsonReportPath?: string
733
+ junitReportPath?: string
734
+ sarifReportPath?: string
735
+ specSnapshotPath?: string
736
+ brunoCollectionPath?: string
737
+ }
738
+
739
+ type OpenApiLintRuntimeFailure = {
740
+ name: string
741
+ message: string
742
+ generatedAt: string
743
+ }
744
+
745
+ // ── Runtime ───────────────────────────────────────────────────────────────────
746
+
747
+ type OpenApiLintRuntime = {
748
+ status: OpenApiLintRuntimeStatus
749
+ startedAt: string | null
750
+ completedAt: string | null
751
+ durationMs: number | null
752
+ latest: LintRunResult | null
753
+ lastSuccess: LintRunResult | null
754
+ lastFailure: OpenApiLintRuntimeFailure | null
755
+ running: boolean
756
+ run: (app: AnyElysia, source?: LintRunSource) => Promise<LintRunResult>
757
+ }
758
+
759
+ function createOpenApiLintRuntime(options?: SpectralPluginOptions): OpenApiLintRuntime
760
+
761
+ // ── Extension points (advanced) ───────────────────────────────────────────────
762
+
763
+ type SpectralLogger = {
764
+ info: (message: string) => void
765
+ warn: (message: string) => void
766
+ error: (message: string) => void
767
+ }
768
+
769
+ type OpenApiLintSinkContext = {
770
+ spec: Record<string, unknown>
771
+ logger: SpectralLogger
772
+ }
773
+
774
+ type OpenApiLintSink = {
775
+ name: string
776
+ write: (
777
+ result: LintRunResult,
778
+ context: OpenApiLintSinkContext,
779
+ ) => undefined | Partial<OpenApiLintArtifacts> | Promise<undefined | Partial<OpenApiLintArtifacts>>
780
+ }
781
+
782
+ type RulesetResolver = (
783
+ input: string | RulesetDefinition | Record<string, unknown> | undefined,
784
+ context: RulesetResolverContext,
785
+ ) => Promise<ResolvedRulesetCandidate | undefined>
786
+
787
+ type RulesetResolverContext = {
788
+ baseDir: string
789
+ defaultRuleset: RulesetDefinition
790
+ mergeAutodiscoveredWithDefault: boolean
791
+ }
792
+
793
+ type ResolvedRulesetCandidate = {
794
+ ruleset: unknown
795
+ source?: LoadedRuleset['source']
796
+ }
797
+
798
+ type LoadedRuleset = {
799
+ ruleset: RulesetDefinition
800
+ source?: {
801
+ path: string
802
+ autodiscovered: boolean
803
+ mergedWithDefault: boolean
708
804
  }
709
805
  }
806
+
807
+ type LoadResolvedRulesetOptions = {
808
+ baseDir?: string
809
+ resolvers?: RulesetResolver[]
810
+ mergeAutodiscoveredWithDefault?: boolean
811
+ /** Override the base ruleset used for autodiscovery merging and the fallback when no ruleset is configured. */
812
+ defaultRuleset?: RulesetDefinition
813
+ }
814
+
815
+ // ── Error classes ─────────────────────────────────────────────────────────────
816
+
817
+ class OpenApiLintThresholdError extends Error {
818
+ readonly threshold: SeverityThreshold
819
+ readonly result: LintRunResult
820
+ }
821
+
822
+ class OpenApiLintArtifactWriteError extends Error {
823
+ readonly artifact: string
824
+ readonly cause: unknown
825
+ }
826
+
827
+ class RulesetLoadError extends Error {}
710
828
  ```
711
829
 
712
830
  ### Presets
@@ -1,2 +1,2 @@
1
- import { _ as lintOpenApi, a as createOpenApiLintRuntime, c as LoadedRuleset, d as RulesetResolver, f as RulesetResolverContext, g as loadRuleset, h as loadResolvedRuleset, i as OpenApiLintArtifactWriteError, l as ResolvedRulesetCandidate, m as defaultRulesetResolvers, n as enforceThreshold, o as resolveStartupMode, p as RulesetResolverInput, r as shouldFail, s as LoadResolvedRulesetOptions, t as OpenApiLintThresholdError, u as RulesetLoadError } from "../index-B4fxn0G6.mjs";
2
- export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, resolveStartupMode, shouldFail };
1
+ import { a as createOpenApiLintRuntime, c as ResolvedRulesetCandidate, d as RulesetResolverContext, f as RulesetResolverInput, g as lintOpenApi, h as loadRuleset, i as OpenApiLintArtifactWriteError, l as RulesetLoadError, m as loadResolvedRuleset, n as enforceThreshold, o as LoadResolvedRulesetOptions, p as defaultRulesetResolvers, r as shouldFail, s as LoadedRuleset, t as OpenApiLintThresholdError, u as RulesetResolver } from "../index-11HnbLDN.mjs";
2
+ export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, shouldFail };
@@ -1,2 +1,2 @@
1
- import { a as enforceThreshold, d as RulesetLoadError, f as defaultRulesetResolvers, g as lintOpenApi, i as OpenApiLintThresholdError, m as loadRuleset, n as createOpenApiLintRuntime, o as shouldFail, p as loadResolvedRuleset, r as resolveStartupMode, t as OpenApiLintArtifactWriteError } from "../core-DTKNy6TU.mjs";
2
- export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, resolveStartupMode, shouldFail };
1
+ import { a as enforceThreshold, d as RulesetLoadError, f as defaultRulesetResolvers, g as lintOpenApi, i as OpenApiLintThresholdError, m as loadRuleset, n as createOpenApiLintRuntime, o as shouldFail, p as loadResolvedRuleset, t as OpenApiLintArtifactWriteError } from "../core-DTKNy6TU.mjs";
2
+ export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, shouldFail };
@@ -157,7 +157,6 @@ declare class OpenApiLintArtifactWriteError extends Error {
157
157
  readonly cause: unknown;
158
158
  constructor(artifact: string, cause: unknown);
159
159
  }
160
- declare const resolveStartupMode: (options?: SpectralPluginOptions) => StartupLintMode;
161
160
  //#endregion
162
161
  //#region src/core/thresholds.d.ts
163
162
  declare class OpenApiLintThresholdError extends Error {
@@ -168,4 +167,4 @@ declare class OpenApiLintThresholdError extends Error {
168
167
  declare const shouldFail: (result: LintRunResult, threshold: SeverityThreshold) => boolean;
169
168
  declare const enforceThreshold: (result: LintRunResult, threshold: SeverityThreshold) => void;
170
169
  //#endregion
171
- export { SeverityThreshold as A, OpenApiLintArtifacts as C, OpenApiLintSink as D, OpenApiLintRuntimeStatus as E, SpectralPluginOptions as M, StartupLintMode as N, OpenApiLintSinkContext as O, LintSeverity as S, OpenApiLintRuntimeFailure as T, lintOpenApi as _, createOpenApiLintRuntime as a, LintRunResult as b, LoadedRuleset as c, RulesetResolver as d, RulesetResolverContext as f, loadRuleset as g, loadResolvedRuleset as h, OpenApiLintArtifactWriteError as i, SpectralLogger as j, PresetName as k, ResolvedRulesetCandidate as l, defaultRulesetResolvers as m, enforceThreshold as n, resolveStartupMode as o, RulesetResolverInput as p, shouldFail as r, LoadResolvedRulesetOptions as s, OpenApiLintThresholdError as t, RulesetLoadError as u, ArtifactWriteFailureMode as v, OpenApiLintRuntime as w, LintRunSource as x, LintFinding as y };
170
+ export { SpectralLogger as A, OpenApiLintRuntime as C, OpenApiLintSinkContext as D, OpenApiLintSink as E, StartupLintMode as M, PresetName as O, OpenApiLintArtifacts as S, OpenApiLintRuntimeStatus as T, ArtifactWriteFailureMode as _, createOpenApiLintRuntime as a, LintRunSource as b, ResolvedRulesetCandidate as c, RulesetResolverContext as d, RulesetResolverInput as f, lintOpenApi as g, loadRuleset as h, OpenApiLintArtifactWriteError as i, SpectralPluginOptions as j, SeverityThreshold as k, RulesetLoadError as l, loadResolvedRuleset as m, enforceThreshold as n, LoadResolvedRulesetOptions as o, defaultRulesetResolvers as p, shouldFail as r, LoadedRuleset as s, OpenApiLintThresholdError as t, RulesetResolver as u, LintFinding as v, OpenApiLintRuntimeFailure as w, LintSeverity as x, LintRunResult as y };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as SeverityThreshold, C as OpenApiLintArtifacts, D as OpenApiLintSink, E as OpenApiLintRuntimeStatus, M as SpectralPluginOptions, N as StartupLintMode, O as OpenApiLintSinkContext, S as LintSeverity, T as OpenApiLintRuntimeFailure, _ as lintOpenApi, a as createOpenApiLintRuntime, b as LintRunResult, c as LoadedRuleset, d as RulesetResolver, f as RulesetResolverContext, g as loadRuleset, h as loadResolvedRuleset, i as OpenApiLintArtifactWriteError, j as SpectralLogger, k as PresetName, l as ResolvedRulesetCandidate, m as defaultRulesetResolvers, n as enforceThreshold, o as resolveStartupMode, p as RulesetResolverInput, r as shouldFail, s as LoadResolvedRulesetOptions, t as OpenApiLintThresholdError, u as RulesetLoadError, v as ArtifactWriteFailureMode, w as OpenApiLintRuntime, x as LintRunSource, y as LintFinding } from "./index-B4fxn0G6.mjs";
1
+ import { A as SpectralLogger, C as OpenApiLintRuntime, D as OpenApiLintSinkContext, E as OpenApiLintSink, M as StartupLintMode, O as PresetName, S as OpenApiLintArtifacts, T as OpenApiLintRuntimeStatus, _ as ArtifactWriteFailureMode, a as createOpenApiLintRuntime, b as LintRunSource, c as ResolvedRulesetCandidate, d as RulesetResolverContext, f as RulesetResolverInput, g as lintOpenApi, h as loadRuleset, i as OpenApiLintArtifactWriteError, j as SpectralPluginOptions, k as SeverityThreshold, l as RulesetLoadError, m as loadResolvedRuleset, n as enforceThreshold, o as LoadResolvedRulesetOptions, p as defaultRulesetResolvers, r as shouldFail, s as LoadedRuleset, t as OpenApiLintThresholdError, u as RulesetResolver, v as LintFinding, w as OpenApiLintRuntimeFailure, x as LintSeverity, y as LintRunResult } from "./index-11HnbLDN.mjs";
2
2
  import { RulesetDefinition } from "@stoplight/spectral-core";
3
3
  import { Elysia } from "elysia";
4
4
 
@@ -72,4 +72,4 @@ declare const strict: RulesetDefinition;
72
72
  //#region src/presets/index.d.ts
73
73
  declare const presets: Record<PresetName, RulesetDefinition>;
74
74
  //#endregion
75
- export { ArtifactWriteFailureMode, LintFinding, LintRunResult, LintRunSource, LintSeverity, LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintArtifacts, OpenApiLintRuntime, OpenApiLintRuntimeFailure, OpenApiLintRuntimeStatus, OpenApiLintSink, OpenApiLintSinkContext, OpenApiLintThresholdError, PresetName, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, SeverityThreshold, SpectralLogger, SpectralPluginOptions, StartupLintMode, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, presets, recommended, resolveStartupMode, server, shouldFail, spectralPlugin, strict };
75
+ export { ArtifactWriteFailureMode, LintFinding, LintRunResult, LintRunSource, LintSeverity, LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintArtifacts, OpenApiLintRuntime, OpenApiLintRuntimeFailure, OpenApiLintRuntimeStatus, OpenApiLintSink, OpenApiLintSinkContext, OpenApiLintThresholdError, PresetName, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, SeverityThreshold, SpectralLogger, SpectralPluginOptions, StartupLintMode, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, presets, recommended, server, shouldFail, spectralPlugin, strict };
package/dist/index.mjs CHANGED
@@ -82,4 +82,4 @@ const spectralPlugin = (options = {}) => {
82
82
  return plugin;
83
83
  };
84
84
  //#endregion
85
- export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, presets, recommended, resolveStartupMode, server, shouldFail, spectralPlugin, strict };
85
+ export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, presets, recommended, server, shouldFail, spectralPlugin, strict };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsydyn/elysia-spectral",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Thin Elysia plugin that lints generated OpenAPI documents with Spectral.",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "publishConfig": {