Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support app id with namespaces #61

Merged
merged 4 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libs/client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fal-ai/serverless-client",
"description": "The fal serverless JS/TS client",
"version": "0.9.0",
"version": "0.9.1-alpha.1",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
6 changes: 3 additions & 3 deletions libs/client/src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { getRestApiUrl } from './config';
import { dispatchRequest } from './request';
import { ensureAppIdFormat } from './utils';
import { parseAppId } from './utils';

export const TOKEN_EXPIRATION_SECONDS = 120;

/**
* Get a token to connect to the realtime endpoint.
*/
export async function getTemporaryAuthToken(app: string): Promise<string> {
const [, appAlias] = ensureAppIdFormat(app).split('/');
const appId = parseAppId(app);
const token: string | object = await dispatchRequest<any, string>(

Check warning on line 12 in libs/client/src/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
'POST',
`${getRestApiUrl()}/tokens/`,
{
allowed_apps: [appAlias],
allowed_apps: [appId.alias],
token_expiration: TOKEN_EXPIRATION_SECONDS,
}
);
Expand Down
12 changes: 7 additions & 5 deletions libs/client/src/function.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { dispatchRequest } from './request';
import { storageImpl } from './storage';
import { EnqueueResult, QueueStatus } from './types';
import { ensureAppIdFormat, isUUIDv4, isValidUrl } from './utils';
import { ensureAppIdFormat, isUUIDv4, isValidUrl, parseAppId } from './utils';

/**
* The function input and other configuration when running
Expand Down Expand Up @@ -284,8 +284,9 @@ export const queue: Queue = {
id: string,
{ requestId, logs = false }: QueueStatusOptions
): Promise<QueueStatus> {
const [appOwner, appAlias] = ensureAppIdFormat(id).split('/');
return send(`${appOwner}/${appAlias}`, {
const appId = parseAppId(id);
const prefix = appId.namespace ? `${appId.namespace}/` : '';
return send(`${prefix}${appId.owner}/${appId.alias}`, {
subdomain: 'queue',
method: 'get',
path: `/requests/${requestId}/status`,
Expand All @@ -298,8 +299,9 @@ export const queue: Queue = {
id: string,
{ requestId }: BaseQueueOptions
): Promise<Output> {
const [appOwner, appAlias] = ensureAppIdFormat(id).split('/');
return send(`${appOwner}/${appAlias}`, {
const appId = parseAppId(id);
const prefix = appId.namespace ? `${appId.namespace}/` : '';
return send(`${prefix}${appId.owner}/${appId.alias}`, {
subdomain: 'queue',
method: 'get',
path: `/requests/${requestId}`,
Expand Down
6 changes: 3 additions & 3 deletions libs/client/src/realtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import { TOKEN_EXPIRATION_SECONDS, getTemporaryAuthToken } from './auth';
import { ApiError } from './response';
import { isBrowser } from './runtime';
import { ensureAppIdFormat, isReact, throttle } from './utils';
import { ensureAppIdFormat, isReact, parseAppId, throttle } from './utils';

// Define the context
interface Context {
Expand Down Expand Up @@ -273,9 +273,9 @@
queryParams.set('max_buffering', maxBuffering.toFixed(0));
}
const appId = ensureAppIdFormat(app);
const [, appAlias] = ensureAppIdFormat(app).split('/');
const { alias } = parseAppId(appId);
const suffix =
LEGACY_APPS.includes(appAlias) || !app.includes('/') ? 'ws' : 'realtime';
LEGACY_APPS.includes(alias) || !app.includes('/') ? 'ws' : 'realtime';
return `wss://fal.run/${appId}/${suffix}?${queryParams.toString()}`;
}

Expand Down Expand Up @@ -455,7 +455,7 @@
}
send({ type: 'connectionClosed', code: event.code });
};
ws.onerror = (event) => {

Check warning on line 458 in libs/client/src/realtime.ts

View workflow job for this annotation

GitHub Actions / build

'event' is defined but never used
// TODO specify error protocol for identified errors
const { onError = noop } = getCallbacks();
onError(new ApiError({ message: 'Unknown error', status: 500 }));
Expand Down
40 changes: 39 additions & 1 deletion libs/client/src/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import uuid from 'uuid-random';
import { ensureAppIdFormat, isUUIDv4 } from './utils';
import { ensureAppIdFormat, isUUIDv4, parseAppId } from './utils';

describe('The utils test suite', () => {
it('should match a valid v4 uuid', () => {
Expand Down Expand Up @@ -31,4 +31,42 @@ describe('The utils test suite', () => {
const id = 'just-an-id';
expect(() => ensureAppIdFormat(id)).toThrowError();
});

it('should parse a legacy app id', () => {
const id = '12345-abcde-fgh';
const parsed = parseAppId(id);
expect(parsed).toEqual({
owner: '12345',
alias: 'abcde-fgh',
});
});

it('should parse a current app id', () => {
const id = 'fal-ai/fast-sdxl';
const parsed = parseAppId(id);
expect(parsed).toEqual({
owner: 'fal-ai',
alias: 'fast-sdxl',
});
});

it('should parse a current app id with path', () => {
const id = 'fal-ai/fast-sdxl/image-to-image';
const parsed = parseAppId(id);
expect(parsed).toEqual({
owner: 'fal-ai',
alias: 'fast-sdxl',
path: 'image-to-image',
});
});

it('should parse a current app id with namespace', () => {
const id = 'workflows/fal-ai/fast-sdxl';
const parsed = parseAppId(id);
expect(parsed).toEqual({
owner: 'fal-ai',
alias: 'fast-sdxl',
namespace: 'workflows',
});
});
});
29 changes: 29 additions & 0 deletions libs/client/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,35 @@ export function ensureAppIdFormat(id: string): string {
);
}

const APP_NAMESPACES = ['workflows'] as const;

type AppNamespace = (typeof APP_NAMESPACES)[number];

export type AppId = {
readonly owner: string;
readonly alias: string;
readonly path?: string;
readonly namespace?: AppNamespace;
};

export function parseAppId(id: string): AppId {
const normalizedId = ensureAppIdFormat(id);
const parts = normalizedId.split('/');
if (APP_NAMESPACES.includes(parts[0] as any)) {
return {
owner: parts[1],
alias: parts[2],
path: parts.slice(3).join('/') || undefined,
namespace: parts[0] as AppNamespace,
};
}
return {
owner: parts[0],
alias: parts[1],
path: parts.slice(2).join('/') || undefined,
};
}

export function isValidUrl(url: string) {
try {
const { host } = new URL(url);
Expand Down
Loading