• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

whitfin / cachex / 64ffdb79131a3eb9e15e483ea81eedd63160a005-PR-426

30 Oct 2025 04:39AM UTC coverage: 99.042% (-1.0%) from 100.0%
64ffdb79131a3eb9e15e483ea81eedd63160a005-PR-426

Pull #426

github

whitfin
Remove tagging from inspection and commands
Pull Request #426: Simplify and naturalize API signatures and return types

68 of 74 new or added lines in 25 files covered. (91.89%)

2 existing lines in 2 files now uncovered.

827 of 835 relevant lines covered (99.04%)

1054.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.07
/lib/cachex.ex
1
defmodule Cachex do
2
  @moduledoc """
3
  Cachex provides a straightforward interface for in-memory key/value storage.
4

5
  Cachex is an extremely fast, designed for caching but also allowing for more
6
  general in-memory storage. The main goal of Cachex is achieve a caching
7
  implementation with a wide array of options, without sacrificing performance.
8
  Internally, Cachex is backed by ETS, allowing for an easy-to-use interface
9
  sitting upon extremely well tested tools.
10

11
  Cachex comes with support for all of the following (amongst other things):
12

13
  - Time-based key expirations
14
  - Maximum size protection
15
  - Pre/post execution hooks
16
  - Proactive/reactive cache warming
17
  - Transactions and row locking
18
  - Asynchronous write operations
19
  - Distribution across app nodes
20
  - Syncing to a local filesystem
21
  - Idiomatic cache streaming
22
  - Batched write operations
23
  - User command invocation
24
  - Statistics gathering
25

26
  All features are optional to allow you to tune based on the throughput needed.
27

28
  Please see `Cachex.start_link/2` inside the Cachex [documentation](https://hexdocs.pm/cachex)
29
  for further details about how to configure these  options and example usage.
30
  """
31
  use Supervisor
32

33
  # add all imports
34
  import Cachex.Error
35
  import Cachex.Spec
36

37
  # allow unsafe generation
38
  use Unsafe.Generator,
39
    handler: :unwrap_unsafe
40

41
  # add some aliases
42
  alias Cachex.Options
43
  alias Cachex.Query, as: Q
44
  alias Cachex.Router
45
  alias Cachex.Services
46

47
  # alias any services
48
  alias Services.Overseer
49

50
  # import util macros
51
  require Router
52
  require Overseer
53

54
  # avoid inspect clashes
55
  import Kernel, except: [inspect: 2]
56

57
  # the type aliases for a cache type
58
  @type t :: atom | Cachex.Spec.cache()
59

60
  # custom status type
61
  @type error :: {:error, atom()}
62
  @type status :: :ok | :error
63

64
  # generate unsafe definitions
65
  @unsafe [
66
    clear: [1, 2],
67
    decr: [2, 3, 4],
68
    del: [2, 3],
69
    empty?: [1, 2],
70
    execute: [2, 3],
71
    exists?: [2, 3],
72
    expire: [3, 4],
73
    expire_at: [3, 4],
74
    export: [1, 2],
75
    fetch: [3, 4],
76
    get: [2, 3],
77
    get_and_update: [3, 4],
78
    import: [2, 3],
79
    incr: [2, 3, 4],
80
    inspect: [2, 3],
81
    invoke: [3, 4],
82
    keys: [1, 2],
83
    persist: [2, 3],
84
    prune: [2, 3],
85
    purge: [1, 2],
86
    put: [3, 4],
87
    put_many: [2, 3],
88
    refresh: [2, 3],
89
    reset: [1, 2],
90
    restore: [2, 3],
91
    save: [2, 3],
92
    size: [1, 2],
93
    stats: [1, 2],
94
    stream: [1, 2, 3],
95
    take: [2, 3],
96
    touch: [2, 3],
97
    transaction: [3, 4],
98
    ttl: [2, 3],
99
    update: [3, 4],
100
    warm: [1, 2]
101
  ]
102

103
  ##############
104
  # Public API #
105
  ##############
106

107
  @doc """
108
  Creates a new Cachex cache service tree, linked to the current process.
109

110
  This will link the cache to the current process, so if your process dies the
111
  cache will also die. If you don't want this behaviour, please use `start/2`.
112

113
  The first argument should be a unique atom, used as the name of the cache
114
  service for future calls through to Cachex. For all options requiring a record
115
  argument, please import `Cachex.Spec` in advance.
116

117
  ## Options
118

119
    * `:commands`
120

121
      This option allows you to attach a set of custom commands to a cache in
122
      order to provide shorthand execution. A cache command must be constructed
123
      using the `:command` record provided by `Cachex.Spec`.
124

125
      A cache command will adhere to these basic rules:
126

127
      - If you define a `:read` command, the return value of your command will
128
        be passed through as the result of your call to `invoke/4`.
129
      - If you define a `:write` command, your command must return a two-element
130
        Tuple. The first element represents the value being returned from your
131
        `invoke/4` call, and the second represents the value to write back into
132
        the cache (as an update). If your command does not fit this, errors will
133
        happen (intentionally).
134

135
      Commands are set on a per-cache basis, but can be reused across caches. They're
136
      set only on cache startup and cannot be modified after the cache tree is created.
137

138
          iex> import Cachex.Spec
139
          ...>
140
          ...> Cachex.start_link(:my_cache, [
141
          ...>   commands: [
142
          ...>     last: command(type:  :read, execute:   &List.last/1),
143
          ...>     trim: command(type: :write, execute: &String.trim/1)
144
          ...>   ]
145
          ...> ])
146
          { :ok, _pid }
147

148
      Either a `Keyword` or a `Map` can be provided against the `:commands` option as
149
      we only use `Enum` to verify them before attaching them internally. Please see
150
      the `Cachex.Spec.command/1` documentation for further customization options.
151

152
    * `:compressed`
153

154
      This option will specify whether this cache should have enable ETS compression,
155
      which is likely to reduce memory overhead. Please note that there is a potential
156
      for this option to slow your cache due to compression overhead, so benchmark as
157
      appropriate when using this option. This option defaults to `false`.
158

159
          iex> Cachex.start_link(:my_cache, [ compressed: true ])
160
          { :ok, _pid }
161

162
    * `:expiration`
163

164
      The expiration option provides the ability to customize record expiration at
165
      a global cache level. The value provided here must be a valid `:expiration`
166
      record provided by `Cachex.Spec`.
167

168
          iex> import Cachex.Spec
169
          ...>
170
          ...> Cachex.start_link(:my_cache, [
171
          ...>   expiration: expiration(
172
          ...>     # how often cleanup should occur
173
          ...>     interval: :timer.seconds(30),
174
          ...>
175
          ...>     # default record expiration
176
          ...>     default: :timer.seconds(60),
177
          ...>
178
          ...>     # whether to enable lazy checking
179
          ...>     lazy: true
180
          ...>   )
181
          ...> ])
182
          { :ok, _pid }
183

184
      Please see the `Cachex.Spec.expiration/1` documentation for further customization
185
      options.
186

187
    * `:hooks`
188

189
      The `:hooks` option allow the user to attach a list of notification hooks to
190
      enable listening on cache actions (either before or after they happen). These
191
      hooks should be valid `:hook` records provided by `Cachex.Spec`. Example hook
192
      implementations can be found in `Cachex.Stats` and `Cachex.Limited.Scheduled`.
193

194
          iex> import Cachex.Spec
195
          ...>
196
          ...> Cachex.start_link(:my_cache, [
197
          ...>   hooks: [
198
          ...>     hook(module: MyHook, name: :my_hook, args: { })
199
          ...>   ]
200
          ...> ])
201
          { :ok, _pid }
202

203
      Please see the `Cachex.Spec.hook/1` documentation for further customization options.
204

205
    * `:ordered`
206

207
      This option will specify whether this cache should enable ETS ordering, which can
208
      improve performance if ordered traversal of a cache is required. Setting `:ordered`
209
      to `true` will result in both insert and lookup times being proportional to the
210
      logarithm of the number of objects in the table. This option defaults to `false`.
211

212
          iex> Cachex.start_link(:my_cache, [ ordered: true ])
213
          { :ok, _pid }
214

215
    * `:router`
216

217
      This option determines which module is used for cache routing inside distributed
218
      caches. You can provide either a full `record` structure or simply a module name.
219

220
          iex> import Cachex.Spec
221
          ...>
222
          ...> Cachex.start_link(:my_cache, [
223
          ...>   router: router(
224
          ...>     module: Cachex.Router.Jump,
225
          ...>     options: []
226
          ...>   )
227
          ...> ])
228
          { :ok, _pid }
229

230
      Please see the `Cachex.Spec.router/1` documentation for further customization options.
231

232
    * `:transactions`
233

234
      This option will specify whether this cache should have transactions and row
235
      locking enabled from cache startup. Please note that even if this is false,
236
      it will be enabled the moment a transaction is executed. It's recommended to
237
      leave this as default as it will handle most use cases in the most performant
238
      way possible.
239

240
          iex> Cachex.start_link(:my_cache, [ transactions: true ])
241
          { :ok, _pid }
242

243
    * `:warmers`
244

245
      The `:warmers` option allows the user to attach a list of warming modules to
246
      a cache. These cache warmers must implement the `Cachex.Warmer` behaviour
247
      and are defined as `warmer` records.
248

249
      The only required value is the `:module` definition, although you can also
250
      choose to provide a name and state to attach to the warmer process. The flag
251
      `:required` is used to control whether the warmer must finish execution before
252
      the cache supervision tree can be considered fully started.
253

254
          iex> import Cachex.Spec
255
          ...>
256
          ...> Cachex.start_link(:my_cache, [
257
          ...>   warmers: [
258
          ...>     warmer(
259
          ...>       required: true,
260
          ...>       module: MyProject.DatabaseWarmer,
261
          ...>       state: connection,
262
          ...>       name: MyProject.DatabaseWarmer
263
          ...>     )
264
          ...>   ]
265
          ...> ])
266
          { :ok, _pid }
267

268
      Please see the `Cachex.Spec.warmer/1` documentation for further customization options.
269

270
  """
