Skip to content

feat(pipeline-builder): complete typed expression and accumulator helpers#318

Merged
wmadden merged 23 commits intomainfrom
tml-2217-complete-typed-expression-and-accumulator-helpers-for
Apr 8, 2026
Merged

feat(pipeline-builder): complete typed expression and accumulator helpers#318
wmadden merged 23 commits intomainfrom
tml-2217-complete-typed-expression-and-accumulator-helpers-for

Conversation

@wmadden
Copy link
Copy Markdown
Contributor

@wmadden wmadden commented Apr 8, 2026

closes TML-2217

Key snippet (new capability)

// Named-args expression helper with typed keys and values
fn.dateToString({
  date: f.createdAt,            // TypedAggExpr<DateField> — only date fields accepted
  format: fn.literal('%Y-%m-%d'), // TypedAggExpr<StringField>
}); // → TypedAggExpr<StringField>

// Named-args accumulator helper
acc.firstN({
  input: f.name,     // TypedAggExpr<DocField>
  n: fn.literal(2),  // TypedAggExpr<NumericField> — wrong types rejected at compile time
}); // → TypedAccumulatorExpr<ArrayField>

Intent

Expand the pipeline builder's fn (expression) and acc (accumulator) helpers from a starter set (~10 each) to the full MongoDB aggregation operator set (~65 expression helpers, ~10 new accumulators). This requires extending the AST to support named-argument operators natively, since many MongoDB operators (date, string, regex, top/bottom accumulators) take { key: expr } records rather than positional arrays.

Change map

The story

  1. Extend the AST to support named-argument operators. MongoAggOperator.args and MongoAggAccumulator.arg are widened to accept Readonly<Record<string, MongoAggExpr>> alongside existing forms. Constructors freeze record args for immutability. rewrite() recurses into record entries. Type guards (isExprArray, isRecordArgs) are exported from query-ast and reused by the lowering module (eliminating prior duplication).

  2. Add internal factory functions for each return-type category. expression-helpers.ts gains numericUnaryExpr, booleanExpr, booleanUnaryExpr, dateUnaryExpr, arrayExpr, arrayUnaryExpr, docUnaryExpr, nullableDocUnaryExpr, and namedArgsExpr — reducing ~65 helpers to one-liner delegations.

  3. Add ~65 expression helpers across 7 categories. Date (13), string (13), comparison (7), array (11), set (7), type (10), and object (4) operators, all on the flat fn namespace. Named-args operators like dateToString, trim, regexMatch accept typed records; positional operators accept typed varargs.

  4. Add ~10 accumulator helpers. stdDevPop, stdDevSamp (single-arg), plus N-variant (firstN, lastN, maxN, minN) and top/bottom (top, bottom, topN, bottomN) accumulators that use named-arg records.

  5. Narrow named-args signatures for input type safety. All ~20 named-args helpers use specific record types with per-key field type constraints (e.g. date: TypedAggExpr<DateField>, n: TypedAggExpr<NumericField>), rejecting wrong types at compile time. Negative type tests verify this.

  6. Add nullable precision for array-element accessors. firstElem, lastElem, and arrayElemAt return TypedAggExpr<NullableDocField> instead of DocField, reflecting that these operators can return null on empty arrays or out-of-bounds indices.

  7. Update lowering to handle record args. The adapter lowering visitor branches on isRecordArgs() to produce { [op]: { key1: lowered, key2: lowered } } for both operators and accumulators.

Behavior changes & evidence

  • AST accepts named-argument records: MongoAggOperator and MongoAggAccumulator now accept Readonly<Record<string, MongoAggExpr>> as args, with immutable freezing and full rewrite traversal.

    • Why: MongoDB operators like $dateToString, $trim, $topN require named arguments. Without AST support, these operators could not be represented in the typed pipeline builder.
    • Implementation: aggregation-expressions.ts
    • Tests: aggregation-expressions.test.ts — "constructs with record args", "freezes record args", "rewrites record-arg operator/accumulator children"
  • ~65 new typed expression helpers on fn: Adds date, string, comparison, array, set, type, and object operator helpers, each returning the correct TypedAggExpr<F> for its MongoDB output type. Named-args helpers use specific record types with per-key constraints.

  • ~10 new typed accumulator helpers on acc: Adds stdDevPop, stdDevSamp, firstN, lastN, maxN, minN, top, bottom, topN, bottomN with correct return types and named-arg signatures.

  • firstElem, lastElem, arrayElemAt return nullable types: These operators return TypedAggExpr<NullableDocField> (nullable: true), reflecting that they can return null when the array is empty or the index is out of bounds.

    • Implementation: expression-helpers.tsnullableDocUnaryExpr factory + arrayElemAt method
    • Tests: expression-helpers.test-d.ts — "arrayElemAt returns NullableDocField", "firstElem returns NullableDocField", "lastElem returns NullableDocField"
  • Type guards exported and deduplicated: isExprArray and isRecordArgs are now exported from @prisma-next/mongo-query-ast instead of being duplicated in the lowering module.

  • New field types: BooleanField, DateField, NullableDocField: Added to types.ts and exported from the package index.

    • Tests: types.test-d.ts — "BooleanField resolves to boolean", "DateField resolves to Date"

