nohead 0.4.0 → 0.6.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: 747275a28bad0e0410f52109f1bac03d8d703fedccafe3110f21ae904421d511
4
- data.tar.gz: 696d827a38b169e1a047b38a40d9263417cff3f111cec74d825a77236072a62c
3
+ metadata.gz: 4f80d6fdee7ba3b00716d16b5a0e0203b8e2b0c12e35cbf9747bb2e17a40335f
4
+ data.tar.gz: 413cce95b1bc00ae8c68ca6d4c951975940b5e5e61e1bb0ab9a807c4b9b32f5b
5
5
  SHA512:
6
- metadata.gz: e82adbab22c4ea949feccc1c347926b5c2a6795f087b02a8915255f4c7a49f56b53e088ae4b8579374c3db27a59c8bf12c428970d28f83124f047f120c08c7bb
7
- data.tar.gz: 6c0c8b4dfc63a7eb8d89cc76030e7cf9797ba28541bd677b1da117b7975039d5b3742677230057ff29c4826ac7abf47acd1e3cfba3b38bb84809c4fa4cf233da
6
+ metadata.gz: 3a0ef2d3ca637467739881a7bc9ccf667d47d8419dafb2959290b2108e6ad8b7204661452272e218c73b5dee7c8c3f69611404a2253cb2b998f7ff1b3daec207
7
+ data.tar.gz: 3658e753b524d77745c43947edf2a5f4926b615adfa6fbd30bf46bea84a2c6f02210d9b40fdb7323497d879f2e96c59372dd483a8cde4402d9b41246b1c28517
data/CHANGELOG.md CHANGED
@@ -5,6 +5,59 @@ Changes to the `nohead` gem that you can notice. Versions follow
5
5
  releases; a change that could break your code is a major one. Each release's
6
6
  section is its GitHub release's notes.
7
7
 
8
+ ## 0.6.0
9
+
10
+ - API keys now start `nohead_sk_`, or `nohead_pk_` for published-only keys;
11
+ keys in the old `sk_live_` format no longer work. The gem sends whatever key
12
+ it's given, so only the docs and examples change.
13
+
14
+ - `assets.update(asset, filename:, alt:)` renames an asset and sets its alt
15
+ text (`PATCH /v1/assets/{asset_id}`). Assets have `alt` (a string or
16
+ `nil`), and webhooks can subscribe to `asset.updated`.
17
+
18
+ - Image URLs are open. Assets have `image_url`, the whole image, and the new
19
+ `Nohead::Images.url` writes any of imgproxy's processing options into it,
20
+ without a request.
21
+
22
+ - **Breaking:** `assets.image_url` is gone, with the API's
23
+ `GET /v1/assets/{asset_id}/image-url`: use `Nohead::Images.url`, which
24
+ needs no request.
25
+
26
+ - **Breaking:** collections have no `search_enabled`, and search indexes no
27
+ `enabled`, and collections' `create` and `update` no longer take
28
+ `search_enabled:`: API keys search every collection they can read.
29
+
30
+ - `revisions.get(revision)` reads a revision by its own `rev_` ID, as
31
+ webhooks and the activity log name it (`GET /v1/revisions/{revision_id}`).
32
+
33
+ - `records.create` takes `upsert_on:`, a unique field's key: the record
34
+ holding that value is updated instead, if one exists, and `if_match:`
35
+ applies to that update.
36
+
37
+ - Collections have `public_search`: whether the project's search tokens find
38
+ their published records. Off by default;
39
+ `collections.create(public_search: ...)` and `collections.update` take it. A
40
+ collection's search index has `index`, the name a token searches it by.
41
+
42
+ ## 0.5.0
43
+
44
+ - **Breaking:** field migrations are created under their collection:
45
+ `migrations.create(collection, field_id:, ..., dry_run: true)`
46
+ (`POST /v1/collections/{collection_id}/migrations`) replaces
47
+ `fields.migrate(field, ...)`. It returns a preview for a dry run, and the
48
+ FieldMigration otherwise.
49
+ - **Breaking:** `drafts.list` no longer takes `record_id:`; a record's draft
50
+ is `records.drafts(record)`.
51
+ - **Breaking:** `assets.list(content_type:)` takes one MIME type or `type/*`
52
+ wildcard (`"image/*"`), or an Array of them, sent as
53
+ `filter[content_type][in]` (an empty Array is any type). A
54
+ comma-separated string is no longer several types.
55
+ - **Breaking:** `audit_events.list` sorts by `created_at` or `-created_at`
56
+ (the default; was `id` and `-id`), and `records.list` no longer sorts by
57
+ `id`.
58
+ - Webhook events always carry `actor` and `via`, and `via` is no longer
59
+ `import` or `migration`.
60
+
8
61
  ## 0.4.0
