atlas_rb 1.6.6 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73faf0ef6535c48fc4e56f6aead845d9110cd9cc41966a8fc336d397840040b0
4
- data.tar.gz: 36ad840afae41129b06fafd5b4957502d9264800dc27b3998e36dfaa6d3c1a4a
3
+ metadata.gz: 1a9ee4ab4b57221618d8ad13ab874acbfce61a452efc8e9562559fcb0215bef8
4
+ data.tar.gz: 22c8c48404af609784177ba0f157380bc1cef435f9b6b37b76f6e65b95cef1a2
5
5
  SHA512:
6
- metadata.gz: 830fc91838b5aaa0a6faa92f040657caa59aa5da3b304c251faf5966cea78807b045af0299220378a0a5c8214c88e8aad335c0aa98f9ff0ec8148204d36bb6a9
7
- data.tar.gz: be1b28b4473fe3761c9d99f00e7138b9bb7ef7445bf62b58d990d94b9b34627434e5a72b4d9cfe9bb4666f0a5c1ec0a95a4a9f6d795aea97078b7b2bf8aa3ede
6
+ metadata.gz: 68ddbb93d8ad4049f16d01786d860f3ccc7bd610e5f57144f79ba0e30dd1bb8e66fd4c98adf17e9847b8551e9b42265b649650acd6c23f3a6dad3ffa3479879b
7
+ data.tar.gz: bdfa9d364af363d50466158d13a944c3062b6d8649e5de57fdbb350d87e30e2c0fbf4dcc72c6ff515b536b40358456dc7359d449e1b2926149ff5f489f3ba9d5
data/.version CHANGED
@@ -1 +1 @@
1
- 1.6.6
1
+ 1.7.0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Added — binary version read surface (`Blob.versions` / `version_content` / `rollback`)
6
+
7
+ The binary counterpart to `Resource.mods_versions` / `mods_version`. Replacing a
8
+ file (`Blob.update`) already retains prior bytes in OCFL; these bindings make the
9
+ retained versions addressable:
10
+
11
+ - `Blob.versions(id)` → `GET /files/:id/versions` — reverse-chronological
12
+ envelope (`{ "blob_id", "versions" }`), one descriptor per retained content
13
+ revision (`version_id`, `file_identifier`, `created`, `digest`, `size`,
14
+ `original_filename`, plus correlated `actor_nuid` / `on_behalf_of_nuid`).
15
+ Admin-gated by the server.
16
+ - `Blob.version_content(id, version_id, &chunk_handler)` →
17
+ `GET /files/:id/versions/:version_id/content` — streams a prior version's
18
+ bytes through a block, exactly like `Blob.content`.
19
+ - `Blob.rollback(id, version_id)` → `POST /files/:id/rollback` — reinstates a
20
+ prior version by appending its bytes as a new revision (non-destructive; NOID
21
+ preserved).
22
+
23
+ ### Added — `Blob.update` accepts `idempotency_key:`
24
+
25
+ `Blob.update` (`PATCH /files/:id`) now takes an optional `idempotency_key:`,
26
+ threaded as the `Idempotency-Key` header (same semantics as `Blob.create` /
27
+ `FileSet.create`). A double-submitted replace sharing a key returns the existing
28
+ Blob instead of minting a second OCFL version.
29
+
3
30
  ## 1.5.0
4
31
 
5
32
  ### Added — optional auth for `Reset.clean`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- atlas_rb (1.6.6)
4
+ atlas_rb (1.7.0)
5
5
  faraday (~> 2.7)
6
6
  faraday-follow_redirects (~> 0.3.0)
7
7
  faraday-multipart (~> 1)
@@ -25,7 +25,7 @@ GEM
25
25
  net-http (~> 0.5)
26
26
  hashie (5.1.0)
27
27
  logger
28
- json (2.19.9)
28
+ json (2.20.0)
29
29
  jwt (2.10.3)
30
30
  base64
31
31
  logger (1.7.0)
data/lib/atlas_rb/blob.rb CHANGED
@@ -153,6 +153,10 @@ module AtlasRb
153
153
  # @param blob_path [String] path to the replacement binary on disk.
154
154
  # @param expected_digest [String, nil] optional verify-on-ingest checksum,
155
155
  # `"<algorithm>:<hexvalue>"`. 422 ({AtlasRb::FixityMismatchError}) on mismatch.
156
+ # @param idempotency_key [String, nil] optional UUID. A double-submit of the
157
+ # replace with the same key returns the existing Blob instead of minting a
158
+ # second OCFL version — without it a retried replace appends a duplicate
159
+ # revision. See {AtlasRb::Work.create} for full semantics.
156
160
  # @param nuid [String, nil] optional acting user's NUID. On the relay-signing
157
161
  # path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
158
162
  # path it is ignored (identity lives in the token).
@@ -168,15 +172,123 @@ module AtlasRb
168
172
  #
169
173
  # @example
170
174
  # AtlasRb::Blob.update("b-321", "/tmp/revised.pdf")
171
- def self.update(id, blob_path, expected_digest: nil, nuid: nil, on_behalf_of: nil)
175
+ #
176
+ # @example Retry-safe replace
177
+ # AtlasRb::Blob.update("b-321", "/tmp/revised.pdf", idempotency_key: SecureRandom.uuid)
178
+ def self.update(id, blob_path, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil)
172
179
  with_file_part(blob_path) do |part|
173
180
  payload = { binary: part }
174
181
  payload[:expected_digest] = expected_digest if expected_digest
175
182
 
176
183
  AtlasRb::Mash.new(JSON.parse(
177
- multipart(nuid, on_behalf_of: on_behalf_of).patch(ROUTE + id, payload)&.body
184
+ multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key)
185
+ .patch(ROUTE + id, payload)&.body
178
186
  ))
