This topic was automatically generated from Slack. You can find the original thread here.
Hello! I am migrating the SDK to the new version, and I am finding a few issues along the way which I’ll explain in this thread. Could I get some help?
This topic was automatically generated from Slack. You can find the original thread here.
Hello! I am migrating the SDK to the new version, and I am finding a few issues along the way which I’ll explain in this thread. Could I get some help?
Create Frontend Client
First, for instantiating the frontend client, as instructed here
It provides with 2 options:
• Option 1: Using PipedreamClient
with token callback (for dynamic token management)
• Option 2: Using createFrontendClient
with connect token (for simple token-based auth)
I’m trying to use option 2, as it’s the one that fits my current structure the best.
In this option, there is the following example:
// `tokenCallback` is also supported here
const client: PipedreamClient = createFrontendClient({
token: 'connect-token',
externalUserId: 'user-123'
});
Here, only token
and externalUserId
are provided, although it is stated that tokenCallback
is also supported.
However, in my case, tokenCallback
is REQUIRED, at least from the createFrontendClient
args type in the SDK’s version 2.0.5
Listing Apps
Before the migration, I obtained a list of apps using this method:
const page = await pipedream.getApps(searchProps)
In this “page” result object, I could obtain not only the list of apps in the current page, using page.data
, but also page.page_info.total_count
and page.page_info.end_cursor
for paginated queries.
Now, it has changed to this:
const page = await pipedream.apps.list(searchProps)
However, the result object now only has data
, getNextPage
and hasNextPage
, which I cannot use because I stored the end_cursor
value to perform paginated queries asyncronously
Run Actions
Now, pipedream.actions.run(...)
will return this object, which is kind of useless:
export interface RunActionResponse {
exports?: unknown;
os?: unknown;
ret?: unknown;
stashId?: Pipedream.StashId | undefined;
}
How do I know how to obtain the result from a RunActionResponse?
pls help
I’m sorry you’re running into issues with the SDK migration. I’ll address each of your questions step by step: — ### 1. Frontend Client Instantiation (createFrontendClient
and tokenCallback
required) You noticed that tokenCallback
is required in createFrontendClient
in SDK v2.0.5, even though the docs suggest you can just pass a static token
. What’s happening: This is likely a breaking change or a documentation mismatch in the SDK. In v2.0.5, the type definition for createFrontendClient
requires tokenCallback
, even if you want to use a static token. What to do: - If you only have a static token, you can wrap it in a callback: const client = createFrontendClient({ tokenCallback: async () => "your-static-connect-token", externalUserId: "user-123" });
- This satisfies the type requirement and works the same as passing a static token. — ### 2. Listing Apps: Pagination Info (end_cursor
, total_count
) Previously, you accessed page.page_info.total_count
and page.page_info.end_cursor
. Now, the SDK only exposes data
, getNextPage
, and hasNextPage
. What’s happening: The SDK now abstracts pagination, encouraging you to use getNextPage
/hasNextPage
for sequential fetching. Direct access to cursors and counts may not be exposed. Workarounds: - If you need to store cursors for async pagination, you may need to: - Check if the SDK exposes the raw response (sometimes as raw
or similar). - If not, you may need to request this feature or use the REST API directly for advanced pagination needs. - For now, you can chain getNextPage()
calls, but you can’t manually jump to a cursor. — ### 3. Run Actions: Getting Results from RunActionResponse
You get a response like: ts export interface RunActionResponse { exports?: unknown; os?: unknown; ret?: unknown; stashId?: Pipedream.StashId | undefined; }
How to get the result: - The main result of the action is usually in the ret
property. - If the action step in Pipedream uses return
or $.export("result", value)
, check both ret
and exports
. Example: ```
const response = await pipedream.actions.run(…); const result = response.ret ?? response.exports;
For the RunActionResponse
I have done this:
function getResultFromActionResponse(
response: RunActionResponse,
): TypedResult<unknown, Error> {
// Expected return statement when no error occurs
if (response.ret) return Result.ok(response.ret)
// When an error occurs, the error is usually logged
if (Array.isArray(response.os)) {
// The error is not always in the first log
for (const log of response.os) {
const output = log as {
ts?: number
k?: 'error'
err?: { name: string; message: string; stack: string }
}
if (output.k === 'error' && output.err) {
return Result.error(new Error(output.err.message))
}
}
}
// Some additional data is returned as"exports"
if (typeof response.exports === 'object' && response.exports !== null) {
// Returned value is automatically exported here
if ('$return_value' in response.exports) {
return Result.ok(response.exports['$return_value'])
}
// Some tools may return a summary of the result
if ('$summary' in response.exports) {
return Result.ok(response.exports['$summary'])
}
}
return Result.error(new Error('Tool did not return a value'))
}
What do you think? Would you change this in any way to improve it? I want to know when the tool worked, what did it return, or what error it threw if any
Your approach to extracting the result from a RunActionResponse
is solid and aligns well with Pipedream’s documented response structure: - ret
: the main return value (if any) - exports
: named exports, including $return_value
and $summary
- os
: logs and errors (with errors as { k: 'error', err: { ... } }
) What you’re doing well: - You check ret
first, which is the primary return value. - You scan os
for errors, which is important since errors may be logged there. - You check exports
for $return_value
and $summary
, which covers most cases for exported data. - You provide a fallback error if nothing is found. Possible improvements: 1. Check for explicit errors first. If the action failed, it’s likely that os
will contain an error, and ret
/exports
may be empty or misleading. Consider checking os
for errors before returning ret
or exports
. 2. Return all error details. If you find an error in os
, you might want to include the error name
and stack
in your returned error for better debugging. 3. Handle empty ret
values. If ret
is null
or undefined
, you might want to treat that as “no value returned” unless you expect null
to be a valid result. 4. Consider returning more context. Sometimes, it’s helpful to return the full response (or at least the os
logs) alongside the result or error for debugging. Suggested revision: ```
typescript function getResultFromActionResponse( response: RunActionResponse, ): TypedResult<unknown, Error> { // 1. Check for errors in logs first if (Array.isArray(response.os)) { for (const log of response.os) { const output = log as { ts?: number k?: ‘error’ err?: { name: string; message: string; stack: string } } if (output.k === ‘error’ && output.err) { const err = output.err; const error = new Error(err.message); error.name = err.name; // Optionally attach stack if needed // (error as any).stack = err.stack; return Result.error(error); } } } // 2. Check for a return value if (typeof response.ret !== “undefined” && response.ret !== null) { return Result.ok(response.ret); } // 3. Check for exports if (typeof response.exports === ‘object’ && response.exports !== null) { if (‘$return_value’ in response.exports) { return Result.ok(response.exports[‘$return_value’]); } if (‘$summary’ in response.exports) { return Result.ok(response.exports[‘$summary’]); } } // 4. Fallback error return Result.error(new Error(‘Tool did not return a value’)); }
ConfigurableProp Types
I had the following method that would translate a ConfigurableProp
object to a JSONSchema
:
function propToJSONSchema(prop: ConfigurableProp): JSONSchema7 | undefined {
const base: Partial<JSONSchema7> = {
title: prop.name,
description: prop.description,
...('default' in prop ? { default: prop.default } : {}),
}
switch (prop.type) {
case 'boolean':
return { ...base, type: 'boolean' }
case 'integer':
return {
...base,
type: 'integer',
minimum: prop.min,
maximum: prop.max,
}
case 'integer[]':
return {
...base,
type: 'array',
items: {
type: 'integer',
minimum: prop.min,
maximum: prop.max,
},
}
case 'string':
case 'sql': // treat as string
case '$.airtable.baseId':
case '$.airtable.tableId':
case '$.airtable.viewId':
case '$.airtable.fieldId':
case '$.discord.channel':
return {
...base,
type: 'string',
enum: getOptions<string>(prop),
}
case 'string[]':
case '$.discord.channel[]':
return {
...base,
type: 'array',
items: {
type: 'string',
enum: getOptions<string>(prop),
},
}
case 'object':
case '$.service.db':
case '$.interface.http':
case '$.interface.apphook':
// No structural definition is provided; expose generic object
return {
...base,
type: 'object',
additionalProperties: true,
}
case '$.interface.timer': {
// Two allowed shapes: { intervalSeconds: number } or { cron: string }
const intervalSchema: JSONSchema7 = {
type: 'object',
properties: {
intervalSeconds: { type: 'integer', minimum: 1 },
},
required: ['intervalSeconds'],
additionalProperties: false,
}
const cronSchema: JSONSchema7 = {
type: 'object',
properties: {
cron: { type: 'string' },
},
required: ['cron'],
additionalProperties: false,
}
if (prop.static) {
// Fixed value
return { ...base, const: prop.static }
}
return {
...base,
oneOf: [intervalSchema, cronSchema],
}
}
default:
// Fallback: return nothing
return undefined
}
}
However, since the new SDK version, the ConfigurableProp
type doesn’t seem to have many of the specific attributes, like min
and max
for type “integer”, or options
attribute for type “string”. So everything (or at least typescript’s types) fails.
How do I do it now ? Is there a better way to approach this?
You’re correct: the new Pipedream SDK’s ConfigurableProp
type is more generic and does not guarantee the presence of fields like min
, max
, or options
on all prop types. These fields are now optional and only present when relevant for a given prop. This means your old code, which assumed their presence, will break or cause TypeScript errors. ## Why did this change? Pipedream props are now more flexible and their shape is determined by the type
field and the optional presence of other fields. For example, min
and max
only make sense for integer
props, and options
is only present if the prop is a dropdown. ## How should you handle this now? You need to check for the presence of these fields before using them. This means updating your code to use type guards or optional chaining. ### Example: Handling min
and max
for integer props typescript case 'integer': return { ...base, type: 'integer', ...(typeof prop.min === "number" ? { minimum: prop.min } : {}), ...(typeof prop.max === "number" ? { maximum: prop.max } : {}), }
### Example: Handling options
for string props typescript case 'string': return { ...base, type: 'string', ...(Array.isArray(prop.options) ? { enum: prop.options.map(opt => typeof opt === "string" ? opt : opt.value) } : {}), }
If options
is a function (for dynamic options), you may not be able to statically convert it to JSON Schema. ### Generalized Approach You should narrow the prop type based on the type
field, and only access fields that are valid for that type, checking for their existence: typescript function propToJSONSchema(prop: ConfigurableProp): JSONSchema7 | undefined { const base: Partial<JSONSchema7> = { title: prop.label ?? prop.name, description: prop.description, ...(prop.default !== undefined ? { default: prop.default } : {}), }; switch (prop.type) { case "boolean": return { ...base, type: "boolean" }; case "integer": return { ...base, type: "integer", ...(typeof prop.min === "number" ? { minimum: prop.min } : {}), ...(typeof prop.max === "number" ? { maximum: prop.max } : {}), }; case "integer[]": return { ...base, type: "array", items: { type: "integer", ...(typeof prop.min === "number" ? { minimum: prop.min } : {}), ...(typeof prop.max === "number" ? { maximum: prop.max } : {}), }, }; case "string": return { ...base, type: "string", ...(Array.isArray(prop.options) ? { enum: prop.options.map(opt => typeof opt === "string" ? opt : opt.value) } : {}), }; case "string[]": return { ...base, type: "array", items: { type: "string", ...(Array.isArray(prop.options) ? { enum: prop.options.map(opt => typeof opt === "string" ? opt : opt.value) } : {}), }, }; case "object": return { ...base, type: "object", additionalProperties: true, }; // ...handle other types as needed... default: return undefined; } }
## Summary - Check for the presence of optional fields before using them. - Narrow the prop type using the type
field. - Use optional chaining or type guards to avoid TypeScript errors. - Dynamic options (functions) can’t be represented in JSON Schema—you may need to skip or handle these differently. This approach is now necessary due to the more flexible, generic prop typing in the new SDK. — References: - Pipedream Prop Docs (see the table for which fields are valid for which types) - Pipedream SDK Example (see how ConfigurableProps
are used) If you have a specific prop type or error, please share the type definition or error message for more targeted help!
Great. But I’m still having this issue:
Listing Apps
Before the migration, I obtained a list of apps using this method:
const page = await pipedream.getApps(searchProps)
In this “page” result object, I could obtain not only the list of apps in the current page, using page.data
, but also page.page_info.total_count
and page.page_info.end_cursor
for paginated queries.
Now, it has changed to this:
const page = await pipedream.apps.list(searchProps)
However, the result object now only has data
, getNextPage
and hasNextPage
, which I cannot use because I stored the end_cursor
value to perform paginated queries asyncronously
can this be fixed?
Thank you for explaining your issue in detail. I understand the frustration—pagination is a common need, and losing access to cursors like end_cursor
can break existing async workflows. I’m sorry for the trouble this change has caused. If you need more help or want to talk to a human, please visit Support - Pipedream. ### What Changed? Previously, the result from pipedream.getApps(searchProps)
included a page_info
object with properties like total_count
and end_cursor
, which you could use to manage pagination manually and asynchronously. Now, with pipedream.apps.list(searchProps)
, you only get: - data
(the current page of results) - getNextPage
(a function to fetch the next page) - hasNextPage
(a boolean indicating if more pages exist) The new API abstracts pagination, encouraging you to use getNextPage
for synchronous, sequential fetching, but it no longer exposes the raw cursor values you need for custom async pagination. ### Can This Be Fixed? Currently, with the new SDK, you cannot directly access end_cursor
or total_count
from the result. The SDK intentionally hides these details to simplify usage for most users, but this breaks advanced use cases like yours. #### Workarounds 1. Use the REST API Directly - The REST API docs show that the raw API response still includes page_info
with end_cursor
and total_count
. - You can call the API directly with axios
or fetch
to get these values: import { axios } from "@pipedream/platform"; export default defineComponent({ async run({ steps, $ }) { const response = await axios($, { url: "https://api.pipedream.com/v1/apps", headers: { Authorization: `Bearer ${this.pipedream.$auth.api_key}`, }, params: { // your searchProps here } }); // response.page_info.end_cursor, response.page_info.total_count, etc. return response; } });
2. Request SDK Enhancement - If you prefer using the SDK, you can request that Pipedream exposes pagination cursors and metadata in the SDK response. Please submit this feedback at Support - Pipedream. #### Summary - Short-term: Use the REST API directly to access end_cursor
and total_count
. - Long-term: Request SDK support for exposing pagination metadata. Again, sorry for the disruption. If you need more help or want to discuss this with the Pipedream team, please visit Support - Pipedream.