@lovrozagar/honey 0.7.1 → 0.7.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/src/codegen.ts CHANGED
@@ -851,12 +851,21 @@ export function deduplicateSchemas(spec: OpenApiSpec): OpenApiSpec {
851
851
  const mergedSchemas: Record<string, Record<string, unknown>> = { ...existing, ...extractedSchemas }
852
852
 
853
853
  /* a hoisted nested schema whose every user became a component $ref is still referenced from
854
- that component; one referenced from nowhere is dropped */
854
+ that component; one referenced from nowhere is dropped. A kept hoisted schema can itself
855
+ reference hoisted schemas (an object nested in a shared object), so keep closing over the
856
+ references until no new one appears. */
855
857
  const referenced = new Set<string>()
856
858
  collectComponentRefs(paths, referenced)
857
859
  collectComponentRefs(mergedSchemas, referenced)
858
- for (const [name, schema] of Object.entries(hoistedSchemas)) {
859
- if (referenced.has(name)) mergedSchemas[name] = schema
860
+ let added = true
861
+ while (added) {
862
+ added = false
863
+ for (const [name, schema] of Object.entries(hoistedSchemas)) {
864
+ if (Object.hasOwn(mergedSchemas, name) || !referenced.has(name)) continue
865
+ mergedSchemas[name] = schema
866
+ collectComponentRefs(schema, referenced)
867
+ added = true
868
+ }
860
869
  }
861
870
 
862
871
  const components: OpenApiSpec["components"] = {
package/src/index.ts CHANGED
@@ -72,7 +72,7 @@ import { getI18nRuntime } from "./i18n-slot.ts"
72
72
  import { bindInternalHandler, epochCached, getOpenApiRuntime } from "./openapi/spec-factory.ts"
73
73
  import type { ServedArtifact } from "./openapi/spec-factory.ts"
74
74
  import type { MetaSpecSource } from "./meta-spec-merge.ts"
75
- import { getServeRuntime } from "./serve-slot.ts"
75
+ import { getServeRuntime, isCodegenProcess } from "./serve-slot.ts"
76
76
  import type { HoneyServeOptions, ServeHandle } from "./serve.ts"
77
77
 
78
78
  export { clientInfo, HoneyContext } from "./context.ts"
@@ -370,6 +370,8 @@ type HoneyGraph = {
370
370
  settings: AppSettings<unknown>
371
371
  /** registered after routeTree() with no leaf in the loaded tree — stale generated file */
372
372
  unexpected: Set<RouteId>
373
+ /** the loaded tree came in while `honey generate` loaded the app: a stale one is replaced, not fatal */
374
+ unloadIfStale: boolean
373
375
  wsRecords: Map<RouteId, WSRouteHandler>
374
376
  }
375
377
 
@@ -398,6 +400,7 @@ function createGraph(): HoneyGraph {
398
400
  served: new Set(),
399
401
  settings: createSettings(),
400
402
  unexpected: new Set(),
403
+ unloadIfStale: false,
401
404
  wsRecords: new Map(),
402
405
  }
403
406
  }
@@ -1292,6 +1295,11 @@ export class Honey<
1292
1295
  }
1293
1296
  }
1294
1297
  if (problems.length > 0) {
1298
+ /* generation rewrites this file from the registered routes; serving it stale is the error */
1299
+ if (g.unloadIfStale) {
1300
+ this._unloadTree()
1301
+ return this._finalize()
1302
+ }
1295
1303
  throw new Error(`Route tree out of date — regenerate it (\`honey generate\`):\n ${problems.join("\n ")}`)
1296
1304
  }
1297
1305
 
@@ -1640,6 +1648,16 @@ export class Honey<
1640
1648
  * registers as its gateway catch-all. Must be called before any route is registered.
1641
1649
  */
