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

satoren / y_ex / 74825bffb704ae641d52bb30236f2f617a925d8c-PR-195

20 Nov 2025 04:46AM UTC coverage: 94.71% (-1.6%) from 96.276%
74825bffb704ae641d52bb30236f2f617a925d8c-PR-195

Pull #195

github

satoren
add helper function insert_and_get
Pull Request #195: add helper function insert_and_get

39 of 50 new or added lines in 4 files covered. (78.0%)

7 existing lines in 4 files now uncovered.

555 of 586 relevant lines covered (94.71%)

22.24 hits per line

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

94.74
/lib/shared_type/array.ex
1
defmodule Yex.Array do
2
  @moduledoc """
3
  A shareable Array-like type that supports efficient insert/delete of elements at any position.
4
  This module provides functionality for collaborative array manipulation with support for
5
  concurrent modifications and automatic conflict resolution.
6

7
  ## Features
8
  - Insert and delete elements at any position
9
  - Push and unshift operations for adding elements
10
  - Move elements between positions
11
  - Support for nested shared types
12
  - Automatic conflict resolution for concurrent modifications
13
  """
14
  defstruct [
15
    :doc,
16
    :reference
17
  ]
18

19
  @type t :: %__MODULE__{
20
          doc: Yex.Doc.t(),
21
          reference: reference()
22
        }
23
  alias Yex.Doc
24
  require Yex.Doc
25

26
  @doc """
27
  Inserts content at the specified index.
28
  Returns :ok on success.
29

30
  ## Parameters
31
    * `array` - The array to modify
32
    * `index` - The position to insert at (0-based)
33
    * `content` - The content to insert
34
  """
35
  @spec insert(t, integer(), Yex.input_type()) :: :ok
36
  def insert(%__MODULE__{doc: doc} = array, index, content) when is_integer(index) do
37
    Doc.run_in_worker_process(doc,
144✔
38
      do: Yex.Nif.array_insert(array, cur_txn(array), index, content)
144✔
39
    )
40
  end
41

42
  @doc """
43
  Inserts content at the specified index and returns the inserted content.
44
  Returns {:ok, content} on success, :error on failure.
45
  ## Parameters
46
    * `array` - The array to modify
47
    * `index` - The position to insert at (0-based)
48
    * `content` - The content to insert
49
  """
50
  @spec insert_and_get(t, integer(), Yex.input_type()) :: {:ok, term()} | :error
51
  def insert_and_get(%__MODULE__{doc: doc} = array, index, content) when is_integer(index) do
52
    Doc.run_in_worker_process doc do
6✔
53
      index = if index < 0, do: __MODULE__.length(array) + index, else: index
6✔
54

55
      case Yex.Nif.array_insert(array, cur_txn(array), index, content) do
6✔
56
        :ok -> Yex.Nif.array_get(array, cur_txn(array), index)
6✔
NEW
57
        :error -> :error
×
58
      end
59
    end
60
  end
61

62
  @doc """
63
  Insert contents at the specified index.
64

65
  ## Examples
66
      iex> doc = Yex.Doc.new()
67
      iex> array = Yex.Doc.get_array(doc, "array")
68
      iex> Yex.Array.insert_list(array, 0, [1,2,3,4,5])
69
      iex> Yex.Array.to_json(array)
70
      [1.0, 2.0, 3.0, 4.0, 5.0]
71
  """
72
  @spec insert_list(t, integer(), list(Yex.any_type())) :: :ok
73
  def insert_list(%__MODULE__{doc: doc} = array, index, contents) when is_integer(index) do
74
    Doc.run_in_worker_process(doc,
17✔
75
      do: Yex.Nif.array_insert_list(array, cur_txn(array), index, contents)
17✔
76
    )
77
  end
78

79
  @doc """
80
  Pushes content to the end of the array.
81
  Returns :ok on success, :error on failure.
82

83
  ## Parameters
84
    * `array` - The array to modify
85
    * `content` - The content to append
86
  """
87
  @spec push(t, Yex.input_type()) :: :ok
88
  def push(%__MODULE__{doc: doc} = array, content) do
89
    Doc.run_in_worker_process(doc,
88✔
90
      do: insert(array, __MODULE__.length(array), content)
88✔
91
    )
92
  end
93

94
  @doc """
95
  Pushes content to the end of the array and returns the pushed content.
96
  Returns {:ok, content} on success, :error on failure.
97

98
  ## Parameters
99
    * `array` - The array to modify
100
    * `content` - The content to append
101

102
  ## Examples
103
      iex> doc = Yex.Doc.new()
104
      iex> array = Yex.Doc.get_array(doc, "array")
105
      iex> {:ok, value} = Yex.Array.push_and_get(array, "Hello")
106
      iex> value
107
      "Hello"
108
  """
109
  @spec push_and_get(t, Yex.input_type()) :: {:ok, term()} | :error
110
  def push_and_get(%__MODULE__{doc: doc} = array, content) do
111
    Doc.run_in_worker_process doc do
5✔
112
      index = __MODULE__.length(array)
5✔
113

114
      case Yex.Nif.array_insert(array, cur_txn(array), index, content) do
5✔
115
        :ok -> Yex.Nif.array_get(array, cur_txn(array), index)
5✔
NEW
UNCOV
116
        :error -> :error
×
117
      end
118
    end
119
  end
120

121
  @doc """
122
  Unshifts content to the beginning of the array.
123
  Returns :ok on success, :error on failure.
124

125
  ## Parameters
126
    * `array` - The array to modify
127
    * `content` - The content to prepend
128
  """
129
  def unshift(%__MODULE__{} = array, content) do
130
    insert(array, 0, content)
2✔
131
  end
132

133
  @doc """
134
  Deletes content at the specified index.
135
  Returns :ok on success, :error on failure.
136

137
  ## Parameters
138
    * `array` - The array to modify
139
    * `index` - The position to delete from (0-based)
140
  """
141
  @spec delete(t, integer()) :: :ok
142
  def delete(%__MODULE__{} = array, index) do
143
    delete_range(array, index, 1)
7✔
144
  end
145

146
  @doc """
147
  Deletes a range of contents starting at the specified index.
148
  Returns :ok on success, :error on failure.
149

150
  ## Parameters
151
    * `array` - The array to modify
152
    * `index` - The starting position to delete from (0-based)
153
    * `length` - The number of elements to delete
154
  """
155
  @spec delete_range(t, integer(), integer()) :: :ok
156
  def delete_range(%__MODULE__{doc: doc} = array, index, length)
157
      when is_integer(index) and is_integer(length) do
158
    Doc.run_in_worker_process doc do
11✔
159
      index = if index < 0, do: __MODULE__.length(array) + index, else: index
11✔
160
      Yex.Nif.array_delete_range(array, cur_txn(array), index, length)
11✔
161
    end
162
  end
163

164
  @doc """
165
  Moves element found at `source` index into `target` index position. Both indexes refer to a current state of the document.
166
  ## Examples pushes a string then fetches it back
167
      iex> doc = Yex.Doc.new()
168
      iex> array = Yex.Doc.get_array(doc, "array")
169
      iex> Yex.Array.push(array, Yex.ArrayPrelim.from([1, 2]))
170
      iex> Yex.Array.push(array, Yex.ArrayPrelim.from([3, 4]))
171
      iex> :ok = Yex.Array.move_to(array, 0, 2)
172
      iex> Yex.Array.to_json(array)
173
      [[3.0, 4.0], [1.0, 2.0]]
174
  """
175
  @spec move_to(t, integer(), integer()) :: :ok
176
  def move_to(%__MODULE__{doc: doc} = array, from, to) when is_integer(from) and is_integer(to) do
177
    Doc.run_in_worker_process(doc,
10✔
178
      do: Yex.Nif.array_move_to(array, cur_txn(array), from, to)
10✔
179
    )
180
  end
181

182
  @deprecated "Rename to `fetch/2`"
183
  @spec get(t, integer()) :: {:ok, term()} | :error
184
  def get(array, index) do
185
    fetch(array, index)
1✔
186
  end
187

188
  @doc """
189
  Get content at the specified index.
190
  ## Examples pushes a string then fetches it back
191
      iex> doc = Yex.Doc.new()
192
      iex> array = Yex.Doc.get_array(doc, "array")
193
      iex> Yex.Array.push(array, "Hello")
194
      iex> Yex.Array.fetch(array, 0)
195
      {:ok, "Hello"}
196
  """
197
  @spec fetch(t, integer()) :: {:ok, term()} | :error
198
  def fetch(%__MODULE__{doc: doc} = array, index) when is_integer(index) do
199
    Doc.run_in_worker_process doc do
28✔
200
      index = if index < 0, do: __MODULE__.length(array) + index, else: index
28✔
201
      Yex.Nif.array_get(array, cur_txn(array), index)
28✔
202
    end
203
  end
204

205
  @spec fetch!(t, integer()) :: term()
206
  def fetch!(%__MODULE__{} = array, index) when is_integer(index) do
207
    case fetch(array, index) do
3✔
208
      {:ok, value} -> value
2✔
209
      :error -> raise ArgumentError, "Index out of bounds"
1✔
210
    end
211
  end
212

213
  @doc """
214
  Returns as list
215

216
  ## Examples adds a few items to an array, then gets them back as Elixir List
217
      iex> doc = Yex.Doc.new()
218
      iex> array = Yex.Doc.get_array(doc, "array")
219
      iex> Yex.Array.push(array, "Hello")
220
      iex> Yex.Array.push(array, "World")
221
      iex> Yex.Array.push(array, Yex.ArrayPrelim.from([1, 2]))
222
      iex> ["Hello", "World", %Yex.Array{}] = Yex.Array.to_list(array)
223

224
  """
225
  def to_list(%__MODULE__{doc: doc} = array) do
226
    Doc.run_in_worker_process doc do
55✔
227
      Yex.Nif.array_to_list(array, cur_txn(array))
55✔
228
    end
229
  end
230

231
  @doc """
232
   slices the array from start_index for amount of elements, then gets them back as Elixir List.
233
  """
234
  def slice(array, start_index, amount) do
235
    slice_take_every(array, start_index, amount, 1)
3✔
236
  end
237

238
  @doc """
239
    slices the array from start_index for amount of elements, then gets them back as Elixir List and takes every `step` element.
240
  """
241
  def slice_take_every(_array, _start_index, _amount, 0) do
1✔
242
    []
243
  end
244

245
  def slice_take_every(%__MODULE__{doc: doc} = array, start_index, amount, step)
246
      when is_integer(step) and step > 0 do
247
    Doc.run_in_worker_process doc do
9✔
248
      Yex.Nif.array_slice(array, cur_txn(array), start_index, amount, step)
9✔
249
    end
250
  end
251

252
  @doc """
253
  Returns the length of the array
254

255
  ## Examples adds a few items to an array and returns its length
256
      iex> doc = Yex.Doc.new()
257
      iex> array = Yex.Doc.get_array(doc, "array")
258
      iex> Yex.Array.push(array, "Hello")
259
      iex> Yex.Array.push(array, "World")
260
      iex> Yex.Array.length(array)
261
      2
262
  """
263
  def length(%__MODULE__{doc: doc} = array) do
264
    Doc.run_in_worker_process doc do
118✔
265
      Yex.Nif.array_length(array, cur_txn(array))
118✔
266
    end
267
  end
268

269
  @doc """
270
  Convert to json-compatible format.
271

272
  ## Examples adds a few items to an array and returns as Elixir List
273
      iex> doc = Yex.Doc.new()
274
      iex> array = Yex.Doc.get_array(doc, "array")
275
      iex> Yex.Array.push(array, "Hello")
276
      iex> Yex.Array.push(array, "World")
277
      iex> Yex.Array.to_json(array)
278
      ["Hello", "World"]
279
  """
280
  @spec to_json(t) :: term()
281
  def to_json(%__MODULE__{doc: doc} = array) do
282
    Doc.run_in_worker_process doc do
28✔
283
      Yex.Nif.array_to_json(array, cur_txn(array))
28✔
284
    end
285
  end
286

287
  def member?(array, val) do
288
    val = Yex.normalize(val)
7✔
289
    Enum.member?(to_list(array), val)
7✔
290
  end
291

292
  defp cur_txn(%{doc: %Yex.Doc{reference: doc_ref}}) do
293
    Process.get(doc_ref, nil)
442✔
294
  end
295

296
  @doc """
297
  Converts the array to its preliminary representation.
298
  This is useful when you need to serialize or transfer the array's contents.
299

300
  ## Parameters
301
    * `array` - The array to convert
302
  """
303
  @spec as_prelim(t) :: Yex.ArrayPrelim.t()
304
  def as_prelim(%__MODULE__{doc: doc} = array) do
305
    Doc.run_in_worker_process(doc,
6✔
306
      do:
307
        Yex.Array.to_list(array)
308
        |> Enum.map(&Yex.Output.as_prelim/1)
309
        |> Yex.ArrayPrelim.from()
6✔
310
    )
311
  end
312

313
  defimpl Yex.Output do
314
    def as_prelim(array) do
315
      Yex.Array.as_prelim(array)
3✔
316
    end
317
  end
318

319
  defimpl Enumerable do
320
    def count(array) do
2✔
321
      {:ok, Yex.Array.length(array)}
322
    end
323

324
    def member?(array, val) do
4✔
325
      {:ok, Yex.Array.member?(array, val)}
326
    end
327

328
    def slice(array) do
329
      size = Yex.Array.length(array)
12✔
330
      {:ok, size, &slice_impl(array, &1, &2, &3)}
12✔
331
    end
332

333
    defp slice_impl(array, start, length, step) do
334
      # Optimize for single element access (Enum.at)
335
      if length == 1 and step == 1 do
9✔
336
        case Yex.Array.fetch(array, start) do
8✔
337
          {:ok, value} -> [value]
8✔
UNCOV
338
          :error -> []
×
339
        end
340
      else
341
        Yex.Array.slice_take_every(array, start, length, step)
1✔
342
      end
343
    end
344

345
    def reduce(array, acc, fun) do
346
      Enumerable.List.reduce(Yex.Array.to_list(array), acc, fun)
5✔
347
    end
348
  end
349
end
350

351
defmodule Yex.ArrayPrelim do
352
  @moduledoc """
353
  A preliminary array. It can be used to early initialize the contents of a Array.
354

355
  ## Examples
356
      iex> doc = Yex.Doc.new()
357
      iex> map = Yex.Doc.get_map(doc, "map")
358
      iex> Yex.Map.set(map, "key", Yex.ArrayPrelim.from(["Hello", "World"]))
359
      iex> {:ok, %Yex.Array{} = array} = Yex.Map.fetch(map, "key")
360
      iex> Yex.Array.fetch(array, 1)
361
      {:ok, "World"}
362

363
  """
364
  defstruct [
365
    :list
366
  ]
367

368
  @type t :: %__MODULE__{
369
          list: list(Yex.input_type())
370
        }
371

372
  @spec from(Enumerable.t(Yex.input_type())) :: t
373
  def from(enumerable) do
374
    %__MODULE__{list: Enum.to_list(enumerable)}
26✔
375
  end
376
end
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

© 2026 Coveralls, Inc