271
  @spec start_link(atom, Keyword.t()) :: {atom, pid}
272
  def start_link(name, options) when is_atom(name) and is_list(options) do
273
    with {:ok, true} <- ensure_started(),
197✔
274
         {:ok, true} <- ensure_unused(name),
196✔
275
         {:ok, cache} <- Options.parse(name, options),
194✔
276
         {:ok, pid} = Supervisor.start_link(__MODULE__, cache, name: name),
193✔
277
         {:ok, cache} = Services.link(cache),
193✔
278
         {:ok, cache} <- setup_router(cache),
193✔
279
         ^cache <- Overseer.update(name, cache),
193✔
280
         :ok <- setup_warmers(cache) do
193✔
281
      {:ok, pid}
282
    end
283
  end
284

285
  @doc false
286
  # Grace handlers for `Supervisor.child_spec/1`.
287
  #
288
  # Without this, Cachex is not compatible per Elixir's documentation.
289
  @spec start_link(atom | Keyword.t()) :: {atom, pid} | {:error, :invalid_name}
290
  def start_link([name]) when is_atom(name),
291
    do: start_link(name)
1✔
292

293
  def start_link([name, options]) when is_atom(name) and is_list(options),
294
    do: start_link(name, options)
1✔
295

296
  def start_link(options) when is_list(options) do
297
    case Keyword.fetch(options, :name) do
3✔
298
      {:ok, name} ->
299
        start_link(name, options)
2✔
300

301
      _invalid ->
1✔
302
        error(:invalid_name)
303
    end
304
  end
305

306
  def start_link(name) when is_atom(name),
307
    do: start_link(name, [])
7✔
308

309
  @doc """
310
  Creates a new Cachex cache service tree.
311

312
  This will not link the cache to the current process, so if your process dies
313
  the cache will continue to live. If you don't want this behaviour, please use
314
  the provided `Cachex.start_link/2`.
315

316
  This function is otherwise identical to `start_link/2` so please see that
317
  documentation for further information and configuration.
318
  """
319
  @spec start(atom, Keyword.t()) :: {atom, pid}
320
  def start(name, options \\ []) do
321
    with {:ok, pid} <- start_link(name, options), true <- :erlang.unlink(pid) do
42✔
322
      {:ok, pid}
323
    end
324
  end
325

326
  @doc false
327
  # Basic initialization phase for a cache.
328
  #
329
  # This will start all cache services required using the `Cachex.Services`
330
  # module and attach them under a Supervisor instance backing the cache.
331
  @spec init(cache :: Cachex.t()) ::
332
          {:ok, {Supervisor.sup_flags(), [Supervisor.child_spec()]}}
333
  def init(cache() = cache) do
334
    cache
335
    |> Services.cache_spec()
336
    |> Supervisor.init(strategy: :one_for_one)
193✔
337
  end
338

339
  @doc """
340
  Removes all entries from a cache.
341

342
  The returned numeric value will contain the total number of keys removed
343
  from the cache. This is equivalent to running `size/2` before running
344
  the internal clear operation.
345

346
  ## Examples
347

348
      iex> Cachex.put(:my_cache, "key", "value")
349
      iex> Cachex.get(:my_cache, "key")
350
      iex> Cachex.size(:my_cache)
351
      1
352

353
      iex> Cachex.clear(:my_cache)
354
      1
355

356
      iex> Cachex.size(:my_cache)
357
      0
358

359
  """
360
  @spec clear(Cachex.t(), Keyword.t()) :: integer()
361
  def clear(cache, options \\ []) when is_list(options),
11✔
362
    do: Router.route(cache, {:clear, [options]})
20✔
363

