nohead 0.3.0 → 0.5.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: 4381c6ca769b33734069a6b117b59f03d1ebfa3b283698f2f20047b527bf376f
4
- data.tar.gz: 9b6dbcf5e1cc441cc10356a2d4b18ae0ae7485faeb4bba79a474ce045aa0150e
3
+ metadata.gz: e94f698ee3cb6906c1b21535666bdefc57f6d864dbad5eb3436c9e0a7a4bff2f
4
+ data.tar.gz: a47d275d02c1b5111b0eadc5f14dedf2a201c5fdcf01ae0eaba306c3bdb87ad2
5
5
  SHA512:
6
- metadata.gz: c3491a32b9f27da5dc3ca67b2dbed3952508130cc04ee129954c8ecc4684e4e7fa36806e5914f34bba73ac2da4c43162ffd025891f03f77ad3d6151faa55d1fe
7
- data.tar.gz: 5da0e9d7a4869ae460a70737dd34a4e4b9a20fa61d3524a2f5bd0a1e8fa67de2f61886e0e1f003a0b575b0e20006f5529933dc49433e2d2d3d68189ce4913947
6
+ metadata.gz: bbac1644f34a8ea9ff5b41b342facdfea9df8de606cca48e00fcbde9adcb0d3564c65973f43f087d8d6acc4f9443057107c7eb244ab47f557d96bb6b1f1d14cb
7
+ data.tar.gz: e650ba515324f59b5cd141534be18d49ae4530e5fe89d7d19bf3aac1e2a6c97fc502b6094b06e43e580b771391d110ad72e32c166235eab6492b8592aaba4a0c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ 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.5.0
9
+
10
+ - **Breaking:** field migrations are created under their collection:
11
+ `migrations.create(collection, field_id:, ..., dry_run: true)`
12
+ (`POST /v1/collections/{collection_id}/migrations`) replaces
13
+ `fields.migrate(field, ...)`. It returns a preview for a dry run, and the
14
+ FieldMigration otherwise.
15
+ - **Breaking:** `drafts.list` no longer takes `record_id:`; a record's draft
16
+ is `records.drafts(record)`.
17
+ - **Breaking:** `assets.list(content_type:)` takes one MIME type or `type/*`
18
+ wildcard (`"image/*"`), or an Array of them, sent as
19
+ `filter[content_type][in]` (an empty Array is any type). A
20
+ comma-separated string is no longer several types.
21
+ - **Breaking:** `audit_events.list` sorts by `created_at` or `-created_at`
22
+ (the default; was `id` and `-id`), and `records.list` no longer sorts by
23
+ `id`.
24
+ - Webhook events always carry `actor` and `via`, and `via` is no longer
25
+ `import` or `migration`.
26
+
27
+ ## 0.4.0
28
+
29
+ - **Breaking:** a field's `api_key` is its `key` (in fields, their aliases,
30
+ `fields.create(..., key:)` and `fields.update(..., key:)`), as the
31
+ editor's "Field key" says. API keys keep their name.
32
+ - **Breaking:** `collections.schema_changes.get(schema_change)` takes the
33
+ schema change's ID alone (`GET /v1/schema-changes/{id}`).
34
+ - **Breaking:** `fields.update` no longer takes `type` or `multiple`; they
35
+ rewrite record values, so they are `fields.migrate`. The API refuses a
36
+ body key an operation doesn't take with `400` (`unknown_parameter`,
37
+ naming it), so a misspelt keyword no longer passes unnoticed.
38
+ - The contract types each field's `configuration` by field type; the API
39
+ reference lists each type's options.
40
+
8
41
  ## 0.3.0
9
42
 
10
43
  - **Upgrade required:** 0.1 and 0.2 no longer work with the current API,
data/README.md CHANGED
@@ -95,7 +95,7 @@ nohead.drafts.merge(draft.id) # or drafts.schedule(draft.id, merge_at: ...)
95
95
 
96
96
  - A record has at most one draft; starting a second raises `Nohead::ConflictError` (detail code `draft_exists`, with its `draft_id`).
97
97
  - 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).
98
+ - `records.drafts(id)` lists a record's draft, and `drafts.list(collection)` a collection's.
99
99
  - `drafts.discard` deletes one for good.
100
100
 
101
101
  ## Pagination
@@ -229,12 +229,12 @@ Search needs `search_enabled` collections and the `search:read` scope. It pages
229
229
 
230
230
  ```ruby
231
231
  nohead.collections.create(name: "Posts", slug: "posts",
232
- fields: [{ name: "Title", api_key: "title", type: "text", required: true }])
233
- nohead.fields.create("posts", name: "Summary", api_key: "summary", type: "long_text")
232
+ fields: [{ name: "Title", key: "title", type: "text", required: true }])
233
+ nohead.fields.create("posts", name: "Summary", key: "summary", type: "long_text")
234
234
 
235
235
  # 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")
236
+ preview = nohead.migrations.create("posts", field_id: "fld_...", type: "long_text", dry_run: true)
237
+ migration = nohead.migrations.create("posts", field_id: "fld_...", type: "long_text")
238
238
  nohead.migrations.get(migration.id)
239
239
  ```