Compatibility / migration / risk

  • No breaking changes to existing APIs. All type widenings are additive. Existing fn.* and acc.* helpers retain their signatures unchanged.
  • New type exports (BooleanField, DateField, NullableDocField, NullableNumericField, NumericField, StringField, ArrayField) are additive.
  • arrayElemAt/firstElem/lastElem nullable change: These are new helpers introduced in this branch, so no downstream breakage.

Follow-ups / open questions

  • Accumulator output type precision ($sum on int returning int instead of double) is deferred and tracked separately.
  • $accumulator (custom JavaScript accumulator) is out of scope.
  • Close-out: Migrate long-lived docs from projects/ to docs/ and delete the transient project directory once the parent project completes.

Non-goals / intentionally out of scope

  • New dedicated AST node classes for named-args operators — the generic MongoAggOperator with record args suffices.
  • Removing existing dedicated AST nodes (MongoAggArrayFilter, MongoAggMap, MongoAggReduce, MongoAggLet) — they serve a purpose (variable binding semantics) and remain as-is.

Summary by CodeRabbit

  • New Features

    • Support for named-argument (record-style) aggregation operators and accumulators; added stdDevPop/stdDevSamp plus push, addToSet, count, top/bottom and N-variants.
    • 40+ new expression helpers across dates, strings, comparisons, arrays, sets, type conversions and object/field operations.
    • New field-type aliases (boolean, date, nullable) for stronger typing.
  • Documentation

    • Helpers reference and developer guide updated with named-arg conventions and examples.
  • Tests

    • Extensive unit, type-level, and integration tests added/updated.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds record-style (named) argument support for aggregation operators and accumulators: new AggRecordArgs type and isRecordArgs guard, AST freezing/rewriting for record args, query-builder helpers producing record args, lowering that handles record args, and tests/docs exercising the feature.

Changes

