GraphQL for AT Protocol
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(lex-gql): add sort to searchFn and require search function for query param

- Pass sort parameter to searchFn when using full-text search
- Throw error when query parameter is used without search function configured
- Limit sortable fields to integers, booleans, and datetime strings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+53 -18
+2 -2
e2e/lex-gql.e2e.test.js
··· 580 580 }); 581 581 }); 582 582 583 - it('sorts by record field ascending', async () => { 583 + it('sorts by datetime field ascending', async () => { 584 584 const result = await adapter.execute(` 585 585 query { 586 - appBskyFeedPost(first: 10, sortBy: [{ field: text, direction: ASC }]) { 586 + appBskyFeedPost(first: 10, sortBy: [{ field: createdAt, direction: ASC }]) { 587 587 edges { 588 588 node { text } 589 589 }
+7
examples/jetstream/CHANGELOG.md
··· 1 1 # lex-gql-jetstream-example 2 2 3 + ## 1.0.9 4 + 5 + ### Patch Changes 6 + 7 + - Updated dependencies 8 + - lex-gql@0.5.0 9 + 3 10 ## 1.0.8 4 11 5 12 ### Patch Changes
+1 -1
examples/jetstream/package.json
··· 1 1 { 2 2 "name": "lex-gql-jetstream-example", 3 - "version": "1.0.8", 3 + "version": "1.0.9", 4 4 "private": true, 5 5 "type": "module", 6 6 "description": "Example: lex-gql subscriptions with Jetstream",
+7
examples/relay/CHANGELOG.md
··· 1 1 # relay-example 2 2 3 + ## 0.0.5 4 + 5 + ### Patch Changes 6 + 7 + - Updated dependencies 8 + - lex-gql@0.5.0 9 + 3 10 ## 0.0.4 4 11 5 12 ### Patch Changes
+2 -2
examples/relay/package.json
··· 1 1 { 2 2 "name": "relay-example", 3 3 "private": true, 4 - "version": "0.0.4", 4 + "version": "0.0.5", 5 5 "type": "module", 6 6 "scripts": { 7 7 "dev": "concurrently \"node index.js\" \"vite\"", ··· 15 15 "graphql": "^16.11.0", 16 16 "graphql-http": "^1.22.0", 17 17 "graphql-ws": "^5.16.0", 18 - "lex-gql": "^0.4.0", 18 + "lex-gql": "^0.5.0", 19 19 "lex-gql-duckdb": "^0.3.0", 20 20 "react": "^19.1.1", 21 21 "react-dom": "^19.1.1",
+8
examples/tap/CHANGELOG.md
··· 1 1 # lex-gql-tap-example 2 2 3 + ## 1.0.9 4 + 5 + ### Patch Changes 6 + 7 + - Updated dependencies 8 + - lex-gql@0.5.0 9 + - lex-gql-sqlite@0.2.1 10 + 3 11 ## 1.0.8 4 12 5 13 ### Patch Changes
+1 -1
examples/tap/package.json
··· 1 1 { 2 2 "name": "lex-gql-tap-example", 3 - "version": "1.0.8", 3 + "version": "1.0.9", 4 4 "private": true, 5 5 "type": "module", 6 6 "description": "Example: lex-gql queries with AT Protocol tap",
+10
packages/lex-gql/CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## 0.5.0 4 + 5 + ### Minor Changes 6 + 7 + - Add sort parameter to search function and require searchFn for query parameter 8 + 9 + - Pass `sort` parameter to `searchFn` when using full-text search 10 + - Throw error when `query` parameter is used without a `search` function configured 11 + - Limit sortable fields to integers, booleans, and datetime strings (removes plain string sorting) 12 + 3 13 ## 0.4.1 4 14 5 15 ### Patch Changes
+1 -1
packages/lex-gql/package.json
··· 1 1 { 2 2 "name": "lex-gql", 3 - "version": "0.4.1", 3 + "version": "0.5.0", 4 4 "description": "Generate a complete GraphQL API from AT Protocol lexicons", 5 5 "license": "MIT", 6 6 "type": "module",
+9 -3
packages/lex-gql/src/lex-gql.js
··· 797 797 indexedAt: { value: 'indexedAt' }, 798 798 }; 799 799 800 - // Add primitive lexicon fields 800 + // Add sortable lexicon fields: integers, booleans, and datetime strings 801 801 for (const prop of recordDef.properties) { 802 - if (['string', 'integer', 'number', 'boolean'].includes(prop.type)) { 802 + if (['integer', 'boolean'].includes(prop.type)) { 803 + values[prop.name] = { value: prop.name }; 804 + } else if (prop.type === 'string' && prop.format === 'datetime') { 803 805 values[prop.name] = { value: prop.name }; 804 806 } 805 807 } ··· 2238 2240 }, 2239 2241 resolve: async (_, args, _context, info) => { 2240 2242 // Search mode - delegate to searchFn if query parameter is present 2241 - if (args.query && searchFn) { 2243 + if (args.query) { 2244 + if (!searchFn) { 2245 + throw new Error('Full-text search requires a search function. Provide a search option when building the schema.'); 2246 + } 2242 2247 const result = await searchFn({ 2243 2248 collection: lexicon.id, 2244 2249 query: args.query, 2245 2250 where: args.where, 2246 2251 first: args.first || 20, 2247 2252 after: args.after, 2253 + sort: compileSortBy(args.sortBy), 2248 2254 }); 2249 2255 return formatConnection(result); 2250 2256 }
+5 -8
packages/lex-gql/test/lex-gql.test.js
··· 4018 4018 expect(searchCalls[0].first).toBe(20); 4019 4019 }); 4020 4020 4021 - it('falls back to query function when search is not provided', async () => { 4021 + it('throws error when query used without search function', async () => { 4022 4022 const lexicons = [ 4023 4023 parseLexicon({ 4024 4024 lexicon: 1, ··· 4035 4035 }), 4036 4036 ]; 4037 4037 4038 - const queryCalls = []; 4039 - 4040 4038 const adapter = createAdapter(lexicons, { 4041 - query: async (op) => { 4042 - queryCalls.push(op); 4039 + query: async () => { 4043 4040 return { rows: [], hasNext: false, hasPrev: false, totalCount: 0 }; 4044 4041 }, 4045 4042 // No search function provided ··· 4053 4050 } 4054 4051 `); 4055 4052 4056 - // Should fall back to regular query since no search function 4057 - expect(result.errors).toBeUndefined(); 4058 - expect(queryCalls).toHaveLength(1); 4053 + // Should error since no search function is provided 4054 + expect(result.errors).toHaveLength(1); 4055 + expect(result.errors[0].message).toContain('search function'); 4059 4056 }); 4060 4057 });