Skip to content

Linked 2.0 - introducing dynamic query building. QueryBuilder, FieldSet, mutation builders.#23

Merged
flyon merged 112 commits intodevfrom
claude/linked-2-cleanup-K01bt
Mar 10, 2026
Merged

Linked 2.0 - introducing dynamic query building. QueryBuilder, FieldSet, mutation builders.#23
flyon merged 112 commits intodevfrom
claude/linked-2-cleanup-K01bt

Conversation

@flyon
Copy link
Member

@flyon flyon commented Mar 10, 2026

Summary

Replaces the mutable SelectQueryFactory + PatchedQueryPromise + nextTick query system with an immutable QueryBuilder + FieldSet architecture. The DSL (Person.select(...), Person.create(...), etc.) is now syntactic sugar over composable, serializable builders. Mutation operations follow the same immutable builder pattern.

Key Changes

  • QueryBuilder: New immutable, fluent, PromiseLike select query builder with .select(), .where(), .orderBy(), .limit(), .for(), .forAll() methods
  • FieldSet: Immutable, composable, serializable collection of property paths with entries carrying filters, sub-selects, aggregations, and preloads
  • PropertyPath: Value object representing chains of property traversals
  • Mutation builders: CreateBuilder, UpdateBuilder, DeleteBuilder follow the same immutable PromiseLike pattern
  • ShapeConstructor: Renamed from ShapeType for clarity at runtime boundaries
  • QueryPrimitive consolidation: QueryString, QueryNumber, QueryBoolean, QueryDate merged into single generic QueryPrimitive<T> class
  • Direct FieldSet-to-desugar conversion: Eliminated SelectPath intermediate representation; desugarSelectQuery() now accepts FieldSetEntry[] directly via RawSelectInput.entries
  • Targeting API: .for(id) targets single subject (unwraps result type), .forAll(ids) generates VALUES clause for multi-ID filtering
  • JSON serialization: QueryBuilder and FieldSet support round-trip JSON serialization for CMS/dynamic query construction
  • DSL API changes: Shape.select() and Shape.update() no longer accept ID as first argument; use .for(id) instead

Breaking Changes

  • Shape.select(id, fn)Shape.select(fn).for(id)
  • Shape.update(id, payload)Shape.update(payload).for(id)
  • ShapeType renamed to ShapeConstructor
  • Removed internal IR types: SelectPath, QueryPath, CustomQueryObject, SubQueryPaths, ComponentQueryPath
  • Removed QueryString, QueryNumber, QueryBoolean, QueryDate classes (use QueryPrimitive<T>)

Implementation Details

  • Both DSL and dynamic paths converge at FieldSet, which converts directly to RawSelectInput — the existing IR pipeline entry point
  • Immutable builders use shallow clone with structural sharing
  • ProxiedPathBuilder proxy implementation powers both DSL and dynamic query construction
  • Existing buildSelectQuery() → IRDesugar → IRCanonicalize → IRLower → irToAlgebra pipeline reused unchanged
  • 629 passing tests across 22 test suites; ~20 commits across 19 phases

claude added 30 commits March 4, 2026 08:21
Analyze the full query pipeline (DSL → RawSelectInput → Desugar →
Canonicalize → Lower → IRSelectQuery) and propose five approaches for
dynamic query building: raw IR helpers (A), fluent builder (B), dynamic
DSL with string resolution (C), extending SelectQueryFactory (D), and
composable path objects (E). Includes comparison matrix, code examples,
and a phased implementation plan. Recommends B+C layered approach for
CMS use case.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Expand the recommendation to B+C+E-style composability via a FieldSet
primitive. Add concrete examples for all five CMS surfaces: table
overview (columns as FieldSet), edit forms (shape-derived all()),
drag-drop builder (merge component requirements), NL chat (incremental
extend), and shape-level defaults. Include FieldSet API design,
comparison table of when shapes suffice vs when FieldSet is needed,
and updated phased implementation plan.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
… example

Three major additions to 003:

1. Scoped filters in FieldSet — filters that attach to specific
   traversals (e.g. "only active friends") vs top-level query filters.
   Merged FieldSets AND-combine scoped filters on the same traversal.

2. Query derivation — immutable QueryBuilder where every .where(),
   .include(), .limit() returns a new builder. Enables fork/extend
   patterns for table→detail page transitions and NL chat evolution.

3. Shape remapping — ShapeAdapter maps a query from one ontology to
   another (PersonShape → schema:Person) while preserving result keys
   so components render identically across different graph environments.

Includes full end-to-end example combining all features across all five
CMS surfaces, updated open questions, and revised implementation plan
with shape remapping as Phase 4.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
…ter format details

Four additions to 003:

1. FieldSet immutability — extend/omit/pick return new instances,
   parallel to QueryBuilder immutability. Shared FieldSets across
   components are safe from mutation.

2. Filter-on-selected-path semantics — selecting 'age' and filtering
   'age > 30' share the same IR traversal/variable via existing
   LoweringContext.getOrCreateTraversal() deduplication. Scoped
   filters and top-level filters AND-combine on shared variables.

3. Variable reuse / shared bindings — document current implicit
   variable sharing via alias dedup, and design direction for future
   explicit .as()/$ref binding pattern. PropertyPath gets optional
   bindingName slot. IRAliasScope already supports this structurally.

4. ShapeAdapter properties format — clarify string labels vs
   PropertyShape references ({id: IRI}), mixed usage, and internal
   resolution to IRI-to-IRI map. Add examples of both forms.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
…, and IR changes

Replace the placeholder binding design with comprehensive examples showing
how .as()/.joins() works across all API surfaces:

- Static DSL: .as('sharedHobby') names a path endpoint, .joins(hobby)
  creates a structural join (vs .equals() which generates FILTER)
- QueryBuilder: callback and string-based binding patterns
- FieldSet composition: exported/consumed bindings that connect across
  merged FieldSets (component A exports a binding, component B joins on it)
- Immutability: bindings are part of the cloned state on fork/extend
- NL chat: incremental binding construction
- Drag-drop: component-declared bindings auto-connect on merge

Also detail the IR changes needed: LoweringContext gets a namedBindings
map, IRTraversePattern gets optional bindingName/joinBinding fields.
Show that the change is small — if two patterns share a `to` alias,
existing downstream code already produces shared ?variables.

v1 types reserve optional fields (bindingName, joinBinding, $ref)
that cost nothing until implementation, but allow FieldSets created
now to carry .as() declarations that work when bindings land.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
…more options

The previous examples were confusing — they conflated FieldSets with binding
references and didn't show the generated SPARQL. This rewrite:

- Adds a clear "problem" section explaining why shared variables matter
- Shows side-by-side SPARQL: FILTER approach vs shared variable approach
- Explains .as() and .joins() in plain terms (label a node / reuse that label)
- Presents 4 API design options with pros/cons and recommendation
- Every example now includes the generated SPARQL
- Adds 7 open design questions specific to bindings (scope, validation,
  multiple exporters, cross-shape, OPTIONAL, serialization, naming)
- Renames .join() to .constrain() for string-based API (less SQL confusion)

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
- New 008-shared-variable-bindings.md: full design with agreed decisions
  (symmetric .as(), .matches() sugar, no type checks, per-query scope)
- 003: replaced ~500-line variable section with compact forward-compat
  summary + link to 008. Keeps only what v1 needs: reserved optional
  fields on PropertyPath, FieldSetEntry, WhereConditionValue, QueryBuilder

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
- New 009-shape-remapping.md: full ShapeAdapter design (3 options, CMS
  examples, pipeline placement, open questions)
- 003: replaced shape remapping section with 6-line forward ref,
  replaced end-to-end step 7 with link, replaced Phase 4 with link
