polen 0.11.0-next.24 → 0.11.0-next.25
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/config/input.d.ts +76 -77
- package/build/api/config/input.d.ts.map +1 -1
- package/build/api/config/normalized.d.ts +136 -143
- package/build/api/config/normalized.d.ts.map +1 -1
- package/build/api/config-template/template.d.ts +110 -121
- package/build/api/config-template/template.d.ts.map +1 -1
- package/build/api/examples/diagnostic/diagnostic.d.ts +0 -23
- package/build/api/examples/diagnostic/diagnostic.d.ts.map +1 -1
- package/build/api/examples/diagnostic/diagnostic.js +1 -3
- package/build/api/examples/diagnostic/diagnostic.js.map +1 -1
- package/build/api/examples/scanner.d.ts +27 -1
- package/build/api/examples/scanner.d.ts.map +1 -1
- package/build/api/examples/scanner.js +19 -17
- package/build/api/examples/scanner.js.map +1 -1
- package/build/api/examples/schemas/catalog.d.ts +20 -12
- package/build/api/examples/schemas/catalog.d.ts.map +1 -1
- package/build/api/examples/schemas/example/example.d.ts +17 -11
- package/build/api/examples/schemas/example/example.d.ts.map +1 -1
- package/build/api/schema/augmentations/$$.d.ts +7 -0
- package/build/api/schema/augmentations/$$.d.ts.map +1 -0
- package/build/api/schema/augmentations/$$.js +7 -0
- package/build/api/schema/augmentations/$$.js.map +1 -0
- package/build/api/schema/augmentations/$.d.ts +1 -1
- package/build/api/schema/augmentations/$.d.ts.map +1 -1
- package/build/api/schema/augmentations/$.js +1 -1
- package/build/api/schema/augmentations/$.js.map +1 -1
- package/build/api/schema/augmentations/apply.d.ts +29 -0
- package/build/api/schema/augmentations/apply.d.ts.map +1 -0
- package/build/api/schema/augmentations/apply.js +181 -0
- package/build/api/schema/augmentations/apply.js.map +1 -0
- package/build/api/schema/augmentations/augmentation.d.ts +31 -0
- package/build/api/schema/augmentations/augmentation.d.ts.map +1 -0
- package/build/api/schema/augmentations/augmentation.js +22 -0
- package/build/api/schema/augmentations/augmentation.js.map +1 -0
- package/build/api/schema/augmentations/config.d.ts +24 -0
- package/build/api/schema/augmentations/config.d.ts.map +1 -0
- package/build/api/schema/augmentations/config.js +9 -0
- package/build/api/schema/augmentations/config.js.map +1 -0
- package/build/api/schema/augmentations/diagnostics/diagnostic.d.ts +67 -0
- package/build/api/schema/augmentations/diagnostics/diagnostic.d.ts.map +1 -0
- package/build/api/schema/augmentations/diagnostics/diagnostic.js +13 -0
- package/build/api/schema/augmentations/diagnostics/diagnostic.js.map +1 -0
- package/build/api/schema/augmentations/diagnostics/duplicate-version.d.ts +45 -0
- package/build/api/schema/augmentations/diagnostics/duplicate-version.d.ts.map +1 -0
- package/build/api/schema/augmentations/diagnostics/duplicate-version.js +17 -0
- package/build/api/schema/augmentations/diagnostics/duplicate-version.js.map +1 -0
- package/build/api/schema/augmentations/diagnostics/invalid-path.d.ts +46 -0
- package/build/api/schema/augmentations/diagnostics/invalid-path.d.ts.map +1 -0
- package/build/api/schema/augmentations/diagnostics/invalid-path.js +18 -0
- package/build/api/schema/augmentations/diagnostics/invalid-path.js.map +1 -0
- package/build/api/schema/augmentations/diagnostics/version-mismatch.d.ts +46 -0
- package/build/api/schema/augmentations/diagnostics/version-mismatch.d.ts.map +1 -0
- package/build/api/schema/augmentations/diagnostics/version-mismatch.js +18 -0
- package/build/api/schema/augmentations/diagnostics/version-mismatch.js.map +1 -0
- package/build/api/schema/augmentations/input.d.ts +145 -0
- package/build/api/schema/augmentations/input.d.ts.map +1 -0
- package/build/api/schema/augmentations/input.js +191 -0
- package/build/api/schema/augmentations/input.js.map +1 -0
- package/build/api/schema/augmentations/placement.d.ts +8 -0
- package/build/api/schema/augmentations/placement.d.ts.map +1 -0
- package/build/api/schema/augmentations/placement.js +7 -0
- package/build/api/schema/augmentations/placement.js.map +1 -0
- package/build/api/schema/config-schema.d.ts +66 -66
- package/build/api/schema/config-schema.d.ts.map +1 -1
- package/build/api/schema/config-schema.js +2 -2
- package/build/api/schema/config-schema.js.map +1 -1
- package/build/api/schema/input-source/load.d.ts +2 -0
- package/build/api/schema/input-source/load.d.ts.map +1 -1
- package/build/api/schema/input-source/load.js.map +1 -1
- package/build/api/schema/input-sources/directory.d.ts +39 -39
- package/build/api/schema/input-sources/file.d.ts +39 -39
- package/build/api/schema/input-sources/introspection-file.d.ts +39 -39
- package/build/api/schema/input-sources/introspection.d.ts +39 -39
- package/build/api/schema/input-sources/memory.d.ts +39 -39
- package/build/api/schema/input-sources/versioned-directory.d.ts +79 -79
- package/build/api/schema/load.d.ts.map +1 -1
- package/build/api/schema/load.js +9 -2
- package/build/api/schema/load.js.map +1 -1
- package/build/lib/catalog/catalog.d.ts +1181 -1181
- package/build/lib/catalog/unversioned.d.ts +312 -312
- package/build/lib/catalog/versioned.d.ts +634 -634
- package/build/lib/change/change.d.ts +238 -238
- package/build/lib/document/document.d.ts +14 -8
- package/build/lib/document/document.d.ts.map +1 -1
- package/build/lib/document/versioned.d.ts +17 -10
- package/build/lib/document/versioned.d.ts.map +1 -1
- package/build/lib/grafaid/schema/KindMap/_.d.ts +1 -1
- package/build/lib/graphql-path/$$.d.ts +7 -13
- package/build/lib/graphql-path/$$.d.ts.map +1 -1
- package/build/lib/graphql-path/$$.js +7 -13
- package/build/lib/graphql-path/$$.js.map +1 -1
- package/build/lib/graphql-path/definition.d.ts +104 -94
- package/build/lib/graphql-path/definition.d.ts.map +1 -1
- package/build/lib/graphql-path/definition.js +126 -125
- package/build/lib/graphql-path/definition.js.map +1 -1
- package/build/lib/graphql-path/query.d.ts +25 -57
- package/build/lib/graphql-path/query.d.ts.map +1 -1
- package/build/lib/graphql-path/query.js +15 -93
- package/build/lib/graphql-path/query.js.map +1 -1
- package/build/lib/graphql-path/schema.d.ts +49 -0
- package/build/lib/graphql-path/schema.d.ts.map +1 -0
- package/build/lib/graphql-path/schema.js +89 -0
- package/build/lib/graphql-path/schema.js.map +1 -0
- package/build/lib/graphql-path/types.d.ts +76 -28
- package/build/lib/graphql-path/types.d.ts.map +1 -1
- package/build/lib/graphql-path/types.js +101 -2
- package/build/lib/graphql-path/types.js.map +1 -1
- package/build/lib/revision/revision.d.ts +1170 -1170
- package/build/lib/schema/schema.d.ts +708 -708
- package/build/lib/schema/unversioned.d.ts +1092 -1092
- package/build/lib/schema/versioned.d.ts +634 -634
- package/build/lib/semver/official-release.d.ts +10 -10
- package/build/lib/semver/pre-release.d.ts +10 -10
- package/build/lib/semver/semver.d.ts +50 -50
- package/build/lib/version-coverage/version-coverage.d.ts +43 -11
- package/build/lib/version-coverage/version-coverage.d.ts.map +1 -1
- package/build/lib/version-coverage/version-coverage.js +40 -6
- package/build/lib/version-coverage/version-coverage.js.map +1 -1
- package/build/template/hooks/use-examples.d.ts +1 -1
- package/build/template/routes/changelog/ChangelogSidebar.d.ts +2 -2
- package/build/template/routes/changelog/ChangelogSidebar.d.ts.map +1 -1
- package/build/template/routes/changelog/ChangelogSidebar.js +4 -4
- package/build/template/routes/changelog/ChangelogSidebar.js.map +1 -1
- package/build/template/routes/examples/_.d.ts +3 -3
- package/build/template/routes/examples/_index.d.ts +2 -2
- package/build/template/routes/examples/name.d.ts +3 -3
- package/build/template/stores/changelog.d.ts +39 -39
- package/build/vite/plugins/examples.d.ts.map +1 -1
- package/build/vite/plugins/examples.js +0 -2
- package/build/vite/plugins/examples.js.map +1 -1
- package/build/vite/plugins/schemas.d.ts.map +1 -1
- package/build/vite/plugins/schemas.js +38 -2
- package/build/vite/plugins/schemas.js.map +1 -1
- package/package.json +1 -1
- package/src/api/examples/diagnostic/diagnostic.ts +0 -3
- package/src/api/examples/scanner.test.ts +83 -0
- package/src/api/examples/scanner.ts +17 -21
- package/src/api/schema/augmentations/$$.ts +6 -0
- package/src/api/schema/augmentations/$.ts +1 -1
- package/src/api/schema/augmentations/apply.test.ts +89 -0
- package/src/api/schema/augmentations/apply.ts +277 -0
- package/src/api/schema/augmentations/augmentation.ts +24 -0
- package/src/api/schema/augmentations/config.ts +11 -0
- package/src/api/schema/augmentations/diagnostics/diagnostic.ts +20 -0
- package/src/api/schema/augmentations/diagnostics/duplicate-version.ts +20 -0
- package/src/api/schema/augmentations/diagnostics/invalid-path.ts +21 -0
- package/src/api/schema/augmentations/diagnostics/version-mismatch.ts +21 -0
- package/src/api/schema/augmentations/input.test.ts +144 -0
- package/src/api/schema/augmentations/input.ts +215 -0
- package/src/api/schema/augmentations/placement.ts +11 -0
- package/src/api/schema/config-schema.ts +2 -2
- package/src/api/schema/input-source/load.ts +2 -0
- package/src/api/schema/load.ts +19 -2
- package/src/lib/graphql-path/$$.ts +7 -13
- package/src/lib/graphql-path/$.test.ts +175 -0
- package/src/lib/graphql-path/definition.ts +162 -162
- package/src/lib/graphql-path/query.ts +15 -98
- package/src/lib/graphql-path/schema.ts +136 -0
- package/src/lib/graphql-path/types.ts +108 -28
- package/src/lib/version-coverage/version-coverage.ts +48 -6
- package/src/template/routes/changelog/ChangelogSidebar.tsx +4 -4
- package/src/vite/plugins/examples.ts +0 -2
- package/src/vite/plugins/schemas.ts +51 -2
- package/build/api/examples/diagnostic/unused-default.d.ts +0 -49
- package/build/api/examples/diagnostic/unused-default.d.ts.map +0 -1
- package/build/api/examples/diagnostic/unused-default.js +0 -19
- package/build/api/examples/diagnostic/unused-default.js.map +0 -1
- package/build/api/schema/augmentations/augmentations/description.d.ts +0 -26
- package/build/api/schema/augmentations/augmentations/description.d.ts.map +0 -1
- package/build/api/schema/augmentations/augmentations/description.js +0 -55
- package/build/api/schema/augmentations/augmentations/description.js.map +0 -1
- package/build/api/schema/augmentations/schema-augmentation.d.ts +0 -20
- package/build/api/schema/augmentations/schema-augmentation.d.ts.map +0 -1
- package/build/api/schema/augmentations/schema-augmentation.js +0 -22
- package/build/api/schema/augmentations/schema-augmentation.js.map +0 -1
- package/build/api/schema/augmentations/target.d.ts +0 -25
- package/build/api/schema/augmentations/target.d.ts.map +0 -1
- package/build/api/schema/augmentations/target.js +0 -39
- package/build/api/schema/augmentations/target.js.map +0 -1
- package/build/lib/graphql-path/constructors.d.ts +0 -57
- package/build/lib/graphql-path/constructors.d.ts.map +0 -1
- package/build/lib/graphql-path/constructors.js +0 -73
- package/build/lib/graphql-path/constructors.js.map +0 -1
- package/src/api/examples/diagnostic/unused-default.ts +0 -22
- package/src/api/schema/augmentations/augmentations/description.ts +0 -69
- package/src/api/schema/augmentations/schema-augmentation.ts +0 -32
- package/src/api/schema/augmentations/target.ts +0 -61
- package/src/lib/graphql-path/constructors.ts +0 -81
@@ -1 +1 @@
|
|
1
|
-
export * as Augmentations from '
|
1
|
+
export * as Augmentations from './$$.js'
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import type { GrafaidOld } from '#lib/grafaid-old'
|
2
|
+
import { describe, expect, test } from 'vitest'
|
3
|
+
import { mutateDescription } from './apply.js'
|
4
|
+
import type { AugmentationConfig } from './config.js'
|
5
|
+
|
6
|
+
describe('mutateDescription', () => {
|
7
|
+
describe('with empty existing description', () => {
|
8
|
+
test.for([
|
9
|
+
{ placement: 'before' as const, expected: 'New content' },
|
10
|
+
{ placement: 'after' as const, expected: 'New content' },
|
11
|
+
{ placement: 'over' as const, expected: 'New content' },
|
12
|
+
])('$placement placement does not add extra newlines', ({ placement, expected }) => {
|
13
|
+
const type = { description: undefined } as GrafaidOld.Groups.Describable
|
14
|
+
const augmentation: AugmentationConfig = {
|
15
|
+
on: {} as any, // Not used by mutateDescription
|
16
|
+
content: 'New content',
|
17
|
+
placement,
|
18
|
+
}
|
19
|
+
|
20
|
+
mutateDescription(type, augmentation)
|
21
|
+
|
22
|
+
expect(type.description).toBe(expected)
|
23
|
+
})
|
24
|
+
|
25
|
+
test.for([
|
26
|
+
{ placement: 'before' as const, expected: 'New content' },
|
27
|
+
{ placement: 'after' as const, expected: 'New content' },
|
28
|
+
{ placement: 'over' as const, expected: 'New content' },
|
29
|
+
])('$placement placement handles empty string description', ({ placement, expected }) => {
|
30
|
+
const type = { description: '' } as GrafaidOld.Groups.Describable
|
31
|
+
const augmentation: AugmentationConfig = {
|
32
|
+
on: {} as any,
|
33
|
+
content: 'New content',
|
34
|
+
placement,
|
35
|
+
}
|
36
|
+
|
37
|
+
mutateDescription(type, augmentation)
|
38
|
+
|
39
|
+
expect(type.description).toBe(expected)
|
40
|
+
})
|
41
|
+
})
|
42
|
+
|
43
|
+
describe('with existing description', () => {
|
44
|
+
test.for([
|
45
|
+
{ placement: 'before' as const, expected: 'New content\n\nExisting description' },
|
46
|
+
{ placement: 'after' as const, expected: 'Existing description\n\nNew content' },
|
47
|
+
{ placement: 'over' as const, expected: 'New content' },
|
48
|
+
])('$placement placement correctly combines content', ({ placement, expected }) => {
|
49
|
+
const type = { description: 'Existing description' } as GrafaidOld.Groups.Describable
|
50
|
+
const augmentation: AugmentationConfig = {
|
51
|
+
on: {} as any,
|
52
|
+
content: 'New content',
|
53
|
+
placement,
|
54
|
+
}
|
55
|
+
|
56
|
+
mutateDescription(type, augmentation)
|
57
|
+
|
58
|
+
expect(type.description).toBe(expected)
|
59
|
+
})
|
60
|
+
})
|
61
|
+
|
62
|
+
describe('edge cases', () => {
|
63
|
+
test('handles whitespace-only existing description as empty', () => {
|
64
|
+
const type = { description: ' \n \t ' } as GrafaidOld.Groups.Describable
|
65
|
+
const augmentation: AugmentationConfig = {
|
66
|
+
on: {} as any,
|
67
|
+
content: 'New content',
|
68
|
+
placement: 'before',
|
69
|
+
}
|
70
|
+
|
71
|
+
mutateDescription(type, augmentation)
|
72
|
+
|
73
|
+
expect(type.description).toBe('New content')
|
74
|
+
})
|
75
|
+
|
76
|
+
test('preserves meaningful whitespace in existing description', () => {
|
77
|
+
const type = { description: ' Indented content ' } as GrafaidOld.Groups.Describable
|
78
|
+
const augmentation: AugmentationConfig = {
|
79
|
+
on: {} as any,
|
80
|
+
content: 'New content',
|
81
|
+
placement: 'before',
|
82
|
+
}
|
83
|
+
|
84
|
+
mutateDescription(type, augmentation)
|
85
|
+
|
86
|
+
expect(type.description).toBe('New content\n\nIndented content')
|
87
|
+
})
|
88
|
+
})
|
89
|
+
})
|
@@ -0,0 +1,277 @@
|
|
1
|
+
import type { Augmentation } from '#api/schema/augmentations/augmentation'
|
2
|
+
import type { AugmentationConfig } from '#api/schema/augmentations/config'
|
3
|
+
import {
|
4
|
+
Diagnostic,
|
5
|
+
makeDiagnosticDuplicateVersion,
|
6
|
+
makeDiagnosticInvalidPath,
|
7
|
+
makeDiagnosticVersionMismatch,
|
8
|
+
} from '#api/schema/augmentations/diagnostics/diagnostic'
|
9
|
+
import type { AugmentationInput } from '#api/schema/augmentations/input'
|
10
|
+
import { normalizeAugmentationInput } from '#api/schema/augmentations/input'
|
11
|
+
import type { GrafaidOld } from '#lib/grafaid-old'
|
12
|
+
import { GraphQLPath } from '#lib/graphql-path'
|
13
|
+
import { VersionCoverage } from '#lib/version-coverage'
|
14
|
+
import { Version } from '#lib/version/$'
|
15
|
+
import { Either, HashMap, Match, Option, pipe } from 'effect'
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Apply version-aware augmentations to a schema.
|
19
|
+
*
|
20
|
+
* @param schema - The GraphQL schema to augment (mutated in place)
|
21
|
+
* @param augmentations - The input augmentations (may include version specifications)
|
22
|
+
* @param version - The specific version to apply augmentations for (null for unversioned)
|
23
|
+
* @returns Diagnostics generated during augmentation application
|
24
|
+
*/
|
25
|
+
export const applyAll = (
|
26
|
+
schema: GrafaidOld.Schema.Schema,
|
27
|
+
augmentations: readonly AugmentationInput[],
|
28
|
+
version: Version.Version | null = null,
|
29
|
+
): { diagnostics: Diagnostic[] } => {
|
30
|
+
const diagnostics: Diagnostic[] = []
|
31
|
+
|
32
|
+
// Track versions to detect duplicates
|
33
|
+
const seenVersions = new Map<string, AugmentationInput>()
|
34
|
+
|
35
|
+
for (const augmentationInput of augmentations) {
|
36
|
+
// Check for duplicate versions
|
37
|
+
const versionKey = augmentationInput.versions
|
38
|
+
? Object.keys(augmentationInput.versions).sort().join(',')
|
39
|
+
: 'unversioned'
|
40
|
+
|
41
|
+
if (seenVersions.has(versionKey) && versionKey !== 'unversioned') {
|
42
|
+
const firstInput = seenVersions.get(versionKey)!
|
43
|
+
diagnostics.push(makeDiagnosticDuplicateVersion({
|
44
|
+
message: `Duplicate version '${versionKey}' found in augmentation configuration`,
|
45
|
+
version: versionKey,
|
46
|
+
firstPath: firstInput.on ?? 'unknown',
|
47
|
+
duplicatePath: augmentationInput.on ?? 'unknown',
|
48
|
+
}))
|
49
|
+
continue
|
50
|
+
}
|
51
|
+
seenVersions.set(versionKey, augmentationInput)
|
52
|
+
|
53
|
+
// Transform input to normalized format
|
54
|
+
const normalized = normalizeAugmentationInput(augmentationInput)
|
55
|
+
|
56
|
+
if (!normalized) {
|
57
|
+
console.warn('Skipping invalid augmentation configuration')
|
58
|
+
continue
|
59
|
+
}
|
60
|
+
|
61
|
+
const applyDiagnostics = applyVersioned(schema, normalized, version)
|
62
|
+
diagnostics.push(...applyDiagnostics)
|
63
|
+
}
|
64
|
+
|
65
|
+
return { diagnostics }
|
66
|
+
}
|
67
|
+
|
68
|
+
export const apply = (
|
69
|
+
schema: GrafaidOld.Schema.Schema,
|
70
|
+
augmentation: AugmentationConfig,
|
71
|
+
): Diagnostic[] => {
|
72
|
+
const diagnostics: Diagnostic[] = []
|
73
|
+
|
74
|
+
pipe(
|
75
|
+
augmentation.on,
|
76
|
+
Match.value,
|
77
|
+
Match.when(
|
78
|
+
GraphQLPath.Definition.isTypeDefinitionPath,
|
79
|
+
(path) => {
|
80
|
+
pipe(
|
81
|
+
GraphQLPath.Schema.locateType(schema, path),
|
82
|
+
Either.match({
|
83
|
+
onLeft: (error) => {
|
84
|
+
diagnostics.push(makeDiagnosticInvalidPath({
|
85
|
+
message: `Type '${error.typeName}' not found in schema`,
|
86
|
+
path: error.path,
|
87
|
+
version: null,
|
88
|
+
error: `Type '${error.typeName}' not found`,
|
89
|
+
}))
|
90
|
+
},
|
91
|
+
onRight: (type) => {
|
92
|
+
mutateDescription(type, augmentation)
|
93
|
+
},
|
94
|
+
}),
|
95
|
+
)
|
96
|
+
},
|
97
|
+
),
|
98
|
+
Match.when(
|
99
|
+
GraphQLPath.Definition.isFieldDefinitionPath,
|
100
|
+
(path) => {
|
101
|
+
pipe(
|
102
|
+
GraphQLPath.Schema.locateField(schema, path),
|
103
|
+
Either.match({
|
104
|
+
onLeft: (error) => {
|
105
|
+
diagnostics.push(makeDiagnosticInvalidPath({
|
106
|
+
message: `Field '${error.fieldName}' not found on type '${error.typeName}'`,
|
107
|
+
path: error.path,
|
108
|
+
version: null,
|
109
|
+
error: `Field '${error.fieldName}' not found on type '${error.typeName}'`,
|
110
|
+
}))
|
111
|
+
},
|
112
|
+
onRight: (field) => {
|
113
|
+
mutateDescription(field, augmentation)
|
114
|
+
},
|
115
|
+
}),
|
116
|
+
)
|
117
|
+
},
|
118
|
+
),
|
119
|
+
Match.orElse(() => {
|
120
|
+
diagnostics.push(makeDiagnosticInvalidPath({
|
121
|
+
message: `Unsupported path type for augmentations: ${GraphQLPath.Definition.encodeSync(augmentation.on)}`,
|
122
|
+
path: GraphQLPath.Definition.encodeSync(augmentation.on),
|
123
|
+
version: null,
|
124
|
+
error: 'Unsupported path type for augmentations',
|
125
|
+
}))
|
126
|
+
}),
|
127
|
+
)
|
128
|
+
|
129
|
+
return diagnostics
|
130
|
+
}
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Apply a version-aware augmentation to a schema.
|
134
|
+
*
|
135
|
+
* @param schema - The GraphQL schema to augment
|
136
|
+
* @param augmentation - The normalized augmentation with version coverage
|
137
|
+
* @param version - The specific version to apply augmentations for (null for unversioned)
|
138
|
+
* @returns Diagnostics generated during augmentation application
|
139
|
+
*/
|
140
|
+
export const applyVersioned = (
|
141
|
+
schema: GrafaidOld.Schema.Schema,
|
142
|
+
augmentation: Augmentation,
|
143
|
+
version: Version.Version | null,
|
144
|
+
): Diagnostic[] => {
|
145
|
+
const diagnostics: Diagnostic[] = []
|
146
|
+
// Try to find augmentation for this specific version
|
147
|
+
let config: AugmentationConfig | undefined
|
148
|
+
|
149
|
+
if (version) {
|
150
|
+
// First try exact version match
|
151
|
+
const versionCoverage = VersionCoverage.single(version)
|
152
|
+
const maybeConfig = HashMap.get(augmentation.versionAugmentations, versionCoverage)
|
153
|
+
if (Option.isSome(maybeConfig)) {
|
154
|
+
config = maybeConfig.value
|
155
|
+
}
|
156
|
+
|
157
|
+
// If not found, check if any coverage matches this version
|
158
|
+
if (!config) {
|
159
|
+
for (const [coverage, cfg] of augmentation.versionAugmentations) {
|
160
|
+
if (VersionCoverage.matches(coverage, version)) {
|
161
|
+
config = cfg
|
162
|
+
break
|
163
|
+
}
|
164
|
+
}
|
165
|
+
}
|
166
|
+
} else {
|
167
|
+
// For unversioned schemas, only apply unversioned augmentations
|
168
|
+
const unversionedCoverage = VersionCoverage.unversioned()
|
169
|
+
const maybeConfig = HashMap.get(augmentation.versionAugmentations, unversionedCoverage)
|
170
|
+
if (Option.isSome(maybeConfig)) {
|
171
|
+
config = maybeConfig.value
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
// Apply the augmentation if found
|
176
|
+
if (config) {
|
177
|
+
pipe(
|
178
|
+
config.on,
|
179
|
+
Match.value,
|
180
|
+
Match.when(
|
181
|
+
GraphQLPath.Definition.isTypeDefinitionPath,
|
182
|
+
(path) => {
|
183
|
+
pipe(
|
184
|
+
GraphQLPath.Schema.locateType(schema, path),
|
185
|
+
Either.match({
|
186
|
+
onLeft: (error) => {
|
187
|
+
diagnostics.push(makeDiagnosticInvalidPath({
|
188
|
+
message: `Type '${error.typeName}' not found in schema`,
|
189
|
+
path: error.path,
|
190
|
+
version: version,
|
191
|
+
error: `Type '${error.typeName}' not found`,
|
192
|
+
}))
|
193
|
+
},
|
194
|
+
onRight: (type) => {
|
195
|
+
mutateDescription(type, config)
|
196
|
+
},
|
197
|
+
}),
|
198
|
+
)
|
199
|
+
},
|
200
|
+
),
|
201
|
+
Match.when(
|
202
|
+
GraphQLPath.Definition.isFieldDefinitionPath,
|
203
|
+
(path) => {
|
204
|
+
pipe(
|
205
|
+
GraphQLPath.Schema.locateField(schema, path),
|
206
|
+
Either.match({
|
207
|
+
onLeft: (error) => {
|
208
|
+
diagnostics.push(makeDiagnosticInvalidPath({
|
209
|
+
message: `Field '${error.fieldName}' not found on type '${error.typeName}'`,
|
210
|
+
path: error.path,
|
211
|
+
version: version,
|
212
|
+
error: `Field '${error.fieldName}' not found on type '${error.typeName}'`,
|
213
|
+
}))
|
214
|
+
},
|
215
|
+
onRight: (field) => {
|
216
|
+
mutateDescription(field, config)
|
217
|
+
},
|
218
|
+
}),
|
219
|
+
)
|
220
|
+
},
|
221
|
+
),
|
222
|
+
Match.orElse(() => {
|
223
|
+
diagnostics.push(makeDiagnosticInvalidPath({
|
224
|
+
message: `Unsupported path type for augmentations: ${GraphQLPath.Definition.encodeSync(config.on)}`,
|
225
|
+
path: GraphQLPath.Definition.encodeSync(config.on),
|
226
|
+
version: version,
|
227
|
+
error: 'Unsupported path type for augmentations',
|
228
|
+
}))
|
229
|
+
}),
|
230
|
+
)
|
231
|
+
} else if (version && HashMap.size(augmentation.versionAugmentations) > 0) {
|
232
|
+
// No matching version coverage found - generate version mismatch diagnostic
|
233
|
+
// Get first config from HashMap
|
234
|
+
const firstEntry = pipe(
|
235
|
+
augmentation.versionAugmentations,
|
236
|
+
HashMap.values,
|
237
|
+
(iter) => Array.from(iter)[0],
|
238
|
+
)
|
239
|
+
|
240
|
+
if (firstEntry) {
|
241
|
+
diagnostics.push(makeDiagnosticVersionMismatch({
|
242
|
+
message: `No augmentation found for version '${version}' on path '${
|
243
|
+
GraphQLPath.Definition.encodeSync(firstEntry.on)
|
244
|
+
}'`,
|
245
|
+
path: GraphQLPath.Definition.encodeSync(firstEntry.on),
|
246
|
+
requestedVersion: version,
|
247
|
+
availableVersions: pipe(
|
248
|
+
augmentation.versionAugmentations,
|
249
|
+
HashMap.keys,
|
250
|
+
(iter) => Array.from(iter).map(VersionCoverage.toLabel),
|
251
|
+
),
|
252
|
+
}))
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
return diagnostics
|
257
|
+
}
|
258
|
+
|
259
|
+
export const mutateDescription = (
|
260
|
+
type: GrafaidOld.Groups.Describable,
|
261
|
+
augmentation: AugmentationConfig,
|
262
|
+
) => {
|
263
|
+
const existingDescription = type.description?.trim() ?? ''
|
264
|
+
|
265
|
+
type.description = Match.value(augmentation.placement).pipe(
|
266
|
+
Match.when(
|
267
|
+
'before',
|
268
|
+
() => existingDescription ? `${augmentation.content}\n\n${existingDescription}` : augmentation.content,
|
269
|
+
),
|
270
|
+
Match.when(
|
271
|
+
'after',
|
272
|
+
() => existingDescription ? `${existingDescription}\n\n${augmentation.content}` : augmentation.content,
|
273
|
+
),
|
274
|
+
Match.when('over', () => augmentation.content),
|
275
|
+
Match.exhaustive,
|
276
|
+
)
|
277
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { AugmentationConfig } from '#api/schema/augmentations/config'
|
2
|
+
import { S } from '#lib/kit-temp/effect'
|
3
|
+
import { VersionCoverage } from '#lib/version-coverage'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Internal normalized schema for description augmentations.
|
7
|
+
* Uses VersionCoverage as keys to support unversioned (all versions) and version-specific augmentations.
|
8
|
+
*/
|
9
|
+
export const Augmentation = S.Struct({
|
10
|
+
// Map from VersionCoverage to augmentation config
|
11
|
+
// Key can be:
|
12
|
+
// - VersionCoverageUnversioned: applies to all versions
|
13
|
+
// - VersionCoverageOne: applies to a specific version
|
14
|
+
// - VersionCoverageSet: applies to a set of versions (future enhancement)
|
15
|
+
versionAugmentations: S.HashMap({
|
16
|
+
key: VersionCoverage.VersionCoverage,
|
17
|
+
value: AugmentationConfig,
|
18
|
+
}),
|
19
|
+
}).annotations({
|
20
|
+
identifier: 'AugmentationNormalized',
|
21
|
+
description: 'Internal normalized description augmentation with version coverage support',
|
22
|
+
})
|
23
|
+
|
24
|
+
export type Augmentation = S.Schema.Type<typeof Augmentation>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { Placement } from '#api/schema/augmentations/placement'
|
2
|
+
import { GraphQLPath } from '#lib/graphql-path'
|
3
|
+
import { S } from '#lib/kit-temp/effect'
|
4
|
+
|
5
|
+
export const AugmentationConfig = S.Struct({
|
6
|
+
on: GraphQLPath.Definition.DefinitionPath,
|
7
|
+
placement: Placement,
|
8
|
+
content: S.String,
|
9
|
+
})
|
10
|
+
|
11
|
+
export type AugmentationConfig = S.Schema.Type<typeof AugmentationConfig>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { DiagnosticDuplicateVersion } from '#api/schema/augmentations/diagnostics/duplicate-version'
|
2
|
+
import { DiagnosticInvalidPath } from '#api/schema/augmentations/diagnostics/invalid-path'
|
3
|
+
import { DiagnosticVersionMismatch } from '#api/schema/augmentations/diagnostics/version-mismatch'
|
4
|
+
import { S } from '#lib/kit-temp/effect'
|
5
|
+
|
6
|
+
// Re-export all individual diagnostics
|
7
|
+
export * from './duplicate-version.js'
|
8
|
+
export * from './invalid-path.js'
|
9
|
+
export * from './version-mismatch.js'
|
10
|
+
|
11
|
+
export const Diagnostic = S.Union(
|
12
|
+
DiagnosticInvalidPath,
|
13
|
+
DiagnosticVersionMismatch,
|
14
|
+
DiagnosticDuplicateVersion,
|
15
|
+
).annotations({
|
16
|
+
identifier: 'AugmentationsDiagnostic',
|
17
|
+
description: 'All diagnostics that can be generated for schema augmentations',
|
18
|
+
})
|
19
|
+
|
20
|
+
export type Diagnostic = S.Schema.Type<typeof Diagnostic>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { Diagnostic } from '#lib/diagnostic/$'
|
2
|
+
import { S } from '#lib/kit-temp/effect'
|
3
|
+
|
4
|
+
export const DiagnosticDuplicateVersion = Diagnostic.create({
|
5
|
+
source: 'schema-augmentations',
|
6
|
+
name: 'duplicate-version',
|
7
|
+
severity: 'error',
|
8
|
+
context: {
|
9
|
+
version: S.String,
|
10
|
+
firstPath: S.String,
|
11
|
+
duplicatePath: S.String,
|
12
|
+
},
|
13
|
+
}).annotations({
|
14
|
+
identifier: 'DiagnosticDuplicateVersion',
|
15
|
+
description: 'Same version is specified multiple times in augmentation configuration',
|
16
|
+
})
|
17
|
+
|
18
|
+
export const makeDiagnosticDuplicateVersion = Diagnostic.createMake(DiagnosticDuplicateVersion)
|
19
|
+
|
20
|
+
export type DiagnosticDuplicateVersion = S.Schema.Type<typeof DiagnosticDuplicateVersion>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { Diagnostic } from '#lib/diagnostic/$'
|
2
|
+
import { S } from '#lib/kit-temp/effect'
|
3
|
+
import { Version } from '#lib/version/$'
|
4
|
+
|
5
|
+
export const DiagnosticInvalidPath = Diagnostic.create({
|
6
|
+
source: 'schema-augmentations',
|
7
|
+
name: 'invalid-path',
|
8
|
+
severity: 'error',
|
9
|
+
context: {
|
10
|
+
path: S.String,
|
11
|
+
version: S.optional(S.Union(Version.Version, S.Null)),
|
12
|
+
error: S.String,
|
13
|
+
},
|
14
|
+
}).annotations({
|
15
|
+
identifier: 'DiagnosticInvalidPath',
|
16
|
+
description: 'Augmentation references a non-existent type or field in the schema',
|
17
|
+
})
|
18
|
+
|
19
|
+
export const makeDiagnosticInvalidPath = Diagnostic.createMake(DiagnosticInvalidPath)
|
20
|
+
|
21
|
+
export type DiagnosticInvalidPath = S.Schema.Type<typeof DiagnosticInvalidPath>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { Diagnostic } from '#lib/diagnostic/$'
|
2
|
+
import { S } from '#lib/kit-temp/effect'
|
3
|
+
import { Version } from '#lib/version/$'
|
4
|
+
|
5
|
+
export const DiagnosticVersionMismatch = Diagnostic.create({
|
6
|
+
source: 'schema-augmentations',
|
7
|
+
name: 'version-mismatch',
|
8
|
+
severity: 'error',
|
9
|
+
context: {
|
10
|
+
path: S.String,
|
11
|
+
requestedVersion: Version.Version,
|
12
|
+
availableVersions: S.Array(S.String),
|
13
|
+
},
|
14
|
+
}).annotations({
|
15
|
+
identifier: 'DiagnosticVersionMismatch',
|
16
|
+
description: 'Augmentation specifies a version that does not match any schema version',
|
17
|
+
})
|
18
|
+
|
19
|
+
export const makeDiagnosticVersionMismatch = Diagnostic.createMake(DiagnosticVersionMismatch)
|
20
|
+
|
21
|
+
export type DiagnosticVersionMismatch = S.Schema.Type<typeof DiagnosticVersionMismatch>
|
@@ -0,0 +1,144 @@
|
|
1
|
+
import { VersionCoverage } from '#lib/version-coverage'
|
2
|
+
import { Version } from '#lib/version/$'
|
3
|
+
import { HashMap, Option } from 'effect'
|
4
|
+
import { describe, expect, test } from 'vitest'
|
5
|
+
import { normalizeAugmentationInput } from './input.js'
|
6
|
+
|
7
|
+
describe('Description Augmentation Transform', () => {
|
8
|
+
test('transforms unversioned augmentation', () => {
|
9
|
+
const input = {
|
10
|
+
on: 'Query',
|
11
|
+
placement: 'before' as const,
|
12
|
+
content: 'Test content',
|
13
|
+
}
|
14
|
+
|
15
|
+
const result = normalizeAugmentationInput(input)
|
16
|
+
expect(result).not.toBeNull()
|
17
|
+
|
18
|
+
// Should have one unversioned entry
|
19
|
+
expect(HashMap.size(result!.versionAugmentations)).toBe(1)
|
20
|
+
|
21
|
+
// Check the entry exists by iterating
|
22
|
+
let foundUnversioned = false
|
23
|
+
for (const [coverage, config] of result!.versionAugmentations) {
|
24
|
+
if (VersionCoverage.isUnversioned(coverage)) {
|
25
|
+
foundUnversioned = true
|
26
|
+
expect(config.content).toBe('Test content')
|
27
|
+
expect(config.placement).toBe('before')
|
28
|
+
}
|
29
|
+
}
|
30
|
+
expect(foundUnversioned).toBe(true)
|
31
|
+
})
|
32
|
+
|
33
|
+
test('transforms versioned augmentation with defaults', () => {
|
34
|
+
const input = {
|
35
|
+
on: 'Pokemon',
|
36
|
+
placement: 'after' as const,
|
37
|
+
content: 'Default content',
|
38
|
+
versions: {
|
39
|
+
v1: { content: 'V1 content' },
|
40
|
+
v2: { content: 'V2 content', placement: 'before' as const },
|
41
|
+
},
|
42
|
+
}
|
43
|
+
|
44
|
+
const result = normalizeAugmentationInput(input)
|
45
|
+
expect(result).not.toBeNull()
|
46
|
+
|
47
|
+
// Should have two version entries (no unversioned when versions exist)
|
48
|
+
expect(HashMap.size(result!.versionAugmentations)).toBe(2)
|
49
|
+
|
50
|
+
// Check v1 - inherits placement from default
|
51
|
+
const v1 = Version.decodeSync('v1')
|
52
|
+
const v1Config = HashMap.get(
|
53
|
+
result!.versionAugmentations,
|
54
|
+
VersionCoverage.single(v1),
|
55
|
+
)
|
56
|
+
expect(Option.isSome(v1Config)).toBe(true)
|
57
|
+
if (Option.isSome(v1Config)) {
|
58
|
+
expect(v1Config.value.content).toBe('V1 content')
|
59
|
+
expect(v1Config.value.placement).toBe('after') // inherited
|
60
|
+
}
|
61
|
+
|
62
|
+
// Check v2 - overrides placement
|
63
|
+
const v2 = Version.decodeSync('v2')
|
64
|
+
const v2Config = HashMap.get(
|
65
|
+
result!.versionAugmentations,
|
66
|
+
VersionCoverage.single(v2),
|
67
|
+
)
|
68
|
+
expect(Option.isSome(v2Config)).toBe(true)
|
69
|
+
if (Option.isSome(v2Config)) {
|
70
|
+
expect(v2Config.value.content).toBe('V2 content')
|
71
|
+
expect(v2Config.value.placement).toBe('before') // overridden
|
72
|
+
}
|
73
|
+
})
|
74
|
+
|
75
|
+
test('transforms version-only augmentation', () => {
|
76
|
+
const input = {
|
77
|
+
versions: {
|
78
|
+
v2: {
|
79
|
+
on: 'Pokemon.stats',
|
80
|
+
content: 'V2 only content',
|
81
|
+
placement: 'after' as const,
|
82
|
+
},
|
83
|
+
},
|
84
|
+
}
|
85
|
+
|
86
|
+
const result = normalizeAugmentationInput(input)
|
87
|
+
expect(result).not.toBeNull()
|
88
|
+
|
89
|
+
// Should have one version entry
|
90
|
+
expect(HashMap.size(result!.versionAugmentations)).toBe(1)
|
91
|
+
|
92
|
+
const v2 = Version.decodeSync('v2')
|
93
|
+
const v2Config = HashMap.get(
|
94
|
+
result!.versionAugmentations,
|
95
|
+
VersionCoverage.single(v2),
|
96
|
+
)
|
97
|
+
expect(Option.isSome(v2Config)).toBe(true)
|
98
|
+
if (Option.isSome(v2Config)) {
|
99
|
+
expect(v2Config.value.content).toBe('V2 only content')
|
100
|
+
// v2Config.value.on is now a GraphQLPath.Definition.FieldDefinitionPath
|
101
|
+
// which is a tuple: [TypeSegment, FieldSegment]
|
102
|
+
expect(v2Config.value.on).toEqual([
|
103
|
+
{ _tag: 'TypeSegment', type: 'Pokemon' },
|
104
|
+
{ _tag: 'FieldSegment', field: 'stats' },
|
105
|
+
])
|
106
|
+
}
|
107
|
+
})
|
108
|
+
|
109
|
+
test('returns null for invalid augmentation', () => {
|
110
|
+
const input = {
|
111
|
+
// Missing required fields and no versions
|
112
|
+
}
|
113
|
+
|
114
|
+
const result = normalizeAugmentationInput(input)
|
115
|
+
expect(result).toBeNull()
|
116
|
+
})
|
117
|
+
|
118
|
+
test('skips incomplete version entries', () => {
|
119
|
+
const input = {
|
120
|
+
on: 'Query',
|
121
|
+
// Missing placement and content at top level
|
122
|
+
versions: {
|
123
|
+
v1: { content: 'V1 content' }, // Missing placement, can't inherit
|
124
|
+
v2: {
|
125
|
+
content: 'V2 content',
|
126
|
+
placement: 'after' as const,
|
127
|
+
}, // Complete
|
128
|
+
},
|
129
|
+
}
|
130
|
+
|
131
|
+
const result = normalizeAugmentationInput(input)
|
132
|
+
expect(result).not.toBeNull()
|
133
|
+
|
134
|
+
// Should have only v2 (v1 is incomplete)
|
135
|
+
expect(HashMap.size(result!.versionAugmentations)).toBe(1)
|
136
|
+
|
137
|
+
const v2 = Version.decodeSync('v2')
|
138
|
+
const v2Config = HashMap.get(
|
139
|
+
result!.versionAugmentations,
|
140
|
+
VersionCoverage.single(v2),
|
141
|
+
)
|
142
|
+
expect(Option.isSome(v2Config)).toBe(true)
|
143
|
+
})
|
144
|
+
})
|