364
  @doc """
365
  Decrements an entry in the cache.
366

367
  This will overwrite any value that was previously set against the provided key.
368

369
  ## Options
370

371
    * `:default`
372

373
      An initial value to set the key to if it does not exist. This will
374
      take place *before* the decrement call. Defaults to 0.
375

376
  ## Examples
377

378
      iex> Cachex.put(:my_cache, "my_key", 10)
379
      iex> Cachex.decr(:my_cache, "my_key")
380
      :ok, 9
381

382
      iex> Cachex.put(:my_cache, "my_new_key", 10)
383
      iex> Cachex.decr(:my_cache, "my_new_key", 5)
384
      :ok, 5
385

386
      iex> Cachex.decr(:my_cache, "missing_key", 5, default: 2)
387
      -3
388

389
  """
390
  @spec decr(Cachex.t(), any(), integer(), Keyword.t()) :: integer() | Cachex.error()
391
  def decr(cache, key, amount \\ 1, options \\ []) when is_integer(amount) and is_list(options) do
392
    via_opt = via({:decr, [key, amount, options]}, options)
8✔
393
    incr(cache, key, amount * -1, via_opt)
8✔
394
  end
395

396
  @doc """
397
  Removes an entry from a cache.
398

399
  This will return `true` regardless of whether a key has been removed or
400
  not. The `true` value can be thought of as "is key no longer present?".
401

402
  ## Examples
403

404
      iex> Cachex.put(:my_cache, "key", "value")
405
      iex> Cachex.get(:my_cache, "key")
406
      { :ok, "value" }
407

408
      iex> Cachex.del(:my_cache, "key")
409
      true
410

411
      iex> Cachex.get(:my_cache, "key")
412
      nil
413

414
  """
415
  @spec del(Cachex.t(), any(), Keyword.t()) :: boolean()
416
  def del(cache, key, options \\ []) when is_list(options),
6✔
417
    do: Router.route(cache, {:del, [key, options]})
13✔
418

419
  @doc """
420
  Determines whether a cache contains any entries.
421

422
  This does not take the expiration time of keys into account. As such,
423
  if there are any unremoved (but expired) entries in the cache, they
424
  will be included in the returned determination.
425

426
  ## Examples
427

428
      iex> Cachex.put(:my_cache, "key1", "value1")
429
      iex> Cachex.empty?(:my_cache)
430
      false
431

432
      iex> Cachex.clear(:my_cache)
433
      iex> Cachex.empty?(:my_cache)
434
      true
435

436
  """
437
  @spec empty?(Cachex.t(), Keyword.t()) :: boolean()
438
  def empty?(cache, options \\ []) when is_list(options),
12✔
439
    do: Router.route(cache, {:empty?, [options]})
18✔
440

441
  @doc """
442
  Executes multiple functions in the context of a cache.
443

444
  This can be used when carrying out several cache operations at once
445
  to avoid the overhead of cache loading and jumps between processes.
446

447
  This does not provide a transactional execution, it simply avoids
448
  the overhead involved in the initial calls to a cache. For a transactional
449
  implementation, please see `transaction/3`.
450

451
  To take advantage of the cache context, ensure to use the cache
452
  instance provided when executing cache calls. If this is not done
453
  you will see zero benefits from using `execute/3`.
454

455
  ## Examples
456

457
      iex> Cachex.put(:my_cache, "key1", "value1")
458
      iex> Cachex.put(:my_cache, "key2", "value2")
459
      iex> Cachex.execute(:my_cache, fn(worker) ->
460
      ...>   val1 = Cachex.get!(worker, "key1")
461
      ...>   val2 = Cachex.get!(worker, "key2")
462
      ...>   [val1, val2]
463
      ...> end)
464
      { :ok, [ "value1", "value2" ] }
465

466
  """
467
  @spec execute(Cachex.t(), function, Keyword.t()) :: {status, any}
468
  def execute(cache, operation, options \\ [])
3✔
469
      when is_function(operation, 1) and is_list(options) do
470
    Overseer.with(cache, fn cache ->
3✔
471
      {:ok, operation.(cache)}
472
    end)
473
  end
474

475
  @doc """
476
  Determines whether an entry exists in a cache.
477

478
  This will take expiration times into account, meaning that
479
  expired entries will not be considered to exist.
480

481
  ## Examples
482

483
      iex> Cachex.put(:my_cache, "key", "value")
484
      iex> Cachex.exists?(:my_cache, "key")
485
      true
486

487
      iex> Cachex.exists?(:my_cache, "missing_key")
488
      false
489

490
  """
491
  @spec exists?(Cachex.t(), any(), Keyword.t()) :: boolean()
492
  def exists?(cache, key, options \\ []) when is_list(options),
317✔
493
    do: Router.route(cache, {:exists?, [key, options]})
317✔
494

495
  @doc """
496
  Places an expiration time on an entry in a cache.
497

498
  The provided expiration must be a integer value representing the
499
  lifetime of the entry in milliseconds. If the provided value is
500
  not positive, the entry will be immediately evicted.
501

502
  If the entry does not exist, no changes will be made in the cache.
503

504
  ## Examples
505

506
      iex> Cachex.put(:my_cache, "key", "value")
507
      iex> Cachex.expire(:my_cache, "key", :timer.seconds(5))
508
      true
509

510
      iex> Cachex.expire(:my_cache, "missing_key", :timer.seconds(5))
511
      false
512

513
  """
514
  @spec expire(Cachex.t(), any(), number() | nil, Keyword.t()) :: boolean()
515
  def expire(cache, key, expiration, options \\ [])
6✔
516
      when (is_nil(expiration) or is_number(expiration)) and is_list(options),
517
      do: Router.route(cache, {:expire, [key, expiration, options]})
17✔
518

519
  @doc """
520
  Updates an entry in a cache to expire at a given time.
521

522
  Unlike `expire/4` this call uses an instant in time, rather than a
523
  duration. The same semantics apply as calls to `expire/4` in that
524
  instants which have passed will result in immediate eviction.
525

526
  ## Examples
527

528
      iex> Cachex.put(:my_cache, "key", "value")
529
      iex> Cachex.expire_at(:my_cache, "key", 1455728085502)
530
      { :ok, true }
531

532
      iex> Cachex.expire_at(:my_cache, "missing_key", 1455728085502)
533
      { :ok, false }
534

535
  """
536
  @spec expire_at(Cachex.t(), any(), number(), Keyword.t()) :: boolean()
537
  def expire_at(cache, key, timestamp, options \\ [])
6✔
538
      when is_number(timestamp) and is_list(options) do
539
    via_opts = via({:expire_at, [key, timestamp, options]}, options)
6✔
540
    expire(cache, key, timestamp - now(), via_opts)
6✔
541
  end
542

543
  @doc """
544
  Exports all entries from a cache.
545

546
  This provides a raw read of the entire backing table into a list
547
  of cache records for export purposes.
548

549
  This function is very heavy, so it should typically only be used
550
  when debugging and/or exporting of tables (although the latter case
551
  should really use `Cachex.save/3`).
552

553
   ## Examples
554

555
      iex> Cachex.put(:my_cache, "key", "value")
556
      iex> Cachex.export(:my_cache)
557
      [ { :entry, "key", 1538714590095, nil, "value" } ]
558

559
  """
560
  @spec export(Cachex.t(), Keyword.t()) :: [Cachex.Spec.entry()] | Cachex.error()
