-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Start cache api * Cache policy * Cleanup names * Fix return policy * Use short syntax * Cancel trx on error * Move to public Cache * Fix usable * Use empty options set * Deps * Add stubs * Add cache state * Fix initial age * Try closing writer * Optional length
- Loading branch information
1 parent
dcc7014
commit 29fb196
Showing
8 changed files
with
475 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// Cache.swift | ||
// | ||
// | ||
// Created by Andrew Barba on 9/13/23. | ||
// | ||
|
||
public struct Cache: Sendable { | ||
|
||
public static func getOrSet(_ key: String, _ handler: () async throws -> (FetchResponse, CachePolicy)) async throws -> Entry { | ||
let trx = try await Fastly.Cache.getOrSet(key) { | ||
let (res, cachePolicy) = try await handler() | ||
let length = res.headers[.contentLength].flatMap(Int.init) | ||
return await (.body(res.body.body, length: length), cachePolicy) | ||
} | ||
return try .init(trx) | ||
} | ||
|
||
public static func getOrSet(_ key: String, _ handler: () async throws -> ([UInt8], CachePolicy)) async throws -> Entry { | ||
let trx = try await Fastly.Cache.getOrSet(key) { | ||
let (bytes, cachePolicy) = try await handler() | ||
return (.bytes(bytes), cachePolicy) | ||
} | ||
return try .init(trx) | ||
} | ||
|
||
public static func getOrSet(_ key: String, _ handler: () async throws -> (String, CachePolicy)) async throws -> Entry { | ||
let trx = try await Fastly.Cache.getOrSet(key) { | ||
let (text, cachePolicy) = try await handler() | ||
return (.bytes(.init(text.utf8)), cachePolicy) | ||
} | ||
return try .init(trx) | ||
} | ||
|
||
public static func getOrSet(_ key: String, _ handler: () async throws -> (Data, CachePolicy)) async throws -> Entry { | ||
let trx = try await Fastly.Cache.getOrSet(key) { | ||
let (data, cachePolicy) = try await handler() | ||
return (.bytes(data.bytes), cachePolicy) | ||
} | ||
return try .init(trx) | ||
} | ||
|
||
public static func getOrSet(_ key: String, _ handler: () async throws -> ([String: Sendable], CachePolicy)) async throws -> Entry { | ||
let trx = try await Fastly.Cache.getOrSet(key) { | ||
let (json, cachePolicy) = try await handler() | ||
let data = try JSONSerialization.data(withJSONObject: json) | ||
return (.bytes(data.bytes), cachePolicy) | ||
} | ||
return try .init(trx) | ||
} | ||
|
||
public static func getOrSet(_ key: String, _ handler: () async throws -> ([Sendable], CachePolicy)) async throws -> Entry { | ||
let trx = try await Fastly.Cache.getOrSet(key) { | ||
let (json, cachePolicy) = try await handler() | ||
let data = try JSONSerialization.data(withJSONObject: json) | ||
return (.bytes(data.bytes), cachePolicy) | ||
} | ||
return try .init(trx) | ||
} | ||
} | ||
|
||
extension Cache { | ||
|
||
public struct Entry: Sendable { | ||
|
||
public let body: ReadableBody | ||
|
||
public let age: Int | ||
|
||
public let hits: Int | ||
|
||
public let contentLength: Int | ||
|
||
public let state: CacheState | ||
|
||
internal init(_ trx: Fastly.Cache.Transaction) throws { | ||
self.body = try ReadableWasiBody(trx.getBody()) | ||
self.age = try trx.getAge() | ||
self.hits = try trx.getHits() | ||
self.contentLength = try trx.getLength() | ||
self.state = try trx.getState() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
// | ||
// FastlyCache.swift | ||
// | ||
// | ||
// Created by Andrew Barba on 9/13/23. | ||
// | ||
|
||
import ComputeRuntime | ||
|
||
extension Fastly { | ||
public struct Cache: Sendable { | ||
|
||
public typealias InsertResult = (data: HandlerData, cachePolicy: CachePolicy) | ||
|
||
public static func getOrSet(_ key: String, _ handler: () async throws -> InsertResult) async throws -> Transaction { | ||
// Open the transaction | ||
let trx = try Transaction.lookup(key) | ||
|
||
// Get current transaction state | ||
let state = try trx.getState() | ||
|
||
// If state is usable then return the body from cache | ||
guard state.contains(.mustInsertOrUpdate) else { | ||
return trx | ||
} | ||
|
||
do { | ||
// If its not usable then begin executing handler with new value | ||
let (data, cachePolicy) = try await handler() | ||
|
||
// Get an instance to the insert handle | ||
var writer = try trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length) | ||
|
||
// Append bytes from handler to writeable body | ||
switch data { | ||
case .body(let body, _): | ||
try writer.body.append(body) | ||
case .bytes(let bytes): | ||
try writer.body.write(bytes) | ||
} | ||
|
||
// Finish the body | ||
try writer.body.close() | ||
|
||
return writer.transaction | ||
} catch { | ||
// Cancel the transaction if something went wrong | ||
try trx.cancel() | ||
|
||
// Rethrow original error | ||
throw error | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension Fastly.Cache { | ||
public enum HandlerData { | ||
case body(_ body: Fastly.Body, length: Int? = nil) | ||
case bytes(_ bytes: [UInt8]) | ||
|
||
public var length: Int? { | ||
switch self { | ||
case .body(_, let length): | ||
return length | ||
case .bytes(let bytes): | ||
return bytes.count | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension Fastly.Cache { | ||
public struct Transaction: Sendable { | ||
|
||
internal let handle: WasiHandle | ||
|
||
internal init(_ handle: WasiHandle) { | ||
self.handle = handle | ||
} | ||
|
||
public static func lookup(_ key: String) throws -> Transaction { | ||
var handle: WasiHandle = 0 | ||
let options = CacheLookupOptions.none | ||
var config = CacheLookupConfig() | ||
try wasi(fastly_cache__cache_transaction_lookup(key, key.utf8.count, options.rawValue, &config, &handle)) | ||
return Transaction(handle) | ||
} | ||
|
||
public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int?) throws -> (body: Fastly.Body, transaction: Transaction) { | ||
var bodyHandle: WasiHandle = 0 | ||
var cacheHandle: WasiHandle = 0 | ||
var options: CacheWriteOptions = [] | ||
var config = CacheWriteConfig() | ||
config.max_age_ns = .init(cachePolicy.maxAge) * 1_000_000_000 | ||
if cachePolicy.staleMaxAge > 0 { | ||
options.insert(.staleWhileRevalidateNs) | ||
config.stale_while_revalidate_ns = .init(cachePolicy.staleMaxAge) * 1_000_000_000 | ||
} | ||
if let length { | ||
options.insert(.length) | ||
config.length = .init(length) | ||
} | ||
try wasi(fastly_cache__cache_transaction_insert_and_stream_back(handle, options.rawValue, &config, &bodyHandle, &cacheHandle)) | ||
return (.init(bodyHandle), .init(cacheHandle)) | ||
} | ||
|
||
public func getState() throws -> CacheState { | ||
var value: UInt8 = 0 | ||
try wasi(fastly_cache__cache_get_state(handle, &value)) | ||
return CacheState(rawValue: value) | ||
} | ||
|
||
public func getAge() throws -> Int { | ||
var value: UInt64 = 0 | ||
try wasi(fastly_cache__cache_get_age_ns(handle, &value)) | ||
return .init(value / 1_000_000_000) | ||
} | ||
|
||
public func getLength() throws -> Int { | ||
var value: UInt64 = 0 | ||
try wasi(fastly_cache__cache_get_length(handle, &value)) | ||
return .init(value) | ||
} | ||
|
||
public func getHits() throws -> Int { | ||
var value: UInt64 = 0 | ||
try wasi(fastly_cache__cache_get_hits(handle, &value)) | ||
return .init(value) | ||
} | ||
|
||
public func getBody() throws -> Fastly.Body { | ||
var bodyHandle: WasiHandle = 0 | ||
let options = CacheGetBodyOptions.none | ||
var config = CacheGetBodyConfig() | ||
try wasi(fastly_cache__cache_get_body(handle, options.rawValue, &config, &bodyHandle)) | ||
return .init(bodyHandle) | ||
} | ||
|
||
public func cancel() throws { | ||
try wasi(fastly_cache__cache_transaction_cancel(handle)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.