SPB Git

spb/ultra-sharp-agent-skills Public

Ultra-Sharp Agent Skills — a research-first skill-authoring system + 72 production-ready skills for AI agents.

Python 100%
3.8 KB · 128 lines markdown
Rendered Raw Blame History
1<!--2Author: Simon-Pierre Boucher3Contact: contact@spboucher.ai4-->56# Patterns — Designing GraphQL APIs78## Contents9- Connection SDL (Relay style)10- Mutation with union result11- DataLoader implementation12- Depth and complexity limits13- Deprecation flow14- Gotchas1516## Connection SDL (Relay style)1718```graphql19type Query {20  orders(first: Int = 20, after: String, status: OrderStatus): OrderConnection!21}2223type OrderConnection {24  edges: [OrderEdge!]!25  pageInfo: PageInfo!26  totalCount: Int          # nullable: expensive, resolve only when asked27}2829type OrderEdge {30  node: Order!31  cursor: String!32}3334type PageInfo {35  hasNextPage: Boolean!36  endCursor: String37}38```39Clamp `first` to a max of 100 in the resolver (same reasoning as REST page40limits); reject negative values with a validation error.4142## Mutation with union result4344```graphql45input CreateOrderInput {46  customerId: ID!47  items: [OrderItemInput!]!48}4950union CreateOrderResult = CreateOrderSuccess | ValidationError | OutOfStockError5152type CreateOrderSuccess { order: Order! }53type ValidationError { fields: [FieldError!]! }54type OutOfStockError { itemIds: [ID!]!, message: String! }5556type Mutation {57  createOrder(input: CreateOrderInput!): CreateOrderResult!58}59```6061Client handles each case via `... on` fragments; the top-level `errors` array62stays reserved for genuine faults (auth failure, internal error).6364## DataLoader implementation6566```python67# Python (aiodataloader); same shape in JS's dataloader package.68from aiodataloader import DataLoader6970class CustomerLoader(DataLoader):71    async def batch_load_fn(self, ids):72        rows = await db.fetch(73            "SELECT * FROM customers WHERE id = ANY($1)", ids)74        by_id = {r["id"]: r for r in rows}75        # Must return results in the SAME ORDER as ids, None for misses.76        return [by_id.get(i) for i in ids]7778# Create ONE loader per request (in context), never a module-level singleton —79# its cache would leak data across users.80async def resolve_customer(order, info):81    return await info.context["customer_loader"].load(order["customer_id"])82```8384## Depth and complexity limits8586```javascript87// graphql-depth-limit + graphql-query-complexity (npm)88validationRules: [89  depthLimit(10),                       // pathological nesting stops here90  createComplexityRule({91    maximumComplexity: 1000,            // ~1 point per field92    listFactor: 10,                     // lists multiply child cost93    onComplete: (c) => log.info({ complexity: c }),94  }),95]96```97Return the budget in the rejection message so clients can adapt:98`"Query complexity 2140 exceeds maximum 1000"`.99100## Deprecation flow101102```graphql103type Order {104  total: Money!105  amount: Int @deprecated(reason: "Use total; amount is cents-only and removed after 2027-01-01.")106}107```1081. Add the replacement field. 2. Deprecate with a reason that names the109replacement and a date. 3. Monitor field usage (most gateways report it).1104. Remove only when usage is zero or the date passes.111112## Gotchas113114- **Non-null cascade:** an error in `Order.customer: Customer!` nulls the115  entire `order` — with `Customer` (nullable) only the field nulls. This is116  why rule 2 defaults to nullable.117- **DataLoader order contract:** `batch_load_fn` must return exactly118  len(ids) results in input order; returning a dict or short list corrupts119  unrelated resolvers silently.120- **Enums over booleans:** `status: OrderStatus` beats `isActive/isArchived`121  boolean pairs that can contradict each other.122- **Introspection in production:** disable for public APIs unless the API is123  deliberately open; it enumerates your entire attack surface.124- **`totalCount` on large tables** is a full COUNT(*) per query — make it125  nullable and resolve lazily, or return an estimate and say so.126- **Cursor stability:** cursors must encode the ORDER BY key, not row127  position, or pagination skips/duplicates under concurrent writes.128