Skip to main content
Wildo.ai Coming soon

Declaring a resource

Make your records ready to use

Choose the everyday actions a resource needs; Wildo supplies their shared execution machinery, with contracts and policies connected to the resource.

Create, read, update and delete act on the same task.

Make your records ready to use

People need to create records, find the right ones, open them and make changes. Each action also needs a valid input, an access decision and a useful response.

Choose the standard actions your resource offers. Wildo derives their contracts and connects them to the shared resource services, so each business object benefits from the same execution machinery.

Your application decides who can act and how the interaction feels: a list, a detail page, an editing screen or a small form within the current view.

Example — Turn a task model into daily work

Team members can add tasks, find the ones that matter and update their progress. Removing a task can also confirm success to the person who acted and notify the rest of the organization, according to the operation’s settings.

For engineers

Choose the actions the resource offers

coreOperations selects the standard verbs: for example, CREATE, READ, LIST, SEARCH, UPDATE and DELETE. operationsConfiguration then gives each operation its variants, roles and other execution choices.

READ addresses a record. LIST and SEARCH work on a collection, with filters, sorting and pagination. API and internal variants are explicit choices; a resource can offer an operation internally without publishing an HTTP endpoint for it.

Connect the schema, identity and selected operations

The resource declaration is a factory: the module supplies its relationship graph, and the factory returns the configuration used by the engine. Wonder Todos connects those pieces in todos.resources-config.ts:

export const todos_ResourceConfiguration_InitializationFactory = (resourcesRelationships: ResourceRelationship[]) => createResourceConfiguration_Initialization<
  typeof Todos_Operations,
  Todos_CoreOperations,
  typeof Todos_Schema
>({
  mainSchema: Todos_Schema,
  resourceIdentifier: TasksManager_ResourceType.TODOS,
  resourceFieldIdentifier: TasksManager_ResourceFieldIdentifier[TasksManager_ResourceType.TODOS],
  resourceRelationships: resourcesRelationships,
  inheritenceSchemaDefinition: TodosSchemaFamily.inheritenceSchemaDefinition,
  isSystemResource: false,
  // Other application policies are omitted from this excerpt.
  coreOperations: [
    CoreResourceOperation.READ,
    CoreResourceOperation.LIST,
    CoreResourceOperation.SEARCH,
    CoreResourceOperation.CREATE,
    CoreResourceOperation.UPDATE,
    CoreResourceOperation.UPDATE_MANY,
    CoreResourceOperation.DELETE,
    CoreResourceOperation.COUNT,
  ],
  customOperation: Todos_Operations,
  // operationsConfiguration follows; see the DELETE entry below.
});

This is an abbreviated wiring excerpt, not a replacement for the complete application configuration. createResourceConfiguration_Initialization, CoreResourceOperation and the ResourceRelationship type are public exports of @wildo-ai/saas-models. The Todos_* and TasksManager_* names are application declarations.

mainSchema provides the shared fields. The identifiers give the resource a stable identity, while the supplied relationships establish its parents and scope. inheritenceSchemaDefinition carries its recurring-task variants. Listing an operation in coreOperations does not choose its public route or grant access: its variant configuration does that. Wonder Todos’ UPDATE_MANY, for example, is internal and supports retention work; it is not a client bulk-update endpoint.

The factory must also enter the application’s shared module registry. Module registration connects discovery to execution; declaring a factory in an otherwise unregistered file is not sufficient.

Keep the action and its surrounding behavior together

This is the DELETE configuration from Wonder Todos’ todos.resources-config.ts, inside operationsConfiguration. Source comments are omitted:

[CoreResourceOperation.DELETE]: {
  variants: [
    {
      variantType: ResourceOperationVariantType.API_CALL,
      isDefault: true,
      haveBulkOperation: true,
      roles: [CORE_ORG_ROLES.ORG_MEMBER],
      riskLevel: ResourceOperationRiskLevel.LOW,
      mcp: { exposed: true, servers: ['ops'], description: 'Delete a todo by its id.' },
    }
  ],
   userNotifications: [
    { target: CoreUserNotificationTarget.USER_SELF, channel: CoreUserNotificationChannel.FRONT_END_SUCCESS },
    { target: CoreUserNotificationTarget.ORGANIZATION_USERS, channel: CoreUserNotificationChannel.WEBSOCKET },
  ],
  m2mNotifications: [{
    channel : CoreM2MNotificationChannel.WEBHOOK_ORGANIZATION, level : M2MNotificationLevel.INFO,
  }]
},

