Skip to content

Commit 14450ca

Browse files
authored
feat: idiomatic graphql api (#769)
* feat: idiomatic graphql api * feat: idiomatic graphql api tests * fix: idiomatic tests * fix: code rabbit suggestion
1 parent f0513a5 commit 14450ca

9 files changed

Lines changed: 1796 additions & 139 deletions

File tree

docs/docs/Infrastructure/eVault.md

Lines changed: 124 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ A **MetaEnvelope** is the top-level container for an entity (post, user, message
7373
Each field in a MetaEnvelope becomes a separate **Envelope** node in Neo4j:
7474

7575
- **id**: Unique identifier
76-
- **ontology**: The field name from the ontology schema (e.g., "content", "authorId", "createdAt") - this identifies which field in the schema this envelope represents
76+
- **fieldKey**: The field name from the payload (e.g., "content", "authorId", "createdAt") - this identifies which field in the payload this envelope represents
77+
- **ontology**: Alias for fieldKey (kept for backward compatibility)
7778
- **value**: The actual field value (string, number, object, array)
78-
- **valueType**: Type of the value ("string", "number", "object", "array") - cached from the ontology schema for optimization purposes
79+
- **valueType**: Type of the value ("string", "number", "object", "array")
7980

8081
### Storage Structure
8182

@@ -97,75 +98,95 @@ This flat graph structure allows:
9798

9899
## GraphQL API
99100

100-
eVault exposes a GraphQL API at `/graphql` for all data operations.
101+
eVault exposes a GraphQL API at `/graphql` for all data operations. All operations require the `X-ENAME` header to identify the eVault owner.
102+
103+
**Required Header for all operations:**
104+
```http
105+
X-ENAME: @user-a.w3id
106+
```
101107

102108
### Queries
103109

104-
#### getMetaEnvelopeById
110+
#### metaEnvelope
105111

106-
Retrieve a specific MetaEnvelope by its global ID.
112+
Retrieve a single MetaEnvelope by its ID.
107113

108114
**Query**:
109115
```graphql
110116
query {
111-
getMetaEnvelopeById(id: "global-id-123") {
117+
metaEnvelope(id: "global-id-123") {
112118
id
113119
ontology
114120
parsed
115121
envelopes {
116122
id
117-
ontology
123+
fieldKey
118124
value
119125
valueType
120126
}
121127
}
122128
}
123129
```
124130

125-
**Headers Required**:
126-
- `X-ENAME`: The W3ID of the eVault owner
127-
- `Authorization: Bearer <token>`: Platform authentication token
131+
#### metaEnvelopes
128132

129-
#### findMetaEnvelopesByOntology
130-
131-
Find all MetaEnvelopes of a specific ontology type.
133+
Retrieve MetaEnvelopes with cursor-based pagination and optional filtering.
132134

133135
**Query**:
134136
```graphql
135137
query {
136-
findMetaEnvelopesByOntology(ontology: "550e8400-e29b-41d4-a716-446655440001") {
137-
id
138-
ontology
139-
parsed
138+
metaEnvelopes(
139+
filter: {
140+
ontologyId: "550e8400-e29b-41d4-a716-446655440001"
141+
search: {
142+
term: "hello"
143+
caseSensitive: false
144+
mode: CONTAINS
145+
}
146+
}
147+
first: 10
148+
after: "cursor-string"
149+
) {
150+
edges {
151+
cursor
152+
node {
153+
id
154+
ontology
155+
parsed
156+
}
157+
}
158+
pageInfo {
159+
hasNextPage
160+
hasPreviousPage
161+
startCursor
162+
endCursor
163+
}
164+
totalCount
140165
}
141166
}
142167
```
143168

144-
#### searchMetaEnvelopes
169+
**Filter Options**:
170+
- `ontologyId`: Filter by ontology schema ID
171+
- `search.term`: Search term to match against envelope values
172+
- `search.caseSensitive`: Whether search is case-sensitive (default: false)
173+
- `search.fields`: Specific field names to search within (optional)
174+
- `search.mode`: `CONTAINS`, `STARTS_WITH`, or `EXACT` (default: CONTAINS)
145175

146-
Search MetaEnvelopes by content within a specific ontology. The `term` parameter performs case-sensitive text matching against the string values in any of the envelopes within MetaEnvelopes of the specified ontology. The search looks for the term within envelope values (not field names).
147-
148-
**Query**:
149-
```graphql
150-
query {
151-
searchMetaEnvelopes(ontology: "550e8400-e29b-41d4-a716-446655440001", term: "hello") {
152-
id
153-
ontology
154-
parsed
155-
}
156-
}
157-
```
176+
**Pagination**:
177+
- `first` / `after`: Forward pagination
178+
- `last` / `before`: Backward pagination
158179

159180
### Mutations
160181

161-
#### storeMetaEnvelope
182+
#### createMetaEnvelope
162183

163-
Store a new MetaEnvelope in the eVault.
184+
Create a new MetaEnvelope. Returns a structured payload with the created entity or errors.
164185

165186
**Mutation**:
166187
```graphql
167188
mutation {
168-
storeMetaEnvelope(input: {
189+
createMetaEnvelope(input: {
169190
ontology: "550e8400-e29b-41d4-a716-446655440001"
170191
payload: {
171192
content: "Hello, world!"
@@ -179,56 +200,29 @@ mutation {
179200
id
180201
ontology
181202
parsed
203+
envelopes {
204+
id
205+
fieldKey
206+
value
207+
}
182208
}
183-
envelopes {
184-
id
185-
value
186-
valueType
209+
errors {
210+
field
211+
message
212+
code
187213
}
188214
}
189215
}
190216
```
191217

192-
**Headers Required**:
193-
- `X-ENAME`: The W3ID of the eVault owner (required)
194-
- `Authorization: Bearer <token>`: Optional, but recommended for webhook delivery
195-
196-
**Response Structure**:
218+
#### updateMetaEnvelope
197219

198-
The `envelopes` array in the response contains one envelope per field in the payload, where each envelope's `ontology` field contains the field name from the schema. For example, storing a post with `content`, `authorId`, and `createdAt` fields produces:
199-
200-
```json
201-
{
202-
"envelopes": [
203-
{
204-
"ontology": "content",
205-
"value": "Hello, world!"
206-
},
207-
{
208-
"ontology": "authorId",
209-
"value": "..."
210-
},
211-
{
212-
"ontology": "createdAt",
213-
"value": "2025-01-24T10:00:00Z"
214-
},
215-
{
216-
"ontology": "mediaUrls",
217-
"value": []
218-
}
219-
],
220-
"id": "9a84e965-2604-52bf-97a7-5c4f4151fea2"
221-
}
222-
```
223-
224-
#### updateMetaEnvelopeById
225-
226-
Update an existing MetaEnvelope.
220+
Update an existing MetaEnvelope. Returns a structured payload with the updated entity or errors.
227221

228222
**Mutation**:
229223
```graphql
230224
mutation {
231-
updateMetaEnvelopeById(
225+
updateMetaEnvelope(
232226
id: "global-id-123"
233227
input: {
234228
ontology: "550e8400-e29b-41d4-a716-446655440001"
@@ -244,21 +238,50 @@ mutation {
244238
ontology
245239
parsed
246240
}
241+
errors {
242+
message
243+
code
244+
}
247245
}
248246
}
249247
```
250248

251-
#### deleteMetaEnvelope
249+
#### removeMetaEnvelope
252250

253-
Delete a MetaEnvelope and all its Envelopes.
251+
Delete a MetaEnvelope and all its Envelopes. Returns a structured payload confirming deletion.
254252

255253
**Mutation**:
256254
```graphql
257255
mutation {
258-
deleteMetaEnvelope(id: "global-id-123")
256+
removeMetaEnvelope(id: "global-id-123") {
257+
deletedId
258+
success
259+
errors {
260+
message
261+
code
262+
}
263+
}
259264
}
260265
```
261266

267+
### Legacy API
268+
269+
The following queries and mutations are preserved for backward compatibility but are considered legacy. New integrations should use the idiomatic API above.
270+
271+
#### Legacy Queries
272+
273+
- `getMetaEnvelopeById(id: String!)` - Use `metaEnvelope(id: ID!)` instead
274+
- `findMetaEnvelopesByOntology(ontology: String!)` - Use `metaEnvelopes(filter: {ontologyId: ...})` instead
275+
- `searchMetaEnvelopes(ontology: String!, term: String!)` - Use `metaEnvelopes(filter: {search: ...})` instead
276+
- `getAllEnvelopes` - Returns all envelopes (no pagination)
277+
278+
#### Legacy Mutations
279+
280+
- `storeMetaEnvelope(input: MetaEnvelopeInput!)` - Use `createMetaEnvelope` instead
281+
- `updateMetaEnvelopeById(id: String!, input: MetaEnvelopeInput!)` - Use `updateMetaEnvelope` instead
282+
- `deleteMetaEnvelope(id: String!)` - Use `removeMetaEnvelope` instead (returns `Boolean!`)
283+
- `updateEnvelopeValue(envelopeId: String!, newValue: JSON!)` - Update individual envelope value
284+
262285
## HTTP API
263286

264287
### /whois
@@ -451,26 +474,48 @@ The provisioning layer supports shared tenancy (multiple W3IDs can be provisione
451474

452475
## API Examples
453476

454-
### Storing a Post
477+
### Creating a Post
455478

456479
```bash
457480
curl -X POST http://localhost:4000/graphql \
458481
-H "Content-Type: application/json" \
459482
-H "X-ENAME: @user-a.w3id" \
460483
-d '{
461-
"query": "mutation { storeMetaEnvelope(input: { ontology: \"550e8400-e29b-41d4-a716-446655440001\", payload: { content: \"Hello!\", authorId: \"...\", createdAt: \"2025-01-24T10:00:00Z\" }, acl: [\"*\"] }) { metaEnvelope { id ontology } } }"
484+
"query": "mutation { createMetaEnvelope(input: { ontology: \"550e8400-e29b-41d4-a716-446655440001\", payload: { content: \"Hello!\", authorId: \"...\", createdAt: \"2025-01-24T10:00:00Z\" }, acl: [\"*\"] }) { metaEnvelope { id ontology } errors { message } } }"
462485
}'
463486
```
464487

465-
### Querying Posts
488+
### Querying Posts with Pagination
466489

467490
```bash
468491
curl -X POST http://localhost:4000/graphql \
469492
-H "Content-Type: application/json" \
470493
-H "X-ENAME: @user-a.w3id" \
471494
-H "Authorization: Bearer <token>" \
472495
-d '{
473-
"query": "{ findMetaEnvelopesByOntology(ontology: \"550e8400-e29b-41d4-a716-446655440001\") { id parsed } }"
496+
"query": "{ metaEnvelopes(filter: { ontologyId: \"550e8400-e29b-41d4-a716-446655440001\" }, first: 10) { edges { node { id parsed } } pageInfo { hasNextPage endCursor } } }"
497+
}'
498+
```
499+
500+
### Searching Posts
501+
502+
```bash
503+
curl -X POST http://localhost:4000/graphql \
504+
-H "Content-Type: application/json" \
505+
-H "X-ENAME: @user-a.w3id" \
506+
-d '{
507+
"query": "{ metaEnvelopes(filter: { ontologyId: \"550e8400-e29b-41d4-a716-446655440001\", search: { term: \"hello\", mode: CONTAINS } }, first: 10) { edges { node { id parsed } } totalCount } }"
508+
}'
509+
```
510+
511+
### Deleting a Post
512+
513+
```bash
514+
curl -X POST http://localhost:4000/graphql \
515+
-H "Content-Type: application/json" \
516+
-H "X-ENAME: @user-a.w3id" \
517+
-d '{
518+
"query": "mutation { removeMetaEnvelope(id: \"global-id-123\") { deletedId success errors { message } } }"
474519
}'
475520
```
476521

docs/docs/W3DS Basics/getting-started.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,17 @@ The adapter makes an HTTP POST request to the eVault's GraphQL endpoint. The req
106106

107107
**GraphQL Mutation**:
108108
```graphql
109-
mutation StoreMetaEnvelope($input: MetaEnvelopeInput!) {
110-
storeMetaEnvelope(input: $input) {
109+
mutation CreateMetaEnvelope($input: MetaEnvelopeInput!) {
110+
createMetaEnvelope(input: $input) {
111111
metaEnvelope {
112112
id
113113
ontology
114114
parsed
115115
}
116+
errors {
117+
message
118+
code
119+
}
116120
}
117121
}
118122
```
@@ -133,7 +137,7 @@ mutation StoreMetaEnvelope($input: MetaEnvelopeInput!) {
133137
}
134138
```
135139

136-
**Response**: The eVault returns the created MetaEnvelope with a global ID that should be stored for future reference.
140+
**Response**: The eVault returns a payload containing the created MetaEnvelope with a global ID (or errors if the operation failed). The ID should be stored for future reference.
137141

138142
**Implementation Notes**:
139143
- Use any HTTP client library in your language (requests in Python, http in Go, fetch in JavaScript, etc.)

0 commit comments

Comments
 (0)