240
240
 
@@ -279,8 +279,8 @@ The API's resources come back as `Nohead::NoheadObject`s.
279
279
  | `collections` | `list`, `get`, `create`, `update`, `delete`, `restore`, `schema` |
280
280
  | `collections.schema_changes` | `list`, `get` |
281
281
  | `collections.search_index` | `get`, `rebuild` |
282
- | `fields` | `list`, `get`, `create`, `update`, `delete`, `restore`, `order`, `reorder`, `remove_alias`, `migrate` |
283
- | `migrations` | `list`, `get`, `cancel` |
282
+ | `fields` | `list`, `get`, `create`, `update`, `delete`, `restore`, `order`, `reorder`, `remove_alias` |
283
+ | `migrations` | `create`, `list`, `get`, `cancel` |
284
284
  | `assets` | `upload`, `create_upload`, `complete`, `list`, `get`, `delete`, `restore`, `purge`, `usage`, `image_url`, `download_url` |
285
285
  | `webhooks` | `list`, `get`, `create`, `update`, `delete`, `restore`, `rotate_secret`, `test`, `unwrap` |
286
286
  | `webhooks.secrets` | `list`, `retire` |
data/lib/nohead/client.rb CHANGED
@@ -148,6 +148,10 @@ module Nohead
148
148
 
149
149
  def build_url(template, values)
150
150
  values = values.transform_keys(&:to_s)
151
+ # A value the template doesn't take is a method out of step with its operation.
152
+ unknown = values.keys - template.scan(/\{(\w+)\}/).flatten
153
+ raise ArgumentError, "#{unknown.join(', ')} is not a path parameter of #{template}" if unknown.any?
154
+
151
155
  values["project_id"] ||= project_id if template.include?("{project_id}")
152
156
  path = template.gsub(/\{(\w+)\}/) do
153
157
  value = values[Regexp.last_match(1)]
@@ -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"],
@@ -68,7 +68,7 @@ module Nohead
68
68
  "records_unschedule" => ["DELETE", "/v1/records/{record_id}/schedule"],
69
69
  "records_update" => ["PATCH", "/v1/records/{record_id}"],
70
70
  "scheduled_changes_list" => ["GET", "/v1/collections/{collection_id}/scheduled-changes"],
71
- "schema_changes_get" => ["GET", "/v1/collections/{collection_id}/schema-changes/{schema_change_id}"],
71
+ "schema_changes_get" => ["GET", "/v1/schema-changes/{schema_change_id}"],
72
72
  "schema_changes_list" => ["GET", "/v1/collections/{collection_id}/schema-changes"],
73
73
  "search_index_get" => ["GET", "/v1/collections/{collection_id}/search-index"],
74
74
  "search_index_rebuild" => ["POST", "/v1/collections/{collection_id}/search-index/rebuild"],
@@ -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
@@ -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)
@@ -60,9 +60,8 @@ module Nohead
60
60
  query: { limit: limit, cursor: cursor })
61
61
  end
62
62
 
63
- def get(collection, schema_change)
64
- @client.request("schema_changes_get",
65
- path: { collection_id: collection, schema_change_id: schema_change })
63
+ def get(schema_change)
64
+ @client.request("schema_changes_get", path: { schema_change_id: schema_change })
66
65
  end
67
66
  end
68
67
 
@@ -96,7 +95,7 @@ module Nohead
96
95
  end
97
96
 
98
97
  # Renames, describes or loosens a field. Changes that rewrite record
99
- # values (type, multiple, tighter rules) are `migrate`.
98
+ # values (type, multiple, tighter rules) are `migrations.create`.
100
99
  def update(field, idempotency_key: nil, change_note: nil, **params)
101
100
  @client.request("fields_update", path: { field_id: field }, body: body_of(params),
102
101
  idempotency_key: idempotency_key, change_note: change_note)
@@ -126,23 +125,26 @@ module Nohead
126
125
  idempotency_key: idempotency_key, change_note: change_note)
127
126
  end
128
127
 
129
- # 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.
130
129
  def remove_alias(field, alias_key, idempotency_key: nil)
131
130
  @client.request("fields_remove_alias", path: { field_id: field, alias: alias_key },
132
131
  idempotency_key: idempotency_key)
133
132
  end
134
-
135
- # Starts a field migration (type, multiple, tighter configuration or a
136
- # backfill) that rewrites every record. With dry_run: true, previews it.
137
- def migrate(field, dry_run: false, idempotency_key: nil, change_note: nil, **params)
138
- @client.request("fields_migrate", path: { field_id: field }, query: { dry_run: dry_run || nil },
139
- body: body_of(params), idempotency_key: idempotency_key,
140
- change_note: change_note)
141
- end
142
133
  end
143
134
 
144
135
  # Field migrations. While one runs, its collection is read-only.
145
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
+
146
148
  def list(collection, limit: nil, cursor: nil)
147
149
  @client.paginate("migrations_list", path: { collection_id: collection },
148
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.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
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.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nohead