@studio-foundation/api 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +52 -0
- package/LICENSE +663 -0
- package/README.md +161 -0
- package/dist/api.d.ts +8 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +6 -0
- package/dist/api.js.map +1 -0
- package/dist/bootstrap.d.ts +66 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +193 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/event-bus.d.ts +16 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +37 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/integration-runtime.d.ts +22 -0
- package/dist/integration-runtime.d.ts.map +1 -0
- package/dist/integration-runtime.js +150 -0
- package/dist/integration-runtime.js.map +1 -0
- package/dist/integration-store.d.ts +26 -0
- package/dist/integration-store.d.ts.map +1 -0
- package/dist/integration-store.js +80 -0
- package/dist/integration-store.js.map +1 -0
- package/dist/integrations/linear/failure-handler.d.ts +5 -0
- package/dist/integrations/linear/failure-handler.d.ts.map +1 -0
- package/dist/integrations/linear/failure-handler.js +73 -0
- package/dist/integrations/linear/failure-handler.js.map +1 -0
- package/dist/integrations/linear/webhook-handler.d.ts +6 -0
- package/dist/integrations/linear/webhook-handler.d.ts.map +1 -0
- package/dist/integrations/linear/webhook-handler.js +131 -0
- package/dist/integrations/linear/webhook-handler.js.map +1 -0
- package/dist/integrations/registry.d.ts +4 -0
- package/dist/integrations/registry.d.ts.map +1 -0
- package/dist/integrations/registry.js +9 -0
- package/dist/integrations/registry.js.map +1 -0
- package/dist/integrations/types.d.ts +33 -0
- package/dist/integrations/types.d.ts.map +1 -0
- package/dist/integrations/types.js +2 -0
- package/dist/integrations/types.js.map +1 -0
- package/dist/launcher.d.ts +45 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +149 -0
- package/dist/launcher.js.map +1 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +37 -0
- package/dist/logger.js.map +1 -0
- package/dist/plans.d.ts +13 -0
- package/dist/plans.d.ts.map +1 -0
- package/dist/plans.js +33 -0
- package/dist/plans.js.map +1 -0
- package/dist/routes/agents.d.ts +6 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +118 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/config.d.ts +6 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +129 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/contracts.d.ts +6 -0
- package/dist/routes/contracts.d.ts.map +1 -0
- package/dist/routes/contracts.js +188 -0
- package/dist/routes/contracts.js.map +1 -0
- package/dist/routes/pipelines.d.ts +6 -0
- package/dist/routes/pipelines.d.ts.map +1 -0
- package/dist/routes/pipelines.js +133 -0
- package/dist/routes/pipelines.js.map +1 -0
- package/dist/routes/projects.d.ts +6 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +220 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/runs.d.ts +6 -0
- package/dist/routes/runs.d.ts.map +1 -0
- package/dist/routes/runs.js +427 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +123 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/tools.d.ts +6 -0
- package/dist/routes/tools.d.ts.map +1 -0
- package/dist/routes/tools.js +193 -0
- package/dist/routes/tools.js.map +1 -0
- package/dist/routes/users.d.ts +6 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +152 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/routes/validate.d.ts +6 -0
- package/dist/routes/validate.d.ts.map +1 -0
- package/dist/routes/validate.js +171 -0
- package/dist/routes/validate.js.map +1 -0
- package/dist/routes/webhooks.d.ts +8 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +102 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +101 -0
- package/dist/server.js.map +1 -0
- package/dist/spawners/http-api-spawner.d.ts +10 -0
- package/dist/spawners/http-api-spawner.d.ts.map +1 -0
- package/dist/spawners/http-api-spawner.js +88 -0
- package/dist/spawners/http-api-spawner.js.map +1 -0
- package/dist/user-store-pg.d.ts +21 -0
- package/dist/user-store-pg.d.ts.map +1 -0
- package/dist/user-store-pg.js +97 -0
- package/dist/user-store-pg.js.map +1 -0
- package/dist/user-store.d.ts +29 -0
- package/dist/user-store.d.ts.map +1 -0
- package/dist/user-store.js +97 -0
- package/dist/user-store.js.map +1 -0
- package/dist/utils/repo-resolver.d.ts +3 -0
- package/dist/utils/repo-resolver.d.ts.map +1 -0
- package/dist/utils/repo-resolver.js +3 -0
- package/dist/utils/repo-resolver.js.map +1 -0
- package/dist/webhook-dispatcher.d.ts +13 -0
- package/dist/webhook-dispatcher.d.ts.map +1 -0
- package/dist/webhook-dispatcher.js +108 -0
- package/dist/webhook-dispatcher.js.map +1 -0
- package/dist/webhook-store.d.ts +31 -0
- package/dist/webhook-store.d.ts.map +1 -0
- package/dist/webhook-store.js +91 -0
- package/dist/webhook-store.js.map +1 -0
- package/package.json +59 -0
- package/src/.gitkeep +0 -0
- package/src/api.ts +8 -0
- package/src/bootstrap.ts +259 -0
- package/src/event-bus.ts +64 -0
- package/src/index.ts +58 -0
- package/src/integration-runtime.ts +180 -0
- package/src/integration-store.ts +125 -0
- package/src/integrations/linear/failure-handler.ts +93 -0
- package/src/integrations/linear/webhook-handler.ts +156 -0
- package/src/integrations/registry.ts +12 -0
- package/src/integrations/types.ts +37 -0
- package/src/launcher.ts +214 -0
- package/src/logger.ts +50 -0
- package/src/plans.ts +43 -0
- package/src/routes/agents.ts +131 -0
- package/src/routes/config.ts +154 -0
- package/src/routes/contracts.ts +205 -0
- package/src/routes/pipelines.ts +146 -0
- package/src/routes/projects.ts +237 -0
- package/src/routes/runs.ts +468 -0
- package/src/routes/skills.ts +136 -0
- package/src/routes/tools.ts +222 -0
- package/src/routes/users.ts +169 -0
- package/src/routes/validate.ts +196 -0
- package/src/routes/webhooks.ts +120 -0
- package/src/server.ts +155 -0
- package/src/spawners/http-api-spawner.ts +96 -0
- package/src/user-store-pg.ts +138 -0
- package/src/user-store.ts +125 -0
- package/src/utils/repo-resolver.ts +3 -0
- package/src/webhook-dispatcher.ts +142 -0
- package/src/webhook-store.ts +141 -0
- package/tests/agents.test.ts +164 -0
- package/tests/cancel.test.ts +120 -0
- package/tests/config.test.ts +196 -0
- package/tests/contracts.test.ts +302 -0
- package/tests/event-bus.test.ts +53 -0
- package/tests/http-api-spawner.test.ts +158 -0
- package/tests/integration-runtime.test.ts +199 -0
- package/tests/integration-store.test.ts +66 -0
- package/tests/integrations/linear/failure-handler.test.ts +113 -0
- package/tests/integrations/linear/webhook-handler.test.ts +191 -0
- package/tests/launcher.test.ts +380 -0
- package/tests/linear-webhook.test.ts +390 -0
- package/tests/pipelines.test.ts +166 -0
- package/tests/projects.test.ts +283 -0
- package/tests/runs.test.ts +379 -0
- package/tests/server.test.ts +208 -0
- package/tests/skills.test.ts +149 -0
- package/tests/sse.test.ts +129 -0
- package/tests/tools.test.ts +233 -0
- package/tests/user-store.test.ts +93 -0
- package/tests/users.test.ts +189 -0
- package/tests/utils/repo-resolver.test.ts +105 -0
- package/tests/validate.test.ts +233 -0
- package/tests/webhook-dispatcher.test.ts +214 -0
- package/tests/webhook-store.test.ts +98 -0
- package/tests/webhooks.test.ts +176 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { mkdirSync } from 'node:fs';
|
|
4
|
+
import { buildServer } from '../src/server.js';
|
|
5
|
+
import { InMemoryRunStore } from '@studio-foundation/engine';
|
|
6
|
+
import { WebhookStore } from '../src/webhook-store.js';
|
|
7
|
+
import type { IntegrationRuntime } from '../src/integration-runtime.js';
|
|
8
|
+
import type { IntegrationStore } from '../src/integration-store.js';
|
|
9
|
+
|
|
10
|
+
const nullIntegrationRuntime = { registerRoutes: () => {} } as unknown as IntegrationRuntime;
|
|
11
|
+
const nullIntegrationStore = {} as unknown as IntegrationStore;
|
|
12
|
+
|
|
13
|
+
function makeServer() {
|
|
14
|
+
const dir = resolve('/tmp', `.studio-webhooks-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
const webhookStore = new WebhookStore(resolve(dir, 'runs.db'));
|
|
17
|
+
|
|
18
|
+
const server = buildServer({
|
|
19
|
+
store: new InMemoryRunStore(),
|
|
20
|
+
launcher: {
|
|
21
|
+
launch: async () => ({ run_id: 'test' }),
|
|
22
|
+
cancel: async () => {},
|
|
23
|
+
subscribe: () => () => {},
|
|
24
|
+
},
|
|
25
|
+
configsDir: dir,
|
|
26
|
+
projectName: 'test-project',
|
|
27
|
+
apiConfig: {},
|
|
28
|
+
studioVersion: '0.0.0',
|
|
29
|
+
maskedConfig: { providers: [] },
|
|
30
|
+
webhookStore,
|
|
31
|
+
integrationRuntime: nullIntegrationRuntime,
|
|
32
|
+
integrationStore: nullIntegrationStore,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return { server, webhookStore, cleanup: () => webhookStore.close() };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('POST /api/webhooks', () => {
|
|
39
|
+
let server: ReturnType<typeof makeServer>['server'];
|
|
40
|
+
let cleanup: () => void;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
({ server, cleanup } = makeServer());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
cleanup();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('registers a webhook and returns 201 with id', async () => {
|
|
51
|
+
const res = await server.inject({
|
|
52
|
+
method: 'POST',
|
|
53
|
+
url: '/api/webhooks',
|
|
54
|
+
payload: {
|
|
55
|
+
url: 'https://example.com/hook',
|
|
56
|
+
events: ['pipeline_complete', 'stage_failed'],
|
|
57
|
+
secret: 'mysecret',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
expect(res.statusCode).toBe(201);
|
|
61
|
+
const body = res.json<{ id: string; url: string; events: string[]; status: string }>();
|
|
62
|
+
expect(body.id).toBeDefined();
|
|
63
|
+
expect(body.url).toBe('https://example.com/hook');
|
|
64
|
+
expect(body.events).toEqual(['pipeline_complete', 'stage_failed']);
|
|
65
|
+
expect(body.status).toBe('active');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('registers a webhook without a secret', async () => {
|
|
69
|
+
const res = await server.inject({
|
|
70
|
+
method: 'POST',
|
|
71
|
+
url: '/api/webhooks',
|
|
72
|
+
payload: {
|
|
73
|
+
url: 'https://example.com/hook',
|
|
74
|
+
events: ['pipeline_complete'],
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
expect(res.statusCode).toBe(201);
|
|
78
|
+
const body = res.json<{ id: string }>();
|
|
79
|
+
expect(body.id).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('returns 400 when url is missing', async () => {
|
|
83
|
+
const res = await server.inject({
|
|
84
|
+
method: 'POST',
|
|
85
|
+
url: '/api/webhooks',
|
|
86
|
+
payload: { events: ['pipeline_complete'] },
|
|
87
|
+
});
|
|
88
|
+
expect(res.statusCode).toBe(400);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('returns 400 when events is missing', async () => {
|
|
92
|
+
const res = await server.inject({
|
|
93
|
+
method: 'POST',
|
|
94
|
+
url: '/api/webhooks',
|
|
95
|
+
payload: { url: 'https://example.com/hook' },
|
|
96
|
+
});
|
|
97
|
+
expect(res.statusCode).toBe(400);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('returns 400 when events is empty', async () => {
|
|
101
|
+
const res = await server.inject({
|
|
102
|
+
method: 'POST',
|
|
103
|
+
url: '/api/webhooks',
|
|
104
|
+
payload: { url: 'https://example.com/hook', events: [] },
|
|
105
|
+
});
|
|
106
|
+
expect(res.statusCode).toBe(400);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('GET /api/webhooks', () => {
|
|
111
|
+
let server: ReturnType<typeof makeServer>['server'];
|
|
112
|
+
let webhookStore: WebhookStore;
|
|
113
|
+
let cleanup: () => void;
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
({ server, webhookStore, cleanup } = makeServer());
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
cleanup();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('returns empty list when no webhooks registered', async () => {
|
|
124
|
+
const res = await server.inject({ method: 'GET', url: '/api/webhooks' });
|
|
125
|
+
expect(res.statusCode).toBe(200);
|
|
126
|
+
const body = res.json<{ webhooks: unknown[] }>();
|
|
127
|
+
expect(body.webhooks).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('returns list of registered webhooks', async () => {
|
|
131
|
+
webhookStore.saveWebhook({ id: 'wh-1', url: 'https://a.com/hook', events: ['pipeline_complete'], status: 'active', created_at: '2026-01-01T00:00:00.000Z' });
|
|
132
|
+
webhookStore.saveWebhook({ id: 'wh-2', url: 'https://b.com/hook', events: ['stage_failed'], status: 'active', created_at: '2026-01-01T00:00:00.000Z' });
|
|
133
|
+
|
|
134
|
+
const res = await server.inject({ method: 'GET', url: '/api/webhooks' });
|
|
135
|
+
expect(res.statusCode).toBe(200);
|
|
136
|
+
const body = res.json<{ webhooks: Array<{ id: string }> }>();
|
|
137
|
+
expect(body.webhooks).toHaveLength(2);
|
|
138
|
+
expect(body.webhooks.map(w => w.id)).toContain('wh-1');
|
|
139
|
+
expect(body.webhooks.map(w => w.id)).toContain('wh-2');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('does not expose secret in response', async () => {
|
|
143
|
+
webhookStore.saveWebhook({ id: 'wh-1', url: 'https://a.com/hook', events: ['pipeline_complete'], secret: 'topsecret', status: 'active', created_at: '2026-01-01T00:00:00.000Z' });
|
|
144
|
+
|
|
145
|
+
const res = await server.inject({ method: 'GET', url: '/api/webhooks' });
|
|
146
|
+
const body = res.json<{ webhooks: Array<Record<string, unknown>> }>();
|
|
147
|
+
expect(body.webhooks[0]['secret']).toBeUndefined();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('DELETE /api/webhooks/:id', () => {
|
|
152
|
+
let server: ReturnType<typeof makeServer>['server'];
|
|
153
|
+
let webhookStore: WebhookStore;
|
|
154
|
+
let cleanup: () => void;
|
|
155
|
+
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
({ server, webhookStore, cleanup } = makeServer());
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
afterEach(() => {
|
|
161
|
+
cleanup();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('deletes an existing webhook and returns 204', async () => {
|
|
165
|
+
webhookStore.saveWebhook({ id: 'wh-1', url: 'https://a.com/hook', events: ['pipeline_complete'], status: 'active', created_at: '2026-01-01T00:00:00.000Z' });
|
|
166
|
+
|
|
167
|
+
const res = await server.inject({ method: 'DELETE', url: '/api/webhooks/wh-1' });
|
|
168
|
+
expect(res.statusCode).toBe(204);
|
|
169
|
+
expect(webhookStore.getWebhook('wh-1')).toBeNull();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('returns 404 for unknown webhook id', async () => {
|
|
173
|
+
const res = await server.inject({ method: 'DELETE', url: '/api/webhooks/nonexistent' });
|
|
174
|
+
expect(res.statusCode).toBe(404);
|
|
175
|
+
});
|
|
176
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src/**/*"],
|
|
23
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
24
|
+
}
|