djazzkit 0.0.1
Django ORM, everywhere — generate TypeScript types from Django models
Requires Python: >=3.13

# djazzkit Django Package

Django adapter for djazzkit sync/query/rpc functionality.

## Conflict Resolution v2

Conflict handling is opt-in per model and preserves current LWW behavior by default.

### Configure per model

```python
from djazzkit.models import SyncModel
from djazzkit.sync_meta import SyncScope, ConflictMode

class Note(SyncModel):
    # ... fields ...

    class SyncMeta:
        scope = SyncScope.USER
        owner_field = "author"
        conflict_mode = ConflictMode.REVISION_TREE
```

### Optional server-side resolver hook

```python
def note_conflict_resolver(ctx: dict) -> dict | None:
    # Return resolved data to auto-resolve, or None to keep conflict unresolved.
    return None

class Note(SyncModel):
    class SyncMeta:
        scope = SyncScope.USER
        owner_field = "author"
        conflict_mode = ConflictMode.REVISION_TREE
        conflict_resolver = staticmethod(note_conflict_resolver)
```

### Surface unresolved conflicts

```python
conflicted_notes = Note.objects.with_conflicts()
```

### TypeScript side

Generated model classes provide:
- `Model.getConflicts(instanceId)`
- `Model.resolveConflict(instanceId, resolvedData)`
- `Model.conflict(row)._conflicts`
- `Model.conflict(row).resolveConflict(...)`

Rows continue to serve the deterministic winner by default while losing revisions
remain retained until explicitly resolved.

## Remote query safety bounds

Remote query AST validation enforces server-side maxima for pagination inputs:

- `DJAZZKIT_QUERY_MAX_LIMIT` (default: `1000`)
- `DJAZZKIT_QUERY_MAX_OFFSET` (default: `10000`)

Requests above these bounds are rejected with explicit validation codes:

- `limit_out_of_range`
- `offset_out_of_range`

Example settings:

```python
DJAZZKIT_QUERY_MAX_LIMIT = 500
DJAZZKIT_QUERY_MAX_OFFSET = 5000
```

## WebSocket auth and origin hardening

WebSocket auth/origin behavior is configurable:

- `DJAZZKIT_WS_ALLOW_ANONYMOUS_FALLBACK` (default: `True`)
  - When `False`, missing/invalid auth rejects the socket instead of downgrading to anonymous.
- `DJAZZKIT_WS_REQUIRE_ORIGIN` (default: `False`)
  - When `True`, sockets without an `Origin` header are rejected.

Recommended production settings:

```python
DJAZZKIT_WS_ALLOW_ANONYMOUS_FALLBACK = False
DJAZZKIT_WS_REQUIRE_ORIGIN = True
```

For trusted non-browser clients that cannot send `Origin`, keep
`DJAZZKIT_WS_REQUIRE_ORIGIN = False` and enforce transport-level trust.

## Schema mismatch signaling

`djazzkit` now signals schema incompatibility over the sync websocket:

- Client sends `schema_version` in `sync_start` (via `@djazzkit/core` `SyncClientOptions.schemaVersion`)
- Server compares it to the current runtime schema hash
- On mismatch, server responds with:
  - `{"type":"schema_mismatch","client_schema_version":"...","server_schema_version":"...","message":"..."}`

`@djazzkit/core` exposes `onSchemaMismatch` so apps can choose their policy:

- reset local SQLite and resync
- run custom migration logic to preserve local data
- prompt user before taking action

## Migration note

If internal djazzkit tables were previously created through `--run-syncdb`, run:

```bash
python manage.py migrate djazzkit --fake-initial
```

when adopting migration-managed internal tables.