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

satoren / y_ex / 427bae37232c598b7d69e4ba7338889fbe0f4e42-PR-195

20 Nov 2025 04:39AM UTC coverage: 94.463% (-1.8%) from 96.276%
427bae37232c598b7d69e4ba7338889fbe0f4e42-PR-195

Pull #195

github

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

47 of 60 new or added lines in 4 files covered. (78.33%)

8 existing lines in 4 files now uncovered.

563 of 596 relevant lines covered (94.46%)

22.0 hits per line

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

92.54
/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,
147✔
38
      do: Yex.Nif.array_insert(array, cur_txn(array), index, content)
147✔
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
  Insert contents at the specified index and returns the inserted contents.
81
  Returns {:ok, list} on success, :error on failure.
82

83
  ## Parameters
84
    * `array` - The array to modify
85
    * `index` - The position to insert at (0-based)
86
    * `contents` - The list of contents to insert
87

88
  ## Examples
89
      iex> doc = Yex.Doc.new()
90
      iex> array = Yex.Doc.get_array(doc, "array")
91
      iex> {:ok, inserted} = Yex.Array.insert_list_and_get(array, 0, [1,2,3])
92
      iex> inserted
93
      [1.0, 2.0, 3.0]
94
  """
95
  @spec insert_list_and_get(t, integer(), list(Yex.any_type())) :: {:ok, list()} | :error
96
  def insert_list_and_get(%__MODULE__{doc: doc} = array, index, contents)
97
      when is_integer(index) and is_list(contents) do
98
    Doc.run_in_worker_process doc do
3✔
99
      index = if index < 0, do: __MODULE__.length(array) + index, else: index
3✔
100
      count = Enum.count(contents)
3✔
101

102
      txn = cur_txn(array)
3✔
103

104
      case Yex.Nif.array_insert_list(array, txn, index, contents) do
3✔
105
        :ok ->
106
          # Get the inserted elements after insert completes
107

108
          inserted =
3✔
109
            Enum.map(0..(count - 1), fn offset ->
110
              case Yex.Nif.array_get(array, txn, index + offset) do
8✔
111
                {:ok, value} -> value
8✔
NEW
112
                :error -> nil
×
113
              end
114
            end)
115

116
          {:ok, inserted}
117

NEW
UNCOV
118
        :error ->
×
119
          :error
120
      end
121
    end
122
  end
123

124
  @doc """
125
  Pushes content to the end of the array.
126
  Returns :ok on success, :error on failure.
127

128
  ## Parameters
129
    * `array` - The array to modify
130
    * `content` - The content to append
131
  """
132
  @spec push(t, Yex.input_type()) :: :ok
133
  def push(%__MODULE__{doc: doc} = array, content) do
134
    Doc.run_in_worker_process(doc,
88✔
135
      do: insert(array, __MODULE__.length(array), content)
88✔
136
    )
137
  end
138

139
  @doc """
140
  Pushes content to the end of the array and returns the pushed content.
141
  Returns {:ok, content} on success, :error on failure.
142

143
  ## Parameters
144
    * `array` - The array to modify
145
    * `content` - The content to append
146

147
  ## Examples
148
      iex> doc = Yex.Doc.new()
149
      iex> array = Yex.Doc.get_array(doc, "array")
150
      iex> {:ok, value} = Yex.Array.push_and_get(array, "Hello")
151
      iex> value
152
      "Hello"
153
  """
154
  @spec push_and_get(t, Yex.input_type()) :: {:ok, term()} | :error
155
  def push_and_get(%__MODULE__{doc: doc} = array, content) do
156
    Doc.run_in_worker_process doc do
5✔
157
      index = __MODULE__.length(array)
5✔
158

159
      case Yex.Nif.array_insert(array, cur_txn(array), index, content) do
5✔
160
        :ok -> Yex.Nif.array_get(array, cur_txn(array), index)
5✔
NEW
UNCOV
161
        :error -> :error
×
162
      end
163
    end
164
  end
165

166
  @doc """
167
  Unshifts content to the beginning of the array.
168
  Returns :ok on success, :error on failure.
169

170
  ## Parameters
171
    * `array` - The array to modify
172
    * `content` - The content to prepend
173
  """
174
  def unshift(%__MODULE__{} = array, content) do
175
    insert(array, 0, content)
2✔
176
  end
177

178
  @doc """
179
  Deletes content at the specified index.
180
  Returns :ok on success, :error on failure.
181

182
  ## Parameters
183
    * `array` - The array to modify
184
    * `index` - The position to delete from (0-based)
185
  """
186
  @spec delete(t, integer()) :: :ok
187
  def delete(%__MODULE__{} = array, index) do
188
    delete_range(array, index, 1)
7✔
189
  end
190

191
  @doc """
