Skip to content

Commit 91700d3

Browse files
committed
Improve auth types and API
1 parent ce6e01e commit 91700d3

File tree

12 files changed

+152
-73
lines changed

12 files changed

+152
-73
lines changed

packages/core/src/v3/apiClient/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export class ApiClient {
187187
secretKey: this.accessToken,
188188
payload: {
189189
...claims,
190-
permissions: [data.id],
190+
permissions: [`read:runs:${data.id}`],
191191
},
192192
expirationTime: requestOptions?.jwt?.expirationTime ?? "1h",
193193
});

packages/core/src/v3/runMetadata/index.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class RunMetadataAPI {
7575
this.store = nextStore;
7676
}
7777

78-
public deleteKey(key: string, requestOptions?: ApiRequestOptions) {
78+
public deleteKey(key: string) {
7979
const runId = taskContext.ctx?.run.id;
8080

8181
if (!runId) {
@@ -92,21 +92,18 @@ export class RunMetadataAPI {
9292
this.store = nextStore;
9393
}
9494

95-
public async update(
96-
metadata: Record<string, DeserializedJson>,
97-
requestOptions?: ApiRequestOptions
98-
): Promise<void> {
95+
public update(metadata: Record<string, DeserializedJson>): void {
9996
const runId = taskContext.ctx?.run.id;
10097

10198
if (!runId) {
10299
return;
103100
}
104101

105-
const apiClient = apiClientManager.clientOrThrow();
106-
107-
const response = await apiClient.updateRunMetadata(runId, { metadata }, requestOptions);
102+
if (!dequal(this.store, metadata)) {
103+
this.hasChanges = true;
104+
}
108105

109-
this.store = response.metadata;
106+
this.store = metadata;
110107
}
111108

112109
public async flush(requestOptions?: ApiRequestOptions): Promise<void> {

packages/trigger-sdk/src/v3/auth.ts

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,48 @@ export function configure(options: ApiClientConfiguration) {
2424

2525
export const auth = {
2626
configure,
27-
generateJWT,
28-
context,
27+
createPublicToken,
28+
withAuth,
2929
};
3030

31-
export type GenerateJWTOptions = {
32-
permissions?: string[];
31+
type PublicTokenPermissionAction = "read"; // Add more actions as needed
32+
33+
type PublicTokenPermissionProperties = {
34+
tags?: string | string[];
35+
runs?: string | string[];
36+
};
37+
38+
export type PublicTokenPermissions = {
39+
[key in PublicTokenPermissionAction]?: PublicTokenPermissionProperties | true;
40+
};
41+
42+
export type CreatePublicTokenOptions = {
43+
permissions?: PublicTokenPermissions;
3344
expirationTime?: number | Date | string;
3445
};
3546

36-
async function generateJWT(options?: GenerateJWTOptions): Promise<string> {
47+
/**
48+
* Creates a public token using the provided options.
49+
*
50+
* @param options - Optional parameters for creating the public token.
51+
* @param options.permissions - An array of permissions to be included in the token.
52+
* @param options.expirationTime - The expiration time for the token.
53+
* @returns A promise that resolves to a string representing the generated public token.
54+
*
55+
* @example
56+
*
57+
* ```typescript
58+
* import { auth } from "@trigger.dev/sdk/v3";
59+
*
60+
* const publicToken = await auth.createPublicToken({
61+
* permissions: {
62+
* read: {
63+
* tags: ["file:1234"]
64+
* }
65+
* });
66+
* ```
67+
*/
68+
async function createPublicToken(options?: CreatePublicTokenOptions): Promise<string> {
3769
const apiClient = apiClientManager.clientOrThrow();
3870

3971
const claims = await apiClient.generateJWTClaims();
@@ -42,15 +74,47 @@ async function generateJWT(options?: GenerateJWTOptions): Promise<string> {
4274
secretKey: apiClient.accessToken,
4375
payload: {
4476
...claims,
45-
permissions: options?.permissions,
77+
permissions: options?.permissions ? flattenPermissions(options.permissions) : undefined,
4678
},
4779
expirationTime: options?.expirationTime,
4880
});
4981
}
5082

51-
async function context<R extends (...args: any[]) => Promise<any>>(
83+
/**
84+
* Executes a provided asynchronous function with a specified API client configuration.
85+
*
86+
* @template R - The type of the asynchronous function to be executed.
87+
* @param {ApiClientConfiguration} config - The configuration for the API client.
88+
* @param {R} fn - The asynchronous function to be executed.
89+
* @returns {Promise<ReturnType<R>>} A promise that resolves to the return type of the provided function.
90+
*/
91+
async function withAuth<R extends (...args: any[]) => Promise<any>>(
5292
config: ApiClientConfiguration,
5393
fn: R
5494
): Promise<ReturnType<R>> {
5595
return apiClientManager.runWithConfig(config, fn);
5696
}
97+
98+
function flattenPermissions(permissions: PublicTokenPermissions): string[] {
99+
const flattenedPermissions: string[] = [];
100+
101+
for (const [action, properties] of Object.entries(permissions)) {
102+
if (properties) {
103+
if (typeof properties === "boolean" && properties) {
104+
flattenedPermissions.push(action);
105+
} else if (typeof properties === "object") {
106+
for (const [property, value] of Object.entries(properties)) {
107+
if (Array.isArray(value)) {
108+
for (const item of value) {
109+
flattenedPermissions.push(`${action}:${property}:${item}`);
110+
}
111+
} else if (typeof value === "string") {
112+
flattenedPermissions.push(`${action}:${property}:${value}`);
113+
}
114+
}
115+
}
116+
}
117+
}
118+
119+
return flattenedPermissions;
120+
}

packages/trigger-sdk/src/v3/metadata.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const metadata = {
2323
set: setMetadataKey,
2424
del: deleteMetadataKey,
2525
save: saveMetadata,
26+
replace: replaceMetadata,
2627
flush: flushMetadata,
2728
};
2829

@@ -88,34 +89,24 @@ function deleteMetadataKey(key: string) {
8889
* This function allows you to replace the entire metadata object with a new one.
8990
*
9091
* @param {RunMetadata} metadata - The new metadata object to set for the run.
91-
* @param {ApiRequestOptions} [requestOptions] - Optional API request options.
92-
* @returns {Promise<void>} A promise that resolves when the metadata is updated.
92+
* @returns {void}
9393
*
9494
* @example
95-
* await metadata.save({ progress: 0.6, user: { name: "Alice", id: "user_5678" } });
95+
* metadata.replace({ progress: 0.6, user: { name: "Alice", id: "user_5678" } });
9696
*/
97-
async function saveMetadata(
98-
metadata: RunMetadata,
99-
requestOptions?: ApiRequestOptions
100-
): Promise<void> {
101-
const $requestOptions = mergeRequestOptions(
102-
{
103-
tracer,
104-
name: "metadata.save()",
105-
icon: "code-plus",
106-
attributes: {
107-
...flattenAttributes(metadata),
108-
},
109-
},
110-
requestOptions
111-
);
97+
function replaceMetadata(metadata: RunMetadata): void {
98+
runMetadata.update(metadata);
99+
}
112100

113-
await runMetadata.update(metadata, $requestOptions);
101+
/**
102+
* @deprecated Use `metadata.replace()` instead.
103+
*/
104+
function saveMetadata(metadata: RunMetadata): void {
105+
runMetadata.update(metadata);
114106
}
115107

116108
/**
117-
* Flushes metadata by merging the provided request options with default options
118-
* and then executing the flush operation.
109+
* Flushes metadata to the Trigger.dev instance
119110
*
120111
* @param {ApiRequestOptions} [requestOptions] - Optional request options to customize the API request.
121112
* @returns {Promise<void>} A promise that resolves when the metadata flush operation is complete.

references/nextjs-realtime/src/app/api/uploadthing/core.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,16 @@ export const ourFileRouter = {
3535
tags: [`user:${metadata.userId}`, fileTag],
3636
});
3737

38-
const jwt = await auth.generateJWT({ permissions: [`read:tags:${fileTag}`] });
38+
const publicAccessToken = await auth.createPublicToken({
39+
permissions: {
40+
read: { tags: fileTag },
41+
},
42+
});
3943

40-
console.log("Generated JWT:", jwt);
44+
console.log("Generated access token:", publicAccessToken);
4145

4246
// !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
43-
return { uploadedBy: metadata.userId, jwt, fileId: file.key };
47+
return { uploadedBy: metadata.userId, publicAccessToken, fileId: file.key };
4448
}),
4549
} satisfies FileRouter;
4650

references/nextjs-realtime/src/app/uploads/[id]/ClientUploadDetails.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,17 @@ function UploadDetailsWrapper({ fileId }: { fileId: string }) {
6060
);
6161
}
6262

63-
export default function ClientUploadDetails({ fileId, jwt }: { fileId: string; jwt: string }) {
63+
export default function ClientUploadDetails({
64+
fileId,
65+
publicAccessToken,
66+
}: {
67+
fileId: string;
68+
publicAccessToken: string;
69+
}) {
6470
return (
65-
<TriggerAuthContext.Provider value={{ accessToken: jwt, baseURL: "http://localhost:3030" }}>
71+
<TriggerAuthContext.Provider
72+
value={{ accessToken: publicAccessToken, baseURL: "http://localhost:3030" }}
73+
>
6674
<UploadDetailsWrapper fileId={fileId} />
6775
</TriggerAuthContext.Provider>
6876
);

references/nextjs-realtime/src/app/uploads/[id]/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ export default async function UploadPage({
88
params: { id: string };
99
searchParams: { [key: string]: string | string[] | undefined };
1010
}) {
11-
const jwt = searchParams.jwt;
11+
const publicAccessToken = searchParams.publicAccessToken;
1212

13-
if (typeof jwt !== "string") {
13+
if (typeof publicAccessToken !== "string") {
1414
notFound();
1515
}
1616

1717
return (
1818
<main className="flex min-h-screen items-center justify-center p-4 bg-gray-100">
19-
<ClientUploadDetails fileId={params.id} jwt={jwt} />
19+
<ClientUploadDetails fileId={params.id} publicAccessToken={publicAccessToken} />
2020
</main>
2121
);
2222
}

references/nextjs-realtime/src/components/ImageUploadButton.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export function ImageUploadButton() {
1515

1616
const firstFile = res[0];
1717

18-
router.push(`/uploads/${firstFile.serverData.fileId}?jwt=${firstFile.serverData.jwt}`);
18+
router.push(
19+
`/uploads/${firstFile.serverData.fileId}?publicAccessToken=${firstFile.serverData.publicAccessToken}`
20+
);
1921
}}
2022
onUploadError={(error: Error) => {
2123
// Do something with the error.
@@ -37,7 +39,9 @@ export function ImageUploadDropzone() {
3739

3840
const firstFile = res[0];
3941

40-
router.push(`/uploads/${firstFile.serverData.fileId}?jwt=${firstFile.serverData.jwt}`);
42+
router.push(
43+
`/uploads/${firstFile.serverData.fileId}?publicAccessToken=${firstFile.serverData.publicAccessToken}`
44+
);
4145
}}
4246
onUploadError={(error: Error) => {
4347
// Do something with the error.

references/nextjs-realtime/src/trigger/images.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { logger, metadata, schemaTask } from "@trigger.dev/sdk/v3";
1+
import { idempotencyKeys, logger, metadata, schemaTask } from "@trigger.dev/sdk/v3";
22
import { FalResult, GridImage, UploadedFileData } from "@/utils/schemas";
33
import { z } from "zod";
44

@@ -14,35 +14,39 @@ export const handleUpload = schemaTask({
1414
run: async (file, { ctx }) => {
1515
logger.info("Handling uploaded file", { file });
1616

17-
await Promise.all([
18-
runFalModel.trigger(
19-
{
17+
const results = await runFalModel.batchTriggerAndWait([
18+
{
19+
payload: {
2020
model: "fal-ai/image-preprocessors/canny",
2121
url: file.url,
2222
input: {
2323
low_threshold: 100,
2424
high_threshold: 200,
2525
},
2626
},
27-
{ tags: ctx.run.tags }
28-
),
29-
runFalModel.trigger(
30-
{
27+
options: {
28+
tags: ctx.run.tags,
29+
},
30+
},
31+
{
32+
payload: {
3133
model: "fal-ai/aura-sr",
3234
url: file.url,
3335
input: {},
3436
},
35-
{ tags: ctx.run.tags }
36-
),
37-
runFalModel.trigger(
38-
{
37+
options: { tags: ctx.run.tags },
38+
},
39+
{
40+
payload: {
3941
model: "fal-ai/imageutils/depth",
4042
url: file.url,
4143
input: {},
4244
},
43-
{ tags: ctx.run.tags }
44-
),
45+
options: { tags: ctx.run.tags },
46+
},
4547
]);
48+
49+
return results;
4650
},
4751
});
4852

references/v3-catalog/src/clientUsage.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ import { runs, tasks, auth, AnyTask, Task } from "@trigger.dev/sdk/v3";
22
import type { task1, task2 } from "./trigger/taskTypes.js";
33

44
async function main() {
5-
const anyHandle = await tasks.trigger<typeof task1>("types/task-1", {
6-
foo: "baz",
7-
});
5+
const anyHandle = await tasks.trigger<typeof task1>(
6+
"types/task-1",
7+
{
8+
foo: "baz",
9+
},
10+
{
11+
tags: ["user:1234"],
12+
}
13+
);
814

9-
const jwt = await auth.generateJWT({ permissions: [anyHandle.id] });
15+
const jwt = await auth.createPublicToken({ permissions: { read: { tags: "user:1234" } } });
1016

1117
console.log("Generated JWT:", jwt);
1218

13-
// The JWT will be passed to the client
14-
await auth.context({ accessToken: jwt }, async () => {
19+
await auth.withAuth({ accessToken: jwt }, async () => {
1520
const subscription = runs.subscribeToTag<typeof task1 | typeof task2>("user:1234");
1621

1722
for await (const run of subscription) {

references/v3-catalog/src/trigger/runMetadata.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,19 @@ export const runMetadataChildTask = task({
2121
run: async (payload: any, { ctx }) => {
2222
logger.info("metadata", { metadata: metadata.current() });
2323

24-
await metadata.set("child", "task");
24+
metadata.set("child", "task");
2525

2626
logger.info("metadata", { metadata: metadata.current() });
2727

28-
await metadata.set("child-2", "task-2");
28+
metadata.set("child-2", "task-2");
2929

3030
logger.info("metadata", { current: metadata.current() });
3131

32-
await metadata.del("hello");
32+
metadata.del("hello");
3333

3434
logger.info("metadata", { metadata: metadata.current() });
3535

36-
await metadata.save({
36+
metadata.replace({
3737
there: {
3838
is: {
3939
something: "here",

0 commit comments

Comments
 (0)