{
  "site": "accesspatterns.dev",
  "url": "https://accesspatterns.dev",
  "description": "Learn DynamoDB access patterns by running real queries in your browser and seeing what each one costs. A real DynamoDB-compatible engine, backed by SQLite, runs in the tab.",
  "engine": {
    "package": "@dynoxide/wasm-engine",
    "version": "0.11.2-preview",
    "preview": true,
    "caveat": "The in-browser engine is a preview build. It has not been run against the conformance suite that backs dynoxide's native build, so treat its behaviour as illustrative rather than authoritative."
  },
  "course": {
    "name": "Learn DynamoDB by running it",
    "url": "https://accesspatterns.dev/lessons",
    "lessonCount": 20,
    "tiers": [
      {
        "name": "Foundations",
        "blurb": "What an item is, and how the key addresses it.",
        "lessons": [
          {
            "slug": "items-and-attributes",
            "title": "Items, not rows",
            "concept": "Two items in one table can have completely different attributes.",
            "url": "https://accesspatterns.dev/lessons/items-and-attributes"
          },
          {
            "slug": "primary-keys",
            "title": "The key is the address",
            "concept": "GetItem needs the whole primary key: partition and sort.",
            "url": "https://accesspatterns.dev/lessons/primary-keys"
          }
        ]
      },
      {
        "name": "Reads and cost",
        "blurb": "The three ways to read, and what each one charges you.",
        "lessons": [
          {
            "slug": "query-a-partition",
            "title": "One query, a whole collection",
            "concept": "Items that share a partition key are read together in one Query.",
            "url": "https://accesspatterns.dev/lessons/query-a-partition"
          },
          {
            "slug": "sort-key-conditions",
            "title": "The sort key is a query language",
            "concept": "Conditions on the sort key narrow a Query before it reads, for free.",
            "url": "https://accesspatterns.dev/lessons/sort-key-conditions"
          },
          {
            "slug": "the-filter-trap",
            "title": "A filter is not an index",
            "concept": "A FilterExpression runs after the read: you pay for everything scanned.",
            "url": "https://accesspatterns.dev/lessons/the-filter-trap"
          },
          {
            "slug": "latest-first",
            "title": "Read the latest first",
            "concept": "A sorted timestamp makes 'the most recent' a direction, not a sort in your code.",
            "url": "https://accesspatterns.dev/lessons/latest-first"
          },
          {
            "slug": "pagination",
            "title": "Read a page, keep the bookmark",
            "concept": "Limit caps a page; LastEvaluatedKey is the bookmark you feed back to resume.",
            "url": "https://accesspatterns.dev/lessons/pagination"
          }
        ]
      },
      {
        "name": "Writing data",
        "blurb": "Putting, changing, and guarding items.",
        "lessons": [
          {
            "slug": "putitem-replaces",
            "title": "PutItem replaces",
            "concept": "PutItem writes a whole item and overwrites any item with the same key.",
            "url": "https://accesspatterns.dev/lessons/putitem-replaces"
          },
          {
            "slug": "updateitem-in-place",
            "title": "UpdateItem edits in place",
            "concept": "UpdateItem changes named attributes and can increment a counter atomically.",
            "url": "https://accesspatterns.dev/lessons/updateitem-in-place"
          },
          {
            "slug": "conditional-writes",
            "title": "Claim it, or fail",
            "concept": "A ConditionExpression lets a write happen only if the data still allows it.",
            "url": "https://accesspatterns.dev/lessons/conditional-writes"
          },
          {
            "slug": "optimistic-locking",
            "title": "No silent clobbers",
            "concept": "A version check lets an update land only if nobody changed the item since you read it.",
            "url": "https://accesspatterns.dev/lessons/optimistic-locking"
          }
        ]
      },
      {
        "name": "Modelling relationships",
        "blurb": "One-to-many, secondary indexes, and sparse indexes.",
        "lessons": [
          {
            "slug": "one-to-many",
            "title": "Model a one-to-many with one partition",
            "concept": "A parent and its children share a partition key, so one Query fetches them together.",
            "url": "https://accesspatterns.dev/lessons/one-to-many"
          },
          {
            "slug": "hierarchical-keys",
            "title": "One key, every level",
            "concept": "A path in the sort key lets one begins_with read any level of a hierarchy.",
            "url": "https://accesspatterns.dev/lessons/hierarchical-keys"
          },
          {
            "slug": "secondary-index",
            "title": "A different key for a different question",
            "concept": "A secondary index re-keys your items so you can Query them on a new axis.",
            "url": "https://accesspatterns.dev/lessons/secondary-index"
          },
          {
            "slug": "inverted-index",
            "title": "Traverse a relationship both ways",
            "concept": "An index that swaps the partition and sort keys reads the same edges from the other side.",
            "url": "https://accesspatterns.dev/lessons/inverted-index"
          },
          {
            "slug": "sparse-index",
            "title": "Absence as a filter",
            "concept": "An item appears in an index only if it has the index's key, so leaving the key off filters it out.",
            "url": "https://accesspatterns.dev/lessons/sparse-index"
          },
          {
            "slug": "aggregate-counter",
            "title": "Keep the total where you read it",
            "concept": "A denormalised count on the parent turns an aggregate into a single GetItem.",
            "url": "https://accesspatterns.dev/lessons/aggregate-counter"
          }
        ]
      },
      {
        "name": "Single-table design",
        "blurb": "One table, one index, many access patterns.",
        "lessons": [
          {
            "slug": "key-overloading",
            "title": "One index, many questions",
            "concept": "Different entity types share generic index keys, so one index serves several access patterns.",
            "url": "https://accesspatterns.dev/lessons/key-overloading"
          },
          {
            "slug": "gsi-projection",
            "title": "An index holds only what you project",
            "concept": "Projection decides which attributes an index carries, trading storage against an extra read.",
            "url": "https://accesspatterns.dev/lessons/gsi-projection"
          },
          {
            "slug": "single-table-capstone",
            "title": "One table, an application's worth of questions",
            "concept": "Generic keys and one overloaded index answer five access patterns with no join and no scan.",
            "url": "https://accesspatterns.dev/lessons/single-table-capstone"
          }
        ]
      }
    ]
  },
  "models": [
    {
      "id": "url-shortener",
      "name": "URL Shortener",
      "summary": "A hash-only table keyed by short code, the classic key-value lookup.",
      "category": "general",
      "url": "https://accesspatterns.dev/library/url-shortener"
    },
    {
      "id": "saas-multi-tenant",
      "name": "SaaS Multi-Tenant",
      "summary": "Organisations and their members in one table, isolated by partition.",
      "category": "saas",
      "url": "https://accesspatterns.dev/library/saas-multi-tenant"
    },
    {
      "id": "game-leaderboard",
      "name": "Game Leaderboard",
      "summary": "Per-game high scores, ranked with a secondary index on score.",
      "category": "social",
      "url": "https://accesspatterns.dev/library/game-leaderboard"
    },
    {
      "id": "maintainers-projects",
      "name": "Maintainers & Projects",
      "summary": "A many-to-many between people and projects, read from either side.",
      "category": "general",
      "url": "https://accesspatterns.dev/library/maintainers-projects"
    }
  ]
}
