@stepflowjs/adapter-shared 0.0.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.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Route names that can be configured for authorization and enable/disable.
3
+ */
4
+ type RouteName = "trigger" | "notify" | "runs" | "runsStream" | "health" | "workflowsTrigger" | "workflowsStream" | "eventsNotify";
5
+ /**
6
+ * Sensitive routes that typically require authorization.
7
+ */
8
+ type SensitiveRouteName = "trigger" | "notify" | "workflowsTrigger" | "workflowsStream" | "eventsNotify";
9
+ /**
10
+ * Preset configurations for endpoint visibility.
11
+ *
12
+ * - `all` / `public`: All endpoints enabled (default, backward-compatible)
13
+ * - `none`: All endpoints disabled
14
+ * - `private`: Disable trigger/notify endpoints (backend-only mode)
15
+ * - `internal`: Only health endpoint enabled
16
+ */
17
+ type EndpointPreset = "all" | "none" | "public" | "private" | "internal";
18
+ /**
19
+ * Fine-grained endpoint configuration.
20
+ * Each key controls whether that endpoint is enabled.
21
+ */
22
+ interface EndpointConfig {
23
+ /** POST /trigger/:workflowId */
24
+ trigger?: boolean;
25
+ /** POST /notify/:eventId */
26
+ notify?: boolean;
27
+ /** GET /runs/:runId */
28
+ runs?: boolean;
29
+ /** GET /runs/:runId/stream */
30
+ runsStream?: boolean;
31
+ /** GET /health */
32
+ health?: boolean;
33
+ /** POST /workflows/:workflowId/trigger (client SDK alias) */
34
+ workflowsTrigger?: boolean;
35
+ /** POST /workflows/:workflowId/stream (client SDK alias) */
36
+ workflowsStream?: boolean;
37
+ /** POST /events/:eventId/notify (client SDK alias) */
38
+ eventsNotify?: boolean;
39
+ }
40
+ /**
41
+ * Endpoint configuration can be a preset string or fine-grained config.
42
+ */
43
+ type EndpointOption = EndpointPreset | EndpointConfig;
44
+ /**
45
+ * Context passed to authorization handlers.
46
+ * Generic types allow framework-specific request/response objects.
47
+ */
48
+ interface AuthContext<TRequest = unknown, TExtra = unknown> {
49
+ /** The route being accessed */
50
+ route: RouteName;
51
+ /** Workflow ID (for trigger routes) */
52
+ workflowId?: string;
53
+ /** Event ID (for notify routes) */
54
+ eventId?: string;
55
+ /** Run ID (for runs routes) */
56
+ runId?: string;
57
+ /** The original request object (framework-specific) */
58
+ request: TRequest;
59
+ /** Additional framework-specific context (e.g., Hono's Context, Express's Response) */
60
+ extra?: TExtra;
61
+ }
62
+ /**
63
+ * Result of an authorization check.
64
+ */
65
+ type AuthResult = {
66
+ ok: true;
67
+ } | {
68
+ ok: false;
69
+ /** HTTP status code (default: 401 for missing auth, 403 for denied) */
70
+ status?: number;
71
+ /** Error message for the response */
72
+ message?: string;
73
+ /** Error code for programmatic handling */
74
+ code?: string;
75
+ };
76
+ /**
77
+ * Authorization handler function.
78
+ * Returns AuthResult or void (void = allow).
79
+ */
80
+ type AuthHandler<TRequest = unknown, TExtra = unknown> = (ctx: AuthContext<TRequest, TExtra>) => Promise<AuthResult | void> | AuthResult | void;
81
+ /**
82
+ * Per-route authorization configuration.
83
+ * Global handler applies to all routes; specific handlers override global.
84
+ */
85
+ interface AuthConfig<TRequest = unknown, TExtra = unknown> {
86
+ /** Global auth handler applied to all routes (unless overridden) */
87
+ global?: AuthHandler<TRequest, TExtra>;
88
+ /** Auth handler for POST /trigger/:workflowId */
89
+ trigger?: AuthHandler<TRequest, TExtra>;
90
+ /** Auth handler for POST /notify/:eventId */
91
+ notify?: AuthHandler<TRequest, TExtra>;
92
+ /** Auth handler for GET /runs/:runId */
93
+ runs?: AuthHandler<TRequest, TExtra>;
94
+ /** Auth handler for GET /runs/:runId/stream */
95
+ runsStream?: AuthHandler<TRequest, TExtra>;
96
+ /** Auth handler for GET /health */
97
+ health?: AuthHandler<TRequest, TExtra>;
98
+ /** Auth handler for POST /workflows/:workflowId/trigger */
99
+ workflowsTrigger?: AuthHandler<TRequest, TExtra>;
100
+ /** Auth handler for POST /workflows/:workflowId/stream */
101
+ workflowsStream?: AuthHandler<TRequest, TExtra>;
102
+ /** Auth handler for POST /events/:eventId/notify */
103
+ eventsNotify?: AuthHandler<TRequest, TExtra>;
104
+ }
105
+ /**
106
+ * Base options for all Stepflow adapters.
107
+ * Extended by framework-specific adapter options.
108
+ */
109
+ interface BaseAdapterOptions<TRequest = unknown, TExtra = unknown> {
110
+ /** Base path prefix for all routes (e.g., "/api/stepflow") */
111
+ basePath?: string;
112
+ /** Custom health check function */
113
+ healthCheck?: () => Promise<boolean>;
114
+ /** Endpoint configuration - preset or fine-grained */
115
+ endpoints?: EndpointOption;
116
+ /** Authorization configuration */
117
+ auth?: AuthConfig<TRequest, TExtra>;
118
+ /**
119
+ * Callback when authorization fails.
120
+ * Use for logging, metrics, or custom error responses.
121
+ */
122
+ onAuthFailure?: (ctx: AuthContext<TRequest, TExtra>, result: AuthResult & {
123
+ ok: false;
124
+ }) => void | Promise<void>;
125
+ }
126
+ /**
127
+ * Standard error response body format.
128
+ */
129
+ interface ErrorResponseBody {
130
+ error: {
131
+ code: string;
132
+ message: string;
133
+ };
134
+ }
135
+ /**
136
+ * Creates a standard error response body.
137
+ */
138
+ declare function createErrorBody(code: string, message: string): ErrorResponseBody;
139
+
140
+ /**
141
+ * Resolves endpoint option to a full EndpointConfig.
142
+ * If undefined, defaults to 'all' (backward-compatible).
143
+ */
144
+ declare function resolveEndpoints(option?: EndpointOption): EndpointConfig;
145
+ /**
146
+ * Checks if a specific route is enabled.
147
+ */
148
+ declare function isRouteEnabled(route: RouteName, option?: EndpointOption): boolean;
149
+ /**
150
+ * Gets all enabled routes.
151
+ */
152
+ declare function getEnabledRoutes(option?: EndpointOption): RouteName[];
153
+ /**
154
+ * Gets all disabled routes.
155
+ */
156
+ declare function getDisabledRoutes(option?: EndpointOption): RouteName[];
157
+ /**
158
+ * Validates that at least one endpoint is enabled.
159
+ * Throws if all endpoints are disabled.
160
+ */
161
+ declare function validateEndpoints(option?: EndpointOption): void;
162
+
163
+ /**
164
+ * Normalizes auth handler result.
165
+ * - void or undefined = allow (returns { ok: true })
166
+ * - AuthResult = returns as-is
167
+ */
168
+ declare function normalizeAuthResult(result: AuthResult | void): AuthResult;
169
+ /**
170
+ * Gets the appropriate auth handler for a route.
171
+ * Route-specific handler takes precedence over global.
172
+ */
173
+ declare function getAuthHandler<TRequest, TExtra>(route: RouteName, config?: AuthConfig<TRequest, TExtra>): AuthHandler<TRequest, TExtra> | undefined;
174
+ /**
175
+ * Runs authorization for a route.
176
+ * Returns normalized AuthResult.
177
+ */
178
+ declare function runAuth<TRequest, TExtra>(ctx: AuthContext<TRequest, TExtra>, config?: AuthConfig<TRequest, TExtra>): Promise<AuthResult>;
179
+ /**
180
+ * Creates default auth failure response data.
181
+ */
182
+ declare function getAuthFailureResponse(result: AuthResult & {
183
+ ok: false;
184
+ }): {
185
+ status: number;
186
+ code: string;
187
+ message: string;
188
+ };
189
+ /**
190
+ * Determines if a route is considered "sensitive" (typically requires auth).
191
+ * Sensitive routes: trigger, notify, and their aliases.
192
+ */
193
+ declare function isSensitiveRoute(route: RouteName): boolean;
194
+ /**
195
+ * Creates an API key auth handler.
196
+ * Checks for the API key in the specified header.
197
+ */
198
+ declare function createApiKeyAuth(expectedKey: string, options?: {
199
+ header?: string;
200
+ code?: string;
201
+ message?: string;
202
+ }): AuthHandler<{
203
+ headers: {
204
+ get(name: string): string | null;
205
+ };
206
+ }>;
207
+ /**
208
+ * Creates a Bearer token auth handler.
209
+ * Validates the token using the provided validator function.
210
+ */
211
+ declare function createBearerAuth<TRequest extends {
212
+ headers: {
213
+ get(name: string): string | null;
214
+ };
215
+ }>(validator: (token: string, ctx: AuthContext<TRequest>) => Promise<boolean> | boolean, options?: {
216
+ code?: string;
217
+ message?: string;
218
+ }): AuthHandler<TRequest>;
219
+ /**
220
+ * Combines multiple auth handlers with OR logic.
221
+ * Returns allow if ANY handler allows, deny if ALL deny.
222
+ */
223
+ declare function anyOf<TRequest, TExtra>(...handlers: AuthHandler<TRequest, TExtra>[]): AuthHandler<TRequest, TExtra>;
224
+ /**
225
+ * Combines multiple auth handlers with AND logic.
226
+ * Returns allow if ALL handlers allow, deny if ANY denies.
227
+ */
228
+ declare function allOf<TRequest, TExtra>(...handlers: AuthHandler<TRequest, TExtra>[]): AuthHandler<TRequest, TExtra>;
229
+
230
+ export { type AuthConfig, type AuthContext, type AuthHandler, type AuthResult, type BaseAdapterOptions, type EndpointConfig, type EndpointOption, type EndpointPreset, type ErrorResponseBody, type RouteName, type SensitiveRouteName, allOf, anyOf, createApiKeyAuth, createBearerAuth, createErrorBody, getAuthFailureResponse, getAuthHandler, getDisabledRoutes, getEnabledRoutes, isRouteEnabled, isSensitiveRoute, normalizeAuthResult, resolveEndpoints, runAuth, validateEndpoints };
package/dist/index.js ADDED
@@ -0,0 +1,265 @@
1
+ // src/types.ts
2
+ function createErrorBody(code, message) {
3
+ return {
4
+ error: {
5
+ code,
6
+ message
7
+ }
8
+ };
9
+ }
10
+
11
+ // src/endpoints.ts
12
+ var PRESETS = {
13
+ /** All endpoints enabled (default) */
14
+ all: {
15
+ trigger: true,
16
+ notify: true,
17
+ runs: true,
18
+ runsStream: true,
19
+ health: true,
20
+ workflowsTrigger: true,
21
+ workflowsStream: true,
22
+ eventsNotify: true
23
+ },
24
+ /** Alias for 'all' - all endpoints enabled */
25
+ public: {
26
+ trigger: true,
27
+ notify: true,
28
+ runs: true,
29
+ runsStream: true,
30
+ health: true,
31
+ workflowsTrigger: true,
32
+ workflowsStream: true,
33
+ eventsNotify: true
34
+ },
35
+ /** All endpoints disabled */
36
+ none: {
37
+ trigger: false,
38
+ notify: false,
39
+ runs: false,
40
+ runsStream: false,
41
+ health: false,
42
+ workflowsTrigger: false,
43
+ workflowsStream: false,
44
+ eventsNotify: false
45
+ },
46
+ /** Backend-only mode: disable trigger/notify endpoints */
47
+ private: {
48
+ trigger: false,
49
+ notify: false,
50
+ runs: true,
51
+ runsStream: true,
52
+ health: true,
53
+ workflowsTrigger: false,
54
+ workflowsStream: false,
55
+ eventsNotify: false
56
+ },
57
+ /** Internal mode: only health endpoint enabled */
58
+ internal: {
59
+ trigger: false,
60
+ notify: false,
61
+ runs: false,
62
+ runsStream: false,
63
+ health: true,
64
+ workflowsTrigger: false,
65
+ workflowsStream: false,
66
+ eventsNotify: false
67
+ }
68
+ };
69
+ function resolveEndpoints(option) {
70
+ if (option === void 0) {
71
+ return PRESETS.all;
72
+ }
73
+ if (typeof option === "string") {
74
+ return PRESETS[option] ?? PRESETS.all;
75
+ }
76
+ return {
77
+ ...PRESETS.all,
78
+ ...option
79
+ };
80
+ }
81
+ function isRouteEnabled(route, option) {
82
+ const config = resolveEndpoints(option);
83
+ return config[route] ?? true;
84
+ }
85
+ function getEnabledRoutes(option) {
86
+ const config = resolveEndpoints(option);
87
+ const routes = [];
88
+ for (const [key, value] of Object.entries(config)) {
89
+ if (value) {
90
+ routes.push(key);
91
+ }
92
+ }
93
+ return routes;
94
+ }
95
+ function getDisabledRoutes(option) {
96
+ const config = resolveEndpoints(option);
97
+ const routes = [];
98
+ for (const [key, value] of Object.entries(config)) {
99
+ if (!value) {
100
+ routes.push(key);
101
+ }
102
+ }
103
+ return routes;
104
+ }
105
+ function validateEndpoints(option) {
106
+ const enabled = getEnabledRoutes(option);
107
+ if (enabled.length === 0) {
108
+ throw new Error(
109
+ "All endpoints are disabled. At least one endpoint must be enabled."
110
+ );
111
+ }
112
+ }
113
+
114
+ // src/auth.ts
115
+ function normalizeAuthResult(result) {
116
+ if (result === void 0 || result === null) {
117
+ return { ok: true };
118
+ }
119
+ return result;
120
+ }
121
+ function getAuthHandler(route, config) {
122
+ if (!config) return void 0;
123
+ const routeHandler = config[route];
124
+ if (routeHandler !== void 0) {
125
+ return routeHandler;
126
+ }
127
+ return config.global;
128
+ }
129
+ async function runAuth(ctx, config) {
130
+ const handler = getAuthHandler(ctx.route, config);
131
+ if (!handler) {
132
+ return { ok: true };
133
+ }
134
+ try {
135
+ const result = await handler(ctx);
136
+ return normalizeAuthResult(result);
137
+ } catch (error) {
138
+ return {
139
+ ok: false,
140
+ status: 500,
141
+ code: "auth_error",
142
+ message: error instanceof Error ? error.message : "Authorization error"
143
+ };
144
+ }
145
+ }
146
+ function getAuthFailureResponse(result) {
147
+ return {
148
+ status: result.status ?? 401,
149
+ code: result.code ?? "unauthorized",
150
+ message: result.message ?? "Unauthorized"
151
+ };
152
+ }
153
+ function isSensitiveRoute(route) {
154
+ const sensitiveRoutes = [
155
+ "trigger",
156
+ "notify",
157
+ "workflowsTrigger",
158
+ "workflowsStream",
159
+ "eventsNotify"
160
+ ];
161
+ return sensitiveRoutes.includes(route);
162
+ }
163
+ function createApiKeyAuth(expectedKey, options = {}) {
164
+ const headerName = options.header ?? "x-api-key";
165
+ return ({ request }) => {
166
+ const key = request.headers.get(headerName);
167
+ if (!key) {
168
+ return {
169
+ ok: false,
170
+ status: 401,
171
+ code: options.code ?? "missing_api_key",
172
+ message: options.message ?? "API key is required"
173
+ };
174
+ }
175
+ if (key !== expectedKey) {
176
+ return {
177
+ ok: false,
178
+ status: 401,
179
+ code: options.code ?? "invalid_api_key",
180
+ message: options.message ?? "Invalid API key"
181
+ };
182
+ }
183
+ return { ok: true };
184
+ };
185
+ }
186
+ function createBearerAuth(validator, options = {}) {
187
+ return async (ctx) => {
188
+ const authHeader = ctx.request.headers.get("authorization");
189
+ if (!authHeader) {
190
+ return {
191
+ ok: false,
192
+ status: 401,
193
+ code: options.code ?? "missing_token",
194
+ message: options.message ?? "Authorization header is required"
195
+ };
196
+ }
197
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
198
+ if (!match) {
199
+ return {
200
+ ok: false,
201
+ status: 401,
202
+ code: options.code ?? "invalid_auth_format",
203
+ message: options.message ?? "Invalid authorization format. Use: Bearer <token>"
204
+ };
205
+ }
206
+ const token = match[1];
207
+ const isValid = await validator(token, ctx);
208
+ if (!isValid) {
209
+ return {
210
+ ok: false,
211
+ status: 401,
212
+ code: options.code ?? "invalid_token",
213
+ message: options.message ?? "Invalid or expired token"
214
+ };
215
+ }
216
+ return { ok: true };
217
+ };
218
+ }
219
+ function anyOf(...handlers) {
220
+ return async (ctx) => {
221
+ let lastDeny = {
222
+ ok: false,
223
+ status: 401,
224
+ code: "unauthorized",
225
+ message: "Unauthorized"
226
+ };
227
+ for (const handler of handlers) {
228
+ const result = normalizeAuthResult(await handler(ctx));
229
+ if (result.ok) {
230
+ return result;
231
+ }
232
+ lastDeny = result;
233
+ }
234
+ return lastDeny;
235
+ };
236
+ }
237
+ function allOf(...handlers) {
238
+ return async (ctx) => {
239
+ for (const handler of handlers) {
240
+ const result = normalizeAuthResult(await handler(ctx));
241
+ if (!result.ok) {
242
+ return result;
243
+ }
244
+ }
245
+ return { ok: true };
246
+ };
247
+ }
248
+ export {
249
+ allOf,
250
+ anyOf,
251
+ createApiKeyAuth,
252
+ createBearerAuth,
253
+ createErrorBody,
254
+ getAuthFailureResponse,
255
+ getAuthHandler,
256
+ getDisabledRoutes,
257
+ getEnabledRoutes,
258
+ isRouteEnabled,
259
+ isSensitiveRoute,
260
+ normalizeAuthResult,
261
+ resolveEndpoints,
262
+ runAuth,
263
+ validateEndpoints
264
+ };
265
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/endpoints.ts","../src/auth.ts"],"sourcesContent":["// ============================================================================\n// Endpoint Types\n// ============================================================================\n\n/**\n * Route names that can be configured for authorization and enable/disable.\n */\nexport type RouteName =\n | \"trigger\"\n | \"notify\"\n | \"runs\"\n | \"runsStream\"\n | \"health\"\n | \"workflowsTrigger\"\n | \"workflowsStream\"\n | \"eventsNotify\";\n\n/**\n * Sensitive routes that typically require authorization.\n */\nexport type SensitiveRouteName =\n | \"trigger\"\n | \"notify\"\n | \"workflowsTrigger\"\n | \"workflowsStream\"\n | \"eventsNotify\";\n\n/**\n * Preset configurations for endpoint visibility.\n *\n * - `all` / `public`: All endpoints enabled (default, backward-compatible)\n * - `none`: All endpoints disabled\n * - `private`: Disable trigger/notify endpoints (backend-only mode)\n * - `internal`: Only health endpoint enabled\n */\nexport type EndpointPreset = \"all\" | \"none\" | \"public\" | \"private\" | \"internal\";\n\n/**\n * Fine-grained endpoint configuration.\n * Each key controls whether that endpoint is enabled.\n */\nexport interface EndpointConfig {\n /** POST /trigger/:workflowId */\n trigger?: boolean;\n /** POST /notify/:eventId */\n notify?: boolean;\n /** GET /runs/:runId */\n runs?: boolean;\n /** GET /runs/:runId/stream */\n runsStream?: boolean;\n /** GET /health */\n health?: boolean;\n /** POST /workflows/:workflowId/trigger (client SDK alias) */\n workflowsTrigger?: boolean;\n /** POST /workflows/:workflowId/stream (client SDK alias) */\n workflowsStream?: boolean;\n /** POST /events/:eventId/notify (client SDK alias) */\n eventsNotify?: boolean;\n}\n\n/**\n * Endpoint configuration can be a preset string or fine-grained config.\n */\nexport type EndpointOption = EndpointPreset | EndpointConfig;\n\n// ============================================================================\n// Authorization Types\n// ============================================================================\n\n/**\n * Context passed to authorization handlers.\n * Generic types allow framework-specific request/response objects.\n */\nexport interface AuthContext<TRequest = unknown, TExtra = unknown> {\n /** The route being accessed */\n route: RouteName;\n /** Workflow ID (for trigger routes) */\n workflowId?: string;\n /** Event ID (for notify routes) */\n eventId?: string;\n /** Run ID (for runs routes) */\n runId?: string;\n /** The original request object (framework-specific) */\n request: TRequest;\n /** Additional framework-specific context (e.g., Hono's Context, Express's Response) */\n extra?: TExtra;\n}\n\n/**\n * Result of an authorization check.\n */\nexport type AuthResult =\n | { ok: true }\n | {\n ok: false;\n /** HTTP status code (default: 401 for missing auth, 403 for denied) */\n status?: number;\n /** Error message for the response */\n message?: string;\n /** Error code for programmatic handling */\n code?: string;\n };\n\n/**\n * Authorization handler function.\n * Returns AuthResult or void (void = allow).\n */\nexport type AuthHandler<TRequest = unknown, TExtra = unknown> = (\n ctx: AuthContext<TRequest, TExtra>,\n) => Promise<AuthResult | void> | AuthResult | void;\n\n/**\n * Per-route authorization configuration.\n * Global handler applies to all routes; specific handlers override global.\n */\nexport interface AuthConfig<TRequest = unknown, TExtra = unknown> {\n /** Global auth handler applied to all routes (unless overridden) */\n global?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for POST /trigger/:workflowId */\n trigger?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for POST /notify/:eventId */\n notify?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for GET /runs/:runId */\n runs?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for GET /runs/:runId/stream */\n runsStream?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for GET /health */\n health?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for POST /workflows/:workflowId/trigger */\n workflowsTrigger?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for POST /workflows/:workflowId/stream */\n workflowsStream?: AuthHandler<TRequest, TExtra>;\n /** Auth handler for POST /events/:eventId/notify */\n eventsNotify?: AuthHandler<TRequest, TExtra>;\n}\n\n// ============================================================================\n// Adapter Options Types\n// ============================================================================\n\n/**\n * Base options for all Stepflow adapters.\n * Extended by framework-specific adapter options.\n */\nexport interface BaseAdapterOptions<TRequest = unknown, TExtra = unknown> {\n /** Base path prefix for all routes (e.g., \"/api/stepflow\") */\n basePath?: string;\n /** Custom health check function */\n healthCheck?: () => Promise<boolean>;\n /** Endpoint configuration - preset or fine-grained */\n endpoints?: EndpointOption;\n /** Authorization configuration */\n auth?: AuthConfig<TRequest, TExtra>;\n /**\n * Callback when authorization fails.\n * Use for logging, metrics, or custom error responses.\n */\n onAuthFailure?: (\n ctx: AuthContext<TRequest, TExtra>,\n result: AuthResult & { ok: false },\n ) => void | Promise<void>;\n}\n\n// ============================================================================\n// Error Response Types\n// ============================================================================\n\n/**\n * Standard error response body format.\n */\nexport interface ErrorResponseBody {\n error: {\n code: string;\n message: string;\n };\n}\n\n/**\n * Creates a standard error response body.\n */\nexport function createErrorBody(\n code: string,\n message: string,\n): ErrorResponseBody {\n return {\n error: {\n code,\n message,\n },\n };\n}\n","import type {\n EndpointConfig,\n EndpointOption,\n EndpointPreset,\n RouteName,\n} from \"./types.js\";\n\n// ============================================================================\n// Preset Definitions\n// ============================================================================\n\n/**\n * Preset configurations for endpoint visibility.\n */\nconst PRESETS: Record<EndpointPreset, EndpointConfig> = {\n /** All endpoints enabled (default) */\n all: {\n trigger: true,\n notify: true,\n runs: true,\n runsStream: true,\n health: true,\n workflowsTrigger: true,\n workflowsStream: true,\n eventsNotify: true,\n },\n /** Alias for 'all' - all endpoints enabled */\n public: {\n trigger: true,\n notify: true,\n runs: true,\n runsStream: true,\n health: true,\n workflowsTrigger: true,\n workflowsStream: true,\n eventsNotify: true,\n },\n /** All endpoints disabled */\n none: {\n trigger: false,\n notify: false,\n runs: false,\n runsStream: false,\n health: false,\n workflowsTrigger: false,\n workflowsStream: false,\n eventsNotify: false,\n },\n /** Backend-only mode: disable trigger/notify endpoints */\n private: {\n trigger: false,\n notify: false,\n runs: true,\n runsStream: true,\n health: true,\n workflowsTrigger: false,\n workflowsStream: false,\n eventsNotify: false,\n },\n /** Internal mode: only health endpoint enabled */\n internal: {\n trigger: false,\n notify: false,\n runs: false,\n runsStream: false,\n health: true,\n workflowsTrigger: false,\n workflowsStream: false,\n eventsNotify: false,\n },\n};\n\n// ============================================================================\n// Endpoint Helpers\n// ============================================================================\n\n/**\n * Resolves endpoint option to a full EndpointConfig.\n * If undefined, defaults to 'all' (backward-compatible).\n */\nexport function resolveEndpoints(option?: EndpointOption): EndpointConfig {\n if (option === undefined) {\n return PRESETS.all;\n }\n\n if (typeof option === \"string\") {\n return PRESETS[option] ?? PRESETS.all;\n }\n\n // Merge with 'all' preset to fill in any missing keys\n return {\n ...PRESETS.all,\n ...option,\n };\n}\n\n/**\n * Checks if a specific route is enabled.\n */\nexport function isRouteEnabled(\n route: RouteName,\n option?: EndpointOption,\n): boolean {\n const config = resolveEndpoints(option);\n return config[route] ?? true;\n}\n\n/**\n * Gets all enabled routes.\n */\nexport function getEnabledRoutes(option?: EndpointOption): RouteName[] {\n const config = resolveEndpoints(option);\n const routes: RouteName[] = [];\n\n for (const [key, value] of Object.entries(config)) {\n if (value) {\n routes.push(key as RouteName);\n }\n }\n\n return routes;\n}\n\n/**\n * Gets all disabled routes.\n */\nexport function getDisabledRoutes(option?: EndpointOption): RouteName[] {\n const config = resolveEndpoints(option);\n const routes: RouteName[] = [];\n\n for (const [key, value] of Object.entries(config)) {\n if (!value) {\n routes.push(key as RouteName);\n }\n }\n\n return routes;\n}\n\n/**\n * Validates that at least one endpoint is enabled.\n * Throws if all endpoints are disabled.\n */\nexport function validateEndpoints(option?: EndpointOption): void {\n const enabled = getEnabledRoutes(option);\n if (enabled.length === 0) {\n throw new Error(\n \"All endpoints are disabled. At least one endpoint must be enabled.\",\n );\n }\n}\n","import type {\n AuthConfig,\n AuthContext,\n AuthHandler,\n AuthResult,\n RouteName,\n} from \"./types.js\";\n\n// ============================================================================\n// Auth Helpers\n// ============================================================================\n\n/**\n * Normalizes auth handler result.\n * - void or undefined = allow (returns { ok: true })\n * - AuthResult = returns as-is\n */\nexport function normalizeAuthResult(result: AuthResult | void): AuthResult {\n if (result === undefined || result === null) {\n return { ok: true };\n }\n return result;\n}\n\n/**\n * Gets the appropriate auth handler for a route.\n * Route-specific handler takes precedence over global.\n */\nexport function getAuthHandler<TRequest, TExtra>(\n route: RouteName,\n config?: AuthConfig<TRequest, TExtra>,\n): AuthHandler<TRequest, TExtra> | undefined {\n if (!config) return undefined;\n\n // Check for route-specific handler first\n const routeHandler = config[route] as\n | AuthHandler<TRequest, TExtra>\n | undefined;\n if (routeHandler !== undefined) {\n return routeHandler;\n }\n\n // Fall back to global handler\n return config.global;\n}\n\n/**\n * Runs authorization for a route.\n * Returns normalized AuthResult.\n */\nexport async function runAuth<TRequest, TExtra>(\n ctx: AuthContext<TRequest, TExtra>,\n config?: AuthConfig<TRequest, TExtra>,\n): Promise<AuthResult> {\n const handler = getAuthHandler(ctx.route, config);\n\n if (!handler) {\n // No auth configured = allow\n return { ok: true };\n }\n\n try {\n const result = await handler(ctx);\n return normalizeAuthResult(result);\n } catch (error) {\n // Auth handler threw an error = deny with 500\n return {\n ok: false,\n status: 500,\n code: \"auth_error\",\n message: error instanceof Error ? error.message : \"Authorization error\",\n };\n }\n}\n\n/**\n * Creates default auth failure response data.\n */\nexport function getAuthFailureResponse(result: AuthResult & { ok: false }): {\n status: number;\n code: string;\n message: string;\n} {\n return {\n status: result.status ?? 401,\n code: result.code ?? \"unauthorized\",\n message: result.message ?? \"Unauthorized\",\n };\n}\n\n/**\n * Determines if a route is considered \"sensitive\" (typically requires auth).\n * Sensitive routes: trigger, notify, and their aliases.\n */\nexport function isSensitiveRoute(route: RouteName): boolean {\n const sensitiveRoutes: RouteName[] = [\n \"trigger\",\n \"notify\",\n \"workflowsTrigger\",\n \"workflowsStream\",\n \"eventsNotify\",\n ];\n return sensitiveRoutes.includes(route);\n}\n\n// ============================================================================\n// Common Auth Patterns\n// ============================================================================\n\n/**\n * Creates an API key auth handler.\n * Checks for the API key in the specified header.\n */\nexport function createApiKeyAuth(\n expectedKey: string,\n options: {\n header?: string;\n code?: string;\n message?: string;\n } = {},\n): AuthHandler<{ headers: { get(name: string): string | null } }> {\n const headerName = options.header ?? \"x-api-key\";\n\n return ({ request }) => {\n const key = request.headers.get(headerName);\n\n if (!key) {\n return {\n ok: false,\n status: 401,\n code: options.code ?? \"missing_api_key\",\n message: options.message ?? \"API key is required\",\n };\n }\n\n if (key !== expectedKey) {\n return {\n ok: false,\n status: 401,\n code: options.code ?? \"invalid_api_key\",\n message: options.message ?? \"Invalid API key\",\n };\n }\n\n return { ok: true };\n };\n}\n\n/**\n * Creates a Bearer token auth handler.\n * Validates the token using the provided validator function.\n */\nexport function createBearerAuth<\n TRequest extends { headers: { get(name: string): string | null } },\n>(\n validator: (\n token: string,\n ctx: AuthContext<TRequest>,\n ) => Promise<boolean> | boolean,\n options: {\n code?: string;\n message?: string;\n } = {},\n): AuthHandler<TRequest> {\n return async (ctx) => {\n const authHeader = ctx.request.headers.get(\"authorization\");\n\n if (!authHeader) {\n return {\n ok: false,\n status: 401,\n code: options.code ?? \"missing_token\",\n message: options.message ?? \"Authorization header is required\",\n };\n }\n\n const match = authHeader.match(/^Bearer\\s+(.+)$/i);\n if (!match) {\n return {\n ok: false,\n status: 401,\n code: options.code ?? \"invalid_auth_format\",\n message:\n options.message ??\n \"Invalid authorization format. Use: Bearer <token>\",\n };\n }\n\n const token = match[1];\n const isValid = await validator(token, ctx);\n\n if (!isValid) {\n return {\n ok: false,\n status: 401,\n code: options.code ?? \"invalid_token\",\n message: options.message ?? \"Invalid or expired token\",\n };\n }\n\n return { ok: true };\n };\n}\n\n/**\n * Combines multiple auth handlers with OR logic.\n * Returns allow if ANY handler allows, deny if ALL deny.\n */\nexport function anyOf<TRequest, TExtra>(\n ...handlers: AuthHandler<TRequest, TExtra>[]\n): AuthHandler<TRequest, TExtra> {\n return async (ctx) => {\n let lastDeny: AuthResult & { ok: false } = {\n ok: false,\n status: 401,\n code: \"unauthorized\",\n message: \"Unauthorized\",\n };\n\n for (const handler of handlers) {\n const result = normalizeAuthResult(await handler(ctx));\n if (result.ok) {\n return result;\n }\n lastDeny = result;\n }\n\n return lastDeny;\n };\n}\n\n/**\n * Combines multiple auth handlers with AND logic.\n * Returns allow if ALL handlers allow, deny if ANY denies.\n */\nexport function allOf<TRequest, TExtra>(\n ...handlers: AuthHandler<TRequest, TExtra>[]\n): AuthHandler<TRequest, TExtra> {\n return async (ctx) => {\n for (const handler of handlers) {\n const result = normalizeAuthResult(await handler(ctx));\n if (!result.ok) {\n return result;\n }\n }\n\n return { ok: true };\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@stepflowjs/adapter-shared",
3
+ "version": "0.0.1",
4
+ "description": "Shared utilities for Stepflow framework adapters",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "devDependencies": {
19
+ "tsup": "^8.5.1",
20
+ "typescript": "^5.8.3",
21
+ "vitest": "^4.0.17"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Stepflow Contributors",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://stepflow-production.up.railway.app",
28
+ "directory": "packages/adapters/shared"
29
+ },
30
+ "homepage": "https://stepflow-production.up.railway.app",
31
+ "bugs": {
32
+ "url": "https://stepflow-production.up.railway.app"
33
+ },
34
+ "keywords": [
35
+ "stepflow",
36
+ "adapter",
37
+ "shared",
38
+ "utilities",
39
+ "workflow",
40
+ "orchestration"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "dev": "tsup --watch",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest",
50
+ "clean": "rm -rf dist"
51
+ }
52
+ }