Skip to content

Commit 41f2aee

Browse files
Add documentation for Data Store TTL functionality (#15825)
* Add documentation for Data Store TTL functionality * Update pnpm-lock.yaml * update python data stores * change data to dataStore * remove duplicate limitation --------- Co-authored-by: Andrew Chuang <[email protected]>
1 parent 9e744c7 commit 41f2aee

File tree

3 files changed

+201
-34
lines changed

3 files changed

+201
-34
lines changed

docs-v2/pages/workflows/building-workflows/code/nodejs/using-data-stores.mdx

Lines changed: 110 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ In Node.js (Javascript) code steps, you can also store and retrieve data within
66

77
Add data stores to steps as props. By adding the store as a prop, it's available under `this`.
88

9-
For example, you can define a data store as a data prop, and reference it at `this.data`:
9+
For example, you can define a data store as a dataStore prop, and reference it at `this.dataStore`:
1010

1111
```javascript
1212
export default defineComponent({
1313
props: {
14-
// Define that the "data" variable in our component is a data store
15-
data: { type: "data_store" },
14+
// Define that the "dataStore" variable in our component is a data store
15+
dataStore: { type: "data_store" },
1616
},
1717
async run({ steps, $ }) {
18-
// Now we can access the data store at "this.data"
19-
await this.data.get("email");
18+
// Now we can access the data store at "this.dataStore"
19+
await this.dataStore.get("email");
2020
},
2121
});
2222
```
2323

2424
<Callout type="info">
25-
**`props` injects variables into `this`**. See how we declared the `data` prop in the `props` object, and then accessed it at `this.data` in the `run` method.
25+
**`props` injects variables into `this`**. See how we declared the `dataStore` prop in the `props` object, and then accessed it at `this.dataStore` in the `run` method.
2626
</Callout>
2727

2828
<Callout type="warning">
@@ -37,32 +37,74 @@ Once you've defined a data store prop for your component, then you'll be able to
3737

3838
## Saving data
3939

40-
Data Stores are key-value stores. Save data within a Data Store using the `this.data.set` method. The first argument is the _key_ where the data should be held, and the second argument is the _value_ assigned to that key.
40+
Data Stores are key-value stores. Save data within a Data Store using the `this.dataStore.set` method. The first argument is the _key_ where the data should be held, and the second argument is the _value_ assigned to that key.
4141

4242
```javascript
4343
export default defineComponent({
4444
props: {
45-
data: { type: "data_store" },
45+
dataStore: { type: "data_store" },
4646
},
4747
async run({ steps, $ }) {
4848
// Store a timestamp each time this step is executed in the workflow
49-
await this.data.set("lastRanAt", new Date());
49+
await this.dataStore.set("lastRanAt", new Date());
5050
},
5151
});
5252
```
5353

54+
### Setting expiration (TTL) for records
55+
56+
You can set an expiration time for a record by passing a TTL (Time-To-Live) option as the third argument to the `set` method. The TTL value is specified in seconds:
57+
58+
```javascript
59+
export default defineComponent({
60+
props: {
61+
dataStore: { type: "data_store" },
62+
},
63+
async run({ steps, $ }) {
64+
// Store a temporary value that will expire after 1 hour (3600 seconds)
65+
await this.dataStore.set("temporaryToken", "abc123", { ttl: 3600 });
66+
67+
// Store a value that will expire after 1 day
68+
await this.dataStore.set("dailyMetric", 42, { ttl: 86400 });
69+
},
70+
});
71+
```
72+
73+
When the TTL period elapses, the record will be automatically deleted from the data store.
74+
75+
### Updating TTL for existing records
76+
77+
You can update the TTL for an existing record using the `setTtl` method:
78+
79+
```javascript
80+
export default defineComponent({
81+
props: {
82+
dataStore: { type: "data_store" },
83+
},
84+
async run({ steps, $ }) {
85+
// Update an existing record to expire after 30 minutes
86+
await this.dataStore.setTtl("temporaryToken", 1800);
87+
88+
// Remove expiration from a record
89+
await this.dataStore.setTtl("temporaryToken", null);
90+
},
91+
});
92+
```
93+
94+
This is useful for extending the lifetime of temporary data or removing expiration from records that should now be permanent.
95+
5496
## Retrieving keys
5597

5698
Fetch all the keys in a given Data Store using the `keys` method:
5799

58100
```javascript
59101
export default defineComponent({
60102
props: {
61-
data: { type: "data_store" },
103+
dataStore: { type: "data_store" },
62104
},
63105
async run({ steps, $ }) {
64106
// Return a list of all the keys in a given Data Store
65-
return await this.data.keys();
107+
return await this.dataStore.keys();
66108
},
67109
});
68110
```
@@ -74,11 +116,11 @@ If you need to check whether a specific `key` exists in a Data Store, you can pa
74116
```javascript
75117
export default defineComponent({
76118
props: {
77-
data: { type: "data_store" },
119+
dataStore: { type: "data_store" },
78120
},
79121
async run({ steps, $ }) {
80122
// Check if a specific key exists in your Data Store
81-
return await this.data.has("lastRanAt");
123+
return await this.dataStore.has("lastRanAt");
82124
},
83125
});
84126
```
@@ -90,11 +132,11 @@ You can retrieve data with the Data Store using the `get` method. Pass the _key_
90132
```javascript
91133
export default defineComponent({
92134
props: {
93-
data: { type: "data_store" },
135+
dataStore: { type: "data_store" },
94136
},
95137
async run({ steps, $ }) {
96138
// Check if the lastRanAt key exists
97-
const lastRanAt = await this.data.get("lastRanAt");
139+
const lastRanAt = await this.dataStore.get("lastRanAt");
98140
},
99141
});
100142
```
@@ -106,11 +148,11 @@ Use an async iterator to efficiently retrieve all records or keys in your data s
106148
```javascript
107149
export default defineComponent({
108150
props: {
109-
data: { type: "data_store" },
151+
dataStore: { type: "data_store" },
110152
},
111153
async run({ steps, $ }) {
112154
const records = {};
113-
for await (const [k,v] of this.data) {
155+
for await (const [k,v] of this.dataStore) {
114156
records[k] = v;
115157
}
116158
return records;
@@ -125,14 +167,14 @@ To delete or update the _value_ of an individual record, use the `set` method fo
125167
```javascript
126168
export default defineComponent({
127169
props: {
128-
data: { type: "data_store" },
170+
dataStore: { type: "data_store" },
129171
},
130172
async run({ steps, $ }) {
131173
// Update the value associated with the key, myKey
132-
await this.data.set("myKey", "newValue");
174+
await this.dataStore.set("myKey", "newValue");
133175

134176
// Remove the value but retain the key
135-
await this.data.set("myKey", "");
177+
await this.dataStore.set("myKey", "");
136178
},
137179
});
138180
```
@@ -144,11 +186,11 @@ To delete individual records in a Data Store, use the `delete` method for a spec
144186
```javascript
145187
export default defineComponent({
146188
props: {
147-
data: { type: "data_store" },
189+
dataStore: { type: "data_store" },
148190
},
149191
async run({ steps, $ }) {
150192
// Delete the lastRanAt record
151-
const lastRanAt = await this.data.delete("lastRanAt");
193+
const lastRanAt = await this.dataStore.delete("lastRanAt");
152194
},
153195
});
154196
```
@@ -160,11 +202,11 @@ If you need to delete all records in a given Data Store, you can use the `clear`
160202
```javascript
161203
export default defineComponent({
162204
props: {
163-
data: { type: "data_store" },
205+
dataStore: { type: "data_store" },
164206
},
165207
async run({ steps, $ }) {
166208
// Delete all records from a specific Data Store
167-
return await this.data.clear();
209+
return await this.dataStore.clear();
168210
},
169211
});
170212
```
@@ -201,16 +243,16 @@ You can use a data store as a counter. For example, this code counts the number
201243
```javascript
202244
export default defineComponent({
203245
props: {
204-
data: { type: "data_store" },
246+
dataStore: { type: "data_store" },
205247
},
206248
async run({ steps, $ }) {
207249
// By default, all database entries are undefined.
208250
// It's wise to set a default value so our code as an initial value to work with
209-
const counter = (await this.data.get("counter")) ?? 0;
251+
const counter = (await this.dataStore.get("counter")) ?? 0;
210252

211253
// On the first run "counter" will be 0 and we'll increment it to 1
212254
// The next run will increment the counter to 2, and so forth
213-
await this.data.set("counter", counter + 1);
255+
await this.dataStore.set("counter", counter + 1);
214256
},
215257
});
216258
```
@@ -224,27 +266,63 @@ For example, this workflow's trigger contains an email address from a potential
224266
```javascript
225267
export default defineComponent({
226268
props: {
227-
data: { type: "data_store" },
269+
dataStore: { type: "data_store" },
228270
},
229271
async run({ steps, $ }) {
230272
const email = steps.trigger.event.body.new_customer_email;
231273
// Retrieve the past recorded emails from other runs
232-
const emails = (await this.data.get("emails")) ?? [];
274+
const emails = (await this.dataStore.get("emails")) ?? [];
233275

234276
// If the current email being passed from our webhook is already in our list, exit early
235277
if (emails.includes(email)) {
236278
return $.flow.exit("Already welcomed this user");
237279
}
238280

239281
// Add the current email to the list of past emails so we can detect it in the future runs
240-
await this.data.set("emails", [...emails, email]);
282+
await this.dataStore.set("emails", [...emails, email]);
241283
},
242284
});
243285
```
244286
245-
## Data store limitations
287+
## TTL use case: temporary caching and rate limiting
288+
289+
TTL functionality is particularly useful for implementing temporary caching and rate limiting. Here's an example of a simple rate limiter that prevents a user from making more than 5 requests per hour:
290+
291+
```javascript
292+
export default defineComponent({
293+
props: {
294+
dataStore: { type: "data_store" },
295+
},
296+
async run({ steps, $ }) {
297+
const userId = steps.trigger.event.userId;
298+
const rateKey = `ratelimit:${userId}`;
299+
300+
// Try to get current rate limit counter
301+
let requests = await this.dataStore.get(rateKey);
302+
303+
if (requests === undefined) {
304+
// First request from this user in the time window
305+
await this.dataStore.set(rateKey, 1, { ttl: 3600 }); // Expire after 1 hour
306+
return { allowed: true, remaining: 4 };
307+
}
308+
309+
if (requests >= 5) {
310+
// Rate limit exceeded
311+
return { allowed: false, error: "Rate limit exceeded", retryAfter: "1 hour" };
312+
}
313+
314+
// Increment the counter
315+
await this.dataStore.set(rateKey, requests + 1);
316+
return { allowed: true, remaining: 4 - requests };
317+
},
318+
});
319+
```
246320
247-
Data Stores are only currently available in Node.js and Python steps. They are not yet available [Bash](/workflows/building-workflows/code/bash/) or [Go](/workflows/building-workflows/code/go/).
321+
This pattern can be extended for various temporary caching scenarios like:
322+
- Session tokens with automatic expiration
323+
- Short-lived feature flags
324+
- Temporary access grants
325+
- Time-based promotional codes
248326
249327
### Supported data types
250328

docs-v2/pages/workflows/building-workflows/code/python/using-data-stores.mdx

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Callout from '@/components/Callout'
22

33
# Using Data Stores
44

5-
You can store and retrieve data from [Data stores](/workflows/data-management/data-stores/) in Python without connecting to a 3rd party database.
5+
You can store and retrieve data from [Data Stores](/workflows/data-management/data-stores/) in Python without connecting to a 3rd party database.
66

77
Add a data store as a input to a Python step, then access it in your Python `handler` with `pd.inputs["data_store"]`.
88

@@ -42,6 +42,42 @@ def handler(pd: "pipedream"):
4242
data_store["last_ran_at"] = datetime.now().isoformat()
4343
```
4444

45+
### Setting expiration (TTL) for records
46+
47+
You can set an expiration time for a record by passing a TTL (Time-To-Live) option as the third argument to the `set` method. The TTL value is specified in seconds:
48+
49+
```python
50+
def handler(pd: "pipedream"):
51+
# Access the data store under the pd.inputs
52+
data_store = pd.inputs["data_store"]
53+
54+
# Store a temporary value that will expire after 1 hour (3600 seconds)
55+
data_store.set("temporaryToken", "abc123", ttl=3600)
56+
57+
# Store a value that will expire after 1 day
58+
data_store.set("dailyMetric", 42, ttl=86400)
59+
```
60+
61+
When the TTL period elapses, the record will be automatically deleted from the data store.
62+
63+
### Updating TTL for existing records
64+
65+
You can update the TTL for an existing record using the `set_ttl` method:
66+
67+
```python
68+
def handler(pd: "pipedream"):
69+
# Access the data store under the pd.inputs
70+
data_store = pd.inputs["data_store"]
71+
72+
# Update an existing record to expire after 30 minutes
73+
data_store.set_ttl("temporaryToken", ttl=1800)
74+
75+
# Remove expiration from a record
76+
data_store.set_ttl("temporaryToken", ttl=None)
77+
```
78+
79+
This is useful for extending the lifetime of temporary data or removing expiration from records that should now be permanent.
80+
4581
## Retrieving keys
4682

4783
Fetch all the keys in a given data store using the `keys` method:
@@ -285,6 +321,40 @@ def handler(pd: "pipedream"):
285321
return new_email
286322
```
287323

324+
## TTL use case: temporary caching and rate limiting
325+
326+
TTL functionality is particularly useful for implementing temporary caching and rate limiting. Here's an example of a simple rate limiter that prevents a user from making more than 5 requests per hour:
327+
328+
```python
329+
def handler(pd: "pipedream"):
330+
# Access the data store
331+
data_store = pd.inputs["data_store"]
332+
user_id = pd.steps["trigger"]["event"]["user_id"]
333+
rate_key = f"ratelimit:{user_id}"
334+
335+
# Try to get current rate limit counter
336+
requests_num = data_store.get("rate_key")
337+
338+
if not requests_num:
339+
# First request from this user in the time window
340+
data_store.set(rate_key, 1, ttl=3600) # Expire after 1 hour
341+
return { "allowed": True, "remaining": 4 }
342+
343+
if requests_num >= 5:
344+
# Rate limit exceeded
345+
return { "allowed": False, "error": "Rate limit exceeded", "retry_after": "1 hour" }
346+
347+
# Increment the counter
348+
data_store["rate_key"] = requests_num + 1
349+
return { "allowed": True, "remaining": 4 - requests_num }
350+
```
351+
352+
This pattern can be extended for various temporary caching scenarios like:
353+
- Session tokens with automatic expiration
354+
- Short-lived feature flags
355+
- Temporary access grants
356+
- Time-based promotional codes
357+
288358
### Supported data types
289359

290360
Data stores can hold any JSON-serializable data within the storage limits. This includes data types including:

0 commit comments

Comments
 (0)