- 008: added status note that nothing is implemented yet

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
- Question 9 resolved: serialize at QueryBuilder/FieldSet level (not IR)
- Shape/property identifiers use prefixed IRIs (my:PersonShape)
- Added QueryBuilder.toJSON()/fromJSON() to implementation plan
- Note on question 4: plain-object where clause form used in JSON

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
- Replaced generic end-to-end example with 3 real CMS surface examples:
  grid/table (add columns, filter, switch view mode), drag-drop page
  builder (merge component requirements), NL chat (incremental refinement)
- Added nested selection syntax: { 'hobbies': ['label', 'description'] }
  to avoid repeating long path prefixes
- Added method naming ideation section comparing .with()/.without(),
  .expand()/.contract(), .addFields()/.removeFields(), etc.
- Note: .select() = replace fields (switch view mode, keep filters),
  merge method name still open
- Updated FieldSetInput type to support nested object + nested FieldSet

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Key decisions captured:
- DSL is syntactic sugar over QueryBuilder + FieldSet (shared proxy)
- ProxiedPathBuilder is shared between DSL, FieldSet, QueryBuilder callbacks
- .path('string') is escape hatch on proxy for dynamic/runtime paths
- Method naming decided: setFields/addFields/removeFields (QueryBuilder),
  set/add/remove/pick (FieldSet)
- Where clauses: proxy form (p => p.age.gt(18)) matches DSL,
  string form ('age', '>', 18) is convenience shorthand
- Type validation: operators validated against sh:datatype
- .select() in callbacks = sub-selection (matches DSL), not field replacement
- .for(id) chainable pattern for DSL alignment
- Variable bindings: .as() on proxy, { path, as } for string form,
  no separate .bind()/.constrain() needed
- Phase 3 added: DSL alignment (refactor DSL to use QueryBuilder internally)

Updated 008 to reflect shared proxy, new method names, resolved
.bind()/.constrain() question (not needed — .as() is enough)

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
QueryBuilder implements PromiseLike so `await` triggers execution.
No more mutable PatchedQueryPromise or nextTick scheduling.
.select() and .query() both return QueryBuilder (same type).

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
…tes, decided APIs

- Replace .include() → .select() in all conceptual examples
- Replace .setFields() → .select() where it's initial field selection (creation)
- Keep .setFields() only for replacing fields on existing builders
- Mark .for(id)/.forAll() chainable pattern as decided (was "open for discussion")
- Update delete API: delete(id) requires arg, deleteAll() for bulk
- Update mutations: .for()/.forAll() required on update (type error without targeting)
- Fix missing parentheses around callback params on line 87
- Clean up .include() placeholder note
- Add .select() to QueryBuilder class design and method naming tables
- Add .forAll() to QueryBuilder narrowing API
- Update implementation plan phases 2 and 6

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
…ment)

Distills decided architecture from 003 ideation into actionable plan:
- PropertyPath, FieldSet, QueryBuilder contracts
- Files expected to change with accurate line references
- Pitfalls, open questions, and scope boundaries

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Added detailed file sizes from codebase exploration:
- SelectQuery.ts: 72KB, 4 interrelated classes
- Full pipeline file sizes (IRDesugar 12KB, IRLower 11KB, etc.)
- Supporting files and existing test suite that must pass
- QueryFactory.ts and IRDesugar.ts as additional modified files

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
- Remove .one(id) — .for(id) already implies singleResult
- All targeting methods accept string | NodeReferenceValue for flexibility
- Rename PropertyPath.steps → segments (clearer naming)
- Add FieldSet.for(shape).select(fields) chained construction form
- Remove backward compat pitfall (current version not in use)
- Expand mutation builders open question with codebase context
- Add note that scoped filter merging needs resolution before plan closes
- Document QueryFactory.ts disposition: remove empty abstract class, keep types

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
- CreateBuilder, UpdateBuilder, DeleteBuilder replace existing factory classes
- Same immutable + PromiseLike pattern as QueryBuilder
- UpdateBuilder requires .for()/.forAll() targeting before execution
- DeleteBuilder accepts ids at construction (string | NodeReferenceValue)
- Reuse MutationQueryFactory input normalization, existing IR pipeline
- Add contracts, new files, modified files for mutations
- Remove mutation builders from open questions (decided: in scope)

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
- Add ProxiedPathBuilder contract (.path() escape hatch for dynamic strings)
- Add sh:datatype validation on comparison helpers (boolean=only equals, etc.)
- Add FieldSet.summary() for shape-metadata-derived summaries
- Add serialization format spec (QueryBuilder.toJSON/fromJSON, FieldSet.toJSON/fromJSON)
- Add shape resolution by prefixed IRI string
- Add Person.selectAll({ depth }) to scope
- Keep __id in data support alongside .withId() on CreateBuilder
- Add ProxiedPathBuilder.ts to new files list
- Move result typing to future work section
- Expand scope boundaries with all included items

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Plan: scoped filter merging defaults to AND with warnings on conflicts.
OR support deferred to when it actually comes up.

