harn-stdlib 0.8.52

Embedded Harn standard library source catalog
Documentation
// 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())
}