polen 0.11.0-next.17 → 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 (196) 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 +1 -1
  22. package/build/api/examples/diagnostic/validation-error.d.ts +3 -2
  23. package/build/api/examples/diagnostic/validation-error.d.ts.map +1 -1
  24. package/build/api/examples/diagnostic/validation-error.js +9 -3
  25. package/build/api/examples/diagnostic/validation-error.js.map +1 -1
  26. package/build/api/examples/diagnostic/validator.d.ts.map +1 -1
  27. package/build/api/examples/diagnostic/validator.js +115 -68
  28. package/build/api/examples/diagnostic/validator.js.map +1 -1
  29. package/build/api/examples/filter.d.ts.map +1 -1
  30. package/build/api/examples/filter.js +9 -6
  31. package/build/api/examples/filter.js.map +1 -1
  32. package/build/api/examples/scanner.d.ts.map +1 -1
  33. package/build/api/examples/scanner.js +89 -103
  34. package/build/api/examples/scanner.js.map +1 -1
  35. package/build/api/examples/type-usage-indexer.d.ts.map +1 -1
  36. package/build/api/examples/type-usage-indexer.js +17 -30
  37. package/build/api/examples/type-usage-indexer.js.map +1 -1
  38. package/build/api/iso/schema/routing.d.ts.map +1 -1
  39. package/build/api/iso/schema/routing.js +8 -8
  40. package/build/api/iso/schema/routing.js.map +1 -1
  41. package/build/api/iso/schema/validation.d.ts.map +1 -1
  42. package/build/api/iso/schema/validation.js +3 -2
  43. package/build/api/iso/schema/validation.js.map +1 -1
  44. package/build/api/schema/input-sources/directory.js +2 -2
  45. package/build/api/schema/input-sources/directory.js.map +1 -1
  46. package/build/api/schema/input-sources/versioned-directory.d.ts.map +1 -1
  47. package/build/api/schema/input-sources/versioned-directory.js +3 -3
  48. package/build/api/schema/input-sources/versioned-directory.js.map +1 -1
  49. package/build/api/schema/load.d.ts.map +1 -1
  50. package/build/api/schema/load.js +1 -1
  51. package/build/api/schema/load.js.map +1 -1
  52. package/build/lib/catalog/catalog.d.ts +43 -3
  53. package/build/lib/catalog/catalog.d.ts.map +1 -1
  54. package/build/lib/catalog/catalog.js +67 -5
  55. package/build/lib/catalog/catalog.js.map +1 -1
  56. package/build/lib/catalog/versioned.d.ts +11 -1
  57. package/build/lib/catalog/versioned.d.ts.map +1 -1
  58. package/build/lib/catalog/versioned.js +23 -5
  59. package/build/lib/catalog/versioned.js.map +1 -1
  60. package/build/lib/document/document.d.ts +55 -5
  61. package/build/lib/document/document.d.ts.map +1 -1
  62. package/build/lib/document/document.js +96 -2
  63. package/build/lib/document/document.js.map +1 -1
  64. package/build/lib/document/versioned.d.ts +2 -2
  65. package/build/lib/document/versioned.d.ts.map +1 -1
  66. package/build/lib/document/versioned.js +7 -7
  67. package/build/lib/document/versioned.js.map +1 -1
  68. package/build/lib/lifecycles/lifecycles.d.ts +5 -4
  69. package/build/lib/lifecycles/lifecycles.d.ts.map +1 -1
  70. package/build/lib/lifecycles/lifecycles.js +14 -12
  71. package/build/lib/lifecycles/lifecycles.js.map +1 -1
  72. package/build/lib/version-coverage/$$.d.ts +2 -0
  73. package/build/lib/version-coverage/$$.d.ts.map +1 -0
  74. package/build/lib/version-coverage/$$.js +2 -0
  75. package/build/lib/version-coverage/$$.js.map +1 -0
  76. package/build/lib/version-coverage/$.d.ts.map +1 -0
  77. package/build/lib/version-coverage/$.js.map +1 -0
  78. package/build/lib/{version-selection/version-selection.d.ts → version-coverage/version-coverage.d.ts} +1 -1
  79. package/build/lib/version-coverage/version-coverage.d.ts.map +1 -0
  80. package/build/lib/{version-selection/version-selection.js → version-coverage/version-coverage.js} +2 -2
  81. package/build/lib/version-coverage/version-coverage.js.map +1 -0
  82. package/build/template/components/GraphQLDocument.d.ts +1 -1
  83. package/build/template/components/GraphQLDocument.d.ts.map +1 -1
  84. package/build/template/components/GraphQLDocument.js +10 -39
  85. package/build/template/components/GraphQLDocument.js.map +1 -1
  86. package/build/template/components/GraphQLInteractive/lib/parser.d.ts +28 -0
  87. package/build/template/components/GraphQLInteractive/lib/parser.d.ts.map +1 -1
  88. package/build/template/components/GraphQLInteractive/lib/parser.js +60 -27
  89. package/build/template/components/GraphQLInteractive/lib/parser.js.map +1 -1
  90. package/build/template/components/VersionCoveragePicker.d.ts +1 -1
  91. package/build/template/components/VersionCoveragePicker.d.ts.map +1 -1
  92. package/build/template/components/VersionCoveragePicker.js +4 -6
  93. package/build/template/components/VersionCoveragePicker.js.map +1 -1
  94. package/build/template/components/home/QuickStart.d.ts.map +1 -1
  95. package/build/template/components/home/QuickStart.js +8 -4
  96. package/build/template/components/home/QuickStart.js.map +1 -1
  97. package/build/template/hooks/use-highlighted.d.ts.map +1 -1
  98. package/build/template/hooks/use-highlighted.js +19 -13
  99. package/build/template/hooks/use-highlighted.js.map +1 -1
  100. package/build/template/lib/fetch-text.d.ts +18 -0
  101. package/build/template/lib/fetch-text.d.ts.map +1 -1
  102. package/build/template/lib/fetch-text.js +32 -4
  103. package/build/template/lib/fetch-text.js.map +1 -1
  104. package/build/template/routes/examples/name.d.ts.map +1 -1
  105. package/build/template/routes/examples/name.js +4 -2
  106. package/build/template/routes/examples/name.js.map +1 -1
  107. package/build/template/stores/toast.d.ts.map +1 -1
  108. package/build/template/stores/toast.js +5 -3
  109. package/build/template/stores/toast.js.map +1 -1
  110. package/package.json +7 -7
  111. package/src/api/builder/ssg/generate.ts +10 -5
  112. package/src/api/builder/ssg/page-generator.worker.ts +18 -3
  113. package/src/api/config/normalized.ts +12 -3
  114. package/src/api/config-template/template.ts +2 -2
  115. package/src/api/content/sidebar.ts +3 -3
  116. package/src/api/examples/config.test.ts +10 -0
  117. package/src/api/examples/config.ts +33 -4
  118. package/src/api/examples/diagnostic/validation-error.ts +9 -3
  119. package/src/api/examples/diagnostic/validator.test.ts +30 -0
  120. package/src/api/examples/diagnostic/validator.ts +148 -103
  121. package/src/api/examples/filter.ts +9 -6
  122. package/src/api/examples/scanner.ts +136 -117
  123. package/src/api/examples/type-usage-indexer.ts +24 -36
  124. package/src/api/iso/schema/routing.ts +10 -10
  125. package/src/api/iso/schema/validation.ts +3 -2
  126. package/src/api/schema/input-sources/directory.ts +2 -2
  127. package/src/api/schema/input-sources/versioned-directory.ts +5 -7
  128. package/src/api/schema/load.ts +1 -1
  129. package/src/lib/catalog/catalog.ts +89 -6
  130. package/src/lib/catalog/versioned.ts +26 -5
  131. package/src/lib/document/document.ts +135 -2
  132. package/src/lib/document/versioned.ts +8 -8
  133. package/src/lib/lifecycles/lifecycles.ts +32 -27
  134. package/src/lib/version-coverage/$$.ts +1 -0
  135. package/src/lib/{version-selection/version-selection.ts → version-coverage/version-coverage.ts} +1 -1
  136. package/src/template/components/GraphQLDocument.tsx +11 -69
  137. package/src/template/components/GraphQLInteractive/lib/parser.ts +81 -29
  138. package/src/template/components/VersionCoveragePicker.tsx +4 -5
  139. package/src/template/components/home/QuickStart.tsx +16 -7
  140. package/src/template/hooks/use-highlighted.ts +31 -19
  141. package/src/template/lib/fetch-text.ts +45 -4
  142. package/src/template/routes/examples/name.tsx +4 -2
  143. package/src/template/stores/toast.ts +6 -3
  144. package/build/lib/graph/$$.d.ts +0 -2
  145. package/build/lib/graph/$$.d.ts.map +0 -1
  146. package/build/lib/graph/$$.js +0 -2
  147. package/build/lib/graph/$$.js.map +0 -1
  148. package/build/lib/graph/$.d.ts +0 -2
  149. package/build/lib/graph/$.d.ts.map +0 -1
  150. package/build/lib/graph/$.js +0 -2
  151. package/build/lib/graph/$.js.map +0 -1
  152. package/build/lib/graph/graph.d.ts +0 -127
  153. package/build/lib/graph/graph.d.ts.map +0 -1
  154. package/build/lib/graph/graph.js +0 -152
  155. package/build/lib/graph/graph.js.map +0 -1
  156. package/build/lib/mask/$$.d.ts +0 -3
  157. package/build/lib/mask/$$.d.ts.map +0 -1
  158. package/build/lib/mask/$$.js +0 -3
  159. package/build/lib/mask/$$.js.map +0 -1
  160. package/build/lib/mask/$.d.ts +0 -2
  161. package/build/lib/mask/$.d.ts.map +0 -1
  162. package/build/lib/mask/$.js +0 -2
  163. package/build/lib/mask/$.js.map +0 -1
  164. package/build/lib/mask/apply.d.ts +0 -86
  165. package/build/lib/mask/apply.d.ts.map +0 -1
  166. package/build/lib/mask/apply.js +0 -86
  167. package/build/lib/mask/apply.js.map +0 -1
  168. package/build/lib/mask/mask.d.ts +0 -124
  169. package/build/lib/mask/mask.d.ts.map +0 -1
  170. package/build/lib/mask/mask.js +0 -137
  171. package/build/lib/mask/mask.js.map +0 -1
  172. package/build/lib/mask/mask.test-d.d.ts +0 -2
  173. package/build/lib/mask/mask.test-d.d.ts.map +0 -1
  174. package/build/lib/mask/mask.test-d.js +0 -102
  175. package/build/lib/mask/mask.test-d.js.map +0 -1
  176. package/build/lib/version-selection/$$.d.ts +0 -2
  177. package/build/lib/version-selection/$$.d.ts.map +0 -1
  178. package/build/lib/version-selection/$$.js +0 -2
  179. package/build/lib/version-selection/$$.js.map +0 -1
  180. package/build/lib/version-selection/$.d.ts.map +0 -1
  181. package/build/lib/version-selection/$.js.map +0 -1
  182. package/build/lib/version-selection/version-selection.d.ts.map +0 -1
  183. package/build/lib/version-selection/version-selection.js.map +0 -1
  184. package/src/lib/graph/$$.ts +0 -1
  185. package/src/lib/graph/$.ts +0 -1
  186. package/src/lib/graph/graph.ts +0 -197
  187. package/src/lib/mask/$$.ts +0 -2
  188. package/src/lib/mask/$.test.ts +0 -226
  189. package/src/lib/mask/$.ts +0 -1
  190. package/src/lib/mask/apply.ts +0 -134
  191. package/src/lib/mask/mask.test-d.ts +0 -156
  192. package/src/lib/mask/mask.ts +0 -244
  193. package/src/lib/version-selection/$$.ts +0 -1
  194. /package/build/lib/{version-selection → version-coverage}/$.d.ts +0 -0
  195. /package/build/lib/{version-selection → version-coverage}/$.js +0 -0
  196. /package/src/lib/{version-selection → version-coverage}/$.ts +0 -0
