@bubblelab/bubble-runtime 0.1.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.
Files changed (70) hide show
  1. package/LICENSE.txt +202 -0
  2. package/dist/extraction/BubbleParser.d.ts +80 -0
  3. package/dist/extraction/BubbleParser.d.ts.map +1 -0
  4. package/dist/extraction/BubbleParser.js +926 -0
  5. package/dist/extraction/BubbleParser.js.map +1 -0
  6. package/dist/extraction/index.d.ts +2 -0
  7. package/dist/extraction/index.d.ts.map +1 -0
  8. package/dist/extraction/index.js +4 -0
  9. package/dist/extraction/index.js.map +1 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +8 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/injection/BubbleInjector.d.ts +71 -0
  15. package/dist/injection/BubbleInjector.d.ts.map +1 -0
  16. package/dist/injection/BubbleInjector.js +326 -0
  17. package/dist/injection/BubbleInjector.js.map +1 -0
  18. package/dist/injection/LoggerInjector.d.ts +33 -0
  19. package/dist/injection/LoggerInjector.d.ts.map +1 -0
  20. package/dist/injection/LoggerInjector.js +154 -0
  21. package/dist/injection/LoggerInjector.js.map +1 -0
  22. package/dist/injection/index.d.ts +3 -0
  23. package/dist/injection/index.d.ts.map +1 -0
  24. package/dist/injection/index.js +3 -0
  25. package/dist/injection/index.js.map +1 -0
  26. package/dist/parse/BubbleScript.d.ts +141 -0
  27. package/dist/parse/BubbleScript.d.ts.map +1 -0
  28. package/dist/parse/BubbleScript.js +513 -0
  29. package/dist/parse/BubbleScript.js.map +1 -0
  30. package/dist/parse/index.d.ts +3 -0
  31. package/dist/parse/index.d.ts.map +1 -0
  32. package/dist/parse/index.js +3 -0
  33. package/dist/parse/index.js.map +1 -0
  34. package/dist/parse/traceDependencies.d.ts +18 -0
  35. package/dist/parse/traceDependencies.d.ts.map +1 -0
  36. package/dist/parse/traceDependencies.js +181 -0
  37. package/dist/parse/traceDependencies.js.map +1 -0
  38. package/dist/runtime/BubbleRunner.d.ts +91 -0
  39. package/dist/runtime/BubbleRunner.d.ts.map +1 -0
  40. package/dist/runtime/BubbleRunner.js +586 -0
  41. package/dist/runtime/BubbleRunner.js.map +1 -0
  42. package/dist/runtime/index.d.ts +2 -0
  43. package/dist/runtime/index.d.ts.map +1 -0
  44. package/dist/runtime/index.js +2 -0
  45. package/dist/runtime/index.js.map +1 -0
  46. package/dist/runtime/types.d.ts +29 -0
  47. package/dist/runtime/types.d.ts.map +1 -0
  48. package/dist/runtime/types.js +2 -0
  49. package/dist/runtime/types.js.map +1 -0
  50. package/dist/types/index.d.ts +8 -0
  51. package/dist/types/index.d.ts.map +1 -0
  52. package/dist/types/index.js +2 -0
  53. package/dist/types/index.js.map +1 -0
  54. package/dist/utils/bubble-helper.d.ts +11 -0
  55. package/dist/utils/bubble-helper.d.ts.map +1 -0
  56. package/dist/utils/bubble-helper.js +15 -0
  57. package/dist/utils/bubble-helper.js.map +1 -0
  58. package/dist/utils/parameter-formatter.d.ts +22 -0
  59. package/dist/utils/parameter-formatter.d.ts.map +1 -0
  60. package/dist/utils/parameter-formatter.js +219 -0
  61. package/dist/utils/parameter-formatter.js.map +1 -0
  62. package/dist/validation/BubbleValidator.d.ts +43 -0
  63. package/dist/validation/BubbleValidator.d.ts.map +1 -0
  64. package/dist/validation/BubbleValidator.js +172 -0
  65. package/dist/validation/BubbleValidator.js.map +1 -0
  66. package/dist/validation/index.d.ts +27 -0
  67. package/dist/validation/index.d.ts.map +1 -0
  68. package/dist/validation/index.js +126 -0
  69. package/dist/validation/index.js.map +1 -0
  70. package/package.json +47 -0