Skill: add rule to carry forward all decided features from ideation.
No idea can be silently dropped — if unsure whether tentative or
decided, ask the user rather than omitting.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Structures the plan into 5 phases: ProxiedPathBuilder extraction,
QueryBuilder, FieldSet, mutation builders, and serialization. Removes
FieldSet.summary() from scope (CMS-layer concern, not core).

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Extract the proxy creation logic from SelectQueryFactory.getQueryShape()
into a standalone createProxiedPathBuilder() function. Add PropertyPath
value object and WhereCondition type as minimal foundations for Phase 2.

All 477 existing tests pass with no behavioral changes.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Break the implementation phases into concrete tasks with:
- Dependency graph showing Phase 3a/3b can run in parallel
- Detailed task descriptions per phase
- Test specifications with named test cases and assertions
- Validation commands (tsc --noEmit, npm test)
- Stubs for parallel execution noted
- Phase 1 marked as complete

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Documents the four test invariants that must hold across all
implementation phases: existing test regression, new code coverage,
Fuseki integration compatibility, and zero TypeScript errors.

https://claude.ai/code/session_01G1WX9eoMGi6n9P8eMNW3uT
Immutable, PromiseLike QueryBuilder that produces identical IR to the
existing DSL (verified via 12 IR equivalence tests). Delegates to
SelectQueryFactory internally for guaranteed correctness.

New files:
- src/queries/QueryBuilder.ts — fluent API: from, select, selectAll,
  where, orderBy/sortBy, limit, offset, for, forAll, one, build, exec
- src/tests/query-builder.test.ts — 28 tests (immutability, IR
  equivalence, walkPropertyPath, shape resolution, PromiseLike)

Changes:
- src/queries/PropertyPath.ts — add walkPropertyPath(shape, path)
- src/index.ts — export QueryBuilder, PropertyPath, walkPropertyPath
- jest.config.js — add query-builder.test.ts to testMatch
- docs/plans/001-dynamic-queries.md — mark Phase 2 complete

Tasks 2.3/2.4 (Shape.select() rewire, factory deprecation) deferred to
Phase 4 — requires threading result types through QueryBuilder generics.

505 tests pass, tsc --noEmit clean, zero regressions.

https://claude.ai/code/session_01JCtsSZg1vcWZ5jzhgH4TYy
Phase 3a — FieldSet:
- Immutable composable collection of PropertyPaths for shape field selection
- Static constructors: for() (string/callback/PropertyPath), all(), merge()
- Composition: select(), add(), remove(), set(), pick()
- Nesting support via object form: { friends: ['name', 'hobby'] }
- Integrated with QueryBuilder via .select(fieldSet) and .fields()

Phase 3b — Mutation builders:
- CreateBuilder: immutable fluent builder for create mutations with .set()/.withId()
- UpdateBuilder: immutable fluent builder with .for()/.set(), guards on missing target
- DeleteBuilder: immutable builder for delete mutations
- All implement PromiseLike for await-triggered execution
- All delegate to existing factories for identical IR generation

Tests: 39 new tests (544 total passing), IR equivalence verified against DSL.

https://claude.ai/code/session_01JCtsSZg1vcWZ5jzhgH4TYy
…ilder

- FieldSet.toJSON() serializes shape IRI + dot-separated field paths
- FieldSet.fromJSON() reconstructs via getShapeClass() + walkPropertyPath()
- QueryBuilder.toJSON() serializes shape, fields, limit, offset, subject, orderDirection
- QueryBuilder.fromJSON() reconstructs builder from JSON with FieldSet integration
- Export new types: FieldSetJSON, FieldSetFieldJSON, QueryBuilderJSON
- 14 new serialization tests (558 total passing)

Dead code cleanup (4.4) deferred: PatchedQueryPromise, patchResultPromise,
and nextTick removal blocked by Shape.select() DSL rewire type complexity.

https://claude.ai/code/session_01JCtsSZg1vcWZ5jzhgH4TYy
Phases 1-4 all complete. 558 tests passing total.
Dead code cleanup (PatchedQueryPromise, nextTick removal) deferred
due to Shape.select() DSL rewire type complexity.

https://claude.ai/code/session_01JCtsSZg1vcWZ5jzhgH4TYy
…code cleanup)

Break deferred Phase 4.4 into 6 sub-phases (4.4a–4.4f) with code examples,
files to edit, validation steps, and dependency graph.

https://claude.ai/code/session_01JCtsSZg1vcWZ5jzhgH4TYy
…oval

- Add type invariant: result types must stay identical, query.types.test.ts
  is source of truth, no test weakening allowed
- Add query-builder.types.test.ts requirement mirroring DSL type tests
- Mark Shape.query() for removal in 4.4e (breaking change, document in changelog)
- Clarify fallback strategy: DSL types must never regress even if QueryBuilder
  types fall back to any

https://claude.ai/code/session_01JCtsSZg1vcWZ5jzhgH4TYy
Type probe confirms QueryResponseToResultType resolves correctly through
class generics and Awaited<PromiseLike>. Replaces monolithic 4.4a with
6 incremental steps, each independently verifiable.

https://claude.ai/code/session_01JCtsSZg1vcWZ5jzhgH4TYy
claude added 24 commits March 10, 2026 00:07
…le TODOs

- Remove commented where() method (SelectQuery.ts)
- Remove commented TestNode/convertOriginal code blocks (SelectQuery.ts)
- Remove abandoned TestNode approach (SelectQuery.ts)
- Remove debug console.log(lim) from limit() method (SelectQuery.ts)
- Remove commented console.error and old proxy return (SelectQuery.ts)
- Remove old countable logic comments (SelectQuery.ts)
- Remove stale "strange bug" TODO comment (SelectQuery.ts)
- Remove commented validation in convertNodeReference (MutationQuery.ts)
- Strip commented body from ensureShapeConstructor, keep passthrough stub (ShapeClass.ts)

All 619 tests pass, no functional changes.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
…d never errors

14.1: Type RawSelectInput.shape as structural type, removing 2 'as any' casts
      in desugarSelectQuery
14.3: Add branded error types to never fallthroughs in QueryResponseToResultType,
      GetQueryObjectResultType, and ToQueryPrimitive for better IDE diagnostics
14.2: Skipped — constraining QResult's Object generic to Record<string, unknown>
      cascades through SubProperties in 4+ types and conflicts with
      QueryResponseToResultType's union return type. Not worth the churn.

All 619 tests pass, tsc clean.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
…into QueryPrimitive

Remove 4 empty subclasses — all logic was already in QueryPrimitive<T>.
- Remove abstract from QueryPrimitive (now concrete)
- Replace all new QueryString/Number/Boolean/Date with new QueryPrimitive<T>
- Update instanceof QueryString → instanceof QueryPrimitive
- Update SetSize to extend QueryPrimitive<number> directly
- Update ToQueryPrimitive and all conditional types
- Update comments

