Class AttributeMap<T>

java.lang.Object
io.superdurable.dex.PersistenceDefinition
io.superdurable.dex.AttributeMap<T>
Type Parameters:
T - the Java value type stored in every map instance

public final class AttributeMap<T> extends PersistenceDefinition
Defines a durable map of named, typed Attribute instances.

An AttributeMap is useful when keys are dynamically determined at runtime. Register the definition once in the Flow's PersistenceSchema, then supply an instance name for each read, write, delete, or lock. Each key/value pair is stored as a separate blob, so updating one entry does not rewrite a large Attribute containing the entire logical map. Each value is serialized with the definition's concrete Class. Instance names must be nonblank and must not contain /.

private final AttributeMap<String> orderStatus =
        AttributeMap.define("order-status", String.class);

orderStatus.set(context, orderId, "PAID");
String status = orderStatus.get(context, orderId);
  • Method Details

    • define

      public static <T> AttributeMap<T> define(String name, Class<T> valueType)
      Defines an Attribute map without a search index.
      Type Parameters:
      T - the Java value type
      Parameters:
      name - the stable Attribute-map name; must not be blank or contain /
      valueType - the concrete Java class used for every instance value
      Returns:
      the typed Attribute-map definition
      Throws:
      IllegalArgumentException - if name is null, blank, or contains /
      NullPointerException - if valueType is null
    • define

      public static <T> AttributeMap<T> define(String name, Class<T> valueType, AttributeIndex index)
      Defines an Attribute map with a search index.
      Type Parameters:
      T - the Java value type
      Parameters:
      name - the stable Attribute-map name; must not be blank or contain /
      valueType - the concrete Java class used for every instance value
      index - the search-index definition, or null for no index
      Returns:
      the typed Attribute-map definition
      Throws:
      IllegalArgumentException - if name is null, blank, or contains /
      NullPointerException - if valueType is null
    • syncToAttributeStore

      public AttributeMap<T> syncToAttributeStore()
      Returns a definition whose instance writes are projected through the Flow's Attribute Store.

      The physical Attribute-map key must match a target column. Projection is asynchronous and does not roll back the Flow Attribute when external storage fails. Deletion projects SQL NULL when the target column permits it.

      Returns:
      a new immutable Attribute-map definition with Attribute Store synchronization enabled
      Throws:
      IllegalStateException - if this definition uses hardDeleteOnNull()
    • hardDeleteOnNull

      public AttributeMap<T> hardDeleteOnNull()
      Returns a definition whose instance values are stored only in the Dex blob store and are permanently deleted when an instance is deleted or set to null.

      Use this option for short-lived secrets that are keyed at runtime, such as one API key per tenant or per connected account. Each instance value is never recorded in Flow history. Dex stores it only in the blob store and records only a reference in Flow history. Calling delete(Context, String) or set(Context, String, Object) with null permanently deletes that instance's stored value. After the deletion, the instance reads as absent. Other instances are unaffected.

      Only string and object values are supported. Object values include JSON-mapped classes and byte[]. Numeric and Boolean value types, such as Integer, Long, Double, and Boolean, are rejected when this option is applied. When the declared value type is broader, such as Object or Number, a write whose value maps to a number or a Boolean is rejected when it is written. Writing null is always allowed.

      This option cannot be combined with an AttributeIndex or with syncToAttributeStore(). Client.waitForAttributeMatch(String, Attribute, AttributeMatch, WaitForAttributeOptions) rejects a hard-delete Attribute map before it contacts the Server. Use an RPC that reads the instance when a caller needs the value.

      The Dex Server must have an enabled blob store and must support Server protocol version 3 or later. A Step, RPC, StartFlow, or SubFlow start that writes this Attribute map fails with IllegalStateException when the negotiated Server protocol is older. The Server rejects the write when its blob store is disabled. Steps and RPC handlers receive instance values inline. The SDK never stores a hard-delete value in the local BlobCache.

      Know these limitations before you store a secret:

      • Each Flow keeps one stored copy per instance key. Every write overwrites that copy in place.
      • A Step or RPC attempt writes or deletes the stored value before Dex commits the attempt's result. A retried attempt, other Steps, and other readers can therefore observe a write or a deletion from an attempt that later failed.
      • When concurrent Steps or RPCs write the same instance, the final stored value may not be the value from the last committed write. When the order matters, use a single writer, or make every Step and RPC that writes the instance hold an AttributeLock on it. A failed locked attempt can still overwrite the value.
      • A Client.startFlow(Flow, String, I) call or SubFlow.run(Class, I) start that fails because the Flow ID is already running still overwrites the running Flow's instance value. With a null initial value, it deletes the running Flow's instance value.
      • Executions that reuse a Flow ID share the stored values.
      • Client.timeTravel(String, TimeTravelOptions) does not restore a deleted value. A deleted value reads as absent.
      • A Flow retry or a restarted SubFlow starts from the original initial value. If an earlier run deleted that value, the new run reads it as absent.
      • Removing hardDeleteOnNull() from a definition leaves the stored value in place until Dex removes the Flow. Delete the value with the hard-delete definition first.
      • A value remains in the blob store until it is deleted, or until Flow retention removes the Flow and blob cleanup runs.
      • Blob store backups and bucket versioning can retain copies after deletion. Configure them separately when deleted secrets must not survive.
      final class TenantSyncFlow implements Flow<String> {
          final AttributeMap<String> tenantApiKeys =
                  AttributeMap.define("tenant-api-keys", String.class).hardDeleteOnNull();
          private final SyncTenant sync = new SyncTenant(tenantApiKeys);
      
          @Override
          public StepList<String> getSteps() {
              return StepList.startStep(sync);
          }
      
          @Override
          public PersistenceSchema getPersistenceSchema() {
              return PersistenceSchema.of(tenantApiKeys);
          }
      }
      
      final class SyncTenant implements Step<String> {
          private final AttributeMap<String> tenantApiKeys;
      
          SyncTenant(final AttributeMap<String> tenantApiKeys) {
              this.tenantApiKeys = tenantApiKeys;
          }
      
          @Override
          public StepDecision execute(final Context context, final String tenantId) {
              final String key = tenantApiKeys.get(context, tenantId);
              ProviderApi.sync(tenantId, key);
              // Permanently delete this tenant's secret once it is no longer needed.
              tenantApiKeys.delete(context, tenantId);
              return StepDecision.gracefulComplete();
          }
      }
      
      // Start the Flow with one tenant's secret as an initial instance value.
      client.startFlow(
              flow,
              "tenant-sync-7",
              "tenant-7",
              StartFlowOptions.newBuilder()
                      .addAttribute(flow.tenantApiKeys, "tenant-7", System.getenv("TENANT_7_KEY"))
                      .build());
      
      Returns:
      a new immutable Attribute-map definition with hard deletion on null enabled
      Throws:
      IllegalStateException - if this definition has an index, uses syncToAttributeStore(), or declares a numeric or Boolean value type
    • get

      public T get(Context context, String instance)
      Reads one map instance from the current invocation.
      Parameters:
      context - the Step or RPC invocation context
      instance - the instance key within this map
      Returns:
      the current typed value, or null when the instance is absent
    • set

      public void set(Context context, String instance, T value)
      Writes one map instance in the current invocation.
      Parameters:
      context - the Step or RPC invocation context
      instance - the instance key within this map
      value - the typed value to persist; null deletes the instance
      Throws:
      IllegalArgumentException - if this is a hardDeleteOnNull() Attribute map and the value maps to a number or a Boolean
    • delete

      public void delete(Context context, String instance)
      Deletes one map instance in the current invocation.
      Parameters:
      context - the Step or RPC invocation context
      instance - the instance key within this map
    • getMapSize

      public int getMapSize(Context context)
      Returns the number of existing instances, including writes buffered by this invocation.
      Parameters:
      context - the Step or RPC invocation context
      Returns:
      the current number of instances
    • getAllInstanceKeys

      public List<String> getAllInstanceKeys(Context context)
      Returns existing instance keys in ascending order, including buffered writes.
      Parameters:
      context - the Step or RPC invocation context
      Returns:
      an immutable sorted list of decoded instance keys