1642
1650
  routeTree(tree: RouteTree): this {
1651
+ /* `honey generate` loads the app that imports the very file it is about to rewrite: a tree
1652
+ from an older honey is ignored there, and the routes the app registers are used instead */
1653
+ const generating = isCodegenProcess()
1654
+ if (generating) {
1655
+ try {
1656
+ assertTreeFormat(tree)
1657
+ } catch {
1658
+ return this
1659
+ }
1660
+ }
1643
1661
  assertTreeFormat(tree)
1644
1662
  const g = this._graph
1645
1663
  if (g.loaded !== null) throw new Error("routeTree() was already called on this app")
@@ -1657,6 +1675,7 @@ export class Honey<
1657
1675
  g.root = freezeTree(tree.root)
1658
1676
  g.rootShared = true
1659
1677
  g.loaded = new Map()
1678
+ g.unloadIfStale = generating
1660
1679
  for (const id of Object.keys(tree.routes)) g.loaded.set(id, { ...tree.routes[id] })
1661
1680
  if (hasWsLeaf(g.root)) g.hasWs = true
1662
1681
  /* internal routes mounted before the tree was loaded move onto the loaded topology */
@@ -1798,6 +1817,29 @@ export class Honey<
1798
1817
  return tree
1799
1818
  }
1800
1819
 
1820
+ /** @internal — drop the loaded tree and route by the registered routes alone. */
1821
+ _unloadTree(): void {
1822
+ const g = this._graph
1823
+ const http = [...g.records.values(), ...g.catchAll.values()]
1824
+ const ws = [...g.wsRecords.values()]
1825
+ g.records.clear()
1826
+ g.catchAll.clear()
1827
+ g.wsRecords.clear()
1828
+ g.unexpected.clear()
1829
+ g.loaded = null
1830
+ g.unloadIfStale = false
1831
+ g.root = createNode()
1832
+ g.rootShared = false
1833
+ g.hasWs = false
1834
+ for (const r of http) {
1835
+ delete r.ca
1836
+ const { method, segments } = patternOf(r.id as RouteId)
1837
+ this._addRoute(method, segments, r)
1838
+ }
1839
+ for (const r of ws) this._addWsRoute(patternOf(r.id as RouteId).segments, r)
1840
+ this._bumpEpoch()
1841
+ }
1842
+
1801
1843
  /** @internal — swap the loaded tree, re-binding every registered route to it. */
1802
1844
  _replaceLoadedTree(tree: RouteTree): void {
1803
1845
  const g = this._graph
package/src/plugin.ts CHANGED
@@ -533,9 +533,24 @@ export async function generateAndWrite(
533
533
  const unfilteredSpec = (what: string): Promise<OpenApiSpecInput> => {
534
534
  if (!app) throw new Error(`${what} generation requires a configured app`)
535
535
  const primaryOA = cg.openApi && cg.openApi.length > 0 ? cg.openApi[0] : null
536
+ /* routes name schemes the openApi outputs declare; checked against all of them, so a declared
537
+ scheme is not reported as missing. The document the SDK reads stays without them, as it
538
+ always was, so generated SDK auth does not change. */
539
+ const schemes: Record<string, unknown> = {}
540
+ for (const entry of cg.openApi || []) {
541
+ for (const [name, scheme] of Object.entries(entry.securitySchemes ?? {})) {
542
+ if (!Object.hasOwn(schemes, name)) schemes[name] = scheme
543
+ }
544
+ }
545
+ const declared = Object.keys(schemes).length > 0
536
546
  unfiltered ??= generateOpenApi(app, {
537
547
  info: primaryOA ? { title: primaryOA.title, version: primaryOA.version } : { title: "API", version: "1.0.0" },
538
548
  invalidate: primaryOA ? "off" : cg.invalidate,
549
+ ...(declared ? { securitySchemes: schemes } : {}),
550
+ }).then((spec) => {
551
+ if (!declared || !spec.components?.securitySchemes) return spec
552
+ const { securitySchemes: _schemes, ...components } = spec.components
553
+ return { ...spec, components }
539
554
  })
540
555
  return unfiltered
541
556
  }