Skip to content

Add Core Cache API Support #53

@posborne

Description

@posborne

Overview

Add support for Fastly's Core Cache API, providing advanced caching capabilities with request collapsing, stale-while-revalidate, and streaming.

WIT Interface

interface cache {
  use types.{error};
  use http-body.{body};
  use http-req.{request};
  use async-io.{pollable as pending-entry};

  resource entry {
    lookup: static func(key: list<u8>, options: lookup-options) -> result<entry, error>;
    transaction-lookup: static func(key: list<u8>, options: lookup-options) -> result<entry, error>;
    transaction-lookup-async: static func(key: list<u8>, options: lookup-options) -> result<pending-entry, error>;
    transaction-insert: func(options: write-options) -> result<body, error>;
    transaction-insert-and-stream-back: func(options: write-options) -> result<tuple<body, entry>, error>;
    transaction-update: func(options: write-options) -> result<_, error>;
    get-state: func() -> result<lookup-state, error>;
    get-user-metadata: func(max-len: u64) -> result<option<list<u8>>, error>;
    get-body: func(options: get-body-options) -> result<body, error>;
    get-length: func() -> result<option<object-length>, error>;
    get-max-age-ns: func() -> result<option<duration-ns>, error>;
    get-stale-while-revalidate-ns: func() -> result<option<duration-ns>, error>;
    get-age-ns: func() -> result<option<duration-ns>, error>;
    get-hits: func() -> result<option<cache-hit-count>, error>;
    transaction-cancel: func() -> result<_, error>;
  }

  resource replace-entry {
    replace: static func(key: list<u8>, options: replace-options) -> result<replace-entry, error>;
    get-age-ns: func() -> result<option<duration-ns>, error>;
    get-body: func(options: get-body-options) -> result<option<body>, error>;
    get-hits: func() -> result<option<cache-hit-count>, error>;
    get-length: func() -> result<option<object-length>, error>;
    get-max-age-ns: func() -> result<option<duration-ns>, error>;
    get-stale-while-revalidate-ns: func() -> result<option<duration-ns>, error>;
    get-state: func() -> result<option<lookup-state>, error>;
    get-user-metadata: func(max-len: u64) -> result<option<list<u8>>, error>;
  }

  type object-length = u64;
  type duration-ns = u64;
  type cache-hit-count = u64;

  resource extra-lookup-options {
    constructor();
  }

  record lookup-options {
    request-headers: option<borrow<request>>,
    always-use-requested-range: bool,
    extra: option<borrow<extra-lookup-options>>,
  }

  resource extra-write-options {
    constructor();
  }

  record write-options {
    max-age-ns: duration-ns,
    request-headers: option<borrow<request>>,
    vary-rule: option<string>,
    initial-age-ns: option<duration-ns>,
    stale-while-revalidate-ns: option<duration-ns>,
    surrogate-keys: option<string>,
    length: option<object-length>,
    user-metadata: option<list<u8>>,
    edge-max-age-ns: option<duration-ns>,
    sensitive-data: bool,
    extra: option<borrow<extra-write-options>>,
  }

  resource extra-get-body-options;

  record get-body-options {
    %from: option<u64>,
    to: option<u64>,
    extra: option<borrow<extra-get-body-options>>,
  }

  flags lookup-state {
    found,
    usable,
    stale,
    must-insert-or-update,
    usable-if-error,
  }

  resource extra-replace-options {
    constructor();
  }

  enum replace-strategy {
    immediate,
    immediate-force-miss,
    wait,
  }

  record replace-options {
    request-headers: option<borrow<request>>,
    replace-strategy: option<replace-strategy>,
    always-use-requested-range: bool,
    extra: option<borrow<extra-replace-options>>,
  }

  insert: func(key: list<u8>, options: write-options) -> result<body, error>;
  await-entry: func(handle: pending-entry) -> result<entry, error>;
  close-pending-entry: func(handle: pending-entry) -> result<_, error>;
  close-entry: func(handle: entry) -> result<_, error>;
  replace-insert: func(handle: replace-entry, options: write-options) -> result<body, error>;
  close-replace-entry: func(handle: replace-entry) -> result<_, error>;
}

WIT bindings: stubs/wit_world/imports/cache.py

API Design

  • Implement CacheEntry resource wrapper for cache operations
  • Support lookup and transaction-based caching patterns
  • Implement LookupOptions and WriteOptions dataclasses
  • Provide access to cache metadata: age, TTL, hits, state flags
  • Transaction methods: transaction_lookup(), insert(), update(), cancel()
  • Entry body should implement io.IOBase for streaming (similar to KV Store)
  • get_body() returns file-like object that can be read in chunks or passed to stdlib functions

Cross-SDK Comparison: All SDKs provide lookup(), insert(), transaction_lookup() with similar semantics. Rust uses strong typing for state/options enums, Go uses simple structs, JS uses async/Promise-based API. Python should support both blocking operations and context managers for transactions.

Viceroy Testing

Viceroy's cache implementation is limited/stubbed. Testing focus should be on:

  • Verifying hostcall invocations work correctly
  • Testing SDK wrapper logic (option parsing, state flag handling, error mapping)
  • Ensuring body streaming integration functions properly

Do NOT:

  • Try to verify actual cache persistence behavior
  • Create elaborate tests around Viceroy's stubbed functionality
  • Test cache hit/miss logic (this is host behavior, not SDK code)

The goal is to verify our Python wrapper code works, not to test Viceroy's cache implementation.

Reference

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions