@critical-path/server 0.14.0 → 0.16.0

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.
@@ -0,0 +1,30 @@
1
+ type Method = 'get' | 'post' | 'patch' | 'delete';
2
+ interface RouteDoc {
3
+ method: Method;
4
+ path: string;
5
+ summary: string;
6
+ tag: string;
7
+ /** Query parameters; names ending in `!` are required. */
8
+ query?: string[];
9
+ body?: {
10
+ safeParse(data: unknown): unknown;
11
+ };
12
+ /** Key of the JSON response envelope, e.g. `task` for `{ "task": { ... } }`. */
13
+ responseKey?: string;
14
+ status?: number;
15
+ }
16
+ /** Every route served by `CriticalPathRouter`. Kept in sync by the route drift test. */
17
+ export declare const ROUTES: RouteDoc[];
18
+ export interface OpenApiOptions {
19
+ /** Public URL the API is served from, e.g. `https://app.example.com/api/critical-path`. */
20
+ serverUrl?: string;
21
+ title?: string;
22
+ version?: string;
23
+ }
24
+ /**
25
+ * Builds an OpenAPI 3.1 document for the router. Request bodies are generated from the same zod
26
+ * schemas the router validates with, so the contract cannot drift from enforcement.
27
+ */
28
+ export declare function buildOpenApiDocument(options?: OpenApiOptions): Record<string, unknown>;
29
+ export {};
30
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAGA,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;KAAE,CAAC;IAC7C,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wFAAwF;AACxF,eAAO,MAAM,MAAM,EAAE,QAAQ,EAgF5B,CAAC;AAuBF,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4C1F"}
@@ -0,0 +1,139 @@
1
+ import { zodToJsonSchema } from 'zod-to-json-schema';
2
+ import * as schemas from '@critical-path/core/schemas';
3
+ /** Every route served by `CriticalPathRouter`. Kept in sync by the route drift test. */
4
+ export const ROUTES = [
5
+ { method: 'get', path: '/workflows', summary: 'List workflows', tag: 'Workflows', responseKey: 'workflows' },
6
+ { method: 'post', path: '/workflows', summary: 'Create a workflow', tag: 'Workflows', body: schemas.CreateWorkflowSchema, responseKey: 'workflow', status: 201 },
7
+ { method: 'get', path: '/workflows/{workflowId}', summary: 'Get a workflow', tag: 'Workflows', responseKey: 'workflow' },
8
+ { method: 'patch', path: '/workflows/{workflowId}', summary: 'Update a workflow', tag: 'Workflows', body: schemas.UpdateWorkflowSchema, responseKey: 'workflow' },
9
+ { method: 'delete', path: '/workflows/{workflowId}', summary: 'Delete a workflow', tag: 'Workflows' },
10
+ { method: 'get', path: '/projects', summary: 'List projects', tag: 'Projects', responseKey: 'projects' },
11
+ { method: 'post', path: '/projects', summary: 'Create a project', tag: 'Projects', body: schemas.CreateProjectSchema, responseKey: 'project', status: 201 },
12
+ { method: 'get', path: '/projects/{projectId}', summary: 'Get a project', tag: 'Projects', responseKey: 'project' },
13
+ { method: 'patch', path: '/projects/{projectId}', summary: 'Update a project', tag: 'Projects', body: schemas.UpdateProjectSchema, responseKey: 'project' },
14
+ { method: 'delete', path: '/projects/{projectId}', summary: 'Delete a project and its tasks', tag: 'Projects' },
15
+ { method: 'get', path: '/projects/{projectId}/critical-path', summary: 'Critical path (CPM) analysis', tag: 'Analysis', responseKey: 'analysis' },
16
+ { method: 'get', path: '/projects/{projectId}/ladder', summary: 'Timeline ladder of abstraction', tag: 'Analysis', query: ['level', 'containerId', 'iterationId'], responseKey: 'ladder' },
17
+ { method: 'get', path: '/projects/{projectId}/workload', summary: 'Project workload distribution', tag: 'Analysis', query: ['startDate', 'endDate', 'interval', 'groupBy', 'metric', 'defaultWeeklyCapacityHours'], responseKey: 'workload' },
18
+ { method: 'get', path: '/tasks', summary: 'List tasks', tag: 'Tasks', query: ['projectId'], responseKey: 'tasks' },
19
+ { method: 'post', path: '/tasks', summary: 'Create a task', tag: 'Tasks', body: schemas.CreateTaskSchema, responseKey: 'task', status: 201 },
20
+ { method: 'get', path: '/tasks/{taskId}', summary: 'Get a task', tag: 'Tasks', responseKey: 'task' },
21
+ { method: 'patch', path: '/tasks/{taskId}', summary: 'Update a task (enforces workflow transitions)', tag: 'Tasks', body: schemas.UpdateTaskSchema, responseKey: 'task' },
22
+ { method: 'delete', path: '/tasks/{taskId}', summary: 'Delete a task', tag: 'Tasks' },
23
+ { method: 'get', path: '/tasks/{taskId}/comments', summary: 'List task comments', tag: 'Comments', responseKey: 'comments' },
24
+ { method: 'post', path: '/tasks/{taskId}/comments', summary: 'Comment on a task', tag: 'Comments', body: schemas.CreateCommentSchema.omit({ taskId: true }), responseKey: 'comment', status: 201 },
25
+ { method: 'get', path: '/tasks/{taskId}/attachments', summary: 'List task attachments', tag: 'Attachments', responseKey: 'attachments' },
26
+ { method: 'post', path: '/tasks/{taskId}/attachments', summary: 'Attach a file link to a task', tag: 'Attachments', body: schemas.CreateAttachmentSchema.omit({ taskId: true }), responseKey: 'attachment', status: 201 },
27
+ { method: 'get', path: '/tasks/{taskId}/dependencies', summary: 'Task dependency graph', tag: 'Dependencies', responseKey: 'graph' },
28
+ { method: 'post', path: '/tasks/{taskId}/dependencies', summary: 'Add a dependency (409 on cycles)', tag: 'Dependencies', body: schemas.CreateDependencySchema, responseKey: 'dependency', status: 201 },
29
+ { method: 'get', path: '/tasks/{taskId}/lifecycle', summary: 'Derived lifecycle state', tag: 'Tasks', responseKey: 'state' },
30
+ { method: 'get', path: '/tasks/{taskId}/transitions', summary: 'Allowed status transitions', tag: 'Tasks' },
31
+ { method: 'get', path: '/tasks/{taskId}/ladder', summary: 'Single-task ladder view', tag: 'Analysis', responseKey: 'taskLadder' },
32
+ { method: 'get', path: '/tasks/{taskId}/metrics', summary: 'Task metrics and EVM', tag: 'Analysis', responseKey: 'metrics' },
33
+ { method: 'get', path: '/tasks/{taskId}/progress-history', summary: 'Reconstructed progress history', tag: 'Analysis', responseKey: 'progressHistory' },
34
+ { method: 'get', path: '/teams', summary: 'List teams', tag: 'Teams', responseKey: 'teams' },
35
+ { method: 'post', path: '/teams', summary: 'Create a team', tag: 'Teams', body: schemas.CreateTeamSchema, responseKey: 'team', status: 201 },
36
+ { method: 'get', path: '/teams/{teamId}', summary: 'Get a team', tag: 'Teams', responseKey: 'team' },
37
+ { method: 'patch', path: '/teams/{teamId}', summary: 'Update a team', tag: 'Teams', body: schemas.UpdateTeamSchema, responseKey: 'team' },
38
+ { method: 'delete', path: '/teams/{teamId}', summary: 'Delete a team', tag: 'Teams' },
39
+ { method: 'get', path: '/containers', summary: 'List containers', tag: 'Containers', query: ['projectId!'], responseKey: 'containers' },
40
+ { method: 'post', path: '/containers', summary: 'Create a container', tag: 'Containers', body: schemas.CreateContainerSchema, responseKey: 'container', status: 201 },
41
+ { method: 'get', path: '/containers/{containerId}', summary: 'Get a container', tag: 'Containers', responseKey: 'container' },
42
+ { method: 'patch', path: '/containers/{containerId}', summary: 'Update a container', tag: 'Containers', body: schemas.UpdateContainerSchema, responseKey: 'container' },
43
+ { method: 'delete', path: '/containers/{containerId}', summary: 'Delete a container', tag: 'Containers' },
44
+ { method: 'get', path: '/deliverables', summary: 'List deliverables', tag: 'Deliverables', query: ['projectId!'], responseKey: 'deliverables' },
45
+ { method: 'post', path: '/deliverables', summary: 'Create a deliverable', tag: 'Deliverables', body: schemas.CreateDeliverableSchema, responseKey: 'deliverable', status: 201 },
46
+ { method: 'get', path: '/deliverables/{deliverableId}', summary: 'Get a deliverable', tag: 'Deliverables', responseKey: 'deliverable' },
47
+ { method: 'get', path: '/deliverables/{deliverableId}/summary', summary: 'Deliverable rollup summary', tag: 'Deliverables', responseKey: 'summary' },
48
+ { method: 'patch', path: '/deliverables/{deliverableId}', summary: 'Update a deliverable', tag: 'Deliverables', body: schemas.UpdateDeliverableSchema, responseKey: 'deliverable' },
49
+ { method: 'delete', path: '/deliverables/{deliverableId}', summary: 'Delete a deliverable', tag: 'Deliverables' },
50
+ { method: 'get', path: '/iterations', summary: 'List iterations', tag: 'Iterations', query: ['projectId!'], responseKey: 'iterations' },
51
+ { method: 'post', path: '/iterations', summary: 'Create an iteration', tag: 'Iterations', body: schemas.CreateIterationSchema, responseKey: 'iteration', status: 201 },
52
+ { method: 'get', path: '/iterations/{iterationId}', summary: 'Get an iteration', tag: 'Iterations', responseKey: 'iteration' },
53
+ { method: 'patch', path: '/iterations/{iterationId}', summary: 'Update an iteration', tag: 'Iterations', body: schemas.UpdateIterationSchema, responseKey: 'iteration' },
54
+ { method: 'delete', path: '/iterations/{iterationId}', summary: 'Delete an iteration', tag: 'Iterations' },
55
+ { method: 'get', path: '/activities', summary: 'Activity (audit) stream', tag: 'Activity', query: ['projectId', 'taskId'], responseKey: 'activities' },
56
+ { method: 'get', path: '/comments', summary: 'List comments for a task', tag: 'Comments', query: ['taskId!'], responseKey: 'comments' },
57
+ { method: 'post', path: '/comments', summary: 'Create a comment', tag: 'Comments', body: schemas.CreateCommentSchema, responseKey: 'comment', status: 201 },
58
+ { method: 'get', path: '/comments/{commentId}', summary: 'Get a comment', tag: 'Comments', responseKey: 'comment' },
59
+ { method: 'patch', path: '/comments/{commentId}', summary: 'Update a comment', tag: 'Comments', body: schemas.UpdateCommentSchema, responseKey: 'comment' },
60
+ { method: 'delete', path: '/comments/{commentId}', summary: 'Delete a comment', tag: 'Comments' },
61
+ { method: 'post', path: '/comments/{commentId}/reactions', summary: 'Add a reaction', tag: 'Comments', body: schemas.CommentReactionSchema, responseKey: 'comment' },
62
+ { method: 'delete', path: '/comments/{commentId}/reactions', summary: 'Remove a reaction', tag: 'Comments', query: ['emoji', 'userId'], responseKey: 'comment' },
63
+ { method: 'get', path: '/attachments', summary: 'List attachments', tag: 'Attachments', query: ['taskId', 'projectId', 'commentId'], responseKey: 'attachments' },
64
+ { method: 'post', path: '/attachments', summary: 'Register an attachment', tag: 'Attachments', body: schemas.CreateAttachmentSchema, responseKey: 'attachment', status: 201 },
65
+ { method: 'post', path: '/attachments/upload', summary: 'Upload a file through the configured FileStorageAdapter', tag: 'Attachments', body: schemas.UploadAttachmentSchema, responseKey: 'attachment', status: 201 },
66
+ { method: 'post', path: '/attachments/presign', summary: 'Request a presigned upload URL', tag: 'Attachments', body: schemas.PresignAttachmentSchema, responseKey: 'presigned' },
67
+ { method: 'get', path: '/attachments/{attachmentId}', summary: 'Get an attachment', tag: 'Attachments', responseKey: 'attachment' },
68
+ { method: 'delete', path: '/attachments/{attachmentId}', summary: 'Delete an attachment', tag: 'Attachments' },
69
+ { method: 'get', path: '/time-entries', summary: 'List time entries for a task', tag: 'Time Tracking', query: ['taskId!'], responseKey: 'timeEntries' },
70
+ { method: 'post', path: '/time-entries', summary: 'Log time against a task', tag: 'Time Tracking', body: schemas.LogTimeSchema, responseKey: 'timeEntry', status: 201 },
71
+ { method: 'get', path: '/workload', summary: 'Workload distribution across projects', tag: 'Analysis', query: ['projectId', 'startDate', 'endDate', 'interval', 'groupBy', 'metric', 'defaultWeeklyCapacityHours'], responseKey: 'workload' },
72
+ { method: 'post', path: '/status', summary: 'Publish an agent status update', tag: 'Agents' }
73
+ ];
74
+ const errorResponse = {
75
+ description: 'Error',
76
+ content: {
77
+ 'application/json': {
78
+ schema: {
79
+ type: 'object',
80
+ properties: {
81
+ error: { type: 'string' },
82
+ issues: { type: 'array', items: { type: 'object', properties: { path: { type: 'string' }, message: { type: 'string' } } } }
83
+ },
84
+ required: ['error']
85
+ }
86
+ }
87
+ }
88
+ };
89
+ function toJsonSchema(schema) {
90
+ const { $schema: _ignored, ...json } = zodToJsonSchema(schema, { $refStrategy: 'none' });
91
+ return json;
92
+ }
93
+ /**
94
+ * Builds an OpenAPI 3.1 document for the router. Request bodies are generated from the same zod
95
+ * schemas the router validates with, so the contract cannot drift from enforcement.
96
+ */
97
+ export function buildOpenApiDocument(options = {}) {
98
+ const paths = {};
99
+ for (const route of ROUTES) {
100
+ const pathParams = [...route.path.matchAll(/\{(\w+)\}/g)].map((m) => m[1]);
101
+ const parameters = [
102
+ ...pathParams.map((name) => ({ name, in: 'path', required: true, schema: { type: 'string' } })),
103
+ ...(route.query ?? []).map((q) => ({
104
+ name: q.replace(/!$/, ''),
105
+ in: 'query',
106
+ required: q.endsWith('!'),
107
+ schema: { type: 'string' }
108
+ }))
109
+ ];
110
+ const status = String(route.status ?? 200);
111
+ const successSchema = route.responseKey
112
+ ? { type: 'object', properties: { [route.responseKey]: {} }, required: [route.responseKey] }
113
+ : { type: 'object' };
114
+ paths[route.path] ??= {};
115
+ paths[route.path][route.method] = {
116
+ summary: route.summary,
117
+ tags: [route.tag],
118
+ ...(parameters.length ? { parameters } : {}),
119
+ ...(route.body
120
+ ? { requestBody: { required: true, content: { 'application/json': { schema: toJsonSchema(route.body) } } } }
121
+ : {}),
122
+ responses: {
123
+ [status]: { description: 'Success', content: { 'application/json': { schema: successSchema } } },
124
+ '400': errorResponse,
125
+ '401': errorResponse,
126
+ '404': errorResponse,
127
+ ...(route.path.endsWith('/dependencies') && route.method === 'post' ? { '409': errorResponse } : {}),
128
+ '500': errorResponse
129
+ }
130
+ };
131
+ }
132
+ return {
133
+ openapi: '3.1.0',
134
+ info: { title: options.title ?? 'Critical Path API', version: options.version ?? '1.0.0' },
135
+ ...(options.serverUrl ? { servers: [{ url: options.serverUrl }] } : {}),
136
+ paths
137
+ };
138
+ }
139
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=openapi.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.test.d.ts","sourceRoot":"","sources":["../src/openapi.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { CriticalPathRouter } from './router.js';
3
+ import { ROUTES, buildOpenApiDocument } from './openapi.js';
4
+ const base = 'http://localhost:3000/api/critical-path';
5
+ describe('OpenAPI document', () => {
6
+ it('documents every route the router serves, with matching response envelopes', async () => {
7
+ const router = new CriticalPathRouter();
8
+ const engine = router.engine;
9
+ const project = await engine.createProject({ key: 'OAS', name: 'OpenAPI' });
10
+ const task = await engine.createTask({ projectId: project.id, title: 'Task' });
11
+ const workflow = await engine.createWorkflow({ name: 'WF', statuses: [], transitions: [] });
12
+ const team = await engine.createTeam({ name: 'Team', memberIds: [] });
13
+ const container = await engine.createContainer({ projectId: project.id, name: 'Epic' });
14
+ const deliverable = await engine.createDeliverable({ projectId: project.id, title: 'Cut' });
15
+ const iteration = await engine.createIteration({ projectId: project.id, name: 'S1', status: 'planning' });
16
+ const comment = await engine.addComment({ taskId: task.id, content: 'hi', authorId: 'u1' });
17
+ const attachment = await engine.createAttachment({
18
+ taskId: task.id,
19
+ uploaderId: 'u1',
20
+ filename: 'a.md',
21
+ mimeType: 'text/markdown',
22
+ sizeBytes: 1,
23
+ url: 'https://example.com/a.md'
24
+ });
25
+ const ids = {
26
+ projectId: project.id,
27
+ taskId: task.id,
28
+ workflowId: workflow.id,
29
+ teamId: team.id,
30
+ containerId: container.id,
31
+ deliverableId: deliverable.id,
32
+ iterationId: iteration.id,
33
+ commentId: comment.id,
34
+ attachmentId: attachment.id
35
+ };
36
+ const query = { projectId: project.id, taskId: task.id, emoji: '👍', userId: 'u1' };
37
+ // Deletes run last so earlier routes still have data; the project goes last of all.
38
+ const ordered = [...ROUTES].sort((a, b) => {
39
+ const rank = (r) => (r.method === 'delete' ? (r.path === '/projects/{projectId}' ? 2 : 1) : 0);
40
+ return rank(a) - rank(b);
41
+ });
42
+ for (const route of ordered) {
43
+ const path = route.path.replace(/\{(\w+)\}/g, (_, name) => ids[name]);
44
+ const params = new URLSearchParams();
45
+ for (const q of route.query ?? []) {
46
+ const name = q.replace(/!$/, '');
47
+ if (query[name])
48
+ params.set(name, query[name]);
49
+ }
50
+ const res = await router.handleRequest(new Request(`${base}${path}${params.size ? `?${params}` : ''}`, {
51
+ method: route.method.toUpperCase(),
52
+ headers: { 'Content-Type': 'application/json' },
53
+ ...(route.method === 'post' || route.method === 'patch' ? { body: '{}' } : {})
54
+ }));
55
+ const body = await res.json();
56
+ const label = `${route.method.toUpperCase()} ${route.path}`;
57
+ expect(String(body.error ?? ''), label).not.toMatch(/^Route not found/);
58
+ if (res.status < 300 && route.responseKey) {
59
+ expect(body, label).toHaveProperty(route.responseKey);
60
+ }
61
+ }
62
+ });
63
+ it('serves the document at /openapi.json with request bodies from the validation schemas', async () => {
64
+ const router = new CriticalPathRouter();
65
+ const res = await router.handleRequest(new Request(`${base}/openapi.json`));
66
+ expect(res.status).toBe(200);
67
+ const doc = await res.json();
68
+ expect(doc.openapi).toBe('3.1.0');
69
+ expect(doc.servers[0].url).toBe('http://localhost:3000/api/critical-path');
70
+ const createTask = doc.paths['/tasks'].post.requestBody.content['application/json'].schema;
71
+ expect(createTask.required).toEqual(expect.arrayContaining(['projectId', 'title']));
72
+ expect(createTask.properties).not.toHaveProperty('id');
73
+ expect(doc.paths['/tasks/{taskId}/dependencies'].post.responses).toHaveProperty('409');
74
+ });
75
+ it('respects requireAuth', async () => {
76
+ const router = new CriticalPathRouter(undefined, { requireAuth: true });
77
+ const res = await router.handleRequest(new Request(`${base}/openapi.json`));
78
+ expect(res.status).toBe(401);
79
+ });
80
+ it('can be built without a router for static publishing', () => {
81
+ const doc = buildOpenApiDocument({ serverUrl: 'https://api.example.com/pm', version: '2.0.0' });
82
+ expect(doc.info.version).toBe('2.0.0');
83
+ expect(Object.keys(doc.paths).length).toBeGreaterThan(30);
84
+ });
85
+ });
86
+ //# sourceMappingURL=openapi.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.test.js","sourceRoot":"","sources":["../src/openapi.test.ts"],"names":[],"mappings":"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"}
package/dist/router.d.ts CHANGED
@@ -1,4 +1,28 @@
1
- import { CriticalPathEngine, type CriticalPathConfig } from '@critical-path/core';
1
+ import { CriticalPathEngine, type AuthorType, type CriticalPathConfig } from '@critical-path/core';
2
+ /**
3
+ * Per-request identity resolved by `getContext`. `userId` becomes the actor every mutation in
4
+ * the request is attributed to. Extra fields (e.g. `tenantId`, `roles`) are carried for
5
+ * future authorization hooks.
6
+ */
7
+ export interface RequestContext {
8
+ userId?: string;
9
+ userName?: string;
10
+ actorType?: AuthorType;
11
+ [key: string]: unknown;
12
+ }
13
+ export interface RequestInitOptions {
14
+ getContext?: CriticalPathRouterOptions['getContext'];
15
+ }
16
+ export interface CorsOptions {
17
+ /** Allowed origins. `'*'` allows any origin (and cannot be combined with `credentials`). */
18
+ origins: '*' | string[];
19
+ /** Send `Access-Control-Allow-Credentials: true` for allowed origins. */
20
+ credentials?: boolean;
21
+ /** Request headers browsers may send. Defaults to `Content-Type` and `Authorization`. */
22
+ allowHeaders?: string[];
23
+ /** Seconds browsers may cache preflight results. */
24
+ maxAge?: number;
25
+ }
2
26
  export interface CriticalPathRouterOptions {
3
27
  /**
4
28
  * Called for unexpected errors (those that would produce a 500), e.g. to report to Sentry.
@@ -11,13 +35,53 @@ export interface CriticalPathRouterOptions {
11
35
  * `NODE_ENV === 'development'`, so production deployments never leak internals.
12
36
  */
13
37
  exposeErrors?: boolean;
38
+ /**
39
+ * Resolves the caller's identity from the request (session cookie, bearer token, etc.).
40
+ * When it returns a `userId`, all mutations in the request are attributed to that user and
41
+ * identity fields in request bodies (`actorId`, `authorId`, `userId`, ...) are ignored.
42
+ */
43
+ getContext?: (request: Request) => RequestContext | null | undefined | Promise<RequestContext | null | undefined>;
44
+ /** Reject requests with `401` unless `getContext` returns a `userId`. Defaults to `false`. */
45
+ requireAuth?: boolean;
46
+ /**
47
+ * The path the router is mounted at, e.g. `/api/pm`. Requests outside it return `404`.
48
+ * When omitted, everything up to the first `/critical-path` segment is stripped.
49
+ */
50
+ basePath?: string;
51
+ /** CORS policy. Defaults to `{ origins: '*' }`; pass `false` to send no CORS headers. */
52
+ cors?: CorsOptions | false;
14
53
  }
15
54
  export declare class CriticalPathRouter {
16
55
  engine: CriticalPathEngine;
17
56
  private readonly onError?;
18
57
  private readonly exposeErrors;
58
+ private readonly getContext?;
59
+ private readonly requireAuth;
60
+ private readonly basePath?;
61
+ private readonly cors;
19
62
  constructor(configOrEngine?: CriticalPathConfig | CriticalPathEngine, options?: CriticalPathRouterOptions);
20
- handleRequest(request: Request): Promise<Response>;
63
+ /**
64
+ * Handles a request. Adapters that resolve identity from something other than the `Request`
65
+ * (e.g. SvelteKit `locals`) pass `init.getContext`, which replaces the router's `getContext`
66
+ * for this request.
67
+ */
68
+ handleRequest(request: Request, init?: RequestInitOptions): Promise<Response>;
69
+ /** Returns the path after the mount point, or `null` when the request is outside `basePath`. */
70
+ private resolveSubpath;
71
+ private applyCors;
72
+ private dispatch;
73
+ /**
74
+ * Reads and validates a JSON body. `overrides` (e.g. a `taskId` from the URL) are applied before
75
+ * validation so path parameters always win over body fields.
76
+ */
77
+ private readBody;
78
+ /**
79
+ * Returns the identity to record for a write: the resolved caller when there is one, otherwise
80
+ * the value supplied in the body. Throws a 400 when neither is present.
81
+ */
82
+ private requireIdentity;
83
+ /** The API root as seen by this request, e.g. `https://host/api/critical-path`. */
84
+ private openApiServerUrl;
21
85
  private readJson;
22
86
  private errorResponse;
23
87
  private jsonResponse;
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAelF,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAC3F;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAMD,qBAAa,kBAAkB;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;gBAE3B,cAAc,CAAC,EAAE,kBAAkB,GAAG,kBAAkB,EAAE,OAAO,GAAE,yBAA8B;IAUvG,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;YAsiB1C,QAAQ;YAQR,aAAa;IAoC3B,OAAO,CAAC,YAAY;CASrB"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAOnG;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,yBAAyB,CAAC,YAAY,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,WAAW;IAC1B,4FAA4F;IAC5F,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC;IACxB,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAC3F;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAClH,8FAA8F;IAC9F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yFAAyF;IACzF,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;CAC5B;AAMD,qBAAa,kBAAkB;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAA0C;IACtE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;gBAE/B,cAAc,CAAC,EAAE,kBAAkB,GAAG,kBAAkB,EAAE,OAAO,GAAE,yBAA8B;IAiB7G;;;;OAIG;IACG,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAKvF,gGAAgG;IAChG,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,SAAS;YA0BH,QAAQ;IAsjBtB;;;OAGG;YACW,QAAQ;IAUtB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAMvB,mFAAmF;IACnF,OAAO,CAAC,gBAAgB;YAKV,QAAQ;YAQR,aAAa;IAqC3B,OAAO,CAAC,YAAY;CAQrB"}