Metadata-Version: 2.4
Name: strawberry-django-nestjs
Version: 0.1.0
Summary: Expose Django models over GraphQL in the nestjs-query convention — the stock @refinedev/nestjs-query refine data provider drives Strawberry/Django, composing strawberry-django + strawberry-django-aggregates.
Author: strawberry-django-nestjs maintainers
License: AGPL-3.0-or-later
Project-URL: Homepage, https://github.com/ang-ee/strawberry-django-nestjs
Project-URL: Documentation, https://github.com/ang-ee/strawberry-django-nestjs/blob/main/CONTRACT.md
Project-URL: Issues, https://github.com/ang-ee/strawberry-django-nestjs/issues
Keywords: django,strawberry,graphql,nestjs,nestjs-query,refine,crud,aggregate,data-provider
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Django :: 6.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Database
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.14
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: django>=6.0
Requires-Dist: strawberry-graphql>=0.315.0
Requires-Dist: strawberry-graphql-django>=0.82.0
Requires-Dist: strawberry-django-aggregates>=0.6.0
Dynamic: license-file

# strawberry-django-nestjs

Expose Django models over GraphQL in the **nestjs-query** convention, so the
**stock** [`@refinedev/nestjs-query`](https://refine.dev/docs/data/packages/nestjs-query/)
refine data provider drives a Strawberry/Django backend with **no patching**.

It is a thin adapter: it *composes*
[`strawberry-django`](https://strawberry-django.readthedocs.io) (filters,
ordering, pagination) and
[`strawberry-django-aggregates`](https://github.com/ang-ee/strawberry-django-aggregates)
(the `compute_aggregation` primitive) and emits the exact GraphQL shape the
refine provider speaks. One unmodified frontend data provider, any Django
model. The precise target SDL is in [`CONTRACT.md`](./CONTRACT.md).

## Why

refine's nestjs-query provider expects a specific GraphQL shape per resource:
a `notes(filter, sorting, paging): NoteConnection` list, a `note(id): Note`
detail, `createOneNote` / `updateOneNote` / `deleteOneNote` mutations,
`NoteFilter` operator objects, `NoteSort` + `SortDirection`, `OffsetPaging`,
and a `noteAggregate` group-by surface. This library emits all of it from your
Strawberry types — you keep the stock provider, no custom mapping layer.

## Install

```sh
pip install strawberry-django-nestjs
# or
uv add strawberry-django-nestjs
```

Requires Python 3.14+, Django 6.0+, and a Strawberry/strawberry-django stack
(installed transitively).

## Quickstart

Declare the per-model nestjs types from your Strawberry types, then compose the
adapter's helpers in plain resolvers. (Condensed from
[`examples/demo_schema.py`](./examples/demo_schema.py), which exercises every
surface.)

```python
import enum
import strawberry
import strawberry_django
from strawberry import UNSET, auto

from strawberry_django_nestjs import (
    AggregateField, Connection, OffsetPaging, SortDirection, SortNulls,
    apply_sorting, build_aggregate_types, filter_to_q, input_to_dict,
    make_aggregate_resolver, paginate,
)
from strawberry_django_nestjs.comparisons import (
    NumberFieldComparison, StringFieldComparison,
)

from .models import Note  # your Django model


@strawberry_django.type(Note)
class NoteType:           # GraphQL type name `Note`
    id: auto
    title: auto
    word_count: auto
    status: auto


@strawberry.input
class NoteFilter:
    title: StringFieldComparison | None = UNSET
    status: StringFieldComparison | None = UNSET
    word_count: NumberFieldComparison | None = UNSET
    and_: list["NoteFilter"] | None = strawberry.field(name="and", default=UNSET)
    or_: list["NoteFilter"] | None = strawberry.field(name="or", default=UNSET)


@strawberry.enum
class NoteSortFields(enum.Enum):   # field value = the Django column name
    title = "title"
    word_count = "word_count"


@strawberry.input
class NoteSort:
    field: NoteSortFields
    direction: SortDirection
    nulls: SortNulls | None = UNSET


def base_qs():
    # Apply your row-level (e.g. REBAC) scoping here — reads run on this.
    return Note.objects.all()


# Declare the aggregate surface once; the same list builds the
# `<Model>Aggregate` types and maps GraphQL (camelCase) names to columns.
AGG_FIELDS = [
    AggregateField("title", str),
    AggregateField("word_count", int, numeric=True),
    AggregateField("status", str),
]
NoteAggregateResponse, _blocks = build_aggregate_types("Note", AGG_FIELDS)
note_aggregate = make_aggregate_resolver(
    NoteAggregateResponse, _blocks, AGG_FIELDS,
    base_queryset=base_qs, filter_to_q=filter_to_q,
)


@strawberry.type
class Query:
    @strawberry.field
    def notes(
        self,
        filter: NoteFilter | None = None,
        sorting: list[NoteSort] | None = None,
        paging: OffsetPaging | None = None,
    ) -> Connection[NoteType]:
        qs = apply_sorting(base_qs().filter(filter_to_q(filter)), sorting)
        return Connection(nodes=list(paginate(qs, paging)), total_count=qs.count())

    @strawberry.field
    def note(self, id: strawberry.ID) -> NoteType | None:
        return base_qs().filter(pk=id).first()

    @strawberry.field
    def note_aggregate(
        self, info: strawberry.Info, filter: NoteFilter | None = None
    ) -> list[NoteAggregateResponse]:
        return note_aggregate(info, filter=filter)
```

`createOne` / `updateOne` / `deleteOne` mutations follow the same pattern, using
`input_to_dict` to translate the nestjs input envelope into model kwargs. Your
`<Model>Update` input type is the authoritative writable-field allowlist (keep
server-owned columns out of it); run the `updateOne` read-modify-write inside
`transaction.atomic()` and `save(update_fields=…)` so a patch touches only the
columns it set (plus any `auto_now` columns you want refreshed, which
`update_fields` otherwise skips) — see
[`examples/demo_schema.py`](./examples/demo_schema.py).

## The five surfaces

| Surface | Module | What it emits / does |
| --- | --- | --- |
| Filtering | `comparisons`, `filtering` | `<Model>Filter` operator objects → a Django `Q` |
| Sorting | `sorting`, `types` | `[<Model>Sort!]` + `SortDirection`/`SortNulls` → `.order_by()` |
| Pagination | `connection` | `OffsetPaging` + `<Model>Connection { nodes, totalCount }` |
| Mutations | `mutations` | `createOne`/`updateOne`/`deleteOne` input envelope → model kwargs |
| Aggregation | `aggregation` | selection-driven `<Model>Aggregate` group-by, composing `compute_aggregation` |

## Proof: the stock provider drives it

[`examples/`](./examples/) is a runnable proof that the unmodified
`@refinedev/nestjs-query` provider drives a schema built with this library
(`getList` / `getOne` / `create` / `update` / `deleteOne`), no patching.

## Status

Beta (v0.1.0). The public API (`__init__` exports) and the emitted SDL shape
follow [`CONTRACT.md`](./CONTRACT.md) and are stable for early adopters; minor
iteration is expected before a 1.0 stability commitment. Runtime: Python 3.14,
Django 6.0.

## Documentation

- Target SDL contract: [`CONTRACT.md`](./CONTRACT.md)
- Architecture and contributor guide: [`AGENTS.md`](./AGENTS.md)

## License

AGPL-3.0-or-later. See [`LICENSE`](./LICENSE).