561
  def export(cache, options \\ []) when is_list(options),
4✔
562
    do: Router.route(cache, {:export, [options]})
11✔
563

564
  @doc """
565
  Fetches an entry from a cache, generating a value on cache miss.
566

567
  If the entry requested is found in the cache, this function will
568
  operate in the same way as `get/3`. If the entry is not contained
569
  in the cache, the provided fallback function will be executed.
570

571
  A fallback function is a function used to lazily generate a value
572
  to place inside a cache on miss. Consider it a way to achieve the
573
  ability to create a read-through cache.
574

575
  A fallback function should return a Tuple consisting of a `:commit`
576
  or `:ignore` tag and a value. If the Tuple is tagged `:commit` the
577
  value will be placed into the cache and then returned. If tagged
578
  `:ignore` the value will be returned without being written to the
579
  cache. If you return a value which does not fit this structure, it
580
  will be assumed that you are committing the value.
581

582
  As of Cachex v3.6, you can also provide a third element in a `:commit`
583
  Tuple, to allow passthrough of options from within your fallback. The
584
  options supported in this list match the options you can provide to a
585
  call of `Cachex.put/4`. An example is the `:expire` option to set an
586
  expiration from directly inside your fallback.
587

588
  If a fallback function has an arity of 1, the requested entry key
589
  will be passed through to allow for contextual computation. If a
590
  function has an arity of 0, it will be executed without arguments.
591

592
  ## Examples
593

594
      iex> Cachex.put(:my_cache, "key", "value")
595
      iex> Cachex.fetch(:my_cache, "key", fn(key) ->
596
      ...>   { :commit, String.reverse(key) }
597
      ...> end)
598
      { :ok, "value" }
599

600
      iex> Cachex.fetch(:my_cache, "missing_key", fn(key) ->
601
      ...>   { :ignore, String.reverse(key) }
602
      ...> end)
603
      { :ignore, "yek_gnissim" }
604

605
      iex> Cachex.fetch(:my_cache, "missing_key", fn(key) ->
606
      ...>   { :commit, String.reverse(key) }
607
      ...> end)
608
      { :commit, "yek_gnissim" }
609

610
      iex> Cachex.fetch(:my_cache, "missing_key_expires", fn(key) ->
611
      ...>   { :commit, String.reverse(key), expire: :timer.seconds(60) }
612
      ...> end)
613
      { :commit, "seripxe_yek_gnissim" }
614

615
  """
616
  @spec fetch(Cachex.t(), any, function(), Keyword.t()) ::
617
          {status | :commit | :ignore, any}
618
  def fetch(cache, key, fallback, options \\ [])
619
      when is_function(fallback) and is_list(options),
8,047✔
620
      do: Router.route(cache, {:fetch, [key, fallback, options]})
621

8,047✔
622
  @doc """
623
  Retrieves an entry from a cache.
624

625
  ## Examples
626

627
      iex> Cachex.put(:my_cache, "key", "value")
628
      iex> Cachex.get(:my_cache, "key")
629
      { :ok, "value" }
630

631
      iex> Cachex.get(:my_cache, "missing_key")
632
      { :ok, nil }
633

634
  """
635
  @spec get(Cachex.t(), any, Keyword.t()) :: {atom, any}
636
  def get(cache, key, options \\ []) when is_list(options),
637
    do: Router.route(cache, {:get, [key, options]})
86✔
638

107✔
639
  @doc """
640
  Retrieves and updates an entry in a cache.
641

642
  This operation can be seen as an internal mutation, meaning that any previously
643
  set expiration time is kept as-is.
644

645
  This function accepts the same return syntax as fallback functions, in that if
646
  you return a Tuple of the form `{ :ignore, value }`, the value is returned from
647
  the call but is not written to the cache. You can use this to abandon writes
648
  which began eagerly (for example if a key is actually missing)
649

650
  See the `fetch/4` documentation for more information on return formats.
651

652
  ## Examples
653

654
      iex> Cachex.put(:my_cache, "key", [2])
655
      iex> Cachex.get_and_update(:my_cache, "key", &([1|&1]))
656
      { :commit, [1, 2] }
657

658
      iex> Cachex.get_and_update(:my_cache, "missing_key", fn
659
      ...>   (nil) -> { :ignore, nil }
660
      ...>   (val) -> { :commit, [ "value" | val ] }
661
      ...> end)
662
      { :ignore, nil }
663

664
  """
665
  @spec get_and_update(Cachex.t(), any, function, Keyword.t()) ::
666
          {:commit | :ignore, any}
667
  def get_and_update(cache, key, updater, options \\ [])
668
      when is_function(updater, 1) and is_list(options),
10✔
669
      do: Router.route(cache, {:get_and_update, [key, updater, options]})
670

10✔
671
  @doc """
672
  Retrieves a list of all entry keys from a cache.
673

674
  The order these keys are returned should be regarded as unordered.
675

676
  ## Examples
677

678
      iex> Cachex.put(:my_cache, "key1", "value1")
679
      iex> Cachex.put(:my_cache, "key2", "value2")
680
      iex> Cachex.put(:my_cache, "key3", "value3")
681
      iex> Cachex.keys(:my_cache)
682
      [ "key2", "key1", "key3" ]
683

684
      iex> Cachex.clear(:my_cache)
685
      iex> Cachex.keys(:my_cache)
686
      []
687

688
  """
689
  @spec keys(Cachex.t(), Keyword.t()) :: [any()]
690
  def keys(cache, options \\ []) when is_list(options),
691
    do: Router.route(cache, {:keys, [options]})
1✔
692

9✔
693
  @doc """
694
  Imports an export set into a cache.
695

696
  This provides a raw import of a previously exported cache via the use
697
  of the `export/2` command.
698

699
   ## Examples
700

701
      iex> Cachex.put(:my_cache, "key", "value")
702
      iex> Cachex.import(:my_cache, [ { :entry, "key", "value", 1538714590095, nil } ])
703
      1
704

705
  """
706
  @spec import(Cachex.t(), Enumerable.t(), Keyword.t()) :: integer() | Cachex.error()
707
  def import(cache, entries, options \\ []) when is_list(options),
708
    do: Router.route(cache, {:import, [entries, options]})
1✔
709

6✔
710
  @doc """
711
  Increments an entry in the cache.
712

713
  This will overwrite any value that was previously set against the provided key.
714

715
  ## Options
716

717
    * `:default`
718

719
      An initial value to set the key to if it does not exist. This will
720
      take place *before* the increment call. Defaults to 0.
721

722
  ## Examples
723

724
      iex> Cachex.put(:my_cache, "my_key", 10)
725
      iex> Cachex.incr(:my_cache, "my_key")
726
      11
727

728
      iex> Cachex.put(:my_cache, "my_new_key", 10)
729
      iex> Cachex.incr(:my_cache, "my_new_key", 5)
730
      15
731

732
      iex> Cachex.incr(:my_cache, "missing_key", 5, default: 2)
733
      7
734

735
  """
736
  @spec incr(Cachex.t(), any(), integer(), Keyword.t()) :: integer() | Cachex.error()
