// std/collections — Collection utilities.
/**
* Options for `pick_keys`.
*
* `drop_nil` removes selected keys whose value is `nil` from the result;
* defaults to `false`.
*/
type PickKeysOptions = {drop_nil?: bool}
/**
* Remove entries from a dict where the value is nil, an empty string, or the
* literal "null" string. Generic over the value type so a typed input
* (`dict<string, V>` or a structural shape) projects back to a dict with the
* same value contract instead of collapsing to untyped `dict`.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: stable
* @example: filter_nil(d)
*/
pub fn filter_nil<V>(d: dict<string, V>) -> dict<string, V> {
return __dict_filter_nil(d ?? {})
}
/**
* Project a dict onto an explicit list of keys, preserving the original value
* contract. Pass `{drop_nil: true}` to omit keys whose value resolves to nil
* after lookup. Missing keys are silently dropped.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: stable
* @example: pick_keys(d, selected_keys, options)
*/
pub fn pick_keys<V>(d: dict<string, V>, selected_keys: list<string>, options: PickKeysOptions = {}) -> dict<string, V> {
if type_of(d) != "dict" {
return {}
}
let drop_nil = options.drop_nil ?? false
return __dict_pick_keys(d, selected_keys, drop_nil)
}
/**
* Recursively merges `b` into `a`. When both sides have a dict at the
* same key, the dicts are merged recursively; otherwise the right-hand
* value wins. Nil arguments are treated as empty dicts so variadic
* accumulators don't need a base case.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: stable
* @example: deep_merge(defaults, overrides)
*/
pub fn deep_merge<V>(a: dict<string, V>, b: dict<string, V>) -> dict<string, V> {
return __deep_merge(a ?? {}, b ?? {})
}
/**
* Returns the list with duplicate entries removed, preserving the
* first-seen order. Equality is structural — dicts and lists with
* identical contents collapse to a single entry.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: stable
* @example: unique(values)
*/
pub fn unique<T>(values: list<T>) -> list<T> {
return __list_unique(values ?? [])
}
/**
* Convert a list of `[key, value]` pairs (or `pair(key, value)`
* values) into a dict. Later pairs override earlier ones — matching
* `__dict_merge`. Pair with `map(items, { it -> [it.id, it] })` to
* build a lookup table from a list of records.
*
* @effects: []
* @allocation: heap
* @errors: [TypeError]
* @api_stability: stable
* @example: dict_from_pairs([["a", 1], ["b", 2]])
*/
pub fn dict_from_pairs<V>(pairs: list<list<V>>) -> dict<string, V> {
return __dict_from_pairs(pairs ?? [])
}
/**
* Build a dict that indexes the items by the value returned from the
* key function. The last item per key wins, matching `dict_from_pairs`.
* Equivalent to `dict_from_pairs(map(items, { it -> [key_fn(it), it] }))`.
*
* @effects: []
* @allocation: heap
* @errors: [TypeError]
* @api_stability: stable
* @example: index_by(records, { r -> r.id })
*/
pub fn index_by<V>(items: list<V>, key_fn) -> dict<string, V> {
var pairs = []
for item in items ?? [] {
pairs = pairs + [[to_string(key_fn(item)), item]]
}
return __dict_from_pairs(pairs)
}
// Check if a store key is stale (older than max_age_seconds).
// Uses store_get/store_set convention: timestamps stored as key + "_ts".
/**
* store_stale.
*
* @effects: [time, store.read]
* @allocation: heap
* @errors: []
* @api_stability: stable
* @example: store_stale(key, max_age_seconds)
*/
pub fn store_stale(key, max_age_seconds) {
let ts = store_get(key + "_ts")
if ts == nil {
return true
}
return harness.clock.timestamp() - ts > max_age_seconds
}
// Refresh a store key's timestamp to now.
/**
* store_refresh.
*
* @effects: [time, store.write]
* @allocation: heap
* @errors: []
* @api_stability: stable
* @example: store_refresh(key)
*/
pub fn store_refresh(key) {
store_set(key + "_ts", harness.clock.timestamp())
}