feat(workers): expose app_uid in puter.workers.get and puter.workers.list (#3596)

This commit is contained in:
Anany Singh
2026-08-18 00:05:17 -07:00
committed by GitHub
parent bd06e88185
commit 95d797b047
6 changed files with 83 additions and 12 deletions
@@ -152,11 +152,11 @@ const waitFor = async (
const putCalls = () =>
fetchSpy.mock.calls.filter(
([, init]) => (init as RequestInit | undefined)?.method === 'PUT',
(call) => (call[1] as RequestInit | undefined)?.method === 'PUT',
);
const deleteCalls = () =>
fetchSpy.mock.calls.filter(
([, init]) => (init as RequestInit | undefined)?.method === 'DELETE',
(call) => (call[1] as RequestInit | undefined)?.method === 'DELETE',
);
// -- create ----------------------------------------------------------
@@ -504,6 +504,7 @@ describe('WorkerDriver.getFilePaths source resolution', () => {
url: string;
file_path: string | null;
file_uid: string | null;
app_uid: string | null;
created_at: string | null;
}>;
@@ -512,6 +513,7 @@ describe('WorkerDriver.getFilePaths source resolution', () => {
expect(row.url).toBe(`https://${name}.puter.work`);
expect(row.file_path).toBe(path);
expect(row.file_uid).toBe(entry.uuid);
expect(row.app_uid).toBeNull();
expect(row.created_at).toMatch(/^\d{4}-\d{2}-\d{2}T/);
});
@@ -619,7 +621,7 @@ describe('WorkerDriver hot reload', () => {
);
return !row;
}, 'subdomain row removal after source delete');
expect(deleteCalls().map(([u]) => u)).toContain(
expect(deleteCalls().map((call) => call[0])).toContain(
`${SCRIPTS_BASE}/${name}/`,
);
expect(path).toContain(user.username);
@@ -646,7 +648,7 @@ describe('WorkerDriver hot reload', () => {
);
return !row;
}, 'subdomain row removal after trash move');
expect(deleteCalls().map(([u]) => u)).toContain(
expect(deleteCalls().map((call) => call[0])).toContain(
`${SCRIPTS_BASE}/${name}/`,
);
});
@@ -190,7 +190,7 @@ describe('WorkerDriver', () => {
it('rejects reserved worker names', async () => {
const serverWithReserved = await setupTestServer({
reserved_words: ['admin', 'api'],
});
} as never);
const driverWithReserved = serverWithReserved.drivers
.workers as unknown as WorkerDriver;
try {
@@ -486,6 +486,46 @@ describe('WorkerDriver', () => {
expect.arrayContaining([`${tag}-bound`, `${tag}-loose`]),
);
});
it('returns app_uid for sandboxed/app-bound workers and null for user-scoped workers', async () => {
const tag = `wkuid${Math.random().toString(36).slice(2, 8)}`;
const owner = await makeUser(tag);
const app = await server.stores.app.create(
{
name: `app-${tag}`,
title: `app-${tag}`,
index_url: `https://app-${tag}.example.com/`,
},
{ ownerUserId: owner.id },
);
await server.stores.subdomain.create({
userId: owner.id,
subdomain: `workers.puter.${tag}-bound`,
rootDirId: null,
associatedAppId: null,
appOwner: app.id,
});
await server.stores.subdomain.create({
userId: owner.id,
subdomain: `workers.puter.${tag}-plain`,
rootDirId: null,
associatedAppId: null,
appOwner: null,
});
const seen = (await inCtx(
() => target.getFilePaths({}),
actorFor(owner),
)) as Array<{ name: string; app_uid: string | null }>;
const bound = seen.find((r) => r.name === `${tag}-bound`);
const plain = seen.find((r) => r.name === `${tag}-plain`);
expect(bound).toBeDefined();
expect(bound?.app_uid).toBe(app.uid);
expect(plain).toBeDefined();
expect(plain?.app_uid).toBeNull();
});
});
describe('getFilePaths pagination', () => {
@@ -586,7 +626,7 @@ describe('WorkerDriver', () => {
it('returns the configured loggingUrl', async () => {
const serverWithLogging = await setupTestServer({
workers: { loggingUrl: 'https://logs.test/view' },
});
} as never);
const driverWithLogging = serverWithLogging.drivers
.workers as unknown as WorkerDriver;
try {
@@ -622,7 +662,7 @@ describe('WorkerDriver', () => {
it('rejects an unverified account without the bypass', async () => {
const strictServer = await setupTestServer({
strict_email_verification_required: true,
});
} as never);
const strictDriver = strictServer.drivers
.workers as unknown as WorkerDriver;
try {
@@ -646,7 +686,7 @@ describe('WorkerDriver', () => {
it('lets the bypass past the verified-email gate', async () => {
const strictServer = await setupTestServer({
strict_email_verification_required: true,
});
} as never);
const strictDriver = strictServer.drivers
.workers as unknown as WorkerDriver;
try {
@@ -725,7 +765,7 @@ describe('WorkerDriver', () => {
it('cannot be forged through caller-supplied args', async () => {
const strictServer = await setupTestServer({
strict_email_verification_required: true,
});
} as never);
const strictDriver = strictServer.drivers
.workers as unknown as WorkerDriver;
// Driver args reach the method as parsed JSON from the request
@@ -558,6 +558,11 @@ export class WorkerDriver extends PuterDriver {
});
}
const appOwnerIds = rows
.map((r) => r.app_owner)
.filter((id): id is number => typeof id === 'number');
const appsById = await this.stores.app.getByIds(appOwnerIds);
const items = rows.map((r) => {
const name =
String(r.subdomain ?? '')
@@ -570,11 +575,17 @@ export class WorkerDriver extends PuterDriver {
file_path = loaded?.path;
file_uid = loaded?.uuid;
}
let app_uid = null;
if (typeof r.app_owner === 'number') {
const loadedApp = appsById.get(r.app_owner);
app_uid = loadedApp?.uid ?? null;
}
return {
name,
url: `https://${name}.puter.work`,
file_path,
file_uid,
app_uid,
created_at: r.ts
? new Date(r.ts as string).toISOString()
: null,
+4
View File
@@ -23,6 +23,10 @@ A string containing the file path of the worker source code.
A string containing the unique identifier of the worker file.
#### `app_uid` (String)
A string containing the unique identifier of the app or sandbox app associated with the worker, or `null` if the worker is user-scoped.
#### `created_at` (String)
A string containing the date and time when the worker was created.
+1
View File
@@ -11,6 +11,7 @@ import { fetchAllPages, iteratePages } from '../lib/pagination.js';
* @property {string} url The URL of the worker.
* @property {string} file_path The file path of the worker's source code.
* @property {string} file_uid The unique identifier of the worker file.
* @property {string | null} app_uid The unique identifier of the app or sandbox app owning the worker, or null if user-scoped.
* @property {string} created_at The date and time when the worker was created.
*/
+16 -3
View File
@@ -161,9 +161,20 @@ export default suite('workers', {
},
'get returns the deployed worker': async (t) => {
await deployWorker(t, 'workers-suite-get');
const worker = await t.puter.workers.get('workers-suite-get');
const name = 'workers-suite-get';
await deployWorker(t, name);
const worker = await t.puter.workers.get(name);
t.assert.ok(worker, 'get should return the worker');
const sandboxApp = await t.puter.apps.get(`sandbox-${name}`);
t.assert.equal(worker.app_uid, sandboxApp.uid, 'worker app_uid should match sandbox app uid');
},
'get returns null app_uid for unsandboxed worker': async (t) => {
const name = 'workers-suite-get-plain';
await deployWorker(t, name, { sandbox: false });
const worker = await t.puter.workers.get(name);
t.assert.ok(worker, 'get should return the worker');
t.assert.equal(worker.app_uid, null, 'unsandboxed worker app_uid should be null');
},
'list includes deployed workers': async (t) => {
@@ -231,7 +242,7 @@ export default suite('workers', {
'create binds the worker to a named app': async (t) => {
const appName = 'workers-suite-host-app';
await t.puter.apps.create(appName, 'https://example.com/worker-host');
const hostApp = await t.puter.apps.create(appName, 'https://example.com/worker-host');
const name = 'workers-suite-bound';
const sourcePath = `${home(t)}/workers-suite-${name}.js`;
await t.puter.fs.write(sourcePath, WORKER_SOURCE);
@@ -242,6 +253,8 @@ export default suite('workers', {
false,
'binding to an app should not create a sandbox app',
);
const worker = await t.puter.workers.get(name);
t.assert.equal(worker?.app_uid, hostApp.uid, 'bound worker app_uid should match host app uid');
},
'create with an unknown app name rejects with app_not_found': async (t) => {