737
  def incr(cache, key, amount \\ 1, options \\ []) when is_integer(amount) and is_list(options),
738
    do: Router.route(cache, {:incr, [key, amount, options]})
32✔
739

42✔
740
  @doc """
741
  Inspects various aspects of a cache.
742

743
  These operations should be regarded as debug tools, and should really
744
  only happen outside of production code (unless absolutely) necessary.
745

746
  Accepted options are only provided for convenience and should not be
747
  heavily relied upon.  They are not part of the public interface
748
  (despite being documented) and as such  may be removed at any time
749
  (however this does not mean that they will be).
750

751
  Please use cautiously. `inspect/2` is provided mainly for testing
752
  purposes and so performance isn't as much of a concern. It should
753
  also be noted that `inspect/2` will *always* operate locally.
754

755
  ## Options
756

757
    * `:cache`
758

759
      Retrieves the internal cache record for a cache.
760

761
    * `{ :entry, key }`
762

763
      Retrieves a raw entry record from inside a cache.
764

765
    * `{ :expired, :count }`
766

767
      Retrieves the number of expired entries which currently live in the cache
768
      but have not yet been removed by cleanup tasks (either scheduled or lazy).
769

770
    * `{ :expired, :keys }`
771

772
      Retrieves the list of expired entry keys which current live in the cache
773
      but have not yet been removed by cleanup tasks (either scheduled or lazy).
774

775
    * `{ :janitor, :last }`
776

777
      Retrieves metadata about the last execution of the Janitor service for
778
      the specified cache.
779

780
    * `{ :memory, :bytes }`
781

782
      Retrieves an approximate memory footprint of a cache in bytes.
783

784
    * `{ :memory, :binary }`
785

786
      Retrieves an approximate memory footprint of a cache in binary format.
787

788
    * `{ :memory, :words }`
789

790
      Retrieve an approximate memory footprint of a cache as a number of
791
      machine words.
792

793
  ## Examples
794

795
      iex> Cachex.inspect(:my_cache, :cache)
796
      {:cache, :test, %{}, false, {:expiration, nil, 3000, true},
797
        {:hooks, [], [], []}, nil, false, {:router, [], Cachex.Router.Local, nil},
798
        false, []}
799

800
      iex> Cachex.inspect(:my_cache, { :entry, "my_key" } )
801
      { :entry, "my_key", 1475476615662, 1, "my_value" }
802

803
      iex> Cachex.inspect(:my_cache, { :expired, :count })
804
      0
805

806
      iex> Cachex.inspect(:my_cache, { :expired, :keys })
807
      [ ]
808

809
      iex> Cachex.inspect(:my_cache, { :janitor, :last })
810
      %{ count: 0, duration: 57, started: 1475476530925 }
811

812
      iex> Cachex.inspect(:my_cache, { :memory, :binary })
813
      "10.38 KiB"
814

815
      iex> Cachex.inspect(:my_cache, { :memory, :bytes })
816
      10624
817

818
      iex> Cachex.inspect(:my_cache, { :memory, :words })
819
      1328
820

821
  """
822
  @spec inspect(Cachex.t(), atom() | tuple(), Keyword.t()) :: any() | Cachex.error()
823
  def inspect(cache, option, options \\ []) when is_list(options),
824
    do: Router.route(cache, {:inspect, [option, options]})
122✔
825

122✔
826
  @doc """
827
  Invokes a custom command against a cache entry.
828

829
  The provided command name must be a valid command which was
830
  previously attached to the cache in calls to `start_link/2`.
831

832
  ## Examples
833

834
      iex> import Cachex.Spec
835
      iex>
836
      iex> Cachex.start_link(:my_cache, [
837
      ...>    commands: [
838
      ...>      last: command(type: :read, execute: &List.last/1)
839
      ...>    ]
840
      ...> ])
841
      { :ok, _pid }
842

843
      iex> Cachex.put(:my_cache, "my_list", [ 1, 2, 3 ])
844
      iex> Cachex.invoke(:my_cache, :last, "my_list")
845
      { :ok, 3 }
846

847
  """
848
  @spec invoke(Cachex.t(), atom(), any(), Keyword.t()) :: any()
849
  def invoke(cache, cmd, key, options \\ []) when is_list(options),
850
    do: Router.route(cache, {:invoke, [cmd, key, options]})
16✔
851

16✔
852
  @doc """
853
  Removes an expiration time from an entry in a cache.
854

855
  ## Examples
856

857
      iex> Cachex.put(:my_cache, "key", "value", expiration: 1000)
858
      iex> Cachex.persist(:my_cache, "key")
859
      true
860

861
      iex> Cachex.persist(:my_cache, "missing_key")
862
      false
863

864
  """
865
  @spec persist(Cachex.t(), any(), Keyword.t()) :: boolean()
866
  def persist(cache, key, options \\ []) when is_list(options),
867
    do: expire(cache, key, nil, via({:persist, [key, options]}, options))
5✔
868

5✔
869
  @doc """
870
  Prunes to a maximum size of records in a cache.
871

872
  Pruning is done via a Least Recently Written (LRW) approach, determined by the
873
  modification time inside each cache record to avoid storing additional state.
874

875
  For full details on this feature, please see the section of the documentation
876
  related to limitation of caches.
877

878
  ## Options
879

880
    * `:buffer`
881

882
      Allows customization of the internal batching when paginating the cursor
883
      coming back from ETS. It's unlikely this will ever need changing.
884

885
    * `:reclaim`
886

887
      Provides control over thrashing by evicting an additonal number of cache
888
      entries beyond the maximum size. This option accepts a percentage (as a
889
      decimal) of extra keys to evict, to provide buffer between pruning passes.
890
      Defaults to `0.1` (i.e. 10%).
891

892
  ## Examples
893

894
      iex> Cachex.put(:my_cache, "key1", "value1")
895
      { :ok, true }
896

897
      iex> :timer.sleep(1)
898
      :ok
899

900
      iex> Cachex.put(:my_cache, "key2", "value2")
901
      { :ok, true }
902

903
      iex> Cachex.prune(:my_cache, 1, reclaim: 0)
904
      true
905

906
      iex> Cachex.keys(:my_cache)
907
      [ "key2"]
908

909
  """
910
  @spec prune(Cachex.t(), integer, Keyword.t()) :: boolean()
911
  def prune(cache, size, options \\ []) when is_positive_integer(size) and is_list(options),
912
    do: Router.route(cache, {:prune, [size, options]})
1✔
913

106✔
914
  @doc """
915
  Triggers a cleanup of all expired entries in a cache.
916

917
  This can be used to implement custom eviction policies rather than
918
  relying on the internal Janitor service. Take care when using this
919
  method though; calling `purge/2` manually will result in a purge
920
  firing inside the calling process.
921

922
  ## Examples
923

924
      iex> Cachex.purge(:my_cache)
925
      15
926

927
  """
928
  @spec purge(Cachex.t(), Keyword.t()) :: number()
929
  def purge(cache, options \\ []) when is_list(options),
930
    do: Router.route(cache, {:purge, [options]})