179
187
  end
180
188
  end
189
+
190
+ # List a Blob's retained binary version history.
191
+ #
192
+ # Wraps Atlas's `GET /files/<id>/versions` — the binary counterpart to
193
+ # {Resource.mods_versions}. Returns a reverse-chronological (newest first)
194
+ # envelope: one descriptor per retained content revision, each carrying its
195
+ # OCFL `version_id` label, the `file_identifier` appended for that revision,
196
+ # the `created` timestamp, the `digest`/`size` recorded at that version,
197
+ # the stable `original_filename`, and actor attribution (`actor_nuid` /
198
+ # `on_behalf_of_nuid`, null when no audit event correlates).
199
+ #
200
+ # Server admin-gates this endpoint (it exposes edit attribution), so
201
+ # `401` / `403` surface as raw Faraday responses, matching
202
+ # {Resource.mods_versions}. An unknown Blob id yields a `404`.
203
+ #
204
+ # @param id [String] the Blob ID.
205
+ # @param nuid [String, nil] optional acting user's NUID. On the relay-signing
206
+ # path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
207
+ # path it is ignored (identity lives in the token).
208
+ # @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
209
+ # header. Falls through to {AtlasRb.config}.default_on_behalf_of when
210
+ # omitted.
211
+ # @return [AtlasRb::Mash] the parsed envelope, with `"blob_id"` and a
212
+ # `"versions"` array (reverse chronological).
213
+ #
214
+ # @example
215
+ # history = AtlasRb::Blob.versions("b-321")
216
+ # history["versions"].first["version_id"] # => "v5"
217
+ # history["versions"].first["digest"] # => "sha512:9f86d0…"
218
+ def self.versions(id, nuid: nil, on_behalf_of: nil)
219
+ AtlasRb::Mash.new(JSON.parse(
220
+ connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/versions")&.body
221
+ ))
222
+ end
223
+
224
+ # Stream the bytes of a *prior* version of a Blob through a block.
225
+ #
226
+ # Wraps `GET /files/<id>/versions/<version_id>/content` — the version-pinned
227
+ # twin of {.content}, and the read half of "download the superseded file".
228
+ # Like {.content}, the body is **not** buffered: each chunk is yielded to
229
+ # `chunk_handler` immediately (safe for files larger than memory), and the
230
+ # response headers are captured and returned.
231
+ #
232
+ # Pass a `version_id` obtained from {.versions} (an opaque OCFL `vN` label);
233
+ # only labels the history surfaced are addressable. An unknown id or version
234
+ # yields a `404` (raw Faraday response).
235
+ #
236
+ # @param id [String] the Blob ID.
237
+ # @param version_id [String] an OCFL version label from {.versions}, e.g. `"v1"`.
238
+ # @param nuid [String, nil] optional acting user's NUID. On the relay-signing
239
+ # path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
240
+ # path it is ignored (identity lives in the token).
241
+ # @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
242
+ # header. Falls through to {AtlasRb.config}.default_on_behalf_of when
243
+ # omitted.
244
+ # @yieldparam chunk [String] the next chunk of binary data.
245
+ # @return [Hash] the response headers from the version-content request.
246
+ #
247
+ # @example Download a superseded version to disk
248
+ # File.open("/tmp/old.pdf", "wb") do |f|
249
+ # AtlasRb::Blob.version_content("b-321", "v1") { |chunk| f.write(chunk) }
250
+ # end
251
+ def self.version_content(id, version_id, nuid: nil, on_behalf_of: nil, &chunk_handler)
252
+ headers = {}
253
+ connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/versions/#{version_id}/content") do |req|
254
+ req.options.on_data = proc do |chunk, _bytes_received, env|
255
+ headers = env.response_headers if headers.empty? && env
256
+ chunk_handler.call(chunk)
257
+ end
258
+ end
259
+ headers
260
+ end
261
+
262
+ # Roll a Blob back to a prior version.
263
+ #
264
+ # Wraps `POST /files/<id>/rollback`. Atlas promotes the given version to
265
+ # current by appending its bytes again as a NEW revision — so rollback is
266
+ # itself non-destructive (it becomes vN+1 with the bytes of vN) and the Blob
267
+ # NOID is preserved. OCFL dedups the identical content, so no bytes are
268
+ # recopied. Avoids a full round-trip of the bytes back through the caller
269
+ # (vs. re-streaming {.version_content} into {.update}).
270
+ #
271
+ # Pass a `version_id` obtained from {.versions}. An unknown id or version
272
+ # yields a `404` (raw Faraday response).
273
+ #
274
+ # @param id [String] the Blob ID.
275
+ # @param version_id [String] the OCFL version label to reinstate, e.g. `"v1"`.
276
+ # @param nuid [String, nil] optional acting user's NUID. On the relay-signing
277
+ # path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
278
+ # path it is ignored (identity lives in the token).
279
+ # @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
280
+ # header. Falls through to {AtlasRb.config}.default_on_behalf_of when
281
+ # omitted.
282
+ # @return [AtlasRb::Mash] the updated `"blob"` payload (NOID unchanged,
283
+ # `"digest"` refreshed to the reinstated bytes).
284
+ #
285
+ # @example
286
+ # AtlasRb::Blob.rollback("b-321", "v1")
287
+ def self.rollback(id, version_id, nuid: nil, on_behalf_of: nil)
288
+ AtlasRb::Mash.new(JSON.parse(
289
+ connection({}, nuid, on_behalf_of: on_behalf_of)
290
+ .post("#{ROUTE}#{id}/rollback", JSON.dump(version_id: version_id))&.body
291
+ ))['blob']
292
+ end
181
293
  end
182
294
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atlas_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.6
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cliff
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-22 00:00:00.000000000 Z
11
+ date: 2026-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday