Commits
- 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>
Regenerated .d.ts file to include the search property that was added
in 0.4.0 but missing from the type definitions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add optional `query` argument to collection query fields. When present,
delegates to the optional `search` function instead of the regular query
function. Both paths use formatConnection for consistent response shape.
Also:
- Fix blob URL resolver to return null for missing did/ref
- Remove unused parameters from createRecordTypeWithResolvers
- Bump version to 0.4.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Handles cases where blob data is missing did or ref (e.g., schema
migrations, old data formats) gracefully by returning null for the
URL instead of throwing an error that breaks the entire query.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix resolver to extract URI from strongRef {uri, cid} objects
- Fix buildSchema to type *Resolved fields as Record union
- Add tests for both strongRef and at-uri format resolution
- Add like lexicon test fixture
- Document forward joins in README
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Explain reverse join naming convention and use cases
- Document findManyPartitioned operation for adapters
- Note N+1 prevention via batching
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Verifies that multiple reverse join resolvers are batched into a
single findManyPartitioned operation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests batching behavior, grouping by parameters, fallback to
individual queries, and handling of missing partitions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reverse join fields now batch queries via ReverseJoinCollector,
eliminating N+1 queries when resolving reverse joins on multiple
parent records.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Batches reverse join resolver calls within a microtask, grouping by
(collection, fieldName, pagination, sort). Falls back to individual
queries if adapter doesn't support findManyPartitioned.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New operation type for batched reverse join queries with per-partition
pagination. Adapters can use SQL window functions for efficient
top-N-per-group queries.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The where argument was accepted but silently ignored. Removing it
simplifies the API and avoids user confusion.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add unit tests verifying reverse join resolver operations
- Add e2e tests using threadgate -> post reverse joins
- Test pagination and sorting in Via fields
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Via fields with resolvers to createRecordTypeWithResolvers,
enabling reverse join queries (e.g., appBskyFeedLikeViaSubject).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduces DidCollector class that batches DID-based lookups across
GraphQL resolvers, reducing N+1 queries to single batched queries
per collection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, cursor pagination used only the record ID which caused
duplicate records when paginating with custom sort fields. This fix
encodes actual sort field values in the cursor for correct pagination.
Changes:
- Encode sort field values + URI tiebreaker in JSON cursor format
- Add progressive WHERE clauses for multi-field sorts
- Add field name validation to prevent SQL injection
- Add cursor field count validation with debug logging
- Remove ID dependency - use indexedAt DESC, uri DESC as default
- Export centralized DEFAULT_SORT constant from lex-gql
- Apply fix to both DuckDB and SQLite adapters
- Add comprehensive tests including multi-field sort scenarios
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Re-enable GraphQL subscriptions in frontend
- Add profile lookup (avatar, displayName) to subscription emits
- New card-based TrackItem layout with user info and relative time
- Add hideUser prop to hide user info on profile page
- Use uri as list key to prevent image reuse on subscription updates
- Sort plays client-side to maintain order with subscription items
- TODO: Abstract subscription layer in lex-gql for field resolution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add insertRecordsBatch() that uses single INSERT with multiple VALUES
- Update relay example to use batch inserts
- Fix memory leak from unbounded pendingWrites queue
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When grouping by date interval (playedTime_day), the result field
is playedTime_day, not playedTime.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add nixpacks.toml with Python/build tools for DuckDB compilation
- Add railway.toml for relay service config
- Add tap/ subdirectory with Dockerfile for tap service
- Update package.json to use published npm versions
- Skip records without data to avoid insert errors
- Non-blocking batch writes with event loop yielding
- Update README with Railway deployment instructions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add arrayFields to Operation typedef
- Use undefined instead of null for optional GraphQL types
- Cast error objects in catch blocks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- lex-gql@0.2.0
- lex-gql-sqlite@0.2.0
- lex-gql-duckdb@0.2.0
Add aggregate enhancements, actorHandle filtering, and DuckDB adapter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove [Unreleased] sections - changesets will manage going forward.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Install @changesets/cli
- Configure for public npm access
- Add scripts: changeset, version, publish
Workflow:
1. pnpm changeset - describe changes
2. pnpm version - bump versions and update changelogs
3. pnpm publish - build and publish to npm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a full-stack example combining tap, lex-gql, and React/Relay for
displaying teal.fm music scrobbles.
New packages:
- lex-gql-duckdb: Analytics-optimized DuckDB adapter (~17x faster aggregates)
lex-gql enhancements:
- actorHandle system field for WHERE filtering
- Array fields in aggregate group results
- Forward join resolution, union types, blob URL generation
lex-gql-sqlite fixes:
- Escape LIKE wildcards in contains operator (security)
- Log malformed cursor errors instead of silently ignoring
Relay example features:
- Real-time scrobble feed with WebSocket subscriptions
- Profile pages with user listening history
- Top tracks/albums aggregate views with date filtering
- Scrobble activity charts
- MusicBrainz and album art integration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add date interval grouping, configurable limit, and orderBy for aggregate queries.
lex-gql-sqlite:
- Date interval grouping with _day, _week, _month suffixes on datetime fields
- Configurable limit parameter (default: 50, max: 1000)
- Configurable orderBy parameter (COUNT_ASC, COUNT_DESC)
lex-gql:
- Add limit and orderBy args to aggregate query fields
- Add AggregateOrderBy enum (COUNT_ASC, COUNT_DESC)
- Add date interval fields to AggregateGroup types for datetime properties
Includes unit tests and e2e tests for all new features.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Forward join fields ({field}Resolved) now return the Record union type
instead of a generic ResolvedRecord object. This enables inline fragment
queries like `postResolved { ... on AppBskyFeedPost { text } }`.
Changes:
- lex-gql: Use getRecordUnionType thunk for forward join field types
- lex-gql: Remove unused createResolvedRecordType function
- lex-gql-sqlite: Support collection '*' for cross-collection URI resolution
- e2e: Add tests for at-uri and StrongRef forward join resolution
- docs: Document collection '*' adapter contract in READMEs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BREAKING CHANGE: insertRecord no longer accepts did, collection, or rkey
fields. These are now parsed automatically from the URI.
Before: insertRecord({ uri, did, collection, rkey, cid?, record, indexedAt? })
After: insertRecord({ uri, cid?, record, indexedAt? })
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace raw SQL insert statements with the createWriter helper methods
(insertRecord, upsertActor) for cleaner and more maintainable test code.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move unreleased features into 0.1.0 section and fix release date.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move source files to src/ and test files to test/ directories for both
lex-gql and lex-gql-sqlite packages. Update all import paths and config
files accordingly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
lex-gql:
- Union type support with type registry and resolveRefKey helper
- Forward joins on nested types
- System fields in WhereInput and GroupByEnum
- Blob ref resolution for ATProto format
- AND/OR where clause format change
lex-gql-sqlite:
- createWriter helper for efficient writes
- totalCount in findMany results
- AND/OR conditions format change
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use 'graphql-http/lib/use/http' instead of deprecated '/node' path.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Port Interface:
- Clean up AND/OR where clause format: { op: 'and', conditions: [...] }
instead of redundant { field: 'AND', op: 'and', value: [...] }
- Update WhereClause typedef with clear documentation
- Add system fields (uri, did, collection) to WhereInput and GroupByEnum
lex-gql-sqlite:
- Add createWriter(db) helper with prepared statements for efficient writes
- insertRecord({ uri, did, collection, rkey, cid?, record, indexedAt? })
- deleteRecord(uri)
- upsertActor(did, handle)
- Add totalCount support to findMany queries
- Update tap example to use createWriter
E2E Fixes:
- Fix cursor-based pagination using _id field
- Fix blob ref resolution for ATProto format (ref.$link)
- Add properties check in buildSchemaWithResolvers pre-pass
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Verifies that union types have a working resolveType function that
uses the $type field to determine the concrete type at runtime.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds forward join fields (like rootResolved, parentResolved, uriResolved)
to nested types that have strongRef references:
- Add addForwardJoinFields helper function
- Update createNestedObjectType to use getter for Record union type
- Update createStrongRefType to include uriResolved field
- Use holder pattern to allow field thunks to access Record union type
after it's created
Nested types like AppBskyFeedPostReplyRef now have forward join fields
to resolve their strongRef references.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements union type creation for lexicon union fields:
- Update getGraphQLType to handle union type with refs array
- Create dynamic union types named after parent type + field name
- Add unionRegistry to track created union types
- Include union types in schema output
Fields like `embed` now correctly resolve to `AppBskyFeedPostEmbed` union
containing all referenced types. Schema match improved to 64.8%.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements ref resolution in the schema builder:
- Build unified type registry containing all object types before creating record types
- Update getGraphQLType to resolve refs using typeRegistry and resolveRefKey
- Update createNestedObjectType to pass typeRegistry for nested ref resolution
- Handle both single refs and arrays of refs
Fields like `reply` now correctly resolve to `AppBskyFeedPostReplyRef`
instead of `String`. Schema match percentage improved from 63.3% to 64.3%.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds a utility function to resolve lexicon ref strings to full registry keys.
This handles both local refs (#replyRef -> app.bsky.feed.post#replyRef)
and external refs (already fully qualified).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create e2e package with comprehensive integration tests
- Use real AT Protocol lexicons from lex-gql test fixtures
- Test basic queries, pagination, filtering, sorting, aggregates
- Test ByDid resolvers for cross-collection joins
- Add e2e to pnpm workspace
Note: Some tests will fail until union/ref type support is implemented
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add optional `query` argument to collection query fields. When present,
delegates to the optional `search` function instead of the regular query
function. Both paths use formatConnection for consistent response shape.
Also:
- Fix blob URL resolver to return null for missing did/ref
- Remove unused parameters from createRecordTypeWithResolvers
- Bump version to 0.4.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix resolver to extract URI from strongRef {uri, cid} objects
- Fix buildSchema to type *Resolved fields as Record union
- Add tests for both strongRef and at-uri format resolution
- Add like lexicon test fixture
- Document forward joins in README
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, cursor pagination used only the record ID which caused
duplicate records when paginating with custom sort fields. This fix
encodes actual sort field values in the cursor for correct pagination.
Changes:
- Encode sort field values + URI tiebreaker in JSON cursor format
- Add progressive WHERE clauses for multi-field sorts
- Add field name validation to prevent SQL injection
- Add cursor field count validation with debug logging
- Remove ID dependency - use indexedAt DESC, uri DESC as default
- Export centralized DEFAULT_SORT constant from lex-gql
- Apply fix to both DuckDB and SQLite adapters
- Add comprehensive tests including multi-field sort scenarios
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Re-enable GraphQL subscriptions in frontend
- Add profile lookup (avatar, displayName) to subscription emits
- New card-based TrackItem layout with user info and relative time
- Add hideUser prop to hide user info on profile page
- Use uri as list key to prevent image reuse on subscription updates
- Sort plays client-side to maintain order with subscription items
- TODO: Abstract subscription layer in lex-gql for field resolution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add nixpacks.toml with Python/build tools for DuckDB compilation
- Add railway.toml for relay service config
- Add tap/ subdirectory with Dockerfile for tap service
- Update package.json to use published npm versions
- Skip records without data to avoid insert errors
- Non-blocking batch writes with event loop yielding
- Update README with Railway deployment instructions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a full-stack example combining tap, lex-gql, and React/Relay for
displaying teal.fm music scrobbles.
New packages:
- lex-gql-duckdb: Analytics-optimized DuckDB adapter (~17x faster aggregates)
lex-gql enhancements:
- actorHandle system field for WHERE filtering
- Array fields in aggregate group results
- Forward join resolution, union types, blob URL generation
lex-gql-sqlite fixes:
- Escape LIKE wildcards in contains operator (security)
- Log malformed cursor errors instead of silently ignoring
Relay example features:
- Real-time scrobble feed with WebSocket subscriptions
- Profile pages with user listening history
- Top tracks/albums aggregate views with date filtering
- Scrobble activity charts
- MusicBrainz and album art integration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add date interval grouping, configurable limit, and orderBy for aggregate queries.
lex-gql-sqlite:
- Date interval grouping with _day, _week, _month suffixes on datetime fields
- Configurable limit parameter (default: 50, max: 1000)
- Configurable orderBy parameter (COUNT_ASC, COUNT_DESC)
lex-gql:
- Add limit and orderBy args to aggregate query fields
- Add AggregateOrderBy enum (COUNT_ASC, COUNT_DESC)
- Add date interval fields to AggregateGroup types for datetime properties
Includes unit tests and e2e tests for all new features.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Forward join fields ({field}Resolved) now return the Record union type
instead of a generic ResolvedRecord object. This enables inline fragment
queries like `postResolved { ... on AppBskyFeedPost { text } }`.
Changes:
- lex-gql: Use getRecordUnionType thunk for forward join field types
- lex-gql: Remove unused createResolvedRecordType function
- lex-gql-sqlite: Support collection '*' for cross-collection URI resolution
- e2e: Add tests for at-uri and StrongRef forward join resolution
- docs: Document collection '*' adapter contract in READMEs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BREAKING CHANGE: insertRecord no longer accepts did, collection, or rkey
fields. These are now parsed automatically from the URI.
Before: insertRecord({ uri, did, collection, rkey, cid?, record, indexedAt? })
After: insertRecord({ uri, cid?, record, indexedAt? })
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
lex-gql:
- Union type support with type registry and resolveRefKey helper
- Forward joins on nested types
- System fields in WhereInput and GroupByEnum
- Blob ref resolution for ATProto format
- AND/OR where clause format change
lex-gql-sqlite:
- createWriter helper for efficient writes
- totalCount in findMany results
- AND/OR conditions format change
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Port Interface:
- Clean up AND/OR where clause format: { op: 'and', conditions: [...] }
instead of redundant { field: 'AND', op: 'and', value: [...] }
- Update WhereClause typedef with clear documentation
- Add system fields (uri, did, collection) to WhereInput and GroupByEnum
lex-gql-sqlite:
- Add createWriter(db) helper with prepared statements for efficient writes
- insertRecord({ uri, did, collection, rkey, cid?, record, indexedAt? })
- deleteRecord(uri)
- upsertActor(did, handle)
- Add totalCount support to findMany queries
- Update tap example to use createWriter
E2E Fixes:
- Fix cursor-based pagination using _id field
- Fix blob ref resolution for ATProto format (ref.$link)
- Add properties check in buildSchemaWithResolvers pre-pass
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds forward join fields (like rootResolved, parentResolved, uriResolved)
to nested types that have strongRef references:
- Add addForwardJoinFields helper function
- Update createNestedObjectType to use getter for Record union type
- Update createStrongRefType to include uriResolved field
- Use holder pattern to allow field thunks to access Record union type
after it's created
Nested types like AppBskyFeedPostReplyRef now have forward join fields
to resolve their strongRef references.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements union type creation for lexicon union fields:
- Update getGraphQLType to handle union type with refs array
- Create dynamic union types named after parent type + field name
- Add unionRegistry to track created union types
- Include union types in schema output
Fields like `embed` now correctly resolve to `AppBskyFeedPostEmbed` union
containing all referenced types. Schema match improved to 64.8%.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements ref resolution in the schema builder:
- Build unified type registry containing all object types before creating record types
- Update getGraphQLType to resolve refs using typeRegistry and resolveRefKey
- Update createNestedObjectType to pass typeRegistry for nested ref resolution
- Handle both single refs and arrays of refs
Fields like `reply` now correctly resolve to `AppBskyFeedPostReplyRef`
instead of `String`. Schema match percentage improved from 63.3% to 64.3%.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create e2e package with comprehensive integration tests
- Use real AT Protocol lexicons from lex-gql test fixtures
- Test basic queries, pagination, filtering, sorting, aggregates
- Test ByDid resolvers for cross-collection joins
- Add e2e to pnpm workspace
Note: Some tests will fail until union/ref type support is implemented
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>