feat: support app id with namespaces (#61)
* feat: support app id with namespaces * chore: remove unwanted page * fix: queue status url * chore: bump version for release
This commit is contained in:
parent
335b817e9c
commit
dfeb8689f4
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@fal-ai/serverless-client",
|
"name": "@fal-ai/serverless-client",
|
||||||
"description": "The fal serverless JS/TS client",
|
"description": "The fal serverless JS/TS client",
|
||||||
"version": "0.9.0",
|
"version": "0.9.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { getRestApiUrl } from './config';
|
import { getRestApiUrl } from './config';
|
||||||
import { dispatchRequest } from './request';
|
import { dispatchRequest } from './request';
|
||||||
import { ensureAppIdFormat } from './utils';
|
import { parseAppId } from './utils';
|
||||||
|
|
||||||
export const TOKEN_EXPIRATION_SECONDS = 120;
|
export const TOKEN_EXPIRATION_SECONDS = 120;
|
||||||
|
|
||||||
@ -8,12 +8,12 @@ export const TOKEN_EXPIRATION_SECONDS = 120;
|
|||||||
* Get a token to connect to the realtime endpoint.
|
* Get a token to connect to the realtime endpoint.
|
||||||
*/
|
*/
|
||||||
export async function getTemporaryAuthToken(app: string): Promise<string> {
|
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>(
|
const token: string | object = await dispatchRequest<any, string>(
|
||||||
'POST',
|
'POST',
|
||||||
`${getRestApiUrl()}/tokens/`,
|
`${getRestApiUrl()}/tokens/`,
|
||||||
{
|
{
|
||||||
allowed_apps: [appAlias],
|
allowed_apps: [appId.alias],
|
||||||
token_expiration: TOKEN_EXPIRATION_SECONDS,
|
token_expiration: TOKEN_EXPIRATION_SECONDS,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { dispatchRequest } from './request';
|
import { dispatchRequest } from './request';
|
||||||
import { storageImpl } from './storage';
|
import { storageImpl } from './storage';
|
||||||
import { EnqueueResult, QueueStatus } from './types';
|
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
|
* The function input and other configuration when running
|
||||||
@ -284,8 +284,9 @@ export const queue: Queue = {
|
|||||||
id: string,
|
id: string,
|
||||||
{ requestId, logs = false }: QueueStatusOptions
|
{ requestId, logs = false }: QueueStatusOptions
|
||||||
): Promise<QueueStatus> {
|
): Promise<QueueStatus> {
|
||||||
const [appOwner, appAlias] = ensureAppIdFormat(id).split('/');
|
const appId = parseAppId(id);
|
||||||
return send(`${appOwner}/${appAlias}`, {
|
const prefix = appId.namespace ? `${appId.namespace}/` : '';
|
||||||
|
return send(`${prefix}${appId.owner}/${appId.alias}`, {
|
||||||
subdomain: 'queue',
|
subdomain: 'queue',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: `/requests/${requestId}/status`,
|
path: `/requests/${requestId}/status`,
|
||||||
@ -298,8 +299,9 @@ export const queue: Queue = {
|
|||||||
id: string,
|
id: string,
|
||||||
{ requestId }: BaseQueueOptions
|
{ requestId }: BaseQueueOptions
|
||||||
): Promise<Output> {
|
): Promise<Output> {
|
||||||
const [appOwner, appAlias] = ensureAppIdFormat(id).split('/');
|
const appId = parseAppId(id);
|
||||||
return send(`${appOwner}/${appAlias}`, {
|
const prefix = appId.namespace ? `${appId.namespace}/` : '';
|
||||||
|
return send(`${prefix}${appId.owner}/${appId.alias}`, {
|
||||||
subdomain: 'queue',
|
subdomain: 'queue',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: `/requests/${requestId}`,
|
path: `/requests/${requestId}`,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import uuid from 'uuid-random';
|
|||||||
import { TOKEN_EXPIRATION_SECONDS, getTemporaryAuthToken } from './auth';
|
import { TOKEN_EXPIRATION_SECONDS, getTemporaryAuthToken } from './auth';
|
||||||
import { ApiError } from './response';
|
import { ApiError } from './response';
|
||||||
import { isBrowser } from './runtime';
|
import { isBrowser } from './runtime';
|
||||||
import { ensureAppIdFormat, isReact, throttle } from './utils';
|
import { ensureAppIdFormat, isReact, parseAppId, throttle } from './utils';
|
||||||
|
|
||||||
// Define the context
|
// Define the context
|
||||||
interface Context {
|
interface Context {
|
||||||
@ -273,9 +273,9 @@ function buildRealtimeUrl(
|
|||||||
queryParams.set('max_buffering', maxBuffering.toFixed(0));
|
queryParams.set('max_buffering', maxBuffering.toFixed(0));
|
||||||
}
|
}
|
||||||
const appId = ensureAppIdFormat(app);
|
const appId = ensureAppIdFormat(app);
|
||||||
const [, appAlias] = ensureAppIdFormat(app).split('/');
|
const { alias } = parseAppId(appId);
|
||||||
const suffix =
|
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()}`;
|
return `wss://fal.run/${appId}/${suffix}?${queryParams.toString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import uuid from 'uuid-random';
|
import uuid from 'uuid-random';
|
||||||
import { ensureAppIdFormat, isUUIDv4 } from './utils';
|
import { ensureAppIdFormat, isUUIDv4, parseAppId } from './utils';
|
||||||
|
|
||||||
describe('The utils test suite', () => {
|
describe('The utils test suite', () => {
|
||||||
it('should match a valid v4 uuid', () => {
|
it('should match a valid v4 uuid', () => {
|
||||||
@ -31,4 +31,42 @@ describe('The utils test suite', () => {
|
|||||||
const id = 'just-an-id';
|
const id = 'just-an-id';
|
||||||
expect(() => ensureAppIdFormat(id)).toThrowError();
|
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',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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) {
|
export function isValidUrl(url: string) {
|
||||||
try {
|
try {
|
||||||
const { host } = new URL(url);
|
const { host } = new URL(url);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user