@@ -0,0 +1,926 @@
1
+ import { buildClassNameLookup } from '../utils/bubble-helper';
2
+ import { BubbleParameterType } from '@bubblelab/shared-schemas';
3
+ import { parseToolsParamValue } from '../utils/parameter-formatter';
4
+ export class BubbleParser {
5
+ bubbleScript;
6
+ constructor(bubbleScript) {
7
+ this.bubbleScript = bubbleScript;
8
+ }
9
+ /**
10
+ * Parse bubble dependencies from an AST using the provided factory and scope manager
11
+ */
12
+ parseBubblesFromAST(bubbleFactory, ast, scopeManager) {
13
+ // Build registry lookup from bubble-core
14
+ const classNameToInfo = buildClassNameLookup(bubbleFactory);
15
+ if (classNameToInfo.size === 0) {
16
+ throw new Error('Failed to trace bubble dependencies: No bubbles found in BubbleFactory');
17
+ }
18
+ const nodes = {};
19
+ const errors = [];
20
+ // Find handle method location
21
+ const handleMethodLocation = this.findHandleMethodLocation(ast);
22
+ // Visit AST nodes to find bubble instantiations
23
+ this.visitNode(ast, nodes, classNameToInfo, scopeManager);
24
+ if (errors.length > 0) {
25
+ throw new Error(`Failed to trace bubble dependencies: ${errors.join(', ')}`);
26
+ }
27
+ // Build a set of used variable IDs to ensure uniqueness for any synthetic IDs we allocate
28
+ const usedVariableIds = new Set();
29
+ for (const [idStr, node] of Object.entries(nodes)) {
30
+ const id = Number(idStr);
31
+ if (!Number.isNaN(id))
32
+ usedVariableIds.add(id);
33
+ for (const param of node.parameters) {
34
+ if (typeof param.variableId === 'number') {
35
+ usedVariableIds.add(param.variableId);
36
+ }
37
+ }
38
+ }
39
+ // For each bubble, compute flat dependencies and construct a detailed dependency graph
40
+ for (const bubble of Object.values(nodes)) {
41
+ const all = this.findDependenciesForBubble([bubble.bubbleName], bubbleFactory, bubble.parameters);
42
+ bubble.dependencies = all;
43
+ // If this node is an ai-agent, extract tools for graph inclusion at the root level
44
+ let rootAIAgentTools;
45
+ if (bubble.bubbleName === 'ai-agent') {
46
+ const toolsParam = bubble.parameters.find((p) => p.name === 'tools');
47
+ const tools = toolsParam
48
+ ? parseToolsParamValue(toolsParam.value)
49
+ : null;
50
+ if (Array.isArray(tools)) {
51
+ rootAIAgentTools = tools
52
+ .map((t) => t?.name)
53
+ .filter((n) => typeof n === 'string');
54
+ }
55
+ }
56
+ // Build hierarchical graph annotated with uniqueId and variableId
57
+ const ordinalCounters = new Map();
58
+ bubble.dependencyGraph = this.buildDependencyGraph(bubble.bubbleName, bubbleFactory, new Set(), rootAIAgentTools, String(bubble.variableId), // Root uniqueId starts with the root variableId string
59
+ ordinalCounters, usedVariableIds, bubble.variableId, // Root variable id mirrors the parsed bubble's variable id
60
+ true, // suppress adding self segment for root
61
+ bubble.variableName);
62
+ }
63
+ return {
64
+ bubbles: nodes,
65
+ handleMethodLocation,
66
+ };
67
+ }
68
+ findDependenciesForBubble(currentDependencies, bubbleFactory, parameters, seen = new Set()) {
69
+ const queue = [...currentDependencies];
70
+ // Mark initial seeds as seen so they are not included in results
71
+ for (const seed of currentDependencies)
72
+ seen.add(seed);
73
+ const result = [];
74
+ while (queue.length > 0) {
75
+ const name = queue.shift();
76
+ // If the bubble is an ai agent, add the tools to the dependencies
77
+ if (name === 'ai-agent') {
78
+ const toolsParam = parameters.find((param) => param.name === 'tools');
79
+ const tools = toolsParam
80
+ ? parseToolsParamValue(toolsParam.value)
81
+ : null;
82
+ if (Array.isArray(tools)) {
83
+ for (const tool of tools) {
84
+ if (tool &&
85
+ typeof tool === 'object' &&
86
+ typeof tool.name === 'string') {
87
+ const toolName = tool.name;
88
+ if (seen.has(toolName))
89
+ continue;
90
+ seen.add(toolName);
91
+ result.push(toolName);
92
+ queue.push(toolName);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ const metadata = bubbleFactory.getMetadata(name);
98
+ const detailed = metadata?.bubbleDependenciesDetailed || [];
99
+ if (Array.isArray(detailed) && detailed.length > 0) {
100
+ for (const spec of detailed) {
101
+ const depName = spec.name;
102
+ if (!seen.has(depName)) {
103
+ seen.add(depName);
104
+ result.push(depName);
105
+ queue.push(depName);
106
+ }
107
+ // If this dependency is an AI agent with declared tools, include them as dependencies too
108
+ if (depName === 'ai-agent' && Array.isArray(spec.tools)) {
109
+ for (const toolName of spec.tools) {
110
+ if (seen.has(toolName))
111
+ continue;
112
+ seen.add(toolName);
113
+ result.push(toolName);
114
+ queue.push(toolName);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ // Fallback to flat dependencies
121
+ const deps = metadata?.bubbleDependencies || [];
122
+ for (const dep of deps) {
123
+ const depName = dep;
124
+ if (seen.has(depName))
125
+ continue;
126
+ seen.add(depName);
127
+ result.push(depName);
128
+ queue.push(depName);
129
+ }
130
+ }
131
+ }
132
+ return result;
133
+ }
134
+ buildDependencyGraph(bubbleName, bubbleFactory, seen, toolsForThisNode, parentUniqueId = '', ordinalCounters = new Map(), usedVariableIds = new Set(), explicitVariableId, suppressSelfSegment = false, instanceVariableName) {
135
+ // Compute this node's uniqueId and variableId FIRST so even cycle hits have IDs
136
+ const countKey = `${parentUniqueId}|${bubbleName}`;
137
+ const nextOrdinal = (ordinalCounters.get(countKey) || 0) + 1;
138
+ ordinalCounters.set(countKey, nextOrdinal);
139
+ const uniqueId = suppressSelfSegment
140
+ ? parentUniqueId
141
+ : parentUniqueId && parentUniqueId.length > 0
142
+ ? `${parentUniqueId}.${bubbleName}#${nextOrdinal}`
143
+ : `${bubbleName}#${nextOrdinal}`;
144
+ const variableId = typeof explicitVariableId === 'number'
145
+ ? explicitVariableId
146
+ : this.hashUniqueIdToVarId(uniqueId);
147
+ const metadata = bubbleFactory.getMetadata(bubbleName);
148
+ if (seen.has(bubbleName)) {
149
+ return {
150
+ name: bubbleName,
151
+ nodeType: metadata?.type || 'unknown',
152
+ uniqueId,
153
+ variableId,
154
+ variableName: instanceVariableName,
155
+ dependencies: [],
156
+ };
157
+ }
158
+ const nextSeen = new Set(seen);
159
+ nextSeen.add(bubbleName);
160
+ const children = [];
161
+ const detailed = metadata?.bubbleDependenciesDetailed;
162
+ if (Array.isArray(detailed) && detailed.length > 0) {
163
+ for (const spec of detailed) {
164
+ const childName = spec.name;
165
+ const toolsForChild = childName === 'ai-agent' ? spec.tools : undefined;
166
+ const instancesArr = Array.isArray(spec.instances)
167
+ ? spec.instances
168
+ : [];
169
+ const instanceCount = instancesArr.length > 0 ? instancesArr.length : 1;
170
+ const nodeType = bubbleFactory.getMetadata(childName)?.type || 'unknown';
171
+ for (let i = 0; i < instanceCount; i++) {
172
+ const instVarName = instancesArr[i]?.variableName;
173
+ // Special handling: avoid cycles when ai-agent appears again. If seen already has ai-agent
174
+ // but we have tools to display, synthesize a child node with tool dependencies directly.
175
+ if (childName === 'ai-agent' &&
176
+ Array.isArray(toolsForChild) &&
177
+ nextSeen.has('ai-agent')) {
178
+ // Synthesize an ai-agent node under the current uniqueId with its own ordinal
179
+ const aiCountKey = `${uniqueId}|ai-agent`;
180
+ const aiOrdinal = (ordinalCounters.get(aiCountKey) || 0) + 1;
181
+ ordinalCounters.set(aiCountKey, aiOrdinal);
182
+ const aiAgentUniqueId = `${uniqueId}.ai-agent#${aiOrdinal}`;
183
+ const aiAgentVarId = this.hashUniqueIdToVarId(aiAgentUniqueId);
184
+ const toolChildren = [];
185
+ for (const toolName of toolsForChild) {
186
+ toolChildren.push(this.buildDependencyGraph(toolName, bubbleFactory, nextSeen, undefined, aiAgentUniqueId, ordinalCounters, usedVariableIds, undefined, false, toolName));
187
+ }
188
+ children.push({
189
+ name: 'ai-agent',
190
+ uniqueId: aiAgentUniqueId,
191
+ variableId: aiAgentVarId,
192
+ variableName: instVarName,
193
+ dependencies: toolChildren,
194
+ nodeType,
195
+ });
196
+ continue;
197
+ }
198
+ children.push(this.buildDependencyGraph(childName, bubbleFactory, nextSeen, toolsForChild, uniqueId, ordinalCounters, usedVariableIds, undefined, false, instVarName));
199
+ }
200
+ }
201
+ }
202
+ else {
203
+ const directDeps = metadata?.bubbleDependencies || [];
204
+ for (const dep of directDeps) {
205
+ console.warn('No bubble detail dependency', dep);
206
+ children.push(this.buildDependencyGraph(dep, bubbleFactory, nextSeen, undefined, uniqueId, ordinalCounters, usedVariableIds, undefined, false, 'No bubble detail dependency'));
207
+ }
208
+ }
209
+ // Include dynamic tool dependencies for ai-agent at the root node
210
+ if (bubbleName === 'ai-agent' && Array.isArray(toolsForThisNode)) {
211
+ for (const toolName of toolsForThisNode) {
212
+ if (nextSeen.has(toolName))
213
+ continue;
214
+ // No variable name for tool, just use tool name
215
+ children.push(this.buildDependencyGraph(toolName, bubbleFactory, nextSeen, undefined, uniqueId, ordinalCounters, usedVariableIds, undefined, false, toolName));
216
+ }
217
+ }
218
+ const nodeObj = {
219
+ name: bubbleName,
220
+ uniqueId,
221
+ variableId,
222
+ variableName: instanceVariableName,
223
+ nodeType: metadata?.type || 'unknown',
224
+ dependencies: children,
225
+ };
226
+ return nodeObj;
227
+ }
228
+ // Deterministic non-negative integer ID from uniqueId string
229
+ hashUniqueIdToVarId(input) {
230
+ let hash = 2166136261; // FNV-1a 32-bit offset basis
231
+ for (let i = 0; i < input.length; i++) {
232
+ hash ^= input.charCodeAt(i);
233
+ hash = (hash * 16777619) >>> 0; // unsigned 32-bit
234
+ }
235
+ // Map to 6-digit range to avoid colliding with small AST ids while readable
236
+ const mapped = 100000 + (hash % 900000);
237
+ return mapped;
238
+ }
239
+ /**
240
+ * Build a JSON Schema object for the payload parameter of the top-level `handle` entrypoint.
241
+ * Supports primitives, arrays, unions (anyOf), intersections (allOf), type literals, and
242
+ * same-file interfaces/type aliases. Interface `extends` are ignored for now.
243
+ */
244
+ getPayloadJsonSchema(ast) {
245
+ const handleNode = this.findHandleFunctionNode(ast);
246
+ if (!handleNode)
247
+ return null;
248
+ const params = handleNode.type === 'FunctionDeclaration' ||
249
+ handleNode.type === 'FunctionExpression' ||
250
+ handleNode.type === 'ArrowFunctionExpression'
251
+ ? handleNode.params
252
+ : [];
253
+ if (!params || params.length === 0)
254
+ return null;
255
+ const firstParam = params[0];
256
+ let typeAnn;
257
+ if (firstParam.type === 'Identifier') {
258
+ typeAnn = firstParam.typeAnnotation || undefined;
259
+ }
260
+ else if (firstParam.type === 'AssignmentPattern' &&
261
+ firstParam.left.type === 'Identifier') {
262
+ typeAnn = firstParam.left.typeAnnotation || undefined;
263
+ }
264
+ else if (firstParam.type === 'RestElement' &&
265
+ firstParam.argument.type === 'Identifier') {
266
+ typeAnn = firstParam.argument.typeAnnotation || undefined;
267
+ }
268
+ if (!typeAnn)
269
+ return {};
270
+ return this.tsTypeToJsonSchema(typeAnn.typeAnnotation, ast) || {};
271
+ }
272
+ /**
273
+ * Find the actual Function/ArrowFunction node corresponding to the handle entrypoint.
274
+ */
275
+ findHandleFunctionNode(ast) {
276
+ for (const stmt of ast.body) {
277
+ if (stmt.type === 'FunctionDeclaration' && stmt.id?.name === 'handle') {
278
+ return stmt;
279
+ }
280
+ if (stmt.type === 'ExportNamedDeclaration' &&
281
+ stmt.declaration?.type === 'FunctionDeclaration' &&
282
+ stmt.declaration.id?.name === 'handle') {
283
+ return stmt.declaration;
284
+ }
285
+ if (stmt.type === 'VariableDeclaration') {
286
+ for (const d of stmt.declarations) {
287
+ if (d.id.type === 'Identifier' &&
288
+ d.id.name === 'handle' &&
289
+ d.init &&
290
+ (d.init.type === 'ArrowFunctionExpression' ||
291
+ d.init.type === 'FunctionExpression')) {
292
+ return d.init;
293
+ }
294
+ }
295
+ }
296
+ if (stmt.type === 'ExportNamedDeclaration' &&
297
+ stmt.declaration?.type === 'VariableDeclaration') {
298
+ for (const d of stmt.declaration.declarations) {
299
+ if (d.id.type === 'Identifier' &&
300
+ d.id.name === 'handle' &&
301
+ d.init &&
302
+ (d.init.type === 'ArrowFunctionExpression' ||
303
+ d.init.type === 'FunctionExpression')) {
304
+ return d.init;
305
+ }
306
+ }
307
+ }
308
+ if (stmt.type === 'ClassDeclaration') {
309
+ const fn = this.findHandleInClass(stmt);
310
+ if (fn)
311
+ return fn;
312
+ }
313
+ if (stmt.type === 'ExportNamedDeclaration' &&
314
+ stmt.declaration?.type === 'ClassDeclaration') {
315
+ const fn = this.findHandleInClass(stmt.declaration);
316
+ if (fn)
317
+ return fn;
318
+ }
319
+ }
320
+ return null;
321
+ }
322
+ findHandleInClass(cls) {
323
+ for (const member of cls.body.body) {
324
+ if (member.type === 'MethodDefinition' &&
325
+ member.key.type === 'Identifier' &&
326
+ member.key.name === 'handle' &&
327
+ member.value.type === 'FunctionExpression') {
328
+ return member.value;
329
+ }
330
+ }
331
+ return null;
332
+ }
333
+ /** Convert a TS type AST node into a JSON Schema object */
334
+ tsTypeToJsonSchema(typeNode, ast) {
335
+ switch (typeNode.type) {
336
+ case 'TSStringKeyword':
337
+ return { type: 'string' };
338
+ case 'TSNumberKeyword':
339
+ return { type: 'number' };
340
+ case 'TSBooleanKeyword':
341
+ return { type: 'boolean' };
342
+ case 'TSNullKeyword':
343
+ return { type: 'null' };
344
+ case 'TSAnyKeyword':
345
+ case 'TSUnknownKeyword':
346
+ case 'TSUndefinedKeyword':
347
+ return {};
348
+ case 'TSLiteralType': {
349
+ const lit = typeNode.literal;
350
+ if (lit.type === 'Literal') {
351
+ return { const: lit.value };
352
+ }
353
+ return {};
354
+ }
355
+ case 'TSArrayType': {
356
+ const items = this.tsTypeToJsonSchema(typeNode.elementType, ast) || {};
357
+ return { type: 'array', items };
358
+ }
359
+ case 'TSUnionType': {
360
+ const anyOf = typeNode.types.map((t) => this.tsTypeToJsonSchema(t, ast) || {});
361
+ return { anyOf };
362
+ }
363
+ case 'TSIntersectionType': {
364
+ const allOf = typeNode.types.map((t) => this.tsTypeToJsonSchema(t, ast) || {});
365
+ return { allOf };
366
+ }
367
+ case 'TSTypeLiteral': {
368
+ return this.objectTypeToJsonSchema(typeNode, ast);
369
+ }
370
+ case 'TSIndexedAccessType': {
371
+ // Handle BubbleTriggerEventRegistry['event/key'] → specific event schema
372
+ const obj = typeNode.objectType;
373
+ const idx = typeNode.indexType;
374
+ if (obj.type === 'TSTypeReference' &&
375
+ obj.typeName.type === 'Identifier' &&
376
+ obj.typeName.name === 'BubbleTriggerEventRegistry' &&
377
+ idx.type === 'TSLiteralType' &&
378
+ idx.literal.type === 'Literal' &&
379
+ typeof idx.literal.value === 'string') {
380
+ const schema = this.eventKeyToSchema(idx.literal.value);
381
+ if (schema)
382
+ return schema;
383
+ }
384
+ return {};
385
+ }
386
+ case 'TSTypeReference': {
387
+ const name = this.extractTypeReferenceName(typeNode);
388
+ if (!name)
389
+ return {};
390
+ const resolved = this.resolveTypeNameToJson(name, ast);
391
+ return resolved || {};
392
+ }
393
+ default:
394
+ return {};
395
+ }
396
+ }
397
+ extractTypeReferenceName(ref) {
398
+ if (ref.typeName.type === 'Identifier')
399
+ return ref.typeName.name;
400
+ return null;
401
+ }
402
+ objectTypeToJsonSchema(node, ast) {
403
+ const elements = node.type === 'TSTypeLiteral' ? node.members : node.body;
404
+ const properties = {};
405
+ const required = [];
406
+ for (const m of elements) {
407
+ if (m.type !== 'TSPropertySignature')
408
+ continue;
409
+ let keyName = null;
410
+ if (m.key.type === 'Identifier')
411
+ keyName = m.key.name;
412
+ else if (m.key.type === 'Literal' && typeof m.key.value === 'string')
413
+ keyName = m.key.value;
414
+ if (!keyName)
415
+ continue;
416
+ const propSchema = m.typeAnnotation
417
+ ? this.tsTypeToJsonSchema(m.typeAnnotation.typeAnnotation, ast)
418
+ : {};
419
+ properties[keyName] = propSchema;
420
+ if (!m.optional)
421
+ required.push(keyName);
422
+ }
423
+ const schema = { type: 'object', properties };
424
+ if (required.length > 0)
425
+ schema.required = required;
426
+ return schema;
427
+ }
428
+ // Minimal mapping for known trigger event keys to JSON Schema shapes
429
+ eventKeyToSchema(eventKey) {
430
+ if (eventKey === 'slack/bot_mentioned') {
431
+ return {
432
+ type: 'object',
433
+ properties: {
434
+ text: { type: 'string' },
435
+ channel: { type: 'string' },
436
+ thread_ts: { type: 'string' },
437
+ user: { type: 'string' },
438
+ slack_event: { type: 'object' },
439
+ // Allow additional field used in flows
440
+ monthlyLimitError: {},
441
+ },
442
+ required: ['text', 'channel', 'user', 'slack_event'],
443
+ };
444
+ }
445
+ if (eventKey === 'webhook/http') {
446
+ return {
447
+ type: 'object',
448
+ properties: {
449
+ body: { type: 'object' },
450
+ },
451
+ };
452
+ }
453
+ if (eventKey === 'gmail/email_received') {
454
+ return {
455
+ type: 'object',
456
+ properties: {
457
+ email: { type: 'string' },
458
+ },
459
+ required: ['email'],
460
+ };
461
+ }
462
+ if (eventKey === 'schedule/cron/daily') {
463
+ return {
464
+ type: 'object',
465
+ properties: {
466
+ cron: { type: 'string' },
467
+ },
468
+ required: ['cron'],
469
+ };
470
+ }
471
+ if (eventKey === 'slack/message_received') {
472
+ return {
473
+ type: 'object',
474
+ properties: {
475
+ text: { type: 'string' },
476
+ channel: { type: 'string' },
477
+ user: { type: 'string' },
478
+ channel_type: { type: 'string' },
479
+ slack_event: { type: 'object' },
480
+ },
481
+ required: ['text', 'channel', 'user', 'slack_event'],
482
+ };
483
+ }
484
+ return null;
485
+ }
486
+ /** Resolve in-file interface/type alias by name to JSON Schema */
487
+ resolveTypeNameToJson(name, ast) {
488
+ for (const stmt of ast.body) {
489
+ if (stmt.type === 'TSInterfaceDeclaration' && stmt.id.name === name) {
490
+ return this.objectTypeToJsonSchema(stmt.body, ast);
491
+ }
492
+ if (stmt.type === 'TSTypeAliasDeclaration' && stmt.id.name === name) {
493
+ return this.tsTypeToJsonSchema(stmt.typeAnnotation, ast) || {};
494
+ }
495
+ if (stmt.type === 'ExportNamedDeclaration' &&
496
+ stmt.declaration?.type === 'TSInterfaceDeclaration' &&
497
+ stmt.declaration.id.name === name) {
498
+ return this.objectTypeToJsonSchema(stmt.declaration.body, ast);
499
+ }
500
+ if (stmt.type === 'ExportNamedDeclaration' &&
501
+ stmt.declaration?.type === 'TSTypeAliasDeclaration' &&
502
+ stmt.declaration.id.name === name) {
503
+ return (this.tsTypeToJsonSchema(stmt.declaration.typeAnnotation, ast) || {});
504
+ }
505
+ }
506
+ return null;
507
+ }
508
+ /**
509
+ * Find the handle method location in the AST
510
+ */
511
+ findHandleMethodLocation(ast) {
512
+ for (const statement of ast.body) {
513
+ // Look for function declarations named 'handle'
514
+ if (statement.type === 'FunctionDeclaration' &&
515
+ statement.id?.name === 'handle') {
516
+ return {
517
+ startLine: statement.loc?.start.line || -1,
518
+ endLine: statement.loc?.end.line || -1,
519
+ };
520
+ }
521
+ // Look for exported function declarations: export function handle() {}
522
+ if (statement.type === 'ExportNamedDeclaration' &&
523
+ statement.declaration?.type === 'FunctionDeclaration' &&
524
+ statement.declaration.id?.name === 'handle') {
525
+ return {
526
+ startLine: statement.declaration.loc?.start.line || -1,
527
+ endLine: statement.declaration.loc?.end.line || -1,
528
+ };
529
+ }
530
+ // Look for variable declarations with function expressions: const handle = () => {}
531
+ if (statement.type === 'VariableDeclaration') {
532
+ for (const declarator of statement.declarations) {
533
+ if (declarator.type === 'VariableDeclarator' &&
534
+ declarator.id.type === 'Identifier' &&
535
+ declarator.id.name === 'handle' &&
536
+ (declarator.init?.type === 'FunctionExpression' ||
537
+ declarator.init?.type === 'ArrowFunctionExpression')) {
538
+ return {
539
+ startLine: declarator.init.loc?.start.line || -1,
540
+ endLine: declarator.init.loc?.end.line || -1,
541
+ };
542
+ }
543
+ }
544
+ }
545
+ // Look for exported variable declarations: export const handle = () => {}
546
+ if (statement.type === 'ExportNamedDeclaration' &&
547
+ statement.declaration?.type === 'VariableDeclaration') {
548
+ for (const declarator of statement.declaration.declarations) {
549
+ if (declarator.type === 'VariableDeclarator' &&
550
+ declarator.id.type === 'Identifier' &&
551
+ declarator.id.name === 'handle' &&
552
+ (declarator.init?.type === 'FunctionExpression' ||
553
+ declarator.init?.type === 'ArrowFunctionExpression')) {
554
+ return {
555
+ startLine: declarator.init.loc?.start.line || -1,
556
+ endLine: declarator.init.loc?.end.line || -1,
557
+ };
558
+ }
559
+ }
560
+ }
561
+ // Look for exported class declarations with handle method
562
+ if (statement.type === 'ExportNamedDeclaration' &&
563
+ statement.declaration?.type === 'ClassDeclaration') {
564
+ const handleMethod = this.findHandleMethodInClass(statement.declaration);
565
+ if (handleMethod) {
566
+ return handleMethod;
567
+ }
568
+ }
569
+ // Look for class declarations with handle method
570
+ if (statement.type === 'ClassDeclaration') {
571
+ const handleMethod = this.findHandleMethodInClass(statement);
572
+ if (handleMethod) {
573
+ return handleMethod;
574
+ }
575
+ }
576
+ }
577
+ return null; // Handle method not found
578
+ }
579
+ /**
580
+ * Find handle method within a class declaration
581
+ */
582
+ findHandleMethodInClass(classDeclaration) {
583
+ if (!classDeclaration.body)
584
+ return null;
585
+ for (const member of classDeclaration.body.body) {
586
+ if (member.type === 'MethodDefinition' &&
587
+ member.key.type === 'Identifier' &&
588
+ member.key.name === 'handle' &&
589
+ member.value.type === 'FunctionExpression') {
590
+ return {
591
+ startLine: member.value.loc?.start.line || -1,
592
+ endLine: member.value.loc?.end.line || -1,
593
+ };
594
+ }
595
+ }
596
+ return null;
597
+ }
598
+ /**
599
+ * Recursively visit AST nodes to find bubble instantiations
600
+ */
601
+ visitNode(node, nodes, classNameLookup, scopeManager) {
602
+ // Capture variable declarations
603
+ if (node.type === 'VariableDeclaration') {
604
+ for (const declarator of node.declarations) {
605
+ if (declarator.type === 'VariableDeclarator' &&
606
+ declarator.id.type === 'Identifier' &&
607
+ declarator.init) {
608
+ const nameText = declarator.id.name;
609
+ const bubbleNode = this.extractBubbleFromExpression(declarator.init, classNameLookup);
610
+ if (bubbleNode) {
611
+ bubbleNode.variableName = nameText;
612
+ // Find the Variable object for this bubble declaration
613
+ const variable = this.findVariableForBubble(nameText, node, scopeManager);
614
+ if (variable) {
615
+ bubbleNode.variableId = variable.$id;
616
+ // Add variable references to parameters
617
+ bubbleNode.parameters = this.addVariableReferencesToParameters(bubbleNode.parameters, node, scopeManager);
618
+ nodes[variable.$id] = bubbleNode;
619
+ }
620
+ else {
621
+ // Fallback: use variable name as key if Variable not found
622
+ throw new Error(`Variable ${nameText} not found in scope manager`);
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ // Anonymous instantiations in expression statements
629
+ if (node.type === 'ExpressionStatement') {
630
+ const bubbleNode = this.extractBubbleFromExpression(node.expression, classNameLookup);
631
+ if (bubbleNode) {
632
+ const synthetic = `_anonymous_${bubbleNode.className}_${Object.keys(nodes).length}`;
633
+ bubbleNode.variableName = synthetic;
634
+ // For anonymous bubbles, use negative synthetic ID (no Variable object exists)
635
+ const syntheticId = -1 * (Object.keys(nodes).length + 1);
636
+ bubbleNode.variableId = syntheticId;
637
+ // Still add variable references to parameters (they can reference other variables)
638
+ bubbleNode.parameters = this.addVariableReferencesToParameters(bubbleNode.parameters, node, scopeManager);
639
+ nodes[syntheticId] = bubbleNode;
640
+ }
641
+ }
642
+ // Recursively visit child nodes
643
+ for (const key in node) {
644
+ const child = node[key];
645
+ if (Array.isArray(child)) {
646
+ for (const item of child) {
647
+ if (item && typeof item === 'object' && 'type' in item) {
648
+ this.visitNode(item, nodes, classNameLookup, scopeManager);
649
+ }
650
+ }
651
+ }
652
+ else if (child && typeof child === 'object' && 'type' in child) {
653
+ this.visitNode(child, nodes, classNameLookup, scopeManager);
654
+ }
655
+ }
656
+ }
657
+ /**
658
+ * Find the Variable object corresponding to a bubble declaration
659
+ */
660
+ findVariableForBubble(variableName, declarationNode, scopeManager) {
661
+ const line = declarationNode.loc?.start.line;
662
+ if (!line)
663
+ return null;
664
+ // Find scopes that contain this line
665
+ for (const scope of scopeManager.scopes) {
666
+ const scopeStart = scope.block.loc?.start.line || 0;
667
+ const scopeEnd = scope.block.loc?.end.line || 0;
668
+ if (line >= scopeStart && line <= scopeEnd) {
669
+ // Look for a variable with this name in this scope
670
+ for (const variable of scope.variables) {
671
+ if (variable.name === variableName) {
672
+ // Check if this variable is declared on or near the same line
673
+ const declLine = variable.defs[0]?.node?.loc?.start?.line;
674
+ if (declLine && Math.abs(declLine - line) <= 2) {
675
+ return variable;
676
+ }
677
+ }
678
+ }
679
+ }
680
+ }
681
+ return null;
682
+ }
683
+ /**
684
+ * Add variable ID references to parameters that are variables
685
+ */
686
+ addVariableReferencesToParameters(parameters, contextNode, scopeManager) {
687
+ const contextLine = contextNode.loc?.start.line || 0;
688
+ return parameters.map((param) => {
689
+ if (param.type === 'variable') {
690
+ const baseVariableName = this.extractBaseVariableName(param.value);
691
+ if (baseVariableName) {
692
+ const variableId = this.findVariableIdByName(baseVariableName, contextLine, scopeManager);
693
+ if (variableId !== undefined) {
694
+ return {
695
+ ...param,
696
+ variableId,
697
+ };
698
+ }
699
+ }
700
+ }
701
+ return param;
702
+ });
703
+ }
704
+ /**
705
+ * Extract base variable name from expressions like "prompts[i]", "result.data"
706
+ */
707
+ extractBaseVariableName(expression) {
708
+ const trimmed = expression.trim();
709
+ // Handle array access: "prompts[i]" -> "prompts"
710
+ const arrayMatch = trimmed.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)\[/);
711
+ if (arrayMatch) {
712
+ return arrayMatch[1];
713
+ }
714
+ // Handle property access: "result.data" -> "result"
715
+ const propertyMatch = trimmed.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)\./);
716
+ if (propertyMatch) {
717
+ return propertyMatch[1];
718
+ }
719
+ // Handle simple variable: "myVar" -> "myVar"
720
+ const simpleMatch = trimmed.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/);
721
+ if (simpleMatch) {
722
+ return trimmed;
723
+ }
724
+ return null;
725
+ }
726
+ /**
727
+ * Find the Variable.$id for a variable name at a specific context line
728
+ */
729
+ findVariableIdByName(variableName, contextLine, scopeManager) {
730
+ // Find ALL scopes that contain this line (not just the smallest)
731
+ const containingScopes = [];
732
+ for (const scope of scopeManager.scopes) {
733
+ const scopeStart = scope.block.loc?.start.line || 0;
734
+ const scopeEnd = scope.block.loc?.end.line || 0;
735
+ if (contextLine >= scopeStart && contextLine <= scopeEnd) {
736
+ containingScopes.push(scope);
737
+ }
738
+ }
739
+ if (containingScopes.length === 0) {
740
+ console.warn(`No scopes found containing line ${contextLine} for variable ${variableName}`);
741
+ return undefined;
742
+ }
743
+ // Look through all containing scopes and their parents
744
+ const allScopes = new Set();
745
+ for (const scope of containingScopes) {
746
+ let currentScope = scope;
747
+ while (currentScope) {
748
+ allScopes.add(currentScope);
749
+ if (!currentScope.upper)
750
+ break;
751
+ currentScope = currentScope.upper;
752
+ }
753
+ }
754
+ // Search through all accessible scopes
755
+ for (const scope of allScopes) {
756
+ for (const variable of scope.variables) {
757
+ if (variable.name === variableName) {
758
+ // Check if this variable is declared before the context line
759
+ const declLine = variable.defs[0]?.node?.loc?.start?.line;
760
+ if (declLine && declLine <= contextLine) {
761
+ return variable.$id;
762
+ }
763
+ }
764
+ }
765
+ }
766
+ console.warn(`Variable ${variableName} not found or not declared before line ${contextLine}`);
767
+ return undefined;
768
+ }
769
+ /**
770
+ * Extract bubble information from an expression node
771
+ */
772
+ extractBubbleFromExpression(expr, classNameLookup) {
773
+ // await new X(...)
774
+ if (expr.type === 'AwaitExpression') {
775
+ const inner = this.extractBubbleFromExpression(expr.argument, classNameLookup);
776
+ if (inner)
777
+ inner.hasAwait = true;
778
+ return inner;
779
+ }
780
+ // new X({...})
781
+ if (expr.type === 'NewExpression') {
782
+ return this.extractFromNewExpression(expr, classNameLookup);
783
+ }
784
+ // new X({...}).action() pattern
785
+ if (expr.type === 'CallExpression' &&
786
+ expr.callee.type === 'MemberExpression') {
787
+ const prop = expr.callee;
788
+ if (prop.property.type === 'Identifier' &&
789
+ prop.property.name === 'action' &&
790
+ prop.object.type === 'NewExpression') {
791
+ const node = this.extractFromNewExpression(prop.object, classNameLookup);
792
+ if (node)
793
+ node.hasActionCall = true;
794
+ return node;
795
+ }
796
+ }
797
+ return null;
798
+ }
799
+ /**
800
+ * Extract bubble information from a NewExpression node
801
+ */
802
+ extractFromNewExpression(newExpr, classNameLookup) {
803
+ if (!newExpr.callee || newExpr.callee.type !== 'Identifier')
804
+ return null;
805
+ const className = newExpr.callee.name;
806
+ const info = classNameLookup.get(className);
807
+ if (!info)
808
+ return null;
809
+ const parameters = [];
810
+ if (newExpr.arguments && newExpr.arguments.length > 0) {
811
+ const firstArg = newExpr.arguments[0];
812
+ if (firstArg.type === 'ObjectExpression') {
813
+ for (const prop of firstArg.properties) {
814
+ if (prop.type === 'Property') {
815
+ if (prop.key.type === 'Identifier' &&
816
+ 'type' in prop.value &&
817
+ prop.value.type !== 'AssignmentPattern') {
818
+ const name = prop.key.name;
819
+ const value = this.extractParameterValue(prop.value);
820
+ // Extract location information for the parameter value
821
+ const valueExpr = prop.value;
822
+ const location = valueExpr.loc
823
+ ? {
824
+ startLine: valueExpr.loc.start.line,
825
+ startCol: valueExpr.loc.start.column,
826
+ endLine: valueExpr.loc.end.line,
827
+ endCol: valueExpr.loc.end.column,
828
+ }
829
+ : undefined;
830
+ parameters.push({
831
+ name,
832
+ ...value,
833
+ location,
834
+ });
835
+ }
836
+ }
837
+ else if (prop.type === 'SpreadElement') {
838
+ // Handle spread properties if needed
839
+ }
840
+ }
841
+ }
842
+ }
843
+ return {
844
+ variableId: -1,
845
+ variableName: '',
846
+ bubbleName: info.bubbleName,
847
+ className: info.className,
848
+ parameters,
849
+ hasAwait: false,
850
+ hasActionCall: false,
851
+ nodeType: info.nodeType,
852
+ location: {
853
+ startLine: newExpr.loc?.start.line || 0,
854
+ startCol: newExpr.loc?.start.column || 0,
855
+ endLine: newExpr.loc?.end.line || 0,
856
+ endCol: newExpr.loc?.end.column || 0,
857
+ },
858
+ };
859
+ }
860
+ /**
861
+ * Extract parameter value and type from an expression
862
+ */
863
+ extractParameterValue(expression) {
864
+ const valueText = this.bubbleScript.substring(expression.range[0], expression.range[1]);
865
+ // process.env detection (with or without non-null)
866
+ const isProcessEnv = (text) => text.startsWith('process.env.');
867
+ if (expression.type === 'TSNonNullExpression') {
868
+ const inner = expression.expression;
869
+ if (inner.type === 'MemberExpression') {
870
+ const full = this.bubbleScript.substring(inner.range[0], inner.range[1]);
871
+ if (isProcessEnv(full)) {
872
+ return { value: valueText, type: BubbleParameterType.ENV };
873
+ }
874
+ }
875
+ }
876
+ if (expression.type === 'MemberExpression' ||
877
+ expression.type === 'ChainExpression') {
878
+ const full = valueText;
879
+ if (isProcessEnv(full)) {
880
+ return { value: full, type: BubbleParameterType.ENV };
881
+ }
882
+ return { value: full, type: BubbleParameterType.VARIABLE };
883
+ }
884
+ // Identifiers treated as variable references
885
+ if (expression.type === 'Identifier') {
886
+ return { value: valueText, type: BubbleParameterType.VARIABLE };
887
+ }
888
+ // Literals and structured
889
+ if (expression.type === 'Literal') {
890
+ if (typeof expression.value === 'string') {
891
+ return { value: valueText, type: BubbleParameterType.STRING };
892
+ }
893
+ if (typeof expression.value === 'number') {
894
+ return { value: valueText, type: BubbleParameterType.NUMBER };
895
+ }
896
+ if (typeof expression.value === 'boolean') {
897
+ return { value: valueText, type: BubbleParameterType.BOOLEAN };
898
+ }
899
+ }
900
+ if (expression.type === 'TemplateLiteral') {
901
+ return { value: valueText, type: BubbleParameterType.STRING };
902
+ }
903
+ if (expression.type === 'ArrayExpression') {
904
+ return { value: valueText, type: BubbleParameterType.ARRAY };
905
+ }
906
+ if (expression.type === 'ObjectExpression') {
907
+ return { value: valueText, type: BubbleParameterType.OBJECT };
908
+ }
909
+ // Check for complex expressions (anything that's not a simple literal or identifier)
910
+ // These are expressions that need to be evaluated rather than treated as literal values
911
+ const simpleTypes = [
912
+ 'Literal',
913
+ 'Identifier',
914
+ 'MemberExpression',
915
+ 'TemplateLiteral',
916
+ 'ArrayExpression',
917
+ 'ObjectExpression',
918
+ ];
919
+ if (!simpleTypes.includes(expression.type)) {
920
+ return { value: valueText, type: BubbleParameterType.EXPRESSION };
921
+ }
922
+ // Fallback
923
+ return { value: valueText, type: BubbleParameterType.UNKNOWN };
924
+ }
925
+ }
926
+ //# sourceMappingURL=BubbleParser.js.map