polen 0.11.0-next.16 → 0.11.0-next.18

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 (251) hide show
  1. package/build/api/builder/ssg/generate.d.ts.map +1 -1
  2. package/build/api/builder/ssg/generate.js +5 -5
  3. package/build/api/builder/ssg/generate.js.map +1 -1
  4. package/build/api/builder/ssg/page-generator.worker.js +13 -3
  5. package/build/api/builder/ssg/page-generator.worker.js.map +1 -1
  6. package/build/api/config/input.d.ts +88 -3
  7. package/build/api/config/input.d.ts.map +1 -1
  8. package/build/api/config/normalized.d.ts +92 -7
  9. package/build/api/config/normalized.d.ts.map +1 -1
  10. package/build/api/config/normalized.js +11 -3
  11. package/build/api/config/normalized.js.map +1 -1
  12. package/build/api/config-template/template.js +2 -2
  13. package/build/api/config-template/template.js.map +1 -1
  14. package/build/api/content/sidebar.d.ts.map +1 -1
  15. package/build/api/content/sidebar.js +2 -1
  16. package/build/api/content/sidebar.js.map +1 -1
  17. package/build/api/examples/config.d.ts +366 -3
  18. package/build/api/examples/config.d.ts.map +1 -1
  19. package/build/api/examples/config.js +25 -3
  20. package/build/api/examples/config.js.map +1 -1
  21. package/build/api/examples/diagnostic/diagnostic.d.ts +5 -5
  22. package/build/api/examples/diagnostic/missing-versions.d.ts +5 -4
  23. package/build/api/examples/diagnostic/missing-versions.d.ts.map +1 -1
  24. package/build/api/examples/diagnostic/missing-versions.js +3 -2
  25. package/build/api/examples/diagnostic/missing-versions.js.map +1 -1
  26. package/build/api/examples/diagnostic/unknown-version.d.ts +5 -4
  27. package/build/api/examples/diagnostic/unknown-version.d.ts.map +1 -1
  28. package/build/api/examples/diagnostic/unknown-version.js +3 -2
  29. package/build/api/examples/diagnostic/unknown-version.js.map +1 -1
  30. package/build/api/examples/diagnostic/validation-error.d.ts +3 -2
  31. package/build/api/examples/diagnostic/validation-error.d.ts.map +1 -1
  32. package/build/api/examples/diagnostic/validation-error.js +9 -3
  33. package/build/api/examples/diagnostic/validation-error.js.map +1 -1
  34. package/build/api/examples/diagnostic/validator.d.ts.map +1 -1
  35. package/build/api/examples/diagnostic/validator.js +115 -69
  36. package/build/api/examples/diagnostic/validator.js.map +1 -1
  37. package/build/api/examples/filter.d.ts.map +1 -1
  38. package/build/api/examples/filter.js +9 -6
  39. package/build/api/examples/filter.js.map +1 -1
  40. package/build/api/examples/scanner.d.ts.map +1 -1
  41. package/build/api/examples/scanner.js +93 -103
  42. package/build/api/examples/scanner.js.map +1 -1
  43. package/build/api/examples/type-usage-indexer.d.ts.map +1 -1
  44. package/build/api/examples/type-usage-indexer.js +18 -32
  45. package/build/api/examples/type-usage-indexer.js.map +1 -1
  46. package/build/api/iso/schema/routing.d.ts.map +1 -1
  47. package/build/api/iso/schema/routing.js +8 -8
  48. package/build/api/iso/schema/routing.js.map +1 -1
  49. package/build/api/iso/schema/validation.d.ts.map +1 -1
  50. package/build/api/iso/schema/validation.js +3 -2
  51. package/build/api/iso/schema/validation.js.map +1 -1
  52. package/build/api/schema/input-sources/directory.js +2 -2
  53. package/build/api/schema/input-sources/directory.js.map +1 -1
  54. package/build/api/schema/input-sources/versioned-directory.d.ts +3 -3
  55. package/build/api/schema/input-sources/versioned-directory.d.ts.map +1 -1
  56. package/build/api/schema/input-sources/versioned-directory.js +6 -4
  57. package/build/api/schema/input-sources/versioned-directory.js.map +1 -1
  58. package/build/api/schema/load.d.ts.map +1 -1
  59. package/build/api/schema/load.js +2 -2
  60. package/build/api/schema/load.js.map +1 -1
  61. package/build/cli/commands/hero-image.js +1 -1
  62. package/build/cli/commands/hero-image.js.map +1 -1
  63. package/build/lib/catalog/catalog.d.ts +65 -24
  64. package/build/lib/catalog/catalog.d.ts.map +1 -1
  65. package/build/lib/catalog/catalog.js +72 -16
  66. package/build/lib/catalog/catalog.js.map +1 -1
  67. package/build/lib/catalog/versioned.d.ts +46 -26
  68. package/build/lib/catalog/versioned.d.ts.map +1 -1
  69. package/build/lib/catalog/versioned.js +44 -7
  70. package/build/lib/catalog/versioned.js.map +1 -1
  71. package/build/lib/catalog-statistics/analyze-catalog.js +3 -3
  72. package/build/lib/catalog-statistics/analyze-catalog.js.map +1 -1
  73. package/build/lib/document/document.d.ts +55 -5
  74. package/build/lib/document/document.d.ts.map +1 -1
  75. package/build/lib/document/document.js +96 -2
  76. package/build/lib/document/document.js.map +1 -1
  77. package/build/lib/document/versioned.d.ts +2 -2
  78. package/build/lib/document/versioned.d.ts.map +1 -1
  79. package/build/lib/document/versioned.js +7 -7
  80. package/build/lib/document/versioned.js.map +1 -1
  81. package/build/lib/lifecycles/lifecycles.d.ts +5 -4
  82. package/build/lib/lifecycles/lifecycles.d.ts.map +1 -1
  83. package/build/lib/lifecycles/lifecycles.js +15 -13
  84. package/build/lib/lifecycles/lifecycles.js.map +1 -1
  85. package/build/lib/version-coverage/$$.d.ts +2 -0
  86. package/build/lib/version-coverage/$$.d.ts.map +1 -0
  87. package/build/lib/version-coverage/$$.js +2 -0
  88. package/build/lib/version-coverage/$$.js.map +1 -0
  89. package/build/lib/version-coverage/$.d.ts.map +1 -0
  90. package/build/lib/version-coverage/$.js.map +1 -0
  91. package/build/lib/{version-selection/version-selection.d.ts → version-coverage/version-coverage.d.ts} +1 -1
  92. package/build/lib/version-coverage/version-coverage.d.ts.map +1 -0
  93. package/build/lib/{version-selection/version-selection.js → version-coverage/version-coverage.js} +2 -2
  94. package/build/lib/version-coverage/version-coverage.js.map +1 -0
  95. package/build/template/components/Changelog/Changelog.d.ts.map +1 -1
  96. package/build/template/components/Changelog/Changelog.js +7 -7
  97. package/build/template/components/Changelog/Changelog.js.map +1 -1
  98. package/build/template/components/GraphQLDocument.d.ts +1 -1
  99. package/build/template/components/GraphQLDocument.d.ts.map +1 -1
  100. package/build/template/components/GraphQLDocument.js +10 -38
  101. package/build/template/components/GraphQLDocument.js.map +1 -1
  102. package/build/template/components/GraphQLInteractive/lib/parser.d.ts +28 -0
  103. package/build/template/components/GraphQLInteractive/lib/parser.d.ts.map +1 -1
  104. package/build/template/components/GraphQLInteractive/lib/parser.js +60 -27
  105. package/build/template/components/GraphQLInteractive/lib/parser.js.map +1 -1
  106. package/build/template/components/VersionCoveragePicker.d.ts +1 -1
  107. package/build/template/components/VersionCoveragePicker.d.ts.map +1 -1
  108. package/build/template/components/VersionCoveragePicker.js +4 -6
  109. package/build/template/components/VersionCoveragePicker.js.map +1 -1
  110. package/build/template/components/VersionPicker.d.ts.map +1 -1
  111. package/build/template/components/VersionPicker.js +5 -2
  112. package/build/template/components/VersionPicker.js.map +1 -1
  113. package/build/template/components/home/FeaturesGrid.js +1 -1
  114. package/build/template/components/home/FeaturesGrid.js.map +1 -1
  115. package/build/template/components/home/HeroSection.js +1 -1
  116. package/build/template/components/home/HeroSection.js.map +1 -1
  117. package/build/template/components/home/QuickStart.d.ts.map +1 -1
  118. package/build/template/components/home/QuickStart.js +8 -4
  119. package/build/template/components/home/QuickStart.js.map +1 -1
  120. package/build/template/components/home/RecentChanges.d.ts.map +1 -1
  121. package/build/template/components/home/RecentChanges.js +2 -1
  122. package/build/template/components/home/RecentChanges.js.map +1 -1
  123. package/build/template/hooks/use-highlighted.d.ts.map +1 -1
  124. package/build/template/hooks/use-highlighted.js +19 -13
  125. package/build/template/hooks/use-highlighted.js.map +1 -1
  126. package/build/template/lib/fetch-text.d.ts +18 -0
  127. package/build/template/lib/fetch-text.d.ts.map +1 -1
  128. package/build/template/lib/fetch-text.js +32 -4
  129. package/build/template/lib/fetch-text.js.map +1 -1
  130. package/build/template/routes/changelog.d.ts +1 -1
  131. package/build/template/routes/changelog.d.ts.map +1 -1
  132. package/build/template/routes/changelog.js +7 -4
  133. package/build/template/routes/changelog.js.map +1 -1
  134. package/build/template/routes/examples/_index.js +1 -1
  135. package/build/template/routes/examples/_index.js.map +1 -1
  136. package/build/template/routes/examples/name.d.ts.map +1 -1
  137. package/build/template/routes/examples/name.js +4 -2
  138. package/build/template/routes/examples/name.js.map +1 -1
  139. package/build/template/routes/reference.js +6 -6
  140. package/build/template/routes/reference.js.map +1 -1
  141. package/build/template/stores/toast.d.ts.map +1 -1
  142. package/build/template/stores/toast.js +5 -3
  143. package/build/template/stores/toast.js.map +1 -1
  144. package/build/vite/plugins/navbar.js +1 -1
  145. package/build/vite/plugins/navbar.js.map +1 -1
  146. package/build/vite/plugins/routes-manifest.js +1 -1
  147. package/build/vite/plugins/routes-manifest.js.map +1 -1
  148. package/package.json +7 -7
  149. package/src/api/builder/ssg/generate.ts +10 -5
  150. package/src/api/builder/ssg/page-generator.worker.ts +18 -3
  151. package/src/api/config/normalized.ts +12 -3
  152. package/src/api/config-template/template.ts +2 -2
  153. package/src/api/content/sidebar.ts +3 -3
  154. package/src/api/examples/config.test.ts +10 -0
  155. package/src/api/examples/config.ts +33 -4
  156. package/src/api/examples/diagnostic/missing-versions.ts +3 -2
  157. package/src/api/examples/diagnostic/unknown-version.ts +3 -2
  158. package/src/api/examples/diagnostic/validation-error.ts +9 -3
  159. package/src/api/examples/diagnostic/validator.test.ts +100 -55
  160. package/src/api/examples/diagnostic/validator.ts +148 -105
  161. package/src/api/examples/filter.ts +9 -6
  162. package/src/api/examples/scanner.ts +144 -120
  163. package/src/api/examples/type-usage-indexer.test.ts +44 -33
  164. package/src/api/examples/type-usage-indexer.ts +25 -40
  165. package/src/api/iso/schema/routing.ts +10 -10
  166. package/src/api/iso/schema/validation.ts +3 -2
  167. package/src/api/schema/$.test.ts +2 -2
  168. package/src/api/schema/input-sources/directory.ts +2 -2
  169. package/src/api/schema/input-sources/versioned-directory.ts +11 -8
  170. package/src/api/schema/load.ts +2 -2
  171. package/src/cli/commands/hero-image.ts +1 -1
  172. package/src/lib/catalog/catalog.ts +93 -16
  173. package/src/lib/catalog/versioned.ts +57 -7
  174. package/src/lib/catalog-statistics/$.test.ts +22 -12
  175. package/src/lib/catalog-statistics/analyze-catalog.ts +3 -3
  176. package/src/lib/document/document.ts +135 -2
  177. package/src/lib/document/versioned.ts +8 -8
  178. package/src/lib/lifecycles/lifecycles.ts +33 -28
  179. package/src/lib/version-coverage/$$.ts +1 -0
  180. package/src/lib/{version-selection/version-selection.ts → version-coverage/version-coverage.ts} +1 -1
  181. package/src/template/components/Changelog/Changelog.tsx +10 -6
  182. package/src/template/components/GraphQLDocument.tsx +11 -68
  183. package/src/template/components/GraphQLInteractive/lib/parser.ts +81 -29
  184. package/src/template/components/VersionCoveragePicker.tsx +4 -5
  185. package/src/template/components/VersionPicker.tsx +9 -2
  186. package/src/template/components/home/FeaturesGrid.tsx +1 -1
  187. package/src/template/components/home/HeroSection.tsx +1 -1
  188. package/src/template/components/home/QuickStart.tsx +16 -7
  189. package/src/template/components/home/RecentChanges.tsx +3 -1
  190. package/src/template/hooks/use-highlighted.ts +31 -19
  191. package/src/template/lib/fetch-text.ts +45 -4
  192. package/src/template/routes/changelog.tsx +10 -4
  193. package/src/template/routes/examples/_index.tsx +1 -1
  194. package/src/template/routes/examples/name.tsx +4 -2
  195. package/src/template/routes/reference.tsx +6 -6
  196. package/src/template/stores/toast.ts +6 -3
  197. package/src/vite/plugins/navbar.ts +1 -1
  198. package/src/vite/plugins/routes-manifest.ts +1 -1
  199. package/build/lib/graph/$$.d.ts +0 -2
  200. package/build/lib/graph/$$.d.ts.map +0 -1
  201. package/build/lib/graph/$$.js +0 -2
  202. package/build/lib/graph/$$.js.map +0 -1
  203. package/build/lib/graph/$.d.ts +0 -2
  204. package/build/lib/graph/$.d.ts.map +0 -1
  205. package/build/lib/graph/$.js +0 -2
  206. package/build/lib/graph/$.js.map +0 -1
  207. package/build/lib/graph/graph.d.ts +0 -127
  208. package/build/lib/graph/graph.d.ts.map +0 -1
  209. package/build/lib/graph/graph.js +0 -152
  210. package/build/lib/graph/graph.js.map +0 -1
  211. package/build/lib/mask/$$.d.ts +0 -3
  212. package/build/lib/mask/$$.d.ts.map +0 -1
  213. package/build/lib/mask/$$.js +0 -3
  214. package/build/lib/mask/$$.js.map +0 -1
  215. package/build/lib/mask/$.d.ts +0 -2
  216. package/build/lib/mask/$.d.ts.map +0 -1
  217. package/build/lib/mask/$.js +0 -2
  218. package/build/lib/mask/$.js.map +0 -1
  219. package/build/lib/mask/apply.d.ts +0 -86
  220. package/build/lib/mask/apply.d.ts.map +0 -1
  221. package/build/lib/mask/apply.js +0 -86
  222. package/build/lib/mask/apply.js.map +0 -1
  223. package/build/lib/mask/mask.d.ts +0 -124
  224. package/build/lib/mask/mask.d.ts.map +0 -1
  225. package/build/lib/mask/mask.js +0 -137
  226. package/build/lib/mask/mask.js.map +0 -1
  227. package/build/lib/mask/mask.test-d.d.ts +0 -2
  228. package/build/lib/mask/mask.test-d.d.ts.map +0 -1
  229. package/build/lib/mask/mask.test-d.js +0 -102
  230. package/build/lib/mask/mask.test-d.js.map +0 -1
  231. package/build/lib/version-selection/$$.d.ts +0 -2
  232. package/build/lib/version-selection/$$.d.ts.map +0 -1
  233. package/build/lib/version-selection/$$.js +0 -2
  234. package/build/lib/version-selection/$$.js.map +0 -1
  235. package/build/lib/version-selection/$.d.ts.map +0 -1
  236. package/build/lib/version-selection/$.js.map +0 -1
  237. package/build/lib/version-selection/version-selection.d.ts.map +0 -1
  238. package/build/lib/version-selection/version-selection.js.map +0 -1
  239. package/src/lib/graph/$$.ts +0 -1
  240. package/src/lib/graph/$.ts +0 -1
  241. package/src/lib/graph/graph.ts +0 -197
  242. package/src/lib/mask/$$.ts +0 -2
  243. package/src/lib/mask/$.test.ts +0 -226
  244. package/src/lib/mask/$.ts +0 -1
  245. package/src/lib/mask/apply.ts +0 -134
  246. package/src/lib/mask/mask.test-d.ts +0 -156
  247. package/src/lib/mask/mask.ts +0 -244
  248. package/src/lib/version-selection/$$.ts +0 -1
  249. /package/build/lib/{version-selection → version-coverage}/$.d.ts +0 -0
  250. /package/build/lib/{version-selection → version-coverage}/$.js +0 -0
  251. /package/src/lib/{version-selection → version-coverage}/$.ts +0 -0
