Browse all documentation

Typed handles

Use the Go structs and handles that ridu generate writes for each collection and global, so the compiler checks field names and types.

ridu generate writes a Go struct for each collection’s documents, creates and updates, plus a handle that reads and writes them. Bind a handle to the Local API and the compiler checks every field name and type. It runs the same operations as store.Values; only the types change.

Examples use the example schema. Its generated package is generated.

Bind a handle

go
posts := generated.PostsCollection.With(app.Local())

Bind once and reuse the handle. In a hook, bind to ctx.Local so calls join the hook’s transaction.

Create

go
post, err := posts.Create(ctx, generated.PostCreate{
  Title:  "Hello, Ridu",
  Status: core.Set(generated.PostStatusPublished),
  Tags:   core.Set([]string{"launch", "go"}),
}, ridu.TypedMutationOptions{Actor: user})

post is a generated.Post. Required fields, such as Title, are plain values; the others are optional, as described in Optional and null values.

Read

go
page, err := posts.List(ctx, core.TypedListOptions{
  Where: query.Equal("status", "published"),
  Sort:  []query.Sort{query.Desc("publishedAt")},
  Actor: user,
})
if err != nil {
  return err
}
for _, post := range page.Documents {
  fmt.Println(*post.Title)
}

List takes the same filters, sorting, pagination, selection and population as the dynamic API; see Querying data. Find reads one document with core.TypedReadOptions.

Output fields are pointers because access rules or a select can leave any of them out. A relationship is a generated.Reference[T] with the target’s ID, and its Document once you populate it:

go
post, err := posts.Find(ctx, postID, core.TypedReadOptions{
  Populate: []query.Population{{Path: query.Field("author")}},
  Actor:    user,
})
if err != nil {
  return err
}
if post.Author != nil && post.Author.Document != nil {
  fmt.Println(*post.Author.Document.Name)
}

Update and delete

go
title := "A better idea"
post, err := posts.Update(ctx, postID, generated.PostUpdate{
  Title: &title,
  Tags:  core.Null[[]string](),
}, ridu.TypedMutationOptions{Actor: user})

This retitles the post and clears its tags; fields you leave nil keep their values. Delete takes the ID and the same options.

Optional and null values

An optional field in a create or update struct is a *core.Input[T]:

Write To
nil leave the field out, keeping its stored value or default
core.Set(v) set it to v
core.Null[T]() clear it

A required field is a plain value in a create, and a *T in an update because you may leave it out; it can’t be cleared. Lists, maps and some plugin types that can never be null use core.NonNullInput[T]: set them with core.NonNull(v) when required, or core.SetNonNull(v) when optional.

Publish a versioned document

In a versioned collection, a published document changes by being published again. Send the changes to PublishChanges with the revision you read:

go
pages := generated.PagesCollection.With(app.Local())

title := "About us"
updated, err := pages.PublishChanges(ctx, page.ID, generated.PageUpdate{
  Title: &title,
}, ridu.TypedMutationOptions{
  ExpectedRevision: page.Revision,
  Actor:            user,
})

Update refuses a published document here with publish_required. Publish publishes a saved draft, and Unpublish takes a document offline and keeps its content. See Update a versioned document for revision checks.

Globals

go
settings := generated.SiteSettingsGlobal.With(app.Local())

current, err := settings.Update(ctx, generated.SiteSettingsUpdate{
  SiteName: core.Set("Acme"),
}, ridu.TypedMutationOptions{Actor: user})

A global handle has Find and Update, and for a versioned global PublishChanges, Publish, Unpublish, Restore, RestoreAsDraft, SaveDraft and DiscardDraft.

What a handle covers

A collection handle has Create, Find, List, Update, Delete and Import; for versioned collections, PublishChanges, Publish and Unpublish; and for collections with drafts, CreateDraft, SaveDraft and DiscardDraft. For bulk actions, trash, versions, locale copies and inverse joins, use the dynamic Local API from the operation table with the same ctx and caller.

A localized project also gets AllLocales read bindings, whose fields hold every locale’s value. Typed writes always target one locale.