3✔
931

11✔
932
  @doc """
933
  Places an entry in a cache.
934

935
  This will overwrite any value that was previously set against the provided key,
936
  and overwrite any TTLs which were already set.
937

938
  ## Options
939

940
    * `:expire`
941

942
      An expiration value to set for the provided key (time-to-live), overriding
943
      any default expirations set on a cache. This value should be in milliseconds.
944

945
  ## Examples
946

947
      iex> Cachex.put(:my_cache, "key", "value")
948
      { :ok, true }
949

950
      iex> Cachex.put(:my_cache, "key", "value", expire: :timer.seconds(5))
951
      iex> Cachex.ttl(:my_cache, "key")
952
      { :ok, 5000 }
953

954
  """
955
  @spec put(Cachex.t(), any, any, Keyword.t()) :: {status, any}
956
  def put(cache, key, value, options \\ []) when is_list(options),
957
    do: Router.route(cache, {:put, [key, value, options]})
10,562✔
958

10,670✔
959
  @doc """
960
  Places a batch of entries in a cache.
961

962
  This operates in the same way as `put/4`, except that multiple keys can be
963
  inserted in a single atomic batch. This is a performance gain over writing
964
  keys using multiple calls to `put/4`, however it's a performance penalty
965
  when writing a single key pair due to some batching overhead.
966

967
  ## Options
968

969
    * `:expire`
970

971
      An expiration value to set for the provided keys (time-to-live), overriding
972
      any default expirations set on a cache. This value should be in milliseconds.
973

974
  ## Examples
975

976
      iex> Cachex.put_many(:my_cache, [ { "key", "value" } ])
977
      { :ok, true }
978

979
      iex> Cachex.put_many(:my_cache, [ { "key", "value" } ], expire: :timer.seconds(5))
980
      iex> Cachex.ttl(:my_cache, "key")
981
      { :ok, 5000 }
982

983
  """
984
  @spec put_many(Cachex.t(), [{any, any}], Keyword.t()) :: {status, any}
985
  def put_many(cache, pairs, options \\ [])
986
      when is_list(pairs) and is_list(options),
22✔
987
      do: Router.route(cache, {:put_many, [pairs, options]})
988

24✔
989
  @doc """
990
  Refreshes an expiration for an entry in a cache.
991

992
  Refreshing an expiration will reset the existing expiration with an offset
993
  from the current time - i.e. if you set an expiration of 5 minutes and wait
994
  3 minutes before refreshing, the entry will expire 8 minutes after the initial
995
  insertion.
996

997
  ## Examples
998

999
      iex> Cachex.put(:my_cache, "my_key", "my_value", expire: :timer.seconds(5))
1000
      iex> Process.sleep(4)
1001
      iex> Cachex.ttl(:my_cache, "my_key")
1002
      { :ok, 1000 }
1003

1004
      iex> Cachex.refresh(:my_cache, "my_key")
1005
      iex> Cachex.ttl(:my_cache, "my_key")
1006
      { :ok, 5000 }
1007

1008
      iex> Cachex.refresh(:my_cache, "missing_key")
1009
      false
1010

1011
  """
1012
  @spec refresh(Cachex.t(), any(), Keyword.t()) :: boolean()
1013
  def refresh(cache, key, options \\ []) when is_list(options),
1014
    do: Router.route(cache, {:refresh, [key, options]})
5✔
1015

5✔
1016
  @doc """
1017
  Resets a cache by clearing the keyspace and restarting any hooks.
1018

1019
  ## Options
1020

1021
    * `:hooks`
1022

1023
      A whitelist of hooks to reset on the cache instance (call the
1024
      initialization phase of a hook again). This will default to
1025
      resetting all hooks associated with a cache, which is usually
1026
      the desired behaviour.
1027

1028
    * `:only`
1029

1030
      A whitelist of components to reset, which can currently contain
1031
      either the `:cache` or `:hooks` tag to determine what to reset.
1032
      This will default to `[ :cache, :hooks ]`.
1033

1034
  ## Examples
1035

1036
      iex> Cachex.put(:my_cache, "my_key", "my_value")
1037
      iex> Cachex.reset(:my_cache)
1038
      iex> Cachex.size(:my_cache)
1039
      0
1040

1041
      iex> Cachex.reset(:my_cache, [ only: :hooks ])
1042
      true
1043

1044
      iex> Cachex.reset(:my_cache, [ only: :hooks, hooks: [ MyHook ] ])
1045
      true
1046

1047
      iex> Cachex.reset(:my_cache, [ only: :cache ])
1048
      true
1049

1050
  """
1051
  @spec reset(Cachex.t(), Keyword.t()) :: boolean()
1052
  def reset(cache, options \\ []) when is_list(options),
1053
    do: Router.route(cache, {:reset, [options]})
2✔
1054

7✔
1055
  @doc """
1056
  Deserializes a cache from a location on a filesystem.
1057

1058
  This operation will read the current state of a cache from a provided
1059
  location on a filesystem. This function will only understand files
1060
  which have previously been created using `Cachex.save/3`.
1061

1062
  It is the responsibility of the user to ensure that the location is
1063
  able to be read from, not the responsibility of Cachex.
1064

1065
  ## Options
1066

1067
    * `:trust`
1068

1069
      Allow for loading from trusted or untrusted sources; trusted
1070
      sources can load atoms into the table, whereas untrusted sources
1071
      cannot. Defaults to `true`.
1072

1073
  ## Examples
1074

1075
      iex> Cachex.put(:my_cache, "my_key", 10)
1076
      iex> Cachex.save(:my_cache, "/tmp/my_backup")
1077
      true
1078

1079
      iex> Cachex.size(:my_cache)
1080
      1
1081

1082
      iex> Cachex.clear(:my_cache)
1083
      iex> Cachex.size(:my_cache)
1084
      0
1085

1086
      iex> Cachex.restore(:my_cache, "/tmp/my_backup")
1087
      1
1088

1089
      iex> Cachex.size(:my_cache)
1090
      1
1091

1092
  """
1093
  @spec restore(Cachex.t(), binary, Keyword.t()) :: integer() | Cachex.error()
1094
  def restore(cache, path, options \\ []) when is_binary(path) and is_list(options),
1095
    do: Router.route(cache, {:restore, [path, options]})
4✔
1096

5✔
1097
  @doc """
1098
  Serializes a cache to a location on a filesystem.
1099

1100
  This operation will write the current state of a cache to a provided
1101
  location on a filesystem. The written state can be used alongside the
1102
  `Cachex.restore/3` command to import back in the future.
1103

1104
  It is the responsibility of the user to ensure that the location is
1105
  able to be written to, not the responsibility of Cachex.
1106

1107
  ## Options
1108

1109
    * `:buffer`
1110

1111
      Allows customization of the internal batching when paginating the cursor
1112
      coming back from ETS. It's unlikely this will ever need changing.
1113

1114
  ## Examples
1115

1116
      iex> Cachex.save(:my_cache, "/tmp/my_default_backup")
1117
      true
1118

1119
  """
