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.
- package/build/api/builder/ssg/generate.d.ts.map +1 -1
- package/build/api/builder/ssg/generate.js +5 -5
- package/build/api/builder/ssg/generate.js.map +1 -1
- package/build/api/builder/ssg/page-generator.worker.js +13 -3
- package/build/api/builder/ssg/page-generator.worker.js.map +1 -1
- package/build/api/config/input.d.ts +88 -3
- package/build/api/config/input.d.ts.map +1 -1
- package/build/api/config/normalized.d.ts +92 -7
- package/build/api/config/normalized.d.ts.map +1 -1
- package/build/api/config/normalized.js +11 -3
- package/build/api/config/normalized.js.map +1 -1
- package/build/api/config-template/template.js +2 -2
- package/build/api/config-template/template.js.map +1 -1
- package/build/api/content/sidebar.d.ts.map +1 -1
- package/build/api/content/sidebar.js +2 -1
- package/build/api/content/sidebar.js.map +1 -1
- package/build/api/examples/config.d.ts +366 -3
- package/build/api/examples/config.d.ts.map +1 -1
- package/build/api/examples/config.js +25 -3
- package/build/api/examples/config.js.map +1 -1
- package/build/api/examples/diagnostic/diagnostic.d.ts +1 -1
- package/build/api/examples/diagnostic/validation-error.d.ts +3 -2
- package/build/api/examples/diagnostic/validation-error.d.ts.map +1 -1
- package/build/api/examples/diagnostic/validation-error.js +9 -3
- package/build/api/examples/diagnostic/validation-error.js.map +1 -1
- package/build/api/examples/diagnostic/validator.d.ts.map +1 -1
- package/build/api/examples/diagnostic/validator.js +115 -68
- package/build/api/examples/diagnostic/validator.js.map +1 -1
- package/build/api/examples/filter.d.ts.map +1 -1
- package/build/api/examples/filter.js +9 -6
- package/build/api/examples/filter.js.map +1 -1
- package/build/api/examples/scanner.d.ts.map +1 -1
- package/build/api/examples/scanner.js +89 -103
- package/build/api/examples/scanner.js.map +1 -1
- package/build/api/examples/type-usage-indexer.d.ts.map +1 -1
- package/build/api/examples/type-usage-indexer.js +17 -30
- package/build/api/examples/type-usage-indexer.js.map +1 -1
- package/build/api/iso/schema/routing.d.ts.map +1 -1
- package/build/api/iso/schema/routing.js +8 -8
- package/build/api/iso/schema/routing.js.map +1 -1
- package/build/api/iso/schema/validation.d.ts.map +1 -1
- package/build/api/iso/schema/validation.js +3 -2
- package/build/api/iso/schema/validation.js.map +1 -1
- package/build/api/schema/input-sources/directory.js +2 -2
- package/build/api/schema/input-sources/directory.js.map +1 -1
- package/build/api/schema/input-sources/versioned-directory.d.ts.map +1 -1
- package/build/api/schema/input-sources/versioned-directory.js +3 -3
- package/build/api/schema/input-sources/versioned-directory.js.map +1 -1
- package/build/api/schema/load.d.ts.map +1 -1
- package/build/api/schema/load.js +1 -1
- package/build/api/schema/load.js.map +1 -1
- package/build/lib/catalog/catalog.d.ts +43 -3
- package/build/lib/catalog/catalog.d.ts.map +1 -1
- package/build/lib/catalog/catalog.js +67 -5
- package/build/lib/catalog/catalog.js.map +1 -1
- package/build/lib/catalog/versioned.d.ts +11 -1
- package/build/lib/catalog/versioned.d.ts.map +1 -1
- package/build/lib/catalog/versioned.js +23 -5
- package/build/lib/catalog/versioned.js.map +1 -1
- package/build/lib/document/document.d.ts +55 -5
- package/build/lib/document/document.d.ts.map +1 -1
- package/build/lib/document/document.js +96 -2
- package/build/lib/document/document.js.map +1 -1
- package/build/lib/document/versioned.d.ts +2 -2
- package/build/lib/document/versioned.d.ts.map +1 -1
- package/build/lib/document/versioned.js +7 -7
- package/build/lib/document/versioned.js.map +1 -1
- package/build/lib/lifecycles/lifecycles.d.ts +5 -4
- package/build/lib/lifecycles/lifecycles.d.ts.map +1 -1
- package/build/lib/lifecycles/lifecycles.js +14 -12
- package/build/lib/lifecycles/lifecycles.js.map +1 -1
- package/build/lib/version-coverage/$$.d.ts +2 -0
- package/build/lib/version-coverage/$$.d.ts.map +1 -0
- package/build/lib/version-coverage/$$.js +2 -0
- package/build/lib/version-coverage/$$.js.map +1 -0
- package/build/lib/version-coverage/$.d.ts.map +1 -0
- package/build/lib/version-coverage/$.js.map +1 -0
- package/build/lib/{version-selection/version-selection.d.ts → version-coverage/version-coverage.d.ts} +1 -1
- package/build/lib/version-coverage/version-coverage.d.ts.map +1 -0
- package/build/lib/{version-selection/version-selection.js → version-coverage/version-coverage.js} +2 -2
- package/build/lib/version-coverage/version-coverage.js.map +1 -0
- package/build/template/components/GraphQLDocument.d.ts +1 -1
- package/build/template/components/GraphQLDocument.d.ts.map +1 -1
- package/build/template/components/GraphQLDocument.js +10 -39
- package/build/template/components/GraphQLDocument.js.map +1 -1
- package/build/template/components/GraphQLInteractive/lib/parser.d.ts +28 -0
- package/build/template/components/GraphQLInteractive/lib/parser.d.ts.map +1 -1
- package/build/template/components/GraphQLInteractive/lib/parser.js +60 -27
- package/build/template/components/GraphQLInteractive/lib/parser.js.map +1 -1
- package/build/template/components/VersionCoveragePicker.d.ts +1 -1
- package/build/template/components/VersionCoveragePicker.d.ts.map +1 -1
- package/build/template/components/VersionCoveragePicker.js +4 -6
- package/build/template/components/VersionCoveragePicker.js.map +1 -1
- package/build/template/components/home/QuickStart.d.ts.map +1 -1
- package/build/template/components/home/QuickStart.js +8 -4
- package/build/template/components/home/QuickStart.js.map +1 -1
- package/build/template/hooks/use-highlighted.d.ts.map +1 -1
- package/build/template/hooks/use-highlighted.js +19 -13
- package/build/template/hooks/use-highlighted.js.map +1 -1
- package/build/template/lib/fetch-text.d.ts +18 -0
- package/build/template/lib/fetch-text.d.ts.map +1 -1
- package/build/template/lib/fetch-text.js +32 -4
- package/build/template/lib/fetch-text.js.map +1 -1
- package/build/template/routes/examples/name.d.ts.map +1 -1
- package/build/template/routes/examples/name.js +4 -2
- package/build/template/routes/examples/name.js.map +1 -1
- package/build/template/stores/toast.d.ts.map +1 -1
- package/build/template/stores/toast.js +5 -3
- package/build/template/stores/toast.js.map +1 -1
- package/package.json +7 -7
- package/src/api/builder/ssg/generate.ts +10 -5
- package/src/api/builder/ssg/page-generator.worker.ts +18 -3
- package/src/api/config/normalized.ts +12 -3
- package/src/api/config-template/template.ts +2 -2
- package/src/api/content/sidebar.ts +3 -3
- package/src/api/examples/config.test.ts +10 -0
- package/src/api/examples/config.ts +33 -4
- package/src/api/examples/diagnostic/validation-error.ts +9 -3
- package/src/api/examples/diagnostic/validator.test.ts +30 -0
- package/src/api/examples/diagnostic/validator.ts +148 -103
- package/src/api/examples/filter.ts +9 -6
- package/src/api/examples/scanner.ts +136 -117
- package/src/api/examples/type-usage-indexer.ts +24 -36
- package/src/api/iso/schema/routing.ts +10 -10
- package/src/api/iso/schema/validation.ts +3 -2
- package/src/api/schema/input-sources/directory.ts +2 -2
- package/src/api/schema/input-sources/versioned-directory.ts +5 -7
- package/src/api/schema/load.ts +1 -1
- package/src/lib/catalog/catalog.ts +89 -6
- package/src/lib/catalog/versioned.ts +26 -5
- package/src/lib/document/document.ts +135 -2
- package/src/lib/document/versioned.ts +8 -8
- package/src/lib/lifecycles/lifecycles.ts +32 -27
- package/src/lib/version-coverage/$$.ts +1 -0
- package/src/lib/{version-selection/version-selection.ts → version-coverage/version-coverage.ts} +1 -1
- package/src/template/components/GraphQLDocument.tsx +11 -69
- package/src/template/components/GraphQLInteractive/lib/parser.ts +81 -29
- package/src/template/components/VersionCoveragePicker.tsx +4 -5
- package/src/template/components/home/QuickStart.tsx +16 -7
- package/src/template/hooks/use-highlighted.ts +31 -19
- package/src/template/lib/fetch-text.ts +45 -4
- package/src/template/routes/examples/name.tsx +4 -2
- package/src/template/stores/toast.ts +6 -3
- package/build/lib/graph/$$.d.ts +0 -2
- package/build/lib/graph/$$.d.ts.map +0 -1
- package/build/lib/graph/$$.js +0 -2
- package/build/lib/graph/$$.js.map +0 -1
- package/build/lib/graph/$.d.ts +0 -2
- package/build/lib/graph/$.d.ts.map +0 -1
- package/build/lib/graph/$.js +0 -2
- package/build/lib/graph/$.js.map +0 -1
- package/build/lib/graph/graph.d.ts +0 -127
- package/build/lib/graph/graph.d.ts.map +0 -1
- package/build/lib/graph/graph.js +0 -152
- package/build/lib/graph/graph.js.map +0 -1
- package/build/lib/mask/$$.d.ts +0 -3
- package/build/lib/mask/$$.d.ts.map +0 -1
- package/build/lib/mask/$$.js +0 -3
- package/build/lib/mask/$$.js.map +0 -1
- package/build/lib/mask/$.d.ts +0 -2
- package/build/lib/mask/$.d.ts.map +0 -1
- package/build/lib/mask/$.js +0 -2
- package/build/lib/mask/$.js.map +0 -1
- package/build/lib/mask/apply.d.ts +0 -86
- package/build/lib/mask/apply.d.ts.map +0 -1
- package/build/lib/mask/apply.js +0 -86
- package/build/lib/mask/apply.js.map +0 -1
- package/build/lib/mask/mask.d.ts +0 -124
- package/build/lib/mask/mask.d.ts.map +0 -1
- package/build/lib/mask/mask.js +0 -137
- package/build/lib/mask/mask.js.map +0 -1
- package/build/lib/mask/mask.test-d.d.ts +0 -2
- package/build/lib/mask/mask.test-d.d.ts.map +0 -1
- package/build/lib/mask/mask.test-d.js +0 -102
- package/build/lib/mask/mask.test-d.js.map +0 -1
- package/build/lib/version-selection/$$.d.ts +0 -2
- package/build/lib/version-selection/$$.d.ts.map +0 -1
- package/build/lib/version-selection/$$.js +0 -2
- package/build/lib/version-selection/$$.js.map +0 -1
- package/build/lib/version-selection/$.d.ts.map +0 -1
- package/build/lib/version-selection/$.js.map +0 -1
- package/build/lib/version-selection/version-selection.d.ts.map +0 -1
- package/build/lib/version-selection/version-selection.js.map +0 -1
- package/src/lib/graph/$$.ts +0 -1
- package/src/lib/graph/$.ts +0 -1
- package/src/lib/graph/graph.ts +0 -197
- package/src/lib/mask/$$.ts +0 -2
- package/src/lib/mask/$.test.ts +0 -226
- package/src/lib/mask/$.ts +0 -1
- package/src/lib/mask/apply.ts +0 -134
- package/src/lib/mask/mask.test-d.ts +0 -156
- package/src/lib/mask/mask.ts +0 -244
- package/src/lib/version-selection/$$.ts +0 -1
- /package/build/lib/{version-selection → version-coverage}/$.d.ts +0 -0
- /package/build/lib/{version-selection → version-coverage}/$.js +0 -0
- /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(
|
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 {
|
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
|
-
|
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 =
|
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
|
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.
|
15
|
-
errors: S.Array(
|
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 {
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
128
|
+
},
|
129
|
+
})
|
130
|
+
}
|
131
|
+
})
|
93
132
|
|
94
|
-
|
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
|
-
|
109
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
}
|
131
|
-
|
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:
|
134
|
-
example: { name:
|
171
|
+
message: formatValidationMessage(example.name, version, errors),
|
172
|
+
example: { name: example.name, path: example.path },
|
135
173
|
version,
|
136
|
-
errors:
|
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:
|
147
|
-
errors:
|
195
|
+
version: Version.Version | undefined,
|
196
|
+
errors: Array.NonEmptyReadonlyArray<GraphQLError>,
|
148
197
|
): string => {
|
149
|
-
const versionStr = 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
|
155
|
-
const preview =
|
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
|
-
|
165
|
-
|
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 =
|
32
|
-
return examples.filter(example =>
|
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 =
|
38
|
-
return examples.filter(example => !
|
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
|
-
|
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
|
-
|
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
|