@@ -469,7 +469,7 @@ const buildPaths = (rootDir: string, overrides?: ConfigAdvancedPathsInput | unde
469
469
  }
470
470
  }
471
471
 
472
- const getConfigInputDefaults = (): Config => ({
472
+ const getConfigInputDefaults = (baseRootDirPath: string): Config => ({
473
473
  _input: {},
474
474
  name: `My Developer Portal`,
475
475
  description: `Explore and integrate with our GraphQL API`,
@@ -525,7 +525,7 @@ const getConfigInputDefaults = (): Config => ({
525
525
  enabled: true,
526
526
  },
527
527
  },
528
- paths: buildPaths(process.cwd()),
528
+ paths: buildPaths(baseRootDirPath),
529
529
  advanced: {
530
530
  isSelfContainedMode: false,
531
531
  debug: false,
@@ -549,7 +549,7 @@ export const normalizeInput = (
549
549
  assertPathAbsolute(baseRootDirPath)
550
550
 
551
551
  const configInput_as_writeable = configInput as WritableDeep<ConfigInput> | undefined
552
- const config = structuredClone(getConfigInputDefaults()) as WritableDeep<Config>
552
+ const config = structuredClone(getConfigInputDefaults(baseRootDirPath)) as WritableDeep<Config>
553
553
 
554
554
  if (configInput_as_writeable) {
555
555
  config._input = configInput_as_writeable
@@ -656,6 +656,15 @@ export const normalizeInput = (
656
656
  if (configInput_as_writeable?.examples) {
657
657
  const examplesInput = configInput_as_writeable.examples
658
658
 
659
+ // The schema transform handles boolean shorthand, so we always get an object here
660
+ if (examplesInput.enabled !== undefined) {
661
+ // If explicitly disabled, set display to 'none'
662
+ if (examplesInput.enabled === false) {
663
+ config.examples.display = 'none'
664
+ }
665
+ // enabled: true keeps defaults
666
+ }
667
+
659
668
  if (examplesInput.display !== undefined) {
660
669
  config.examples.display = examplesInput.display
661
670
  }
@@ -1,5 +1,5 @@
1
1
  import type { Api } from '#api/$'
2
- import { ExamplesConfig } from '#api/examples/config'
2
+ import { ExamplesConfigObject } from '#api/examples/config'
3
3
  import { ConfigSchema } from '#api/schema/config-schema'
4
4
  import type { Catalog } from '#lib/catalog/$'
5
5
  import { S } from '#lib/kit-temp/effect'
@@ -42,7 +42,7 @@ export const TemplateConfig = S.extend(
42
42
  }),
43
43
  ),
44
44
  examples: S.extend(
45
- ExamplesConfig.pipe(S.omit('enabled')),
45
+ ExamplesConfigObject.pipe(S.omit('enabled')),
46
46
  S.Struct({
47
47
  enabled: S.Boolean,
48
48
  }),
@@ -1,5 +1,6 @@
1
1
  import { FileRouter } from '#lib/file-router'
2
2
  import { Str } from '@wollybeard/kit'
3
+ import { Array } from 'effect'
3
4
  import type { Page } from './page.js'
4
5
  import type { ScanResult } from './scan.js'
5
6
 
@@ -129,10 +130,9 @@ export const buildSidebarIndex = (scanResult: ScanResult): SidebarIndex => {
129
130
 
130
131
  // Build sidebar for each directory that has an index page
131
132
  for (const [topLevelDir, pages] of pagesByTopLevelDir) {
132
- const hasIndexPage = pages.some(page =>
133
+ const hasIndexPage = Array.some(pages, page =>
133
134
  page.route.logical.path.length === 1
134
- && FileRouter.routeIsFromIndexFile(page.route)
135
- )
135
+ && FileRouter.routeIsFromIndexFile(page.route))
136
136
 
137
137
  // Skip directories without index pages
138
138
  if (!hasIndexPage) continue
@@ -28,6 +28,16 @@ describe('ExamplesConfig', () => {
28
28
  expect(result).toEqual({})
29
29
  })
30
30
 
31
+ test('boolean shorthand: false disables examples', () => {
32
+ const result = decodeExamplesConfig(false)
33
+ expect(result).toEqual({ enabled: false })
34
+ })
35
+
36
+ test('boolean shorthand: true uses defaults', () => {
37
+ const result = decodeExamplesConfig(true)
38
+ expect(result).toEqual({})
39
+ })
40
+
31
41
  test('decodes full config', () => {
32
42
  const input = {
33
43
  display: { include: ['example1', 'example2'] },
@@ -141,7 +141,7 @@ export type ExampleDiagnostics = S.Schema.Type<typeof ExampleDiagnostics>
141
141
  // Schema - Examples Config
142
142
  // ============================================================================
143
143
 
144
- export const ExamplesConfig = S.Struct({
144
+ export const ExamplesConfigObject = S.Struct({
145
145
  /**
146
146
  * Control whether the examples feature is enabled.
147
147
  * - true: Always enabled (show in nav even if no examples exist)
@@ -220,13 +220,42 @@ export const ExamplesConfig = S.Struct({
220
220
  description: 'Configuration for GraphQL examples behavior and diagnostics',
221
221
  })
222
222
 
223
+ /**
224
+ * Examples configuration supporting both boolean shorthand and detailed object form.
225
+ * - `false`: Disable examples entirely
226
+ * - `true`: Enable examples with defaults
227
+ * - object: Fine-grained configuration
228
+ */
229
+ export const ExamplesConfig = S.transform(
230
+ S.Union(
231
+ S.Boolean,
232
+ ExamplesConfigObject,
233
+ ),
234
+ ExamplesConfigObject,
235
+ {
236
+ strict: false,
237
+ decode: (input) => {
238
+ if (typeof input === 'boolean') {
239
+ // Convert boolean shorthand to object form
240
+ return input === false
241
+ ? { enabled: false } as const
242
+ : {} // true means use defaults
243
+ }
244
+ return input
245
+ },
246
+ encode: (value) => value,
247
+ },
248
+ ).annotations({
249
+ identifier: 'ExamplesConfigTransform',
250
+ title: 'Examples Configuration with Boolean Shorthand',
251
+ description: 'Configuration for GraphQL examples - accepts boolean shorthand or detailed object',
252
+ })
253
+
223
254
  export type ExamplesConfig = S.Schema.Type<typeof ExamplesConfig>
224
255
 
225
256
  // ============================================================================
226
257
  // Constructors
227
- // ============================================================================
228
-
229
- export const makeExamplesConfig = ExamplesConfig.make
258
+ export const makeExamplesConfig = ExamplesConfigObject.make
230
259
 
231
260
  // ============================================================================
232
261
  // Type Guards
@@ -1,6 +1,6 @@
1
1
  import { Diagnostic } from '#lib/diagnostic/$'
2
- import { GraphQLError } from '#lib/graphql-error/$'
3
2
  import { S } from '#lib/kit-temp/effect'
3
+ import { Version } from '#lib/version/$'
4
4
 
5
5
  export const DiagnosticValidationError = Diagnostic.create({
6
6
  source: 'examples-validation',
@@ -11,8 +11,14 @@ export const DiagnosticValidationError = Diagnostic.create({
11
11
  name: S.String,
12
12
  path: S.String,
13
13
  }),
14
- version: S.String,
15
- errors: S.Array(GraphQLError.GraphQLError),
14
+ version: S.optional(Version.Version),
15
+ errors: S.Array(S.Struct({
16
+ message: S.String,
17
+ locations: S.optional(S.Array(S.Struct({
18
+ line: S.Number,
19
+ column: S.Number,
20
+ }))),
21
+ })),
16
22
  },
17
23
  }).annotations({
18
24
  identifier: 'DiagnosticValidationError',
@@ -290,3 +290,33 @@ test('validates examples with version sets', () => {
290
290
  const diagnostics = validateExamples(examples, versionedCatalogWithV3)
291
291
  expect(diagnostics.length).toBe(1) // invalidField doesn't exist in the test schema
292
292
  })
293
+
294
+ test('detects mismatch between versioned document and unversioned catalog', () => {
295
+ // Create a versioned document with multiple versions
296
+ const v1 = Version.fromString('1.0.0')
297
+ const v2 = Version.fromString('2.0.0')
298
+
299
+ const versionedDocument = Document.Versioned.make({
300
+ versionDocuments: HashMap.make(
301
+ [v1, 'query { user(id: "1") { id name } }'],
302
+ [v2, 'query { users { id name } }'],
303
+ ),
304
+ })
305
+
306
+ // Create an example with versioned document
307
+ const example = Example.make({
308
+ name: 'test-version-mismatch',
309
+ path: 'test.graphql',
310
+ document: versionedDocument,
311
+ })
312
+
313
+ // Validate against unversioned catalog - should detect mismatch
314
+ const diagnostics = validateExamples([example], catalog)
315
+
316
+ // Should have exactly one diagnostic about the version mismatch
317
+ expect(diagnostics.length).toBe(1)
318
+ expect(diagnostics[0]!._tag).toBe('Diagnostic')
319
+ expect(diagnostics[0]!.name).toBe('invalid-graphql')
320
+ expect(diagnostics[0]!.message).toContain('versioned content but catalog is unversioned')
321
+ expect(diagnostics[0]!.errors![0]!.message).toContain('Cannot use a version coverage with an unversioned catalog')
322
+ })
@@ -1,9 +1,9 @@
1
1
  import { Catalog as SchemaCatalog } from '#lib/catalog/$'
2
2
  import { Document } from '#lib/document/$'
3
+ import { Schema } from '#lib/schema/$'
3
4
  import { Version } from '#lib/version/$'
4
- import { Match } from 'effect'
5
- import { HashMap } from 'effect/Schema'
6
- import type { GraphQLError, GraphQLSchema } from 'graphql'
5
+ import { Array, Either, Match, Schema as S } from 'effect'
6
+ import type { DocumentNode, GraphQLError } from 'graphql'
7
7
  import { parse, specifiedRules, validate } from 'graphql'
8
8
  import { Example } from '../schemas/example/$.js'
9
9
  import type { DiagnosticValidationError } from './diagnostic.js'
@@ -15,6 +15,35 @@ import { makeDiagnosticValidationError } from './diagnostic.js'
15
15
 
16
16
  export type ValidationDiagnostic = DiagnosticValidationError
17
17
 
18
+ // ============================================================================
19
+ // Schemas
20
+ // ============================================================================
21
+
22
+ const GraphQLErrorData = S.Struct({
23
+ message: S.String,
24
+ locations: S.optional(S.Array(S.Struct({
25
+ line: S.Number,
26
+ column: S.Number,
27
+ }))),
28
+ })
29
+
30
+ type GraphQLErrorData = S.Schema.Type<typeof GraphQLErrorData>
31
+
32
+ // ============================================================================
33
+ // Safe Parsing
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Safely parse a GraphQL document without throwing.
38
+ * Returns an Either with the document on success or error on failure.
39
+ */
40
+ const parseDocumentSafe = (content: string): Either.Either<DocumentNode, GraphQLError> => {
41
+ return Either.try({
42
+ try: () => parse(content),
43
+ catch: (error) => error as GraphQLError,
44
+ })
45
+ }
46
+
18
47
  // ============================================================================
19
48
  // Validation
20
49
  // ============================================================================
@@ -32,69 +61,76 @@ export const validateExamples = (
32
61
  ): ValidationDiagnostic[] => {
33
62
  const diagnostics: ValidationDiagnostic[] = []
34
63
 
35
- return Match.value(catalog).pipe(
36
- Match.tagsExhaustive({
37
- CatalogVersioned: (versioned) => {
38
- for (const example of examples) {
39
- Match.value(example.document).pipe(
40
- Match.tagsExhaustive({
41
- DocumentVersioned: (doc) => {
42
- // Validate each version against its corresponding schema
43
- for (const schema of SchemaCatalog.Versioned.getAll(versioned)) {
44
- const content = Document.Versioned.getContentForVersion(doc, schema.version)
45
- if (content) {
46
- const versionStr = Version.encodeSync(schema.version)
47
- validateDocument(
48
- example.name,
49
- example.path,
50
- versionStr,
51
- content,
52
- schema.definition,
53
- diagnostics,
54
- )
55
- }
56
- }
57
- return undefined
58
- },
59
- DocumentUnversioned: (doc) => {
60
- const latestEntry = SchemaCatalog.Versioned.getLatestOrThrow(versioned)
61
- validateDocument(
62
- example.name,
63
- example.path,
64
- 'default',
65
- doc.document,
66
- latestEntry.definition,
67
- diagnostics,
68
- )
69
- return undefined
70
- },
71
- }),
72
- )
73
- }
74
-
75
- return diagnostics
76
- },
77
- CatalogUnversioned: (unversioned) => {
78
- // For unversioned catalog, only unversioned documents make sense
79
- for (const example of examples) {
80
- if (example.document._tag === 'DocumentUnversioned') {
81
- validateDocument(
82
- example.name,
83
- example.path,
84
- 'default',
85
- example.document.document,
86
- unversioned.schema.definition,
87
- diagnostics,
64
+ Array.forEach(examples, (example) => {
65
+ if (example.document._tag === 'DocumentVersioned') {
66
+ // Versioned document: iterate all versions and let utility handle compatibility
67
+ const documentVersions = Document.Versioned.getAllVersions(example.document)
68
+ let versionMismatchDetected = false
69
+
70
+ Array.forEach(documentVersions, (version) => {
71
+ // Skip remaining versions if we already detected a version mismatch
72
+ if (versionMismatchDetected) return
73
+
74
+ const result = Document.resolveDocumentAndSchema(
75
+ example.document,
76
+ catalog,
77
+ version, // Pass each version as VersionCoverage
78
+ )
79
+
80
+ Either.match(result, {
81
+ onLeft: (error) => {
82
+ // Pattern match on error types - all errors are now tagged
83
+ Match.value(error).pipe(
84
+ Match.when({ _tag: 'VersionCoverageMismatchError' }, (err) => {
85
+ diagnostics.push(makeDiagnosticValidationError({
86
+ message: `Example "${example.name}" has versioned content but catalog is unversioned`,
87
+ example: { name: example.name, path: example.path },
88
+ version: undefined,
89
+ errors: [{ message: err.reason }],
90
+ }))
91
+ // Set flag to skip remaining versions - all will have same error
92
+ versionMismatchDetected = true
93
+ }),
94
+ Match.when({ _tag: 'VersionNotFoundInDocumentError' }, () => {
95
+ // Skip silently - version not found in document
96
+ }),
97
+ Match.when({ _tag: 'VersionNotFoundInCatalogError' }, () => {
98
+ // Skip silently - version not found in catalog
99
+ }),
100
+ Match.when({ _tag: 'EmptyCatalogError' }, () => {
101
+ // Skip silently - catalog has no entries
102
+ }),
103
+ Match.exhaustive,
88
104
  )
105
+ },
106
+ onRight: ({ content, schema }) => {
107
+ if (schema) {
108
+ validateDocument(example, schema, content, diagnostics)
109
+ }
110
+ },
111
+ })
112
+ })
113
+ } else {
114
+ // Unversioned document: call without version coverage
115
+ const result = Document.resolveDocumentAndSchema(
116
+ example.document,
117
+ catalog,
118
+ )
119
+
120
+ Either.match(result, {
121
+ onLeft: () => {
122
+ // Resolution failed - skip silently
123
+ },
124
+ onRight: ({ content, schema }) => {
125
+ if (schema) {
126
+ validateDocument(example, schema, content, diagnostics)
89
127
  }
90
- // Versioned/PartiallyVersioned documents with unversioned catalog don't make sense
91
- // These should be caught by other diagnostics
92
- }
128
+ },
129
+ })
130
+ }
131
+ })
93
132
 
94
- return diagnostics
95
- },
96
- }),
97
- )
133
+ return diagnostics
98
134
  }
99
135
 
100
136
  // ============================================================================
@@ -105,54 +141,67 @@ export const validateExamples = (
105
141
  * Validate a single document and add diagnostics if needed.
106
142
  */
107
143
  const validateDocument = (
108
- exampleName: string,
109
- examplePath: string,
110
- version: string,
144
+ example: Example.Example,
145
+ schema: Schema.Schema,
111
146
  content: string,
112
- schema: GraphQLSchema,
113
147
  diagnostics: ValidationDiagnostic[],
114
148
  ): void => {
115
- try {
116
- // Parse the GraphQL document
117
- const document = parse(content)
118
-
119
- // Validate against the schema
120
- const errors = validate(schema, document, specifiedRules)
121
-
122
- if (errors.length > 0) {
123
- diagnostics.push(makeDiagnosticValidationError({
124
- message: formatValidationMessage(exampleName, version, errors),
125
- example: { name: exampleName, path: examplePath },
126
- version,
127
- errors: errors.map(formatGraphQLError),
128
- }))
129
- }
130
- } catch (parseError) {
131
- // Parse errors are also validation errors
149
+ // Extract version from schema (undefined for unversioned)
150
+ const version = Schema.getVersion(schema)
151
+
152
+ // Parse the document safely
153
+ const parseResult = parseDocumentSafe(content)
154
+
155
+ if (Either.isLeft(parseResult)) {
156
+ // Handle parse errors
157
+ diagnostics.push(makeDiagnosticValidationError({
158
+ message: formatParseError(example.name, version),
159
+ example: { name: example.name, path: example.path },
160
+ version,
161
+ errors: [{ message: parseResult.left.message }],
162
+ }))
163
+ return
164
+ }
165
+
166
+ // Validate the parsed document against the schema
167
+ const errors = validate(schema.definition, parseResult.right, specifiedRules)
168
+
169
+ if (Array.isNonEmptyReadonlyArray(errors)) {
132
170
  diagnostics.push(makeDiagnosticValidationError({
133
- message: `Example "${exampleName}" (${version}) has invalid GraphQL syntax`,
134
- example: { name: exampleName, path: examplePath },
171
+ message: formatValidationMessage(example.name, version, errors),
172
+ example: { name: example.name, path: example.path },
135
173
  version,
136
- errors: [{ message: String(parseError) }],
174
+ errors: errors.map(formatGraphQLError),
137
175
  }))
138
176
  }
139
177
  }
140
178
 
179
+ /**
180
+ * Format a parse error message for an example.
181
+ */
182
+ const formatParseError = (
183
+ exampleId: string,
184
+ version: Version.Version | undefined,
185
+ ): string => {
186
+ const versionStr = version ? ` (${Version.encodeSync(version)})` : ''
187
+ return `Example "${exampleId}"${versionStr} has invalid GraphQL syntax`
188
+ }
189
+
141
190
  /**
142
191
  * Format a validation message for an example.
143
192
  */
144
193
  const formatValidationMessage = (
145
194
  exampleId: string,
146
- version: string,
147
- errors: readonly GraphQLError[],
195
+ version: Version.Version | undefined,
196
+ errors: Array.NonEmptyReadonlyArray<GraphQLError>,
148
197
  ): string => {
149
- const versionStr = version === 'default' ? '' : ` (${version})`
198
+ const versionStr = version ? ` (${Version.encodeSync(version)})` : ''
150
199
  const errorCount = errors.length
151
200
  const errorWord = errorCount === 1 ? 'error' : 'errors'
152
201
 
153
- // Include the first error message for context
154
- const firstError = errors[0]
155
- const preview = firstError ? `: ${firstError.message}` : ''
202
+ // Include the first error message for context - always safe with NonEmptyArray
203
+ const firstError = Array.headNonEmpty(errors)
204
+ const preview = `: ${firstError.message}`
156
205
 
157
206
  return `Example "${exampleId}"${versionStr} has ${errorCount} validation ${errorWord}${preview}`
158
207
  }
@@ -160,17 +209,13 @@ const formatValidationMessage = (
160
209
  /**
161
210
  * Format a GraphQL error for diagnostic reporting.
162
211
  */
163
- const formatGraphQLError = (error: GraphQLError): {
164
- message: string
165
- locations?: Array<{ line: number; column: number }>
166
- } => ({
167
- message: error.message,
168
- ...(error.locations
169
- ? {
212
+ const formatGraphQLError = (error: GraphQLError): GraphQLErrorData =>
213
+ GraphQLErrorData.make({
214
+ message: error.message,
215
+ ...(error.locations && {
170
216
  locations: error.locations.map(loc => ({
171
217
  line: loc.line,
172
218
  column: loc.column,
173
219
  })),
174
- }
175
- : {}),
176
- })
220
+ }),
221
+ })
@@ -1,3 +1,4 @@
1
+ import { HashSet } from 'effect'
1
2
  import type { ExampleName, ExampleSelection } from './config.js'
2
3
  import { Example } from './schemas/example/$.js'
3
4
 
@@ -28,14 +29,14 @@ export const filterExamplesBySelection = (
28
29
 
29
30
  // Handle include pattern
30
31
  if ('include' in selection) {
31
- const includeSet = new Set<ExampleName>(selection.include)
32
- return examples.filter(example => includeSet.has(example.name as ExampleName))
32
+ const includeSet = HashSet.fromIterable(selection.include)
33
+ return examples.filter(example => HashSet.has(includeSet, example.name as ExampleName))
33
34
  }
34
35
 
35
36
  // Handle exclude pattern
36
37
  if ('exclude' in selection) {
37
- const excludeSet = new Set<ExampleName>(selection.exclude)
38
- return examples.filter(example => !excludeSet.has(example.name as ExampleName))
38
+ const excludeSet = HashSet.fromIterable(selection.exclude)
39
+ return examples.filter(example => !HashSet.has(excludeSet, example.name as ExampleName))
39
40
  }
40
41
 
41
42
  // Fallback to all examples if selection pattern is not recognized
@@ -69,12 +70,14 @@ export const shouldDisplayExample = (
69
70
 
70
71
  // Handle include pattern
71
72
  if ('include' in selection) {
72
- return selection.include.includes(exampleName)
73
+ const includeSet = HashSet.fromIterable(selection.include)
74
+ return HashSet.has(includeSet, exampleName)
73
75
  }
74
76
 
75
77
  // Handle exclude pattern
76
78
  if ('exclude' in selection) {
77
- return !selection.exclude.includes(exampleName)
79
+ const excludeSet = HashSet.fromIterable(selection.exclude)
80
+ return !HashSet.has(excludeSet, exampleName)
78
81
  }
79
82
 
80
83
  // Fallback to show if selection pattern is not recognized