1120
  @spec save(Cachex.t(), binary, Keyword.t()) :: boolean() | Cachex.error()
1121
  def save(cache, path, options \\ []) when is_binary(path) and is_list(options),
1122
    do: Router.route(cache, {:save, [path, options]})
3✔
1123

5✔
1124
  @doc """
1125
  Retrieves the total size of a cache.
1126

1127
  By default this does not take expiration time of entries inside the
1128
  cache into account, making it an `O(1)` call. This behaviour can be
1129
  modified by passing `expired: false` to account for expirations, but
1130
  please note that this is a much more involved calculation.
1131

1132
  ## Options
1133

1134
    * `:expired`
1135

1136
      Whether or not to include expired records in the returned total. This
1137
      is a boolean value which defaults to `true`.
1138

1139
  ## Examples
1140

1141
      iex> Cachex.put(:my_cache, "key1", "value1")
1142
      iex> Cachex.put(:my_cache, "key2", "value2")
1143
      iex> Cachex.put(:my_cache, "key3", "value3", expire: 1)
1144
      iex> Cachex.size(:my_cache)
1145
      3
1146

1147
      iex> Cachex.size(:my_cache, expired: false)
1148
      2
1149

1150
  """
1151
  @spec size(Cachex.t(), Keyword.t()) :: integer()
1152
  def size(cache, options \\ []) when is_list(options),
1153
    do: Router.route(cache, {:size, [options]})
904✔
1154

1,029✔
1155
  @doc """
1156
  Retrieves statistics about a cache.
1157

1158
  This will only provide statistics if the `:hooks` option with `hook(module: Cachex.Stats)` was
1159
  provided on cache startup in `start_link/2`.
1160

1161
  ## Examples
1162

1163
      iex> Cachex.stats(:my_cache)
1164
      {:ok, %{meta: %{creation_date: 1518984857331}}}
1165

1166
      iex> Cachex.stats(:cache_with_no_stats)
1167
      { :error, :stats_disabled }
1168

1169
  """
1170
  @spec stats(Cachex.t(), Keyword.t()) :: {status, map()}
1171
  def stats(cache, options \\ []) when is_list(options),
1172
    do: Router.route(cache, {:stats, [options]})
25✔
1173

25✔
1174
  @doc """
1175
  Creates a `Stream` of entries in a cache.
1176

1177
  This will stream all entries matching the match specification provided
1178
  as the second argument. If none is provided, it will default to all entries
1179
  which are yet to expire (in no particular order).
1180

1181
  Consider using `Cachex.Query` to generate match specifications used when
1182
  querying the contents of a cache table.
1183

1184
  ## Options
1185

1186
    * `:buffer`
1187

1188
      Allows customization of the internal batching when paginating the cursor
1189
      coming back from ETS. It's unlikely this will ever need changing.
1190

1191
  ## Examples
1192

1193
      iex> Cachex.put(:my_cache, "a", 1)
1194
      iex> Cachex.put(:my_cache, "b", 2)
1195
      iex> Cachex.put(:my_cache, "c", 3)
1196
      {:ok, true}
1197

1198
      iex> :my_cache |> Cachex.stream |> Enum.to_list
1199
      [{:entry, "b", 1519015801794, nil, 2},
1200
        {:entry, "c", 1519015805679, nil, 3},
1201
        {:entry, "a", 1519015794445, nil, 1}]
1202

1203
      iex> query = Cachex.Query.build(output: :key)
1204
      iex> :my_cache |> Cachex.stream(query) |> Enum.to_list
1205
      ["b", "c", "a"]
1206

1207
      iex> query = Cachex.Query.build(output: :value)
1208
      iex> :my_cache |> Cachex.stream(query) |> Enum.to_list
1209
      [2, 3, 1]
1210

1211
      iex> query = Cachex.Query.build(output: {:key, :value})
1212
      iex> :my_cache |> Cachex.stream(query) |> Enum.to_list
1213
      [{"b", 2}, {"c", 3}, {"a", 1}]
1214

1215
  """
1216
  @spec stream(Cachex.t(), any(), Keyword.t()) :: Enumerable.t() | Cachex.error()
1217
  def stream(cache, query \\ Q.build(where: Q.unexpired()), options \\ []) when is_list(options),
1218
    do: Router.route(cache, {:stream, [query, options]})
5✔
1219

26✔
1220
  @doc """
1221
  Takes an entry from a cache.
1222

1223
  This is conceptually equivalent to running `get/3` followed
1224
  by an atomic `del/3` call.
1225

1226
  ## Examples
1227

1228
      iex> Cachex.put(:my_cache, "key", "value")
1229
      iex> Cachex.take(:my_cache, "key")
1230
      "value"
1231

1232
      iex> Cachex.get(:my_cache, "key")
1233
      nil
1234

1235
      iex> Cachex.take(:my_cache, "missing_key")
1236
      nil
1237

1238
  """
1239
  @spec take(Cachex.t(), any(), Keyword.t()) :: any()
1240
  def take(cache, key, options \\ []) when is_list(options),
1241
    do: Router.route(cache, {:take, [key, options]})
7✔
1242

7✔
1243
  @doc """
1244
  Updates the last write time on a cache entry.
1245

1246
  This is very similar to `refresh/3` except that the expiration
1247
  time is maintained inside the record (using a calculated offset).
1248

1249
  ## Examples
1250

1251
      iex> Cachex.put(:my_cache, "my_key", "my_value", expire: :timer.seconds(5))
1252
      iex> Process.sleep(4)
1253
      iex> Cachex.ttl(:my_cache, "my_key")
1254
      { :ok, 1000 }
1255

1256
      iex> Cachex.touch(:my_cache, "my_key")
1257
      iex> Cachex.ttl(:my_cache, "my_key")
1258
      { :ok, 1000 }
1259

1260
      iex> Cachex.touch(:my_cache, "missing_key")
1261
      false
1262

1263
  """
1264
  @spec touch(Cachex.t(), any, Keyword.t()) :: {status, boolean}
1265
  def touch(cache, key, options \\ []) when is_list(options),
1266
    do: Router.route(cache, {:touch, [key, options]})
7✔
1267

7✔
1268
  @doc """
1269
  Executes multiple functions in the context of a transaction.
1270

1271
  This will operate in the same way as `execute/3`, except that writes
1272
  to the specified keys will be blocked on the execution of this transaction.
1273

1274
  The keys parameter should be a list of keys you wish to lock whilst
1275
  your transaction is executed. Any keys not in this list can still be
1276
  written even during your transaction.
1277

1278
  ## Examples
1279

1280
      iex> Cachex.put(:my_cache, "key1", "value1")
1281
      iex> Cachex.put(:my_cache, "key2", "value2")
1282
      iex> Cachex.transaction(:my_cache, [ "key1", "key2" ], fn(worker) ->
1283
      ...>   val1 = Cachex.get(worker, "key1")
1284
      ...>   val2 = Cachex.get(worker, "key2")
1285
      ...>   [val1, val2]
1286
      ...> end)
1287
      { :ok, [ "value1", "value2" ] }
1288

1289
  """