Cohort / File(s) Summary
Core AST Extensions
packages/2-mongo-family/4-query/query-ast/src/aggregation-expressions.ts
Add AggRecordArgs, isRecordArgs, rewriteRecordArgs; allow operator/accumulator args to be record-form; update constructors/.of to shallow-copy/freeze record args; extend rewrite logic; add stdDevPop/stdDevSamp.
AST Exports
packages/2-mongo-family/4-query/query-ast/src/exports/index.ts
Re-export AggRecordArgs, isExprArray, and new isRecordArgs runtime guards.
Query AST Tests
packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test-d.ts, packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test.ts
Update type assertions to include record args; add runtime tests for operator/accumulator record args, freezing behavior, and rewriter application to record values.
Query Builder Types & Exports
packages/2-mongo-family/5-query-builders/pipeline-builder/src/types.ts, .../src/exports/index.ts
Add BooleanField, DateField, NullableDocField; extend LiteralValue mapping; re-export additional field/type aliases.
Expression Helpers
packages/2-mongo-family/5-query-builders/pipeline-builder/src/expression-helpers.ts
Introduce typed internal factories and namedArgsExpr; convert literal to typed overloads; add many new fn.* helpers including named-argument variants that emit record-form args.
Accumulator Helpers
packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts
Add namedAccumulatorArgs; make acc helpers generic/typed; add push, addToSet, count, stdDevPop, stdDevSamp, and N-/top/bottom variants using named-record args.
Query Builder Docs
packages/2-mongo-family/5-query-builders/pipeline-builder/DEVELOPING.md, packages/2-mongo-family/5-query-builders/pipeline-builder/README.md
Document typed helper factories, named-argument calling conventions, new helpers, and field type aliases.
Query Builder Tests — Helpers
packages/2-mongo-family/5-query-builders/pipeline-builder/test/*
Add extensive type-level and runtime tests for new fn and acc helpers: type assertions, op names, array vs record arg shapes, and lowering coverage.
Adapter Lowering
packages/3-mongo-target/2-mongo-adapter/src/lowering.ts, packages/3-mongo-target/2-mongo-adapter/test/lowering.test.ts
Import isExprArray/isRecordArgs; lower operator/accumulator record args via lowerExprRecord; treat acc.arg === null explicitly; add tests for record-argument lowering.
Integration Tests & Fixtures
test/integration/test/mongo/pipeline-builder.test.ts, packages/2-mongo-family/5-query-builders/pipeline-builder/test/fixtures/test-contract.ts
Add createdAt date field and codec mappings for mongo/date@1/mongo/bool@1; update fixtures and integration tests to exercise named-arg expression and accumulator helpers.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant QB as QueryBuilder
    participant AST as AggregationAST
    participant Adapter as MongoAdapter
    participant Output as BSON

    User->>QB: call fn.dateToString({ date, format })
    QB->>AST: MongoAggOperator.of('$dateToString', { date, format })
    AST->>AST: shallow-copy & freeze record args
    AST-->>QB: Operator node (record args)

    User->>QB: call acc.topN({ output, sortBy, n })
    QB->>AST: MongoAggAccumulator.of('$topN', { output, sortBy, n })
    AST->>AST: shallow-copy & freeze record arg
    AST-->>QB: Accumulator node (record arg)

    QB->>Adapter: lower aggregation expression tree
    Adapter->>Adapter: isRecordArgs? → lowerExprRecord recursively
    Adapter-->>Output: { $dateToString: { date: ..., format: ... } }
    Adapter-->>Output: { $topN: { output: ..., sortBy: ..., n: ... } }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇
I nibble keys and hop through nodes,
Named args tucked in tiny codes.
Builders stitch and ASTs hold tight,
Lowering turns them into sight.
Frozen records, neat and bright.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(pipeline-builder): complete typed expression and accumulator helpers' is clear, specific, and accurately describes the main change—expanding the pipeline builder with comprehensive typed helpers for MongoDB aggregation expressions and accumulators.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2217-complete-typed-expression-and-accumulator-helpers-for

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 8, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-runtime@318

@prisma-next/family-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-mongo@318

@prisma-next/sql-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-runtime@318

@prisma-next/family-sql

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-sql@318

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-paradedb@318

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-pgvector@318

@prisma-next/postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/postgres@318

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-orm-client@318

@prisma-next/target-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-mongo@318

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-mongo@318

@prisma-next/driver-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-mongo@318

@prisma-next/contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract@318

@prisma-next/utils

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/utils@318

@prisma-next/config

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/config@318

@prisma-next/errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/errors@318

@prisma-next/framework-components

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/framework-components@318

@prisma-next/operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/operations@318

@prisma-next/contract-authoring

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract-authoring@318

@prisma-next/ids

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ids@318

@prisma-next/psl-parser

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-parser@318

@prisma-next/psl-printer

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-printer@318

@prisma-next/cli

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/cli@318

@prisma-next/emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/emitter@318

@prisma-next/migration-tools

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/migration-tools@318

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/vite-plugin-contract-emit@318

@prisma-next/runtime-executor

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/runtime-executor@318

@prisma-next/mongo-codec

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-codec@318

@prisma-next/mongo-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract@318

@prisma-next/mongo-value

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-value@318

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-psl@318

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-emitter@318

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-ast@318

@prisma-next/mongo-orm

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-orm@318

@prisma-next/mongo-pipeline-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-pipeline-builder@318

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-lowering@318

@prisma-next/mongo-wire

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-wire@318

@prisma-next/sql-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract@318

@prisma-next/sql-errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-errors@318

@prisma-next/sql-operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-operations@318

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-schema-ir@318

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-psl@318

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-ts@318

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-emitter@318

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-lane-query-builder@318

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-relational-core@318

@prisma-next/sql-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-builder@318

@prisma-next/target-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-postgres@318

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-postgres@318

@prisma-next/driver-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-postgres@318

commit: 93afc7c

@wmadden wmadden force-pushed the tml-2217-complete-typed-expression-and-accumulator-helpers-for branch from 4ee3b42 to b7f80c8 Compare April 8, 2026 16:19
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts (1)

106-112: Validate positional helper arity explicitly.

On Line 106-Line 112, casting to variadic and always passing three args can let arity bugs slip through. Add expected arg count per helper and assert op.args.length.

♻️ Proposed arity-aware test shape
-  it.each([
-    ['add', '$add'],
+  it.each([
+    ['add', '$add', 3],
     ['subtract', '$subtract'],
     ['multiply', '$multiply'],
     ...
-  ] as const)('fn.%s produces operator %s with array args', (helperName, expectedOp) => {
+  ] as const)('fn.%s produces operator %s with array args', (helperName, expectedOp, argCount) => {
     const helper = fn[helperName] as (...a: TypedAggExpr<DocField>[]) => TypedAggExpr<DocField>;
     const result = helper(d, d, d);
     expect(result.node).toBeInstanceOf(MongoAggOperator);
     const op = result.node as MongoAggOperator;
     expect(op.op).toBe(expectedOp);
     expect(Array.isArray(op.args)).toBe(true);
+    expect((op.args as ReadonlyArray<unknown>).length).toBe(argCount);
   });
As per coding guidelines, tests must verify behavior rather than loosely mirroring fixture invocation patterns.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts`
around lines 106 - 112, The test currently invokes helpers via const helper =
fn[helperName] as (...a: TypedAggExpr<DocField>[]) => TypedAggExpr<DocField> and
always calls helper(d, d, d), which hides arity bugs; add an expected-arity
lookup (e.g., a map from helperName to expectedArgCount) and assert
op.args.length === expectedArgCount after creating result (using result.node and
op as in the diff). Update the invocation to pass exactly expectedArgCount
arguments (or build an args array of length expectedArgCount) and keep the
existing assertions that op is a MongoAggOperator and op.args is an array.
packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test.ts (1)

46-50: Tighten record-arg key assertions to exact-match.

On Line 48-Line 50, the test verifies expected keys exist, but it won’t fail if unexpected keys are added. Assert exact key sets to catch arg-shape regressions.

♻️ Proposed test assertion refinement
-    const recordArg = result.node.arg as Readonly<Record<string, unknown>>;
-    for (const key of Object.keys(args)) {
-      expect(recordArg).toHaveProperty(key);
-    }
+    const recordArg = result.node.arg as Readonly<Record<string, unknown>>;
+    expect(Object.keys(recordArg).sort()).toEqual(Object.keys(args).sort());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test.ts`
around lines 46 - 50, The test currently checks only that each expected key
exists on result.node.arg (via isRecordArgs and a loop), which won't catch extra
unexpected keys; change the assertion to assert exact key sets instead: after
casting result.node.arg to Readonly<Record<string, unknown>> (as done now),
compare the set/array of Object.keys(recordArg) to Object.keys(args) for exact
equality (sort both arrays or compare as sets) so the test fails if extra or
missing keys are present; keep the existing isRecordArgs check and use the
identifiers result.node.arg, recordArg, and args to locate and update the
assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/src/expression-helpers.ts`:
- Around line 408-414: The zip() helper currently types its args.inputs as a
single TypedAggExpr<ArrayField>, but MongoDB requires inputs to be an array of
array expressions; update the zip signature so args.inputs is
TypedAggExpr<ArrayField>[] (matching variadic helpers like setUnion()), adjust
any related type references in the zip declaration and keep returning
namedArgsExpr('$zip', args, ARRAY) so the emitted { $zip: { inputs: [ ... ] } }
structure is produced.

---

Nitpick comments:
In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test.ts`:
- Around line 46-50: The test currently checks only that each expected key
exists on result.node.arg (via isRecordArgs and a loop), which won't catch extra
unexpected keys; change the assertion to assert exact key sets instead: after
casting result.node.arg to Readonly<Record<string, unknown>> (as done now),
compare the set/array of Object.keys(recordArg) to Object.keys(args) for exact
equality (sort both arrays or compare as sets) so the test fails if extra or
missing keys are present; keep the existing isRecordArgs check and use the
identifiers result.node.arg, recordArg, and args to locate and update the
assertion.

In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts`:
- Around line 106-112: The test currently invokes helpers via const helper =
fn[helperName] as (...a: TypedAggExpr<DocField>[]) => TypedAggExpr<DocField> and
always calls helper(d, d, d), which hides arity bugs; add an expected-arity
lookup (e.g., a map from helperName to expectedArgCount) and assert
op.args.length === expectedArgCount after creating result (using result.node and
op as in the diff). Update the invocation to pass exactly expectedArgCount
arguments (or build an args array of length expectedArgCount) and keep the
existing assertions that op is a MongoAggOperator and op.args is an array.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 8e5a51d5-4bf9-47d3-bceb-1d53c1355391

📥 Commits

Reviewing files that changed from the base of the PR and between 954b107 and 4ee3b42.

⛔ Files ignored due to path filters (3)
  • projects/mongo-pipeline-builder/plans/expression-accumulator-helpers-plan.md is excluded by !projects/**
  • projects/mongo-pipeline-builder/specs/expression-accumulator-helpers.spec.md is excluded by !projects/**
  • projects/mongo-pipeline-builder/specs/reviews/code-review.md is excluded by !projects/**
📒 Files selected for processing (19)
  • packages/2-mongo-family/4-query/query-ast/src/aggregation-expressions.ts
  • packages/2-mongo-family/4-query/query-ast/src/exports/index.ts
  • packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test-d.ts
  • packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/DEVELOPING.md
  • packages/2-mongo-family/5-query-builders/pipeline-builder/README.md
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/exports/index.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/expression-helpers.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/types.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/fixtures/test-contract.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/types.test-d.ts
  • packages/3-mongo-target/2-mongo-adapter/src/lowering.ts
  • packages/3-mongo-target/2-mongo-adapter/test/lowering.test.ts
  • test/integration/test/mongo/pipeline-builder.test.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/2-mongo-family/4-query/query-ast/src/aggregation-expressions.ts (1)

4-24: ⚠️ Potential issue | 🟠 Major

Widen AggRecordArgs beyond scalar values.

AggRecordArgs only permits MongoAggExpr values, so the new named-arg path cannot represent operators that need array-valued slots. The downstream $zip helper is already forced into { inputs: <expr> } / { defaults: <expr> }, and rewriteRecordArgs() bakes the same scalar assumption into traversal. Please widen the record-arg value type and recurse over nested arrays before shipping the “full operator set”.

MongoDB aggregation `$zip` operator syntax: what types are required for the `inputs` and `defaults` fields?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-mongo-family/4-query/query-ast/src/aggregation-expressions.ts`
around lines 4 - 24, AggRecordArgs is too narrow (values typed only as
MongoAggExpr) so named-arg operators that accept arrays or nested records (e.g.,
$zip.inputs/defaults) cannot be represented or rewritten; change AggRecordArgs
to allow nested arrays and records (e.g., value type = MongoAggExpr |
ReadonlyArray<MongoAggExpr> | AggRecordArgs) and update isRecordArgs and
rewriteRecordArgs to handle those cases by recursing: when a value is an array,
map/rewrite each element; when a value is a record, call rewriteRecordArgs
recursively; keep the same readonly/Record<string, ...> shapes and ensure
rewriteRecordArgs returns AggRecordArgs with rewritten nested arrays and records
using the MongoAggExpr.rewrite(rewriter) for scalar leaves.
🧹 Nitpick comments (1)
packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts (1)

115-149: Lock the $zip shape down with a dedicated case.

The named-args table only checks that each key exists. A helper-specific zip assertion that inspects inputs / defaults would catch the array-valued path that the generic smoke test currently misses.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts`
around lines 115 - 149, The generic named-args test for fn['zip']/$zip only
verifies key presence and misses validating the array shape of the
inputs/defaults; add a dedicated case for the 'zip' helper that calls the zip
helper (fn['zip']) with a typed record and then asserts op.args.inputs is an
array (and if present op.args.defaults is an array or undefined) and that each
element of inputs is the expected TypedAggExpr/element shape; reuse the existing
result.node -> MongoAggOperator extraction (as in the test) and isRecordArgs
check before inspecting recordArgs.inputs and recordArgs.defaults to ensure the
helper produces the correct array-valued structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts`:
- Around line 117-155: The top/bottom/topN/bottomN accumulator helpers accept
sortBy as TypedAggExpr<DocField> but must require a sort-spec document; update
their args so sortBy is a dedicated sort-spec type (e.g., SortSpec or
TypedSortSpec) and inside each helper wrap the provided sort spec with
MongoAggLiteral.of(...) before passing to namedAccumulatorArgs(args) so the
emitted accumulator uses a literal sort document; adjust types on top, bottom,
topN and bottomN and ensure namedAccumulatorArgs is called with the wrapped sort
value.

In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/src/expression-helpers.ts`:
- Around line 93-107: The current helpers docUnaryExpr and nullableDocUnaryExpr
incorrectly propagate the input arg._field.codecId into the returned _field,
which breaks shape-changing operators (e.g., arrayElemAt, firstElem, lastElem,
$arrayToObject) by leaving an array codec on an element/object result; update
call sites that use docUnaryExpr/nullableDocUnaryExpr (and the other similar
helpers) to provide an explicit output field descriptor instead of copying
arg._field.codecId so the returned TypedAggExpr has the correct codecId and
nullability for the transformed shape; search for usages like arrayElemAt,
firstElem, lastElem and arrayToObject and replace the helper calls with ones
that construct the proper {_field: { codecId: <correctCodecId>, nullable:
<true|false> }, node: MongoAggOperator.of(...)} or add an overload/variant that
accepts an explicit output descriptor to avoid copying the input codec.

---

Duplicate comments:
In `@packages/2-mongo-family/4-query/query-ast/src/aggregation-expressions.ts`:
- Around line 4-24: AggRecordArgs is too narrow (values typed only as
MongoAggExpr) so named-arg operators that accept arrays or nested records (e.g.,
$zip.inputs/defaults) cannot be represented or rewritten; change AggRecordArgs
to allow nested arrays and records (e.g., value type = MongoAggExpr |
ReadonlyArray<MongoAggExpr> | AggRecordArgs) and update isRecordArgs and
rewriteRecordArgs to handle those cases by recursing: when a value is an array,
map/rewrite each element; when a value is a record, call rewriteRecordArgs
recursively; keep the same readonly/Record<string, ...> shapes and ensure
rewriteRecordArgs returns AggRecordArgs with rewritten nested arrays and records
using the MongoAggExpr.rewrite(rewriter) for scalar leaves.

---

Nitpick comments:
In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts`:
- Around line 115-149: The generic named-args test for fn['zip']/$zip only
verifies key presence and misses validating the array shape of the
inputs/defaults; add a dedicated case for the 'zip' helper that calls the zip
helper (fn['zip']) with a typed record and then asserts op.args.inputs is an
array (and if present op.args.defaults is an array or undefined) and that each
element of inputs is the expected TypedAggExpr/element shape; reuse the existing
result.node -> MongoAggOperator extraction (as in the test) and isRecordArgs
check before inspecting recordArgs.inputs and recordArgs.defaults to ensure the
helper produces the correct array-valued structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ef22ab17-84c3-4e4e-87f7-72addcfbd721

📥 Commits

Reviewing files that changed from the base of the PR and between 4ee3b42 and b1e0dc5.

⛔ Files ignored due to path filters (4)
  • projects/mongo-pipeline-builder/plan.md is excluded by !projects/**
  • projects/mongo-pipeline-builder/plans/expression-accumulator-helpers-plan.md is excluded by !projects/**
  • projects/mongo-pipeline-builder/specs/expression-accumulator-helpers.spec.md is excluded by !projects/**
  • projects/mongo-pipeline-builder/specs/reviews/code-review.md is excluded by !projects/**
📒 Files selected for processing (19)
  • packages/2-mongo-family/4-query/query-ast/src/aggregation-expressions.ts
  • packages/2-mongo-family/4-query/query-ast/src/exports/index.ts
  • packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test-d.ts
  • packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/DEVELOPING.md
  • packages/2-mongo-family/5-query-builders/pipeline-builder/README.md
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/exports/index.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/expression-helpers.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/types.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/fixtures/test-contract.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/types.test-d.ts
  • packages/3-mongo-target/2-mongo-adapter/src/lowering.ts
  • packages/3-mongo-target/2-mongo-adapter/test/lowering.test.ts
  • test/integration/test/mongo/pipeline-builder.test.ts
✅ Files skipped from review due to trivial changes (8)
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/exports/index.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/types.test-d.ts
  • packages/3-mongo-target/2-mongo-adapter/test/lowering.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/types.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/README.md
  • packages/2-mongo-family/5-query-builders/pipeline-builder/DEVELOPING.md
  • packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/fixtures/test-contract.ts
  • packages/2-mongo-family/4-query/query-ast/src/exports/index.ts
  • packages/2-mongo-family/4-query/query-ast/test/aggregation-expressions.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test-d.ts
  • test/integration/test/mongo/pipeline-builder.test.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts (1)

117-156: ⚠️ Potential issue | 🟠 Major

sortBy is typed too broadly for top/bottom accumulators (Mongo contract mismatch).

At Line 119, Line 129, Line 139, and Line 150, sortBy currently accepts TypedAggExpr<DocField>, which permits invalid inputs like computed expressions. MongoDB expects a literal sort-spec document for $top/$bottom/$topN/$bottomN ({ field: 1 | -1, ... }). This should be modeled as a sort-spec input and lowered via MongoAggLiteral.of(sortSpec).

Proposed fix
 import type { MongoAggExpr } from '@prisma-next/mongo-query-ast';
-import { MongoAggAccumulator } from '@prisma-next/mongo-query-ast';
+import { MongoAggAccumulator, MongoAggLiteral } from '@prisma-next/mongo-query-ast';
@@
+type SortSpec = Readonly<Record<string, 1 | -1>>;
@@
-  top(args: {
-    output: TypedAggExpr<DocField>;
-    sortBy: TypedAggExpr<DocField>;
-  }): TypedAccumulatorExpr<DocField> {
+  top<F extends DocField>(args: {
+    output: TypedAggExpr<F>;
+    sortBy: SortSpec;
+  }): TypedAccumulatorExpr<F> {
     return {
       _field: undefined as never,
-      node: MongoAggAccumulator.of('$top', namedAccumulatorArgs(args)),
+      node: MongoAggAccumulator.of('$top', {
+        output: args.output.node,
+        sortBy: MongoAggLiteral.of(args.sortBy),
+      }),
     };
   },
@@
-  bottom(args: {
-    output: TypedAggExpr<DocField>;
-    sortBy: TypedAggExpr<DocField>;
-  }): TypedAccumulatorExpr<DocField> {
+  bottom<F extends DocField>(args: {
+    output: TypedAggExpr<F>;
+    sortBy: SortSpec;
+  }): TypedAccumulatorExpr<F> {
     return {
       _field: undefined as never,
-      node: MongoAggAccumulator.of('$bottom', namedAccumulatorArgs(args)),
+      node: MongoAggAccumulator.of('$bottom', {
+        output: args.output.node,
+        sortBy: MongoAggLiteral.of(args.sortBy),
+      }),
     };
   },
@@
-  topN(args: {
-    output: TypedAggExpr<DocField>;
-    sortBy: TypedAggExpr<DocField>;
+  topN(args: {
+    output: TypedAggExpr<DocField>;
+    sortBy: SortSpec;
     n: TypedAggExpr<NumericField>;
   }): TypedAccumulatorExpr<ArrayField> {
     return {
       _field: undefined as never,
-      node: MongoAggAccumulator.of('$topN', namedAccumulatorArgs(args)),
+      node: MongoAggAccumulator.of('$topN', {
+        output: args.output.node,
+        sortBy: MongoAggLiteral.of(args.sortBy),
+        n: args.n.node,
+      }),
     };
   },
@@
-  bottomN(args: {
-    output: TypedAggExpr<DocField>;
-    sortBy: TypedAggExpr<DocField>;
+  bottomN(args: {
+    output: TypedAggExpr<DocField>;
+    sortBy: SortSpec;
     n: TypedAggExpr<NumericField>;
   }): TypedAccumulatorExpr<ArrayField> {
     return {
       _field: undefined as never,
-      node: MongoAggAccumulator.of('$bottomN', namedAccumulatorArgs(args)),
+      node: MongoAggAccumulator.of('$bottomN', {
+        output: args.output.node,
+        sortBy: MongoAggLiteral.of(args.sortBy),
+        n: args.n.node,
+      }),
     };
   },
MongoDB docs: for aggregation accumulators $top, $bottom, $topN, and $bottomN, what is the required type/shape of the sortBy argument?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts`
around lines 117 - 156, The top/bottom/topN/bottomN accumulators accept a
too-broad TypedAggExpr<DocField> for the sortBy argument; change the sortBy
param type to a literal sort-spec type (a document mapping field names to 1 |
-1) and ensure you wrap the provided sort-spec with MongoAggLiteral.of(...)
before passing into MongoAggAccumulator.of inside namedAccumulatorArgs; update
the signatures in top, bottom, topN, and bottomN and the call sites so sortSpec
is modeled and lowered as a literal per MongoDB's required sort-spec shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts`:
- Around line 117-156: The top/bottom/topN/bottomN accumulators accept a
too-broad TypedAggExpr<DocField> for the sortBy argument; change the sortBy
param type to a literal sort-spec type (a document mapping field names to 1 |
-1) and ensure you wrap the provided sort-spec with MongoAggLiteral.of(...)
before passing into MongoAggAccumulator.of inside namedAccumulatorArgs; update
the signatures in top, bottom, topN, and bottomN and the call sites so sortSpec
is modeled and lowered as a literal per MongoDB's required sort-spec shape.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: be4be4e5-9f68-4f9d-9ea8-9bedd476a883

📥 Commits

Reviewing files that changed from the base of the PR and between b1e0dc5 and 8489ad4.

⛔ Files ignored due to path filters (1)
  • projects/mongo-pipeline-builder/specs/reviews/code-review.md is excluded by !projects/**
📒 Files selected for processing (6)
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/accumulator-helpers.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/exports/index.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/expression-helpers.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/types.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test-d.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/exports/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/accumulator-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/types.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/test/expression-helpers.test-d.ts
  • packages/2-mongo-family/5-query-builders/pipeline-builder/src/expression-helpers.ts

@wmadden wmadden force-pushed the tml-2217-complete-typed-expression-and-accumulator-helpers-for branch from dec26d4 to f15fd4e Compare April 8, 2026 17:59
wmadden added 23 commits April 8, 2026 20:09
Task spec and execution plan for completing the typed expression
and accumulator helpers in the pipeline builder. Key design decision:
extend MongoAggOperator and MongoAggAccumulator to support named
arguments natively rather than working around the limitation.
…goAggAccumulator

Widen MongoAggOperator.args and MongoAggAccumulator.arg to accept
Readonly<Record<string, MongoAggExpr>> alongside existing forms.
Update rewrite() to recurse into record entries and lowering to
produce { [op]: { key: lowered } } for the record form.

This unblocks all named-args expression and accumulator helpers
(e.g. $dateToString, $topN).
Add BooleanField (mongo/bool@1) and DateField (mongo/date@1) types
to the pipeline builder and export them from the package. Add codec
entries to test fixtures and type tests verifying ResolveRow.
Add ~65 typed expression helpers covering date (13), string (13),
comparison (7), array (11), set (7), type (10), and object (4)
operators. Named-arg operators use the record-form MongoAggOperator.
Each helper is verified by a type test for its TypedAggExpr<F>
return type.
Add stdDevPop/stdDevSamp (NullableNumericField), firstN/lastN/maxN/minN
(named args, ArrayField), and top/bottom/topN/bottomN (named args,
DocField/ArrayField) to the acc object. Includes static factory methods
on MongoAggAccumulator and type tests for every new helper.
Document all expression and accumulator helpers in README.md with
categorized tables. Add named-argument pattern and field type alias
reference to DEVELOPING.md.
…m lowering

The type guards isExprArray and isRecordArgs were duplicated between
query-ast and adapter-mongo with identical logic. Export them (and the
AggRecordArgs type alias) from query-ast public API so downstream
consumers can import rather than copy.
Parameterized tests verify that each helper produces the correct
MongoDB operator string and argument shape (unary, positional array,
or named-args record). Covers all 65 expression helpers and 10 new
accumulator helpers.
Extend the pipeline-builder integration test with a createdAt date
field and 4 representative tests covering the novel patterns:
- fn.dateToString (named-args date expression)
- fn.trim (named-args string expression)
- fn.gt in $cond (positional boolean expression)
- acc.firstN (named-args accumulator)
$first, $last (array forms), and $arrayElemAt can return null when the
array is empty or the index is out of bounds. Add NullableDocField type
alias and use it as the return type for these three helpers.
Replace Record<string, TypedAggExpr<DocField>> with per-helper record
types that enforce correct keys and per-key field types (DateField,
StringField, NumericField, etc.). This catches typos, missing required
keys, and wrong value types at compile time.
Replace Record<string, TypedAggExpr<DocField>> with per-helper record
types enforcing correct keys and value types (e.g. n requires
NumericField). Consistent with the expression helper narrowing.
Use properly typed fixtures (s for StringField, dt for DateField, n for
NumericField, etc.) in named-args helper call sites. Add negative type
tests verifying that wrong-type arguments are rejected at compile time.
Introduce typed fixtures (s for StringField, n for NumericField, dt for
DateField, arr for ArrayField) in the named-args test tables so runtime
tests pass with the narrowed helper signatures.
All five code review findings (F01-F05) are now resolved. The deferred
items section is removed as no items remain deferred.
… deferred

These were listed as "deferred" but TML-2217 is the final task in the
project — there is nothing to defer to. Reclassify them as known gaps
in the spec, code review, and parent plan.
Add LiteralValue<F> conditional type that maps known field types to
their JS value types (StringField->string, NumericField->number, etc.).
Use overloads for direct inference from value type, plus a generic
overload with LiteralValue<F> for contextual inference. Unknown codec
IDs fall through to unknown (escape hatch for custom codecs).
$sum returns the same numeric type as its input (unlike $avg/$stdDev
which always return doubles). Making the signature generic preserves
integer/decimal codecs through the type system.
All originally-identified known gaps are now addressed. Update task
spec, parent spec, parent plan, and task plan to reflect that fn.literal
type safety and acc.sum codec preservation are resolved. Mark TML-2217
as complete in parent spec status table.
Add fallback branch that checks for codecId directly on the field
object, not only under type.kind.scalar. This fixes integration test
contracts that use simplified field shapes (MongoContract intersection)
where the ContractFieldType union prevents the scalar branch from
matching.
@wmadden wmadden force-pushed the tml-2217-complete-typed-expression-and-accumulator-helpers-for branch from 5f6f1d9 to 93afc7c Compare April 8, 2026 18:10
@wmadden wmadden merged commit acb428f into main Apr 8, 2026
16 checks passed
@wmadden wmadden deleted the tml-2217-complete-typed-expression-and-accumulator-helpers-for branch April 8, 2026 18:23
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.

1 participant