gravity-run 0.0.2
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/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/cli/index.js +845 -0
- package/dist/index.d.ts +398 -0
- package/dist/index.js +212 -0
- package/dist/shared/chunk-b636e30q.js +274 -0
- package/package.json +83 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
type DatabaseConfig = {};
|
|
2
|
+
type LoggerOptions = {};
|
|
3
|
+
type StorageConfig = {};
|
|
4
|
+
interface BasaltConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Project name (used for logging, defaults, etc.)
|
|
7
|
+
*/
|
|
8
|
+
name?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Server configuration
|
|
11
|
+
*/
|
|
12
|
+
server?: {
|
|
13
|
+
port?: number;
|
|
14
|
+
host?: string;
|
|
15
|
+
cors?: {
|
|
16
|
+
origin?: string | string[];
|
|
17
|
+
credentials?: boolean;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Database configuration
|
|
22
|
+
*/
|
|
23
|
+
db?: DatabaseConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Storage configuration
|
|
26
|
+
*/
|
|
27
|
+
storage?: StorageConfig;
|
|
28
|
+
/**
|
|
29
|
+
* Authentication configuration
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Logger configuration
|
|
33
|
+
*/
|
|
34
|
+
logger?: LoggerOptions;
|
|
35
|
+
/**
|
|
36
|
+
* Actions directory (default: './src/actions' or './actions')
|
|
37
|
+
*/
|
|
38
|
+
actionsDir?: string;
|
|
39
|
+
/**
|
|
40
|
+
* OpenAPI/docs generation
|
|
41
|
+
*/
|
|
42
|
+
docs?: {
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
path?: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* MCP server configuration
|
|
50
|
+
*/
|
|
51
|
+
mcp?: {
|
|
52
|
+
enabled?: boolean;
|
|
53
|
+
name?: string;
|
|
54
|
+
version?: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Development server options
|
|
58
|
+
*/
|
|
59
|
+
dev?: {
|
|
60
|
+
watch?: boolean;
|
|
61
|
+
hmr?: boolean;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Custom middleware or hooks
|
|
65
|
+
*/
|
|
66
|
+
plugins?: BasaltPlugin[];
|
|
67
|
+
}
|
|
68
|
+
interface BasaltPlugin {
|
|
69
|
+
name: string;
|
|
70
|
+
setup: (context: BasaltPluginContext) => Promise<void> | void;
|
|
71
|
+
}
|
|
72
|
+
interface BasaltPluginContext {
|
|
73
|
+
config: BasaltConfig;
|
|
74
|
+
}
|
|
75
|
+
declare function loadConfig(): Promise<BasaltConfig>;
|
|
76
|
+
declare function defineConfig(config: BasaltConfig): BasaltConfig;
|
|
77
|
+
import { TSchema as TSchema2 } from "typebox";
|
|
78
|
+
import { Static, TSchema } from "typebox";
|
|
79
|
+
declare const LEVELS: {
|
|
80
|
+
readonly DEBUG: 10;
|
|
81
|
+
readonly INFO: 20;
|
|
82
|
+
readonly WARNING: 30;
|
|
83
|
+
readonly ERROR: 40;
|
|
84
|
+
readonly CRITICAL: 50;
|
|
85
|
+
};
|
|
86
|
+
type LogLevel = keyof typeof LEVELS;
|
|
87
|
+
type LogListener = (level: LogLevel, msg: string, args?: unknown) => void;
|
|
88
|
+
declare class Logger {
|
|
89
|
+
readonly isVerbose: boolean;
|
|
90
|
+
private readonly meta;
|
|
91
|
+
private readonly coreListeners;
|
|
92
|
+
private readonly listeners;
|
|
93
|
+
constructor(isVerbose?: boolean, meta?: Record<string, unknown>, coreListeners?: LogListener[]);
|
|
94
|
+
child(meta: Record<string, unknown>): Logger;
|
|
95
|
+
private _log;
|
|
96
|
+
info(message: string, args?: unknown): void;
|
|
97
|
+
error(message: string, args?: unknown): void;
|
|
98
|
+
debug(message: string, args?: unknown): void;
|
|
99
|
+
warn(message: string, args?: unknown): void;
|
|
100
|
+
addListener(listener: LogListener): void;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The Standard Identity Structure for Basalt
|
|
104
|
+
*/
|
|
105
|
+
interface BasaltActor {
|
|
106
|
+
type: "user" | "agent" | "system" | "public";
|
|
107
|
+
id?: string;
|
|
108
|
+
name?: string;
|
|
109
|
+
roles?: string[];
|
|
110
|
+
metadata?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
type Actor = BasaltActor;
|
|
113
|
+
type TriggerKind = "api" | "agent" | "cron" | "event" | "custom";
|
|
114
|
+
/**
|
|
115
|
+
* The Unified Trigger Contract
|
|
116
|
+
*/
|
|
117
|
+
interface BasaltTrigger<
|
|
118
|
+
TName extends string,
|
|
119
|
+
TInput
|
|
120
|
+
> {
|
|
121
|
+
readonly name: TName;
|
|
122
|
+
readonly kind: TriggerKind;
|
|
123
|
+
readonly config: any;
|
|
124
|
+
/** Transforms source data to Action Input */
|
|
125
|
+
readonly map: (source: any) => TInput | Promise<TInput>;
|
|
126
|
+
/** Defines who is performing the action for this trigger */
|
|
127
|
+
readonly actor?: BasaltActor | ((source: any) => BasaltActor | Promise<BasaltActor>);
|
|
128
|
+
}
|
|
129
|
+
declare const trigger: {
|
|
130
|
+
api: <T>(options: {
|
|
131
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
132
|
+
path?: string;
|
|
133
|
+
map: (req: Request) => T | Promise<T>;
|
|
134
|
+
actor?: BasaltActor | ((req: Request) => BasaltActor | Promise<BasaltActor>);
|
|
135
|
+
}) => BasaltTrigger<"api", T>;
|
|
136
|
+
cron: <T>(options: {
|
|
137
|
+
schedule: string;
|
|
138
|
+
input: T;
|
|
139
|
+
actor?: BasaltActor;
|
|
140
|
+
}) => BasaltTrigger<"cron", T>;
|
|
141
|
+
event: <T>(options: {
|
|
142
|
+
name: string;
|
|
143
|
+
map: (payload: any) => T | Promise<T>;
|
|
144
|
+
actor?: BasaltActor | ((payload: any) => BasaltActor | Promise<BasaltActor>);
|
|
145
|
+
}) => BasaltTrigger<"event", T>;
|
|
146
|
+
agent: <T>(options?: {
|
|
147
|
+
name?: string;
|
|
148
|
+
map?: (args: any) => T | Promise<T>;
|
|
149
|
+
actor?: BasaltActor;
|
|
150
|
+
}) => BasaltTrigger<"agent", T>;
|
|
151
|
+
custom: <
|
|
152
|
+
TName extends string,
|
|
153
|
+
T
|
|
154
|
+
>(options: {
|
|
155
|
+
name: TName;
|
|
156
|
+
map: (source: any) => T | Promise<T>;
|
|
157
|
+
actor: BasaltActor;
|
|
158
|
+
}) => BasaltTrigger<TName, T>;
|
|
159
|
+
};
|
|
160
|
+
interface Organization {
|
|
161
|
+
id: string;
|
|
162
|
+
plan?: string;
|
|
163
|
+
[key: string]: any;
|
|
164
|
+
}
|
|
165
|
+
interface JobOptions {
|
|
166
|
+
delay?: string | number;
|
|
167
|
+
}
|
|
168
|
+
type BaseActionContext = {
|
|
169
|
+
actor: BasaltActor;
|
|
170
|
+
org?: Organization;
|
|
171
|
+
db: any;
|
|
172
|
+
storage: any;
|
|
173
|
+
enqueue: (event: string, input: any) => void | Promise<void>;
|
|
174
|
+
schedule: <
|
|
175
|
+
TInput extends TSchema,
|
|
176
|
+
TOutput extends TSchema
|
|
177
|
+
>(when: number | Date | string, action: Action<TInput, TOutput>, input: Static<TInput>) => void | Promise<void>;
|
|
178
|
+
logger: Logger;
|
|
179
|
+
triggerName: string;
|
|
180
|
+
triggerType: TriggerKind;
|
|
181
|
+
requestId: string;
|
|
182
|
+
};
|
|
183
|
+
type ExtendActionContext = {};
|
|
184
|
+
type ActionContext = BaseActionContext & ExtendActionContext;
|
|
185
|
+
interface RateLimitRule {
|
|
186
|
+
/** How many requests */
|
|
187
|
+
limit: number;
|
|
188
|
+
/** Time window, e.g., "1s", "1m", "1h" */
|
|
189
|
+
window: string;
|
|
190
|
+
/** What to group by: 'actor' (user), 'org' (tenant), or 'ip' (guest) */
|
|
191
|
+
by: "actor" | "org" | "ip";
|
|
192
|
+
/** Optional: custom identifier, e.g., 'ai-budget' */
|
|
193
|
+
name?: string;
|
|
194
|
+
}
|
|
195
|
+
type RateLimitConfig = RateLimitRule | RateLimitRule[];
|
|
196
|
+
type ActionRateLimit = RateLimitConfig | {
|
|
197
|
+
common?: RateLimitConfig;
|
|
198
|
+
api?: RateLimitConfig;
|
|
199
|
+
agent?: RateLimitConfig;
|
|
200
|
+
[key: string]: RateLimitConfig | undefined;
|
|
201
|
+
};
|
|
202
|
+
interface AccessConfig {
|
|
203
|
+
common?: {
|
|
204
|
+
authenticated?: boolean;
|
|
205
|
+
};
|
|
206
|
+
api?: {
|
|
207
|
+
roles?: string[];
|
|
208
|
+
};
|
|
209
|
+
agent?: {
|
|
210
|
+
authenticated?: boolean;
|
|
211
|
+
};
|
|
212
|
+
authenticated?: boolean;
|
|
213
|
+
roles?: string[];
|
|
214
|
+
}
|
|
215
|
+
type GuardFunction<TInput> = (input: TInput, ctx: ActionContext) => boolean | Promise<boolean>;
|
|
216
|
+
interface GuardConfig<TInput> {
|
|
217
|
+
api?: GuardFunction<TInput>;
|
|
218
|
+
event?: GuardFunction<TInput>;
|
|
219
|
+
cron?: GuardFunction<TInput>;
|
|
220
|
+
agent?: GuardFunction<TInput>;
|
|
221
|
+
[key: string]: GuardFunction<TInput> | undefined;
|
|
222
|
+
}
|
|
223
|
+
interface ActionConfig<
|
|
224
|
+
TInput extends TSchema = TSchema,
|
|
225
|
+
TOutput extends TSchema = TSchema
|
|
226
|
+
> {
|
|
227
|
+
name: string;
|
|
228
|
+
description?: string;
|
|
229
|
+
input?: TInput;
|
|
230
|
+
output?: TOutput;
|
|
231
|
+
triggers?: BasaltTrigger<string, Static<TInput>>[];
|
|
232
|
+
access?: AccessConfig;
|
|
233
|
+
guard?: GuardConfig<Static<TInput>>;
|
|
234
|
+
rateLimit?: ActionRateLimit;
|
|
235
|
+
}
|
|
236
|
+
type ActionHandler<
|
|
237
|
+
TInput extends TSchema,
|
|
238
|
+
TOutput extends TSchema
|
|
239
|
+
> = (input: Static<TInput>, ctx: ActionContext) => Promise<Static<TOutput>>;
|
|
240
|
+
interface Action<
|
|
241
|
+
TInput extends TSchema = TSchema,
|
|
242
|
+
TOutput extends TSchema = TSchema
|
|
243
|
+
> {
|
|
244
|
+
config: ActionConfig<TInput, TOutput>;
|
|
245
|
+
handler: ActionHandler<TInput, TOutput>;
|
|
246
|
+
(input: Static<TInput>, ctx: ActionContext): Promise<Static<TOutput>>;
|
|
247
|
+
}
|
|
248
|
+
declare function action<
|
|
249
|
+
TInput extends TSchema2 = TSchema2,
|
|
250
|
+
TOutput extends TSchema2 = TSchema2
|
|
251
|
+
>(config: ActionConfig<TInput, TOutput>, handler: ActionHandler<TInput, TOutput>): Action<TInput, TOutput>;
|
|
252
|
+
type TriggerKind2 = "api" | "cron" | "event" | "agent";
|
|
253
|
+
interface BaseTrigger<TKind extends TriggerKind2 = TriggerKind2> {
|
|
254
|
+
kind: TKind;
|
|
255
|
+
name: string;
|
|
256
|
+
/**
|
|
257
|
+
* Transform source data into action input
|
|
258
|
+
*/
|
|
259
|
+
map?: (source: never) => unknown | Promise<unknown>;
|
|
260
|
+
/**
|
|
261
|
+
* Resolve actor from source data or provide static actor
|
|
262
|
+
*/
|
|
263
|
+
actor?: BasaltActor | ((source: never) => BasaltActor | Promise<BasaltActor>);
|
|
264
|
+
}
|
|
265
|
+
interface ApiTrigger extends BaseTrigger<"api"> {
|
|
266
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
267
|
+
path: string;
|
|
268
|
+
/**
|
|
269
|
+
* Map HTTP request to action input
|
|
270
|
+
* Default: body for POST/PUT/PATCH, query for GET/DELETE
|
|
271
|
+
*/
|
|
272
|
+
map?: (req: ApiRequest) => unknown | Promise<unknown>;
|
|
273
|
+
/**
|
|
274
|
+
* Extract actor from request (session, JWT, API key, etc.)
|
|
275
|
+
*/
|
|
276
|
+
actor?: BasaltActor | ((req: ApiRequest) => BasaltActor | Promise<BasaltActor>);
|
|
277
|
+
}
|
|
278
|
+
interface ApiRequest {
|
|
279
|
+
method: string;
|
|
280
|
+
path: string;
|
|
281
|
+
params: Record<string, string>;
|
|
282
|
+
query: Record<string, string>;
|
|
283
|
+
body: unknown;
|
|
284
|
+
headers: Record<string, string>;
|
|
285
|
+
raw?: unknown;
|
|
286
|
+
}
|
|
287
|
+
interface CronTrigger extends BaseTrigger<"cron"> {
|
|
288
|
+
/**
|
|
289
|
+
* Cron expression (e.g., '0 * * * *')
|
|
290
|
+
*/
|
|
291
|
+
schedule: string;
|
|
292
|
+
/**
|
|
293
|
+
* Timezone (default: UTC)
|
|
294
|
+
*/
|
|
295
|
+
timezone?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Provide static input for cron jobs
|
|
298
|
+
*/
|
|
299
|
+
map?: () => unknown | Promise<unknown>;
|
|
300
|
+
}
|
|
301
|
+
interface EventTrigger extends BaseTrigger<"event"> {
|
|
302
|
+
/**
|
|
303
|
+
* Event pattern to match (e.g., 'order.created', 'user.*')
|
|
304
|
+
*/
|
|
305
|
+
event: string;
|
|
306
|
+
/**
|
|
307
|
+
* Map event payload to action input
|
|
308
|
+
*/
|
|
309
|
+
map?: (event: BasaltEvent) => unknown | Promise<unknown>;
|
|
310
|
+
/**
|
|
311
|
+
* Extract actor from event metadata
|
|
312
|
+
*/
|
|
313
|
+
actor?: BasaltActor | ((event: BasaltEvent) => BasaltActor | Promise<BasaltActor>);
|
|
314
|
+
}
|
|
315
|
+
interface BasaltEvent<T = unknown> {
|
|
316
|
+
id: string;
|
|
317
|
+
type: string;
|
|
318
|
+
data: T;
|
|
319
|
+
timestamp: Date;
|
|
320
|
+
actor?: BasaltActor;
|
|
321
|
+
metadata?: Record<string, unknown>;
|
|
322
|
+
}
|
|
323
|
+
interface AgentTrigger extends BaseTrigger<"agent"> {
|
|
324
|
+
/**
|
|
325
|
+
* MCP tool name
|
|
326
|
+
*/
|
|
327
|
+
tool: string;
|
|
328
|
+
/**
|
|
329
|
+
* Tool description for LLM
|
|
330
|
+
*/
|
|
331
|
+
description: string;
|
|
332
|
+
/**
|
|
333
|
+
* Map MCP call arguments to action input
|
|
334
|
+
*/
|
|
335
|
+
map?: (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
336
|
+
/**
|
|
337
|
+
* Extract actor from MCP context
|
|
338
|
+
*/
|
|
339
|
+
actor?: BasaltActor | ((context: McpContext) => BasaltActor | Promise<BasaltActor>);
|
|
340
|
+
}
|
|
341
|
+
interface McpContext {
|
|
342
|
+
toolName: string;
|
|
343
|
+
args: Record<string, unknown>;
|
|
344
|
+
sessionId?: string;
|
|
345
|
+
metadata?: Record<string, unknown>;
|
|
346
|
+
}
|
|
347
|
+
type Trigger = ApiTrigger | CronTrigger | EventTrigger | AgentTrigger;
|
|
348
|
+
type ApiTriggerOptions = {
|
|
349
|
+
map?: (req: ApiRequest) => unknown | Promise<unknown>;
|
|
350
|
+
actor?: BasaltActor | ((req: ApiRequest) => BasaltActor | Promise<BasaltActor>);
|
|
351
|
+
};
|
|
352
|
+
type ApiTriggerBuilder = {
|
|
353
|
+
(method: ApiTrigger["method"], path: string, options?: ApiTriggerOptions): ApiTrigger;
|
|
354
|
+
get: (path: string, options?: Omit<ApiTriggerOptions, never>) => ApiTrigger;
|
|
355
|
+
post: (path: string, options?: Omit<ApiTriggerOptions, never>) => ApiTrigger;
|
|
356
|
+
put: (path: string, options?: Omit<ApiTriggerOptions, never>) => ApiTrigger;
|
|
357
|
+
patch: (path: string, options?: Omit<ApiTriggerOptions, never>) => ApiTrigger;
|
|
358
|
+
delete: (path: string, options?: Omit<ApiTriggerOptions, never>) => ApiTrigger;
|
|
359
|
+
};
|
|
360
|
+
declare const api: ApiTriggerBuilder;
|
|
361
|
+
declare function cron(schedule: string, options?: {
|
|
362
|
+
timezone?: string;
|
|
363
|
+
map?: () => unknown | Promise<unknown>;
|
|
364
|
+
actor?: BasaltActor;
|
|
365
|
+
}): CronTrigger;
|
|
366
|
+
declare function event(eventPattern: string, options?: {
|
|
367
|
+
map?: (event: BasaltEvent) => unknown | Promise<unknown>;
|
|
368
|
+
actor?: BasaltActor | ((event: BasaltEvent) => BasaltActor | Promise<BasaltActor>);
|
|
369
|
+
}): EventTrigger;
|
|
370
|
+
declare function agent(tool: string, description: string, options?: {
|
|
371
|
+
map?: (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
372
|
+
actor?: BasaltActor | ((context: McpContext) => BasaltActor | Promise<BasaltActor>);
|
|
373
|
+
}): AgentTrigger;
|
|
374
|
+
declare class BasaltError extends Error {
|
|
375
|
+
code: string;
|
|
376
|
+
status: number;
|
|
377
|
+
constructor(message: string, code: string, status: number);
|
|
378
|
+
}
|
|
379
|
+
declare class NotFoundError extends BasaltError {
|
|
380
|
+
constructor(message?: string);
|
|
381
|
+
}
|
|
382
|
+
declare class UnauthorizedError extends BasaltError {
|
|
383
|
+
constructor(message?: string);
|
|
384
|
+
}
|
|
385
|
+
declare class ForbiddenError extends BasaltError {
|
|
386
|
+
constructor(message?: string);
|
|
387
|
+
}
|
|
388
|
+
declare class BadRequestError extends BasaltError {
|
|
389
|
+
constructor(message?: string);
|
|
390
|
+
}
|
|
391
|
+
declare class InternalServerError extends BasaltError {
|
|
392
|
+
constructor(message?: string);
|
|
393
|
+
}
|
|
394
|
+
declare class NonRetriableError extends BasaltError {
|
|
395
|
+
constructor(message: string);
|
|
396
|
+
}
|
|
397
|
+
import { Type } from "typebox";
|
|
398
|
+
export { trigger, Type as t, loadConfig, event, defineConfig, cron, api, agent, action, UnauthorizedError, Trigger, RateLimitRule, RateLimitConfig, Organization, NotFoundError, NonRetriableError, McpContext, JobOptions, InternalServerError, GuardFunction, GuardConfig, ForbiddenError, ExtendActionContext, EventTrigger, CronTrigger, BaseTrigger, BaseActionContext, BasaltTrigger, BasaltEvent, BasaltError, BasaltActor, BadRequestError, ApiTrigger, ApiRequest, AgentTrigger, Actor, ActionRateLimit, ActionHandler, ActionContext, ActionConfig, Action, AccessConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestError,
|
|
3
|
+
BasaltError,
|
|
4
|
+
ForbiddenError,
|
|
5
|
+
InternalServerError,
|
|
6
|
+
NonRetriableError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
UnauthorizedError,
|
|
9
|
+
defineConfig,
|
|
10
|
+
globalLogger,
|
|
11
|
+
loadConfig
|
|
12
|
+
} from "./shared/chunk-b636e30q.js";
|
|
13
|
+
// src/core/action.ts
|
|
14
|
+
import { Value } from "typebox/value";
|
|
15
|
+
function resolveRateLimitRules(config, triggerType) {
|
|
16
|
+
if (!config)
|
|
17
|
+
return [];
|
|
18
|
+
let rules;
|
|
19
|
+
if ("limit" in config || Array.isArray(config)) {
|
|
20
|
+
rules = config;
|
|
21
|
+
} else {
|
|
22
|
+
const structured = config;
|
|
23
|
+
const specific = structured[triggerType];
|
|
24
|
+
const common = structured.common;
|
|
25
|
+
if (specific && common) {
|
|
26
|
+
const sRules = Array.isArray(specific) ? specific : [specific];
|
|
27
|
+
const cRules = Array.isArray(common) ? common : [common];
|
|
28
|
+
return [...cRules, ...sRules];
|
|
29
|
+
}
|
|
30
|
+
rules = specific || common;
|
|
31
|
+
}
|
|
32
|
+
if (!rules)
|
|
33
|
+
return [];
|
|
34
|
+
return Array.isArray(rules) ? rules : [rules];
|
|
35
|
+
}
|
|
36
|
+
async function checkRateLimit(ctx, config) {
|
|
37
|
+
if (!config.rateLimit)
|
|
38
|
+
return;
|
|
39
|
+
const rules = resolveRateLimitRules(config.rateLimit, ctx.triggerType);
|
|
40
|
+
if (rules.length === 0)
|
|
41
|
+
return;
|
|
42
|
+
for (const rule of rules) {}
|
|
43
|
+
}
|
|
44
|
+
async function checkAccess(ctx, access) {
|
|
45
|
+
if (ctx.actor.type === "system")
|
|
46
|
+
return;
|
|
47
|
+
let config = { ...access.common };
|
|
48
|
+
if (ctx.triggerType === "api" && access.api) {
|
|
49
|
+
config = { ...config, ...access.api };
|
|
50
|
+
} else if (ctx.triggerType === "agent" && access.agent) {
|
|
51
|
+
config = { ...config, ...access.agent };
|
|
52
|
+
}
|
|
53
|
+
if (Object.keys(config).length === 0 && (access.authenticated !== undefined || access.roles)) {
|
|
54
|
+
config = {
|
|
55
|
+
authenticated: access.authenticated,
|
|
56
|
+
roles: access.roles
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (config.authenticated) {
|
|
60
|
+
if (ctx.actor.type === "public") {
|
|
61
|
+
throw new UnauthorizedError("Authentication required");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (config.roles && config.roles.length > 0) {
|
|
65
|
+
if (ctx.actor.type === "public") {
|
|
66
|
+
throw new UnauthorizedError("Authentication required");
|
|
67
|
+
}
|
|
68
|
+
const userRoles = ctx.actor.roles || [];
|
|
69
|
+
const hasRole = config.roles.some((role) => userRoles.includes(role));
|
|
70
|
+
if (!hasRole) {
|
|
71
|
+
throw new ForbiddenError(`User lacks required role(s): ${config.roles.join(", ")}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function checkGuard(ctx, input, config) {
|
|
76
|
+
if (!config.guard)
|
|
77
|
+
return;
|
|
78
|
+
const guardFn = config.guard[ctx.triggerName] || config.guard[ctx.triggerType];
|
|
79
|
+
if (guardFn) {
|
|
80
|
+
const allowed = await guardFn(input, ctx);
|
|
81
|
+
if (!allowed) {
|
|
82
|
+
throw new ForbiddenError("Guard check failed");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function action(config, handler) {
|
|
87
|
+
const wrapped = async (input, ctx) => {
|
|
88
|
+
const logger = ctx.log ? ctx.log.child({ action: config.name, requestId: ctx.requestId }) : globalLogger.child({ action: config.name, requestId: ctx.requestId });
|
|
89
|
+
logger.info("Action started", { input, trigger: ctx.triggerName });
|
|
90
|
+
try {
|
|
91
|
+
if (config.input && !Value.Check(config.input, input)) {
|
|
92
|
+
const errors = [...Value.Errors(config.input, input)];
|
|
93
|
+
throw new BadRequestError(`Validation Error: ${JSON.stringify(errors)}`);
|
|
94
|
+
}
|
|
95
|
+
await checkRateLimit(ctx, config);
|
|
96
|
+
if (config.access) {
|
|
97
|
+
await checkAccess(ctx, config.access);
|
|
98
|
+
}
|
|
99
|
+
await checkGuard(ctx, input, config);
|
|
100
|
+
const result = await handler(input, ctx);
|
|
101
|
+
logger.info("Action completed successfully");
|
|
102
|
+
return result;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
logger.error("Action failed", {
|
|
105
|
+
error: error.message,
|
|
106
|
+
stack: error.stack
|
|
107
|
+
});
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
wrapped.config = config;
|
|
112
|
+
wrapped.handler = handler;
|
|
113
|
+
return wrapped;
|
|
114
|
+
}
|
|
115
|
+
// src/core/types.ts
|
|
116
|
+
var trigger = {
|
|
117
|
+
api: (options) => ({
|
|
118
|
+
name: "api",
|
|
119
|
+
kind: "api",
|
|
120
|
+
config: options,
|
|
121
|
+
map: options.map,
|
|
122
|
+
actor: options.actor
|
|
123
|
+
}),
|
|
124
|
+
cron: (options) => ({
|
|
125
|
+
name: "cron",
|
|
126
|
+
kind: "cron",
|
|
127
|
+
config: options,
|
|
128
|
+
map: () => options.input,
|
|
129
|
+
actor: options.actor ?? { type: "system", name: "Basalt Scheduler" }
|
|
130
|
+
}),
|
|
131
|
+
event: (options) => ({
|
|
132
|
+
name: "event",
|
|
133
|
+
kind: "event",
|
|
134
|
+
config: options,
|
|
135
|
+
map: options.map,
|
|
136
|
+
actor: options.actor
|
|
137
|
+
}),
|
|
138
|
+
agent: (options = {}) => ({
|
|
139
|
+
name: "agent",
|
|
140
|
+
kind: "agent",
|
|
141
|
+
config: options,
|
|
142
|
+
map: options.map || ((args) => args),
|
|
143
|
+
actor: options.actor
|
|
144
|
+
}),
|
|
145
|
+
custom: (options) => ({
|
|
146
|
+
name: options.name,
|
|
147
|
+
kind: "custom",
|
|
148
|
+
config: options,
|
|
149
|
+
map: options.map,
|
|
150
|
+
actor: options.actor
|
|
151
|
+
})
|
|
152
|
+
};
|
|
153
|
+
// src/triggers/index.ts
|
|
154
|
+
var api = (method, path, options) => {
|
|
155
|
+
return {
|
|
156
|
+
kind: "api",
|
|
157
|
+
name: `api:${method}:${path}`,
|
|
158
|
+
method,
|
|
159
|
+
path,
|
|
160
|
+
...options
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
api.get = (path, options) => api("GET", path, options);
|
|
164
|
+
api.post = (path, options) => api("POST", path, options);
|
|
165
|
+
api.put = (path, options) => api("PUT", path, options);
|
|
166
|
+
api.patch = (path, options) => api("PATCH", path, options);
|
|
167
|
+
api.delete = (path, options) => api("DELETE", path, options);
|
|
168
|
+
function cron(schedule, options) {
|
|
169
|
+
return {
|
|
170
|
+
kind: "cron",
|
|
171
|
+
name: `cron:${schedule}`,
|
|
172
|
+
schedule,
|
|
173
|
+
...options
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function event(eventPattern, options) {
|
|
177
|
+
return {
|
|
178
|
+
kind: "event",
|
|
179
|
+
name: `event:${eventPattern}`,
|
|
180
|
+
event: eventPattern,
|
|
181
|
+
...options
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function agent(tool, description, options) {
|
|
185
|
+
return {
|
|
186
|
+
kind: "agent",
|
|
187
|
+
name: `agent:${tool}`,
|
|
188
|
+
tool,
|
|
189
|
+
description,
|
|
190
|
+
...options
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// src/utils/typebox.ts
|
|
194
|
+
import { Type } from "typebox";
|
|
195
|
+
export {
|
|
196
|
+
trigger,
|
|
197
|
+
Type as t,
|
|
198
|
+
loadConfig,
|
|
199
|
+
event,
|
|
200
|
+
defineConfig,
|
|
201
|
+
cron,
|
|
202
|
+
api,
|
|
203
|
+
agent,
|
|
204
|
+
action,
|
|
205
|
+
UnauthorizedError,
|
|
206
|
+
NotFoundError,
|
|
207
|
+
NonRetriableError,
|
|
208
|
+
InternalServerError,
|
|
209
|
+
ForbiddenError,
|
|
210
|
+
BasaltError,
|
|
211
|
+
BadRequestError
|
|
212
|
+
};
|