API_CALL publishes an API variant; roles requires organization membership within the operation’s scope. haveBulkOperation opts into acting on a selection. These are application decisions attached to this operation.

The notification settings connect success feedback, organization updates over WebSocket and an organization webhook to the same action. The optional mcp configuration exposes it on the named ops server for agent callers. Each channel is chosen explicitly, rather than becoming available simply because DELETE exists.

Understand what runs behind the declaration

The resource factory expands the selected variants and derives their request and response contracts. For an HTTP request, the controller validates the input and performs the appropriate collection or record authorization. The shared service dispatcher executes the resource operation, and the response serializer applies the operation’s output contract.

A synthesized create contract excludes fields the caller cannot provide, including backend-only and creation-excluded fields. Updates use patch semantics: an omitted field keeps its stored value; an explicit null clears it where the field accepts null. A purpose-specific request schema is authored on its operation variant.

These operations continue to use shared engine services as the application evolves. You configure their behavior instead of maintaining a separate implementation of each standard controller for each resource.

Follow one record through the API

For an organization-scoped todo, the collection address is /organizations/{organizationId}/todos; a record appends the identifier returned by CREATE. Resolve that organization and its todo-list reference before sending the request. The following request sequence illustrates the existing CRUD lifecycle test; identifiers stand for records in the caller’s authorized organization.

POST /organizations/{organizationId}/todos
Content-Type: application/json

{
  "title": "Prepare the launch",
  "todoListId": "{todoListId}",
  "recurringType": "one_time"
}

Read the returned record’s _id, then address that same record:

PUT /organizations/{organizationId}/todos/{returnedId}
Content-Type: application/json

{ "title": "Prepare the launch checklist", "status": "in_progress" }

These paths are relative to the application’s API base URL and omit authentication headers for clarity. A subsequent GET to the record address checks the persisted result. The shared schema declares defaults of pending for status and medium for priority, which this illustration leaves to the schema. The lifecycle test itself sends those values explicitly and reads them back; it does not prove omitted-value defaulting over HTTP. After the update, the supplied title and status change while omitted fields remain intact. Do not manufacture an identifier for a new record and assume CREATE will preserve it.

BoundaryWhat the caller must handle
Missing required titleThe CREATE request is refused by validation
Unknown record identifierREAD, UPDATE or DELETE can return not found
Insufficient accessDeclaring a standard verb does not bypass its roles or scope
Success responseUse a read-back when verifying persistence; an example request alone does not prove a deployed journey

Choose where the interaction takes place

The frontend’s resourceUIBehavior uses the same resource configuration factory. Its view declarations decide where people interact with those operations. Selected view entries from Wonder Todos’ todos.ui-behavior.tsx, with source comments omitted:

views: [
  [Op.CREATE, { surface: ResourceOperationFrontendSurface.EMBEDDED }],
  [Op.READ, {
    surface: ResourceOperationFrontendSurface.ADDRESSABLE,
    customView: TodoReadCustomView,
  }],
  [Op.LIST, {
    surface: ResourceOperationFrontendSurface.ADDRESSABLE,
    collectionDisplayConfig: {
      selectable: true,
      cardActionPlacement: { vertical: VerticalPlacement.TOP, horizontal: HorizontalPlacement.END },
    },
  }],
  [Op.SEARCH, { surface: ResourceOperationFrontendSurface.EMBEDDED }],
  [Op.UPDATE, { surface: ResourceOperationFrontendSurface.ADDRESSABLE }],
  [Op.DELETE, { surface: ResourceOperationFrontendSurface.EMBEDDED }],
],

Here, creating and deleting happen in an embedded surface. Reading and updating have addressable views. The list enables selection and positions actions on its cards. TodoReadCustomView supplies an application-specific detail view while keeping the operation identity.

You can start with standard interactions and replace a particular view as your product develops. The API contract, permissions and operation identity remain connected to the resource configuration.

For actions such as approval or assignment, use a named business operation. For the collection experience, continue with filtering, search and pagination.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.