nautilus-orm-protocol 0.1.3

Wire protocol types and serialization for Nautilus ORM
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# Nautilus Protocol

JSON-RPC 2.0 protocol for multi-language Nautilus clients.

## Overview

This crate defines the stable wire format for communication between:
- **Language-specific clients** (JavaScript, Python, etc.)
- **Nautilus engine** (Rust binary running over stdin/stdout)

### Public API

| Module | Key exports |
|--------|------------|
| `wire` | `RpcRequest`, `RpcResponse`, `RpcError`, `RpcId`, `ok()`, `err()` |
| `methods` | 11 method-name constants (`ENGINE_HANDSHAKE`, `QUERY_*`, `SCHEMA_VALIDATE`), request param structs, response structs |
| `error` | `ProtocolError` enum (18 variants), stable error-code constants, `Result<T>` alias |
| `version` | `PROTOCOL_VERSION` constant, `ProtocolVersion` wrapper |

### Design Notes

- **Single consumer** — only `nautilus-engine` depends on this crate today, but the API is designed for any language client that speaks JSON-RPC 2.0 over stdin/stdout.
- **`schema.validate`** types are defined but not yet implemented in the engine; they are reserved for a future release.
- **Value encoding** (base64 for bytes, string for decimals, etc.) is specified in this README as the wire-format contract, but the Rust encoding/decoding logic lives in `nautilus-engine`, not here.

### Protocol Stack

- **Transport**: Line-delimited JSON over stdin/stdout
- **Format**: JSON-RPC 2.0
- **Versioning**: Protocol version included in every request

## Protocol Version

Current version: **1**

All client requests must include `protocolVersion: 1` in their params. The engine will reject requests with unsupported versions.

## JSON Encoding

The protocol uses a stable JSON encoding for Nautilus `Value` types to ensure cross-language compatibility.

### Value Type Mappings

> **Note:** These encoding rules define the stable wire format between clients
> and the engine. The actual encoding/decoding logic lives in the engine layer,
> not in this crate — `nautilus-protocol` only defines the request/response
> structures and their `Serialize` / `Deserialize` implementations.

| Nautilus Type | JSON Type | Example | Notes |
|--------------|-----------|---------|-------|
| `Null` | `null` | `null` | |
| `Bool` | `boolean` | `true`, `false` | |
| `I32` | `number` | `42` | 32-bit signed integer |
| `I64` | `number` | `9007199254740991` | 64-bit signed integer* |
| `F64` | `number` | `3.14159` | IEEE 754 double |
| `String` | `string` | `"hello"` | UTF-8 text |
| `Bytes` | `string` | `"SGVsbG8="` | **Base64 encoded** |
| `Decimal` | `string` | `"123.45"` | **String to avoid precision loss** |
| `DateTime` | `string` | `"2026-02-18T10:30:00Z"` | **RFC3339 / ISO 8601** |
| `Uuid` | `string` | `"550e8400-e29b-41d4-a716-446655440000"` | **Hyphenated lowercase** |
| `Json` | any | `{"key": "value"}` | Pass-through JSON value |
| `Array` | `array` | `["a", "b", "c"]` | Homogeneous array |
| `Array2D` | `array` | `[["a", "b"], ["c", "d"]]` | 2D array |

**\*Note on BigInt**: JavaScript clients should be aware of `Number.MAX_SAFE_INTEGER` (2^53 - 1). For values outside this range, the engine may return strings in future protocol versions.

### Example Value Encodings

```json
{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "age": 25,
  "balance": "1234.56",
  "createdAt": "2026-02-18T10:30:00Z",
  "isActive": true,
  "avatar": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
  "tags": ["rust", "database", "orm"],
  "metadata": {
    "custom": "json",
    "nested": true
  }
}
```

## Methods

### `engine.handshake`

Initial handshake to validate protocol compatibility.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "engine.handshake",
  "params": {
    "protocolVersion": 1,
    "clientName": "nautilus-js",
    "clientVersion": "0.1.0"
  }
}
```

**Response:**
```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "engineVersion": "0.1.0",
    "protocolVersion": 1
  }
}
```

### `query.findMany`

Execute a findMany query on a model.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "query.findMany",
  "params": {
    "protocolVersion": 1,
    "model": "User",
    "args": {
      "where": {
        "email": {
          "contains": "test"
        }
      },
      "orderBy": {
        "createdAt": "desc"
      },
      "take": 10
    }
  }
}
```

**Response:**
```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "data": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "email": "test@example.com",
        "createdAt": "2026-02-18T10:30:00Z"
      }
    ]
  }
}
```

### `query.create`

Create a new record.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "query.create",
  "params": {
    "protocolVersion": 1,
    "model": "User",
    "data": {
      "email": "new@example.com",
      "name": "New User"
    }
  }
}
```

**Response:**
```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "count": 1,
    "data": [
      {
        "id": "660f9511-f3ac-52e5-b827-557766551111",
        "email": "new@example.com",
        "name": "New User"
      }
    ]
  }
}
```

### `query.findFirst`

Return the first record matching the given arguments (or `null`).

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "query.findFirst",
  "params": {
    "protocolVersion": 1,
    "model": "User",
    "args": {
      "where": { "isActive": true },
      "orderBy": { "createdAt": "desc" }
    }
  }
}
```

**Response:** Same shape as `query.findMany` (`QueryResult`).