@@ -3,7 +3,7 @@ import { Catalog } from '#lib/catalog/$'
3
3
  import { Grafaid } from '#lib/grafaid'
4
4
  import { MemoryFilesystem } from '#lib/memory-filesystem/$'
5
5
  import * as NodeFileSystem from '@effect/platform-node/NodeFileSystem'
6
- import { Effect } from 'effect'
6
+ import { Effect, HashMap } from 'effect'
7
7
  import { expect } from 'vitest'
8
8
  import { Test } from '../../../tests/unit/helpers/test.js'
9
9
  import { Schema } from './$.js'
@@ -421,7 +421,7 @@ testWithFileSystem<BaseTestCase & {
421
421
  expect(result!._tag).toBe('CatalogVersioned')
422
422
  if (expected.versionCount !== undefined) {
423
423
  const versioned = result as Catalog.Versioned.Versioned
424
- expect(versioned.entries.length).toBe(expected.versionCount)
424
+ expect(HashMap.size(versioned.entries)).toBe(expected.versionCount)
425
425
  }
426
426
  } else {
427
427
  expect(result!._tag).toBe('CatalogUnversioned')
@@ -98,8 +98,8 @@ export const loader = InputSource.createEffect({
98
98
  // Check if we have either:
99
99
  // 1. A single schema.graphql file (non-versioned mode)
100
100
  // 2. Any .graphql files with valid date names (versioned mode)
101
- const hasSchemaFile = files.some(file => file === 'schema.graphql')
102
- const hasVersionedFiles = files.some(file => {
101
+ const hasSchemaFile = Array.some(files, file => file === 'schema.graphql')
102
+ const hasVersionedFiles = Array.some(files, file => {
103
103
  if (!file.endsWith('.graphql')) return false
104
104
  const name = Path.basename(file, '.graphql')
105
105
  return /^\d{4}-\d{2}-\d{2}$/.test(name)
@@ -10,7 +10,7 @@ import { debugPolen } from '#singletons/debug'
10
10
  import { PlatformError } from '@effect/platform/Error'
11
11
  import { FileSystem } from '@effect/platform/FileSystem'
12
12
  import { Arr, Path } from '@wollybeard/kit'
13
- import { Effect } from 'effect'
13
+ import { Array, Effect, HashMap } from 'effect'
14
14
  import type { GraphQLSchema } from 'graphql'
15
15
 
16
16
  const debug = debugPolen.sub(`schema:data-source-versioned-schema-directory`)
@@ -393,9 +393,14 @@ export const readOrThrow = (
393
393
  // Reverse to have newest first
394
394
  catalogEntries.reverse()
395
395
 
396
+ // Convert array to HashMap with version as key
397
+ const entriesMap = HashMap.fromIterable(
398
+ catalogEntries.map(entry => [entry.version, entry] as const),
399
+ )
400
+
396
401
  debug(`computed ${catalogEntries.length} entries`)
397
402
  return Catalog.Versioned.make({
398
- entries: catalogEntries,
403
+ entries: entriesMap,
399
404
  })
400
405
  })
401
406
 
@@ -447,9 +452,8 @@ export const loader = InputSource.createEffect({
447
452
  // Check for revision files or schema.graphql
448
453
  const dirFiles = yield* Effect.either(fs.readDirectory(versionPath))
449
454
  if (dirFiles._tag === 'Right') {
450
- const hasRevisions = dirFiles.right.some(file =>
451
- /^\d{4}-\d{2}-\d{2}\.graphql$/.test(file) || file === 'schema.graphql'
452
- )
455
+ const hasRevisions = Array.some(dirFiles.right, file =>
456
+ /^\d{4}-\d{2}-\d{2}\.graphql$/.test(file) || file === 'schema.graphql')
453
457
  if (hasRevisions) {
454
458
  return true
455
459
  }
@@ -515,9 +519,8 @@ export const loader = InputSource.createEffect({
515
519
  // Check for schema files
516
520
  const dirFiles = yield* Effect.either(fs.readDirectory(versionPath))
517
521
  if (dirFiles._tag === 'Right') {
518
- const hasSchemaFiles = dirFiles.right.some(file =>
519
- /^\d{4}-\d{2}-\d{2}\.graphql$/.test(file) || file === 'schema.graphql'
520
- )
522
+ const hasSchemaFiles = Array.some(dirFiles.right, file =>
523
+ /^\d{4}-\d{2}-\d{2}\.graphql$/.test(file) || file === 'schema.graphql')
521
524
 
522
525
  if (hasSchemaFiles) {
523
526
  debug('found valid schema files, proceeding with full read')
@@ -7,7 +7,7 @@ import { Catalog } from '#lib/catalog/$'
7
7
  import type { PlatformError } from '@effect/platform/Error'
8
8
  import type { FileSystem } from '@effect/platform/FileSystem'
9
9
  import { Arr } from '@wollybeard/kit'
10
- import { Effect } from 'effect'
10
+ import { Array, Effect, Option } from 'effect'
11
11
 
12
12
  // For now, we'll need a type that accepts both promise and effect sources
13
13
  type AnyInputSource = InputSource | EffectInputSource
@@ -142,7 +142,7 @@ export const loadOrNull = (
142
142
  const catalog = loadedSchema.data as Catalog.Catalog
143
143
  Catalog.fold(
144
144
  (versioned) => {
145
- for (const schema of versioned.entries) {
145
+ for (const schema of Catalog.Versioned.getAll(versioned)) {
146
146
  Augmentations.apply(schema.definition, augmentations)
147
147
  }
148
148
  },
@@ -133,7 +133,7 @@ export const heroImage = Command.make(
133
133
  const schemaResult = yield* Api.Schema.loadOrNull(config)
134
134
  if (schemaResult?.data) {
135
135
  try {
136
- const latestSchema = Catalog.getLatestSchema(schemaResult.data)
136
+ const latestSchema = Catalog.getLatest(schemaResult.data)
137
137
  if (latestSchema?.definition) {
138
138
  schema = latestSchema.definition
139
139
  const context = AiImageGeneration.analyzeSchema(schema)
@@ -1,13 +1,33 @@
1
1
  import { S } from '#lib/kit-temp/effect'
2
2
  import { Schema } from '#lib/schema/$'
3
+ import { VersionCoverage } from '#lib/version-coverage'
3
4
  import { Version } from '#lib/version/$'
4
- import { Match } from 'effect'
5
+ import { Data, Either, HashMap, Match, Option } from 'effect'
5
6
  import * as Unversioned from './unversioned.js'
6
7
  import * as Versioned from './versioned.js'
7
8
 
8
9
  export * as Unversioned from './unversioned.js'
9
10
  export * as Versioned from './versioned.js'
10
11
 
12
+ // ============================================================================
13
+ // Error Types
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Error thrown when a version is not found in the catalog
18
+ */
19
+ export class VersionNotFoundInCatalogError extends Data.TaggedError('VersionNotFoundInCatalogError')<{
20
+ readonly version: string
21
+ readonly reason: string
22
+ }> {}
23
+
24
+ /**
25
+ * Error thrown when a catalog has no entries
26
+ */
27
+ export class EmptyCatalogError extends Data.TaggedError('EmptyCatalogError')<{
28
+ readonly reason: string
29
+ }> {}
30
+
11
31
  // ============================================================================
12
32
  // Schema
13
33
  // ============================================================================
@@ -62,7 +82,7 @@ export const equivalence = S.equivalence(Catalog)
62
82
  */
63
83
  export const getVersionCount = (catalog: Catalog): number =>
64
84
  fold(
65
- (versioned) => versioned.entries.length,
85
+ (versioned) => HashMap.size(versioned.entries),
66
86
  (_unversioned) => 1, // Unversioned catalog is effectively one version
67
87
  )(catalog)
68
88
 
@@ -79,28 +99,85 @@ export const getSchemaVersionString = (schema: Schema.Schema): string => {
79
99
  * Get the version string from a schema.
80
100
  * Returns the stringified version for versioned schemas, or '__UNVERSIONED__' for unversioned schemas.
81
101
  */
82
- export const getLatestSchema = (catalog: Catalog): Schema.Schema =>
102
+ export const getLatest = (catalog: Catalog): Schema.Schema =>
83
103
  Match.value(catalog).pipe(Match.tagsExhaustive({
84
- CatalogVersioned: (versioned) => {
85
- // Entries are sorted newest first, so get the first entry
86
- const latestEntry = versioned.entries[0]!
87
- return latestEntry
88
- },
89
- CatalogUnversioned: (unversioned) => {
90
- return unversioned.schema
91
- },
104
+ CatalogVersioned: Versioned.getLatestOrThrow,
105
+ CatalogUnversioned: (unversioned) => unversioned.schema,
92
106
  }))
93
107
 
94
108
  /**
95
109
  * Get the latest version identifier from a catalog.
96
- * Returns the version for versioned catalogs, or null for unversioned catalogs.
110
+ * Returns the version for versioned catalogs, or none for unversioned catalogs.
97
111
  */
98
- export const getLatestVersionIdentifier = (catalog?: Catalog): Version.Version | null => {
99
- if (!catalog) return null
112
+ export const getLatestVersion = (catalog?: Catalog): Option.Option<Version.Version> => {
113
+ if (!catalog) return Option.none()
100
114
  return Match.value(catalog).pipe(
101
115
  Match.tagsExhaustive({
102
- CatalogUnversioned: () => null,
103
- CatalogVersioned: (cat) => cat.entries[0]?.version ?? null,
116
+ CatalogUnversioned: () => Option.none(),
117
+ CatalogVersioned: (cat) => {
118
+ const versions = Versioned.getVersions(cat)
119
+ return versions[0] ? Option.some(versions[0]) : Option.none()
120
+ },
104
121
  }),
105
122
  )
106
123
  }
124
+
125
+ // ============================================================================
126
+ // Resolution Functions
127
+ // ============================================================================
128
+
129
+ /**
130
+ * Resolve schema from catalog for a given version coverage.
131
+ *
132
+ * @param catalog - The schema catalog
133
+ * @param versionCoverage - The version coverage to use (optional, defaults to latest)
134
+ * @returns Either with the resolved schema or error
135
+ */
136
+ export const resolveCatalogSchemaEither = (
137
+ catalog: Catalog,
138
+ versionCoverage?: VersionCoverage.VersionCoverage | null,
139
+ ): Either.Either<Schema.Schema, VersionNotFoundInCatalogError | EmptyCatalogError> => {
140
+ if (Unversioned.is(catalog)) {
141
+ return Either.right(catalog.schema)
142
+ }
143
+
144
+ // If no version coverage specified, use latest
145
+ if (!versionCoverage) {
146
+ return Versioned.getLatest(catalog)
147
+ }
148
+
149
+ // Get the latest version from the coverage
150
+ const version = VersionCoverage.getLatest(versionCoverage)
151
+
152
+ const schemaOption = HashMap.get(catalog.entries, version)
153
+ if (Option.isNone(schemaOption)) {
154
+ return Either.left(
155
+ new VersionNotFoundInCatalogError({
156
+ version: Version.encodeSync(version),
157
+ reason: `Version ${Version.encodeSync(version)} not found in catalog`,
158
+ }),
159
+ )
160
+ }
161
+
162
+ return Either.right(Option.getOrThrow(schemaOption))
163
+ }
164
+
165
+ /**
166
+ * Resolve schema from catalog for a given version coverage.
167
+ *
168
+ * @param catalog - The schema catalog
169
+ * @param versionCoverage - The version coverage to use (optional, defaults to latest)
170
+ * @returns The resolved schema
171
+ * @throws {Error} If catalog is versioned but version is not found
172
+ * @deprecated Use resolveCatalogSchemaEither which returns Either
173
+ */
174
+ export const resolveCatalogSchema = (
175
+ catalog: Catalog,
176
+ versionCoverage?: VersionCoverage.VersionCoverage | null,
177
+ ): Schema.Schema => {
178
+ const result = resolveCatalogSchemaEither(catalog, versionCoverage)
179
+ if (Either.isLeft(result)) {
180
+ throw new Error(result.left.reason)
181
+ }
182
+ return result.right
183
+ }
@@ -1,12 +1,18 @@
1
1
  import { S } from '#lib/kit-temp/effect'
2
+ import { Array, Either, HashMap, Iterable, Order, pipe } from 'effect'
2
3
  import { Schema } from '../schema/$.js'
4
+ import { Version } from '../version/$.js'
5
+ import { EmptyCatalogError } from './catalog.js'
3
6
 
4
7
  // ============================================================================
5
8
  // Schema
6
9
  // ============================================================================
7
10
 
8
11
  export const Versioned = S.TaggedStruct('CatalogVersioned', {
9
- entries: S.Array(Schema.Versioned.Versioned),
12
+ entries: S.HashMap({
13
+ key: Version.Version,
14
+ value: Schema.Versioned.Versioned,
15
+ }),
10
16
  }).annotations({
11
17
  identifier: 'CatalogVersioned',
12
18
  title: 'Versioned Catalog',
@@ -45,18 +51,62 @@ export const equivalence = S.equivalence(Versioned)
45
51
  // Domain Logic
46
52
  // ============================================================================
47
53
 
54
+ /**
55
+ * Get the latest schema from a versioned catalog.
56
+ * The latest version is determined by Version.max comparison.
57
+ *
58
+ * @param catalog - The versioned catalog
59
+ * @returns Either with the latest schema or EmptyCatalogError
60
+ */
61
+ export const getLatest = (catalog: Versioned): Either.Either<Schema.Versioned.Versioned, EmptyCatalogError> => {
62
+ const schema = getAll(catalog)[0]
63
+ if (!schema) {
64
+ return Either.left(
65
+ new EmptyCatalogError({
66
+ reason: 'Versioned catalog has no entries - cannot get latest schema',
67
+ }),
68
+ )
69
+ }
70
+ return Either.right(schema)
71
+ }
72
+
48
73
  /**
49
74
  * Get the latest schema definition from a versioned catalog.
50
- * The latest version is the first entry in the catalog (entries are ordered newest to oldest).
75
+ * The latest version is determined by Version.max comparison.
51
76
  *
52
77
  * @param catalog - The versioned catalog
53
78
  * @returns The GraphQL schema definition of the latest version
54
79
  * @throws {Error} If the catalog has no entries
80
+ * @deprecated Use getLatest which returns Either
55
81
  */
56
- export const getLatest = (catalog: Versioned): Schema.Schema => {
57
- const latestEntry = catalog.entries[0]
58
- if (!latestEntry) {
59
- throw new Error('Versioned catalog has no entries - cannot get latest schema')
82
+ export const getLatestOrThrow = (catalog: Versioned): Schema.Versioned.Versioned => {
83
+ const result = getLatest(catalog)
84
+ if (Either.isLeft(result)) {
85
+ throw new Error(result.left.reason)
60
86
  }
61
- return latestEntry
87
+ return result.right
88
+ }
89
+
90
+ /**
91
+ * Get all schemas sorted by version (newest first)
92
+ */
93
+ export const getAll = (catalog: Versioned): Schema.Versioned.Versioned[] => {
94
+ return pipe(
95
+ catalog.entries,
96
+ HashMap.values,
97
+ Array.fromIterable,
98
+ // Put newest versions first in array
99
+ Array.sort(Order.reverse(Schema.Versioned.order)),
100
+ )
101
+ }
102
+
103
+ /**
104
+ * Get all versions sorted (newest first)
105
+ */
106
+ export const getVersions = (catalog: Versioned): Version.Version[] => {
107
+ return pipe(
108
+ catalog,
109
+ getAll,
110
+ Array.map(_ => _.version),
111
+ )
62
112
  }
@@ -1,6 +1,7 @@
1
1
  import { Catalog } from '#lib/catalog/$'
2
2
  import { DateOnly } from '#lib/date-only/$'
3
3
  import { Version } from '#lib/version/$'
4
+ import { HashMap } from 'effect'
4
5
  import { buildSchema, type GraphQLSchema } from 'graphql'
5
6
  import { describe, expect, test } from 'vitest'
6
7
  import { CatalogStatistics } from './$.js'
@@ -91,14 +92,20 @@ describe('analyzeCatalog', () => {
91
92
  name: 'versioned catalog',
92
93
  catalog: () =>
93
94
  Catalog.Versioned.make({
94
- entries: [
95
- makeVersionedEntry(1, buildSchema('type Query { hello: String }'), ['2024-01-01', '2024-01-15']),
96
- makeVersionedEntry(
97
- 2,
98
- buildSchema('type Query { hello: String, world: String } type User { id: ID!, name: String }'),
99
- ['2024-02-01'],
100
- ),
101
- ],
95
+ entries: HashMap.make(
96
+ [
97
+ Version.fromInteger(1),
98
+ makeVersionedEntry(1, buildSchema('type Query { hello: String }'), ['2024-01-01', '2024-01-15']),
99
+ ],
100
+ [
101
+ Version.fromInteger(2),
102
+ makeVersionedEntry(
103
+ 2,
104
+ buildSchema('type Query { hello: String, world: String } type User { id: ID!, name: String }'),
105
+ ['2024-02-01'],
106
+ ),
107
+ ],
108
+ ),
102
109
  }),
103
110
  expectedVersions: 2,
104
111
  expectedCurrentVersion: '2',
@@ -132,10 +139,13 @@ describe('analyzeCatalog', () => {
132
139
 
133
140
  test('calculates stability metrics', () => {
134
141
  const catalog = Catalog.Versioned.make({
135
- entries: [
136
- makeVersionedEntry(1, buildSchema('type Query { test: String }'), ['2024-01-01', '2024-01-05']),
137
- makeVersionedEntry(2, buildSchema('type Query { test: String }'), ['2024-01-10']),
138
- ],
142
+ entries: HashMap.make(
143
+ [
144
+ Version.fromInteger(1),
145
+ makeVersionedEntry(1, buildSchema('type Query { test: String }'), ['2024-01-01', '2024-01-05']),
146
+ ],
147
+ [Version.fromInteger(2), makeVersionedEntry(2, buildSchema('type Query { test: String }'), ['2024-01-10'])],
148
+ ),
139
149
  })
140
150
 
141
151
  const report = CatalogStatistics.analyzeCatalog(catalog)
@@ -16,7 +16,7 @@ export const analyzeCatalog = (catalog: Catalog.Catalog, options: AnalyzeOptions
16
16
  const processResult = Catalog.fold(
17
17
  // Versioned catalog
18
18
  (versioned) => {
19
- for (const entry of versioned.entries) {
19
+ for (const entry of Catalog.Versioned.getAll(versioned)) {
20
20
  const versionId = Version.encodeSync(entry.version)
21
21
  // Analyze the schema definition for this version
22
22
  // Use the first revision date if available
@@ -55,8 +55,8 @@ export const analyzeCatalog = (catalog: Catalog.Catalog, options: AnalyzeOptions
55
55
  // Calculate stability metrics
56
56
  const stability = calculateStabilityMetrics(versions, revisionDates)
57
57
 
58
- // Get current version (last in array)
59
- const current = versions[versions.length - 1]
58
+ // Get current version (first in array since sorted newest first)
59
+ const current = versions[0]
60
60
 
61
61
  return {
62
62
  stability,
@@ -1,6 +1,30 @@
1
+ import { Catalog } from '#lib/catalog/$'
1
2
  import { S } from '#lib/kit-temp/effect'
3
+ import { Schema } from '#lib/schema/$'
4
+ import { VersionCoverage } from '#lib/version-coverage'
5
+ import { Version } from '#lib/version/$'
6
+ import { Data, Either, Option } from 'effect'
2
7
  import { DocumentUnversioned } from './unversioned.js'
3
- import { DocumentVersioned } from './versioned.js'
8
+ import * as DocumentVersioned from './versioned.js'
9
+
10
+ // ============================================================================
11
+ // Error Types
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Error thrown when trying to use version coverage with an unversioned catalog
16
+ */
17
+ export class VersionCoverageMismatchError extends Data.TaggedError('VersionCoverageMismatchError')<{
18
+ readonly reason: string
19
+ }> {}
20
+
21
+ /**
22
+ * Error thrown when a version is not found in the document
23
+ */
24
+ export class VersionNotFoundInDocumentError extends Data.TaggedError('VersionNotFoundInDocumentError')<{
25
+ readonly version: string
26
+ readonly reason: string
27
+ }> {}
4
28
 
5
29
  // ============================================================================
6
30
  // Schema
@@ -13,7 +37,7 @@ import { DocumentVersioned } from './versioned.js'
13
37
  */
14
38
  export const Document = S.Union(
15
39
  DocumentUnversioned,
16
- DocumentVersioned,
40
+ DocumentVersioned.DocumentVersioned,
17
41
  )
18
42
 
19
43
  // ============================================================================
@@ -35,3 +59,112 @@ export const is = S.is(Document)
35
59
  export const decode = S.decode(Document)
36
60
  export const decodeSync = S.decodeSync(Document)
37
61
  export const encode = S.encode(Document)
62
+
63
+ // ============================================================================
64
+ // Domain Logic - Resolution Functions
65
+ // ============================================================================
66
+
67
+ /**
68
+ * Resolve document content for a given version coverage.
69
+ *
70
+ * @param document - The document to resolve content from
71
+ * @param versionCoverage - The version coverage to use (optional, defaults to latest)
72
+ * @returns The resolved document content
73
+ * @throws {Error} If version is not found in document
74
+ */
75
+ export const resolveDocumentContent = (
76
+ document: Document,
77
+ versionCoverage?: VersionCoverage.VersionCoverage | null,
78
+ ): string => {
79
+ if (document._tag === 'DocumentUnversioned') {
80
+ return document.document
81
+ }
82
+
83
+ // If no version coverage specified, use latest
84
+ if (!versionCoverage) {
85
+ return DocumentVersioned.getContentForLatestVersionOrThrow(document)
86
+ }
87
+
88
+ // Get the latest version from the coverage
89
+ const version = VersionCoverage.getLatest(versionCoverage)
90
+ const contentOption = DocumentVersioned.getContentForVersion(document, version)
91
+
92
+ if (Option.isNone(contentOption)) {
93
+ throw new VersionNotFoundInDocumentError({
94
+ version: Version.encodeSync(version),
95
+ reason: `Version ${Version.encodeSync(version)} not covered by document`,
96
+ })
97
+ }
98
+
99
+ return contentOption.value
100
+ }
101
+
102
+ /**
103
+ * Resolve both document content and schema for a given version coverage.
104
+ * This is the primary resolution function that handles all combinations
105
+ * of versioned/unversioned documents and catalogs.
106
+ *
107
+ * @param document - The document to resolve content from
108
+ * @param catalog - The schema catalog (optional)
109
+ * @param versionCoverage - The version coverage to use (optional, defaults to latest)
110
+ * @returns Either with resolved content and optional schema, or error
111
+ */
112
+ export const resolveDocumentAndSchema = (
113
+ document: Document,
114
+ catalog?: Catalog.Catalog,
115
+ versionCoverage?: VersionCoverage.VersionCoverage | null,
116
+ ): Either.Either<
117
+ { content: string; schema?: Schema.Schema },
118
+ | VersionCoverageMismatchError
119
+ | VersionNotFoundInDocumentError
120
+ | Catalog.VersionNotFoundInCatalogError
121
+ | Catalog.EmptyCatalogError
122
+ > => {
123
+ // Handle unversioned document
124
+ if (document._tag === 'DocumentUnversioned') {
125
+ const content = document.document
126
+
127
+ if (!catalog) {
128
+ return Either.right({ content })
129
+ }
130
+
131
+ return Catalog.resolveCatalogSchemaEither(catalog, null).pipe(
132
+ Either.map(schema => ({ content, schema })),
133
+ )
134
+ }
135
+
136
+ // Handle versioned document
137
+ let content: string
138
+ try {
139
+ content = resolveDocumentContent(document, versionCoverage)
140
+ } catch (error) {
141
+ // resolveDocumentContent throws our tagged error
142
+ if (error instanceof VersionNotFoundInDocumentError) {
143
+ return Either.left(error)
144
+ }
145
+ // This shouldn't happen but handle gracefully
146
+ return Either.left(
147
+ new VersionNotFoundInDocumentError({
148
+ version: String(versionCoverage),
149
+ reason: error instanceof Error ? error.message : String(error),
150
+ }),
151
+ )
152
+ }
153
+
154
+ if (!catalog) {
155
+ return Either.right({ content })
156
+ }
157
+
158
+ // Cannot use version coverage with unversioned catalog
159
+ if (versionCoverage && Catalog.Unversioned.is(catalog)) {
160
+ return Either.left(
161
+ new VersionCoverageMismatchError({
162
+ reason: 'Cannot use a version coverage with an unversioned catalog',
163
+ }),
164
+ )
165
+ }
166
+
167
+ return Catalog.resolveCatalogSchemaEither(catalog, versionCoverage).pipe(
168
+ Either.map(schema => ({ content, schema })),
169
+ )
170
+ }
@@ -1,5 +1,5 @@
1
1
  import { S } from '#lib/kit-temp/effect'
2
- import { VersionCoverage } from '#lib/version-selection/$'
2
+ import { VersionCoverage } from '#lib/version-coverage'
3
3
  import { Version } from '#lib/version/$'
4
4
  import { HashMap, Option } from 'effect'
5
5
 
@@ -56,21 +56,21 @@ export const encode = S.encode(DocumentVersioned)
56
56
  export const getContentForVersion = (
57
57
  doc: DocumentVersioned,
58
58
  version: Version.Version,
59
- ): string | null => {
59
+ ): Option.Option<string> => {
60
60
  // Try exact match first (single version key)
61
61
  const exactMatch = HashMap.get(doc.versionDocuments, version)
62
62
  if (Option.isSome(exactMatch)) {
63
- return exactMatch.value
63
+ return Option.some(exactMatch.value)
64
64
  }
65
65
 
66
66
  // Check version sets
67
67
  for (const [selection, content] of HashMap.entries(doc.versionDocuments)) {
68
68
  if (VersionCoverage.isSet(selection) && VersionCoverage.contains(selection, version)) {
69
- return content
69
+ return Option.some(content)
70
70
  }
71
71
  }
72
72
 
73
- return null
73
+ return Option.none()
74
74
  }
75
75
 
76
76
  /**
@@ -98,9 +98,9 @@ export const getContentForLatestVersionOrThrow = (doc: DocumentVersioned): strin
98
98
  // Use Version.max with reduce to find the latest version
99
99
  const latestVersion = versions.reduce(Version.max)
100
100
 
101
- const content = getContentForVersion(doc, latestVersion)
102
- if (!content) {
101
+ const contentOption = getContentForVersion(doc, latestVersion)
102
+ if (Option.isNone(contentOption)) {
103
103
  throw new Error('Latest version not found in document')
104
104
  }
105
- return content
105
+ return contentOption.value
106
106
  }