1290
  @spec transaction(Cachex.t(), [any], function, Keyword.t()) :: {status, any}
1291
  def transaction(cache, keys, operation, options \\ [])
1292
      when is_function(operation) and is_list(keys) and is_list(options) do
8✔
1293
    Overseer.with(cache, fn cache ->
1294
      trans_cache =
8✔
1295
        case cache(cache, :transactions) do
8✔
1296
          true ->
1297
            cache
1298

3✔
1299
          false ->
1300
            cache
1301
            |> cache(:name)
1302
            |> Overseer.update(&cache(&1, transactions: true))
1303
        end
5✔
1304

1305
      Router.route(trans_cache, {:transaction, [keys, operation, options]})
1306
    end)
8✔
1307
  end
1308

1309
  @doc """
1310
  Retrieves the expiration for an entry in a cache.
1311

1312
  This is a millisecond value (if set) representing the time a
1313
  cache entry has left to live in a cache. It can return `nil`
1314
  if the entry does not have a set expiration.
1315

1316
  ## Examples
1317

1318
      iex> Cachex.ttl(:my_cache, "my_key")
1319
      13985
1320

1321
      iex> Cachex.ttl(:my_cache, "my_key_with_no_ttl")
1322
      nil
1323

1324
      iex> Cachex.ttl(:my_cache, "missing_key")
1325
      nil
1326

1327
  """
1328
  @spec ttl(Cachex.t(), any(), Keyword.t()) :: integer() | nil
1329
  def ttl(cache, key, options \\ []) when is_list(options),
1330
    do: Router.route(cache, {:ttl, [key, options]})
51✔
1331

51✔
1332
  @doc """
1333
  Updates an entry in a cache.
1334

1335
  Unlike `get_and_update/4`, this does a blind overwrite of a value.
1336

1337
  This operation can be seen as an internal mutation, meaning that any previously
1338
  set expiration time is kept as-is.
1339

1340
  ## Examples
1341

1342
      iex> Cachex.put(:my_cache, "key", "value")
1343
      iex> Cachex.get(:my_cache, "key")
1344
      { :ok, "value" }
1345

1346
      iex> Cachex.update(:my_cache, "key", "new_value")
1347
      iex> Cachex.get(:my_cache, "key")
1348
      { :ok, "new_value" }
1349

1350
      iex> Cachex.update(:my_cache, "missing_key", "new_value")
1351
      false
1352

1353
  """
1354
  @spec update(Cachex.t(), any(), any(), Keyword.t()) :: boolean()
1355
  def update(cache, key, value, options \\ []) when is_list(options),
1356
    do: Router.route(cache, {:update, [key, value, options]})
6✔
1357

15✔
1358
  @doc """
1359
  Triggers a manual warming in a cache.
1360

1361
  This allows for manual warming of a cache in situations where
1362
  you already know the backing state has been updated. The return
1363
  value of this function will contain the list of modules which
1364
  were warmed as a result of this call.
1365

1366
  ## Options
1367

1368
    * `:only`
1369

1370
      An optional list of modules to warm, acting as a whitelist. The default
1371
      behaviour of this function is to trigger warming in all modules. You may
1372
      provide either the module name, or the registered warmer name.
1373

1374
    * `:wait`
1375

1376
      Whether to wait for warmer completion or not, as a boolean. By default
1377
      warmers are triggered to run in the background, but passing `true` here
1378
      will block the return of this call until all warmers have completed.
1379

1380
  ## Examples
1381

1382
      iex> Cachex.warm(:my_cache)
1383
      [MyWarmer]
1384

1385
      iex> Cachex.warm(:my_cache, only: [MyWarmer])
1386
      [MyWarmer]
1387

1388
      iex> Cachex.warm(:my_cache, only: [])
1389
      []
1390

1391
      iex> Cachex.warm(:my_cache, wait: true)
1392
      [MyWarmer]
1393

1394
  """
1395
  @spec warm(Cachex.t(), Keyword.t()) :: [atom()]
1396
  def warm(cache, options \\ []),
1397
    do: Router.route(cache, {:warm, [options]})
1✔
1398

390✔
1399
  ###############
1400
  # Private API #
1401
  ###############
1402

1403
  # Determines whether the Cachex application has been started.
1404
  #
1405
  # This will return an error if the application has not been
1406
  # started, otherwise a truthy result will be returned.
1407
  defp ensure_started do
1408
    if Overseer.started?() do
1409
      {:ok, true}
197✔
1410
    else
1411
      error(:not_started)
1412
    end
1413
  end
1414

1415
  # Determines if a cache name is already in use.
1416
  #
1417
  # If the name is in use, we return an error.
1418
  defp ensure_unused(cache) do
1419
    case GenServer.whereis(cache) do
1420
      nil -> {:ok, true}
196✔
1421
      pid -> {:error, {:already_started, pid}}
194✔
1422
    end
2✔
1423
  end
1424

1425
  # Initializes cache router on startup.
1426
  #
1427
  # This will initialize the base router state and attach all nodes
1428
  # provided at cache startup to the router state.
1429
  defp setup_router(cache(router: router) = cache) do
1430
    router(module: module, options: options) = router
1431

193✔
1432
    state = module.init(cache, options)
1433
    route = router(router, state: state)
193✔
1434

193✔
1435
    {:ok, cache(cache, router: route)}
1436
  end
1437

1438
  # Initializes cache warmers on startup.
1439
  #
1440
  # This will trigger the initial cache warming via `Cachex.warm/2` while
1441
  # also respecting whether certain warmers should block startup or not.
1442
  defp setup_warmers(cache(warmers: warmers) = cache) do
1443
    {req, opt} = Enum.split_with(warmers, &warmer(&1, :required))
1444

193✔
1445
    required = [only: Enum.map(req, &warmer(&1, :name)), wait: true]
1446
    optional = [only: Enum.map(opt, &warmer(&1, :name)), wait: false]
193✔
1447

193✔
1448
    Cachex.warm(cache, const(:notify_false) ++ required)
1449
    Cachex.warm(cache, const(:notify_false) ++ optional)
193✔
1450

193✔
1451
    :ok
1452
  end
1453

1454
  # Unwraps a command result into an unsafe form.
1455
  #
1456
  # This is used alongside the Unsafe library to generate shorthand
1457
  # bang functions for the API. This will expand error messages and
1458
  # remove the binding Tuples in order to allow for easy piping of
1459
  # results from cache calls.
1460
  defp unwrap_unsafe({:error, value}) when is_atom(value),
1461
    do: raise(Cachex.Error, message: Cachex.Error.long_form(value))
UNCOV
1462

×
1463
  defp unwrap_unsafe({:error, value}) when is_binary(value),
1464
    do: raise(Cachex.Error, message: value)
1465

1✔
1466
  defp unwrap_unsafe({:error, %Cachex.Error{stack: stack} = e}),
1467
    do: reraise(e, stack)
1468

1✔
1469
  defp unwrap_unsafe({_state, value}),
1470
    do: value
1471

242✔
1472
  defp unwrap_unsafe(value),
1473
    do: value
1474
end
930✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc