Non-REST & RPC
This is a proposal. Where REST conventions don't fit, the engine lets you say exactly what an endpoint does - verbs in the path, methods used however the real service uses them, and a raw-query escape hatch for the rest.
Verbs in the path
RPC-style and action endpoints name the operation in the URL. The HTTP method is whatever the real service chose; the operation: says what actually happens.
- path: /users/:guid/deactivate
method: POST
operation: update # not "create" - the method doesn't decide
key: guid
response: { schema: User }
- path: /rpc/getUser
method: POST # an RPC call that reads
operation: get
key: guid # bound from the request body
response: { schema: User } A POST that deletes
Plenty of real APIs delete via POST (or never expose DELETE at all). That's a one-line override:
- path: /users/:guid/remove
method: POST
operation: delete
key: guid
response: { schema: User }
status: 204 Custom operations
Some actions aren't CRUD at all - counters, toggles, bulk jobs. The proposed operation: query with an inline statement is the escape hatch when the structured operations can't express the behavior.
Inline custom queries
A parameterized SQL statement, with placeholders bound from path parameters, the request body, and the query string:
- path: /users/:guid/posts
method: GET
operation: query
query: |
SELECT * FROM posts WHERE authorGuid = :guid AND published = 1
response: { type: array, schema: Post } This is deliberately the trapdoor, not the front door - reach for it only when the declarative model falls short.
Open questions
- Safety of raw SQL. Placeholders must be bound (never interpolated). Do we allow only
SELECTfor reads, and a separate write form for mutations? How do we stop aquery:from dropping a table? - Dialect coupling. Raw
query:ties a config to SQLite's SQL. Is that acceptable, or do we want a structuredwhere:filter that stays engine-agnostic? - Binding sources. Precedence when a placeholder name exists in both the path and the body.
- Body-keyed lookups. For RPC reads, how is the key value located in an arbitrary JSON body (a path expression)?