Lightweight Tags
Not every component needs fields. In Nebula, an empty component — one with no properties at all — is a perfectly valid and powerful tool. Its presence on an entity is a boolean signal: has('NeedsReview') is a tag check, and removing the component clears the tag. This pattern covers how to use zero-field and near-zero-field components for categorization, filtering, and workflow gating.
The Scenario
Your application needs to mark entities for various purposes: flagging accounts for manual review, soft-deleting records without destroying data, gating which systems are active for a given customer, and detecting "gaps" where expected processing hasn't happened yet. None of these require storing meaningful data — they just need presence or absence.
The Pattern
Define components with minimal or no fields. Use has() and not(has()) in queries to route entities based on tag presence. Add and remove tags to move entities between queues.
// Tag an entity for review — no data needed, just presence
ctx.upsert('NeedsReview', {});
// Later, clear the tag
ctx.remove('NeedsReview');
// Soft-delete: add Archived tag instead of deleting
ctx.upsert('Archived', {});
These tags immediately change which queries match. Here's a review processor and a gap-detection system:
Tags can also carry a single field for lightweight categorization:
// Near-zero-field tag: a single enum for priority level
ctx.upsert('PriorityTag', { level: 'high' });
// Query for high-priority items
const query = and(has('Account'), has('PriorityTag'));
In Practice
In a production Nebula application, Signal entities use component presence to gate which systems are active. A Signal entity has an AssociatedSystems list and a DenialCodes component — systems check for the Signal's presence and status before processing. The component isn't just data; it's a system-level feature flag.
On the entity processing side, the not(has(...)) pattern is the most common form of gap detection:
not(has('DenialOpportunity'))— "this account hasn't been analyzed for denials yet"not(has('LocationMatch'))— "this account hasn't been matched to a facility"not(parent(has('Team')))— "this account hasn't been assigned to a team"
When a system adds the missing component or link, the entity stops matching the gap-detection query. The system never processes it again. This is a natural idempotency guard — the output of processing removes the entity from the input query.
Why This Works
In traditional systems, you'd use boolean database columns, status enums, or separate "processed" tables to track which entities need attention. Each new tag means a schema migration. Each new filter means a new index.
In Nebula, adding a tag is adding a component — no schema changes, no migrations. The query engine handles the filtering, and has() / not(has()) are as fast as any other query operator. You get boolean flags, soft-delete, feature gating, and gap detection all from the same primitive: component presence.
Component Design covers how to structure component schemas. Component Query Refiners shows how component accumulation creates progressive system routing. Async Workflows demonstrates gap detection as a workflow convergence mechanism.