Creating Plot Twists
    Preparing search index...

    Class StoreAbstract

    Built-in tool for persistent key-value storage.

    The Store tool provides twists and tools with a simple, persistent storage mechanism that survives worker restarts and invocations. Each twist/tool instance gets its own isolated storage namespace.

    Note: Store methods are also available directly on Twist and Tool classes via this.get(), this.set(), this.clear(), and this.clearAll(). This is the recommended approach for most use cases.

    Storage Characteristics:

    • Persistent across worker restarts
    • Isolated per twist/tool instance
    • Supports SuperJSON-serializable data (see below)
    • Async operations for scalability

    Supported Data Types (via SuperJSON):

    • Primitives: string, number, boolean, null, undefined
    • Complex types: Date, RegExp, Map, Set, Error, URL, BigInt
    • Collections: Arrays and objects (recursively)

    NOT Supported (will throw validation errors):

    • Functions (use callback tokens instead - see Callbacks tool)
    • Symbols
    • Circular references
    • Custom class instances

    Use Cases:

    • Storing authentication tokens
    • Caching configuration data
    • Maintaining sync state
    • Persisting user preferences
    • Tracking processing checkpoints
    class CalendarTool extends Tool {
    async saveAuthToken(provider: string, token: string) {
    // Using built-in set method (recommended)
    await this.set(`auth_token_${provider}`, token);
    }

    async getAuthToken(provider: string): Promise<string | null> {
    // Using built-in get method (recommended)
    return await this.get<string>(`auth_token_${provider}`);
    }

    async clearAllTokens() {
    // Using built-in clearAll method (recommended)
    await this.clearAll();
    }
    }

    Hierarchy (View Summary)

    Index

    Constructors

    Methods

    • Retrieves a value from storage by key.

      Returns the stored value deserialized to the specified type, or null if the key doesn't exist or the value is null.

      Values are automatically deserialized using SuperJSON, which properly restores Date objects, Maps, Sets, and other complex types.

      Type Parameters

      • T extends Serializable

        The expected type of the stored value (must be Serializable)

      Parameters

      • key: string

        The storage key to retrieve

      Returns Promise<T | null>

      Promise resolving to the stored value or null

    • Stores a value in persistent storage.

      The value will be serialized using SuperJSON and stored persistently. Any existing value at the same key will be overwritten.

      SuperJSON automatically handles Date objects, Maps, Sets, undefined values, and other complex types that standard JSON doesn't support.

      Type Parameters

      • T extends Serializable

        The type of value being stored (must be Serializable)

      Parameters

      • key: string

        The storage key to use

      • value: T

        The value to store (must be SuperJSON-serializable)

      Returns Promise<void>

      Promise that resolves when the value is stored

      // Date objects are preserved
      await this.set('sync_state', {
      lastSync: new Date(),
      minDate: new Date(2024, 0, 1)
      });

      // undefined is now supported
      await this.set('data', { name: 'test', optional: undefined }); // ✅ Works

      // Arrays with undefined are supported
      await this.set('items', [1, undefined, 3]); // ✅ Works
      await this.set('items', [1, null, 3]); // ✅ Also works

      // Maps and Sets are supported
      await this.set('mapping', new Map([['key', 'value']])); // ✅ Works
      await this.set('tags', new Set(['tag1', 'tag2'])); // ✅ Works

      // Functions are NOT supported - use callback tokens instead
      const token = await this.callback(this.myFunction);
      await this.set('callback_ref', token); // ✅ Use callback token
    • Stores many key/value pairs in one round-trip.

      Equivalent to calling set for each entry, but issues a single storage operation. Always prefer this over looping set() when writing per-item state in a batch (e.g. an id→channel mapping for every message in a sync pass): each set() is a network round-trip to the storage backend, so a loop of hundreds of them dominates the execution's wall-clock time and request budget, while one setMany() costs a single request.

      All entries are written atomically — either every entry lands or none do.

      Type Parameters

      • T extends Serializable

        The type of values being stored (must be Serializable)

      Parameters

      • entries: [key: string, value: T][]

        Array of [key, value] pairs to store

      Returns Promise<void>

      Promise that resolves when all values are stored

      await this.setMany(
      thread.messages.map((m) => [`msg-channel:${m.id}`, channelId])
      );
    • Lists all storage keys matching a prefix.

      Returns an array of key strings that start with the given prefix. Useful for finding all keys in a namespace (e.g., all sync locks).

      Prefer listEntries when you are going to read every value anyway — list() followed by a get() per key costs one round-trip per key.

      Parameters

      • prefix: string

        The prefix to match keys against

      Returns Promise<string[]>

      Promise resolving to an array of matching key strings

    • Lists matching keys with their values in one round-trip.

      This is the read counterpart of setMany. The common list(prefix)get(key) per key shape costs 1 + N round-trips; this costs one, because the storage backend already reads the values during the prefix scan and list() simply discards them.

      Reach for this whenever you buffer per-item state under a prefix and later drain it (occurrence buffers, pending write-backs, id→id caches). A drain loop of a few hundred keys is enough to dominate an execution's wall-clock time and exhaust its request budget.

      Type Parameters

      Parameters

      • prefix: string

        The prefix to match keys against

      Returns Promise<[key: string, value: T][]>

      Promise resolving to [key, value] pairs, key-ascending

      const buffered = await this.tools.store.listEntries<Occurrence>(
      `pending:${id}:`
      );
      merge(buffered.map(([, value]) => value));
      await this.tools.store.clearMany(buffered.map(([key]) => key));
    • Reads many keys in one round-trip.

      Equivalent to calling get for each key, but issues a single storage operation. Always prefer this over looping get() — the same round-trip arithmetic as setMany. Use listEntries instead when the keys share a prefix and you don't already know them.

      Missing keys come back as null, and the result is positionally aligned with keys, so a keys[i]values[i] zip is always safe.

      Type Parameters

      Parameters

      • keys: string[]

        The storage keys to read

      Returns Promise<(T | null)[]>

      Promise resolving to one value (or null) per requested key

      const sent = await this.tools.store.getMany<boolean>(
      notes.map((n) => `sent:${n.key}`)
      );
      const unsent = notes.filter((_, i) => !sent[i]);
    • Removes a specific key from storage.

      After this operation, get() calls for this key will return null. No error is thrown if the key doesn't exist.

      Parameters

      • key: string

        The storage key to remove

      Returns Promise<void>

      Promise that resolves when the key is removed

    • Removes many keys in one round-trip.

      Equivalent to calling clear for each key, but issues a single storage operation — the delete counterpart of setMany. Draining a buffered prefix is the usual case: pair it with listEntries so the whole drain costs two round-trips regardless of how many keys there are, instead of one per key.

      Keys that don't exist are ignored. All deletes are applied atomically.

      Parameters

      • keys: string[]

        The storage keys to remove

      Returns Promise<void>

      Promise that resolves when all keys are removed

    • Removes all keys from this storage instance.

      This operation clears all data stored by this twist/tool instance but does not affect storage for other twists or tools.

      Returns Promise<void>

      Promise that resolves when all keys are removed

    • Acquire a self-expiring lock. Returns true if the caller now holds the lock, false if another holder has a non-expired lease.

      Use this for any operation where you previously hand-rolled a boolean "in progress" flag with manual cleanup on every error path. The lock auto-releases after ttlMs, so a crashed/timed-out holder cannot wedge the system permanently — pick a ttlMs comfortably longer than the worst-case duration of the protected work.

      Acquisition is atomic across concurrent callers (the underlying Durable Object serializes operations). Lock keys live in a reserved namespace and never appear in get / list results.

      Parameters

      • key: string

        Lock identifier (any string).

      • ttlMs: number

        Lease duration in milliseconds. After this time the lock is considered expired and a new caller can acquire it even if releaseLock was never called.

      Returns Promise<boolean>

      Promise resolving to true if acquired, false if held.

      if (!(await this.tools.store.acquireLock(`sync_${id}`, 30 * 60_000))) {
      return; // another sync is already running
      }
      try {
      await this.runSync(id);
      } finally {
      await this.tools.store.releaseLock(`sync_${id}`);
      }
    • Release a lock acquired via acquireLock. Safe to call even if the caller never acquired the lock or the lease has already expired.

      Parameters

      • key: string

        The same key that was passed to acquireLock.

      Returns Promise<void>