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.
Files changed (188) hide show
  1. package/build/api/config/input.d.ts +76 -77
  2. package/build/api/config/input.d.ts.map +1 -1
  3. package/build/api/config/normalized.d.ts +136 -143
  4. package/build/api/config/normalized.d.ts.map +1 -1
  5. package/build/api/config-template/template.d.ts +110 -121
  6. package/build/api/config-template/template.d.ts.map +1 -1
  7. package/build/api/examples/diagnostic/diagnostic.d.ts +0 -23
  8. package/build/api/examples/diagnostic/diagnostic.d.ts.map +1 -1
  9. package/build/api/examples/diagnostic/diagnostic.js +1 -3
  10. package/build/api/examples/diagnostic/diagnostic.js.map +1 -1
  11. package/build/api/examples/scanner.d.ts +27 -1
  12. package/build/api/examples/scanner.d.ts.map +1 -1
  13. package/build/api/examples/scanner.js +19 -17
  14. package/build/api/examples/scanner.js.map +1 -1
  15. package/build/api/examples/schemas/catalog.d.ts +20 -12
  16. package/build/api/examples/schemas/catalog.d.ts.map +1 -1
  17. package/build/api/examples/schemas/example/example.d.ts +17 -11
  18. package/build/api/examples/schemas/example/example.d.ts.map +1 -1
  19. package/build/api/schema/augmentations/$$.d.ts +7 -0
  20. package/build/api/schema/augmentations/$$.d.ts.map +1 -0
  21. package/build/api/schema/augmentations/$$.js +7 -0
  22. package/build/api/schema/augmentations/$$.js.map +1 -0
  23. package/build/api/schema/augmentations/$.d.ts +1 -1
  24. package/build/api/schema/augmentations/$.d.ts.map +1 -1
  25. package/build/api/schema/augmentations/$.js +1 -1
  26. package/build/api/schema/augmentations/$.js.map +1 -1
  27. package/build/api/schema/augmentations/apply.d.ts +29 -0
  28. package/build/api/schema/augmentations/apply.d.ts.map +1 -0
  29. package/build/api/schema/augmentations/apply.js +181 -0
  30. package/build/api/schema/augmentations/apply.js.map +1 -0
  31. package/build/api/schema/augmentations/augmentation.d.ts +31 -0
  32. package/build/api/schema/augmentations/augmentation.d.ts.map +1 -0
  33. package/build/api/schema/augmentations/augmentation.js +22 -0
  34. package/build/api/schema/augmentations/augmentation.js.map +1 -0
  35. package/build/api/schema/augmentations/config.d.ts +24 -0
  36. package/build/api/schema/augmentations/config.d.ts.map +1 -0
  37. package/build/api/schema/augmentations/config.js +9 -0
  38. package/build/api/schema/augmentations/config.js.map +1 -0
  39. package/build/api/schema/augmentations/diagnostics/diagnostic.d.ts +67 -0
  40. package/build/api/schema/augmentations/diagnostics/diagnostic.d.ts.map +1 -0
  41. package/build/api/schema/augmentations/diagnostics/diagnostic.js +13 -0
  42. package/build/api/schema/augmentations/diagnostics/diagnostic.js.map +1 -0
  43. package/build/api/schema/augmentations/diagnostics/duplicate-version.d.ts +45 -0
  44. package/build/api/schema/augmentations/diagnostics/duplicate-version.d.ts.map +1 -0
  45. package/build/api/schema/augmentations/diagnostics/duplicate-version.js +17 -0
  46. package/build/api/schema/augmentations/diagnostics/duplicate-version.js.map +1 -0
  47. package/build/api/schema/augmentations/diagnostics/invalid-path.d.ts +46 -0
  48. package/build/api/schema/augmentations/diagnostics/invalid-path.d.ts.map +1 -0
  49. package/build/api/schema/augmentations/diagnostics/invalid-path.js +18 -0
  50. package/build/api/schema/augmentations/diagnostics/invalid-path.js.map +1 -0
  51. package/build/api/schema/augmentations/diagnostics/version-mismatch.d.ts +46 -0
  52. package/build/api/schema/augmentations/diagnostics/version-mismatch.d.ts.map +1 -0
  53. package/build/api/schema/augmentations/diagnostics/version-mismatch.js +18 -0
  54. package/build/api/schema/augmentations/diagnostics/version-mismatch.js.map +1 -0
  55. package/build/api/schema/augmentations/input.d.ts +145 -0
  56. package/build/api/schema/augmentations/input.d.ts.map +1 -0
  57. package/build/api/schema/augmentations/input.js +191 -0
  58. package/build/api/schema/augmentations/input.js.map +1 -0
  59. package/build/api/schema/augmentations/placement.d.ts +8 -0
  60. package/build/api/schema/augmentations/placement.d.ts.map +1 -0
  61. package/build/api/schema/augmentations/placement.js +7 -0
  62. package/build/api/schema/augmentations/placement.js.map +1 -0
  63. package/build/api/schema/config-schema.d.ts +66 -66
  64. package/build/api/schema/config-schema.d.ts.map +1 -1
  65. package/build/api/schema/config-schema.js +2 -2
  66. package/build/api/schema/config-schema.js.map +1 -1
  67. package/build/api/schema/input-source/load.d.ts +2 -0
  68. package/build/api/schema/input-source/load.d.ts.map +1 -1
  69. package/build/api/schema/input-source/load.js.map +1 -1
  70. package/build/api/schema/input-sources/directory.d.ts +39 -39
  71. package/build/api/schema/input-sources/file.d.ts +39 -39
  72. package/build/api/schema/input-sources/introspection-file.d.ts +39 -39
  73. package/build/api/schema/input-sources/introspection.d.ts +39 -39
  74. package/build/api/schema/input-sources/memory.d.ts +39 -39
  75. package/build/api/schema/input-sources/versioned-directory.d.ts +79 -79
  76. package/build/api/schema/load.d.ts.map +1 -1
  77. package/build/api/schema/load.js +9 -2
  78. package/build/api/schema/load.js.map +1 -1
  79. package/build/lib/catalog/catalog.d.ts +1181 -1181
  80. package/build/lib/catalog/unversioned.d.ts +312 -312
  81. package/build/lib/catalog/versioned.d.ts +634 -634
  82. package/build/lib/change/change.d.ts +238 -238
  83. package/build/lib/document/document.d.ts +14 -8
  84. package/build/lib/document/document.d.ts.map +1 -1
  85. package/build/lib/document/versioned.d.ts +17 -10
  86. package/build/lib/document/versioned.d.ts.map +1 -1
  87. package/build/lib/grafaid/schema/KindMap/_.d.ts +1 -1
  88. package/build/lib/graphql-path/$$.d.ts +7 -13
  89. package/build/lib/graphql-path/$$.d.ts.map +1 -1
  90. package/build/lib/graphql-path/$$.js +7 -13
  91. package/build/lib/graphql-path/$$.js.map +1 -1
  92. package/build/lib/graphql-path/definition.d.ts +104 -94
  93. package/build/lib/graphql-path/definition.d.ts.map +1 -1
  94. package/build/lib/graphql-path/definition.js +126 -125
  95. package/build/lib/graphql-path/definition.js.map +1 -1
  96. package/build/lib/graphql-path/query.d.ts +25 -57
  97. package/build/lib/graphql-path/query.d.ts.map +1 -1
  98. package/build/lib/graphql-path/query.js +15 -93
  99. package/build/lib/graphql-path/query.js.map +1 -1
  100. package/build/lib/graphql-path/schema.d.ts +49 -0
  101. package/build/lib/graphql-path/schema.d.ts.map +1 -0
  102. package/build/lib/graphql-path/schema.js +89 -0
  103. package/build/lib/graphql-path/schema.js.map +1 -0
  104. package/build/lib/graphql-path/types.d.ts +76 -28
  105. package/build/lib/graphql-path/types.d.ts.map +1 -1
  106. package/build/lib/graphql-path/types.js +101 -2
  107. package/build/lib/graphql-path/types.js.map +1 -1
  108. package/build/lib/revision/revision.d.ts +1170 -1170
  109. package/build/lib/schema/schema.d.ts +708 -708
  110. package/build/lib/schema/unversioned.d.ts +1092 -1092
  111. package/build/lib/schema/versioned.d.ts +634 -634
  112. package/build/lib/semver/official-release.d.ts +10 -10
  113. package/build/lib/semver/pre-release.d.ts +10 -10
  114. package/build/lib/semver/semver.d.ts +50 -50
  115. package/build/lib/version-coverage/version-coverage.d.ts +43 -11
  116. package/build/lib/version-coverage/version-coverage.d.ts.map +1 -1
  117. package/build/lib/version-coverage/version-coverage.js +40 -6
  118. package/build/lib/version-coverage/version-coverage.js.map +1 -1
  119. package/build/template/hooks/use-examples.d.ts +1 -1
  120. package/build/template/routes/changelog/ChangelogSidebar.d.ts +2 -2
  121. package/build/template/routes/changelog/ChangelogSidebar.d.ts.map +1 -1
  122. package/build/template/routes/changelog/ChangelogSidebar.js +4 -4
  123. package/build/template/routes/changelog/ChangelogSidebar.js.map +1 -1
  124. package/build/template/routes/examples/_.d.ts +3 -3
  125. package/build/template/routes/examples/_index.d.ts +2 -2
  126. package/build/template/routes/examples/name.d.ts +3 -3
  127. package/build/template/stores/changelog.d.ts +39 -39
  128. package/build/vite/plugins/examples.d.ts.map +1 -1
  129. package/build/vite/plugins/examples.js +0 -2
  130. package/build/vite/plugins/examples.js.map +1 -1
  131. package/build/vite/plugins/schemas.d.ts.map +1 -1
  132. package/build/vite/plugins/schemas.js +38 -2
  133. package/build/vite/plugins/schemas.js.map +1 -1
  134. package/package.json +1 -1
  135. package/src/api/examples/diagnostic/diagnostic.ts +0 -3
  136. package/src/api/examples/scanner.test.ts +83 -0
  137. package/src/api/examples/scanner.ts +17 -21
  138. package/src/api/schema/augmentations/$$.ts +6 -0
  139. package/src/api/schema/augmentations/$.ts +1 -1
  140. package/src/api/schema/augmentations/apply.test.ts +89 -0
  141. package/src/api/schema/augmentations/apply.ts +277 -0
  142. package/src/api/schema/augmentations/augmentation.ts +24 -0
  143. package/src/api/schema/augmentations/config.ts +11 -0
  144. package/src/api/schema/augmentations/diagnostics/diagnostic.ts +20 -0
  145. package/src/api/schema/augmentations/diagnostics/duplicate-version.ts +20 -0
  146. package/src/api/schema/augmentations/diagnostics/invalid-path.ts +21 -0
  147. package/src/api/schema/augmentations/diagnostics/version-mismatch.ts +21 -0
  148. package/src/api/schema/augmentations/input.test.ts +144 -0
  149. package/src/api/schema/augmentations/input.ts +215 -0
  150. package/src/api/schema/augmentations/placement.ts +11 -0
  151. package/src/api/schema/config-schema.ts +2 -2
  152. package/src/api/schema/input-source/load.ts +2 -0
  153. package/src/api/schema/load.ts +19 -2
  154. package/src/lib/graphql-path/$$.ts +7 -13
  155. package/src/lib/graphql-path/$.test.ts +175 -0
  156. package/src/lib/graphql-path/definition.ts +162 -162
  157. package/src/lib/graphql-path/query.ts +15 -98
  158. package/src/lib/graphql-path/schema.ts +136 -0
  159. package/src/lib/graphql-path/types.ts +108 -28
  160. package/src/lib/version-coverage/version-coverage.ts +48 -6
  161. package/src/template/routes/changelog/ChangelogSidebar.tsx +4 -4
  162. package/src/vite/plugins/examples.ts +0 -2
  163. package/src/vite/plugins/schemas.ts +51 -2
  164. package/build/api/examples/diagnostic/unused-default.d.ts +0 -49
  165. package/build/api/examples/diagnostic/unused-default.d.ts.map +0 -1
  166. package/build/api/examples/diagnostic/unused-default.js +0 -19
  167. package/build/api/examples/diagnostic/unused-default.js.map +0 -1
  168. package/build/api/schema/augmentations/augmentations/description.d.ts +0 -26
  169. package/build/api/schema/augmentations/augmentations/description.d.ts.map +0 -1
  170. package/build/api/schema/augmentations/augmentations/description.js +0 -55
  171. package/build/api/schema/augmentations/augmentations/description.js.map +0 -1
  172. package/build/api/schema/augmentations/schema-augmentation.d.ts +0 -20
  173. package/build/api/schema/augmentations/schema-augmentation.d.ts.map +0 -1
  174. package/build/api/schema/augmentations/schema-augmentation.js +0 -22
  175. package/build/api/schema/augmentations/schema-augmentation.js.map +0 -1
  176. package/build/api/schema/augmentations/target.d.ts +0 -25
  177. package/build/api/schema/augmentations/target.d.ts.map +0 -1
  178. package/build/api/schema/augmentations/target.js +0 -39
  179. package/build/api/schema/augmentations/target.js.map +0 -1
  180. package/build/lib/graphql-path/constructors.d.ts +0 -57
  181. package/build/lib/graphql-path/constructors.d.ts.map +0 -1
  182. package/build/lib/graphql-path/constructors.js +0 -73
  183. package/build/lib/graphql-path/constructors.js.map +0 -1
  184. package/src/api/examples/diagnostic/unused-default.ts +0 -22
  185. package/src/api/schema/augmentations/augmentations/description.ts +0 -69
  186. package/src/api/schema/augmentations/schema-augmentation.ts +0 -32
  187. package/src/api/schema/augmentations/target.ts +0 -61
  188. package/src/lib/graphql-path/constructors.ts +0 -81
@@ -0,0 +1,6 @@
1
+ export * from './apply.js'
2
+ export * from './augmentation.js'
3
+ export * from './config.js'
4
+ export { Diagnostic } from './diagnostics/diagnostic.js'
5
+ export * from './input.js'
6
+ export * from './placement.js'
@@ -1 +1 @@
1
- export * as Augmentations from './schema-augmentation.js'
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
+ })