### `query.findFirstOrThrow`

Same as `query.findFirst`, but returns error code `3004` (Record not found)
if no matching record exists.

### `query.findUnique`

Find a single record by a unique filter.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "query.findUnique",
  "params": {
    "protocolVersion": 1,
    "model": "User",
    "filter": { "id": 42 }
  }
}
```

**Response:** Same shape as `query.findMany` (`QueryResult`).

### `query.findUniqueOrThrow`

Same as `query.findUnique`, but returns error code `3004` (Record not found)
if no matching record exists.

### `query.createMany`

Create multiple records in a single operation.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "query.createMany",
  "params": {
    "protocolVersion": 1,
    "model": "User",
    "data": [
      { "email": "alice@example.com", "name": "Alice" },
      { "email": "bob@example.com", "name": "Bob" }
    ]
  }
}
```

**Response:** `MutationResult` with `count` reflecting the number of inserted rows.

### `query.update`

Update a record matching a filter.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "query.update",
  "params": {
    "protocolVersion": 1,
    "model": "User",
    "filter": { "id": 1 },
    "data": { "name": "Updated Name" }
  }
}
```

**Response:** `MutationResult` with `count` and optional `data` (when
the dialect supports `RETURNING`).

### `query.delete`

Delete a record matching a filter.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "query.delete",
  "params": {
    "protocolVersion": 1,
    "model": "Post",
    "filter": { "id": 99 }
  }
}
```

**Response:** `MutationResult` with `count`.

### `schema.validate` *(reserved — not yet implemented)*

Validate a schema string without applying it.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "schema.validate",
  "params": {
    "protocolVersion": 1,
    "schema": "model User { id Int @id\n  name String }"
  }
}
```

**Response:**
```json
{
  "jsonrpc": "2.0",
  "id": 9,
  "result": {
    "valid": true,
    "errors": null
  }
}
```

## Error Codes

### Standard JSON-RPC Errors

| Code | Message | Description |
|------|---------|-------------|
| `-32700` | Parse error | Invalid JSON received |
| `-32600` | Invalid Request | Invalid JSON-RPC request object |
| `-32601` | Method not found | Unknown method name |
| `-32602` | Invalid params | Invalid method parameters |
| `-32603` | Internal error | Internal JSON-RPC error |

### Nautilus Error Codes

| Range | Category | Example Codes |
|-------|----------|---------------|
| `1000-1999` | Schema/Validation | `1000` Schema validation<br>`1001` Invalid model<br>`1002` Invalid field<br>`1003` Type mismatch |
| `2000-2999` | Query Planning | `2000` Query planning<br>`2001` Invalid filter<br>`2002` Invalid orderBy<br>`2003` Unsupported operation |
| `3000-3999` | Database Execution | `3000` Database execution<br>`3001` Connection failed<br>`3002` Constraint violation<br>`3003` Query timeout |
| `9000-9999` | Internal Engine | `9000` Internal error<br>`9001` Unsupported protocol version<br>`9002` Invalid method<br>`9003` Invalid request params |

**Error Response Example:**
```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "error": {
    "code": 1001,
    "message": "Invalid model: UnknownModel"
  }
}
```

## Versioning Policy

### Backward Compatibility

- **Minor changes** (new optional fields, new methods): No version bump, clients ignore unknown fields
- **Breaking changes** (renamed/removed fields, changed semantics): Protocol version increment

### Client Compatibility

Clients should:
1. Send handshake with their protocol version
2. Check engine's protocol version in response
3. Abort if versions are incompatible

### Migration Path

When protocol version 2 is released:
- Engine will support both v1 and v2 simultaneously (for a transition period)
- Clients send their version in every request
- Engine responds according to requested version

## Transport

### Line-Delimited JSON

Each request and response is a single JSON object on one line, terminated by `\n`:

```
{"jsonrpc":"2.0","id":1,"method":"engine.handshake","params":{"protocolVersion":1}}\n
{"jsonrpc":"2.0","id":1,"result":{"engineVersion":"0.1.0","protocolVersion":1}}\n
{"jsonrpc":"2.0","id":2,"method":"query.findMany","params":{"protocolVersion":1,"model":"User"}}\n
```

### Multiplexing

The protocol supports concurrent in-flight requests:
- Each request has a unique `id`
- Responses may arrive out-of-order
- Clients match responses to requests by `id`

### Logging

The engine writes:
- **stdout**: JSON-RPC responses only (one per line)
- **stderr**: Debug logs, warnings, errors (not JSON)

Clients should:
- Read stdout for responses
- Optionally capture stderr for debugging

## Usage Example

```rust
use nautilus_protocol::*;
use serde_json::json;

// Create a findMany request
let request = RpcRequest {
    jsonrpc: "2.0".to_string(),
    id: Some(RpcId::Number(1)),
    method: QUERY_FIND_MANY.to_string(),
    params: serde_json::to_value(FindManyParams {
        protocol_version: PROTOCOL_VERSION,
        model: "User".to_string(),
        args: Some(json!({
            "where": { "email": { "contains": "test" } }
        })),
    }).unwrap(),
};

// Serialize to JSON line
let line = format!("{}\n", serde_json::to_string(&request).unwrap());

// Write to engine stdin...
// Read response from engine stdout...
```

## License

MIT or Apache-2.0