polen 0.11.0-next.16 → 0.11.0-next.17
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/examples/diagnostic/diagnostic.d.ts +4 -4
- package/build/api/examples/diagnostic/missing-versions.d.ts +5 -4
- package/build/api/examples/diagnostic/missing-versions.d.ts.map +1 -1
- package/build/api/examples/diagnostic/missing-versions.js +3 -2
- package/build/api/examples/diagnostic/missing-versions.js.map +1 -1
- package/build/api/examples/diagnostic/unknown-version.d.ts +5 -4
- package/build/api/examples/diagnostic/unknown-version.d.ts.map +1 -1
- package/build/api/examples/diagnostic/unknown-version.js +3 -2
- package/build/api/examples/diagnostic/unknown-version.js.map +1 -1
- package/build/api/examples/diagnostic/validator.d.ts.map +1 -1
- package/build/api/examples/diagnostic/validator.js +7 -8
- package/build/api/examples/diagnostic/validator.js.map +1 -1
- package/build/api/examples/scanner.d.ts.map +1 -1
- package/build/api/examples/scanner.js +20 -16
- 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 +4 -5
- package/build/api/examples/type-usage-indexer.js.map +1 -1
- package/build/api/schema/input-sources/versioned-directory.d.ts +3 -3
- package/build/api/schema/input-sources/versioned-directory.d.ts.map +1 -1
- package/build/api/schema/input-sources/versioned-directory.js +4 -2
- package/build/api/schema/input-sources/versioned-directory.js.map +1 -1
- package/build/api/schema/load.js +1 -1
- package/build/api/schema/load.js.map +1 -1
- package/build/cli/commands/hero-image.js +1 -1
- package/build/cli/commands/hero-image.js.map +1 -1
- package/build/lib/catalog/catalog.d.ts +24 -23
- package/build/lib/catalog/catalog.d.ts.map +1 -1
- package/build/lib/catalog/catalog.js +7 -13
- package/build/lib/catalog/catalog.js.map +1 -1
- package/build/lib/catalog/versioned.d.ts +36 -26
- package/build/lib/catalog/versioned.d.ts.map +1 -1
- package/build/lib/catalog/versioned.js +25 -6
- package/build/lib/catalog/versioned.js.map +1 -1
- package/build/lib/catalog-statistics/analyze-catalog.js +3 -3
- package/build/lib/catalog-statistics/analyze-catalog.js.map +1 -1
- package/build/lib/lifecycles/lifecycles.js +1 -1
- package/build/lib/lifecycles/lifecycles.js.map +1 -1
- package/build/template/components/Changelog/Changelog.d.ts.map +1 -1
- package/build/template/components/Changelog/Changelog.js +7 -7
- package/build/template/components/Changelog/Changelog.js.map +1 -1
- package/build/template/components/GraphQLDocument.js +6 -5
- package/build/template/components/GraphQLDocument.js.map +1 -1
- package/build/template/components/VersionPicker.d.ts.map +1 -1
- package/build/template/components/VersionPicker.js +5 -2
- package/build/template/components/VersionPicker.js.map +1 -1
- package/build/template/components/home/FeaturesGrid.js +1 -1
- package/build/template/components/home/FeaturesGrid.js.map +1 -1
- package/build/template/components/home/HeroSection.js +1 -1
- package/build/template/components/home/HeroSection.js.map +1 -1
- package/build/template/components/home/RecentChanges.d.ts.map +1 -1
- package/build/template/components/home/RecentChanges.js +2 -1
- package/build/template/components/home/RecentChanges.js.map +1 -1
- package/build/template/routes/changelog.d.ts +1 -1
- package/build/template/routes/changelog.d.ts.map +1 -1
- package/build/template/routes/changelog.js +7 -4
- package/build/template/routes/changelog.js.map +1 -1
- package/build/template/routes/examples/_index.js +1 -1
- package/build/template/routes/examples/_index.js.map +1 -1
- package/build/template/routes/reference.js +6 -6
- package/build/template/routes/reference.js.map +1 -1
- package/build/vite/plugins/navbar.js +1 -1
- package/build/vite/plugins/navbar.js.map +1 -1
- package/build/vite/plugins/routes-manifest.js +1 -1
- package/build/vite/plugins/routes-manifest.js.map +1 -1
- package/package.json +1 -1
- package/src/api/examples/diagnostic/missing-versions.ts +3 -2
- package/src/api/examples/diagnostic/unknown-version.ts +3 -2
- package/src/api/examples/diagnostic/validator.test.ts +70 -55
- package/src/api/examples/diagnostic/validator.ts +7 -9
- package/src/api/examples/scanner.ts +25 -20
- package/src/api/examples/type-usage-indexer.test.ts +44 -33
- package/src/api/examples/type-usage-indexer.ts +4 -7
- package/src/api/schema/$.test.ts +2 -2
- package/src/api/schema/input-sources/versioned-directory.ts +7 -2
- package/src/api/schema/load.ts +1 -1
- package/src/cli/commands/hero-image.ts +1 -1
- package/src/lib/catalog/catalog.ts +7 -13
- package/src/lib/catalog/versioned.ts +35 -6
- package/src/lib/catalog-statistics/$.test.ts +22 -12
- package/src/lib/catalog-statistics/analyze-catalog.ts +3 -3
- package/src/lib/lifecycles/lifecycles.ts +1 -1
- package/src/template/components/Changelog/Changelog.tsx +10 -6
- package/src/template/components/GraphQLDocument.tsx +6 -5
- package/src/template/components/VersionPicker.tsx +9 -2
- package/src/template/components/home/FeaturesGrid.tsx +1 -1
- package/src/template/components/home/HeroSection.tsx +1 -1
- package/src/template/components/home/RecentChanges.tsx +3 -1
- package/src/template/routes/changelog.tsx +10 -4
- package/src/template/routes/examples/_index.tsx +1 -1
- package/src/template/routes/reference.tsx +6 -6
- package/src/vite/plugins/navbar.ts +1 -1
- package/src/vite/plugins/routes-manifest.ts +1 -1
@@ -1,8 +1,8 @@
|
|
1
1
|
import { Catalog as SchemaCatalog } from '#lib/catalog/$'
|
2
2
|
import { Document } from '#lib/document/$'
|
3
3
|
import { Version } from '#lib/version/$'
|
4
|
-
import {
|
5
|
-
import {
|
4
|
+
import { Match } from 'effect'
|
5
|
+
import { HashMap } from 'effect/Schema'
|
6
6
|
import type { GraphQLError, GraphQLSchema } from 'graphql'
|
7
7
|
import { parse, specifiedRules, validate } from 'graphql'
|
8
8
|
import { Example } from '../schemas/example/$.js'
|
@@ -35,23 +35,21 @@ export const validateExamples = (
|
|
35
35
|
return Match.value(catalog).pipe(
|
36
36
|
Match.tagsExhaustive({
|
37
37
|
CatalogVersioned: (versioned) => {
|
38
|
-
const schemaVersions = versioned.entries.map(e => e.version)
|
39
|
-
|
40
38
|
for (const example of examples) {
|
41
39
|
Match.value(example.document).pipe(
|
42
40
|
Match.tagsExhaustive({
|
43
41
|
DocumentVersioned: (doc) => {
|
44
42
|
// Validate each version against its corresponding schema
|
45
|
-
for (const
|
46
|
-
const content = Document.Versioned.getContentForVersion(doc,
|
43
|
+
for (const schema of SchemaCatalog.Versioned.getAll(versioned)) {
|
44
|
+
const content = Document.Versioned.getContentForVersion(doc, schema.version)
|
47
45
|
if (content) {
|
48
|
-
const versionStr = Version.encodeSync(
|
46
|
+
const versionStr = Version.encodeSync(schema.version)
|
49
47
|
validateDocument(
|
50
48
|
example.name,
|
51
49
|
example.path,
|
52
50
|
versionStr,
|
53
51
|
content,
|
54
|
-
|
52
|
+
schema.definition,
|
55
53
|
diagnostics,
|
56
54
|
)
|
57
55
|
}
|
@@ -59,7 +57,7 @@ export const validateExamples = (
|
|
59
57
|
return undefined
|
60
58
|
},
|
61
59
|
DocumentUnversioned: (doc) => {
|
62
|
-
const latestEntry = versioned
|
60
|
+
const latestEntry = SchemaCatalog.Versioned.getLatestOrThrow(versioned)
|
63
61
|
validateDocument(
|
64
62
|
example.name,
|
65
63
|
example.path,
|
@@ -91,10 +91,10 @@ const lintFileLayout = (
|
|
91
91
|
schemaCatalog?: SchemaCatalog.Catalog,
|
92
92
|
): Diagnostic[] => {
|
93
93
|
// Extract schema versions from catalog if provided
|
94
|
-
const schemaVersions:
|
94
|
+
const schemaVersions: Version.Version[] = schemaCatalog
|
95
95
|
? SchemaCatalog.fold(
|
96
|
-
(versioned) =>
|
97
|
-
(unversioned) =>
|
96
|
+
(versioned) => SchemaCatalog.Versioned.getVersions(versioned),
|
97
|
+
(unversioned) => [], // Unversioned doesn't have Version objects, just dates
|
98
98
|
)(schemaCatalog)
|
99
99
|
: []
|
100
100
|
|
@@ -105,14 +105,13 @@ const lintFileLayout = (
|
|
105
105
|
DocumentVersioned: (doc) => {
|
106
106
|
// Get all versions covered by this document
|
107
107
|
const coveredVersions = Document.Versioned.getAllVersions(doc)
|
108
|
-
const
|
109
|
-
const missingVersions = schemaVersions.filter(sv => !coveredVersionStrings.includes(sv))
|
108
|
+
const missingVersions = schemaVersions.filter(sv => !coveredVersions.some(cv => Version.equivalence(sv, cv)))
|
110
109
|
|
111
110
|
if (missingVersions.length > 0) {
|
112
111
|
diagnostics.push(makeDiagnosticMissingVersions({
|
113
112
|
message: `Versioned example must provide documents for all schema versions`,
|
114
113
|
example: { name: example.name, path: example.path },
|
115
|
-
providedVersions:
|
114
|
+
providedVersions: coveredVersions,
|
116
115
|
missingVersions,
|
117
116
|
}))
|
118
117
|
}
|
@@ -206,7 +205,7 @@ export const scan = (
|
|
206
205
|
// Get all schema versions to map to this default document
|
207
206
|
const schemaVersions: Version.Version[] = options.schemaCatalog
|
208
207
|
? SchemaCatalog.fold(
|
209
|
-
(versioned) =>
|
208
|
+
(versioned) => SchemaCatalog.Versioned.getVersions(versioned),
|
210
209
|
() => [], // Unversioned schemas don't have version-specific examples
|
211
210
|
)(options.schemaCatalog)
|
212
211
|
: []
|
@@ -238,16 +237,19 @@ export const scan = (
|
|
238
237
|
// Versioned example - multiple files or versioned files
|
239
238
|
let versionDocuments = HashMap.empty<VersionCoverage.VersionCoverage, string>()
|
240
239
|
let defaultDocument: string | undefined
|
241
|
-
|
242
|
-
const unknownVersions:
|
240
|
+
let explicitVersions = HashSet.empty<Version.Version>() // Track which versions have explicit files
|
241
|
+
const unknownVersions: Version.Version[] = []
|
243
242
|
|
244
243
|
// Get available schema versions if catalog is provided
|
245
|
-
const schemaVersions:
|
244
|
+
const schemaVersions: Version.Version[] = options.schemaCatalog
|
246
245
|
? SchemaCatalog.fold(
|
247
|
-
(versioned) =>
|
246
|
+
(versioned) => SchemaCatalog.Versioned.getVersions(versioned),
|
248
247
|
() => [], // Unversioned schemas don't have version-specific examples
|
249
248
|
)(options.schemaCatalog)
|
250
249
|
: []
|
250
|
+
|
251
|
+
// Create HashSet for O(1) lookups
|
252
|
+
const schemaVersionsSet = HashSet.fromIterable(schemaVersions)
|
251
253
|
|
252
254
|
// Read content for each version
|
253
255
|
for (const [version, filePath] of versions) {
|
@@ -257,35 +259,38 @@ export const scan = (
|
|
257
259
|
if (version === 'default') {
|
258
260
|
defaultDocument = fileContent
|
259
261
|
} else if (version !== null) {
|
262
|
+
// Parse the version string
|
263
|
+
const parsedVersion = Version.decodeSync(version)
|
260
264
|
// Check if this version exists in the schema
|
261
|
-
|
262
|
-
|
265
|
+
const versionExists = HashSet.has(schemaVersionsSet, parsedVersion)
|
266
|
+
if (options.schemaCatalog && schemaVersions.length > 0 && !versionExists) {
|
267
|
+
unknownVersions.push(parsedVersion)
|
263
268
|
// Create diagnostic for unknown version
|
264
269
|
diagnostics.push(makeDiagnosticUnknownVersion({
|
265
270
|
message: `Example "${name}" specifies version "${version}" which does not exist in the schema`,
|
266
271
|
example: { name, path: basePath },
|
267
|
-
version,
|
272
|
+
version: parsedVersion,
|
268
273
|
availableVersions: schemaVersions,
|
269
274
|
}))
|
270
275
|
// Skip this version - don't include it in the example
|
271
276
|
continue
|
272
277
|
}
|
273
278
|
|
274
|
-
|
275
|
-
versionDocuments = HashMap.set(versionDocuments,
|
276
|
-
explicitVersions.add(
|
279
|
+
// We already have parsedVersion from above
|
280
|
+
versionDocuments = HashMap.set(versionDocuments, parsedVersion, fileContent)
|
281
|
+
explicitVersions = HashSet.add(explicitVersions, parsedVersion)
|
277
282
|
}
|
278
283
|
}
|
279
284
|
|
280
285
|
if (defaultDocument) {
|
281
286
|
// If we have a default, determine which versions it applies to
|
282
|
-
const defaultVersions = schemaVersions.filter(v => !
|
287
|
+
const defaultVersions = schemaVersions.filter(v => !HashSet.has(explicitVersions, v))
|
283
288
|
|
284
289
|
if (defaultVersions.length > 0) {
|
285
290
|
// Create a version set for the default document
|
286
291
|
const defaultVersionSet = defaultVersions.length === 1
|
287
|
-
?
|
288
|
-
: HashSet.fromIterable(defaultVersions
|
292
|
+
? defaultVersions[0]! // Single version
|
293
|
+
: HashSet.fromIterable(defaultVersions) // Version set
|
289
294
|
|
290
295
|
versionDocuments = HashMap.set(versionDocuments, defaultVersionSet, defaultDocument)
|
291
296
|
}
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { Catalog } from '#lib/catalog/$'
|
2
2
|
import { Document } from '#lib/document/$'
|
3
|
-
import { Revision } from '#lib/revision/$'
|
4
3
|
import { Schema } from '#lib/schema/$'
|
5
4
|
import { Version } from '#lib/version/$'
|
6
5
|
import { HashMap, HashSet, Schema as S } from 'effect'
|
@@ -17,23 +16,23 @@ describe('type-usage-indexer', () => {
|
|
17
16
|
users: [User!]!
|
18
17
|
product(id: ID!): Product
|
19
18
|
}
|
20
|
-
|
19
|
+
|
21
20
|
type User {
|
22
21
|
id: ID!
|
23
22
|
name: String!
|
24
23
|
email: String!
|
25
24
|
}
|
26
|
-
|
25
|
+
|
27
26
|
type Product {
|
28
27
|
id: ID!
|
29
28
|
name: String!
|
30
29
|
price: Float!
|
31
30
|
}
|
32
|
-
|
31
|
+
|
33
32
|
interface Node {
|
34
33
|
id: ID!
|
35
34
|
}
|
36
|
-
|
35
|
+
|
37
36
|
union SearchResult = User | Product
|
38
37
|
`
|
39
38
|
|
@@ -91,20 +90,26 @@ describe('type-usage-indexer', () => {
|
|
91
90
|
})
|
92
91
|
|
93
92
|
const catalog = Catalog.Versioned.make({
|
94
|
-
entries:
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
93
|
+
entries: HashMap.make(
|
94
|
+
[
|
95
|
+
version1,
|
96
|
+
Schema.Versioned.make({
|
97
|
+
version: version1,
|
98
|
+
definition: schemaDef,
|
99
|
+
branchPoint: null,
|
100
|
+
revisions: [],
|
101
|
+
}),
|
102
|
+
],
|
103
|
+
[
|
104
|
+
version2,
|
105
|
+
Schema.Versioned.make({
|
106
|
+
version: version2,
|
107
|
+
definition: schemaDef,
|
108
|
+
branchPoint: null,
|
109
|
+
revisions: [],
|
110
|
+
}),
|
111
|
+
],
|
112
|
+
),
|
108
113
|
})
|
109
114
|
|
110
115
|
const index = createTypeUsageIndex([example], catalog)
|
@@ -212,20 +217,26 @@ describe('type-usage-indexer', () => {
|
|
212
217
|
})
|
213
218
|
|
214
219
|
const catalog = Catalog.Versioned.make({
|
215
|
-
entries:
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
220
|
+
entries: HashMap.make(
|
221
|
+
[
|
222
|
+
version1,
|
223
|
+
Schema.Versioned.make({
|
224
|
+
version: version1,
|
225
|
+
definition: schemaDef,
|
226
|
+
branchPoint: null,
|
227
|
+
revisions: [],
|
228
|
+
}),
|
229
|
+
],
|
230
|
+
[
|
231
|
+
version2,
|
232
|
+
Schema.Versioned.make({
|
233
|
+
version: version2,
|
234
|
+
definition: schemaDef,
|
235
|
+
branchPoint: null,
|
236
|
+
revisions: [],
|
237
|
+
}),
|
238
|
+
],
|
239
|
+
),
|
229
240
|
})
|
230
241
|
|
231
242
|
const index = createTypeUsageIndex([example], catalog)
|
@@ -172,11 +172,8 @@ const getSchemaByVersion = (
|
|
172
172
|
version: Version.Version,
|
173
173
|
): Schema.Schema | undefined => {
|
174
174
|
if (Catalog.Versioned.is(catalog)) {
|
175
|
-
//
|
176
|
-
return catalog.entries.
|
177
|
-
Schema.Versioned.is(entry)
|
178
|
-
&& Version.equivalence(entry.version, version)
|
179
|
-
)
|
175
|
+
// Use HashMap.get for O(1) lookup
|
176
|
+
return HashMap.get(catalog.entries, version).pipe(Option.getOrElse(() => undefined))
|
180
177
|
}
|
181
178
|
// For unversioned catalog, return the single schema if version matches
|
182
179
|
if (Catalog.Unversioned.is(catalog)) {
|
@@ -206,12 +203,12 @@ export const createTypeUsageIndex = (
|
|
206
203
|
// Process based on document type
|
207
204
|
if (Document.Unversioned.is(example.document)) {
|
208
205
|
// Unversioned document
|
209
|
-
const schema = Catalog.
|
206
|
+
const schema = Catalog.getLatest(schemasCatalog)
|
210
207
|
const types = extractTypesFromQuery(example.document.document, schema.definition)
|
211
208
|
|
212
209
|
for (const typeName of types) {
|
213
210
|
// For unversioned, use the latest version from the catalog
|
214
|
-
const latestVersion = Catalog.
|
211
|
+
const latestVersion = Catalog.getLatestVersion(schemasCatalog) ?? Version.fromString('1.0.0')
|
215
212
|
index = addExampleToIndex(index, UNVERSIONED_KEY, typeName, example, latestVersion)
|
216
213
|
}
|
217
214
|
} else if (Document.Versioned.is(example.document)) {
|
package/src/api/schema/$.test.ts
CHANGED
@@ -3,7 +3,7 @@ import { Catalog } from '#lib/catalog/$'
|
|
3
3
|
import { Grafaid } from '#lib/grafaid'
|
4
4
|
import { MemoryFilesystem } from '#lib/memory-filesystem/$'
|
5
5
|
import * as NodeFileSystem from '@effect/platform-node/NodeFileSystem'
|
6
|
-
import { Effect } from 'effect'
|
6
|
+
import { Effect, HashMap } from 'effect'
|
7
7
|
import { expect } from 'vitest'
|
8
8
|
import { Test } from '../../../tests/unit/helpers/test.js'
|
9
9
|
import { Schema } from './$.js'
|
@@ -421,7 +421,7 @@ testWithFileSystem<BaseTestCase & {
|
|
421
421
|
expect(result!._tag).toBe('CatalogVersioned')
|
422
422
|
if (expected.versionCount !== undefined) {
|
423
423
|
const versioned = result as Catalog.Versioned.Versioned
|
424
|
-
expect(versioned.entries
|
424
|
+
expect(HashMap.size(versioned.entries)).toBe(expected.versionCount)
|
425
425
|
}
|
426
426
|
} else {
|
427
427
|
expect(result!._tag).toBe('CatalogUnversioned')
|
@@ -10,7 +10,7 @@ import { debugPolen } from '#singletons/debug'
|
|
10
10
|
import { PlatformError } from '@effect/platform/Error'
|
11
11
|
import { FileSystem } from '@effect/platform/FileSystem'
|
12
12
|
import { Arr, Path } from '@wollybeard/kit'
|
13
|
-
import { Effect } from 'effect'
|
13
|
+
import { Effect, HashMap } from 'effect'
|
14
14
|
import type { GraphQLSchema } from 'graphql'
|
15
15
|
|
16
16
|
const debug = debugPolen.sub(`schema:data-source-versioned-schema-directory`)
|
@@ -393,9 +393,14 @@ export const readOrThrow = (
|
|
393
393
|
// Reverse to have newest first
|
394
394
|
catalogEntries.reverse()
|
395
395
|
|
396
|
+
// Convert array to HashMap with version as key
|
397
|
+
const entriesMap = HashMap.fromIterable(
|
398
|
+
catalogEntries.map(entry => [entry.version, entry] as const),
|
399
|
+
)
|
400
|
+
|
396
401
|
debug(`computed ${catalogEntries.length} entries`)
|
397
402
|
return Catalog.Versioned.make({
|
398
|
-
entries:
|
403
|
+
entries: entriesMap,
|
399
404
|
})
|
400
405
|
})
|
401
406
|
|
package/src/api/schema/load.ts
CHANGED
@@ -142,7 +142,7 @@ export const loadOrNull = (
|
|
142
142
|
const catalog = loadedSchema.data as Catalog.Catalog
|
143
143
|
Catalog.fold(
|
144
144
|
(versioned) => {
|
145
|
-
for (const schema of versioned
|
145
|
+
for (const schema of Catalog.Versioned.getAll(versioned)) {
|
146
146
|
Augmentations.apply(schema.definition, augmentations)
|
147
147
|
}
|
148
148
|
},
|
@@ -133,7 +133,7 @@ export const heroImage = Command.make(
|
|
133
133
|
const schemaResult = yield* Api.Schema.loadOrNull(config)
|
134
134
|
if (schemaResult?.data) {
|
135
135
|
try {
|
136
|
-
const latestSchema = Catalog.
|
136
|
+
const latestSchema = Catalog.getLatest(schemaResult.data)
|
137
137
|
if (latestSchema?.definition) {
|
138
138
|
schema = latestSchema.definition
|
139
139
|
const context = AiImageGeneration.analyzeSchema(schema)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { S } from '#lib/kit-temp/effect'
|
2
2
|
import { Schema } from '#lib/schema/$'
|
3
3
|
import { Version } from '#lib/version/$'
|
4
|
-
import { Match } from 'effect'
|
4
|
+
import { HashMap, Match } from 'effect'
|
5
5
|
import * as Unversioned from './unversioned.js'
|
6
6
|
import * as Versioned from './versioned.js'
|
7
7
|
|
@@ -62,7 +62,7 @@ export const equivalence = S.equivalence(Catalog)
|
|
62
62
|
*/
|
63
63
|
export const getVersionCount = (catalog: Catalog): number =>
|
64
64
|
fold(
|
65
|
-
(versioned) => versioned.entries
|
65
|
+
(versioned) => HashMap.size(versioned.entries),
|
66
66
|
(_unversioned) => 1, // Unversioned catalog is effectively one version
|
67
67
|
)(catalog)
|
68
68
|
|
@@ -79,28 +79,22 @@ export const getSchemaVersionString = (schema: Schema.Schema): string => {
|
|
79
79
|
* Get the version string from a schema.
|
80
80
|
* Returns the stringified version for versioned schemas, or '__UNVERSIONED__' for unversioned schemas.
|
81
81
|
*/
|
82
|
-
export const
|
82
|
+
export const getLatest = (catalog: Catalog): Schema.Schema =>
|
83
83
|
Match.value(catalog).pipe(Match.tagsExhaustive({
|
84
|
-
CatalogVersioned:
|
85
|
-
|
86
|
-
const latestEntry = versioned.entries[0]!
|
87
|
-
return latestEntry
|
88
|
-
},
|
89
|
-
CatalogUnversioned: (unversioned) => {
|
90
|
-
return unversioned.schema
|
91
|
-
},
|
84
|
+
CatalogVersioned: Versioned.getLatestOrThrow,
|
85
|
+
CatalogUnversioned: (unversioned) => unversioned.schema,
|
92
86
|
}))
|
93
87
|
|
94
88
|
/**
|
95
89
|
* Get the latest version identifier from a catalog.
|
96
90
|
* Returns the version for versioned catalogs, or null for unversioned catalogs.
|
97
91
|
*/
|
98
|
-
export const
|
92
|
+
export const getLatestVersion = (catalog?: Catalog): Version.Version | null => {
|
99
93
|
if (!catalog) return null
|
100
94
|
return Match.value(catalog).pipe(
|
101
95
|
Match.tagsExhaustive({
|
102
96
|
CatalogUnversioned: () => null,
|
103
|
-
CatalogVersioned: (cat) => cat
|
97
|
+
CatalogVersioned: (cat) => Versioned.getVersions(cat)[0] ?? null,
|
104
98
|
}),
|
105
99
|
)
|
106
100
|
}
|
@@ -1,12 +1,17 @@
|
|
1
1
|
import { S } from '#lib/kit-temp/effect'
|
2
|
+
import { Array, HashMap, Iterable, Order, pipe } from 'effect'
|
2
3
|
import { Schema } from '../schema/$.js'
|
4
|
+
import { Version } from '../version/$.js'
|
3
5
|
|
4
6
|
// ============================================================================
|
5
7
|
// Schema
|
6
8
|
// ============================================================================
|
7
9
|
|
8
10
|
export const Versioned = S.TaggedStruct('CatalogVersioned', {
|
9
|
-
entries: S.
|
11
|
+
entries: S.HashMap({
|
12
|
+
key: Version.Version,
|
13
|
+
value: Schema.Versioned.Versioned,
|
14
|
+
}),
|
10
15
|
}).annotations({
|
11
16
|
identifier: 'CatalogVersioned',
|
12
17
|
title: 'Versioned Catalog',
|
@@ -47,16 +52,40 @@ export const equivalence = S.equivalence(Versioned)
|
|
47
52
|
|
48
53
|
/**
|
49
54
|
* Get the latest schema definition from a versioned catalog.
|
50
|
-
* The latest version is
|
55
|
+
* The latest version is determined by Version.max comparison.
|
51
56
|
*
|
52
57
|
* @param catalog - The versioned catalog
|
53
58
|
* @returns The GraphQL schema definition of the latest version
|
54
59
|
* @throws {Error} If the catalog has no entries
|
55
60
|
*/
|
56
|
-
export const
|
57
|
-
const
|
58
|
-
if (!
|
61
|
+
export const getLatestOrThrow = (catalog: Versioned): Schema.Versioned.Versioned => {
|
62
|
+
const schema = getAll(catalog)[0]
|
63
|
+
if (!schema) {
|
59
64
|
throw new Error('Versioned catalog has no entries - cannot get latest schema')
|
60
65
|
}
|
61
|
-
return
|
66
|
+
return schema
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Get all schemas sorted by version (newest first)
|
71
|
+
*/
|
72
|
+
export const getAll = (catalog: Versioned): Schema.Versioned.Versioned[] => {
|
73
|
+
return pipe(
|
74
|
+
catalog.entries,
|
75
|
+
HashMap.values,
|
76
|
+
Array.fromIterable,
|
77
|
+
// Put newest versions first in array
|
78
|
+
Array.sort(Order.reverse(Schema.Versioned.order)),
|
79
|
+
)
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Get all versions sorted (newest first)
|
84
|
+
*/
|
85
|
+
export const getVersions = (catalog: Versioned): Version.Version[] => {
|
86
|
+
return pipe(
|
87
|
+
catalog,
|
88
|
+
getAll,
|
89
|
+
Array.map(_ => _.version),
|
90
|
+
)
|
62
91
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Catalog } from '#lib/catalog/$'
|
2
2
|
import { DateOnly } from '#lib/date-only/$'
|
3
3
|
import { Version } from '#lib/version/$'
|
4
|
+
import { HashMap } from 'effect'
|
4
5
|
import { buildSchema, type GraphQLSchema } from 'graphql'
|
5
6
|
import { describe, expect, test } from 'vitest'
|
6
7
|
import { CatalogStatistics } from './$.js'
|
@@ -91,14 +92,20 @@ describe('analyzeCatalog', () => {
|
|
91
92
|
name: 'versioned catalog',
|
92
93
|
catalog: () =>
|
93
94
|
Catalog.Versioned.make({
|
94
|
-
entries:
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
95
|
+
entries: HashMap.make(
|
96
|
+
[
|
97
|
+
Version.fromInteger(1),
|
98
|
+
makeVersionedEntry(1, buildSchema('type Query { hello: String }'), ['2024-01-01', '2024-01-15']),
|
99
|
+
],
|
100
|
+
[
|
101
|
+
Version.fromInteger(2),
|
102
|
+
makeVersionedEntry(
|
103
|
+
2,
|
104
|
+
buildSchema('type Query { hello: String, world: String } type User { id: ID!, name: String }'),
|
105
|
+
['2024-02-01'],
|
106
|
+
),
|
107
|
+
],
|
108
|
+
),
|
102
109
|
}),
|
103
110
|
expectedVersions: 2,
|
104
111
|
expectedCurrentVersion: '2',
|
@@ -132,10 +139,13 @@ describe('analyzeCatalog', () => {
|
|
132
139
|
|
133
140
|
test('calculates stability metrics', () => {
|
134
141
|
const catalog = Catalog.Versioned.make({
|
135
|
-
entries:
|
136
|
-
|
137
|
-
|
138
|
-
|
142
|
+
entries: HashMap.make(
|
143
|
+
[
|
144
|
+
Version.fromInteger(1),
|
145
|
+
makeVersionedEntry(1, buildSchema('type Query { test: String }'), ['2024-01-01', '2024-01-05']),
|
146
|
+
],
|
147
|
+
[Version.fromInteger(2), makeVersionedEntry(2, buildSchema('type Query { test: String }'), ['2024-01-10'])],
|
148
|
+
),
|
139
149
|
})
|
140
150
|
|
141
151
|
const report = CatalogStatistics.analyzeCatalog(catalog)
|
@@ -16,7 +16,7 @@ export const analyzeCatalog = (catalog: Catalog.Catalog, options: AnalyzeOptions
|
|
16
16
|
const processResult = Catalog.fold(
|
17
17
|
// Versioned catalog
|
18
18
|
(versioned) => {
|
19
|
-
for (const entry of versioned
|
19
|
+
for (const entry of Catalog.Versioned.getAll(versioned)) {
|
20
20
|
const versionId = Version.encodeSync(entry.version)
|
21
21
|
// Analyze the schema definition for this version
|
22
22
|
// Use the first revision date if available
|
@@ -55,8 +55,8 @@ export const analyzeCatalog = (catalog: Catalog.Catalog, options: AnalyzeOptions
|
|
55
55
|
// Calculate stability metrics
|
56
56
|
const stability = calculateStabilityMetrics(versions, revisionDates)
|
57
57
|
|
58
|
-
// Get current version (
|
59
|
-
const current = versions[
|
58
|
+
// Get current version (first in array since sorted newest first)
|
59
|
+
const current = versions[0]
|
60
60
|
|
61
61
|
return {
|
62
62
|
stability,
|
@@ -119,7 +119,7 @@ export const create = (catalog: Catalog.Catalog): Lifecycles => {
|
|
119
119
|
Catalog.fold(
|
120
120
|
// Versioned catalog - process each versioned schema
|
121
121
|
(versioned) => {
|
122
|
-
for (const schema of versioned
|
122
|
+
for (const schema of Catalog.Versioned.getAll(versioned)) {
|
123
123
|
processSchema(schema)
|
124
124
|
}
|
125
125
|
},
|
@@ -4,6 +4,7 @@ import { DateOnly } from '#lib/date-only/$'
|
|
4
4
|
import { Revision } from '#lib/revision/$'
|
5
5
|
import { Schema } from '#lib/schema/$'
|
6
6
|
import { Version } from '#lib/version/$'
|
7
|
+
import { HashMap, Option } from 'effect'
|
7
8
|
const CRITICALITY_LEVELS = ['BREAKING', 'DANGEROUS', 'NON_BREAKING'] as const
|
8
9
|
import type { CriticalityLevel } from '@graphql-inspector/core'
|
9
10
|
import { Box, Heading } from '@radix-ui/themes'
|
@@ -35,8 +36,7 @@ export const Changelog: React.FC<{ catalog: Catalog.Catalog }> = ({ catalog }) =
|
|
35
36
|
useEffect(() => {
|
36
37
|
if (urlVersion) return
|
37
38
|
if (Catalog.Unversioned.is(catalog)) return
|
38
|
-
const latestSchema = catalog
|
39
|
-
if (!latestSchema) return
|
39
|
+
const latestSchema = Catalog.Versioned.getLatestOrThrow(catalog)
|
40
40
|
const latestVersion = Version.encodeSync(latestSchema.version)
|
41
41
|
navigate(`/changelog/version/${latestVersion}`, { replace: true })
|
42
42
|
}, [catalog, urlVersion, navigate])
|
@@ -51,10 +51,14 @@ export const Changelog: React.FC<{ catalog: Catalog.Catalog }> = ({ catalog }) =
|
|
51
51
|
} else {
|
52
52
|
// For versioned catalogs, always show specific version (never all)
|
53
53
|
if (urlVersion) {
|
54
|
-
const
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
const entryOption = Option.map(
|
55
|
+
HashMap.findFirst(catalog.entries, (_, key) => Version.encodeSync(key) === urlVersion),
|
56
|
+
([, value]) => value,
|
57
|
+
)
|
58
|
+
return Option.match(entryOption, {
|
59
|
+
onNone: () => ({ revisions: [], schema: null }),
|
60
|
+
onSome: (entry) => ({ revisions: entry.revisions, schema: entry }),
|
61
|
+
})
|
58
62
|
}
|
59
63
|
// This shouldn't happen due to redirect above, but return empty as fallback
|
60
64
|
return { revisions: [], schema: null }
|
@@ -5,7 +5,7 @@ import type { Schema } from '#lib/schema/$'
|
|
5
5
|
import { VersionCoverage } from '#lib/version-selection/$'
|
6
6
|
import { VersionCoverageSet } from '#lib/version-selection/version-selection'
|
7
7
|
import { Version } from '#lib/version/$'
|
8
|
-
import { Array, HashMap, Match } from 'effect'
|
8
|
+
import { Array, HashMap, Match, Option } from 'effect'
|
9
9
|
import type { GraphQLSchema } from 'graphql'
|
10
10
|
import * as React from 'react'
|
11
11
|
import { useHighlighted } from '../hooks/use-highlighted.js'
|
@@ -47,7 +47,7 @@ export const GraphQLDocument: React.FC<GraphQLDocumentProps> = ({
|
|
47
47
|
/// ━ VERSION MANAGEMENT
|
48
48
|
const isControlled = controlledVersionCoverage !== undefined
|
49
49
|
const [internalVersionCoverage, setInternalVersionCoverage] = React.useState<VersionCoverage.VersionCoverage | null>(
|
50
|
-
Catalog.
|
50
|
+
Catalog.getLatestVersion(schemaCatalog),
|
51
51
|
)
|
52
52
|
const selectedVersionCoverage = isControlled ? controlledVersionCoverage : internalVersionCoverage
|
53
53
|
const internalOnVersionChange = (version: VersionCoverage.VersionCoverage) => {
|
@@ -110,7 +110,7 @@ const resolveSelectedVerCov = (
|
|
110
110
|
schema: Match.value(schemaCatalog).pipe(
|
111
111
|
Match.tagsExhaustive({
|
112
112
|
CatalogUnversioned: (catalog) => catalog.schema,
|
113
|
-
CatalogVersioned: (catalog) => Catalog.Versioned.
|
113
|
+
CatalogVersioned: (catalog) => Catalog.Versioned.getLatestOrThrow(catalog),
|
114
114
|
}),
|
115
115
|
),
|
116
116
|
}
|
@@ -134,10 +134,11 @@ const resolveSelectedVerCov = (
|
|
134
134
|
if (Catalog.Unversioned.is(schemaCatalog)) {
|
135
135
|
throw new Error('Cannot use a set of versions with an unversioned catalog')
|
136
136
|
}
|
137
|
-
const
|
138
|
-
if (
|
137
|
+
const schemaOption = HashMap.get(schemaCatalog.entries, version)
|
138
|
+
if (Option.isNone(schemaOption)) {
|
139
139
|
throw new Error(`Version ${Version.encodeSync(version)} not found in catalog`)
|
140
140
|
}
|
141
|
+
const schema = Option.getOrThrow(schemaOption)
|
141
142
|
|
142
143
|
return { content, schema: schema }
|
143
144
|
}),
|