@vibe-lang/runtime 0.2.5
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/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async scheduling helpers.
|
|
3
|
+
* Consolidates the common pattern of scheduling async operations.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RuntimeState, AsyncOperation, PendingAsyncStart, VibeValue, VibeError } from '../types';
|
|
7
|
+
import { createVibeValue } from '../types';
|
|
8
|
+
import { generateAsyncId } from './executor';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Details for different async operation types.
|
|
12
|
+
*/
|
|
13
|
+
export type AsyncOperationDetails =
|
|
14
|
+
| { type: 'do' | 'vibe'; aiDetails: NonNullable<PendingAsyncStart['aiDetails']> }
|
|
15
|
+
| { type: 'ts'; tsDetails: NonNullable<PendingAsyncStart['tsDetails']> }
|
|
16
|
+
| { type: 'ts-function'; tsFuncDetails: NonNullable<PendingAsyncStart['tsFuncDetails']> }
|
|
17
|
+
| { type: 'vibe-function'; vibeFuncDetails: NonNullable<PendingAsyncStart['vibeFuncDetails']> };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Source type for VibeValue based on operation type.
|
|
21
|
+
*/
|
|
22
|
+
function getSourceFromType(type: string): 'ai' | 'ts' | 'vibe-function' {
|
|
23
|
+
if (type === 'do' || type === 'vibe') return 'ai';
|
|
24
|
+
if (type === 'ts' || type === 'ts-function') return 'ts';
|
|
25
|
+
return 'vibe-function';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Schedules an async operation and returns the updated state.
|
|
30
|
+
* This consolidates the common pattern used across AI, TS, and Vibe function async scheduling.
|
|
31
|
+
*/
|
|
32
|
+
export function scheduleAsyncOperation(
|
|
33
|
+
state: RuntimeState,
|
|
34
|
+
details: AsyncOperationDetails,
|
|
35
|
+
logInstructionType: string
|
|
36
|
+
): RuntimeState {
|
|
37
|
+
const operationId = generateAsyncId(state);
|
|
38
|
+
|
|
39
|
+
// Determine variable name (null for destructuring or fire-and-forget)
|
|
40
|
+
const variableName = state.currentAsyncIsDestructure || state.currentAsyncIsFireAndForget
|
|
41
|
+
? null
|
|
42
|
+
: state.currentAsyncVarName;
|
|
43
|
+
|
|
44
|
+
// Create async operation record
|
|
45
|
+
const asyncOp: AsyncOperation = {
|
|
46
|
+
id: operationId,
|
|
47
|
+
variableName,
|
|
48
|
+
status: 'pending',
|
|
49
|
+
operationType: details.type,
|
|
50
|
+
dependencies: [],
|
|
51
|
+
contextSnapshot: [],
|
|
52
|
+
waveId: state.currentWaveId,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Create pending start record with appropriate details
|
|
56
|
+
const pendingStart: PendingAsyncStart = {
|
|
57
|
+
operationId,
|
|
58
|
+
type: details.type,
|
|
59
|
+
variableName,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Add the specific details based on operation type
|
|
63
|
+
if (details.type === 'do' || details.type === 'vibe') {
|
|
64
|
+
pendingStart.aiDetails = details.aiDetails;
|
|
65
|
+
} else if (details.type === 'ts') {
|
|
66
|
+
pendingStart.tsDetails = details.tsDetails;
|
|
67
|
+
} else if (details.type === 'ts-function') {
|
|
68
|
+
pendingStart.tsFuncDetails = details.tsFuncDetails;
|
|
69
|
+
} else if (details.type === 'vibe-function') {
|
|
70
|
+
pendingStart.vibeFuncDetails = details.vibeFuncDetails;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create pending marker for variable
|
|
74
|
+
const pendingMarker: VibeValue = createVibeValue(null, {
|
|
75
|
+
source: getSourceFromType(details.type),
|
|
76
|
+
asyncOperationId: operationId,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Update maps
|
|
80
|
+
const newAsyncOps = new Map(state.asyncOperations);
|
|
81
|
+
newAsyncOps.set(operationId, asyncOp);
|
|
82
|
+
|
|
83
|
+
const newPendingIds = new Set(state.pendingAsyncIds);
|
|
84
|
+
newPendingIds.add(operationId);
|
|
85
|
+
|
|
86
|
+
const newVarToOp = new Map(state.asyncVarToOpId);
|
|
87
|
+
if (variableName !== null) {
|
|
88
|
+
newVarToOp.set(variableName, operationId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
...state,
|
|
93
|
+
// Clear async context
|
|
94
|
+
...clearAsyncContext(),
|
|
95
|
+
// Set the pending marker as lastResult
|
|
96
|
+
lastResult: pendingMarker,
|
|
97
|
+
lastResultSource: getSourceFromType(details.type),
|
|
98
|
+
// Track async operation
|
|
99
|
+
asyncOperations: newAsyncOps,
|
|
100
|
+
pendingAsyncIds: newPendingIds,
|
|
101
|
+
asyncVarToOpId: newVarToOp,
|
|
102
|
+
nextAsyncId: state.nextAsyncId + 1,
|
|
103
|
+
// Schedule for start
|
|
104
|
+
pendingAsyncStarts: [...state.pendingAsyncStarts, pendingStart],
|
|
105
|
+
executionLog: [
|
|
106
|
+
...state.executionLog,
|
|
107
|
+
{
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
instructionType: logInstructionType,
|
|
110
|
+
details: { operationId },
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Returns the properties needed to clear async context.
|
|
118
|
+
* Used when exiting async scheduling mode.
|
|
119
|
+
*/
|
|
120
|
+
export function clearAsyncContext(): Partial<RuntimeState> {
|
|
121
|
+
return {
|
|
122
|
+
currentAsyncVarName: null,
|
|
123
|
+
currentAsyncIsConst: false,
|
|
124
|
+
currentAsyncType: null,
|
|
125
|
+
currentAsyncIsPrivate: false,
|
|
126
|
+
currentAsyncIsDestructure: false,
|
|
127
|
+
currentAsyncIsFireAndForget: false,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Checks if we're currently in an async context.
|
|
133
|
+
*/
|
|
134
|
+
export function isInAsyncContext(state: RuntimeState): boolean {
|
|
135
|
+
return state.currentAsyncVarName !== null ||
|
|
136
|
+
state.currentAsyncIsDestructure ||
|
|
137
|
+
state.currentAsyncIsFireAndForget;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Creates a VibeError from an error.
|
|
142
|
+
*/
|
|
143
|
+
export function createAsyncVibeError(error: unknown, location: { line: number; column: number } | null = null): VibeError {
|
|
144
|
+
return {
|
|
145
|
+
message: error instanceof Error ? error.message : String(error),
|
|
146
|
+
type: error instanceof Error ? error.constructor.name : 'Error',
|
|
147
|
+
location,
|
|
148
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Marks an async operation as running with its promise.
|
|
154
|
+
* Consolidates the repeated pattern of setting promise, status, and startTime.
|
|
155
|
+
*/
|
|
156
|
+
export function startAsyncOperation(
|
|
157
|
+
operation: { promise?: Promise<VibeValue>; status: string; startTime?: number },
|
|
158
|
+
promise: Promise<VibeValue>
|
|
159
|
+
): void {
|
|
160
|
+
operation.promise = promise;
|
|
161
|
+
operation.status = 'running';
|
|
162
|
+
operation.startTime = Date.now();
|
|
163
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../../parser/parse';
|
|
3
|
+
import type * as AST from '../../../ast';
|
|
4
|
+
import type { AsyncOperation } from '../../types';
|
|
5
|
+
import {
|
|
6
|
+
getReferencedVariables,
|
|
7
|
+
detectAsyncDependencies,
|
|
8
|
+
buildExecutionWaves,
|
|
9
|
+
} from '../dependencies';
|
|
10
|
+
|
|
11
|
+
describe('Async Dependency Detection', () => {
|
|
12
|
+
// Helper to extract expression from parsed code
|
|
13
|
+
function parseExpr(code: string): AST.Expression {
|
|
14
|
+
const ast = parse(`let x = ${code}`);
|
|
15
|
+
const decl = ast.body[0] as AST.LetDeclaration;
|
|
16
|
+
return decl.initializer!;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('getReferencedVariables', () => {
|
|
20
|
+
test('extracts variable from identifier', () => {
|
|
21
|
+
const expr = parseExpr('myVar');
|
|
22
|
+
expect(getReferencedVariables(expr)).toEqual(['myVar']);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('extracts no variables from string literal', () => {
|
|
26
|
+
const expr = parseExpr('"hello"');
|
|
27
|
+
expect(getReferencedVariables(expr)).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('extracts no variables from number literal', () => {
|
|
31
|
+
const expr = parseExpr('42');
|
|
32
|
+
expect(getReferencedVariables(expr)).toEqual([]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('extracts variables from binary expression', () => {
|
|
36
|
+
const expr = parseExpr('a + b');
|
|
37
|
+
const vars = getReferencedVariables(expr);
|
|
38
|
+
expect(vars).toContain('a');
|
|
39
|
+
expect(vars).toContain('b');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('extracts variables from nested binary expression', () => {
|
|
43
|
+
const expr = parseExpr('a + b * c');
|
|
44
|
+
const vars = getReferencedVariables(expr);
|
|
45
|
+
expect(vars).toContain('a');
|
|
46
|
+
expect(vars).toContain('b');
|
|
47
|
+
expect(vars).toContain('c');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('extracts variables from function call', () => {
|
|
51
|
+
const expr = parseExpr('func(a, b)');
|
|
52
|
+
const vars = getReferencedVariables(expr);
|
|
53
|
+
expect(vars).toContain('func');
|
|
54
|
+
expect(vars).toContain('a');
|
|
55
|
+
expect(vars).toContain('b');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('extracts variables from member expression', () => {
|
|
59
|
+
const expr = parseExpr('obj.method');
|
|
60
|
+
const vars = getReferencedVariables(expr);
|
|
61
|
+
expect(vars).toContain('obj');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('extracts variables from array literal', () => {
|
|
65
|
+
const expr = parseExpr('[a, b, c]');
|
|
66
|
+
const vars = getReferencedVariables(expr);
|
|
67
|
+
expect(vars).toContain('a');
|
|
68
|
+
expect(vars).toContain('b');
|
|
69
|
+
expect(vars).toContain('c');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('extracts variables from object literal', () => {
|
|
73
|
+
const expr = parseExpr('{ key: value }');
|
|
74
|
+
const vars = getReferencedVariables(expr);
|
|
75
|
+
expect(vars).toContain('value');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('extracts variables from template literal interpolation', () => {
|
|
79
|
+
const expr = parseExpr('`hello {name}`');
|
|
80
|
+
const vars = getReferencedVariables(expr);
|
|
81
|
+
expect(vars).toContain('name');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('extracts multiple variables from template literal', () => {
|
|
85
|
+
const expr = parseExpr('`{greeting} {name}!`');
|
|
86
|
+
const vars = getReferencedVariables(expr);
|
|
87
|
+
expect(vars).toContain('greeting');
|
|
88
|
+
expect(vars).toContain('name');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('deduplicates repeated variables', () => {
|
|
92
|
+
const expr = parseExpr('a + a + a');
|
|
93
|
+
const vars = getReferencedVariables(expr);
|
|
94
|
+
expect(vars).toEqual(['a']);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('extracts variables from index expression', () => {
|
|
98
|
+
const expr = parseExpr('arr[idx]');
|
|
99
|
+
const vars = getReferencedVariables(expr);
|
|
100
|
+
expect(vars).toContain('arr');
|
|
101
|
+
expect(vars).toContain('idx');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('extracts variables from unary expression', () => {
|
|
105
|
+
const expr = parseExpr('not flag');
|
|
106
|
+
const vars = getReferencedVariables(expr);
|
|
107
|
+
expect(vars).toContain('flag');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('detectAsyncDependencies', () => {
|
|
112
|
+
test('returns empty array when no async operations', () => {
|
|
113
|
+
const expr = parseExpr('a + b');
|
|
114
|
+
const asyncVarToOpId = new Map<string, string>();
|
|
115
|
+
const pendingAsyncIds = new Set<string>();
|
|
116
|
+
|
|
117
|
+
expect(detectAsyncDependencies(expr, asyncVarToOpId, pendingAsyncIds)).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('detects single async dependency', () => {
|
|
121
|
+
const expr = parseExpr('asyncResult + 1');
|
|
122
|
+
const asyncVarToOpId = new Map([['asyncResult', 'async-001']]);
|
|
123
|
+
const pendingAsyncIds = new Set(['async-001']);
|
|
124
|
+
|
|
125
|
+
expect(detectAsyncDependencies(expr, asyncVarToOpId, pendingAsyncIds)).toEqual(['async-001']);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('detects multiple async dependencies', () => {
|
|
129
|
+
const expr = parseExpr('a + b');
|
|
130
|
+
const asyncVarToOpId = new Map([
|
|
131
|
+
['a', 'async-001'],
|
|
132
|
+
['b', 'async-002'],
|
|
133
|
+
]);
|
|
134
|
+
const pendingAsyncIds = new Set(['async-001', 'async-002']);
|
|
135
|
+
|
|
136
|
+
const deps = detectAsyncDependencies(expr, asyncVarToOpId, pendingAsyncIds);
|
|
137
|
+
expect(deps).toContain('async-001');
|
|
138
|
+
expect(deps).toContain('async-002');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('ignores completed async operations', () => {
|
|
142
|
+
const expr = parseExpr('asyncResult + 1');
|
|
143
|
+
const asyncVarToOpId = new Map([['asyncResult', 'async-001']]);
|
|
144
|
+
const pendingAsyncIds = new Set<string>(); // async-001 not pending
|
|
145
|
+
|
|
146
|
+
expect(detectAsyncDependencies(expr, asyncVarToOpId, pendingAsyncIds)).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('ignores non-async variables', () => {
|
|
150
|
+
const expr = parseExpr('syncVar + asyncVar');
|
|
151
|
+
const asyncVarToOpId = new Map([['asyncVar', 'async-001']]);
|
|
152
|
+
const pendingAsyncIds = new Set(['async-001']);
|
|
153
|
+
|
|
154
|
+
const deps = detectAsyncDependencies(expr, asyncVarToOpId, pendingAsyncIds);
|
|
155
|
+
expect(deps).toEqual(['async-001']);
|
|
156
|
+
expect(deps).not.toContain('syncVar');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('buildExecutionWaves', () => {
|
|
161
|
+
function createOp(id: string, varName: string | null, deps: string[]): AsyncOperation {
|
|
162
|
+
return {
|
|
163
|
+
id,
|
|
164
|
+
variableName: varName,
|
|
165
|
+
status: 'pending',
|
|
166
|
+
operationType: 'do',
|
|
167
|
+
dependencies: deps,
|
|
168
|
+
contextSnapshot: [],
|
|
169
|
+
waveId: 0,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
test('groups independent operations in same wave', () => {
|
|
174
|
+
const ops = [
|
|
175
|
+
createOp('async-001', 'a', []),
|
|
176
|
+
createOp('async-002', 'b', []),
|
|
177
|
+
createOp('async-003', 'c', []),
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const waves = buildExecutionWaves(ops, []);
|
|
181
|
+
|
|
182
|
+
expect(waves.length).toBe(1);
|
|
183
|
+
expect(waves[0].operationIds).toContain('async-001');
|
|
184
|
+
expect(waves[0].operationIds).toContain('async-002');
|
|
185
|
+
expect(waves[0].operationIds).toContain('async-003');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('separates dependent operations into sequential waves', () => {
|
|
189
|
+
const ops = [
|
|
190
|
+
createOp('async-001', 'a', []),
|
|
191
|
+
createOp('async-002', 'b', ['a']), // depends on a
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const waves = buildExecutionWaves(ops, []);
|
|
195
|
+
|
|
196
|
+
expect(waves.length).toBe(2);
|
|
197
|
+
expect(waves[0].operationIds).toEqual(['async-001']);
|
|
198
|
+
expect(waves[1].operationIds).toEqual(['async-002']);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('handles complex dependency graph', () => {
|
|
202
|
+
// a, b run first (no deps)
|
|
203
|
+
// c depends on a
|
|
204
|
+
// d depends on b
|
|
205
|
+
// e depends on c and d
|
|
206
|
+
const ops = [
|
|
207
|
+
createOp('async-001', 'a', []),
|
|
208
|
+
createOp('async-002', 'b', []),
|
|
209
|
+
createOp('async-003', 'c', ['a']),
|
|
210
|
+
createOp('async-004', 'd', ['b']),
|
|
211
|
+
createOp('async-005', 'e', ['c', 'd']),
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
const waves = buildExecutionWaves(ops, []);
|
|
215
|
+
|
|
216
|
+
expect(waves.length).toBe(3);
|
|
217
|
+
// Wave 0: a, b
|
|
218
|
+
expect(waves[0].operationIds).toContain('async-001');
|
|
219
|
+
expect(waves[0].operationIds).toContain('async-002');
|
|
220
|
+
// Wave 1: c, d
|
|
221
|
+
expect(waves[1].operationIds).toContain('async-003');
|
|
222
|
+
expect(waves[1].operationIds).toContain('async-004');
|
|
223
|
+
// Wave 2: e
|
|
224
|
+
expect(waves[2].operationIds).toEqual(['async-005']);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('handles fire-and-forget operations (null variableName)', () => {
|
|
228
|
+
const ops = [
|
|
229
|
+
createOp('async-001', null, []),
|
|
230
|
+
createOp('async-002', null, []),
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
const waves = buildExecutionWaves(ops, []);
|
|
234
|
+
|
|
235
|
+
expect(waves.length).toBe(1);
|
|
236
|
+
expect(waves[0].operationIds.length).toBe(2);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('assigns sequential wave IDs', () => {
|
|
240
|
+
const ops = [
|
|
241
|
+
createOp('async-001', 'a', []),
|
|
242
|
+
createOp('async-002', 'b', ['a']),
|
|
243
|
+
createOp('async-003', 'c', ['b']),
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const waves = buildExecutionWaves(ops, []);
|
|
247
|
+
|
|
248
|
+
expect(waves[0].id).toBe(0);
|
|
249
|
+
expect(waves[1].id).toBe(1);
|
|
250
|
+
expect(waves[2].id).toBe(2);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('preserves context snapshot in waves', () => {
|
|
254
|
+
const ops = [createOp('async-001', 'a', [])];
|
|
255
|
+
const context = [{ kind: 'variable' as const, name: 'x', value: 1, type: null, isConst: false, source: null, frameName: 'main', frameDepth: 0 }];
|
|
256
|
+
|
|
257
|
+
const waves = buildExecutionWaves(ops, context);
|
|
258
|
+
|
|
259
|
+
expect(waves[0].contextSnapshot).toEqual(context);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('throws on circular dependency', () => {
|
|
263
|
+
const ops = [
|
|
264
|
+
createOp('async-001', 'a', ['b']),
|
|
265
|
+
createOp('async-002', 'b', ['a']),
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
expect(() => buildExecutionWaves(ops, [])).toThrow('Circular dependency');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('handles dependency on non-async variable', () => {
|
|
272
|
+
// If operation depends on a variable that's not an async operation,
|
|
273
|
+
// it should still be able to run
|
|
274
|
+
const ops = [
|
|
275
|
+
createOp('async-001', 'a', ['syncVar']), // syncVar is not async
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
const waves = buildExecutionWaves(ops, []);
|
|
279
|
+
|
|
280
|
+
expect(waves.length).toBe(1);
|
|
281
|
+
expect(waves[0].operationIds).toEqual(['async-001']);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|