192
  Deletes a range of contents starting at the specified index.
193
  Returns :ok on success, :error on failure.
194

195
  ## Parameters
196
    * `array` - The array to modify
197
    * `index` - The starting position to delete from (0-based)
198
    * `length` - The number of elements to delete
199
  """
200
  @spec delete_range(t, integer(), integer()) :: :ok
201
  def delete_range(%__MODULE__{doc: doc} = array, index, length)
202
      when is_integer(index) and is_integer(length) do
203
    Doc.run_in_worker_process doc do
11✔
204
      index = if index < 0, do: __MODULE__.length(array) + index, else: index
11✔
205
      Yex.Nif.array_delete_range(array, cur_txn(array), index, length)
11✔
206
    end
207
  end
208

209
  @doc """
210
  Moves element found at `source` index into `target` index position. Both indexes refer to a current state of the document.
211
  ## Examples pushes a string then fetches it back
212
      iex> doc = Yex.Doc.new()
213
      iex> array = Yex.Doc.get_array(doc, "array")
214
      iex> Yex.Array.push(array, Yex.ArrayPrelim.from([1, 2]))
215
      iex> Yex.Array.push(array, Yex.ArrayPrelim.from([3, 4]))
216
      iex> :ok = Yex.Array.move_to(array, 0, 2)
217
      iex> Yex.Array.to_json(array)
218
      [[3.0, 4.0], [1.0, 2.0]]
219
  """
220
  @spec move_to(t, integer(), integer()) :: :ok
221
  def move_to(%__MODULE__{doc: doc} = array, from, to) when is_integer(from) and is_integer(to) do
222
    Doc.run_in_worker_process(doc,
10✔
223
      do: Yex.Nif.array_move_to(array, cur_txn(array), from, to)
10✔
224
    )
225
  end
226

227
  @deprecated "Rename to `fetch/2`"
228
  @spec get(t, integer()) :: {:ok, term()} | :error
229
  def get(array, index) do
230
    fetch(array, index)
1✔
231
  end
232

233
  @doc """
234
  Get content at the specified index.
235
  ## Examples pushes a string then fetches it back
236
      iex> doc = Yex.Doc.new()
237
      iex> array = Yex.Doc.get_array(doc, "array")
238
      iex> Yex.Array.push(array, "Hello")
239
      iex> Yex.Array.fetch(array, 0)
240
      {:ok, "Hello"}
241
  """
242
  @spec fetch(t, integer()) :: {:ok, term()} | :error
243
  def fetch(%__MODULE__{doc: doc} = array, index) when is_integer(index) do
244
    Doc.run_in_worker_process doc do
31✔
245
      index = if index < 0, do: __MODULE__.length(array) + index, else: index
31✔
246
      Yex.Nif.array_get(array, cur_txn(array), index)
31✔
247
    end
248
  end
249

250
  @spec fetch!(t, integer()) :: term()
251
  def fetch!(%__MODULE__{} = array, index) when is_integer(index) do
252
    case fetch(array, index) do
3✔
253
      {:ok, value} -> value
2✔
254
      :error -> raise ArgumentError, "Index out of bounds"
1✔
255
    end
256
  end
257

258
  @doc """
259
  Returns as list
260

261
  ## Examples adds a few items to an array, then gets them back as Elixir List
262
      iex> doc = Yex.Doc.new()
263
      iex> array = Yex.Doc.get_array(doc, "array")
264
      iex> Yex.Array.push(array, "Hello")
265
      iex> Yex.Array.push(array, "World")
266
      iex> Yex.Array.push(array, Yex.ArrayPrelim.from([1, 2]))
267
      iex> ["Hello", "World", %Yex.Array{}] = Yex.Array.to_list(array)
268

269
  """
270
  def to_list(%__MODULE__{doc: doc} = array) do
271
    Doc.run_in_worker_process doc do
55✔
272
      Yex.Nif.array_to_list(array, cur_txn(array))
55✔
273
    end
274
  end
275

276
  @doc """
277
   slices the array from start_index for amount of elements, then gets them back as Elixir List.
278
  """
279
  def slice(array, start_index, amount) do
280
    slice_take_every(array, start_index, amount, 1)
3✔
281
  end
282

283
  @doc """
284
    slices the array from start_index for amount of elements, then gets them back as Elixir List and takes every `step` element.
285
  """
286
  def slice_take_every(_array, _start_index, _amount, 0) do
1✔
287
    []
288
  end
289

290
  def slice_take_every(%__MODULE__{doc: doc} = array, start_index, amount, step)
291
      when is_integer(step) and step > 0 do
292
    Doc.run_in_worker_process doc do
9✔
293
      Yex.Nif.array_slice(array, cur_txn(array), start_index, amount, step)
9✔
294
    end
295
  end
296

297
  @doc """
