Skip to content

Commit b89de65

Browse files
authored
feat: add write operations, example applications, and comprehensive documentation (#129) (#180)
1 parent 8bc2a3e commit b89de65

190 files changed

Lines changed: 52573 additions & 556 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/docs/QUICK_REFERENCE.md

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# CloudKit Quick Reference for MistKit Development
2+
3+
## REST API Endpoints (webservices.md)
4+
5+
### Base URL Structure
6+
```
7+
https://api.apple-cloudkit.com/database/{version}/{container}/{environment}/{database}/{operation}
8+
```
9+
10+
### Authentication
11+
12+
**API Token (User-based)**
13+
```
14+
Headers:
15+
X-Apple-CloudKit-Request-KeyID: [api-token]
16+
X-Apple-CloudKit-Request-ISO8601Date: [timestamp]
17+
X-Apple-CloudKit-Request-SignatureV1: [signature]
18+
```
19+
20+
**Server-to-Server Key**
21+
```
22+
Headers:
23+
X-Apple-CloudKit-Request-KeyID: [key-id]
24+
X-Apple-CloudKit-Request-ISO8601Date: [timestamp]
25+
X-Apple-CloudKit-Request-SignatureV1: [signature]
26+
Body: Included in signature
27+
```
28+
29+
### Common Endpoints
30+
31+
| Operation | Path | Method |
32+
|-----------|------|--------|
33+
| Query Records | `/records/query` | POST |
34+
| Modify Records | `/records/modify` | POST |
35+
| Lookup Records | `/records/lookup` | POST |
36+
| Record Changes | `/records/changes` | POST |
37+
| List Zones | `/zones/list` | POST |
38+
| Modify Zones | `/zones/modify` | POST |
39+
| Current User | `/users/current` | GET |
40+
| Upload Asset | `/assets/upload` | POST |
41+
42+
### Request Format (POST endpoints)
43+
```json
44+
{
45+
"operations": [
46+
{
47+
"operationType": "create",
48+
"record": {
49+
"recordType": "Article",
50+
"fields": {
51+
"title": {
52+
"value": "Hello World"
53+
}
54+
}
55+
}
56+
}
57+
]
58+
}
59+
```
60+
61+
### Response Format
62+
```json
63+
{
64+
"records": [
65+
{
66+
"recordName": "unique-id",
67+
"recordType": "Article",
68+
"fields": {
69+
"title": {
70+
"value": "Hello World"
71+
}
72+
},
73+
"created": {
74+
"timestamp": 1234567890,
75+
"userRecordName": "_user-id"
76+
},
77+
"modified": {
78+
"timestamp": 1234567890,
79+
"userRecordName": "_user-id"
80+
},
81+
"recordChangeTag": "etag-value"
82+
}
83+
]
84+
}
85+
```
86+
87+
### Error Response
88+
```json
89+
{
90+
"serverErrorCode": "INVALID_ARGUMENTS",
91+
"reason": "Detailed error message",
92+
"uuid": "request-id"
93+
}
94+
```
95+
96+
---
97+
98+
## CloudKit Field Types (webservices.md)
99+
100+
| Type | Description | Example |
101+
|------|-------------|---------|
102+
| `STRING` | Text string | `{"value": "Hello"}` |
103+
| `INT64` | Integer | `{"value": 42}` |
104+
| `DOUBLE` | Floating point | `{"value": 3.14}` |
105+
| `BYTES` | Binary data | `{"value": "base64..."}` |
106+
| `DATE` | Timestamp | `{"value": 1234567890000}` |
107+
| `LOCATION` | Coordinates | `{"value": {"latitude": 37.7, "longitude": -122.4}}` |
108+
| `REFERENCE` | Record ref | `{"value": {"recordName": "id", "action": "NONE"}}` |
109+
| `ASSET` | File reference | `{"value": {"fileChecksum": "...", "size": 1024, "downloadURL": "..."}}` |
110+
| `STRING_LIST` | Array of strings | `{"value": ["a", "b"]}` |
111+
| `INT64_LIST` | Array of ints | `{"value": [1, 2, 3]}` |
112+
| `DOUBLE_LIST` | Array of doubles | `{"value": [1.1, 2.2]}` |
113+
| `DATE_LIST` | Array of dates | `{"value": [123, 456]}` |
114+
| `LOCATION_LIST` | Array of locations | `{"value": [{"latitude": ...}, ...]}` |
115+
| `REFERENCE_LIST` | Array of refs | `{"value": [{"recordName": "id1"}, ...]}` |
116+
117+
---
118+
119+
## Query Filters (cloudkitjs.md adapted)
120+
121+
### Filter Comparators
122+
- `EQUALS`, `NOT_EQUALS`
123+
- `LESS_THAN`, `LESS_THAN_OR_EQUALS`
124+
- `GREATER_THAN`, `GREATER_THAN_OR_EQUALS`
125+
- `IN`, `NOT_IN`
126+
- `BEGINS_WITH`, `NOT_BEGINS_WITH`
127+
- `CONTAINS_ALL_TOKENS`
128+
- `LIST_CONTAINS`, `NOT_LIST_CONTAINS`
129+
130+
### Query Structure
131+
```json
132+
{
133+
"query": {
134+
"recordType": "Article",
135+
"filterBy": [
136+
{
137+
"comparator": "EQUALS",
138+
"fieldName": "status",
139+
"fieldValue": {"value": "published"}
140+
}
141+
],
142+
"sortBy": [
143+
{
144+
"fieldName": "createdAt",
145+
"ascending": false
146+
}
147+
]
148+
},
149+
"zoneID": {
150+
"zoneName": "_defaultZone"
151+
},
152+
"resultsLimit": 100
153+
}
154+
```
155+
156+
---
157+
158+
## Swift Testing Patterns (testing-enablinganddisabling.md)
159+
160+
### Basic Test
161+
```swift
162+
@Test("Description of what is tested")
163+
func testFeature() async throws {
164+
let result = await someAsyncOperation()
165+
#expect(result == expectedValue)
166+
}
167+
```
168+
169+
### Parameterized Test
170+
```swift
171+
@Test("Validate multiple inputs", arguments: [1, 2, 3, 4, 5])
172+
func testWithParameter(value: Int) {
173+
#expect(value > 0)
174+
}
175+
```
176+
177+
### Conditional Tests
178+
```swift
179+
@Test("Only run on macOS", .enabled(if: Platform.current == .macOS))
180+
func macOSTest() { }
181+
182+
@Test("Skip when feature disabled", .disabled("Feature not ready"))
183+
func disabledTest() { }
184+
```
185+
186+
### Async Expectations
187+
```swift
188+
@Test func testAsync() async throws {
189+
let result = try await apiCall()
190+
#expect(result.status == .success)
191+
#expect(result.data != nil)
192+
}
193+
```
194+
195+
### Required Values (halts on nil)
196+
```swift
197+
@Test func testRequired() throws {
198+
let value = try #require(optionalValue) // Stops if nil
199+
#expect(value.count > 0)
200+
}
201+
```
202+
203+
### Known Issues
204+
```swift
205+
@Test("Test with known bug", .bug(id: "12345"))
206+
func testWithBug() {
207+
// Test that tracks a known issue
208+
}
209+
```
210+
211+
### Test Suites
212+
```swift
213+
@Suite("Feature X Tests")
214+
struct FeatureXTests {
215+
@Test func testA() { }
216+
@Test func testB() { }
217+
}
218+
```
219+
220+
---
221+
222+
## MistKit Type Mapping
223+
224+
### CloudKit → Swift
225+
226+
| CloudKit Type | Swift Type |
227+
|---------------|------------|
228+
| `STRING` | `String` |
229+
| `INT64` | `Int` |
230+
| `DOUBLE` | `Double` |
231+
| `BYTES` | `Data` |
232+
| `DATE` | `Date` (milliseconds since epoch) |
233+
| `LOCATION` | `CLLocationCoordinate2D` or custom struct |
234+
| `REFERENCE` | Custom `CKReference` struct |
235+
| `ASSET` | Custom `CKAsset` struct with URL |
236+
| `*_LIST` | `[T]` arrays |
237+
238+
### Swift API Design Patterns
239+
240+
**Container Access**
241+
```swift
242+
let container = CloudKitService.container(identifier: "...")
243+
let database = container.publicDatabase
244+
```
245+
246+
**Record Operations**
247+
```swift
248+
// Create
249+
let record = CKRecord(type: "Article")
250+
record["title"] = "Hello"
251+
try await database.save(record)
252+
253+
// Query
254+
let query = CKQuery(recordType: "Article", predicate: ...)
255+
let results = try await database.perform(query)
256+
257+
// Modify
258+
record["title"] = "Updated"
259+
try await database.save(record)
260+
```
261+
262+
**Async Sequences for Pagination**
263+
```swift
264+
for try await record in database.records(matching: query) {
265+
process(record)
266+
}
267+
```
268+
269+
---
270+
271+
## Common Error Codes (webservices.md)
272+
273+
| Code | Meaning | Action |
274+
|------|---------|--------|
275+
| `AUTHENTICATION_REQUIRED` | Not authenticated | Obtain web auth token |
276+
| `INVALID_ARGUMENTS` | Bad request data | Check request format |
277+
| `NOT_FOUND` | Record doesn't exist | Handle gracefully |
278+
| `CONFLICT` | Record changed | Resolve conflict |
279+
| `ATOMIC_ERROR` | Batch partially failed | Check individual results |
280+
| `ZONE_NOT_FOUND` | Zone doesn't exist | Create zone first |
281+
| `THROTTLED` | Rate limited | Implement backoff |
282+
| `INTERNAL_ERROR` | Server error | Retry with backoff |
283+
284+
---
285+
286+
## Authentication Flow
287+
288+
### User Authentication (API Token)
289+
1. Call `/tokens/create` with API token
290+
2. Receive `webAuthToken`
291+
3. Include in subsequent requests
292+
4. Token expires after 1 hour
293+
5. Refresh before expiry
294+
295+
### Server-to-Server
296+
1. Generate key pair
297+
2. Upload public key to CloudKit Dashboard
298+
3. Sign requests with private key
299+
4. Include signature in headers
300+
301+
---
302+
303+
## Development Checklist
304+
305+
### Before implementing an endpoint:
306+
- [ ] Check `webservices.md` for exact endpoint path and parameters
307+
- [ ] Review `cloudkitjs.md` for operation semantics
308+
- [ ] Design Swift types matching CloudKit structures
309+
- [ ] Plan async/await API surface
310+
- [ ] Consider error handling paths
311+
312+
### Before writing tests:
313+
- [ ] Review `testing-enablinganddisabling.md` for patterns
314+
- [ ] Use `@Test` macro, not XCTest
315+
- [ ] Use `#expect()` and `#require()` for assertions
316+
- [ ] Test async code with `async throws`
317+
- [ ] Consider parameterized tests for multiple cases
318+
319+
### Code review:
320+
- [ ] All types are `Sendable`
321+
- [ ] All network calls use `async/await`
322+
- [ ] Errors conform to `LocalizedError`
323+
- [ ] Public APIs have tests
324+
- [ ] Swift Testing patterns used correctly

0 commit comments

Comments
 (0)