9
62
 
10
63
  - **Breaking:** a field's `api_key` is its `key` (in fields, their aliases,
data/README.md CHANGED
@@ -52,7 +52,7 @@ nohead = Nohead::Client.new(
52
52
 
53
53
  | Option | Default | |
54
54
  |---|---|---|
55
- | `api_key:` | `NOHEAD_API_KEY` | A project API key (`sk_live_...`). Required. |
55
+ | `api_key:` | `NOHEAD_API_KEY` | A project API key (`nohead_sk_...`, or `nohead_pk_...` when published-only). Required. |
56
56
  | `base_url:` | `NOHEAD_API_URL`, else `https://api.nohead.io` | |
57
57
  | `project_id:` | the key's project | Looked up once with `GET /v1/me` when omitted. |
58
58
  | `max_retries:` | `2` | See [retries](#retries-and-idempotency). |
@@ -76,11 +76,14 @@ nohead.records.delete(post.id) # soft delete; records.restore undoes it
76
76
 
77
77
  Methods return the API's resources (see [Results](#results)) and raise on failure. Field values are under `data`, keyed by field API key.
78
78
 
79
+ **Upserts.** With `upsert_on:`, the key of a `unique` field, `create` updates the record holding that value instead, if one exists, so a content sync can run again without duplicates: `nohead.records.create("products", data: { sku: "TEE-01", title: "Tee" }, upsert_on: "sku")`.
80
+
79
81
  **More:**
80
82
 
81
83
  - `count`, and `bulk` (up to 100 records at once)
82
84
  - `diff(record, from: 1, to: 2)`
83
85
  - `revisions.list` (`filter:` `operation`, `actor_id` and a `created_at` range such as `{ gte: Time.utc(2026, 10, 1) }`), `revisions.get` and `revisions.revert` (with `dry_run: true` for a preview)
86
+ - a revision by its own ID, as webhooks and the activity log name it: `nohead.revisions.get("rev_...")`
84
87
 
85
88
  ## Drafts
86
89
 
@@ -95,7 +98,7 @@ nohead.drafts.merge(draft.id) # or drafts.schedule(draft.id, merge_at: ...)
95
98
 
96
99
  - A record has at most one draft; starting a second raises `Nohead::ConflictError` (detail code `draft_exists`, with its `draft_id`).
97
100
  - A merge changes content only: a live record stays live, an unpublished one unpublished. Publishing a live record with a pending draft raises `ConflictError` (`draft_pending`).
98
- - `records.drafts(id)` lists a record's draft, and `drafts.list(collection, record_id: ...)` a collection's (`record_id` takes up to 100 IDs).
101
+ - `records.drafts(id)` lists a record's draft, and `drafts.list(collection)` a collection's.
99
102
  - `drafts.discard` deletes one for good.
100
103
 
101
104
  ## Pagination
@@ -195,9 +198,17 @@ end
195
198
  asset = nohead.assets.upload("cover.jpg")
196
199
  nohead.records.update(id, data: { cover: asset.id })
197
200
 
198
- url = nohead.assets.image_url(asset.id, width: 1200, format: "webp").url
199
201
  ```
200
202
 
203
+ **Images.** Image URLs are open: an image's `image_url` serves the whole image, and any of [imgproxy's processing options](https://docs.imgproxy.net/usage/processing) written into it transform it. `Nohead::Images.url` builds them without a request:
204
+
205
+ ```ruby
206
+ Nohead::Images.url(asset, width: 1200, height: 630, fit: "cover", gravity: "sm", format: "webp")
207
+ Nohead::Images.url(asset.image_url, width: 400, dpr: 2, options: ["bl:2"])
208
+ ```
209
+
210
+ Thumbnails: an image's `preview_url` fits in 640×640, as WebP.
211
+
201
212
  **What `upload` accepts:** a path (String or Pathname), or an IO opened in binary mode. For bytes in memory, pass `StringIO.new(bytes)`.
202
213
 
203
214
  **What it does:**
@@ -212,6 +223,12 @@ url = nohead.assets.image_url(asset.id, width: 1200, format: "webp").url
212
223
 
213
224
  **Uploading from a browser:** create the upload on your server with `create_upload`, `PUT` the file from the browser, then `complete` it.
214
225
 
226
+ **Renaming and alt text:** `update` changes an asset's `filename:` and `alt:` text, the default wherever the asset is used (`expand:` returns it with the asset). The file itself never changes.
227
+
228
+ ```ruby
229
+ nohead.assets.update(asset.id, alt: "The spring menu, on a chalkboard")
230
+ ```
231
+
215
232
  ## Search
216
233
 
217
234
  ```ruby
@@ -223,7 +240,7 @@ results = nohead.search("content model", collections: %w[posts pages], filter: {
223
240
  results.meta.total_estimate
224
241
  ```
225
242
 
226
- Search needs `search_enabled` collections and the `search:read` scope. It pages through the first 1,000 hits.
243
+ Search needs the `search:read` scope and covers every collection the key can read, matching their `searchable` fields. It pages through the first 1,000 hits. For search straight from a site's browser, without the API, use a search token from the web app with Meilisearch's own clients or InstantSearch.
227
244
 
228
245
  ## Schema
229
246
 
@@ -233,8 +250,8 @@ nohead.collections.create(name: "Posts", slug: "posts",
233
250
  nohead.fields.create("posts", name: "Summary", key: "summary", type: "long_text")
234
251
 
235
252
  # Changes that rewrite records go through a migration; preview first
236
- preview = nohead.fields.migrate("fld_...", type: "long_text", dry_run: true)
237
- migration = nohead.fields.migrate("fld_...", type: "long_text")
253
+ preview = nohead.migrations.create("posts", field_id: "fld_...", type: "long_text", dry_run: true)
254
+ migration = nohead.migrations.create("posts", field_id: "fld_...", type: "long_text")
238
255
  nohead.migrations.get(migration.id)
239
256
  ```
240
257
 
@@ -275,13 +292,14 @@ The API's resources come back as `Nohead::NoheadObject`s.
275
292
  | `records` | `list`, `get`, `create`, `update`, `delete`, `restore`, `publish`, `unpublish`, `publication`, `get_schedule`, `schedule`, `unschedule`, `scheduled_changes`, `drafts`, `count`, `bulk`, `diff`, `search` |
276
293
  | `drafts` | `create`, `list`, `get`, `update`, `discard`, `merge`, `schedule`, `unschedule` |
277
294
  | `records.revisions` | `list`, `get`, `revert` |
295
+ | `revisions` | `get` (by its own `rev_` ID) |
278
296
  | `search` | across the project |
279
297
  | `collections` | `list`, `get`, `create`, `update`, `delete`, `restore`, `schema` |
280
298
  | `collections.schema_changes` | `list`, `get` |
281
299
  | `collections.search_index` | `get`, `rebuild` |
282
- | `fields` | `list`, `get`, `create`, `update`, `delete`, `restore`, `order`, `reorder`, `remove_alias`, `migrate` |
283
- | `migrations` | `list`, `get`, `cancel` |
284
- | `assets` | `upload`, `create_upload`, `complete`, `list`, `get`, `delete`, `restore`, `purge`, `usage`, `image_url`, `download_url` |
300
+ | `fields` | `list`, `get`, `create`, `update`, `delete`, `restore`, `order`, `reorder`, `remove_alias` |
301
+ | `migrations` | `create`, `list`, `get`, `cancel` |
302
+ | `assets` | `upload`, `create_upload`, `complete`, `list`, `get`, `update`, `delete`, `restore`, `purge`, `usage`, `download_url` |
285
303
  | `webhooks` | `list`, `get`, `create`, `update`, `delete`, `restore`, `rotate_secret`, `test`, `unwrap` |
286
304
  | `webhooks.secrets` | `list`, `retire` |
287
305
  | `webhooks.deliveries` | `list`, `get`, `retry` |
data/lib/nohead/client.rb CHANGED
@@ -15,10 +15,11 @@ module Nohead
15
15
  RETRYABLE_STATUSES = [429, 500, 502, 503, 504].freeze
16
16
  MAX_RETRY_AFTER = 60
17
17
 
18
- attr_reader :records, :drafts, :collections, :fields, :migrations, :assets, :webhooks,
18
+ attr_reader :records, :revisions, :drafts, :collections, :fields, :migrations, :assets, :webhooks,
19
19
  :audit_events, :feature_flags, :me, :health
20
20
 
21
- # api_key:: A project API key ("sk_live_..."). Defaults to NOHEAD_API_KEY.
21
+ # api_key:: A project API key ("nohead_sk_...", or "nohead_pk_..." when
22
+ # published-only). Defaults to NOHEAD_API_KEY.
22
23
  # base_url:: Defaults to NOHEAD_API_URL, else https://api.nohead.io.
23
24
  # project_id:: The key's project. Looked up once with GET /v1/me when omitted.
24
25
  # max_retries:: Retries of failed requests (see the README, "Retries").
@@ -40,6 +41,7 @@ module Nohead
40
41
  @warned = {}
41
42
 
42
43
  @records = Resources::Records.new(self)
44
+ @revisions = Resources::RevisionsById.new(self)
43
45
  @drafts = Resources::Drafts.new(self)
44
46
  @collections = Resources::Collections.new(self)
45
47
  @fields = Resources::Fields.new(self)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nohead
4
+ # Image renditions without a request. Image URLs are open: an image asset's
5
+ # `image_url` serves the whole image, and any of imgproxy's processing
6
+ # options written into it transform it
7
+ # (https://docs.imgproxy.net/usage/processing).
8
+ #
9
+ # Nohead::Images.url(asset, width: 800, height: 600, fit: "cover", format: "webp")
10
+ module Images
11
+ FITS = { "contain" => "fit", "cover" => "fill", "fill" => "force" }.freeze
12
+
13
+ module_function
14
+
15
+ # A rendition of an image (an asset, as an object or a Hash, or its `image_url`). fit: "contain"
16
+ # fits inside the box, "cover" fills it, cropping, "fill" stretches to it.
17
+ # gravity: is what a crop keeps ("sm" for smart, "fp:0.5:0.3" for a focal
18
+ # point); options: any other processing options, as written in a URL
19
+ # (["bl:2"]).
20
+ def url(image, width: nil, height: nil, fit: "contain", format: nil, quality: nil, dpr: nil,
21
+ gravity: nil, options: [])
22
+ base = base_of(image)
23
+ check_options([gravity, *options])
24
+ at = base.index("/plain/")
25
+ parts = [resize(fit, width, height), ("g:#{gravity}" if gravity), ("dpr:#{dpr}" if dpr),
26
+ ("q:#{quality}" if quality), *options].compact
27
+ base[0...at] + parts.map { |part| "/#{part}" }.join + source(base[at..], format)
28
+ end
29
+
30
+ # rs:<fit>:<width>:<height>, or nothing without a size (0 is none).
31
+ def resize(fit, width, height)
32
+ type = FITS.fetch(fit.to_s) { raise ArgumentError, "fit must be one of #{FITS.keys.join(', ')}" }
33
+ width = nil if width&.zero?
34
+ height = nil if height&.zero?
35
+ "rs:#{type}:#{width || 0}:#{height || 0}" if width || height
36
+ end
37
+ private_class_method :resize
38
+
39
+ def base_of(image)
40
+ base = case image
41
+ when String then image
42
+ when Hash then image["image_url"] || image[:image_url]
43
+ else image.image_url
44
+ end
45
+ raise ArgumentError, "Not a ready image: it has no image_url" if base.nil? || base.empty?
46
+ raise ArgumentError, "Not a Nohead image URL: #{base}" unless base.include?("/plain/")
47
+
48
+ base
49
+ end
50
+ private_class_method :base_of
51
+
52
+ def check_options(values)
53
+ bad = values.compact.find { |value| value.to_s.match?(%r{[/@?#]}) }
54
+ raise ArgumentError, "Not an imgproxy option: #{bad}" if bad
55
+ end
56
+ private_class_method :check_options
57
+
58
+ # The source comes last; a format replaces any the URL already names.
59
+ def source(source, format)
60
+ format ? "#{source.sub(/@[a-z]+\z/, '')}@#{format}" : source
61
+ end
62
+ private_class_method :source
63
+ end
64
+ end
@@ -8,10 +8,10 @@ module Nohead
8
8
  "assets_delete" => ["DELETE", "/v1/assets/{asset_id}"],
9
9
  "assets_download_url" => ["GET", "/v1/assets/{asset_id}/download-url"],
10
10
  "assets_get" => ["GET", "/v1/assets/{asset_id}"],
11
- "assets_image_url" => ["GET", "/v1/assets/{asset_id}/image-url"],
12
11
  "assets_list" => ["GET", "/v1/projects/{project_id}/assets"],
13
12
  "assets_purge" => ["POST", "/v1/assets/{asset_id}/purge"],
14
13
  "assets_restore" => ["POST", "/v1/assets/{asset_id}/restore"],
14
+ "assets_update" => ["PATCH", "/v1/assets/{asset_id}"],
15
15
  "assets_upload" => ["POST", "/v1/projects/{project_id}/assets"],
16
16
  "assets_usage" => ["GET", "/v1/assets/{asset_id}/usage"],
17
17
  "audit_events_list" => ["GET", "/v1/projects/{project_id}/audit-events"],
@@ -37,7 +37,6 @@ module Nohead
37
37
  "fields_get" => ["GET", "/v1/fields/{field_id}"],
38
38
  "fields_get_order" => ["GET", "/v1/collections/{collection_id}/field-order"],
39
39
  "fields_list" => ["GET", "/v1/collections/{collection_id}/fields"],
40
- "fields_migrate" => ["POST", "/v1/fields/{field_id}/migrations"],
41
40
  "fields_remove_alias" => ["DELETE", "/v1/fields/{field_id}/aliases/{alias}"],
42
41
  "fields_reorder" => ["PUT", "/v1/collections/{collection_id}/field-order"],
43
42
  "fields_restore" => ["POST", "/v1/fields/{field_id}/restore"],
@@ -45,6 +44,7 @@ module Nohead
45
44
  "health_check" => ["GET", "/v1/health"],
46
45
  "me_get" => ["GET", "/v1/me"],
47
46
  "migrations_cancel" => ["POST", "/v1/migrations/{migration_id}/cancel"],
47
+ "migrations_create" => ["POST", "/v1/collections/{collection_id}/migrations"],
48
48
  "migrations_get" => ["GET", "/v1/migrations/{migration_id}"],
49
49
  "migrations_list" => ["GET", "/v1/collections/{collection_id}/migrations"],
50
50
  "projects_search" => ["GET", "/v1/projects/{project_id}/search"],
@@ -67,6 +67,7 @@ module Nohead
67
67
  "records_unpublish" => ["DELETE", "/v1/records/{record_id}/publication"],
68
68
  "records_unschedule" => ["DELETE", "/v1/records/{record_id}/schedule"],
69
69
  "records_update" => ["PATCH", "/v1/records/{record_id}"],
70
+ "revisions_get" => ["GET", "/v1/revisions/{revision_id}"],
70
71
  "scheduled_changes_list" => ["GET", "/v1/collections/{collection_id}/scheduled-changes"],
71
72
  "schema_changes_get" => ["GET", "/v1/schema-changes/{schema_change_id}"],
72
73
  "schema_changes_list" => ["GET", "/v1/collections/{collection_id}/schema-changes"],
@@ -54,10 +54,12 @@ module Nohead
54
54
  @client.request("assets_complete", path: { asset_id: asset }, idempotency_key: idempotency_key)
55
55
  end
56
56
 
57
- # `content_type` lists only assets of those MIME types or `type/*`
58
- # wildcards (["image/*", "application/pdf"]), for example the ones an
59
- # asset field accepts (its `accepted_types`).
57
+ # `content_type` lists only assets of a MIME type or `type/*` wildcard
58
+ # ("image/*"), or of several (["image/*", "application/pdf"]), for
59
+ # example the ones an asset field accepts (its `accepted_types`; an
60
+ # empty list, like a field's, means any type).
60
61
  def list(content_type: nil, deleted: nil, limit: nil, cursor: nil)
62
+ content_type = (content_type.empty? ? nil : { in: content_type }) if content_type.is_a?(Array)
61
63
  @client.paginate("assets_list", query: { filter: { content_type: content_type, deleted: deleted },
62
64
  limit: limit, cursor: cursor })
63
65
  end
@@ -66,6 +68,15 @@ module Nohead
66
68
  @client.request("assets_get", path: { asset_id: asset })
67
69
  end
68
70
 
71
+ # Renames the asset (`filename:`) or sets its `alt:` text (nil or ""
72
+ # clears it). The file itself can't change: upload a new asset for that.
73
+ # `alt` is the default wherever the asset is used. A deleted asset is a
74
+ # ConflictError: restore it first.
75
+ def update(asset, idempotency_key: nil, **params)
76
+ @client.request("assets_update", path: { asset_id: asset }, body: body_of(params),
77
+ idempotency_key: idempotency_key)
78
+ end
79
+
69
80
  # Soft-deletes the asset: restore brings it back until its `purge_after`,
70
81
  # when the file is purged.
71
82
  def delete(asset, idempotency_key: nil)
@@ -91,14 +102,7 @@ module Nohead
91
102
  @client.request("assets_usage", path: { asset_id: asset })
92
103
  end
93
104
 
94
- # A signed, cacheable URL of an image rendition (`url`).
95
- def image_url(asset, width: nil, height: nil, fit: nil, format: nil, quality: nil)
96
- @client.request("assets_image_url", path: { asset_id: asset }, query: {
97
- width: width, height: height, fit: fit, format: format, quality: quality
98
- })
99
- end
100
-
101
- # A 15-minute link to download the original file (`url`).
105
+ # A link to download the original file (`url`), valid for 2 minutes.
102
106
  def download_url(asset)
103
107
  @client.request("assets_download_url", path: { asset_id: asset })
104
108
  end
@@ -16,16 +16,11 @@ module Nohead
16
16
  idempotency_key: idempotency_key)
17
17
  end
18
18
 
19
- # A collection's drafts, most recently changed first. `record_id`
20
- # narrows them to one record, or to several (an Array of 1 to 100; an
21
- # empty one raises ArgumentError, since it would match no record).
22
- def list(collection, record_id: nil, sort: nil, limit: nil, cursor: nil)
23
- raise ArgumentError, "record_id needs at least one record ID" if record_id == []
24
-
25
- record_filter = record_id.is_a?(Array) ? { in: record_id } : record_id
19
+ # A collection's drafts, most recently changed first. One record's
20
+ # draft is `records.drafts(record)`.
21
+ def list(collection, sort: nil, limit: nil, cursor: nil)
26
22
  @client.paginate("drafts_list", path: { collection_id: collection },
27
- query: { filter: { record_id: record_filter }, sort: sort,
28
- limit: limit, cursor: cursor })
23
+ query: { sort: sort, limit: limit, cursor: cursor })
29
24
  end
30
25
 
31
26
  # With `data` as the record reads after the merge, for previews.
@@ -10,6 +10,8 @@ module Nohead
10
10
  #
11
11
  # audit_events.list(filter: { type: "record.*", resource_type: %w[record draft],
12
12
  # created_at: { gte: Time.utc(2026, 10, 1) } })
13
+ #
14
+ # `sort` is "-created_at" (the default) or "created_at".
13
15
  def list(filter: nil, sort: nil, limit: nil, cursor: nil)
14
16
  filter = filter&.transform_keys(&:to_sym)
15
17
  types = filter&.[](:resource_type)
@@ -52,9 +52,13 @@ module Nohead
52
52
  end
53
53
 
54
54
  # Creates an unpublished record: create("posts", data: { title: "Hello" }).
55
- def create(collection, idempotency_key: nil, change_note: nil, **params)
55
+ # With upsert_on:, the key of a unique field, it updates the record holding
56
+ # that value instead, if one exists, so a sync can run again without
57
+ # duplicates; if_match: then applies to that update.
58
+ def create(collection, if_match: nil, idempotency_key: nil, change_note: nil, **params)
56
59
  @client.request("records_create", path: { collection_id: collection }, body: body_of(params),
57
- idempotency_key: idempotency_key, change_note: change_note)
60
+ if_match: if_match, idempotency_key: idempotency_key,
61
+ change_note: change_note)
58
62
  end
59
63
 
60
64
  # Sets the given fields (nil clears one); others keep their values.
@@ -147,7 +151,7 @@ module Nohead
147
151
  @client.request("records_diff", path: { record_id: record }, query: { from: from, to: to })
148
152
  end
149
153
 
150
- # Full-text search in one collection (search_enabled collections), most
154
+ # Full-text search in one collection, matching its searchable fields, most
151
155
  # relevant first, through the first 1,000 hits.
152
156
  def search(collection, query, filter: nil, sort: nil, expand: nil, limit: nil, cursor: nil)
153
157
  @client.paginate("collections_search", path: { collection_id: collection }, query: {
@@ -178,5 +182,13 @@ module Nohead
178
182
  idempotency_key: idempotency_key, change_note: change_note)
179
183
  end
180
184
  end
185
+
186
+ # A revision by its own ID ("rev_..."), as webhooks and the activity log name
187
+ # it. Readable while its record is deleted.
188
+ class RevisionsById < Resource
189
+ def get(revision)
190
+ @client.request("revisions_get", path: { revision_id: revision })
191
+ end
192
+ end
181
193
  end
182
194
  end
@@ -95,7 +95,7 @@ module Nohead
95
95
  end
96
96
 
97
97
  # Renames, describes or loosens a field. Changes that rewrite record
98
- # values (type, multiple, tighter rules) are `migrate`.
98
+ # values (type, multiple, tighter rules) are `migrations.create`.
99
99
  def update(field, idempotency_key: nil, change_note: nil, **params)
100
100
  @client.request("fields_update", path: { field_id: field }, body: body_of(params),
101
101
  idempotency_key: idempotency_key, change_note: change_note)
@@ -125,23 +125,26 @@ module Nohead
125
125
  idempotency_key: idempotency_key, change_note: change_note)
126
126
  end
127
127
 
128
- # Stops accepting a renamed field's old API key before its 6 months end.
128
+ # Stops accepting a renamed field's old key before its 6 months end.
129
129
  def remove_alias(field, alias_key, idempotency_key: nil)
130
130
  @client.request("fields_remove_alias", path: { field_id: field, alias: alias_key },
131
131
  idempotency_key: idempotency_key)
132
132
  end
133
-
134
- # Starts a field migration (type, multiple, tighter configuration or a
135
- # backfill) that rewrites every record. With dry_run: true, previews it.
136
- def migrate(field, dry_run: false, idempotency_key: nil, change_note: nil, **params)
137
- @client.request("fields_migrate", path: { field_id: field }, query: { dry_run: dry_run || nil },
138
- body: body_of(params), idempotency_key: idempotency_key,
139
- change_note: change_note)
140
- end
141
133
  end
142
134
 
143
135
  # Field migrations. While one runs, its collection is read-only.
144
136
  class Migrations < Resource
137
+ # Starts a migration of one of the collection's fields (`field_id`):
138
+ # its type, multiple, a tighter configuration or a backfill, which
139
+ # rewrites every record. Returns the FieldMigration, or with
140
+ # dry_run: true a preview (counts and sample records, nothing written).
141
+ def create(collection, field_id:, dry_run: false, idempotency_key: nil, change_note: nil, **params)
142
+ @client.request("migrations_create", path: { collection_id: collection },
143
+ query: { dry_run: dry_run || nil },
144
+ body: { field_id: field_id, **body_of(params) },
145
+ idempotency_key: idempotency_key, change_note: change_note)
146
+ end
147
+
145
148
  def list(collection, limit: nil, cursor: nil)
146
149
  @client.paginate("migrations_list", path: { collection_id: collection },
147
150
  query: { limit: limit, cursor: cursor })
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nohead
4
- VERSION = "0.4.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/nohead.rb CHANGED
@@ -23,4 +23,5 @@ require_relative "nohead/resources/schema"
23
23
  require_relative "nohead/resources/assets"
24
24
  require_relative "nohead/resources/other"
25
25
  require_relative "nohead/webhooks"
26
+ require_relative "nohead/images"
26
27
  require_relative "nohead/client"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nohead
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nohead
@@ -24,6 +24,7 @@ files:
24
24
  - lib/nohead.rb
25
25
  - lib/nohead/client.rb
26
26
  - lib/nohead/errors.rb
27
+ - lib/nohead/images.rb
27
28
  - lib/nohead/nohead_object.rb
28
29
  - lib/nohead/operations.rb
29
30
  - lib/nohead/page.rb