298
  Returns the length of the array
299

300
  ## Examples adds a few items to an array and returns its length
301
      iex> doc = Yex.Doc.new()
302
      iex> array = Yex.Doc.get_array(doc, "array")
303
      iex> Yex.Array.push(array, "Hello")
304
      iex> Yex.Array.push(array, "World")
305
      iex> Yex.Array.length(array)
306
      2
307
  """
308
  def length(%__MODULE__{doc: doc} = array) do
309
    Doc.run_in_worker_process doc do
118✔
310
      Yex.Nif.array_length(array, cur_txn(array))
118✔
311
    end
312
  end
313

314
  @doc """
315
  Convert to json-compatible format.
316

317
  ## Examples adds a few items to an array and returns as Elixir List
318
      iex> doc = Yex.Doc.new()
319
      iex> array = Yex.Doc.get_array(doc, "array")
320
      iex> Yex.Array.push(array, "Hello")
321
      iex> Yex.Array.push(array, "World")
322
      iex> Yex.Array.to_json(array)
323
      ["Hello", "World"]
324
  """
325
  @spec to_json(t) :: term()
326
  def to_json(%__MODULE__{doc: doc} = array) do
327
    Doc.run_in_worker_process doc do
30✔
328
      Yex.Nif.array_to_json(array, cur_txn(array))
30✔
329
    end
330
  end
331

332
  def member?(array, val) do
333
    val = Yex.normalize(val)
7✔
334
    Enum.member?(to_list(array), val)
7✔
335
  end
336

337
  defp cur_txn(%{doc: %Yex.Doc{reference: doc_ref}}) do
338
    Process.get(doc_ref, nil)
453✔
339
  end
340

341
  @doc """
342
  Converts the array to its preliminary representation.
343
  This is useful when you need to serialize or transfer the array's contents.
344

345
  ## Parameters
346
    * `array` - The array to convert
347
  """
348
  @spec as_prelim(t) :: Yex.ArrayPrelim.t()
349
  def as_prelim(%__MODULE__{doc: doc} = array) do
350
    Doc.run_in_worker_process(doc,
6✔
351
      do:
352
        Yex.Array.to_list(array)
353
        |> Enum.map(&Yex.Output.as_prelim/1)
354
        |> Yex.ArrayPrelim.from()
6✔
355
    )
356
  end
357

358
  defimpl Yex.Output do
359
    def as_prelim(array) do
360
      Yex.Array.as_prelim(array)
3✔
361
    end
362
  end
363

364
  defimpl Enumerable do
365
    def count(array) do
2✔
366
      {:ok, Yex.Array.length(array)}
367
    end
368

369
    def member?(array, val) do
4✔
370
      {:ok, Yex.Array.member?(array, val)}
371
    end
372

373
    def slice(array) do
374
      size = Yex.Array.length(array)
12✔
375
      {:ok, size, &slice_impl(array, &1, &2, &3)}
12✔
376
    end
377

378
    defp slice_impl(array, start, length, step) do
379
      # Optimize for single element access (Enum.at)
380
      if length == 1 and step == 1 do
9✔
381
        case Yex.Array.fetch(array, start) do
8✔
382
          {:ok, value} -> [value]
8✔
UNCOV
383
          :error -> []
×
384
        end
385
      else
386
        Yex.Array.slice_take_every(array, start, length, step)
1✔
387
      end
388
    end
389

390
    def reduce(array, acc, fun) do
391
      Enumerable.List.reduce(Yex.Array.to_list(array), acc, fun)
5✔
392
    end
393
  end
394
end
395

396
defmodule Yex.ArrayPrelim do
397
  @moduledoc """
398
  A preliminary array. It can be used to early initialize the contents of a Array.
399

400
  ## Examples
401
      iex> doc = Yex.Doc.new()
402
      iex> map = Yex.Doc.get_map(doc, "map")
403
      iex> Yex.Map.set(map, "key", Yex.ArrayPrelim.from(["Hello", "World"]))
404
      iex> {:ok, %Yex.Array{} = array} = Yex.Map.fetch(map, "key")
405
      iex> Yex.Array.fetch(array, 1)
406
      {:ok, "World"}
407

408
  """
409
  defstruct [
410
    :list
411
  ]
412

413
  @type t :: %__MODULE__{
414
          list: list(Yex.input_type())
415
        }
416

417
  @spec from(Enumerable.t(Yex.input_type())) :: t
418
  def from(enumerable) do
419
    %__MODULE__{list: Enum.to_list(enumerable)}
27✔
420
  end
421
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