All 619 tests pass, tsc clean.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Types are stable and well-tested — risk/reward unfavorable for a cleanup pass.
Created docs/ideas/011-query-type-system-refactor.md with full analysis:
decision trees, branch counts, proposed decomposition, and validation strategy.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
The monkey-patched getQueryPaths on FieldSet was never called —
getComponentQueryPaths() catches FieldSet via instanceof before
the duck-type check, and fieldSetToSelectPath + entryToQueryPath
already handle parent path nesting through entry.path.segments.

- Remove two monkey-patch assignments in QueryShapeSet.select() and QueryShape.select()
- Remove optional getQueryPaths property declaration from FieldSet
- Keep duck-type checks (they target QueryBuilder, which has getQueryPaths as a real method)

All 619 tests pass, tsc clean.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Expanded from a single phase into 18A-D after analyzing the 3 SelectPath
consumers (QueryBuilder pipeline, getQueryPaths, preload system). Each
sub-phase is independently shippable with its own validation strategy.

18A: Direct FieldSet → Desugar conversion (additive, parity tests)
18B: Switch QueryBuilder to new path
18C: Refactor preload to use FieldSet directly
18D: Delete old SelectPath types and bridge functions

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
…desugar

Eliminates the entire SelectPath intermediate representation layer:
- fieldSetToSelectPath() / entryToQueryPath() bridge functions (DELETED)
- SelectPath, CustomQueryObject, SubQueryPaths, ComponentQueryPath types (DELETED)
- BoundComponent.getComponentQueryPaths() and getPropertyPath() (DELETED)
- QueryBuilder.getQueryPaths() (DELETED)
- RawSelectInput.select replaced with RawSelectInput.entries

The desugar pass now converts FieldSetEntry[] directly to DesugaredSelection[],
removing the wasteful serialize-then-reparse roundtrip through SelectPath.

Preload entries now store preloadSubSelect (FieldSet) instead of
preloadQueryPath (opaque QueryStep[] from BoundComponent.getPropertyPath()).

Pipeline: FieldSet entries → desugarSelectQuery → canonicalize → lower → IR

Net: -165 lines, 619 tests pass, tsc clean.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Two related cleanups:

1. parentQueryPath?: any → parentSegments?: PropertyShape[]
   - forSubSelect() now receives PropertyShape[] directly (via
     collectPropertySegments) instead of QueryStep[] from getPropertyPath()
   - convertTraceResult() no longer duck-types QueryStep objects to
     extract PropertyShape — uses parentSegments directly
   - collectPropertySegments() made public for use by SelectQuery.ts

2. SortByPath.paths: QueryPropertyPath[] → PropertyPath[]
   - evaluateSortCallback() now builds PropertyPath directly via
     collectPropertySegments instead of calling getPropertyPath()
   - toSortBy() in IRDesugar reads path.segments directly instead
     of duck-typing through toSelectionPath()
   - Removed `as any` cast on sortBy in QueryBuilder

toSelectionPath() is now only used by the where clause path.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Replace duck-typed unknown/any handling in toSelectionPath() and
toWhereArg() with proper type imports and type guards for QueryStep,
PropertyQueryStep, SizeStep, and ArgPath from SelectQuery.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Define ShapeConstructor<S> — a concrete (non-abstract) constructor type
that includes static properties (shape, targetClass). Use it as:
- The `this` parameter in all Shape static methods (select, selectAll,
  update, create, delete, mapPropertyShapes, getSetOf)
- The shape field type in all Builders and their Init interfaces
- The parameter type in mutation factory constructors
- The parameter type in resolveShape, createProxiedPathBuilder,
  evaluateSortCallback, and FieldSet methods

This eliminates the root cause of most `as any` casts: the mismatch
between ShapeType (abstract new) and what runtime code needs (concrete
new + static property access).

Cast count: ~44 → ~31 in query files + Shape.ts. Remaining casts are
inherent to proxy/dynamic patterns (callback generics, dynamic property
access by string key).

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
ShapeType (with abstract new) is no longer imported anywhere.
ShapeConstructor fully replaces it at all runtime boundaries.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Remove open questions from completed phases (13-19), condense Phase 18
sub-phases into summary, mark Phase 19 complete, update dependency graph,
update Phase 11 status. Keep all context of what was done and why.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
1. Change getShapeClass() return type from `typeof Shape` to
   `ShapeConstructor | undefined` — eliminates `as unknown as
   ShapeConstructor` casts at call sites (resolveShape.ts, FieldSet.ts)
   and removes `as any` casts for `new shapeClass()` (SelectQuery.ts).

2. Update FieldSet.resolveShapeInput() to look up ShapeConstructor from
   NodeShape via getShapeClass(), so NodeShape callers get the full
   ProxiedPathBuilder proxy (sub-selects, .size(), evaluations, etc.)
   instead of the limited string-only fallback.

3. Delete traceFieldsFromCallback — the old simple proxy that only
   captured top-level string keys. No longer needed now that NodeShape
   inputs resolve to ShapeConstructor and use the full proxy path.

All 619 tests pass, tsc clean.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
- FieldSetEntry: explain subSelect vs preloadSubSelect distinction
- QueryPrimitive: document consolidation from former subclasses
- SHACL.ts: explain Shape-as-unknown cast in prototype-walking
- extractComponentFieldSet: document supported component interfaces
- getPackageShape: clarify return type and use case

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
New documentation covering:
- QueryBuilder: fluent API, shape class + IRI string modes, PromiseLike,
  targeting entities, sorting/limiting, FieldSet integration
- FieldSet: composable field selections, string/callback/IRI creation,
  nested fields, composition (add/remove/pick/merge), inspection
- Mutation Builders: CreateBuilder, UpdateBuilder, DeleteBuilder as
  programmatic equivalents of Shape.create/update/delete
- JSON serialization: round-trip toJSON/fromJSON for queries and field sets
- Use case examples: CMS, API gateway, component composition, progressive loading

Also updates feature overview list and "Linked core offers" bullets.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Shape.select(id, callback) → Shape.select(callback).for(id)
Shape.update(id, data) → Shape.update(data).for(id)
Shape.selectAll(id) → Shape.selectAll().for(id)

.for(id) now unwraps the array result type (returns single object,
not array) to match the old single-subject overload behavior.

- Updated all query-fixtures and test call sites to new API
- Added 10 new tests for .for() and .forAll() chaining patterns
- Updated README examples throughout
- Cleaned up unused imports (QShape, QResult, ICoreIterable) from Shape.ts

All 629 tests pass, tsc clean.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Documents all breaking changes and new features:
- Shape.select/update id argument removed (use .for())
- ShapeType → ShapeConstructor rename
- QueryPrimitive consolidation
- SelectPath IR types removed
- New: QueryBuilder, FieldSet composition, JSON serialization,
  PropertyPath export, mutation builders

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
Report at docs/reports/008-dynamic-queries.md covers architecture,
public API, breaking changes, file map, and test coverage.

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
- Add .for(ids) fluent method matching UpdateBuilder's pattern
- Make ids optional in .from() (backwards compatible)
- Move empty-ids validation from constructor to .build() for consistency
- Add immutability test for .for()
- Update tests to exercise both .for() and .from() paths

https://claude.ai/code/session_01P1bPzwN55G6NHXVH1dDQpV
@flyon flyon changed the title Replace SelectQueryFactory with immutable QueryBuilder + FieldSet architecture Linked 2.0 - introducing dynamic query building. QueryBuilder, FieldSet, mutation builders. Mar 10, 2026
@flyon flyon changed the base branch from claude/workflow-skill-phase-2-K2exJ to dev March 10, 2026 07:35
@flyon flyon merged commit 55fab20 into dev Mar 10, 2026
2 checks passed
@flyon flyon deleted the claude/linked-2-cleanup-K01bt branch March 10, 2026 08:24
@flyon flyon restored the claude/linked-2-cleanup-K01bt branch March 10, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants